智能合约安全指南 🛡️
1. 安全基础
1.1 常见漏洞类型
- 重入攻击
- 整数溢出
- 权限控制缺陷
- 随机数漏洞
- 前后运行攻击
- 签名重放
1.2 安全开发原则
- 最小权限原则
- 检查-生效-交互模式
- 状态机安全
- 失败保护机制
2. 重入攻击防护
2.1 基本防护模式
solidity
复制代码
contract ReentrancyGuarded {
bool private locked;
modifier noReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
function withdraw() external noReentrant {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");
balances[msg.sender] = 0; // 先更新状态
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
2.2 检查-生效-交互模式
solidity
复制代码
contract CEIPattern {
mapping(address => uint256) private balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
// 检查
require(balances[msg.sender] >= amount, "Insufficient balance");
// 生效
balances[msg.sender] -= amount;
// 交互
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
3. 访问控制
3.1 角色管理
solidity
复制代码
contract RoleBasedAccess {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(bytes32 => EnumerableSet.AddressSet) private roles;
event RoleGranted(bytes32 indexed role, address indexed account);
event RoleRevoked(bytes32 indexed role, address indexed account);
modifier onlyRole(bytes32 role) {
require(hasRole(role, msg.sender), "Unauthorized");
_;
}
function hasRole(
bytes32 role,
address account
) public view returns (bool) {
return roles[role].contains(account);
}
function grantRole(
bytes32 role,
address account
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (roles[role].add(account)) {
emit RoleGranted(role, account);
}
}
function revokeRole(
bytes32 role,
address account
) external onlyRole(DEFAULT_ADMIN_ROLE) {
if (roles[role].remove(account)) {
emit RoleRevoked(role, account);
}
}
}
3.2 权限代理
solidity
复制代码
contract DelegatedAccess {
mapping(address => mapping(address => bool)) private delegates;
event DelegateChanged(
address indexed delegator,
address indexed delegatee,
bool status
);
function setDelegate(address delegatee, bool status) external {
delegates[msg.sender][delegatee] = status;
emit DelegateChanged(msg.sender, delegatee, status);
}
function isDelegate(
address delegator,
address delegatee
) public view returns (bool) {
return delegates[delegator][delegatee];
}
modifier onlyDelegateOrOwner(address owner) {
require(
msg.sender == owner || isDelegate(owner, msg.sender),
"Not authorized"
);
_;
}
}
4. 数据验证
4.1 输入验证
solidity
复制代码
contract InputValidation {
uint256 public constant MAX_ARRAY_LENGTH = 100;
uint256 public constant MAX_VALUE = 1e20;
function validateArrayInput(uint256[] calldata data) internal pure {
require(data.length > 0, "Empty array");
require(data.length <= MAX_ARRAY_LENGTH, "Array too long");
for (uint i = 0; i < data.length; i++) {
require(data[i] <= MAX_VALUE, "Value too large");
if (i > 0) {
require(data[i] >= data[i-1], "Not sorted");
}
}
}
function validateAddress(address addr) internal pure {
require(addr != address(0), "Zero address");
require(addr.code.length == 0, "Contract address not allowed");
}
}
4.2 状态验证
solidity
复制代码
contract StateValidation {
enum State { Inactive, Active, Paused, Ended }
State public currentState;
modifier inState(State requiredState) {
require(currentState == requiredState, "Invalid state");
_;
}
function validateTransition(State newState) internal view {
if (currentState == State.Inactive) {
require(newState == State.Active, "Invalid transition");
} else if (currentState == State.Active) {
require(
newState == State.Paused || newState == State.Ended,
"Invalid transition"
);
}
}
}
5. 签名验证
5.1 EIP712 签名
solidity
复制代码
contract EIP712Verifier {
bytes32 private DOMAIN_SEPARATOR;
struct EIP712Domain {
string name;
string version;
uint256 chainId;
address verifyingContract;
}
constructor(string memory name, string memory version) {
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes(name)),
keccak256(bytes(version)),
block.chainid,
address(this)
)
);
}
function verifySignature(
bytes32 hash,
bytes memory signature
) internal view returns (address) {
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hash)
);
return ecrecover(digest, signature[0], signature[1], signature[2]);
}
}
5.2 签名重放防护
solidity
复制代码
contract ReplayProtection {
mapping(bytes32 => bool) private usedSignatures;
function isSignatureUsed(bytes32 hash) public view returns (bool) {
return usedSignatures[hash];
}
function markSignatureAsUsed(bytes32 hash) internal {
require(!usedSignatures[hash], "Signature already used");
usedSignatures[hash] = true;
}
function validateSignature(
bytes32 hash,
bytes memory signature,
uint256 deadline
) internal view returns (address) {
require(block.timestamp <= deadline, "Signature expired");
require(!isSignatureUsed(hash), "Signature already used");
return verifySignature(hash, signature);
}
}
6. 紧急响应
6.1 紧急停止
solidity
复制代码
contract EmergencyStop {
bool public stopped;
address public guardian;
modifier whenNotStopped() {
require(!stopped, "Contract is stopped");
_;
}
modifier whenStopped() {
require(stopped, "Contract is not stopped");
_;
}
function toggleStop() external {
require(msg.sender == guardian, "Not authorized");
stopped = !stopped;
emit EmergencyToggled(stopped);
}
function emergencyWithdraw() external whenStopped {
require(msg.sender == guardian, "Not authorized");
// 执行紧急提款逻辑
}
}
6.2 漏洞修复
solidity
复制代码
contract UpgradeableSecurityFix {
address public implementation;
address public admin;
function upgrade(address newImplementation) external {
require(msg.sender == admin, "Not authorized");
require(newImplementation.code.length > 0, "Not a contract");
// 验证新实现是否兼容
require(
IUpgradeable(newImplementation).supportsInterface(0x01ffc9a7),
"Incompatible implementation"
);
implementation = newImplementation;
emit Upgraded(newImplementation);
}
}
7. 审计和测试
7.1 自动化测试
javascript
复制代码
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("SecurityTests", function() {
let contract;
let owner;
let attacker;
beforeEach(async function() {
const Contract = await ethers.getContractFactory("SecureContract");
[owner, attacker] = await ethers.getSigners();
contract = await Contract.deploy();
});
it("Should prevent reentrancy attacks", async function() {
await expect(
contract.connect(attacker).withdraw()
).to.be.revertedWith("Reentrant call");
});
it("Should validate access control", async function() {
await expect(
contract.connect(attacker).adminFunction()
).to.be.revertedWith("Not authorized");
});
});
7.2 形式化验证
solidity
复制代码
/// @notice Invariant: total supply should always equal sum of balances
/// @custom:invariant totalSupply == sum(balances)
contract VerifiedToken {
mapping(address => uint256) public balances;
uint256 public totalSupply;
function transfer(address to, uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
assert(balances[msg.sender] <= totalSupply);
assert(balances[to] <= totalSupply);
}
}
8. 相关资源