如何防止智能合约中的重入攻击(二)——Solidity 中确保唯一性和防止重入攻击
在 Solidity 中确保唯一性和防止重入攻击是智能合约安全的重要课题。以下是几种关键机制的详细说明和代码示例:
一、确保唯一性机制
1. Nonce 计数器
mapping(address => uint256) public nonces;
function executeWithNonce(
uint256 _nonce,
/* 其他参数 */
) external {
require(_nonce == nonces[msg.sender], "Invalid nonce");
nonces[msg.sender] += 1;
// 执行操作
}
-
每笔交易必须附带递增的 nonce 值
-
防止重放攻击和重复操作
2. 唯一哈希标识
mapping(bytes32 => bool) public executedHashes;
function executeUnique(
/* 参数 */
) external {
bytes32 hash = keccak256(abi.encodePacked(/* 唯一标识参数 */));
require(!executedHashes[hash], "Already executed");
executedHashes[hash] = true;
// 执行操作
}
-
通过参数哈希确保操作唯一性
-
适用于需要唯一性保证的任意操作
3. 状态锁机制
bool private locked;
modifier lock() {
require(!locked, "Operation locked");
locked = true;
_;
locked = false;
}
function sensitiveOperation() external lock {
// 受保护的操作
}
-
通过状态变量锁定敏感操作
-
防止并发修改
二、防止重入攻击机制
1. Checks-Effects-Interactions 模式(CEI)
function withdraw() external {
// CHECK
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");
// EFFECT
balances[msg.sender] = 0;
// INTERACTION
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
-
执行顺序:先验证 → 修改状态 → 最后进行外部调用
-
经典安全模式,防止状态被外部调用篡改
2. 重入锁 (ReentrancyGuard)
// OpenZeppelin 实现
modifier nonReentrant() {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
function safeWithdraw() external nonReentrant {
// 安全操作
}
-
使用状态锁防止递归调用
-
OpenZeppelin 标准实现推荐
3. Gas 限制策略
// 使用 transfer 替代 call
payable(msg.sender).transfer(amount);
// 或明确限制 gas
(bool success, ) = msg.sender.call{
value: amount,
gas: 2300
}("");
-
transfer 自动限制 2300 gas
-
防止攻击者在 fallback 函数中执行复杂操作
4. 提前终止模式
function batchOperation() external {
// 操作1
// 操作2
selfdestruct(payable(owner)); // 执行后销毁合约
}
-
极端情况下使用合约自毁
-
确保后续无法进行任何操作
三、组合防御策略示例
contract SecureContract {
using ReentrancyGuard for ReentrancyGuard.Status;
ReentrancyGuard.Status private _guard;
mapping(bytes32 => bool) public executedTx;
function secureFunction(
bytes32 txHash,
uint256 amount
) external nonReentrant {
require(!executedTx[txHash], "Duplicate transaction");
executedTx[txHash] = true;
// 业务逻辑
_transferFunds(amount);
}
function _transferFunds(uint256 amount) internal {
(bool success, ) = msg.sender.call{
value: amount,
gas: 2300
}("");
require(success);
}
}
四、最佳实践建议
-
优先使用 OpenZeppelin 的 ReentrancyGuard 合约
-
对所有外部调用保持警惕(包括 ERC20 转账)
-
使用静态分析工具(如 Slither、MythX)
-
进行完善的测试(包括边界情况和攻击场景)
-
遵循 Solidity 最新安全建议(https://soliditylang.org/security)
典型重入攻击案例参考 SWC-107:https://swcregistry.io/docs/SWC-107
这些机制需要根据具体业务场景组合使用,在安全性和 gas 消耗之间找到平衡点。对于关键合约,建议进行专业审计。

浙公网安备 33010602011771号