---
# System prepended metadata

title: KryptoCamp 模擬考題

---

# KryptoCamp 模擬考題

<style>
    .question {
      color: red;
    }
    .answer {
      color: blue;   
    }
</style>

#### <span class="question">及格線：6題/10題</span>

## Solidity 基礎
Q1. 列舉五種 Solidity 的型別並且寫出實際例子
### <span class="answer">Answer</span>
``` solidity=
contract JasonKryptoCampTest {
    // 布林，以true, false表示   
    bool public _isBool = false;
    
    // interger
    // uint256, uint32.... 正整數 
    // int256, int32.... 可以有負整數
    uint256 public _ubank = 0;
    
    //字串可以轉為 bytes型態，會更節省 gas
    string public _sName = "Jason";
    bytes32 public _bName = "Jason";
    
    address public _aOwner = 0xf8789AB568eC6155Eb3F9056144346590dbF14cc;
    
}
```

Q2. 寫出一 function 可接受 eth value 並且 return 自定義結果
### <span class="answer">Answer</span>
``` solidity=
    function addMyBank() public payable returns ( uint256 ){
        _bank += msg.value;
        return _bank;
    }
```

Q3. ERC20 合約部屬至 rinkeby，寫上部屬的合約地址
### <span class="answer">Answer</span>
[contract address = 0x97c00D241b7A9544D796E8a1D0280DFf6Df97287](https://rinkeby.etherscan.io/address/0x97c00D241b7A9544D796E8a1D0280DFf6Df97287)

## Solidity 進階
Q1. 以文字描述為智能合約增加流動性的流程

### <span class="answer">Answer</span>
* <span class="answer">要先approve多少ERC20的Token給交易所使用。</span>
* <span class="answer">增加流動性時，記得要給錢(msg.value)</span>

Q2. 實作 ERC20 質押某代幣，timelock（固定鎖倉期，自定義）, reward （回饋該代幣）

<span class="answer">Answer</span>
``` solidity=
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract BaseBank {
    //質押代幣ERC20的interface
    IERC20 private _stakingToken;

    //質押的總數量
    uint256 private _totalSupply = 0;

    //個人質押數量
    mapping( address => uint256 ) private _balanceOf;

    //鎖質押 10 秒
    uint256 private _lockBaseTime = 10 seconds;
    //鎖質押的開始時間
    mapping( address => uint256 ) private _lockTimeStart;
    //鎖質押的結束時間
    mapping( address => uint256 ) private _lockTimeEnd;

    //回饋獎勵
    uint256 private _rewardRate = 1;
    //個人回饋的總獎勵
    mapping( address => uint256 ) private _rewardOf;

    //建構子
    constructor( IERC20 stakingToken ) {
        _stakingToken = stakingToken;
    }

    //存入多少代幣
    function deposit( uint256 coin ) public {
        //轉給合約多少代幣
        _stakingToken.transferFrom( msg.sender, address(this), coin );
        //加總合約代幣
        _totalSupply += coin;
        //加總合約的個人代幣
        _balanceOf[ msg.sender ] += coin;

        //設定個人存入的開始時間
        _lockTimeStart[ msg.sender ] = block.timestamp;
        //設定個人存入的結束時間
        _lockTimeEnd[ msg.sender ] = block.timestamp + _lockBaseTime;
    }

    function withdraw( uint256 coin ) external {
        //判斷取出時間是否大於存入的結束時間
        require( block.timestamp >= _lockTimeEnd[ msg.sender ] , "lock time..." );
        //判斷個人目前在合約的代幣是否比取出的代幣多
        require( _balanceOf[ msg.sender ] >= coin, "withdraw too much" );       

        //加總個人的回饋獎勵
        _rewardOf[ msg.sender ] += getReward();

        //從合約轉移代幣給個人
        _stakingToken.transfer( msg.sender, coin );
        //合約的代幣減少總量
        _totalSupply -= coin;
        //個人的合約代幣減少總量
        _balanceOf[ msg.sender ] -= coin;        
    }

    //回饋方式
    function getReward() public view returns ( uint256 ) {
        uint256 reward = block.timestamp - _lockTimeEnd[ msg.sender ];
        return reward * _rewardRate * _balanceOf[ msg.sender ];
    }

    //查看個人目前總回饋量
    function getTotalRewardOf( address owner ) public view returns ( uint256 ) {
        return _rewardOf[ owner ];
    }
    //查看目前合約代幣總量
    function getTotalSupply() public view returns( uint256 ){
        return _totalSupply;
    }
    //查看目前個人在合約的代幣量
    function getBalanceOf( address owner ) public view returns( uint256 ){
        return _balanceOf[ owner ];
    }
}
```


## Solidity 整合開發相關
Q1. 試說明如何整合 VSCode, 以及 Remix 的開發環境

### <span class="answer">Answer</span>
* <span class="answer">Remixd: 連結本地端檔案</span>
* <span class="answer">Remix 亦可串接 Hardhat 本地鏈</span>
* <span class="answer">安裝：</span>
```
npm install -g @remix-project/remixd
```
* <span class="answer">使用：</span>
```
remixd -s path-to-your-project
```
* <span class="answer">進 Remix 點擊以下按鈕即可串連本地端: Connect to Localhost</span>
* <span class="answer">並可在 Remix DEPLOY & RUN TRANSACTIONS，將環境改成 Hardhat 區塊鏈</span>

Q2. 試寫出 contract auto verify 的 相關內容(npm install xxx, hardhat.config.js, npm hardhat xxx)

### <span class="answer">Answer</span>

```
npm install --save-dev hardhat
npm install --save dotenv
npm install --save @nomiclabs/hardhat-etherscan
```

* <span class="answer">hardhat.config.js</span>
``` solidity=
require("@nomiclabs/hardhat-ethers");
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-etherscan");

require("dotenv").config();

const PRIVATE_KEY = process.env.PRIVATE_KEY;
const ENDPOINT_URL = process.env.ENDPOINT_URL;

module.exports = {
  solidity: "0.8.4",
  networks: {
    rinkeby: {
      url: ENDPOINT_URL,
      accounts: [`0x${PRIVATE_KEY}`],
    },
  },
  etherscan: {
    apiKey: process.env.ETHERSCAN_API_KEY,
  },
};
```

```
npx hardhat run scripts/deploy.js --network rinkeby
```

Q3. 試寫出部屬 ERC721 合約並且先 mint 10 個 NFT 的 hardhat.js script

### <span class="answer">Answer</span>
* <span class="answer">ERC721</span>
``` solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract TestToken is ERC721, Ownable {
    constructor( uint256 number ) ERC721("TestToken", "TT") {
        for( uint i = 0 ; i < number ; i++ )
        {
            safeMint( msg.sender, i );
        }
    }

    function safeMint(address to, uint256 tokenId) public onlyOwner {
        _safeMint(to, tokenId);
    }
}

```
* <span class="answer">部屬</span>
``` solidity=
const hre = require("hardhat");

async function main() {
  const TestToken = await hre.ethers.getContractFactory("TestToken");
  const testtoken = await TestToken.deploy( 10 );

  await testtoken.deployed();

  console.log("Deployed to:", testtoken.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
```


## Solidity 資安相關
標註程式碼第幾行可能有資安問題，並說明何種資安問題，並提出解法

``` solidity=
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract DepositContract {
    using SafeMath for uint256;
    mapping(address => uint256) public balance;

    function deposit() external payable {
        balance[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) external {
        require(balance[msg.sender] >= amount, "Account balance is not enough");
        balance[msg.sender] -= amount;

        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed.");
    }

    function withdrawAll() external {
        (bool success, ) = msg.sender.call{value: balance[msg.sender]}("");
        require(success, "Transfer failed.");

        balance[msg.sender] = 0;
    }
}
```
### <span class="answer">Answer</span>

* <span class="answer">在withdrawAll函式中, 使用msg.sender.call的方式提款, 攻擊者可以使用 Re-Entrancy Attack 無限提款</span>
* <span class="answer">使用mapping(address => bool) islock確保呼叫時，不會被Re-Entrancy</span>

``` solidity=
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract DepositContract {
    using SafeMath for uint256;
    mapping(address => uint256) public balance;
    
    mapping(address => bool) private islock;

    function deposit() external payable {
        balance[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) external {
        require(balance[msg.sender] >= amount, "Account balance is not enough");
        //鎖倉
        require(islock[msg.sender] == false, "is lock");
        islock[msg.sender] = true;
        
        balance[msg.sender] -= amount;

        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed.");
        
        islock[msg.sender] = false;
    }

    function withdrawAll() external {
        //鎖倉
        require(islock[msg.sender] == false, "is lock");
        islock[msg.sender] = true;
        
        (bool success, ) = msg.sender.call{value: balance[msg.sender]}("");
        require(success, "Transfer failed.");

        balance[msg.sender] = 0;
        
        islock[msg.sender] = false;
    }
}
```

## Solidity 節省 Gasfee 相關
嘗試閱讀以下程式碼，如何寫出更節省Gas fee 的方法
``` solidity=
address[] whitelistedAddresses;

function isWhitelisted(address _user) public view returns (bool) {
    for (uint i = 0; i < whitelistedAddresses.length; i++) {
      if (whitelistedAddresses[i] == _user) {
          return true;
      }
    }
    return false;
  }
```
完整程式碼
https://etherscan.io/address/0xae122962331c2b02f837b2aa501d3c5d903ed28a#code

### <span class="answer">Answer</span>
<span class="answer">使用mapping來解決這問題</span>
``` solidity=
  mapping( address => bool ) private iswhitelistedAddresses;

  function isWhitelisted( address _user ) public view returns (bool) {
    return iswhitelistedAddresses[ _user ];
  }

```
