# 第五屆QC筆試正式考題 penguin72487 作答
**及格線:6題/10題**
## Solidity 基礎
**1. 如何在部屬合約時,指定 owner,以程式碼舉例**
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract exam{
address public immutable owner;
constructor(){
owner = msg.sender;
}
}
```
**2. 請列出 ERC721 safeMint & mint 兩者的差異?**
```
mint與safeMint都是在創建一個新的NFT給某地址,但是差異在safeMint會檢查地址能不能接收ERC721
```
**3. 試寫出對於操作 ERC20 代幣時,safeTransfer 的功能以及需要使用的時機**
```
通常是用在傳送對象是合約的時候使用,會檢查合約是否能接受代幣,避免轉移代幣時因接收者無法接收代幣而導致的資金損失。
```
**4. 試寫出兩種錯誤處理(require, revert),並解釋兩種的不同**
回答寫在這裡
```
其實兩種很像
1. require我覺得比較像revert使用上的優化
revert在通常的使用場景要搭配if做狀態的檢查:
if (msg.sender != owner) {
revert("Only owner can call this function");
}
require可以增加可讀性
require(msg.sender == owner,"Only owner can call this function");
2.require 和revert在Remix的環境也有些差異,通常Remix會幫你檢查require並且提醒你可能失敗,revert不會事前告訴你會失敗。
```
**5. 試寫出 transfer, call 的不同**
```
# 回答區
transfer:會限制Gas
call:比較底層,可以自定義gas上限等其他複雜邏輯,會回傳是否執行成功。成不成功不一定是由ETH網路決定,是由被Call的人決定,轉給EOA通常沒問題,如果是合約可能會借助fallback()讓合約以為真的沒有把前轉出去而失敗。
```
**6.承上題,transfer 與 ERC20 的 transfer 有何不同?**
回答寫在這裡
```
transfer:用在轉移原生的ETH
ERC20.transfer用在轉移對應的ERC20 token
```
**7. 以下哪個選項為較安全產生隨機數?**
- A:block.timestamp
- B:block.hash
- C:Chainlink VRF 預言機
回答寫在這裡
```
C
```
## Solidity 進階
**1. 請問以下是Solidity允許的數值?(複選)**
- A. 0x1cb89a3833bc070
- B. 129348349684596843
- C. 0.1
回答寫在這裡
```
AB
```
**2. 說明 proxy 概念,如何更新智能合約?**
回答寫在這裡
```
代理(Proxy)是一種智能合約設計模式,它允許合約的業務邏輯在不變更合約地址的情況下進行升級
可以由一個合約專門匹配什麼功能要去什麼合約,那個什麼合約是可以改的,匹配合約不用改。
```
**3. 合約裡的 receive() 用途是?**
回答寫在這裡
```
用來接收ETH,有function 有 payable並且有收到ETH就會執行receive()來接收ETH。
```
**4. 做 Dapp 以下是不需要的?**
- A. ethers.js
- B. RPC Provider
- C. 智能合約 ABI
- D. 智能合約地址
回答寫在這裡
```
A
```
**5. 說明 EOA 與 Contract Address 是如何產生的**
回答寫在這裡
```
EOA 是由私鑰控制的地址。它的地址是由公鑰經過 Keccak-256 哈希後取最後的 20 字節生成的。
Contract Address:合約地址是在合約創建時生成的,它的生成方式是將創建者的地址和創建者的 nonce 進行 RLP 編碼後進行 Keccak-256 哈希,取最後的 20 字節作為合約地址。
```
**6. 承上題,兩者有何不同?要如何區分?**
回答寫在這裡
```
EOA地址是由私鑰直接去受授權,進行交易等等,但是合約需要有人去觸發功能,才能進行交易等等,最快的方法可以直接上Etherscan查察,有看到這地址上有寫程式碼就是合約地址了。
```
## 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;
}
}
```
function withdraw忘了上鎖,如果解收地址是攻擊者的合約可以很簡單的利用 fallback()讓遠本合約以為真的錢沒有轉出去,只要 function withdraw(uint256 amount) external lock就可以解決。
## Solidity 合約應用
**1. 部署一個 ERC721 合約並開源合約,mint 一個 NFT 後轉移到以下地址 `0x6e24f0fF0337edf4af9c67bFf22C402302fc94D3`
請留下 transfer 的 tx hash**
```
# 回答區
tx hash: 0xeb31899dce5d091d9dc5307b085685aa234c47bff10844e85802b8a40f15e236
```
**2. 請部署一個能從 Chainlink 取得 ETH 價格的合約到 goerli 測試網上並開源合約**
```
# 回答區
合約連結:https://goerli.etherscan.io/address/0x276af5e3ef5e6ada70a2e950f9c85716a63498de
```
**3. 以下是一份名單,共有八個地址,請利用 merkle tree 原理製作出一個 root 以及當 address1 要證明自己在 leaves 中時所需要提交的 proof**
```
名單
address1: '0xdab15510af1425ba57499C2284cf420001A24D00'
address2: '0xA2F7B4eA63be89464bE01FB074d981F5917f53ef'
address3: '0x4197b82771654C0cE9049925845a8F942b58ccD0'
address4: '0x1b9024CFB1409c13f3B2ee422e9c196442c699E1'
address5: '0xEa36d9a9d90b7aFA41404CeCa0c06F9d3A75A8fa'
address6: '0x5ea8023bB1cca8aF07bcA9edB8FCE8b8a84C8B3f'
address7: '0x4bCae98Ab9912694af894D82658517782203a1dE'
address8: '0x2834A1487A841930b8b5b3C5812FB526A0189339'
```
```
# 回答區
1. root: ...
2. proof: ...
# 解題過程...
```
**4. 實作 ERC20 質押某代幣,timelock(固定鎖倉期,自定義), reward (回饋該代幣)**
回答寫在這裡
```solidity=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract StakingContract is ReentrancyGuard {
ERC20 public token;
uint256 public lockTime;
struct Stake {
uint256 amount;
uint256 start;
}
mapping(address => Stake) public stakes;
event Staked(address indexed user, uint256 amount, uint256 start);
event Unstaked(address indexed user, uint256 amount);
constructor(ERC20 _token, uint256 _lockTime) {
token = _token;
lockTime = _lockTime;
}
function stake(uint256 amount) external nonReentrant {
token.transferFrom(msg.sender, address(this), amount);
stakes[msg.sender].amount += amount;
stakes[msg.sender].start = block.timestamp;
emit Staked(msg.sender, amount, stakes[msg.sender].start);
}
function claim() external nonReentrant {
require(block.timestamp > stakes[msg.sender].start + lockTime, "Stake is still locked!");
uint256 amount = stakes[msg.sender].amount;
stakes[msg.sender].amount = 0;
token.transfer(msg.sender, amount);
emit Unstaked(msg.sender, amount);
}
}
```
###### tags: `Solidity 工程師實戰營第 5 期`