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