# 第四屆QC筆試考題
**及格線:6題/10題**
## Solidity 基礎
**1. 如何在部屬合約時,指定 owner,以程式碼舉例**
```solidity=
contract exam {
address owner;
constructor(address _owner) {
owner = _owner;
}
}
```
**2. ERC721 合約部屬至 goerli,寫上部屬的合約地址**
回答寫在這裡
``` solidity
# 回答區
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract ShenNFT is ERC721 {
// TODO: 改掉 name, symbol
constructor() ERC721("ShenNFT", "SN") {}
function mint(uint256 tokenId) public {
_safeMint(msg.sender, tokenId);
}
}
Goerli ERC721合約網址
https://goerli.etherscan.io/tx/0x1170c3b5d984aca9f3b670403ef623133aa0a7ac71a7ce073e4db6e59f7dd179
Goerli ERC721合約地址
0x72e49de4d61d60e531777d7d4120a1d77e0dd8d8
```
**3. 試寫出兩種錯誤處理(require, revert),並解釋兩種的不同**
回答寫在這裡
```solidity!
# 回答區
//require
require(msg.sender == owner);
//revert
if(msg.sender != owner){
revert();
}
require和revert 用來檢查較不嚴重的錯誤,可以退回為使用到的 gas費用
require和rever基本上差不多,但revert沒有包括狀態檢查
```
**4. 試寫出 transfer, call 的不同**
- transfer 會限制 gas 花費量 2,300,可防止重入攻擊、call 可以做更多的事
回答寫在這裡
```
# 回答區
transfer 如果轉帳失败,抛出異常
函数原型:<address payable>.transfer(uint256 amount)
gas花費量2300
call 如果轉帳失败,只會返回False,不會中止執行
gas花費量沒有限制
```
```solidity=
// call
(bool sent, bytes memory data) = _to.call{value: msg.value}("");
// transfer
_to.transfer(msg.value);
```
**5.承上題,transfer 與 ERC20 的 transfer 有何不同?**
回答寫在這裡
```
# 回答區
ERC20 transfer 是從自己轉帳到對方,而轉移的是token,ERC721的概念也是一樣,轉移的是NFT
transfer 是轉ETH、BTC等等原生幣種
```
**6. 以下哪個選項為較安全產生隨機數?**
- A:block.timestamp
- B:block.hash
- C:Chainlink VRF 預言機
回答寫在這裡
```
# 回答區
Chainlink VRF 預言機
```
## Solidity 進階
**1. 請問以下是Solidity允許的數值?(複選)**
- A. 0x1cb89a3833bc070
- B. 129348349684596843
- C. 0.1
回答寫在這裡
```
# 回答區
A. 0x1cb89a3833bc070
經由SSH
```
**3. 說明 proxy 概念,如何更新智能合約?**
回答寫在這裡
```
# 回答區
proxy概念:合約部署後往往無法再更動,但利用proxy-implementation 的架構,仍保有一定彈性的作法
有兩個合約(a、b)一個是登入的,一個是修改的合約
a Contract (登入合約)
b Contract (資料合約)
=>只能被 a 呼叫
=>更新合約時,更新 b 的 owner 並通知使用者用新的 a
```
**4. 合約裡的 receive() 用途是?**
回答寫在這裡
```
# 回答區
合約一定能拿得到幣種,就會用receive()來拿
```
**5. 做 Dapp 以下是不需要的?**
- A. ethers.js
- B. RPC Provider
- C. 智能合約 ABI
- D. 智能合約地址
回答寫在這裡
```
# 回答區
A. ethers.js
它是用來呼叫ether.js,然操縱錢包的
```
**6. 說明 EOA 與 Contract Address 是如何產生的**
回答寫在這裡
```
# 回答區
EOA是由 => 私鑰演算法產生地址,SSH概念
Contract Address是由 => 錢包和nonce所組成的
```
**7. 承上題,兩者有何不同?要如何區分?**
回答寫在這裡
```
# 回答區
EOA和Contract Address,如何判斷,他們差在只有一個有合約程式,另一個沒有
EOA是錢包而已
Contract Address 是有自己寫的合約
```
**8. 實作 ERC20 質押某代幣,timelock(固定鎖倉期,自定義), reward (回饋該代幣)**
回答寫在這裡
``` solidity
# 回答區
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Bank{
IERC20 public stakingToken;
uint public totalSupply;
mapping(address => uint256) public balanceOf;
//鎖倉時間
uint256 public lockDuration = 5 seconds;
mapping(address => uint256) public lockStarts;
mapping(address => uint256) public lockEnds;
// struct Deposite {
// uint256 amount;
// uint256 start;
// uint256 end;
// }
//mapping(address => Deposite[]) public depositesOf;
//獎勵機制
uint public reward = 1;
mapping(address => uint) public rewardToken;
constructor(IERC20 _stakingToken){
stakingToken = _stakingToken;
}
//存入
function deposite(uint _amount) external{
stakingToken.transferFrom(msg.sender, address(this), _amount); //轉移代幣
totalSupply += _amount;
balanceOf[msg.sender] += _amount;
//鎖倉時間
lockStarts[msg.sender] = block.timestamp;
lockEnds[msg.sender] = block.timestamp + lockDuration;
// depositesOf[msg.sender].push(
// Deposite({
// amount: _amount,
// start: block.timestamp,
// end: block.timestamp + lockDuration
// })
// );
}
// //提出
// function withdraw(uint _depositeId) external{
// require(block.timestamp > lockEnds[msg.sender], "withdraw to soon");
//require(_amount <= balanceOf[msg.sender], "insufficient token");
//Deposite[] storage deposites = depositesOf[msg.sender];
//uint256 amount = deposites[_depositeId].amount;
//require(_depositeId < deposites.length, "Deposite ID does not exist");
//require(block.timestamp >= deposites[_depositeId].end,"withdraw too soon");
// rewardToken[msg.sender] = getreward(_depositeId);
// stakingToken.transfer(msg.sender, amount); //轉移代幣
// totalSupply -= amount;
// balanceOf[msg.sender] -= amount;
// //remove deposite
// uint256 lastDepositeId = deposites.length -1;
// deposites[_depositeId] = deposites[lastDepositeId];
// deposites.pop();
// }
//獎勵
function getreward(/*uint _depositeId*/) public view returns(uint256){
uint256 duration = block.timestamp - lockStarts[msg.sender];
return duration * reward * balanceOf[msg.sender];
// uint256 start = depositesOf[msg.sender][_depositeId].start;
// uint256 amount = depositesOf[msg.sender][_depositeId].amount;
//return(amount * (block.timestamp - start) * reward);
}
}
```
## Solidity 資安相關
**1. 標註程式碼哪幾行可能有資安問題,並說明何種資安問題,並提出解法**
```=solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract AuthorizeDepositContract {
uint256 public fee;
mapping(address => uint256) public balance;
bool private _lock = false;
modifier lock {
require(!_lock);
_lock = true;
_;
_lock = false;
}
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 lock {
(bool success, ) = msg.sender.call{value: fee}("");
require(success, "Transfer failed.");
fee = 0;
}
}
```
```
ANS
我看到的資安問題是,如果假如第一次存了錢進去,並且提領出來,他是以Lock去鎖的,可是如果第一次有存進去後,第二次第三次等等,都可以以同樣的msg.sender去執行,這樣第二次之後的錢,都可以無限去領錢,不需要什麼證明
```
**2. 試寫出多簽錢包程式碼,調整同意比例(1/3)**
回答寫在這裡
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract MultiSigWallet {
using SafeMath for uint256;
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; //陣列,他可以設定多少個owner在transact裡面寫成["","","",""]
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) {//owners管理者 /未來同意多少人可以做
require(_owners.length > 0, "owners required");
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 = owners.length.div(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
);
}
}
```
### 作法
把numConfirmationsRequired取消掉,因為調整比例改成owner的1/3,因此把numConfirmationsRequired使用safemath給除以3
**3. 標註程式碼哪幾行可能有資安問題,並說明何種資安問題,並提出解法**
```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;
}
```
由於 withdrawFee 沒有限制誰可以領取手續費,因此任何人都可以領走
解法:
使用 Openzeppelin Ownable.sol 來指定權限,讓部署合約的人才能提款
* onlyOwner 可以限制只有部署合約的人才能呼叫該 function
``` solidity
import "@openzeppelin/contracts/access/Ownable.sol";
function withdrawFee() external onlyOwner {
(bool success, ) = msg.sender.call{value: fee}("");
require(success, "Transfer failed.");
fee = 0;
}
```
**4. 此[合約](https://goerli.etherscan.io/address/0x03C928FFF7609849Ce3d7428804Fd7dE4BE3a643#code) 呼叫 mint 預估 Gas 為多少?請寫下你預估 Gas 的詳細步驟、預估 Gas 是多少數字?**
```
# 回答區
```
**5. 這是一個績點奪獎金遊戲,請找出漏洞在哪,讓你可以跳過原本的通關條件領光合約裡所有的錢**
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
contract KryptoGame {
mapping(address => int256) public playerPoints;
uint256 public pointstoWin = 1e10;
uint256 public prize;
bool public status;
address public winner;
address payable public owner;
constructor() payable {
owner = payable(msg.sender);
status = true;
prize += msg.value;
}
modifier onlyowner() {
require(msg.sender == owner, "You are not owner");
_;
}
function getPrizePool() public view returns (uint) {
return address(this).balance;
}
function addPoints(int256 _points) public {
require(status == true, "Game is over.");
require(_points <= 10, "Only allow to add less than 10 points!");
playerPoints[msg.sender] += _points;
}
function winTheGame() public {
require(uint256(playerPoints[msg.sender]) >= pointstoWin, "Not yet.");
winner = msg.sender;
status = false;
payable(msg.sender).transfer(address(this).balance);
}
function BOMB() public onlyowner {
selfdestruct(owner);
}
}
```
請描述你是如何破解的,詳細寫下步驟
constructor() payable {
owner = payable(msg.sender);
status = true;
prize += msg.value;
}
從payable看到
###### tags: `Solidity 工程師實戰營第 4 期`