# QC 檢核
## Solidity 基礎
1. 如何在部屬合約時,指定 owner,以程式碼舉例
```solidity=
contract MyToken is ERC20 {
address Owner;
constructor(address _owner) ERC20("MyToken", "MTK") {
Owner = _owner;
}
}
```
2. ERC721 合約部屬至 rinkeby,寫上部屬的合約地址
合約地址:
0xeedf20626c74ea1eba765a61b08245e613a851ef60d356754a20d86c18088ba4
3. 試寫出兩種錯誤處理(requre, revert),並解釋兩種的不同
遇到錯誤,回滾狀態(回到原本狀態),並且扣除/(不)返還手續費:
require(返還未使用手續費 - 有條件檢查)
revert(返回未使用手續費 - 無條件檢查)
#### 用法:
```solidity=
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
/// Not enough funds for transfer. Requested `requested`,
/// but only `available` available.
error NotEnoughFunds();
contract Token {
mapping(address => uint) balances;
function transferThrowError() public {
throw;
}
function transferAssertError() public {
assert(false);
}
function transferRevertError() public {
revert NotEnoughFunds();
}
function requireRevertError() public {
require(false, "revert error");
}
}
```
## 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;
}
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 / 3,
"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 (回饋該代幣)
ERC20代幣:
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract JStakingToken is ERC20 {
constructor() ERC20("JStaking Token", "JST") {}
// 1 wei -> 1 coin
function mint() external payable {
_mint(msg.sender, msg.value);
}
}
```
質押池:
```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 OneAccount;
mapping(address => uint256) public OneTime;
mapping(address => uint256) public OneStackTime;
/* 鎖倉時間 & 回饋
鎖倉期>10s 領回量 = 1.05*質押數
鎖倉期>20s 領回量 = 1.1*質押數
*/
constructor(IERC20 _stakingToken) {
stakingToken = _stakingToken;
}
function deposit(uint256 _amount , uint256 stackingtime) public {
require( _amount > 0,"Stacking money not enough");
require( stackingtime > 0 ,"Stacking time should not be 0");
stakingToken.transferFrom(msg.sender, address(this), _amount); // 存入代幣
OneAccount[msg.sender] += _amount; //紀錄存入金額
OneTime[msg.sender] = block.timestamp; //紀錄存入時間
OneStackTime[msg.sender] = stackingtime; //紀錄欲鎖倉時間
totalSupply += _amount; //更新代幣總數
}
function withdraw(uint256 _amount) public {
require( _amount > 0,"Withdraw money should not be Zero"); //提取的錢不能是0
require(OneAccount[msg.sender] >= _amount, "insufficient token"); //提取的錢要是帳戶內足夠的
require(block.timestamp - OneTime[msg.sender] > OneStackTime[msg.sender], "Stacking time not enough!"); //必須大於鎖倉時間才能領錢
uint256 duration = block.timestamp - OneTime[msg.sender];
//提幣出來 & 鎖倉期達到將獲得獎勵
if (duration > 20){
stakingToken.transfer(msg.sender, _amount * 11 / 10 );
totalSupply -= (_amount * 11 / 10 ) ;
}else if(duration >10){
stakingToken.transfer(msg.sender, _amount * 105 /100 );
totalSupply -= (_amount * 105 / 100 ) ;
}else{
stakingToken.transfer(msg.sender, _amount);
totalSupply -= _amount;
}
}
}
```
## Solidity 整合開發相關
1. 試寫出 contract auto verify 的 相關內容(npm install xxx, hardhat.config.js, npm hardhat xxx)
#### 安裝hardhat套件
`npm install --save @nomiclabs/hardhat-etherscan`
設定 hardhat.config.js
```javascript=
hardhat.config.js
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,
},
};
```
使用Hardhat etherscan 指令驗證合約
`npx hardhat verify --network rinkby [0x合約地址]`
如果出現錯誤訊息:An unexpected error occurred,輸入`npx hardhat clean`再重複驗證,
驗證完後,終端機會顯示以下資訊:
`Successfully verified contract Counter on Etherscan.`
(並附上智能合約連結)
將連結貼上瀏覽器後就可以發現 Etherscan 驗證已完成。
2. 試寫出部屬 ERC721 合約並且先 mint 10 個 NFT 的 hardhat.js script
#### ERC721
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract ERC721J is ERC20, Ownable {
constructor() ERC20("JTKN", "JTK") {}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
```
#### hardhat.js script
```javascript=
const hre = require("hardhat");
async function main() {
const address = '0x....' //for example
const ERC721J = await hre.ethers.getContractFactory("ERC721J");
const eRC721J = await ERC721J.deploy();
await eRC721J.deployed();
await eRC721J.mint(address,10);
console.log("Deployed to:", eRC721J.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
```
3. 列舉 3 種 window.ethereum 事件並說明此事件含意,不包含舉例XD ( ex. accountsChanged)
1.chainChanged
```=javascript
ethereum.on('chainChanged', (chainId) => {
// Handle the new chain.
// Correctly handling chain changes can be complicated.
// We recommend reloading the page unless you have good reason not to.
window.location.reload();
});
```
2.connect
```=javascript
ethereum.on('connect', handler: (connectInfo: ConnectInfo) => void);
```
The MetaMask provider emits this event when it first becomes able to submit RPC requests to a chain. We recommend using a connect event handler and the `ethereum.isConnected()` method in order to determine when/if the provider is connected.
3.disconnect
```=javascript
ethereum.on('disconnect', handler: (error: ProviderRpcError) => void);
```
The MetaMask provider emits this event if it becomes unable to submit RPC requests to any chain. In general, this will only happen due to network connectivity issues or some unforeseen error.
## 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;
}
}
```
以下這段會有可能會有問題:
```=solidity
function withdrawFee() external {
(bool success, ) = msg.sender.call{value: fee}("");
require(success, "Transfer failed.");
fee = 0;
}
```
沒有設置提領條件,可能會受到重入攻擊而提取交易費,有2個做法可以解決:
1.OpenZeppeling 提供的 ReentrancyGuard
2.自寫安全鎖:
使用交易鎖來確保一個帳戶只能同時執行該 function 一次
新增:
```=solidity
mapping(address => boolean) private lock;
```
改寫為:
```=solidity
function withdrawAll() external {
require(lock[msg.sender] == false, "Transaction locked");
lock[msg.sender] = true;
(bool success, ) = msg.sender.call{value: balance[msg.sender]}("");
require(success, "Transfer failed.");
balance[msg.sender] = 0;
lock[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 );
}
}
...
```
將 _pa_like_top10[] 的形式從array改寫成mapping:
`mapping(address => bool) _pa_like_top10;`
改寫成:
```=solidity
if (_pa_like_top10[msg.sender] == swap_data.tokenID){
idx = _pa_like_top10[msg.sender];
_pa_like_top10[msg.sender] = swap_data;
break;
}
```