# 第五屆QC筆試正式考題 penguin72487 作答 **及格線:6題/10題** ## Solidity 基礎 **1. 如何在部屬合約時,指定 owner,以程式碼舉例** ```solidity= // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; contract exam{ address public immutable owner; constructor(){ owner = msg.sender; } } ``` **2. 請列出 ERC721 safeMint & mint 兩者的差異?** ``` mint與safeMint都是在創建一個新的NFT給某地址,但是差異在safeMint會檢查地址能不能接收ERC721 ``` **3. 試寫出對於操作 ERC20 代幣時,safeTransfer 的功能以及需要使用的時機** ``` 通常是用在傳送對象是合約的時候使用,會檢查合約是否能接受代幣,避免轉移代幣時因接收者無法接收代幣而導致的資金損失。 ``` **4. 試寫出兩種錯誤處理(require, revert),並解釋兩種的不同** 回答寫在這裡 ``` 其實兩種很像 1. require我覺得比較像revert使用上的優化 revert在通常的使用場景要搭配if做狀態的檢查: if (msg.sender != owner) { revert("Only owner can call this function"); } require可以增加可讀性 require(msg.sender == owner,"Only owner can call this function"); 2.require 和revert在Remix的環境也有些差異,通常Remix會幫你檢查require並且提醒你可能失敗,revert不會事前告訴你會失敗。 ``` **5. 試寫出 transfer, call 的不同** ``` # 回答區 transfer:會限制Gas call:比較底層,可以自定義gas上限等其他複雜邏輯,會回傳是否執行成功。成不成功不一定是由ETH網路決定,是由被Call的人決定,轉給EOA通常沒問題,如果是合約可能會借助fallback()讓合約以為真的沒有把前轉出去而失敗。 ``` **6.承上題,transfer 與 ERC20 的 transfer 有何不同?** 回答寫在這裡 ``` transfer:用在轉移原生的ETH ERC20.transfer用在轉移對應的ERC20 token ``` **7. 以下哪個選項為較安全產生隨機數?** - A:block.timestamp - B:block.hash - C:Chainlink VRF 預言機 回答寫在這裡 ``` C ``` ## Solidity 進階 **1. 請問以下是Solidity允許的數值?(複選)** - A. 0x1cb89a3833bc070 - B. 129348349684596843 - C. 0.1 回答寫在這裡 ``` AB ``` **2. 說明 proxy 概念,如何更新智能合約?** 回答寫在這裡 ``` 代理(Proxy)是一種智能合約設計模式,它允許合約的業務邏輯在不變更合約地址的情況下進行升級 可以由一個合約專門匹配什麼功能要去什麼合約,那個什麼合約是可以改的,匹配合約不用改。 ``` **3. 合約裡的 receive() 用途是?** 回答寫在這裡 ``` 用來接收ETH,有function 有 payable並且有收到ETH就會執行receive()來接收ETH。 ``` **4. 做 Dapp 以下是不需要的?** - A. ethers.js - B. RPC Provider - C. 智能合約 ABI - D. 智能合約地址 回答寫在這裡 ``` A ``` **5. 說明 EOA 與 Contract Address 是如何產生的** 回答寫在這裡 ``` EOA 是由私鑰控制的地址。它的地址是由公鑰經過 Keccak-256 哈希後取最後的 20 字節生成的。 Contract Address:合約地址是在合約創建時生成的,它的生成方式是將創建者的地址和創建者的 nonce 進行 RLP 編碼後進行 Keccak-256 哈希,取最後的 20 字節作為合約地址。 ``` **6. 承上題,兩者有何不同?要如何區分?** 回答寫在這裡 ``` EOA地址是由私鑰直接去受授權,進行交易等等,但是合約需要有人去觸發功能,才能進行交易等等,最快的方法可以直接上Etherscan查察,有看到這地址上有寫程式碼就是合約地址了。 ``` ## Solidity 資安相關 **1. 標註程式碼哪幾行可能有資安問題,並說明何種資安問題,並提出解法** ```=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; } } ``` function withdraw忘了上鎖,如果解收地址是攻擊者的合約可以很簡單的利用 fallback()讓遠本合約以為真的錢沒有轉出去,只要 function withdraw(uint256 amount) external lock就可以解決。 ## Solidity 合約應用 **1. 部署一個 ERC721 合約並開源合約,mint 一個 NFT 後轉移到以下地址 `0x6e24f0fF0337edf4af9c67bFf22C402302fc94D3` 請留下 transfer 的 tx hash** ``` # 回答區 tx hash: 0xeb31899dce5d091d9dc5307b085685aa234c47bff10844e85802b8a40f15e236 ``` **2. 請部署一個能從 Chainlink 取得 ETH 價格的合約到 goerli 測試網上並開源合約** ``` # 回答區 合約連結:https://goerli.etherscan.io/address/0x276af5e3ef5e6ada70a2e950f9c85716a63498de ``` **3. 以下是一份名單,共有八個地址,請利用 merkle tree 原理製作出一個 root 以及當 address1 要證明自己在 leaves 中時所需要提交的 proof** ``` 名單 address1: '0xdab15510af1425ba57499C2284cf420001A24D00' address2: '0xA2F7B4eA63be89464bE01FB074d981F5917f53ef' address3: '0x4197b82771654C0cE9049925845a8F942b58ccD0' address4: '0x1b9024CFB1409c13f3B2ee422e9c196442c699E1' address5: '0xEa36d9a9d90b7aFA41404CeCa0c06F9d3A75A8fa' address6: '0x5ea8023bB1cca8aF07bcA9edB8FCE8b8a84C8B3f' address7: '0x4bCae98Ab9912694af894D82658517782203a1dE' address8: '0x2834A1487A841930b8b5b3C5812FB526A0189339' ``` ``` # 回答區 1. root: ... 2. proof: ... # 解題過程... ``` **4. 實作 ERC20 質押某代幣,timelock(固定鎖倉期,自定義), reward (回饋該代幣)** 回答寫在這裡 ```solidity= // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract StakingContract is ReentrancyGuard { ERC20 public token; uint256 public lockTime; struct Stake { uint256 amount; uint256 start; } mapping(address => Stake) public stakes; event Staked(address indexed user, uint256 amount, uint256 start); event Unstaked(address indexed user, uint256 amount); constructor(ERC20 _token, uint256 _lockTime) { token = _token; lockTime = _lockTime; } function stake(uint256 amount) external nonReentrant { token.transferFrom(msg.sender, address(this), amount); stakes[msg.sender].amount += amount; stakes[msg.sender].start = block.timestamp; emit Staked(msg.sender, amount, stakes[msg.sender].start); } function claim() external nonReentrant { require(block.timestamp > stakes[msg.sender].start + lockTime, "Stake is still locked!"); uint256 amount = stakes[msg.sender].amount; stakes[msg.sender].amount = 0; token.transfer(msg.sender, amount); emit Unstaked(msg.sender, amount); } } ``` ###### tags: `Solidity 工程師實戰營第 5 期`