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