---
# System prepended metadata

title: QC 考試題

---

# QC 考試題

**及格線：6 題/10 題**

## Solidity 基礎

**1. 如何在部屬合約時，指定 owner，以程式碼舉例**

```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Ballot {
    address private _owner;
constructor()
  {
    _owner = msg.sender;
  }
}
```

**2. ERC721 合約部屬至 rinkeby，寫上部屬的合約地址**
https://rinkeby.etherscan.io/tx/0x437e5f2e0516d19c88fb834e8840cd50d642ade8f1782a7b9d1c35f628082a2f
合約地址: 0xf61d3c20fec4f37695cdf3b67806d234c1bbd28d

**3. 試寫出兩種錯誤處理(requre, revert)，並解釋兩種的不同**

- require（返還未使用手續費 - 有條件檢查）
- revert（返回未使用手續費 - 無條件檢查）

## Solidity 進階

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

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract MyToken  {
    mapping(address=>bool)public decisions;
    mapping(address=>bool)public joined;
    uint public approveds = 0;
    uint public ratioA = 1; // (`1` / 3)
    uint public ratioB = 3; // (1 / `3`)
    uint public accountAmount = 0;
    bool public transacted = false;
    function join()public{
        require(joined[msg.sender]==false);
        joined[msg.sender]=true;
        accountAmount++;
    }
    function approve()public{
        require(joined[msg.sender]);
        require(decisions[msg.sender]!=true);
        decisions[msg.sender] = true;
        approveds++;
    }
    function reject()public{
        require(joined[msg.sender]);
        require(decisions[msg.sender]!=false);
        decisions[msg.sender] = false;
        approveds--;
    }
    function exec() public {
        require (transacted==false);
        require(accountAmount + approveds > 0);
        require(approveds * ratioB >= accountAmount * ratioA);
        transacted = true;
    }
}
```

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

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

pragma solidity ^0.8.0;

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

contract BasicBank {
    // 質押代幣
    IERC20 public stakingToken;

    // 質押數量
    uint256 public totalSupply;

    // 個人質押數量
    mapping(address => uint256) public balanceOf;

    // 鎖倉時間
    uint256 public lockDuration = 10 seconds;
    mapping(address => uint256) public lockStartOf;
    mapping(address => uint256) public lockEndOf;

    // 獎勵
    uint256 public rewardRate = 1;
    mapping(address => uint256) public rewardOf;

    constructor(IERC20 _stakingToken) {
        stakingToken = _stakingToken;
    }

    function deposit(uint256 _amount) public {
        stakingToken.transferFrom(msg.sender, address(this), _amount); // 轉移代幣
        totalSupply += _amount; // 紀錄質押數量
        balanceOf[msg.sender] += _amount; // 紀錄個人質押數量

        // 紀錄鎖倉時間
        lockStartOf[msg.sender] = block.timestamp;
        lockEndOf[msg.sender] = block.timestamp + lockDuration;
    }

    function withdraw(uint256 _amount) public {
        require(block.timestamp > lockEndOf[msg.sender], "withdraw too soon");
        require(_amount <= balanceOf[msg.sender], "insufficient token");

        // 獲得獎勵
        rewardOf[msg.sender] += getReward();

        stakingToken.transfer(msg.sender, _amount);
        totalSupply -= _amount;
        balanceOf[msg.sender] -= _amount;
    }

    // 獎勵
    function getReward() public view returns (uint256) {
        uint256 duration = block.timestamp - lockStartOf[msg.sender];
        return duration * rewardRate * balanceOf[msg.sender];
    }
}
```

## Solidity 整合開發相關

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

```console
npm install --save @nomiclabs/hardhat-etherscan
npx hardhat verify --network rinkeby 0x智能合約地址
```

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

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MyToken is ERC721, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;
    constructor() ERC721("MyToken", "MTK") {}
    function safeMint(address to) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
    }
}

```

```javascript
const hre = require('hardhat');
async function main() {
  const Contract = await hre.ethers.getContractFactory('MyToken');
  const contract = await Contract.deploy();
  await contract.deployed();
  const Account_19 = '0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199';
  for (let i = 0; i < 10; i++) {
    console.log({ i });
    await contract.safeMint(Account_19);
  }
}
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
```

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

> 這邊: https://docs.metamask.io/guide/ethereum-provider.html#events

- connect: emits this event when it first becomes able to submit RPC requests to a chain
- disconnect: emits this event if it becomes unable to submit RPC requests to any chain
- accountsChanged: emits this event whenever the return value of the eth_accounts RPC method changes

## Solidity 資安相關

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

https://blog.openzeppelin.com/reentrancy-after-istanbul/

> 部署一個 B 合約，用 B 合約來呼叫此合約方法，存錢進去，再提領
> 並在 B 合約的 `receive()` 再度去呼叫題目合約的提款
> 因此還未將 `balance[msg.sender]` 改為 0，造成重入攻擊
> 防範方式：修改方式可使用`OpenZeppeling ReentrancyGuard` 來防範

```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;
    }
}
```

## Solidity 節省 Gasfee 相關

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

> 使用 mapping 避免 loop array

```=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 );
        }
    }
...
```

**完整程式碼**
https://github.com/Krypto-Camp/batch3-final-project-team-2/blob/main/hardhat/contracts/TaoPai721.sol

{%hackmd ehVYaUjsT3KFg_bTSfifBg %}
{%hackmd r2ZRQxmqQ8OyB3efyd6THg %}
