# 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 %}