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