name: solidity-security
description: Solidity安全模式与漏洞防范。在编写任何Solidity代码前、代码审查期间或部署前使用。涵盖重入攻击、访问控制、整数安全、预言机操纵、闪电贷和Gas优化。
Solidity 安全
重入攻击
CEI模式(检查-效果-交互)—— 不可协商
function withdraw(uint256 amount) external {
// 检查
require(balances[msg.sender] >= amount, InsufficientBalance());
// 效果(在外部调用前更新状态)
balances[msg.sender] -= amount;
// 交互(最后进行外部调用)
(bool success, ) = msg.sender.call{value: amount}("");
require(success, TransferFailed());
}
额外防护
- 对于复杂函数,使用OpenZeppelin的
ReentrancyGuard
- 跨函数重入:保护多个函数间共享的状态
- 只读重入:注意在回调期间读取陈旧状态的视图函数
访问控制
| 模式 |
适用场景 |
Ownable |
单一管理员,简单合约 |
AccessControl(角色) |
多角色,细粒度权限 |
| 多签(Safe) |
高价值操作,主网 |
| 时间锁 |
治理,延迟执行 |
- 切勿使用
tx.origin进行授权——使用msg.sender
- 在修饰器中检查
msg.sender,而非函数体内(除非逻辑复杂)
- 考虑两步所有权转移(
Ownable2Step)
整数安全
- Solidity 0.8+ 内置溢出/下溢保护
- 仅当溢出在数学上不可能时使用
unchecked块
- 记录每个
unchecked块安全的原因
- 使用
type(uint256).max而非魔数
- 注意先除后乘的精度损失
抢先交易 / MEV
| 攻击 |
缓解措施 |
| 三明治攻击 |
滑点保护,截止时间参数 |
| 抢先交易 |
提交-揭示方案 |
| MEV提取 |
使用Flashbots,私有内存池 |
| 预言机操纵 |
时间加权平均价格(TWAP),多数据源 |
预言机安全
- 切勿使用单一DEX的现货价格——使用TWAP
- Chainlink:检查
updatedAt新鲜度,answeredInRound >= roundId
- 优雅处理预言机停机(断路器)
- 使用多预言机源并设置回退逻辑
委托调用风险
- 代理与实现合约的存储布局必须匹配
- 切勿对不受信任的地址进行
delegatecall
- 实现合约必须在构造函数中调用
_disableInitializers()
- 注意代理模式中的存储冲突
闪电贷攻击向量
- 假设单笔交易可借入任意数量的代币
- 不要在同一区块内依赖代币余额进行治理权重计算
- 使用快照机制记录投票权
- 对关键操作添加时间加权检查
存储冲突(代理)
- 使用EIP-1967存储槽存储代理状态
- 升级时切勿修改存储布局顺序——仅可追加
- 使用
/// @custom:storage-location进行命名空间存储(EIP-7201)
- 升级前运行
forge inspect验证存储布局
详细参考