1258 lines
26 KiB
Markdown
1258 lines
26 KiB
Markdown
# 智能合约安全
|
||
|
||
## 问题
|
||
|
||
1. 智能合约安全有哪些常见漏洞?
|
||
2. 什么是重入攻击(Reentrancy)?如何防范?
|
||
3. 什么是整数溢出/下溢?Solidity 0.8+如何处理?
|
||
4. 什么是访问控制漏洞?如何设计权限管理?
|
||
5. 什么是前置交易(Front-running)?如何防范?
|
||
6. 什么是闪电贷攻击?如何防范?
|
||
7. 什么是跨链桥安全风险?
|
||
8. 智能合约如何进行安全审计?
|
||
9. 使用哪些工具进行安全检测?
|
||
10. 智能合约有哪些最佳实践?
|
||
|
||
---
|
||
|
||
## 标准答案
|
||
|
||
### 1. 常见智能合约漏洞
|
||
|
||
#### **OWASP Top 10 - Smart Contract**
|
||
|
||
```
|
||
1. 重入攻击(Reentrancy)
|
||
2. 整数溢出/下溢(Arithmetic Issues)
|
||
3. 访问控制失败(Access Control)
|
||
4. 未检查的低级调用(Unchecked Low-Level Calls)
|
||
5. 前置交易(Front-running / TX Origin)
|
||
6. 时间戳依赖(Timestamp Manipulation)
|
||
7. 逻辑错误(Logic Errors)
|
||
8. Gas限制和循环(Gas Limit and Loops)
|
||
9. 默认可见性(Default Visibility)
|
||
10. 竞态条件(Race Conditions)
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 重入攻击(Reentrancy)
|
||
|
||
#### **原理**
|
||
|
||
攻击者在合约更新状态之前,递归调用提款函数,多次提取资金。
|
||
|
||
#### **经典案例:The DAO攻击(2016)**
|
||
|
||
```
|
||
1. 攻击者存入ETH到DAO合约
|
||
2. 攻击者调用withdraw()
|
||
3. DAO合约发送ETH给攻击者
|
||
4. 攻击者的fallback函数再次调用withdraw()
|
||
5. DAO合约未更新余额,再次发送ETH
|
||
6. 重复步骤3-5多次
|
||
7. 最终提取远超存款的ETH
|
||
|
||
损失:360万 ETH(当时价值约$7000万)
|
||
```
|
||
|
||
#### **漏洞代码**
|
||
|
||
```solidity
|
||
// ❌ 易受攻击的合约
|
||
contract VulnerableBank {
|
||
mapping(address => uint256) public balances;
|
||
|
||
function deposit() public payable {
|
||
balances[msg.sender] += msg.value;
|
||
}
|
||
|
||
function withdraw(uint256 amount) public {
|
||
require(balances[msg.sender] >= amount, "Insufficient balance");
|
||
|
||
// ❌ 先转账,后更新状态(漏洞!)
|
||
(bool success, ) = msg.sender.call{value: amount}("");
|
||
require(success, "Transfer failed");
|
||
|
||
// 更新状态(太晚了!)
|
||
balances[msg.sender] -= amount;
|
||
}
|
||
}
|
||
|
||
// 攻击合约
|
||
contract Attack {
|
||
VulnerableBank public bank;
|
||
|
||
constructor(address _bank) {
|
||
bank = VulnerableBank(_bank);
|
||
}
|
||
|
||
// 存入ETH
|
||
function attack() public payable {
|
||
bank.deposit{value: msg.value}();
|
||
bank.withdraw(msg.value);
|
||
}
|
||
|
||
// fallback函数递归调用
|
||
fallback() external payable {
|
||
if (address(bank).balance >= 1 ether) {
|
||
bank.withdraw(1 ether);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**攻击流程**:
|
||
```
|
||
1. 攻击者调用attack(),存入1 ETH
|
||
2. 调用withdraw(1 ether)
|
||
3. 合约检查:balances[msg.sender] >= 1 ether ✓
|
||
4. 合约发送1 ETH给攻击者
|
||
5. 攻击者合约的fallback()被触发
|
||
6. fallback()再次调用withdraw(1 ether)
|
||
7. 合约检查:balances[msg.sender]还是1 ether(未更新!)✓
|
||
8. 合约再次发送1 ETH
|
||
9. 重复步骤5-8,直到合约余额不足
|
||
```
|
||
|
||
---
|
||
|
||
#### **修复方案1:检查-生效-交互模式(CEI)**
|
||
|
||
```solidity
|
||
// ✅ 安全的合约
|
||
contract SecureBank {
|
||
mapping(address => uint256) public balances;
|
||
|
||
function withdraw(uint256 amount) public {
|
||
require(balances[msg.sender] >= amount, "Insufficient balance");
|
||
|
||
// ✅ 先更新状态
|
||
balances[msg.sender] -= amount;
|
||
|
||
// ✅ 后交互(转账)
|
||
(bool success, ) = msg.sender.call{value: amount}("");
|
||
require(success, "Transfer failed");
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **修复方案2:重入锁(ReentrancyGuard)**
|
||
|
||
```solidity
|
||
// OpenZeppelin ReentrancyGuard
|
||
abstract contract ReentrancyGuard {
|
||
uint256 private constant _NOT_ENTERED = 1;
|
||
uint256 private constant _ENTERED = 2;
|
||
|
||
uint256 private _status;
|
||
|
||
constructor() {
|
||
_status = _NOT_ENTERED;
|
||
}
|
||
|
||
modifier nonReentrant() {
|
||
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
|
||
_status = _ENTERED;
|
||
_;
|
||
_status = _NOT_ENTERED;
|
||
}
|
||
}
|
||
|
||
// 使用ReentrancyGuard
|
||
contract SecureBankWithGuard is ReentrancyGuard {
|
||
mapping(address => uint256) public balances;
|
||
|
||
function withdraw(uint256 amount) public nonReentrant {
|
||
require(balances[msg.sender] >= amount, "Insufficient balance");
|
||
|
||
balances[msg.sender] -= amount;
|
||
|
||
(bool success, ) = msg.sender.call{value: amount}("");
|
||
require(success, "Transfer failed");
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **修复方案3:Pull over Push模式**
|
||
|
||
```solidity
|
||
// ✅ 使用Pull模式
|
||
contract PullOverPush {
|
||
mapping(address => uint256) public balances;
|
||
mapping(address => uint256) public withdrawals;
|
||
|
||
function requestWithdraw(uint256 amount) public {
|
||
require(balances[msg.sender] >= amount, "Insufficient balance");
|
||
balances[msg.sender] -= amount;
|
||
withdrawals[msg.sender] += amount;
|
||
}
|
||
|
||
// 用户主动提取
|
||
function withdraw() public {
|
||
uint256 amount = withdrawals[msg.sender];
|
||
require(amount > 0, "Nothing to withdraw");
|
||
|
||
withdrawals[msg.sender] = 0;
|
||
(bool success, ) = msg.sender.call{value: amount}("");
|
||
require(success, "Transfer failed");
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 整数溢出/下溢
|
||
|
||
#### **原理**
|
||
|
||
```
|
||
uint8 最大值:255
|
||
uint8 最小值:0
|
||
|
||
溢出:
|
||
255 + 1 = 0(回绕)
|
||
|
||
下溢:
|
||
0 - 1 = 255(回绕)
|
||
|
||
Solidity 0.8+ 自动检查溢出/下溢
|
||
旧版本需要使用SafeMath库
|
||
```
|
||
|
||
#### **漏洞代码**
|
||
|
||
```solidity
|
||
// ❌ Solidity 0.8之前
|
||
contract VulnerableToken {
|
||
uint8 public totalSupply = 255;
|
||
|
||
function mint(uint8 amount) public {
|
||
// 溢出:255 + 1 = 0
|
||
totalSupply += amount;
|
||
}
|
||
}
|
||
|
||
// 攻击
|
||
// mint(1) → totalSupply = 0(溢出!)
|
||
```
|
||
|
||
#### **修复方案**
|
||
|
||
**Solidity 0.8+**(自动检查):
|
||
```solidity
|
||
// ✅ Solidity 0.8+
|
||
contract SecureToken {
|
||
uint256 public totalSupply;
|
||
|
||
function mint(uint256 amount) public {
|
||
// ✅ 自动检查溢出,失败会revert
|
||
totalSupply += amount;
|
||
}
|
||
}
|
||
```
|
||
|
||
**SafeMath库**(旧版本):
|
||
```solidity
|
||
library SafeMath {
|
||
function add(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
uint256 c = a + b;
|
||
require(c >= a, "SafeMath: addition overflow");
|
||
return c;
|
||
}
|
||
|
||
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
require(b <= a, "SafeMath: subtraction overflow");
|
||
return a - b;
|
||
}
|
||
|
||
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
|
||
if (a == 0) return 0;
|
||
uint256 c = a * b;
|
||
require(c / a == b, "SafeMath: multiplication overflow");
|
||
return c;
|
||
}
|
||
}
|
||
|
||
contract SecureTokenWithSafeMath {
|
||
using SafeMath for uint256;
|
||
|
||
uint256 public totalSupply;
|
||
|
||
function mint(uint256 amount) public {
|
||
// ✅ 使用SafeMath检查溢出
|
||
totalSupply = totalSupply.add(amount);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4. 访问控制漏洞
|
||
|
||
#### **常见问题**
|
||
|
||
```
|
||
1. 函数未设置访问控制
|
||
2. 使用tx.origin认证(中间人攻击)
|
||
3. 权限提升漏洞
|
||
```
|
||
|
||
#### **漏洞代码1:未保护的函数**
|
||
|
||
```solidity
|
||
// ❌ 任何人都可以调用
|
||
contract Vulnerable {
|
||
address public owner;
|
||
uint256 public price = 100;
|
||
|
||
function setPrice(uint256 _price) public {
|
||
// ❌ 没有权限检查
|
||
price = _price;
|
||
}
|
||
}
|
||
```
|
||
|
||
**修复**:
|
||
```solidity
|
||
// ✅ 使用onlyOwner修饰符
|
||
contract Secure {
|
||
address public owner;
|
||
uint256 public price = 100;
|
||
|
||
constructor() {
|
||
owner = msg.sender;
|
||
}
|
||
|
||
modifier onlyOwner() {
|
||
require(msg.sender == owner, "Not owner");
|
||
_;
|
||
}
|
||
|
||
function setPrice(uint256 _price) public onlyOwner {
|
||
// ✅ 只有owner可以调用
|
||
price = _price;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **漏洞代码2:tx.origin认证(钓鱼攻击)**
|
||
|
||
```solidity
|
||
// ❌ 易受钓鱼攻击
|
||
contract Wallet {
|
||
address public owner;
|
||
|
||
constructor() {
|
||
owner = msg.sender;
|
||
}
|
||
|
||
function withdraw() public {
|
||
// ❌ 使用tx.origin(不安全!)
|
||
require(tx.origin == owner, "Not owner");
|
||
payable(msg.sender).transfer(address(this).balance);
|
||
}
|
||
}
|
||
|
||
// 攻击合约
|
||
contract AttackWallet {
|
||
Wallet public wallet;
|
||
|
||
constructor(address _wallet) {
|
||
wallet = Wallet(_wallet);
|
||
}
|
||
|
||
function attack() public {
|
||
// 诱导owner调用这个函数
|
||
wallet.withdraw();
|
||
}
|
||
|
||
fallback() external payable {
|
||
// 转账给攻击者
|
||
payable(msg.sender).transfer(msg.value);
|
||
}
|
||
}
|
||
```
|
||
|
||
**攻击流程**:
|
||
```
|
||
1. 攻击者创建AttackWallet合约
|
||
2. 攻击者诱导owner调用AttackWallet.attack()
|
||
3. attack()调用Wallet.withdraw()
|
||
4. Wallet检查:tx.origin == owner ✓(owner是调用者)
|
||
5. msg.sender是AttackWallet合约
|
||
6. 资金转给AttackWallet合约
|
||
7. AttackWallet的fallback()把资金转给攻击者
|
||
```
|
||
|
||
**修复**:
|
||
```solidity
|
||
// ✅ 使用msg.sender
|
||
contract SecureWallet {
|
||
address public owner;
|
||
|
||
constructor() {
|
||
owner = msg.sender;
|
||
}
|
||
|
||
modifier onlyOwner() {
|
||
require(msg.sender == owner, "Not owner");
|
||
_;
|
||
}
|
||
|
||
function withdraw() public onlyOwner {
|
||
// ✅ 使用msg.sender
|
||
payable(msg.sender).transfer(address(this).balance);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **漏洞代码3:权限提升**
|
||
|
||
```solidity
|
||
// ❌ 可以提升自己为管理员
|
||
contract AccessControl {
|
||
mapping(address => bool) public admins;
|
||
address public owner;
|
||
|
||
constructor() {
|
||
owner = msg.sender;
|
||
admins[owner] = true;
|
||
}
|
||
|
||
function addToAdmin(address _user) public {
|
||
// ❌ 没有检查调用者是否是admin
|
||
admins[_user] = true;
|
||
}
|
||
}
|
||
|
||
// 攻击
|
||
// 攻击者调用addToAdmin(攻击者地址)
|
||
// 攻击者成为管理员
|
||
```
|
||
|
||
**修复**:
|
||
```solidity
|
||
// ✅ 检查调用者权限
|
||
contract SecureAccessControl {
|
||
mapping(address => bool) public admins;
|
||
address public owner;
|
||
|
||
constructor() {
|
||
owner = msg.sender;
|
||
admins[owner] = true;
|
||
}
|
||
|
||
modifier onlyAdmin() {
|
||
require(admins[msg.sender], "Not admin");
|
||
_;
|
||
}
|
||
|
||
function addToAdmin(address _user) public onlyAdmin {
|
||
// ✅ 只有admin可以添加新admin
|
||
admins[_user] = true;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5. 前置交易(Front-running)
|
||
|
||
#### **原理**
|
||
|
||
攻击者看到交易在内存池(Mempool)中,复制交易并设置更高的Gas Price,让自己的交易先执行。
|
||
|
||
#### **示例场景**
|
||
|
||
```
|
||
1. 用户在DEX上用1 ETH买UNI(价格$10)
|
||
2. 攻击者看到这笔交易
|
||
3. 攻击者设置更高Gas Price,抢先买入
|
||
4. 用户交易执行,价格被推高到$11
|
||
5. 攻击者立即卖出,获利
|
||
|
||
用户损失:原本应该花$10,实际花了$11
|
||
攻击者获利:$1/UNI × 交易数量
|
||
```
|
||
|
||
#### **代码示例**
|
||
|
||
```solidity
|
||
// ❌ 易受前置交易攻击
|
||
contract VulnerableDEX {
|
||
mapping(address => uint256) public prices;
|
||
|
||
function setPrice(address token, uint256 price) public {
|
||
// ❌ 可以被抢先交易
|
||
prices[token] = price;
|
||
}
|
||
|
||
function trade(address token, uint256 amount) public {
|
||
uint256 price = prices[token];
|
||
// 使用price交易
|
||
}
|
||
}
|
||
```
|
||
|
||
#### **修复方案1:提交-揭示模式(Commit-Reveal)**
|
||
|
||
```solidity
|
||
// ✅ 使用Commit-Reveal
|
||
contract CommitReveal {
|
||
struct Commitment {
|
||
bytes32 commitHash;
|
||
uint256 revealTime;
|
||
bool revealed;
|
||
}
|
||
|
||
mapping(address => Commitment) public commitments;
|
||
mapping(address => uint256) public revealedValues;
|
||
|
||
// 1. 提交哈希(隐藏真实值)
|
||
function commit(bytes32 commitHash) public {
|
||
commitments[msg.sender] = Commitment({
|
||
commitHash: commitHash,
|
||
revealTime: block.timestamp + 1 days,
|
||
revealed: false
|
||
});
|
||
}
|
||
|
||
// 2. 揭示真实值(1天后)
|
||
function reveal(uint256 value, uint256 salt) public {
|
||
Commitment storage c = commitments[msg.sender];
|
||
require(!c.revealed, "Already revealed");
|
||
require(block.timestamp >= c.revealTime, "Too early");
|
||
|
||
// 验证哈希
|
||
bytes32 computedHash = keccak256(abi.encodePacked(value, salt));
|
||
require(computedHash == c.commitHash, "Invalid reveal");
|
||
|
||
c.revealed = true;
|
||
revealedValues[msg.sender] = value;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **修复方案2:批量交易**
|
||
|
||
```solidity
|
||
// ✅ 批量执行交易,减少前置交易
|
||
contract BatchTrade {
|
||
struct Trade {
|
||
address user;
|
||
address token;
|
||
uint256 amount;
|
||
uint256 price;
|
||
}
|
||
|
||
Trade[] public pendingTrades;
|
||
|
||
function submitTrade(address token, uint256 amount) public {
|
||
pendingTrades.push(Trade({
|
||
user: msg.sender,
|
||
token: token,
|
||
amount: amount,
|
||
price: 0 // 后续设置
|
||
}));
|
||
}
|
||
|
||
// 批量执行所有交易
|
||
function executeBatch() public {
|
||
for (uint256 i = 0; i < pendingTrades.length; i++) {
|
||
Trade memory t = pendingTrades[i];
|
||
// 使用平均价格执行所有交易
|
||
executeTrade(t);
|
||
}
|
||
delete pendingTrades;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 6. 闪电贷攻击
|
||
|
||
#### **原理**
|
||
|
||
```
|
||
1. 攻击者闪电贷借入大量资金
|
||
2. 使用资金操纵市场(如DEX价格)
|
||
3. 利用被操纵的价格获利(如套利、清算)
|
||
4. 还款 + 手续费
|
||
5. 如果失败,交易回滚(无风险)
|
||
```
|
||
|
||
#### **攻击示例**
|
||
|
||
```solidity
|
||
// 闪电贷攻击DEX
|
||
contract FlashLoanAttack {
|
||
IUniswapV2Router public router;
|
||
IUniswapV2Pair public pair;
|
||
|
||
function attack() external {
|
||
// 1. 闪电贷借入10,000 ETH
|
||
flashLoan(10000 ether);
|
||
|
||
// 2. 在DEX上用10,000 ETH卖出代币
|
||
// → 价格被砸到极低
|
||
|
||
// 3. 在另一个DEX用低价买回代币
|
||
// → 赚取差价
|
||
|
||
// 4. 还款 10,000 ETH + 手续费
|
||
repayFlashLoan(10000 ether);
|
||
}
|
||
}
|
||
```
|
||
|
||
#### **防范措施**
|
||
|
||
```
|
||
1. 使用时间加权平均价格(TWAP)
|
||
- 链下预言机(Chainlink)
|
||
- 多个价格源聚合
|
||
|
||
2. 限制单笔交易规模
|
||
- 最大交易量限制
|
||
- 滑点保护
|
||
|
||
3. 闪电贷检测
|
||
- 单个区块内大额交易
|
||
- 可疑价格波动告警
|
||
|
||
4. 暂停机制
|
||
- 检测到攻击时暂停合约
|
||
- 多重签名恢复
|
||
```
|
||
|
||
**代码示例:TWAP预言机**:
|
||
```solidity
|
||
// ✅ 使用TWAP预言机
|
||
contract TWAPOracle {
|
||
uint256 public price;
|
||
uint256 public lastUpdateTime;
|
||
uint256 public constant PERIOD = 1 hours;
|
||
|
||
function updatePrice(uint256 newPrice) external {
|
||
if (block.timestamp >= lastUpdateTime + PERIOD) {
|
||
// 时间到了,可以更新
|
||
price = newPrice;
|
||
lastUpdateTime = block.timestamp;
|
||
} else {
|
||
// 未到期,使用加权平均
|
||
uint256 timeElapsed = block.timestamp - lastUpdateTime;
|
||
uint256 weight = timeElapsed * 100 / PERIOD;
|
||
price = (price * (100 - weight) + newPrice * weight) / 100;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 7. 跨链桥安全风险
|
||
|
||
#### **常见攻击**
|
||
|
||
```
|
||
1. 验证节点私钥泄露
|
||
- Ronin Bridge:$6.25亿被盗
|
||
- 攻击者控制了5/9个验证节点
|
||
|
||
2. 伪造签名
|
||
- Wormhole:$3.2亿被盗
|
||
- 攻击者伪造了验证者签名
|
||
|
||
3. 智能合约漏洞
|
||
- Harmony Bridge:$1亿被盗
|
||
- 漏洞允许绕过验证
|
||
|
||
4. 逻辑漏洞
|
||
- Nomad Bridge:$1.9亿被盗
|
||
- 漏洞允许任何人伪造消息
|
||
```
|
||
|
||
#### **安全设计原则**
|
||
|
||
```solidity
|
||
// ✅ 安全的跨链桥设计
|
||
contract SecureBridge {
|
||
// 1. 多重签名验证
|
||
mapping(bytes32 => bool) public usedSignatures;
|
||
address[] public validators;
|
||
|
||
modifier validateSignature(bytes32 message, bytes memory signature) {
|
||
require(verifySignature(message, signature), "Invalid signature");
|
||
require(!usedSignatures[keccak256(signature)], "Signature used");
|
||
_;
|
||
}
|
||
|
||
// 2. 延迟提款(给用户时间取消)
|
||
mapping(address => uint256) public pendingWithdrawals;
|
||
uint256 public constant DELAY = 24 hours;
|
||
|
||
function requestWithdraw(address token, uint256 amount) public {
|
||
pendingWithdrawals[msg.sender] = block.timestamp;
|
||
emit WithdrawRequested(msg.sender, token, amount);
|
||
}
|
||
|
||
function executeWithdraw(address token, uint256 amount) public {
|
||
require(
|
||
block.timestamp >= pendingWithdrawals[msg.sender] + DELAY,
|
||
"Delay not met"
|
||
);
|
||
// 执行提款
|
||
}
|
||
|
||
// 3. 暂停机制
|
||
bool public paused;
|
||
|
||
modifier whenNotPaused() {
|
||
require(!paused, "Paused");
|
||
_;
|
||
}
|
||
|
||
function pause() public {
|
||
// 多重签名
|
||
paused = true;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 8. 智能合约安全审计
|
||
|
||
#### **审计流程**
|
||
|
||
```
|
||
1. 文档审查
|
||
- 白皮书
|
||
- 技术文档
|
||
- 经济模型
|
||
|
||
2. 代码审查
|
||
- 手动代码审查
|
||
- 自动化工具扫描
|
||
- 测试覆盖率检查
|
||
|
||
3. 测试
|
||
- 单元测试
|
||
- 集成测试
|
||
- 模糊测试(Fuzzing)
|
||
|
||
4. 形式化验证
|
||
- 数学证明
|
||
- 不变量检查
|
||
|
||
5. 渗透测试
|
||
- 模拟攻击
|
||
- 红队演练
|
||
```
|
||
|
||
---
|
||
|
||
#### **审计清单**
|
||
|
||
```
|
||
✅ 访问控制
|
||
- 所有敏感函数都有访问控制
|
||
- 使用OpenZeppelin的AccessControl
|
||
|
||
✅ 重入保护
|
||
- 外部调用在状态更新之后
|
||
- 使用ReentrancyGuard
|
||
|
||
✅ 整数溢出
|
||
- 使用Solidity 0.8+
|
||
- 或使用SafeMath
|
||
|
||
✅ 外部调用
|
||
- 检查返回值
|
||
- 使用try-catch
|
||
|
||
✅ Gas优化
|
||
- 循环有限制
|
||
- 避免动态数组
|
||
|
||
✅ 测试
|
||
- 单元测试覆盖率 > 90%
|
||
- 集成测试
|
||
- 边界条件测试
|
||
|
||
✅ 文档
|
||
- NatSpec注释
|
||
- 清晰的函数说明
|
||
- 使用示例
|
||
```
|
||
|
||
---
|
||
|
||
### 9. 安全检测工具
|
||
|
||
#### **静态分析工具**
|
||
|
||
```
|
||
1. Slither
|
||
- Python开发
|
||
- 快速扫描
|
||
- 检测常见漏洞
|
||
|
||
安装:
|
||
pip install slither-analyzer
|
||
|
||
使用:
|
||
slither contract.sol
|
||
```
|
||
|
||
```bash
|
||
# Slither扫描示例
|
||
$ slither token.sol
|
||
|
||
...
|
||
INFO:Detectors:
|
||
Reentrancy in Token.withdraw(uint256) (token.sol#45-50):
|
||
External calls:
|
||
- msg.sender.call{value: amount}(gas()) (token.sol#48)
|
||
State variables written after the call(s):
|
||
- balances[msg.sender] -= amount (token.sol#49)
|
||
```
|
||
|
||
---
|
||
|
||
```
|
||
2. MythX
|
||
- 商业工具
|
||
- 深度分析
|
||
- 支持多种漏洞检测
|
||
|
||
使用:
|
||
mythx analyze contract.sol
|
||
```
|
||
|
||
---
|
||
|
||
```
|
||
3. Echidna
|
||
- 模糊测试工具
|
||
- 基于属性的测试
|
||
- 发现边缘情况
|
||
|
||
安装:
|
||
cargo install echidna
|
||
|
||
使用:
|
||
echidna-test contract.sol
|
||
```
|
||
|
||
**Echidna示例**:
|
||
```solidity
|
||
// 测试不变量:总供应量 = 所有人余额之和
|
||
contract Token {
|
||
mapping(address => uint256) public balanceOf;
|
||
uint256 public totalSupply;
|
||
|
||
function transfer(address to, uint256 amount) public {
|
||
balanceOf[msg.sender] -= amount;
|
||
balanceOf[to] += amount;
|
||
}
|
||
}
|
||
|
||
// Echidna测试
|
||
contract TestToken is Token, Test {
|
||
function check_total_supply() public {
|
||
uint256 sum = 0;
|
||
// 计算所有余额(需要手动添加)
|
||
assert(totalSupply == sum);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
```
|
||
4. Mythril
|
||
- 符号执行引擎
|
||
- 深度漏洞检测
|
||
- 支持攻击路径分析
|
||
|
||
安装:
|
||
pip install mythril
|
||
|
||
使用:
|
||
myth contract.sol
|
||
```
|
||
|
||
---
|
||
|
||
#### **动态分析工具**
|
||
|
||
```
|
||
1. Hardhat + Tenderly
|
||
- 交易模拟
|
||
- Gas分析
|
||
- 调试工具
|
||
|
||
2. Ganache
|
||
- 本地测试网络
|
||
- 快速部署测试
|
||
|
||
3. Fork测试
|
||
- Fork主网状态
|
||
- 在真实环境测试
|
||
```
|
||
|
||
**Fork测试示例**:
|
||
```javascript
|
||
// hardhat.config.js
|
||
module.exports = {
|
||
networks: {
|
||
hardhat: {
|
||
forking: {
|
||
url: "https://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY",
|
||
blockNumber: 15000000, // Fork特定区块
|
||
},
|
||
},
|
||
},
|
||
};
|
||
|
||
// 测试
|
||
describe("Uniswap Fork Test", function () {
|
||
it("should swap on real Uniswap", async function () {
|
||
// 在主网Uniswap上交易(不花真钱)
|
||
const uniswap = await ethers.getContractAt("IUniswapV2Router", UNISWAP_ROUTER);
|
||
await uniswap.swapExactETHForTokens(...);
|
||
});
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
### 10. 智能合约最佳实践
|
||
|
||
#### **1. 使用OpenZeppelin库**
|
||
|
||
```solidity
|
||
// ✅ 不要重复造轮子
|
||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||
|
||
contract MyToken is ERC20, Ownable, ReentrancyGuard {
|
||
constructor() ERC20("My Token", "MTK") {}
|
||
|
||
function mint(address to, uint256 amount) public onlyOwner {
|
||
_mint(to, amount);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **2. 检查-生效-交互模式**
|
||
|
||
```solidity
|
||
// ✅ CEI模式
|
||
function withdraw(uint256 amount) public {
|
||
// 1. 检查(Checks)
|
||
require(balances[msg.sender] >= amount, "Insufficient");
|
||
|
||
// 2. 生效(Effects)
|
||
balances[msg.sender] -= amount;
|
||
|
||
// 3. 交互(Interactions)
|
||
(bool success, ) = msg.sender.call{value: amount}("");
|
||
require(success, "Transfer failed");
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **3. 使用SafeERC20**
|
||
|
||
```solidity
|
||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||
|
||
contract SafeTransfer {
|
||
using SafeERC20 for IERC20;
|
||
|
||
function transferToken(address token, address to, uint256 amount) public {
|
||
// ✅ 自动处理返回值
|
||
IERC20(token).safeTransfer(to, amount);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **4. 停止机制(Emergency Pause)**
|
||
|
||
```solidity
|
||
import "@openzeppelin/contracts/security/Pausable.sol";
|
||
|
||
contract PausableContract is Pausable, Ownable {
|
||
function deposit() public payable whenNotPaused {
|
||
// 正常逻辑
|
||
}
|
||
|
||
function pause() public onlyOwner {
|
||
_pause();
|
||
}
|
||
|
||
function unpause() public onlyOwner {
|
||
_unpause();
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **5. 事件日志**
|
||
|
||
```solidity
|
||
contract LogEvents {
|
||
event Withdrawal(address indexed user, uint256 amount, uint256 timestamp);
|
||
|
||
function withdraw(uint256 amount) public {
|
||
// ✅ 记录所有重要操作
|
||
emit Withdrawal(msg.sender, amount, block.timestamp);
|
||
|
||
// 执行提款
|
||
payable(msg.sender).transfer(amount);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **6. 限流机制**
|
||
|
||
```solidity
|
||
contract RateLimiter {
|
||
mapping(address => uint256) public lastCallTime;
|
||
uint256 public constant COOLDOWN = 1 minutes;
|
||
|
||
modifier rateLimit() {
|
||
require(
|
||
block.timestamp >= lastCallTime[msg.sender] + COOLDOWN,
|
||
"Cooldown not met"
|
||
);
|
||
_;
|
||
lastCallTime[msg.sender] = block.timestamp;
|
||
}
|
||
|
||
function sensitiveOperation() public rateLimit {
|
||
// 限制每个用户每分钟只能调用一次
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **7. 多重签名钱包**
|
||
|
||
```solidity
|
||
// ✅ 关键操作使用多签
|
||
contract MultiSigWallet {
|
||
address[] public owners;
|
||
uint256 public required;
|
||
|
||
mapping(uint256 => Transaction) public transactions;
|
||
mapping(uint256 => mapping(address => bool)) public confirmations;
|
||
|
||
struct Transaction {
|
||
address to;
|
||
uint256 value;
|
||
bytes data;
|
||
bool executed;
|
||
}
|
||
|
||
function submitTransaction(address to, uint256 value, bytes memory data)
|
||
public
|
||
{
|
||
uint256 txIndex = transactions.length;
|
||
transactions[txIndex] = Transaction({
|
||
to: to,
|
||
value: value,
|
||
data: data,
|
||
executed: false
|
||
});
|
||
confirmTransaction(txIndex);
|
||
}
|
||
|
||
function confirmTransaction(uint256 txIndex) public {
|
||
// 需要多个所有者确认
|
||
confirmations[txIndex][msg.sender] = true;
|
||
if (isConfirmed(txIndex)) {
|
||
executeTransaction(txIndex);
|
||
}
|
||
}
|
||
|
||
function isConfirmed(uint256 txIndex) public view returns (bool) {
|
||
uint256 count = 0;
|
||
for (uint256 i = 0; i < owners.length; i++) {
|
||
if (confirmations[txIndex][owners[i]]) {
|
||
count += 1;
|
||
}
|
||
}
|
||
return count >= required;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### **8. 升级模式**
|
||
|
||
```solidity
|
||
// ✅ 使用代理模式升级
|
||
contract LogicV1 {
|
||
uint256 public value;
|
||
|
||
function setValue(uint256 _value) public {
|
||
value = _value;
|
||
}
|
||
}
|
||
|
||
contract LogicV2 {
|
||
uint256 public value;
|
||
uint256 public newValue;
|
||
|
||
function setValue(uint256 _value) public {
|
||
value = _value;
|
||
newValue = _value * 2;
|
||
}
|
||
}
|
||
|
||
// 使用OpenZeppelin Transparent Proxy
|
||
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
||
|
||
// 部署
|
||
// 1. 部署LogicV1
|
||
// 2. 部署Proxy,指向LogicV1
|
||
// 3. 用户通过Proxy交互
|
||
// 4. 升级:部署LogicV2,调用Proxy.upgradeTo(LogicV2)
|
||
```
|
||
|
||
---
|
||
|
||
## 结合简历的面试题
|
||
|
||
### 1. 电商风控 vs DeFi安全
|
||
|
||
**面试官会问**:
|
||
> "你在电商做过风控系统,DeFi安全有什么异同?"
|
||
|
||
**参考回答**:
|
||
```
|
||
电商风控:
|
||
- 反欺诈:识别虚假订单、刷单
|
||
- 信用评估:用户信用评分
|
||
- 异常检测:异常交易行为
|
||
|
||
DeFi安全:
|
||
- 智能合约审计:代码漏洞检测
|
||
- 链上监控:异常交易监控
|
||
- 价格预言机:防止价格操纵
|
||
- MEV保护:防止三明治攻击
|
||
|
||
共同点:
|
||
- 需要实时监控系统
|
||
- 需要异常检测机制
|
||
- 需要快速响应机制
|
||
|
||
差异:
|
||
- 电商:中心化,可以人工介入
|
||
- DeFi:去中心化,只能通过智能合约
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 高并发与Gas优化
|
||
|
||
**面试官会问**:
|
||
> "你做过高并发系统,智能合约如何优化Gas?"
|
||
|
||
**参考回答**:
|
||
```
|
||
Web2高并发:
|
||
- 缓存热点数据
|
||
- 异步处理
|
||
- 数据库优化
|
||
|
||
Web3 Gas优化:
|
||
1. 存储优化
|
||
- 使用打包类型(uint256 vs uint8)
|
||
- 使用短字符串
|
||
- 删除不需要的数据(refund gas)
|
||
|
||
2. 循环优化
|
||
- 限制循环次数
|
||
- 使用mapping代替array查找
|
||
|
||
3. 批量操作
|
||
- 批量转账
|
||
- 批量Mint
|
||
|
||
4. 链下计算
|
||
- Merkle Tree
|
||
- 零知识证明
|
||
|
||
示例:
|
||
// ❌ 高Gas
|
||
for (uint256 i = 0; i < 1000; i++) {
|
||
users[i].reward = calculateReward(i);
|
||
}
|
||
|
||
// ✅ 低Gas(链下计算 + Merkle Proof)
|
||
bytes32 public merkleRoot;
|
||
function claimReward(uint256 amount, bytes32[] memory proof) public {
|
||
require(verifyMerkleProof(msg.sender, amount, proof), "Invalid proof");
|
||
payable(msg.sender).transfer(amount);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 智能合约安全面试加分项
|
||
|
||
### 1. 实战经验
|
||
|
||
- 参与过智能合约审计
|
||
- 发现过真实漏洞
|
||
- 有漏洞修复经验
|
||
- 熟悉常见攻击手法
|
||
|
||
### 2. 技术深度
|
||
|
||
- 理解EVM底层机制
|
||
- 熟悉汇编语言(Yul)
|
||
- 了解形式化验证
|
||
- 掌握Gas优化技巧
|
||
|
||
### 3. 工具使用
|
||
|
||
- 熟练使用Slither、MythX
|
||
- 会使用Foundry测试框架
|
||
- 会使用Tenderly调试
|
||
- 了解Fuzzing测试
|
||
|
||
### 4. 安全意识
|
||
|
||
- 防御性编程思维
|
||
- 最小权限原则
|
||
- 深度防御策略
|
||
- 持续学习最新漏洞
|