---
# System prepended metadata

title: QC考試題

---

# QC考試題

## Solidity 基礎

**1. 如何在部屬合約時，指定 owner，以程式碼舉例**
Answer :
``` solidity=
contract MyToken {
    address public _contractOwner;
    address public _owner;
    
    function setOwner( address owner ) public{
        _owner = owner;
    }
    function setContractOwner() public{
        _contractOwner = msg.sender;
    }
}
```

**2. ERC721 合約部屬至 rinkeby，寫上部屬的合約地址**
Answer :
合約地址 : [0xc0f4e3E355e1f58381517C66682bC92eDf575ea5](https://rinkeby.etherscan.io/address/0xc0f4e3e355e1f58381517c66682bc92edf575ea5)

**3. 試寫出兩種錯誤處理(requre, revert)，並解釋兩種的不同**
Answer :
require（返還未使用手續費 - 有條件檢查）
revert（返回未使用手續費 - 無條件檢查）

## Solidity 進階

**1. 試寫出多簽錢包程式碼，調整同意比例(1/3)**

``` solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

contract MultiSigWallet {
    event Deposit(address indexed sender, uint amount, uint balance);
    event SubmitTransaction(
        address indexed owner,
        uint indexed txIndex,
        address indexed to,
        uint value,
        bytes data
    );
    event ConfirmTransaction(address indexed owner, uint indexed txIndex);
    event RevokeConfirmation(address indexed owner, uint indexed txIndex);
    event ExecuteTransaction(address indexed owner, uint indexed txIndex);

    address[] public owners;
    mapping(address => bool) public isOwner;
    uint public numConfirmationsRequired;

    struct Transaction {
        address to;
        uint value;
        bytes data;
        bool executed;
        uint numConfirmations;
    }

    // mapping from tx index => owner => bool
    mapping(uint => mapping(address => bool)) public isConfirmed;

    Transaction[] public transactions;

    modifier onlyOwner() {
        require(isOwner[msg.sender], "not owner");
        _;
    }

    modifier txExists(uint _txIndex) {
        require(_txIndex < transactions.length, "tx does not exist");
        _;
    }

    modifier notExecuted(uint _txIndex) {
        require(!transactions[_txIndex].executed, "tx already executed");
        _;
    }

    modifier notConfirmed(uint _txIndex) {
        require(!isConfirmed[_txIndex][msg.sender], "tx already confirmed");
        _;
    }

    constructor(address[] memory _owners, uint _numConfirmationsRequired) {
        require(_owners.length > 0, "owners required");
        require(
            _numConfirmationsRequired > 0 &&
                _numConfirmationsRequired <= _owners.length,
            "invalid number of required confirmations"
        );

        for (uint i = 0; i < _owners.length; i++) {
            address owner = _owners[i];

            require(owner != address(0), "invalid owner");
            require(!isOwner[owner], "owner not unique");

            isOwner[owner] = true;
            owners.push(owner);
        }

        numConfirmationsRequired = _numConfirmationsRequired / 3;
    }

    receive() external payable {
        emit Deposit(msg.sender, msg.value, address(this).balance);
    }

    function submitTransaction(
        address _to,
        uint _value,
        bytes memory _data
    ) public onlyOwner {
        uint txIndex = transactions.length;

        transactions.push(
            Transaction({
                to: _to,
                value: _value,
                data: _data,
                executed: false,
                numConfirmations: 0
            })
        );

        emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data);
    }

    function confirmTransaction(uint _txIndex)
        public
        onlyOwner
        txExists(_txIndex)
        notExecuted(_txIndex)
        notConfirmed(_txIndex)
    {
        Transaction storage transaction = transactions[_txIndex];
        transaction.numConfirmations += 1;
        isConfirmed[_txIndex][msg.sender] = true;

        emit ConfirmTransaction(msg.sender, _txIndex);
    }

    function executeTransaction(uint _txIndex)
        public
        onlyOwner
        txExists(_txIndex)
        notExecuted(_txIndex)
    {
        Transaction storage transaction = transactions[_txIndex];

        require(
            transaction.numConfirmations >= numConfirmationsRequired,
            "cannot execute tx"
        );

        transaction.executed = true;

        (bool success, ) = transaction.to.call{value: transaction.value}(
            transaction.data
        );
        require(success, "tx failed");

        emit ExecuteTransaction(msg.sender, _txIndex);
    }

    function revokeConfirmation(uint _txIndex)
        public
        onlyOwner
        txExists(_txIndex)
        notExecuted(_txIndex)
    {
        Transaction storage transaction = transactions[_txIndex];

        require(isConfirmed[_txIndex][msg.sender], "tx not confirmed");

        transaction.numConfirmations -= 1;
        isConfirmed[_txIndex][msg.sender] = false;

        emit RevokeConfirmation(msg.sender, _txIndex);
    }

    function getOwners() public view returns (address[] memory) {
        return owners;
    }

    function getTransactionCount() public view returns (uint) {
        return transactions.length;
    }

    function getTransaction(uint _txIndex)
        public
        view
        returns (
            address to,
            uint value,
            bytes memory data,
            bool executed,
            uint numConfirmations
        )
    {
        Transaction storage transaction = transactions[_txIndex];

        return (
            transaction.to,
            transaction.value,
            transaction.data,
            transaction.executed,
            transaction.numConfirmations
        );
    }
}
```

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

``` 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 整合開發相關

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

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

hardhat.config.js
``` 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
```

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

* ERC721
``` 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);
    }
}

