---
# System prepended metadata

title: 第四屆QC筆試考題
tags: [Solidity 工程師實戰營第 4 期]

---

# 第四屆QC筆試考題

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

## Solidity 基礎

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

```solidity=
contract exam {
    address owner = msg.sender;
}
```

**2. ERC721 合約部屬至 goerli，寫上部屬的合約地址**


```
# 回答區https://goerli.etherscan.io/address/0xf465d66239c1c6f9aab32a12d83c0709e76afb17
```

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

回答寫在這裡

```
# 回答區
簡述： require 包含了判斷式在其中 ，revert 單純只給訊息

require是最常用的錯誤檢查函數，常出現在function的一開始，做為檢查參數是否有錯誤用，由於require為false時會退回剩下的gas，所以才會放在function的一開始，這樣就能節省gas fee了，因為後面的程式碼不會執行。

//require(判斷式, 返回字串)
require(true, "revert error!!!!!")

revert用於已經在複雜邏輯的情況下了，出錯時要直接回覆訊息，由於revert裡面只包含了執行返回錯誤訊息的文字，所以當用在已經有很多判斷式了，在false裡面要丟出錯誤訊息之用，但由於等於中斷了，所以也和require一樣會把剩下的gas fee退回。

//revert(返回字串)
revert NotEnoughFunds()
revert("error!!!!")
```



**4. 試寫出 transfer, call 的不同**
- transfer 會限制 gas 花費量 2,300，可防止重入攻擊、call 可以做更多的事


回答寫在這裡
```
# 回答區
transfer功能就是單純用來轉錢用，
CALL是底層接口，向合約發送訊息，如果要傳送自己的其他DATA，可以用Call，支持各種參數，把參數打包成32字節，發送出去。
```


```solidity=
// call
(bool sent, bytes memory data) = _to.call{value: msg.value}("");

// transfer
_to.transfer(msg.value);
```

**5.承上題，transfer 與 ERC20 的 transfer 有何不同？**

回答寫在這裡
```
# 回答區
 ERC20 的 transfer 是轉移 Token 從自己的轉移給對方
 而一般的 transfer 是轉移 ETH
```

**6. 以下哪個選項為較安全產生隨機數？**
- A：block.timestamp
- B：block.hash
- C：Chainlink VRF 預言機


回答寫在這裡
```
# 回答區
C
```

## Solidity 進階

**1. 請問以下是Solidity允許的數值？（複選）**

- A. 0x1cb89a3833bc070
- B. 129348349684596843
- C. 0.1


回答寫在這裡
```
# 回答區
A B
```

**3. 說明 proxy 概念，如何更新智能合約？**


回答寫在這裡
```
# 回答區 
Proxy 主要為未來可拓展的合約，主要核心為 delegate 及fallback去做更新，
主合約上不會有太多東西，主要是一個容器，尤其他新增的副合約去與其互動，在互動時會需要先確認你為主合約的owner，才能有去與主合約互動的權限。
```

**4. 合約裡的 receive() 用途是？**


回答寫在這裡
```
# 回答區
為solidity 預設的功能，合約可能收到eth時，就會自動呼叫到receive()，同時 Mutability 一定會用 payable

receive() external payable {}
    
```

**5. 做 Dapp 以下是不需要的？**
- A. ethers.js
- B. RPC Provider
- C. 智能合約 ABI
- D. 智能合約地址


回答寫在這裡
```
# 回答區
B
```

**6. 說明 EOA 與 Contract Address 是如何產生的**


回答寫在這裡
```
# 回答區
EOA : 
先由錢包助記詞(由演算法出來的12個單字排列，還有各種語言)，由特定助記詞排列產生特定私鑰，私鑰再使用ECDSA計算出公鑰，做出Keccak-256雜湊，取最右邊20bytes（40位元），以16 進制字串組成，在最前面加上"0x"，此字串就為個人錢包地址 (EOA)

Contract Address :
錢包地址＋nonce = 合約地址(Contract Address)

```

**7. 承上題，兩者有何不同？要如何區分？**


回答寫在這裡
```
# 回答區
EOA 為單純的個人錢包
Contract Address 為合約地址，也可以說是包含著code的錢包，再etherscan上面可以直接看到Contract，

```

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


回答寫在這裡
```
開兩組合約
    a.質押貸幣的合約 StakingJaredToken.sol
    b.銀行的合約 BasicBank.sol

```

```=solidity=

// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

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

contract StakingJaredToken is ERC20 {
    constructor() ERC20("Staking Jared Token", "SJT") {
      mint(msg.sender, 20000);// 建立時會直接給20000個StakingToken
    }

    function mint(address to, uint256 amount) public  {
        _mint(to, amount);
    }
}



```

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

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