```
* 部屬
``` 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);
  });
```

**3. 列舉 3 種 window.ethereum 事件並說明此事件含意，不包含舉例XD ( ex. accountsChanged)**

connect : provider 連接到鏈上。
disconnect : 取消 provider 連接鏈
networkChanged : 當連接區塊鏈ID變化時觸發。


## Solidity 資安相關

**標註程式碼哪幾行可能有資安問題，並說明何種資安問題，並提出解法**

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

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

contract AuthorizeDepositContract is ReentrancyGuard {
    using SafeMath for uint256;
    uint256 public fee;
    mapping(address => uint256) public balance;

    function deposit() external payable {
        uint256 depositFee = msg.value / 100;
        balance[msg.sender] += msg.value - depositFee;
        fee += depositFee;
    }

    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 withdrawFee() external {
        (bool success, ) = msg.sender.call{value: fee}("");
        require(success, "Transfer failed.");
        fee = 0;
    }
}
```

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

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

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

contract AuthorizeDepositContract is ReentrancyGuard {
    using SafeMath for uint256;
    uint256 public fee;
    mapping(address => uint256) public balance;
    
    mapping(address => bool) private islock;

...
    function withdrawFee() external {
        require(islock[msg.sender] == false, "is lock");
        islock[msg.sender] = true;
        
        (bool success, ) = msg.sender.call{value: fee}("");
        require(success, "Transfer failed.");
        fee = 0;
        
        islock[msg.sender] = false;
    }
}
```

## Solidity 節省 Gasfee 相關
**嘗試閱讀以下程式碼，如何寫出更節省Gas fee 的方法**

```=solidity
...
function parseTopLike( uint256 tokenID ) internal {
        int idx = 0;
        PAData memory tmp_data;
        PAData memory swap_data;
        swap_data.owner = ownerOf( tokenID );
        swap_data.tokenID = tokenID;
        swap_data.like = getLike( tokenID );
        swap_data.metaURL = tokenURI( tokenID);

        for( int i = 0 ; uint( i ) < _pa_like_top10.length ; i++ )
        {
            if( swap_data.tokenID == _pa_like_top10[uint(i)].tokenID ){
                idx = i;
                _pa_like_top10[uint(i)] = swap_data;
                break;
            }
            idx += 1;
        }

        for( int i = (idx - 1) ; i >= 0 ; i-- )
        {
            if( uint(idx) == _pa_like_top10.length )
            {
                if( swap_data.like > _pa_like_top10[uint(i)].like )
                {
                    tmp_data = _pa_like_top10[uint(i)];

                    _pa_like_top10[uint(i)] = swap_data;

                    idx = i;
                }
            }
            else if( _pa_like_top10[uint(idx)].like > _pa_like_top10[uint(i)].like )
            {
                tmp_data = _pa_like_top10[uint(i)];

                _pa_like_top10[uint(i)] = _pa_like_top10[uint(idx)];

                _pa_like_top10[uint(idx)] = tmp_data;

                idx = i;
            }
        }

        if( _pa_like_top10.length < 10 &&  uint(idx) == _pa_like_top10.length )
        {
            _pa_like_top10.push( swap_data );
        }
    }
...
```

```solidity=
mapping( uint256 => uint256 ) private _likeTop10_idx;

function getTop10LikeIdx( uint256 tokenID ) internal view returns ( uint256 ){
    if( _likeTop10_idx[tokenID] > 0 )
        return _likeTop10_idx[tokenID];
    else
    {
        return _pa_like_top10.length;
    }
}

function parseTopLike( uint256 tokenID ) internal {
    uint256 idx;
    PAData memory tmp_data;
    PAData memory swap_data;
    swap_data.owner = ownerOf( tokenID );
    swap_data.tokenID = tokenID;
    swap_data.like = getLike( tokenID );
    swap_data.metaURL = tokenURI( tokenID);
    
    idx = getTop10Idx( tokenID );
    
    for( int i = (idx - 1) ; i >= 0 ; i-- )
    {
        if( uint(idx) == _pa_like_top10.length )
        {
            if( swap_data.like > _pa_like_top10[uint(i)].like )
            {
                tmp_data = _pa_like_top10[uint(i)];

                _pa_like_top10[uint(i)] = swap_data;

                idx = i;
            }
        }
        else if( _pa_like_top10[uint(idx)].like > _pa_like_top10[uint(i)].like )
        {
            tmp_data = _pa_like_top10[uint(i)];

            _pa_like_top10[uint(i)] = _pa_like_top10[uint(idx)];

            _pa_like_top10[uint(idx)] = tmp_data;

            idx = i;
        }
    }

    if( _pa_like_top10.length < 10 &&  uint(idx) == _pa_like_top10.length )
    {
        _pa_like_top10.push( swap_data );
    }
}
```