contract BasicBank {
    
    IERC20 public stakingToken;// 質押 Staking Token代幣
    uint256 public totalSupply; // 全部質押數量
    mapping(address => uint256) public balanceOf;// 個人質押數量

    uint256 public withdrawDeadline = 10 seconds; // 鎖倉時間

    
    uint256 public rewardRate = 1; // 利息獎勵
    mapping(address => uint256) public rewardOf;// 個人總利息


    // 定存每一筆的資料 (定存金額、定存開始時間、定存結束時間)
    struct Deposit {
        uint256 amount; // 定存金額
        uint256 startTime; // 定存開始時間
        uint256 endTime; // 定存結束時間
    }

    mapping(address => Deposit[]) public depositOf;

    constructor(IERC20 _stakingToken) {
        stakingToken = _stakingToken;
    }

    // 存款
    function deposit(uint256 _amount) external  {
        // 將 stakingToken 移轉到 BasicBank 合約
        stakingToken.transferFrom(msg.sender, address(this), _amount);

        // 紀錄存款數量
        totalSupply += _amount;
        balanceOf[msg.sender] += _amount;

        // 定存資訊
        depositOf[msg.sender].push(
            Deposit({
                amount: _amount,
                startTime: block.timestamp,
                endTime: block.timestamp + withdrawDeadline
            })
        );
    }

    // 解除定存
    function withdraw(uint256 _depositId) external {
        // 檢查：餘額需要大於 0
        require(balanceOf[msg.sender] > 0, "You have no balance to withdraw");

        Deposit[] storage deposits = depositOf[msg.sender];
        // 檢查條件: 必須超過鎖倉期才可以提領
        require(block.timestamp >= deposits[_depositId].endTime, "Withdrawal Period is not reached yet");
        // 檢查條件：定存ID 是否存在
        require(_depositId <= deposits.length, "Deposit ID not exist!!");

        uint256 amount = deposits[_depositId].amount;

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

        // 2) 提款
        stakingToken.transfer(msg.sender, amount);
        totalSupply -= amount;
        balanceOf[msg.sender] -= amount;

        // 3) 移除此筆定存，移除陣列 deposits
        // 陣列往左移
        deposits[_depositId] = deposits[deposits.length - 1];
        deposits.pop();
    }

    // 計算利息
    function getReward(uint256 _depositId) public view returns (uint256) {
        //抓取定存那一筆的資訊 (定存開始時間、定存結束時間)
        uint256 start = depositOf[msg.sender][_depositId].startTime;
        uint256 _amount = depositOf[msg.sender][_depositId].amount;
        return (block.timestamp - start) * rewardRate * _amount;
    }

    // 使用者旗下的所有定存利息
    function getAllReward() public view returns (uint256){
        uint256 N = depositOf[msg.sender].length;
        uint256 allRewards;

        for (uint256 i = 0; i < N; i++) {
            allRewards += getReward(i);
        }

        return allRewards;
    }
}

```


## Solidity 資安相關
**1. 標註程式碼哪幾行可能有資安問題，並說明何種資安問題，並提出解法**
1. pragma solidity ^0.6.0; 版本太低。會有溢出問題，可以把版本提高. 若要繼續用這版本，需加入safemath防止溢出
2. 功能中有跟金錢有關，需要加入onlyowner的功能，只讓擁有者有權限使用，於 withdrawFee，加入此功能


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

pragma solidity ^0.6.0;

contract AuthorizeDepositContract {
    uint256 public fee;
    mapping(address => uint256) public balance;
    bool private _lock = false;
    
    modifier lock {
        require(!_lock);
        _lock = true;
        _;
        _lock = false;
    }

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


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

回答寫在這裡
```solidity=

// SPDX-License-Identifier: MIT
// 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;
        uint256 value;
        bytes data;
        bool executed;
        uint256 numConfirmations;
    }

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

    Transaction[] public transactions;

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

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

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

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

    constructor(address[] memory _owners) {
        require(_owners.length > 0, "owners required");

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

        if(_owners.length%3>0){
            numConfirmationsRequired = _owners.length+1;
        }else {
            numConfirmationsRequired = _owners.length;
        }
    }

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

    function deposit() external payable {}

    function submitTransaction(
        address _to,
        uint256 _value,
        bytes memory _data
    ) public onlyOwner {
        uint256 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(uint256 _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
        );
    }
}

```




**4. 此[合約](https://goerli.etherscan.io/address/0x03C928FFF7609849Ce3d7428804Fd7dE4BE3a643#code) 呼叫 mint 預估 Gas 為多少？請寫下你預估 Gas 的詳細步驟、預估 Gas 是多少數字？**

```
# 回答區

```




**5. 這是一個績點奪獎金遊戲，請找出漏洞在哪，讓你可以跳過原本的通關條件領光合約裡所有的錢**


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

contract KryptoGame {
    mapping(address => int256) public playerPoints;
    uint256 public pointstoWin = 1e10;
    uint256 public prize;
    bool public status;
    address public winner;
    address payable public owner;

    constructor() payable {
        owner = payable(msg.sender);
        status = true;
        prize += msg.value;
    }
    
    modifier onlyowner() {
        require(msg.sender == owner, "You are not owner");
        _;
    }

    function getPrizePool() public view returns (uint) {
        return address(this).balance;
    }

    function addPoints(int256 _points) public {
        require(status == true, "Game is over.");
        require(_points <= 10, "Only allow to add less than 10 points!");
        playerPoints[msg.sender] += _points;
    }

    function winTheGame() public {
        require(uint256(playerPoints[msg.sender]) >= pointstoWin, "Not yet.");
        winner = msg.sender;
        status = false;
        payable(msg.sender).transfer(address(this).balance);
    }

    function BOMB() public onlyowner {
        selfdestruct(owner);
    }
}
```
請描述你是如何破解的，詳細寫下步驟




###### tags: `Solidity 工程師實戰營第 4 期`