乐闻世界logo
搜索文章和话题

面试题手册

Solidity 中的继承机制是如何工作的?抽象合约和接口有什么区别?

Solidity 支持面向对象编程的继承特性,允许合约复用代码、建立层次关系。同时,抽象合约和接口提供了定义标准规范的机制。1. 基础继承Solidity 使用 is 关键字实现继承,支持多重继承。// 父合约contract Animal { string public name; constructor(string memory _name) { name = _name; } function speak() public pure virtual returns (string memory) { return "Some sound"; }}// 子合约继承父合约contract Dog is Animal { constructor(string memory _name) Animal(_name) {} // 重写父合约函数 function speak() public pure override returns (string memory) { return "Woof!"; }}// 多重继承contract PetDog is Animal, Pet { constructor(string memory _name) Animal(_name) Pet(_name) {} function speak() public pure override(Animal, Pet) returns (string memory) { return "Friendly Woof!"; }}2. 构造函数继承子合约需要显式调用父合约的构造函数。contract Parent { uint256 public value; constructor(uint256 _value) { value = _value; }}// 方式 1:在继承列表中传递参数contract Child1 is Parent(100) { // value 自动设置为 100}// 方式 2:在子合约构造函数中传递参数contract Child2 is Parent { constructor(uint256 _value) Parent(_value) { // 可以动态设置 value }}// 方式 3:使用输入参数contract Child3 is Parent { constructor(uint256 _value) Parent(_value * 2) { // 可以对参数进行处理 }}3. 函数重写(Override)使用 virtual 和 override 关键字控制函数重写。contract Base { // virtual 允许子合约重写 function getValue() public pure virtual returns (uint256) { return 1; } // 没有 virtual,不能重写 function fixedValue() public pure returns (uint256) { return 100; }}contract Derived is Base { // override 表示重写父合约函数 function getValue() public pure override returns (uint256) { return 2; } // 错误:不能重写没有 virtual 的函数 // function fixedValue() public pure override returns (uint256) { // return 200; // }}4. 多重继承与线性化Solidity 使用 C3 线性化算法解决多重继承的冲突。contract A { function foo() public pure virtual returns (string memory) { return "A"; }}contract B is A { function foo() public pure virtual override returns (string memory) { return "B"; }}contract C is A { function foo() public pure virtual override returns (string memory) { return "C"; }}// 多重继承:D 继承 B 和 C// 顺序很重要!D is B, C 意味着 B 在 C 前面contract D is B, C { // 必须重写 foo,因为 B 和 C 都重写了 A 的 foo function foo() public pure override(B, C) returns (string memory) { return super.foo(); // 调用 C.foo()(最右边的基类) }}// 顺序不同,结果不同contract E is C, B { function foo() public pure override(C, B) returns (string memory) { return super.foo(); // 调用 B.foo() }}5. 抽象合约(Abstract Contract)抽象合约是包含至少一个未实现函数的合约,不能直接部署。// 抽象合约abstract contract Animal { string public name; constructor(string memory _name) { name = _name; } // 未实现的函数(抽象函数) function speak() public pure virtual returns (string memory); // 已实现的函数 function getName() public view returns (string memory) { return name; }}// 具体合约必须实现所有抽象函数contract Dog is Animal { constructor(string memory _name) Animal(_name) {} function speak() public pure override returns (string memory) { return "Woof!"; }}// 错误:不能直接部署抽象合约// Animal animal = new Animal("Test"); // 编译错误!6. 接口(Interface)接口是更严格的抽象,只能包含函数声明,不能包含实现和状态变量。// ERC20 接口示例interface IERC20 { // 函数声明(没有实现) function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); // 事件 event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value);}// 实现接口contract MyToken is IERC20 { mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; function totalSupply() external view override returns (uint256) { return _totalSupply; } function balanceOf(address account) external view override returns (uint256) { return _balances[account]; } function transfer(address to, uint256 amount) external override returns (bool) { // 实现逻辑... return true; } function allowance(address owner, address spender) external view override returns (uint256) { return _allowances[owner][spender]; } function approve(address spender, uint256 amount) external override returns (bool) { // 实现逻辑... return true; } function transferFrom(address from, address to, uint256 amount) external override returns (bool) { // 实现逻辑... return true; }}7. 抽象合约 vs 接口对比| 特性 | 抽象合约 (Abstract Contract) | 接口 (Interface) || ------ | ------------------------ | -------------- || 构造函数 | ✅ 可以有 | ❌ 不能有 || 状态变量 | ✅ 可以有 | ❌ 不能有 || 函数实现 | ✅ 可以有 | ❌ 不能有 || 函数可见性 | 任意 | 必须是 external || 继承其他合约 | ✅ 可以 | ❌ 不能(只能继承接口) || 修饰符 | ✅ 可以有 | ❌ 不能有 || 使用场景 | 部分实现的基础合约 | 完全抽象的规范定义 |8. 实际应用示例使用抽象合约构建基础功能abstract contract Ownable { address public owner; constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; }}abstract contract Pausable is Ownable { bool public paused; modifier whenNotPaused() { require(!paused, "Paused"); _; } function pause() public onlyOwner { paused = true; } function unpause() public onlyOwner { paused = false; }}contract MyContract is Pausable { function sensitiveOperation() public whenNotPaused { // 只有未暂停时才能执行 }}使用接口实现标准兼容interface IERC721 { function balanceOf(address owner) external view returns (uint256 balance); function ownerOf(uint256 tokenId) external view returns (address owner); function safeTransferFrom(address from, address to, uint256 tokenId) external; function transferFrom(address from, address to, uint256 tokenId) external; function approve(address to, uint256 tokenId) external; function getApproved(uint256 tokenId) external view returns (address operator); function setApprovalForAll(address operator, bool _approved) external; function isApprovedForAll(address owner, address operator) external view returns (bool);}contract NFTMarketplace { // 使用接口与任何 ERC721 合约交互 function buyNFT(address nftContract, uint256 tokenId) public payable { IERC721 nft = IERC721(nftContract); address seller = nft.ownerOf(tokenId); // 验证支付... nft.safeTransferFrom(seller, msg.sender, tokenId); }}9. 继承的最佳实践使用 OpenZeppelin 标准库:import "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@openzeppelin/contracts/access/Ownable.sol";contract MyToken is ERC20, Ownable { constructor() ERC20("MyToken", "MTK") { _mint(msg.sender, 1000000 * 10 ** decimals()); }}注意继承顺序:// 正确:基础合约在前contract Good is Ownable, Pausable, ERC20 {}// 避免:顺序混乱可能导致意外行为contract Bad is ERC20, Pausable, Ownable {}明确使用 override:function transfer(address to, uint256 amount) public override(ERC20, IERC20) // 明确指定重写的接口 returns (bool) { return super.transfer(to, amount);}合理使用抽象和接口:需要部分实现时用抽象合约定义标准规范时用接口避免过度设计,保持简单
阅读 0·3月6日 21:50

Solidity 中事件(Event)的作用是什么?如何优化 Gas 成本?

事件(Event)是 Solidity 中一种重要的日志记录机制,用于在区块链上记录特定操作的发生,供外部应用监听和查询。事件的基本概念定义:事件是合约与外部世界通信的方式,将数据写入区块链日志,供 DApp 前端监听。特点:数据存储在交易收据的日志中,不占用合约 storage比 storage 存储便宜得多(每个 topic 消耗 375 Gas,每个数据字节 8 Gas)可以被前端应用通过 RPC 订阅和监听支持索引参数,便于高效过滤查询事件的基本用法contract EventExample { // 定义事件 event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 value); event Log(string message); function transfer(address to, uint256 amount) public { // 执行转账逻辑... // 触发事件 emit Transfer(msg.sender, to, amount); } function approve(address spender, uint256 value) public { // 执行授权逻辑... emit Approval(msg.sender, spender, value); }}索引参数(Indexed Parameters)最多可以对 3 个参数使用 indexed 关键字,这些参数会被存储为 topics,便于过滤查询。contract IndexedEventExample { // indexed 参数最多 3 个 event Transfer( address indexed from, // topic[1] address indexed to, // topic[2] uint256 amount // data ); // 3 个 indexed 参数 event OrderCreated( bytes32 indexed orderId, // topic[1] address indexed buyer, // topic[2] address indexed seller, // topic[3] uint256 amount, // data uint256 timestamp // data ); function createOrder(address seller, uint256 amount) public { bytes32 orderId = keccak256(abi.encodePacked(msg.sender, block.timestamp)); emit OrderCreated(orderId, msg.sender, seller, amount, block.timestamp); }}事件的 Gas 成本分析| 操作 | Gas 成本 || ------------------ | ---------- || 基础事件成本 | 375 Gas || 每个 indexed topic | 375 Gas || 每个数据字节 | 8 Gas || 存储到 storage(32 字节) | 20,000 Gas |对比示例:contract GasComparison { // 方式 1:使用 storage(昂贵) struct Transaction { address from; address to; uint256 amount; uint256 timestamp; } Transaction[] public transactions; function recordWithStorage(address to, uint256 amount) public { transactions.push(Transaction(msg.sender, to, amount, block.timestamp)); // Gas 成本:约 20,000+ Gas } // 方式 2:使用事件(便宜) event TransactionRecorded( address indexed from, address indexed to, uint256 amount, uint256 timestamp ); function recordWithEvent(address to, uint256 amount) public { emit TransactionRecorded(msg.sender, to, amount, block.timestamp); // Gas 成本:约 1,000-2,000 Gas }}Gas 优化技巧1. 合理使用 indexed 参数contract IndexedOptimization { // 不推荐:对不需要过滤的大字段使用 indexed event BadEvent(string indexed largeData); // 浪费 Gas // 推荐:只对需要过滤的地址或 ID 使用 indexed event GoodEvent( address indexed user, // 需要按用户过滤 uint256 indexed itemId, // 需要按物品过滤 string description // 不需要过滤,不使用 indexed );}2. 减少事件参数数量contract ParameterOptimization { // 不推荐:包含过多参数 event VerboseEvent( address user, uint256 amount, uint256 timestamp, uint256 blockNumber, bytes32 txHash, string metadata ); // 推荐:只包含必要信息 event OptimizedEvent( address indexed user, uint256 amount, string metadata ); // 时间戳和区块号可以通过交易信息获取}3. 使用匿名事件匿名事件不使用事件签名作为 topic[0],可以节省 Gas。contract AnonymousEvent { // 匿名事件,节省一个 topic event QuickLog(address indexed user, uint256 amount) anonymous; function quickLog(uint256 amount) public { emit QuickLog(msg.sender, amount); // 节省约 375 Gas }}4. 批量事件 vs 单个事件contract BatchEvent { // 方式 1:逐个触发事件(Gas 高) event SingleTransfer(address indexed to, uint256 amount); function batchTransferV1(address[] calldata recipients, uint256[] calldata amounts) public { for (uint i = 0; i < recipients.length; i++) { // 执行转账... emit SingleTransfer(recipients[i], amounts[i]); // 每个事件约 375+ Gas } } // 方式 2:批量事件(更优) event BatchTransfer(address[] recipients, uint256[] amounts); function batchTransferV2(address[] calldata recipients, uint256[] calldata amounts) public { // 执行批量转账... emit BatchTransfer(recipients, amounts); // 单个事件,更省 Gas }}事件的实际应用场景1. ERC20 Token 标准事件interface IERC20 { event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value);}2. DeFi 协议事件contract DeFiProtocol { event Deposit( address indexed user, address indexed token, uint256 amount, uint256 shares ); event Withdraw( address indexed user, address indexed token, uint256 amount, uint256 shares ); event RewardClaimed( address indexed user, address indexed rewardToken, uint256 amount ); function deposit(address token, uint256 amount) external { // 存款逻辑... uint256 shares = calculateShares(amount); emit Deposit(msg.sender, token, amount, shares); }}前端监听事件// 使用 ethers.js 监听事件const contract = new ethers.Contract(address, abi, provider);// 监听所有 Transfer 事件contract.on("Transfer", (from, to, amount, event) => { console.log(`Transfer: ${from} -> ${to}, Amount: ${amount}`);});// 过滤特定地址的事件const filter = contract.filters.Transfer(userAddress, null);contract.on(filter, (from, to, amount, event) => { console.log(`Outgoing transfer from ${from}`);});// 查询历史事件const events = await contract.queryFilter("Transfer", fromBlock, toBlock);最佳实践对关键操作触发事件:所有状态变更都应该有对应的事件合理使用 indexed:只对需要过滤查询的参数使用 indexed事件命名规范:使用过去时态(如 Transfer、Approved)包含完整信息:事件中包含足够的信息,避免前端需要额外查询Gas 优化:对于高频操作,考虑批量事件或匿名事件文档化:在合约文档中说明所有事件及其用途
阅读 0·3月6日 21:49

Solidity 中如何实现一个去中心化交易所(DEX)的核心功能?

去中心化交易所(DEX)是 DeFi 生态的核心组件。实现一个基础的 DEX 需要理解自动做市商(AMM)机制、流动性池、价格计算等核心概念。1. DEX 核心概念/*DEX 核心组件:1. 流动性池(Liquidity Pool) - 包含两种代币的储备 - 使用恒定乘积公式:x * y = k2. 自动做市商(AMM) - 无需订单簿 - 算法自动定价 - 流动性提供者赚取手续费3. 价格计算 - 基于储备比例 - 考虑滑点和手续费4. 流动性代币(LP Token) - 代表流动性份额 - 可赎回底层资产*/2. 基础 AMM 实现contract BasicAMM { // 代币接口 IERC20 public token0; IERC20 public token1; // 储备量 uint256 public reserve0; uint256 public reserve1; // 流动性代币总量 uint256 public totalSupply; mapping(address => uint256) public balanceOf; // 手续费(0.3% = 30 / 10000) uint256 public constant FEE = 30; uint256 public constant FEE_DENOMINATOR = 10000; // 最小流动性(防止除零) uint256 public constant MINIMUM_LIQUIDITY = 1000; // 事件 event Mint(address indexed sender, uint256 amount0, uint256 amount1); event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); event Swap( address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to ); event Sync(uint256 reserve0, uint256 reserve1); constructor(address _token0, address _token1) { token0 = IERC20(_token0); token1 = IERC20(_token1); } // 添加流动性 function addLiquidity( uint256 _amount0Desired, uint256 _amount1Desired, uint256 _amount0Min, uint256 _amount1Min, address _to ) external returns (uint256 liquidity) { // 计算需要添加的数量 (uint256 amount0, uint256 amount1) = _calculateLiquidity( _amount0Desired, _amount1Desired, _amount0Min, _amount1Min ); // 转账 token0.transferFrom(msg.sender, address(this), amount0); token1.transferFrom(msg.sender, address(this), amount1); // 计算流动性代币 if (totalSupply == 0) { liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY; _mint(address(0), MINIMUM_LIQUIDITY); // 永久锁定 } else { liquidity = min( (amount0 * totalSupply) / reserve0, (amount1 * totalSupply) / reserve1 ); } require(liquidity > 0, "Insufficient liquidity minted"); _mint(_to, liquidity); // 更新储备 _updateReserves(); emit Mint(msg.sender, amount0, amount1); } // 移除流动性 function removeLiquidity( uint256 _liquidity, uint256 _amount0Min, uint256 _amount1Min, address _to ) external returns (uint256 amount0, uint256 amount1) { // 计算可赎回的数量 uint256 balance0 = token0.balanceOf(address(this)); uint256 balance1 = token1.balanceOf(address(this)); amount0 = (_liquidity * balance0) / totalSupply; amount1 = (_liquidity * balance1) / totalSupply; require(amount0 >= _amount0Min, "Insufficient amount0"); require(amount1 >= _amount1Min, "Insufficient amount1"); // 销毁流动性代币 _burn(msg.sender, _liquidity); // 转账 token0.transfer(_to, amount0); token1.transfer(_to, amount1); // 更新储备 _updateReserves(); emit Burn(msg.sender, amount0, amount1, _to); } // 交换代币(token0 -> token1) function swap0For1( uint256 _amount0In, uint256 _amount1OutMin, address _to ) external returns (uint256 amount1Out) { require(_amount0In > 0, "Insufficient input amount"); // 计算输出数量(考虑手续费) amount1Out = getAmountOut(_amount0In, reserve0, reserve1); require(amount1Out >= _amount1OutMin, "Insufficient output amount"); // 转账 token0.transferFrom(msg.sender, address(this), _amount0In); token1.transfer(_to, amount1Out); // 更新储备 _updateReserves(); emit Swap(msg.sender, _amount0In, 0, 0, amount1Out, _to); } // 交换代币(token1 -> token0) function swap1For0( uint256 _amount1In, uint256 _amount0OutMin, address _to ) external returns (uint256 amount0Out) { require(_amount1In > 0, "Insufficient input amount"); amount0Out = getAmountOut(_amount1In, reserve1, reserve0); require(amount0Out >= _amount0OutMin, "Insufficient output amount"); token1.transferFrom(msg.sender, address(this), _amount1In); token0.transfer(_to, amount0Out); _updateReserves(); emit Swap(msg.sender, 0, _amount1In, amount0Out, 0, _to); } // 计算输出数量(恒定乘积公式) function getAmountOut( uint256 _amountIn, uint256 _reserveIn, uint256 _reserveOut ) public pure returns (uint256 amountOut) { require(_amountIn > 0, "Insufficient input amount"); require(_reserveIn > 0 && _reserveOut > 0, "Insufficient liquidity"); uint256 amountInWithFee = _amountIn * (FEE_DENOMINATOR - FEE); uint256 numerator = amountInWithFee * _reserveOut; uint256 denominator = (_reserveIn * FEE_DENOMINATOR) + amountInWithFee; amountOut = numerator / denominator; } // 计算输入数量 function getAmountIn( uint256 _amountOut, uint256 _reserveIn, uint256 _reserveOut ) public pure returns (uint256 amountIn) { require(_amountOut > 0, "Insufficient output amount"); require(_reserveIn > 0 && _reserveOut > 0, "Insufficient liquidity"); uint256 numerator = _reserveIn * _amountOut * FEE_DENOMINATOR; uint256 denominator = (_reserveOut - _amountOut) * (FEE_DENOMINATOR - FEE); amountIn = (numerator / denominator) + 1; } // 计算流动性 function _calculateLiquidity( uint256 _amount0Desired, uint256 _amount1Desired, uint256 _amount0Min, uint256 _amount1Min ) internal view returns (uint256 amount0, uint256 amount1) { if (reserve0 == 0 && reserve1 == 0) { // 首次添加流动性 (amount0, amount1) = (_amount0Desired, _amount1Desired); } else { // 按比例添加 uint256 amount1Optimal = (_amount0Desired * reserve1) / reserve0; if (amount1Optimal <= _amount1Desired) { require(amount1Optimal >= _amount1Min, "Insufficient amount1"); (amount0, amount1) = (_amount0Desired, amount1Optimal); } else { uint256 amount0Optimal = (_amount1Desired * reserve0) / reserve1; assert(amount0Optimal <= _amount0Desired); require(amount0Optimal >= _amount0Min, "Insufficient amount0"); (amount0, amount1) = (amount0Optimal, _amount1Desired); } } } // 更新储备 function _updateReserves() internal { reserve0 = token0.balanceOf(address(this)); reserve1 = token1.balanceOf(address(this)); emit Sync(reserve0, reserve1); } // 流动性代币操作 function _mint(address _to, uint256 _amount) internal { totalSupply += _amount; balanceOf[_to] += _amount; } function _burn(address _from, uint256 _amount) internal { balanceOf[_from] -= _amount; totalSupply -= _amount; } // 数学函数 function sqrt(uint256 y) internal pure returns (uint256 z) { if (y > 3) { z = y; uint256 x = y / 2 + 1; while (x < z) { z = x; x = (y / x + x) / 2; } } else if (y != 0) { z = 1; } } function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; }}interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool);}3. 路由合约contract Router { address public factory; constructor(address _factory) { factory = _factory; } // 添加流动性 function addLiquidity( address _tokenA, address _tokenB, uint256 _amountADesired, uint256 _amountBDesired, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity) { require(block.timestamp <= _deadline, "Expired"); // 获取或创建交易对 address pair = Factory(factory).getPair(_tokenA, _tokenB); if (pair == address(0)) { pair = Factory(factory).createPair(_tokenA, _tokenB); } // 转账代币到交易对 IERC20(_tokenA).transferFrom(msg.sender, pair, _amountADesired); IERC20(_tokenB).transferFrom(msg.sender, pair, _amountBDesired); // 添加流动性 liquidity = BasicAMM(pair).addLiquidity( _amountADesired, _amountBDesired, _amountAMin, _amountBMin, _to ); // 返回实际添加的数量 amountA = _amountADesired; amountB = _amountBDesired; } // 移除流动性 function removeLiquidity( address _tokenA, address _tokenB, uint256 _liquidity, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline ) external returns (uint256 amountA, uint256 amountB) { require(block.timestamp <= _deadline, "Expired"); address pair = Factory(factory).getPair(_tokenA, _tokenB); require(pair != address(0), "Pair does not exist"); // 转账 LP 代币到交易对 IERC20(pair).transferFrom(msg.sender, pair, _liquidity); // 移除流动性 (amountA, amountB) = BasicAMM(pair).removeLiquidity( _liquidity, _amountAMin, _amountBMin, _to ); } // 交换代币(精确输入) function swapExactTokensForTokens( uint256 _amountIn, uint256 _amountOutMin, address[] calldata _path, address _to, uint256 _deadline ) external returns (uint256[] memory amounts) { require(block.timestamp <= _deadline, "Expired"); require(_path.length >= 2, "Invalid path"); amounts = getAmountsOut(_amountIn, _path); require(amounts[amounts.length - 1] >= _amountOutMin, "Insufficient output"); // 转账第一个代币到第一个交易对 IERC20(_path[0]).transferFrom( msg.sender, Factory(factory).getPair(_path[0], _path[1]), amounts[0] ); // 执行交换 _swap(amounts, _path, _to); } // 交换代币(精确输出) function swapTokensForExactTokens( uint256 _amountOut, uint256 _amountInMax, address[] calldata _path, address _to, uint256 _deadline ) external returns (uint256[] memory amounts) { require(block.timestamp <= _deadline, "Expired"); require(_path.length >= 2, "Invalid path"); amounts = getAmountsIn(_amountOut, _path); require(amounts[0] <= _amountInMax, "Excessive input"); IERC20(_path[0]).transferFrom( msg.sender, Factory(factory).getPair(_path[0], _path[1]), amounts[0] ); _swap(amounts, _path, _to); } // 内部交换函数 function _swap( uint256[] memory _amounts, address[] memory _path, address _to ) internal { for (uint i = 0; i < _path.length - 1; i++) { (address input, address output) = (_path[i], _path[i + 1]); address pair = Factory(factory).getPair(input, output); uint256 amountOut = _amounts[i + 1]; (uint256 amount0Out, uint256 amount1Out) = input < output ? (uint256(0), amountOut) : (amountOut, uint256(0)); address to = i < _path.length - 2 ? Factory(factory).getPair(output, _path[i + 2]) : _to; BasicAMM(pair).swap(amount0Out, amount1Out, to); } } // 计算输出数量(多跳) function getAmountsOut( uint256 _amountIn, address[] memory _path ) public view returns (uint256[] memory amounts) { require(_path.length >= 2, "Invalid path"); amounts = new uint256[](_path.length); amounts[0] = _amountIn; for (uint i = 0; i < _path.length - 1; i++) { address pair = Factory(factory).getPair(_path[i], _path[i + 1]); require(pair != address(0), "Pair does not exist"); (uint256 reserveIn, uint256 reserveOut) = getReserves(pair, _path[i], _path[i + 1]); amounts[i + 1] = BasicAMM(pair).getAmountOut(amounts[i], reserveIn, reserveOut); } } // 计算输入数量(多跳) function getAmountsIn( uint256 _amountOut, address[] memory _path ) public view returns (uint256[] memory amounts) { require(_path.length >= 2, "Invalid path"); amounts = new uint256[](_path.length); amounts[amounts.length - 1] = _amountOut; for (uint i = _path.length - 1; i > 0; i--) { address pair = Factory(factory).getPair(_path[i - 1], _path[i]); require(pair != address(0), "Pair does not exist"); (uint256 reserveIn, uint256 reserveOut) = getReserves(pair, _path[i - 1], _path[i]); amounts[i - 1] = BasicAMM(pair).getAmountIn(amounts[i], reserveIn, reserveOut); } } // 获取储备 function getReserves( address _pair, address _tokenA, address _tokenB ) internal view returns (uint256 reserveA, uint256 reserveB) { (uint256 reserve0, uint256 reserve1) = BasicAMM(_pair).getReserves(); (reserveA, reserveB) = _tokenA < _tokenB ? (reserve0, reserve1) : (reserve1, reserve0); }}// 工厂合约contract Factory { mapping(address => mapping(address => address)) public getPair; address[] public allPairs; event PairCreated(address indexed token0, address indexed token1, address pair, uint256); function createPair(address _tokenA, address _tokenB) external returns (address pair) { require(_tokenA != _tokenB, "Identical addresses"); (address token0, address token1) = _tokenA < _tokenB ? (_tokenA, _tokenB) : (_tokenB, _tokenA); require(token0 != address(0), "Zero address"); require(getPair[token0][token1] == address(0), "Pair exists"); // 创建交易对合约(简化版,实际使用 create2) bytes memory bytecode = type(BasicAMM).creationCode; bytes32 salt = keccak256(abi.encodePacked(token0, token1)); assembly { pair := create2(0, add(bytecode, 32), mload(bytecode), salt) } BasicAMM(pair).initialize(token0, token1); getPair[token0][token1] = pair; getPair[token1][token0] = pair; allPairs.push(pair); emit PairCreated(token0, token1, pair, allPairs.length); }}4. 价格预言机contract PriceOracle { // 累积价格 uint256 public price0CumulativeLast; uint256 public price1CumulativeLast; // 上次更新时间 uint32 public blockTimestampLast; // 储备量 uint112 public reserve0; uint112 public reserve1; // 更新累积价格 function _update( uint256 _balance0, uint256 _balance1, uint112 _reserve0, uint112 _reserve1 ) internal { require( _balance0 <= type(uint112).max && _balance1 <= type(uint112).max, "Overflow" ); uint32 blockTimestamp = uint32(block.timestamp % 2**32); uint32 timeElapsed = blockTimestamp - blockTimestampLast; if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { // 累积价格 = 价格 * 时间 price0CumulativeLast += uint256(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; price1CumulativeLast += uint256(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed; } reserve0 = uint112(_balance0); reserve1 = uint112(_balance1); blockTimestampLast = blockTimestamp; } // 获取当前价格 function getCurrentPrice() external view returns (uint256 price0, uint256 price1) { price0 = (uint256(reserve1) * 1e18) / reserve0; price1 = (uint256(reserve0) * 1e18) / reserve1; } // 计算 TWAP(时间加权平均价格) function consult( address _token, uint256 _amountIn ) external view returns (uint256 amountOut) { // 简化的 TWAP 计算 if (_token == token0) { amountOut = (_amountIn * reserve1) / reserve0; } else { amountOut = (_amountIn * reserve0) / reserve1; } }}// UQ112x112 库(定点数运算)library UQ112x112 { uint224 constant Q112 = 2**112; function encode(uint112 y) internal pure returns (uint224 z) { z = uint224(y) * Q112; } function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) { z = x / uint224(y); }}5. 闪电贷功能contract FlashSwap { // 闪电贷回调接口 interface IFlashSwapCallee { function uniswapV2Call( address sender, uint256 amount0, uint256 amount1, bytes calldata data ) external; } // 执行闪电贷 function swap( uint256 _amount0Out, uint256 _amount1Out, address _to, bytes calldata _data ) external { require(_amount0Out > 0 || _amount1Out > 0, "Insufficient output"); // 转账给接收者 if (_amount0Out > 0) token0.transfer(_to, _amount0Out); if (_amount1Out > 0) token1.transfer(_to, _amount1Out); // 如果有数据,执行回调 if (_data.length > 0) { IFlashSwapCallee(_to).uniswapV2Call( msg.sender, _amount0Out, _amount1Out, _data ); } // 验证偿还 uint256 balance0 = token0.balanceOf(address(this)); uint256 balance1 = token1.balanceOf(address(this)); uint256 amount0In = balance0 > reserve0 - _amount0Out ? balance0 - (reserve0 - _amount0Out) : 0; uint256 amount1In = balance1 > reserve1 - _amount1Out ? balance1 - (reserve1 - _amount1Out) : 0; require(amount0In > 0 || amount1In > 0, "Insufficient input"); // 验证恒定乘积 uint256 balance0Adjusted = balance0 * 1000 - amount0In * 3; uint256 balance1Adjusted = balance1 * 1000 - amount1In * 3; require( balance0Adjusted * balance1Adjusted >= uint256(reserve0) * reserve1 * 1000**2, "K" ); _update(balance0, balance1, reserve0, reserve1); }}// 闪电贷套利示例contract Arbitrageur is IFlashSwapCallee { address public owner; constructor() { owner = msg.sender; } function executeArbitrage( address _pair, uint256 _amount0, uint256 _amount1, bytes calldata _data ) external { // 触发闪电贷 FlashSwap(_pair).swap(_amount0, _amount1, address(this), _data); } function uniswapV2Call( address _sender, uint256 _amount0, uint256 _amount1, bytes calldata _data ) external override { // 解码数据 (address targetPair, uint256 repayAmount) = abi.decode(_data, (address, uint256)); // 执行套利逻辑 // 1. 在其他交易所卖出 // 2. 获得利润 // 3. 偿还闪电贷 // 偿还贷款(加上手续费) if (_amount0 > 0) { IERC20(token0).transfer(msg.sender, repayAmount); } if (_amount1 > 0) { IERC20(token1).transfer(msg.sender, repayAmount); } // 发送利润给所有者 // ... }}6. DEX 安全考虑contract DEXSecurity { /* DEX 安全要点: 1. 重入攻击防护 - 使用 Checks-Effects-Interactions 模式 - 使用 ReentrancyGuard 2. 价格操纵防护 - 使用 TWAP 预言机 - 设置最大滑点 - 交易限额 3. 无常损失管理 - 流动性提供者教育 - 激励设计 4. 前端运行防护 - 提交-揭示模式 - 批量拍卖 5. 溢出检查 - Solidity 0.8.0+ 自动检查 - 或使用 SafeMath */}// 安全增强的 AMMcontract SecureAMM is ReentrancyGuard { // 最大滑点(1%) uint256 public constant MAX_SLIPPAGE = 100; uint256 public constant SLIPPAGE_DENOMINATOR = 10000; // 交易限额 uint256 public maxSwapAmount; // 暂停功能 bool public paused; address public guardian; modifier whenNotPaused() { require(!paused, "Paused"); _; } modifier onlyGuardian() { require(msg.sender == guardian, "Not guardian"); _; } // 带滑点保护的交换 function swapWithSlippageProtection( uint256 _amountIn, uint256 _minAmountOut, address _to ) external nonReentrant whenNotPaused { require(_amountIn <= maxSwapAmount, "Exceeds max swap"); uint256 amountOut = getAmountOut(_amountIn); // 计算预期输出 uint256 expectedOut = (_amountIn * reserve1) / reserve0; uint256 slippage = ((expectedOut - amountOut) * SLIPPAGE_DENOMINATOR) / expectedOut; require(slippage <= MAX_SLIPPAGE, "Slippage too high"); require(amountOut >= _minAmountOut, "Insufficient output"); // 执行交换... } // 紧急暂停 function pause() external onlyGuardian { paused = true; } function unpause() external onlyGuardian { paused = false; }}import "@openzeppelin/contracts/security/ReentrancyGuard.sol";7. 总结DEX 核心功能实现要点:AMM 机制:恒定乘积公式 x * y = k自动价格发现无需订单簿流动性管理:添加/移除流动性LP 代币代表份额按比例存入交易功能:精确输入/输出交换多跳路由滑点保护高级功能:价格预言机闪电贷治理集成安全考虑:重入防护价格操纵防护紧急暂停访问控制Gas 优化:使用自定义错误批量操作存储优化
阅读 0·3月6日 21:49

Solidity 中如何实现合约升级模式?有哪些常见的升级方案?

合约升级是智能合约开发中的重要话题。由于区块链的不可篡改性,一旦合约部署就无法修改,因此需要设计升级机制来修复漏洞或添加新功能。1. 为什么需要合约升级// 问题:合约部署后无法修改contract ImmutableContract { uint256 public value = 100; // 如果发现 bug,无法直接修复 function setValue(uint256 _value) external { value = _value; // 假设这里有逻辑错误 }}// 解决方案:使用代理模式实现升级2. 代理模式(Proxy Pattern)基本原理代理模式通过分离存储和逻辑来实现升级:Proxy 合约:持有状态存储,委托调用到 Implementation 合约Implementation 合约:包含业务逻辑,可以被替换// 简单的代理合约contract SimpleProxy { address public implementation; address public admin; constructor(address _implementation) { implementation = _implementation; admin = msg.sender; } modifier onlyAdmin() { require(msg.sender == admin, "Not admin"); _; } function upgrade(address _newImplementation) external onlyAdmin { implementation = _newImplementation; } // 委托调用到实现合约 fallback() external payable { address impl = implementation; require(impl != address(0), "Implementation not set"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } receive() external payable {}}// 实现合约 V1contract LogicV1 { uint256 public value; function setValue(uint256 _value) external { value = _value; } function getValue() external view returns (uint256) { return value; }}// 实现合约 V2(升级版本)contract LogicV2 { uint256 public value; uint256 public bonus; // 新增状态变量 function setValue(uint256 _value) external { value = _value; bonus = _value / 10; // 新功能:自动计算 10% 奖励 } function getValue() external view returns (uint256) { return value + bonus; }}3. 透明代理模式(Transparent Proxy Pattern)OpenZeppelin 推荐的升级模式,解决了函数选择器冲突问题。// 透明代理合约contract TransparentUpgradeableProxy { // 存储槽位:避免与实现合约冲突 bytes32 private constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); bytes32 private constant ADMIN_SLOT = bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1); constructor(address _logic, address _admin, bytes memory _data) { _setImplementation(_logic); _setAdmin(_admin); if (_data.length > 0) { (bool success, ) = _logic.delegatecall(_data); require(success, "Initialization failed"); } } modifier ifAdmin() { if (msg.sender == _getAdmin()) { _; } else { _fallback(); } } function admin() external ifAdmin returns (address) { return _getAdmin(); } function implementation() external ifAdmin returns (address) { return _getImplementation(); } function upgradeTo(address _newImplementation) external ifAdmin { _setImplementation(_newImplementation); } function upgradeToAndCall(address _newImplementation, bytes calldata _data) external payable ifAdmin { _setImplementation(_newImplementation); (bool success, ) = _newImplementation.delegatecall(_data); require(success, "Upgrade initialization failed"); } function _getImplementation() internal view returns (address impl) { bytes32 slot = IMPLEMENTATION_SLOT; assembly { impl := sload(slot) } } function _setImplementation(address _newImplementation) internal { bytes32 slot = IMPLEMENTATION_SLOT; assembly { sstore(slot, _newImplementation) } } function _getAdmin() internal view returns (address adm) { bytes32 slot = ADMIN_SLOT; assembly { adm := sload(slot) } } function _setAdmin(address _newAdmin) internal { bytes32 slot = ADMIN_SLOT; assembly { sstore(slot, _newAdmin) } } function _fallback() internal { address impl = _getImplementation(); require(impl != address(0), "Implementation not set"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } fallback() external payable { _fallback(); } receive() external payable { _fallback(); }}// 实现合约(使用初始化代替构造函数)contract MyTokenV1 { string public name; string public symbol; uint256 public totalSupply; mapping(address => uint256) public balanceOf; bool private initialized; function initialize( string memory _name, string memory _symbol, uint256 _initialSupply ) public { require(!initialized, "Already initialized"); initialized = true; name = _name; symbol = _symbol; totalSupply = _initialSupply; balanceOf[msg.sender] = _initialSupply; } function transfer(address _to, uint256 _amount) external returns (bool) { require(balanceOf[msg.sender] >= _amount, "Insufficient balance"); balanceOf[msg.sender] -= _amount; balanceOf[_to] += _amount; return true; }}// 升级版本 V2contract MyTokenV2 { string public name; string public symbol; uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; // 新增 bool private initialized; function initialize( string memory _name, string memory _symbol, uint256 _initialSupply ) public { require(!initialized, "Already initialized"); initialized = true; name = _name; symbol = _symbol; totalSupply = _initialSupply; balanceOf[msg.sender] = _initialSupply; } function transfer(address _to, uint256 _amount) external returns (bool) { require(balanceOf[msg.sender] >= _amount, "Insufficient balance"); balanceOf[msg.sender] -= _amount; balanceOf[_to] += _amount; return true; } // 新增功能:授权转账 function approve(address _spender, uint256 _amount) external returns (bool) { allowance[msg.sender][_spender] = _amount; return true; } function transferFrom(address _from, address _to, uint256 _amount) external returns (bool) { require(balanceOf[_from] >= _amount, "Insufficient balance"); require(allowance[_from][msg.sender] >= _amount, "Insufficient allowance"); balanceOf[_from] -= _amount; balanceOf[_to] += _amount; allowance[_from][msg.sender] -= _amount; return true; }}4. UUPS 代理模式(Universal Upgradeable Proxy Standard)更轻量级的升级模式,升级逻辑放在实现合约中。// UUPS 代理合约contract ERC1967Proxy { bytes32 private constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); constructor(address _logic, bytes memory _data) { _setImplementation(_logic); if (_data.length > 0) { (bool success, ) = _logic.delegatecall(_data); require(success, "Initialization failed"); } } function _getImplementation() internal view returns (address impl) { bytes32 slot = IMPLEMENTATION_SLOT; assembly { impl := sload(slot) } } function _setImplementation(address _newImplementation) internal { bytes32 slot = IMPLEMENTATION_SLOT; assembly { sstore(slot, _newImplementation) } } fallback() external payable { address impl = _getImplementation(); require(impl != address(0), "Implementation not set"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } receive() external payable {}}// UUPS 实现合约基类abstract contract UUPSUpgradeable { bytes32 private constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1); modifier onlyProxy() { require(address(this) != __self, "Function must be called through delegatecall"); _; } address private immutable __self = address(this); function proxiableUUID() external view returns (bytes32) { return IMPLEMENTATION_SLOT; } function upgradeTo(address _newImplementation) external onlyProxy { _authorizeUpgrade(_newImplementation); _upgradeToAndCall(_newImplementation, "", false); } function upgradeToAndCall(address _newImplementation, bytes memory _data) external payable onlyProxy { _authorizeUpgrade(_newImplementation); _upgradeToAndCall(_newImplementation, _data, true); } function _authorizeUpgrade(address _newImplementation) internal virtual; function _upgradeToAndCall( address _newImplementation, bytes memory _data, bool _forceCall ) internal { _setImplementation(_newImplementation); if (_data.length > 0 || _forceCall) { (bool success, ) = _newImplementation.delegatecall(_data); require(success, "Upgrade initialization failed"); } } function _setImplementation(address _newImplementation) private { bytes32 slot = IMPLEMENTATION_SLOT; assembly { sstore(slot, _newImplementation) } }}// 使用 UUPS 的合约contract MyUUPSTokenV1 is UUPSUpgradeable { string public name; uint256 public value; address public owner; bool private initialized; function initialize(string memory _name) public { require(!initialized, "Already initialized"); initialized = true; name = _name; owner = msg.sender; } function setValue(uint256 _value) external { value = _value; } function _authorizeUpgrade(address _newImplementation) internal override { require(msg.sender == owner, "Not authorized"); }}// 升级版本contract MyUUPSTokenV2 is UUPSUpgradeable { string public name; uint256 public value; uint256 public multiplier; // 新增 address public owner; bool private initialized; function initialize(string memory _name) public { require(!initialized, "Already initialized"); initialized = true; name = _name; owner = msg.sender; multiplier = 1; // 初始化新变量 } function setValue(uint256 _value) external { value = _value * multiplier; // 新逻辑 } function setMultiplier(uint256 _multiplier) external { require(msg.sender == owner, "Not owner"); multiplier = _multiplier; } function _authorizeUpgrade(address _newImplementation) internal override { require(msg.sender == owner, "Not authorized"); }}5. 钻石模式(Diamond Pattern - EIP-2535)支持多个实现合约,适合大型复杂系统。// 钻石存储结构library LibDiamond { bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); struct FacetAddressAndPosition { address facetAddress; uint96 functionSelectorPosition; } struct FacetFunctionSelectors { bytes4[] functionSelectors; uint256 facetAddressPosition; } struct DiamondStorage { mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition; mapping(address => FacetFunctionSelectors) facetFunctionSelectors; address[] facetAddresses; address contractOwner; } function diamondStorage() internal pure returns (DiamondStorage storage ds) { bytes32 position = DIAMOND_STORAGE_POSITION; assembly { ds.slot := position } } event DiamondCut( address[] _facetAddresses, bytes4[][] _functionSelectors, address _initAddress, bytes _calldata );}// 钻石合约contract Diamond { constructor(address _contractOwner, address _diamondCutFacet) { LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); ds.contractOwner = _contractOwner; // 添加 diamondCut 函数 // ... } fallback() external payable { LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; require(facet != address(0), "Function does not exist"); assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } receive() external payable {}}// 钻石切面(Facet)示例contract TokenFacet { struct TokenStorage { mapping(address => uint256) balances; uint256 totalSupply; } bytes32 constant TOKEN_STORAGE_POSITION = keccak256("token.facet.storage"); function tokenStorage() internal pure returns (TokenStorage storage ts) { bytes32 position = TOKEN_STORAGE_POSITION; assembly { ts.slot := position } } function balanceOf(address _account) external view returns (uint256) { return tokenStorage().balances[_account]; } function transfer(address _to, uint256 _amount) external { TokenStorage storage ts = tokenStorage(); require(ts.balances[msg.sender] >= _amount, "Insufficient balance"); ts.balances[msg.sender] -= _amount; ts.balances[_to] += _amount; }}// 治理切面contract GovernanceFacet { struct GovernanceStorage { mapping(address => uint256) votingPower; uint256 proposalCount; } bytes32 constant GOVERNANCE_STORAGE_POSITION = keccak256("governance.facet.storage"); function createProposal(string memory _description) external { // 创建提案逻辑 } function vote(uint256 _proposalId, bool _support) external { // 投票逻辑 }}6. 升级模式对比| 特性 | 透明代理 | UUPS | 钻石模式 || ------- | ------ | ------ | ------ || Gas 成本 | 较高 | 较低 | 中等 || 复杂度 | 中等 | 低 | 高 || 函数选择器冲突 | 自动解决 | 需手动处理 | 支持多切面 || 升级权限 | 代理合约控制 | 实现合约控制 | 灵活配置 || 适用场景 | 通用场景 | 简单升级 | 大型复杂系统 || 部署成本 | 中等 | 低 | 高 |7. 升级最佳实践存储布局管理// 使用存储槽位避免冲突contract StorageLayout { // 存储槽位 0 uint256 public value1; // 存储槽位 1 address public owner; // 存储槽位 2 mapping(address => uint256) public balances; // 升级时只能追加新变量,不能修改已有变量的顺序和类型 // V2 版本 uint256 public value1; // 槽位 0 - 保持不变 address public owner; // 槽位 1 - 保持不变 mapping(address => uint256) public balances; // 槽位 2 - 保持不变 uint256 public newValue; // 槽位 3 - 新增变量}使用 OpenZeppelin Upgrades// hardhat.config.jsrequire('@openzeppelin/hardhat-upgrades');module.exports = { solidity: '0.8.19',};// 部署脚本const { ethers, upgrades } = require('hardhat');async function main() { const MyToken = await ethers.getContractFactory('MyTokenV1'); // 部署代理 const proxy = await upgrades.deployProxy(MyToken, ['My Token'], { initializer: 'initialize', }); await proxy.deployed(); console.log('Proxy deployed to:', proxy.address); // 升级 const MyTokenV2 = await ethers.getContractFactory('MyTokenV2'); const upgraded = await upgrades.upgradeProxy(proxy.address, MyTokenV2); console.log('Upgraded to:', upgraded.address);}main();安全注意事项// 1. 使用初始化锁防止重复初始化contract Initializable { bool private _initialized; bool private _initializing; modifier initializer() { require( _initializing || !_initialized, "Initializable: contract is already initialized" ); bool isTopLevelCall = !_initializing; if (isTopLevelCall) { _initializing = true; _initialized = true; } _; if (isTopLevelCall) { _initializing = false; } }}// 2. 权限控制contract UpgradeableWithAuth is Initializable { address public admin; modifier onlyAdmin() { require(msg.sender == admin, "Not admin"); _; } function initialize() public initializer { admin = msg.sender; } function upgrade(address _newImplementation) external onlyAdmin { // 升级逻辑 }}// 3. 升级前测试// - 在测试网上充分测试// - 验证存储布局兼容性// - 检查新功能的安全性8. 总结合约升级是智能合约开发的重要技能:透明代理模式:OpenZeppelin 推荐,适合大多数场景UUPS 模式:更轻量,Gas 效率更高钻石模式:适合大型复杂系统,支持模块化升级最佳实践:仔细管理存储布局使用初始化锁充分测试升级过程考虑使用 OpenZeppelin Upgrades 插件
阅读 0·3月6日 21:48

Solidity 中如何实现多签钱包(Multi-Sig Wallet)?

多签钱包(Multi-Signature Wallet)是一种需要多个私钥共同授权才能执行交易的安全机制。它在资产管理、企业治理和 DAO 组织中广泛应用。1. 多签钱包的核心概念/*多签钱包原理:- 设置多个所有者(Owners)- 设定确认阈值(Threshold):需要多少个签名才能执行交易- 例如:3/5 多签表示需要 5 个所有者中的 3 个确认应用场景:- 企业资产管理- DAO 财库- 项目方资金托管- 冷钱包安全存储*/2. 基础多签钱包实现contract MultiSigWallet { // 事件 event Deposit(address indexed sender, uint256 amount); event SubmitTransaction( address indexed owner, uint256 indexed txIndex, address indexed to, uint256 value, bytes data ); event ConfirmTransaction(address indexed owner, uint256 indexed txIndex); event RevokeConfirmation(address indexed owner, uint256 indexed txIndex); event ExecuteTransaction(address indexed owner, uint256 indexed txIndex); event OwnerAdded(address indexed owner); event OwnerRemoved(address indexed owner); event RequirementChanged(uint256 required); // 状态变量 address[] public owners; // 所有者列表 mapping(address => bool) public isOwner; // 是否是所有者 uint256 public numConfirmationsRequired; // 需要的确认数 struct Transaction { address to; // 目标地址 uint256 value; // 转账金额 bytes data; // 调用数据 bool executed; // 是否已执行 uint256 numConfirmations; // 当前确认数 } Transaction[] public transactions; // 交易索引 => 所有者 => 是否已确认 mapping(uint256 => mapping(address => bool)) public isConfirmed; // 修饰器 modifier onlyOwner() { require(isOwner[msg.sender], "Not owner"); _; } modifier txExists(uint256 _txIndex) { require(_txIndex < transactions.length, "Transaction does not exist"); _; } modifier notExecuted(uint256 _txIndex) { require(!transactions[_txIndex].executed, "Transaction already executed"); _; } modifier notConfirmed(uint256 _txIndex) { require(!isConfirmed[_txIndex][msg.sender], "Transaction already confirmed"); _; } // 构造函数 constructor(address[] memory _owners, uint256 _numConfirmationsRequired) { require(_owners.length > 0, "Owners required"); require( _numConfirmationsRequired > 0 && _numConfirmationsRequired <= _owners.length, "Invalid number of confirmations" ); for (uint i = 0; i < _owners.length; i++) { address owner = _owners[i]; require(owner != address(0), "Invalid owner"); require(!isOwner[owner], "Owner not unique"); isOwner[owner] = true; owners.push(owner); } numConfirmationsRequired = _numConfirmationsRequired; } // 接收 ETH receive() external payable { emit Deposit(msg.sender, msg.value); } // 提交交易 function submitTransaction( address _to, uint256 _value, bytes memory _data ) public onlyOwner { uint256 txIndex = transactions.length; transactions.push(Transaction({ to: _to, value: _value, data: _data, executed: false, numConfirmations: 0 })); emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data); } // 确认交易 function confirmTransaction(uint256 _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) notConfirmed(_txIndex) { Transaction storage transaction = transactions[_txIndex]; transaction.numConfirmations += 1; isConfirmed[_txIndex][msg.sender] = true; emit ConfirmTransaction(msg.sender, _txIndex); } // 执行交易 function executeTransaction(uint256 _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) { Transaction storage transaction = transactions[_txIndex]; require( transaction.numConfirmations >= numConfirmationsRequired, "Not enough confirmations" ); transaction.executed = true; (bool success, ) = transaction.to.call{value: transaction.value}( transaction.data ); require(success, "Transaction failed"); emit ExecuteTransaction(msg.sender, _txIndex); } // 撤销确认 function revokeConfirmation(uint256 _txIndex) public onlyOwner txExists(_txIndex) notExecuted(_txIndex) { require(isConfirmed[_txIndex][msg.sender], "Transaction not confirmed"); Transaction storage transaction = transactions[_txIndex]; transaction.numConfirmations -= 1; isConfirmed[_txIndex][msg.sender] = false; emit RevokeConfirmation(msg.sender, _txIndex); } // 查询函数 function getOwners() public view returns (address[] memory) { return owners; } function getTransactionCount() public view returns (uint256) { return transactions.length; } function getTransaction(uint256 _txIndex) public view returns ( address to, uint256 value, bytes memory data, bool executed, uint256 numConfirmations ) { Transaction storage transaction = transactions[_txIndex]; return ( transaction.to, transaction.value, transaction.data, transaction.executed, transaction.numConfirmations ); }}3. 高级多签钱包(支持动态管理)contract AdvancedMultiSigWallet { // 事件 event Deposit(address indexed sender, uint256 amount, uint256 balance); event TransactionSubmitted( uint256 indexed txId, address indexed proposer, address indexed to, uint256 value, bytes data ); event TransactionConfirmed(uint256 indexed txId, address indexed owner); event TransactionRevoked(uint256 indexed txId, address indexed owner); event TransactionExecuted(uint256 indexed txId); event OwnerAdded(address indexed owner); event OwnerRemoved(address indexed owner); event RequirementChanged(uint256 oldRequired, uint256 newRequired); event PauseStatusChanged(bool paused); // 交易类型枚举 enum TransactionType { Transfer, // 转账 ContractCall, // 合约调用 AddOwner, // 添加所有者 RemoveOwner, // 移除所有者 ChangeRequirement // 修改阈值 } struct Transaction { TransactionType txType; address to; uint256 value; bytes data; bool executed; uint256 confirmations; uint256 submissionTime; uint256 executionTime; } // 状态变量 address[] public owners; mapping(address => bool) public isOwner; mapping(address => uint256) public ownerIndex; uint256 public required; // 需要的确认数 Transaction[] public transactions; mapping(uint256 => mapping(address => bool)) public confirmations; mapping(uint256 => mapping(address => uint256)) public confirmationTime; bool public paused; uint256 public constant TIMELOCK_DURATION = 24 hours; // 时间锁 uint256 public constant MAX_OWNERS = 50; uint256 public constant MAX_TRANSACTIONS = 1000; // 修饰器 modifier onlyWallet() { require(msg.sender == address(this), "Only wallet"); _; } modifier onlyOwner() { require(isOwner[msg.sender], "Not owner"); _; } modifier ownerExists(address _owner) { require(isOwner[_owner], "Owner does not exist"); _; } modifier ownerDoesNotExist(address _owner) { require(!isOwner[_owner], "Owner exists"); _; } modifier notNull(address _address) { require(_address != address(0), "Null address"); _; } modifier validRequirement(uint256 _ownerCount, uint256 _required) { require( _required <= _ownerCount && _required != 0 && _ownerCount != 0, "Invalid requirement" ); _; } modifier whenNotPaused() { require(!paused, "Wallet is paused"); _; } // 构造函数 constructor( address[] memory _owners, uint256 _required ) validRequirement(_owners.length, _required) { require(_owners.length <= MAX_OWNERS, "Too many owners"); for (uint i = 0; i < _owners.length; i++) { address owner = _owners[i]; require(!isOwner[owner] && owner != address(0), "Invalid owner"); isOwner[owner] = true; ownerIndex[owner] = owners.length; owners.push(owner); } required = _required; } // 接收 ETH receive() external payable { emit Deposit(msg.sender, msg.value, address(this).balance); } // 暂停功能 function setPaused(bool _paused) external onlyWallet { paused = _paused; emit PauseStatusChanged(_paused); } // 提交转账交易 function submitTransfer( address _to, uint256 _value ) external onlyOwner whenNotPaused returns (uint256 txId) { require(_to != address(0), "Invalid recipient"); require(_value > 0 && _value <= address(this).balance, "Invalid value"); txId = addTransaction(TransactionType.Transfer, _to, _value, ""); confirmTransaction(txId); } // 提交合约调用 function submitContractCall( address _to, uint256 _value, bytes memory _data ) external onlyOwner whenNotPaused returns (uint256 txId) { require(_to != address(0), "Invalid contract"); txId = addTransaction(TransactionType.ContractCall, _to, _value, _data); confirmTransaction(txId); } // 提交添加所有者 function submitAddOwner( address _owner ) external onlyOwner ownerDoesNotExist(_owner) notNull(_owner) whenNotPaused returns (uint256 txId) { require(owners.length < MAX_OWNERS, "Max owners reached"); txId = addTransaction( TransactionType.AddOwner, _owner, 0, "" ); confirmTransaction(txId); } // 提交移除所有者 function submitRemoveOwner( address _owner ) external onlyOwner ownerExists(_owner) whenNotPaused returns (uint256 txId) { require(owners.length > required, "Cannot remove owner"); txId = addTransaction( TransactionType.RemoveOwner, _owner, 0, "" ); confirmTransaction(txId); } // 提交修改阈值 function submitChangeRequirement( uint256 _newRequired ) external onlyOwner whenNotPaused returns (uint256 txId) { require(_newRequired > 0 && _newRequired <= owners.length, "Invalid requirement"); txId = addTransaction( TransactionType.ChangeRequirement, address(uint160(_newRequired)), 0, "" ); confirmTransaction(txId); } // 添加交易到列表 function addTransaction( TransactionType _txType, address _to, uint256 _value, bytes memory _data ) internal returns (uint256 txId) { require(transactions.length < MAX_TRANSACTIONS, "Too many transactions"); txId = transactions.length; transactions.push(Transaction({ txType: _txType, to: _to, value: _value, data: _data, executed: false, confirmations: 0, submissionTime: block.timestamp, executionTime: 0 })); emit TransactionSubmitted(txId, msg.sender, _to, _value, _data); } // 确认交易 function confirmTransaction(uint256 _txId) public onlyOwner whenNotPaused { require(_txId < transactions.length, "Invalid txId"); require(!transactions[_txId].executed, "Already executed"); require(!confirmations[_txId][msg.sender], "Already confirmed"); confirmations[_txId][msg.sender] = true; confirmationTime[_txId][msg.sender] = block.timestamp; transactions[_txId].confirmations++; emit TransactionConfirmed(_txId, msg.sender); // 自动执行 if (transactions[_txId].confirmations >= required) { executeTransaction(_txId); } } // 撤销确认 function revokeConfirmation(uint256 _txId) external onlyOwner whenNotPaused { require(_txId < transactions.length, "Invalid txId"); require(!transactions[_txId].executed, "Already executed"); require(confirmations[_txId][msg.sender], "Not confirmed"); confirmations[_txId][msg.sender] = false; transactions[_txId].confirmations--; emit TransactionRevoked(_txId, msg.sender); } // 执行交易 function executeTransaction(uint256 _txId) public onlyOwner whenNotPaused { Transaction storage txn = transactions[_txId]; require(!txn.executed, "Already executed"); require(txn.confirmations >= required, "Not enough confirmations"); require( block.timestamp >= txn.submissionTime + TIMELOCK_DURATION, "Timelock not expired" ); txn.executed = true; txn.executionTime = block.timestamp; // 根据交易类型执行 if (txn.txType == TransactionType.Transfer || txn.txType == TransactionType.ContractCall) { (bool success, ) = txn.to.call{value: txn.value}(txn.data); require(success, "External call failed"); } else if (txn.txType == TransactionType.AddOwner) { addOwnerInternal(txn.to); } else if (txn.txType == TransactionType.RemoveOwner) { removeOwnerInternal(txn.to); } else if (txn.txType == TransactionType.ChangeRequirement) { changeRequirementInternal(uint256(uint160(txn.to))); } emit TransactionExecuted(_txId); } // 内部函数:添加所有者 function addOwnerInternal(address _owner) internal { isOwner[_owner] = true; ownerIndex[_owner] = owners.length; owners.push(_owner); emit OwnerAdded(_owner); } // 内部函数:移除所有者 function removeOwnerInternal(address _owner) internal { uint256 index = ownerIndex[_owner]; address lastOwner = owners[owners.length - 1]; owners[index] = lastOwner; ownerIndex[lastOwner] = index; owners.pop(); delete isOwner[_owner]; delete ownerIndex[_owner]; emit OwnerRemoved(_owner); } // 内部函数:修改阈值 function changeRequirementInternal(uint256 _newRequired) internal { uint256 oldRequired = required; required = _newRequired; emit RequirementChanged(oldRequired, _newRequired); } // 批量确认 function confirmMultiple(uint256[] calldata _txIds) external onlyOwner whenNotPaused { for (uint i = 0; i < _txIds.length; i++) { confirmTransaction(_txIds[i]); } } // 查询函数 function getOwners() external view returns (address[] memory) { return owners; } function getTransactionCount() external view returns (uint256) { return transactions.length; } function getTransaction(uint256 _txId) external view returns (Transaction memory) { return transactions[_txId]; } function isConfirmedBy(uint256 _txId, address _owner) external view returns (bool) { return confirmations[_txId][_owner]; } function getConfirmationCount(uint256 _txId) external view returns (uint256) { return transactions[_txId].confirmations; } function getConfirmations(uint256 _txId) external view returns (address[] memory) { address[] memory confirmed = new address[](owners.length); uint256 count = 0; for (uint i = 0; i < owners.length; i++) { if (confirmations[_txId][owners[i]]) { confirmed[count] = owners[i]; count++; } } // 调整数组大小 assembly { mstore(confirmed, count) } return confirmed; } function getPendingTransactions() external view returns (uint256[] memory) { uint256[] memory pending = new uint256[](transactions.length); uint256 count = 0; for (uint i = 0; i < transactions.length; i++) { if (!transactions[i].executed) { pending[count] = i; count++; } } assembly { mstore(pending, count) } return pending; } function getBalance() external view returns (uint256) { return address(this).balance; }}4. 使用 Gnosis Safe 模式// 模拟 Gnosis Safe 的核心功能contract GnosisSafeStyle { // 常量 bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"); bytes32 private constant SAFE_TX_TYPEHASH = keccak256("SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"); // 状态变量 mapping(address => bool) public isOwner; address[] public owners; uint256 public threshold; uint256 public nonce; // 交易哈希 => 是否已执行 mapping(bytes32 => bool) public executed; // 交易哈希 => 签名者 => 是否已签名 mapping(bytes32 => mapping(address => bool)) public signed; // 事件 event SafeSetup(address indexed initiator, address[] owners, uint256 threshold); event ApproveHash(bytes32 indexed approvedHash, address indexed owner); event SignMsg(bytes32 indexed msgHash); event ExecutionSuccess(bytes32 indexed txHash); event ExecutionFailure(bytes32 indexed txHash); // 操作类型 enum Operation { Call, DelegateCall } constructor(address[] memory _owners, uint256 _threshold) { require(_threshold > 0 && _threshold <= _owners.length, "Invalid threshold"); for (uint i = 0; i < _owners.length; i++) { address owner = _owners[i]; require(owner != address(0) && !isOwner[owner], "Invalid owner"); isOwner[owner] = true; owners.push(owner); } threshold = _threshold; emit SafeSetup(msg.sender, _owners, _threshold); } // 接收 ETH receive() external payable {} // 获取交易哈希 function getTransactionHash( address to, uint256 value, bytes calldata data, Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, uint256 _nonce ) public view returns (bytes32) { bytes32 safeTxHash = keccak256( abi.encode( SAFE_TX_TYPEHASH, to, value, keccak256(data), operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce ) ); return keccak256( abi.encodePacked( bytes1(0x19), bytes1(0x01), domainSeparator(), safeTxHash ) ); } // 获取域分隔符 function domainSeparator() public view returns (bytes32) { return keccak256( abi.encode( DOMAIN_SEPARATOR_TYPEHASH, block.chainid, address(this) ) ); } // 执行交易(带签名) function execTransaction( address to, uint256 value, bytes calldata data, Operation operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address payable refundReceiver, bytes memory signatures ) public payable returns (bool success) { bytes32 txHash = getTransactionHash( to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, nonce ); nonce++; checkSignatures(txHash, signatures); // 执行交易 if (operation == Operation.DelegateCall) { (success, ) = to.delegatecall(data); } else { (success, ) = to.call{value: value}(data); } if (success) { emit ExecutionSuccess(txHash); } else { emit ExecutionFailure(txHash); } return success; } // 检查签名 function checkSignatures(bytes32 dataHash, bytes memory signatures) public view { require(signatures.length >= threshold * 65, "Not enough signatures"); address lastOwner = address(0); for (uint i = 0; i < threshold; i++) { bytes memory signature = slice(signatures, i * 65, 65); address signer = recoverSigner(dataHash, signature); require(isOwner[signer], "Invalid signer"); require(signer > lastOwner, "Signers not ordered"); lastOwner = signer; } } // 恢复签名者 function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) internal pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); return ecrecover(_ethSignedMessageHash, v, r, s); } // 分割签名 function splitSignature(bytes memory sig) internal pure returns (bytes32 r, bytes32 s, uint8 v) { require(sig.length == 65, "Invalid signature length"); assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } if (v < 27) { v += 27; } } // 切片函数 function slice(bytes memory data, uint256 start, uint256 length) internal pure returns (bytes memory) { bytes memory result = new bytes(length); for (uint i = 0; i < length; i++) { result[i] = data[start + i]; } return result; }}5. 多签钱包的安全考虑contract MultiSigSecurity { /* 安全最佳实践: 1. 阈值设置 - 推荐 2/3、3/5 等多签配置 - 避免 1/1(单签)或 n/n(需要所有人签名) 2. 所有者管理 - 使用硬件钱包作为所有者 - 定期轮换密钥 - 地理分散存储 3. 交易限制 - 设置每日限额 - 大额交易延迟执行 - 白名单地址 4. 应急机制 - 紧急暂停功能 - 资金回收机制 - 升级能力 */}// 带安全限制的多签钱包contract SecureMultiSig { address[] public owners; mapping(address => bool) public isOwner; uint256 public required; // 安全限制 uint256 public dailyLimit; uint256 public spentToday; uint256 public lastDay; mapping(address => bool) public whitelist; bool public whitelistEnabled; modifier onlyOwner() { require(isOwner[msg.sender], "Not owner"); _; } constructor( address[] memory _owners, uint256 _required, uint256 _dailyLimit ) { // ... 初始化代码 dailyLimit = _dailyLimit; } // 检查并更新每日限额 function checkDailyLimit(uint256 _value) internal { if (block.timestamp > lastDay + 1 days) { lastDay = block.timestamp; spentToday = 0; } require( spentToday + _value <= dailyLimit, "Daily limit exceeded" ); spentToday += _value; } // 检查白名单 function checkWhitelist(address _to) internal view { if (whitelistEnabled) { require(whitelist[_to], "Address not whitelisted"); } } // 添加白名单 function addToWhitelist(address _addr) external onlyOwner { whitelist[_addr] = true; } // 移除白名单 function removeFromWhitelist(address _addr) external onlyOwner { whitelist[_addr] = false; } // 设置白名单开关 function setWhitelistEnabled(bool _enabled) external onlyOwner { whitelistEnabled = _enabled; }}6. 测试和部署建议// 使用 Hardhat 测试多签钱包const { expect } = require("chai");const { ethers } = require("hardhat");describe("MultiSigWallet", function () { let multiSig; let owners; let required = 2; beforeEach(async function () { [owner1, owner2, owner3, nonOwner] = await ethers.getSigners(); owners = [owner1.address, owner2.address, owner3.address]; const MultiSig = await ethers.getContractFactory("MultiSigWallet"); multiSig = await MultiSig.deploy(owners, required); await multiSig.deployed(); // 向多签钱包发送 ETH await owner1.sendTransaction({ to: multiSig.address, value: ethers.utils.parseEther("10") }); }); it("Should submit and confirm transaction", async function () { const to = nonOwner.address; const value = ethers.utils.parseEther("1"); // 提交交易 await multiSig.connect(owner1).submitTransaction(to, value, "0x"); // 确认交易 await multiSig.connect(owner2).confirmTransaction(0); // 验证交易已执行 const tx = await multiSig.transactions(0); expect(tx.executed).to.be.true; // 验证余额 const balance = await ethers.provider.getBalance(to); expect(balance).to.equal(value); }); it("Should require enough confirmations", async function () { const to = nonOwner.address; const value = ethers.utils.parseEther("1"); await multiSig.connect(owner1).submitTransaction(to, value, "0x"); // 只有一个确认,不应该执行 const tx = await multiSig.transactions(0); expect(tx.executed).to.be.false; });});7. 总结多签钱包是重要的安全工具:核心机制:多个所有者共同管理阈值确认机制交易生命周期管理实现要点:完整的交易流程(提交-确认-执行)权限控制事件记录查询功能安全考虑:合理的阈值设置时间锁机制交易限额应急处理生产建议:使用经过审计的库(如 Gnosis Safe)充分测试考虑使用代理模式升级实施监控和报警
阅读 0·3月6日 21:47

Solidity 中如何处理时间锁(Timelock)机制?

时间锁(Timelock)是一种安全机制,要求交易在执行前必须经过一段延迟时间。这在 DeFi 协议、治理合约和升级机制中广泛应用,为用户提供反应时间,防止恶意操作。1. 时间锁的核心概念/*时间锁原理:- 交易提交后进入队列- 必须经过最小延迟时间才能执行- 在延迟期内可以取消交易- 超过最大延迟时间后交易过期应用场景:- 协议参数变更- 合约升级- 大额资金转移- 治理提案执行- 紧急功能暂停*/2. 基础时间锁实现contract BasicTimelock { // 事件 event TransactionScheduled( bytes32 indexed txHash, address indexed target, uint256 value, bytes data, uint256 eta ); event TransactionExecuted( bytes32 indexed txHash, address indexed target, uint256 value, bytes data ); event TransactionCancelled(bytes32 indexed txHash); event DelayChanged(uint256 oldDelay, uint256 newDelay); // 状态变量 address public admin; uint256 public delay; // 最小延迟时间 uint256 public constant GRACE_PERIOD = 14 days; // 宽限期 uint256 public constant MINIMUM_DELAY = 2 days; // 最小延迟 uint256 public constant MAXIMUM_DELAY = 30 days; // 最大延迟 // 交易哈希 => 计划执行时间 mapping(bytes32 => uint256) public queuedTransactions; modifier onlyAdmin() { require(msg.sender == admin, "Timelock: caller is not admin"); _; } constructor(address _admin, uint256 _delay) { require(_delay >= MINIMUM_DELAY, "Delay must exceed minimum"); require(_delay <= MAXIMUM_DELAY, "Delay must not exceed maximum"); admin = _admin; delay = _delay; } // 接收 ETH receive() external payable {} // 设置延迟时间 function setDelay(uint256 _delay) public onlyAdmin { require(_delay >= MINIMUM_DELAY, "Delay must exceed minimum"); require(_delay <= MAXIMUM_DELAY, "Delay must not exceed maximum"); emit DelayChanged(delay, _delay); delay = _delay; } // 计算交易哈希 function hashTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) public pure returns (bytes32) { return keccak256(abi.encode(target, value, signature, data, eta)); } // 计划交易 function queueTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) public onlyAdmin returns (bytes32 txHash) { require( eta >= block.timestamp + delay, "Timelock: estimated execution block must satisfy delay" ); txHash = hashTransaction(target, value, signature, data, eta); require( queuedTransactions[txHash] == 0, "Timelock: transaction already queued" ); queuedTransactions[txHash] = eta; emit TransactionScheduled(txHash, target, value, data, eta); } // 取消交易 function cancelTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) public onlyAdmin { bytes32 txHash = hashTransaction(target, value, signature, data, eta); require( queuedTransactions[txHash] != 0, "Timelock: transaction not queued" ); delete queuedTransactions[txHash]; emit TransactionCancelled(txHash); } // 执行交易 function executeTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) public payable onlyAdmin { bytes32 txHash = hashTransaction(target, value, signature, data, eta); require( queuedTransactions[txHash] != 0, "Timelock: transaction not queued" ); require( block.timestamp >= eta, "Timelock: transaction hasn't surpassed time lock" ); require( block.timestamp <= eta + GRACE_PERIOD, "Timelock: transaction is stale" ); delete queuedTransactions[txHash]; bytes memory callData; if (bytes(signature).length == 0) { callData = data; } else { callData = abi.encodePacked( bytes4(keccak256(bytes(signature))), data ); } (bool success, ) = target.call{value: value}(callData); require(success, "Timelock: transaction execution reverted"); emit TransactionExecuted(txHash, target, value, data); }}3. 高级时间锁(支持批量操作)contract AdvancedTimelock { // 事件 event OperationScheduled( bytes32 indexed id, uint256 indexed delay, address indexed target, uint256 value, bytes data ); event OperationExecuted(bytes32 indexed id); event OperationCancelled(bytes32 indexed id); event MinDelayChange(uint256 oldDuration, uint256 newDuration); event RoleGranted(bytes32 indexed role, address indexed account); event RoleRevoked(bytes32 indexed role, address indexed account); // 角色定义 bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE"); bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE"); // 操作状态 enum OperationState { Unset, // 未设置 Pending, // 等待中 Ready, // 可执行 Done, // 已完成 Expired // 已过期 } struct Operation { uint256 delay; // 延迟时间 uint256 scheduledAt; // 计划时间 bool executed; // 是否已执行 } // 状态变量 mapping(bytes32 => Operation) public operations; mapping(bytes32 => mapping(address => bool)) public hasRole; uint256 public minDelay; // 最小延迟 uint256 public maxDelay; // 最大延迟 uint256 public gracePeriod; // 宽限期 bytes32[] public operationIds; // 操作ID列表 modifier onlyRole(bytes32 role) { require(hasRole[role][msg.sender], "Missing role"); _; } constructor( uint256 _minDelay, address[] memory proposers, address[] memory executors ) { require(_minDelay > 0, "Min delay must be > 0"); minDelay = _minDelay; maxDelay = _minDelay * 10; gracePeriod = 14 days; // 设置管理员 _grantRole(TIMELOCK_ADMIN_ROLE, msg.sender); // 设置提议者 for (uint i = 0; i < proposers.length; i++) { _grantRole(PROPOSER_ROLE, proposers[i]); } // 设置执行者 for (uint i = 0; i < executors.length; i++) { _grantRole(EXECUTOR_ROLE, executors[i]); } // 取消者可以是任何人 _grantRole(CANCELLER_ROLE, msg.sender); } function _grantRole(bytes32 role, address account) internal { hasRole[role][account] = true; emit RoleGranted(role, account); } function grantRole(bytes32 role, address account) external onlyRole(TIMELOCK_ADMIN_ROLE) { _grantRole(role, account); } function revokeRole(bytes32 role, address account) external onlyRole(TIMELOCK_ADMIN_ROLE) { hasRole[role][account] = false; emit RoleRevoked(role, account); } // 接收 ETH receive() external payable {} // 计算操作ID function hashOperation( address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt ) public pure returns (bytes32) { return keccak256(abi.encode(target, value, data, predecessor, salt)); } // 批量操作的哈希 function hashOperationBatch( address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt ) public pure returns (bytes32) { return keccak256(abi.encode(targets, values, datas, predecessor, salt)); } // 获取操作状态 function getOperationState(bytes32 id) public view returns (OperationState) { Operation memory op = operations[id]; if (op.scheduledAt == 0) { return OperationState.Unset; } else if (op.executed) { return OperationState.Done; } else if (block.timestamp < op.scheduledAt + op.delay) { return OperationState.Pending; } else if (block.timestamp > op.scheduledAt + op.delay + gracePeriod) { return OperationState.Expired; } else { return OperationState.Ready; } } // 检查是否准备好执行 function isOperationReady(bytes32 id) public view returns (bool) { return getOperationState(id) == OperationState.Ready; } // 检查是否已完成 function isOperationDone(bytes32 id) public view returns (bool) { return getOperationState(id) == OperationState.Done; } // 计划操作 function schedule( address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt, uint256 delay ) public onlyRole(PROPOSER_ROLE) { bytes32 id = hashOperation(target, value, data, predecessor, salt); require( getOperationState(id) == OperationState.Unset, "Operation already scheduled" ); require(delay >= minDelay, "Delay too short"); require(delay <= maxDelay, "Delay too long"); operations[id] = Operation({ delay: delay, scheduledAt: block.timestamp, executed: false }); operationIds.push(id); emit OperationScheduled(id, delay, target, value, data); } // 批量计划 function scheduleBatch( address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt, uint256 delay ) public onlyRole(PROPOSER_ROLE) { require( targets.length == values.length && targets.length == datas.length, "Length mismatch" ); bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt); require( getOperationState(id) == OperationState.Unset, "Operation already scheduled" ); require(delay >= minDelay, "Delay too short"); operations[id] = Operation({ delay: delay, scheduledAt: block.timestamp, executed: false }); operationIds.push(id); for (uint i = 0; i < targets.length; i++) { emit OperationScheduled(id, delay, targets[i], values[i], datas[i]); } } // 取消操作 function cancel(bytes32 id) external onlyRole(CANCELLER_ROLE) { require( getOperationState(id) != OperationState.Unset, "Operation not scheduled" ); require( getOperationState(id) != OperationState.Done, "Operation already done" ); delete operations[id]; emit OperationCancelled(id); } // 执行操作 function execute( address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt ) public payable onlyRole(EXECUTOR_ROLE) { bytes32 id = hashOperation(target, value, data, predecessor, salt); require( getOperationState(id) == OperationState.Ready, "Operation not ready" ); operations[id].executed = true; (bool success, ) = target.call{value: value}(data); require(success, "Execution failed"); emit OperationExecuted(id); } // 批量执行 function executeBatch( address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt ) public payable onlyRole(EXECUTOR_ROLE) { require( targets.length == values.length && targets.length == datas.length, "Length mismatch" ); bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt); require( getOperationState(id) == OperationState.Ready, "Operation not ready" ); operations[id].executed = true; for (uint i = 0; i < targets.length; i++) { (bool success, ) = targets[i].call{value: values[i]}(datas[i]); require(success, "Batch execution failed"); } emit OperationExecuted(id); } // 更新延迟 function updateDelay(uint256 newDelay) external onlyRole(TIMELOCK_ADMIN_ROLE) { require(newDelay >= minDelay, "Delay too short"); require(newDelay <= maxDelay, "Delay too long"); emit MinDelayChange(minDelay, newDelay); minDelay = newDelay; } // 获取待处理操作 function getPendingOperations() external view returns (bytes32[] memory) { uint256 count = 0; for (uint i = 0; i < operationIds.length; i++) { if (getOperationState(operationIds[i]) == OperationState.Pending) { count++; } } bytes32[] memory pending = new bytes32[](count); uint256 index = 0; for (uint i = 0; i < operationIds.length; i++) { if (getOperationState(operationIds[i]) == OperationState.Pending) { pending[index] = operationIds[i]; index++; } } return pending; } // 获取可执行操作 function getReadyOperations() external view returns (bytes32[] memory) { uint256 count = 0; for (uint i = 0; i < operationIds.length; i++) { if (getOperationState(operationIds[i]) == OperationState.Ready) { count++; } } bytes32[] memory ready = new bytes32[](count); uint256 index = 0; for (uint i = 0; i < operationIds.length; i++) { if (getOperationState(operationIds[i]) == OperationState.Ready) { ready[index] = operationIds[i]; index++; } } return ready; }}4. 时间锁在治理中的应用contract GovernanceWithTimelock { // 事件 event ProposalCreated( uint256 indexed id, address proposer, address[] targets, uint256[] values, bytes[] calldatas, uint256 eta ); event ProposalExecuted(uint256 indexed id); event ProposalCancelled(uint256 indexed id); event VoteCast( address indexed voter, uint256 indexed proposalId, bool support, uint256 votes ); // 提案结构 struct Proposal { address proposer; address[] targets; uint256[] values; bytes[] calldatas; uint256 forVotes; uint256 againstVotes; bool executed; bool canceled; uint256 eta; uint256 startBlock; uint256 endBlock; } // 状态变量 address public timelock; address public governanceToken; uint256 public votingDelay; // 投票延迟(区块数) uint256 public votingPeriod; // 投票期(区块数) uint256 public proposalThreshold; // 提案门槛 uint256 public quorumVotes; // 法定票数 uint256 public gracePeriod; // 宽限期 mapping(uint256 => Proposal) public proposals; mapping(uint256 => mapping(address => bool)) public hasVoted; uint256 public proposalCount; constructor( address _timelock, address _governanceToken, uint256 _votingDelay, uint256 _votingPeriod, uint256 _proposalThreshold, uint256 _quorumVotes ) { timelock = _timelock; governanceToken = _governanceToken; votingDelay = _votingDelay; votingPeriod = _votingPeriod; proposalThreshold = _proposalThreshold; quorumVotes = _quorumVotes; gracePeriod = 14 days; } // 创建提案 function propose( address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description ) public returns (uint256) { require( getVotes(msg.sender, block.number - 1) >= proposalThreshold, "Below proposal threshold" ); require( targets.length == values.length && targets.length == calldatas.length, "Length mismatch" ); require(targets.length > 0, "Must provide actions"); proposalCount++; uint256 proposalId = proposalCount; Proposal storage newProposal = proposals[proposalId]; newProposal.proposer = msg.sender; newProposal.targets = targets; newProposal.values = values; newProposal.calldatas = calldatas; newProposal.startBlock = block.number + votingDelay; newProposal.endBlock = block.number + votingDelay + votingPeriod; emit ProposalCreated( proposalId, msg.sender, targets, values, calldatas, 0 ); return proposalId; } // 投票 function castVote(uint256 proposalId, bool support) external { require(state(proposalId) == ProposalState.Active, "Voting closed"); Proposal storage proposal = proposals[proposalId]; require(!hasVoted[proposalId][msg.sender], "Already voted"); uint256 votes = getVotes(msg.sender, proposal.startBlock); require(votes > 0, "No voting power"); hasVoted[proposalId][msg.sender] = true; if (support) { proposal.forVotes += votes; } else { proposal.againstVotes += votes; } emit VoteCast(msg.sender, proposalId, support, votes); } // 排队执行(通过时间锁) function queue(uint256 proposalId) external { require( state(proposalId) == ProposalState.Succeeded, "Proposal not succeeded" ); Proposal storage proposal = proposals[proposalId]; uint256 eta = block.timestamp + AdvancedTimelock(timelock).minDelay(); proposal.eta = eta; // 通过时间锁排队 for (uint i = 0; i < proposal.targets.length; i++) { AdvancedTimelock(timelock).schedule( proposal.targets[i], proposal.values[i], proposal.calldatas[i], bytes32(0), bytes32(proposalId), AdvancedTimelock(timelock).minDelay() ); } } // 执行提案 function execute(uint256 proposalId) external payable { require( state(proposalId) == ProposalState.Queued, "Proposal not queued" ); Proposal storage proposal = proposals[proposalId]; require( block.timestamp >= proposal.eta, "Timelock not passed" ); require( block.timestamp <= proposal.eta + gracePeriod, "Proposal expired" ); proposal.executed = true; // 通过时间锁执行 for (uint i = 0; i < proposal.targets.length; i++) { AdvancedTimelock(timelock).execute( proposal.targets[i], proposal.values[i], proposal.calldatas[i], bytes32(0), bytes32(proposalId) ); } emit ProposalExecuted(proposalId); } // 取消提案 function cancel(uint256 proposalId) external { require( state(proposalId) != ProposalState.Executed, "Already executed" ); Proposal storage proposal = proposals[proposalId]; require( msg.sender == proposal.proposer || getVotes(proposal.proposer, block.number - 1) < proposalThreshold, "Not authorized" ); proposal.canceled = true; // 取消时间锁中的操作 for (uint i = 0; i < proposal.targets.length; i++) { bytes32 id = keccak256(abi.encode( proposal.targets[i], proposal.values[i], proposal.calldatas[i], bytes32(0), bytes32(proposalId) )); if (AdvancedTimelock(timelock).getOperationState(id) != AdvancedTimelock.OperationState.Unset) { AdvancedTimelock(timelock).cancel(id); } } emit ProposalCancelled(proposalId); } // 提案状态枚举 enum ProposalState { Pending, Active, Canceled, Defeated, Succeeded, Queued, Expired, Executed } // 获取提案状态 function state(uint256 proposalId) public view returns (ProposalState) { require(proposalCount >= proposalId && proposalId > 0, "Invalid id"); Proposal storage proposal = proposals[proposalId]; if (proposal.canceled) { return ProposalState.Canceled; } else if (proposal.executed) { return ProposalState.Executed; } else if (block.number <= proposal.startBlock) { return ProposalState.Pending; } else if (block.number <= proposal.endBlock) { return ProposalState.Active; } else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes) { return ProposalState.Defeated; } else if (proposal.eta == 0) { return ProposalState.Succeeded; } else if (block.timestamp >= proposal.eta + gracePeriod) { return ProposalState.Expired; } else { return ProposalState.Queued; } } // 获取投票权 function getVotes(address account, uint256 blockNumber) public view returns (uint256) { // 简化实现,实际应查询治理代币 return 1000; }}5. 时间锁安全最佳实践contract TimelockSecurity { /* 安全最佳实践: 1. 延迟时间设置 - 最小延迟:2-3 天(给用户反应时间) - 最大延迟:30 天(防止无限期延迟) - 宽限期:14 天(过期后无法执行) 2. 权限分离 - 提议者:可以提交操作 - 执行者:可以执行操作 - 取消者:可以取消操作 - 管理员:可以修改参数 3. 监控和报警 - 监听所有时间锁事件 - 设置异常操作报警 - 建立应急响应机制 4. 多重验证 - 操作哈希验证 - 参数边界检查 - 状态前置检查 */}// 带紧急暂停的时间锁contract PausableTimelock { bool public paused; address public guardian; modifier whenNotPaused() { require(!paused, "Timelock paused"); _; } modifier onlyGuardian() { require(msg.sender == guardian, "Not guardian"); _; } function pause() external onlyGuardian { paused = true; } function unpause() external onlyGuardian { paused = false; } function execute( address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt ) external payable whenNotPaused { // 执行逻辑... }}6. 测试示例// Hardhat 测试时间锁const { expect } = require("chai");const { ethers } = require("hardhat");describe("Timelock", function () { let timelock; let admin, proposer, executor, canceller; const minDelay = 2 * 24 * 60 * 60; // 2 days beforeEach(async function () { [admin, proposer, executor, canceller] = await ethers.getSigners(); const Timelock = await ethers.getContractFactory("AdvancedTimelock"); timelock = await Timelock.deploy( minDelay, [proposer.address], [executor.address] ); await timelock.deployed(); // 给时间锁发送 ETH await admin.sendTransaction({ to: timelock.address, value: ethers.utils.parseEther("10") }); }); it("Should schedule and execute operation after delay", async function () { const target = admin.address; const value = ethers.utils.parseEther("1"); const data = "0x"; const predecessor = ethers.constants.HashZero; const salt = ethers.utils.randomBytes(32); // 计划操作 await timelock.connect(proposer).schedule( target, value, data, predecessor, salt, minDelay ); const id = await timelock.hashOperation(target, value, data, predecessor, salt); expect(await timelock.getOperationState(id)).to.equal(1); // Pending // 时间推进 await network.provider.send("evm_increaseTime", [minDelay]); await network.provider.send("evm_mine"); expect(await timelock.getOperationState(id)).to.equal(2); // Ready // 执行操作 await timelock.connect(executor).execute(target, value, data, predecessor, salt); expect(await timelock.getOperationState(id)).to.equal(3); // Done }); it("Should not execute before delay", async function () { const target = admin.address; const value = ethers.utils.parseEther("1"); const data = "0x"; const predecessor = ethers.constants.HashZero; const salt = ethers.utils.randomBytes(32); await timelock.connect(proposer).schedule( target, value, data, predecessor, salt, minDelay ); // 尝试提前执行 await expect( timelock.connect(executor).execute(target, value, data, predecessor, salt) ).to.be.revertedWith("Operation not ready"); });});7. 总结时间锁是重要的安全机制:核心功能:延迟执行可取消过期机制权限控制应用场景:治理合约协议升级参数调整资金转移安全要点:合理的延迟时间权限分离监控报警应急机制最佳实践:使用 OpenZeppelin 的 TimelockController充分测试时间边界建立监控体系制定应急预案
阅读 0·3月6日 21:47

Solidity 中如何防止常见的智能合约安全漏洞?

智能合约安全是区块链开发的核心议题。由于合约一旦部署就难以修改,且涉及资产安全,预防漏洞比修复漏洞更重要。以下是常见安全漏洞及防护措施。1. 重入攻击(Reentrancy Attack)漏洞原理攻击者利用合约在状态更新前调用外部合约的特性,递归调用目标合约。// 存在漏洞的合约contract VulnerableBank { mapping(address => uint256) public balances; function withdraw() public { uint256 amount = balances[msg.sender]; require(amount > 0); // 危险:先转账,后更新状态 (bool success, ) = msg.sender.call{value: amount}(""); require(success); balances[msg.sender] = 0; // 更新太晚! }}// 攻击合约contract Attacker { VulnerableBank public bank; constructor(address _bank) { bank = VulnerableBank(_bank); } receive() external payable { if (address(bank).balance >= 1 ether) { bank.withdraw(); // 递归调用 } } function attack() external payable { bank.deposit{value: 1 ether}(); bank.withdraw(); }}防护措施import "@openzeppelin/contracts/security/ReentrancyGuard.sol";contract SecureBank is ReentrancyGuard { mapping(address => uint256) public balances; bool private locked; // 互斥锁 // 方案 1:Checks-Effects-Interactions 模式 function withdraw() public { uint256 amount = balances[msg.sender]; require(amount > 0, "Insufficient balance"); // Checks balances[msg.sender] = 0; // Effects:先更新状态 (bool success, ) = msg.sender.call{value: amount}(""); // Interactions require(success, "Transfer failed"); } // 方案 2:使用 ReentrancyGuard function withdrawWithGuard() public nonReentrant { uint256 amount = balances[msg.sender]; require(amount > 0, "Insufficient balance"); (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed"); balances[msg.sender] = 0; } // 方案 3:自定义互斥锁 modifier noReentrant() { require(!locked, "Reentrant call"); locked = true; _; locked = false; }}2. 整数溢出/下溢(Integer Overflow/Underflow)漏洞原理Solidity 0.8.0 之前,整数运算溢出不会报错,导致意外结果。// Solidity < 0.8.0 的漏洞contract VulnerableOverflow { uint8 public counter = 255; function increment() public { counter++; // 溢出为 0! } function decrement() public { counter--; // 下溢为 255! }}防护措施// 方案 1:使用 Solidity 0.8.0+contract SafeInSolidity08 { uint8 public counter = 255; function increment() public { counter++; // 自动检查溢出,溢出会 revert }}// 方案 2:使用 SafeMath 库(Solidity < 0.8.0)library SafeMath { function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "Addition overflow"); return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "Subtraction underflow"); 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, "Multiplication overflow"); return c; }}// 方案 3:使用 unchecked 进行显式控制contract ExplicitControl { function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { uint256 c = a + b; require(c >= a, "Overflow"); return c; } }}3. 访问控制漏洞漏洞原理不正确的权限检查导致未授权访问。// 存在漏洞的合约contract VulnerableAccess { address public owner; function init() public { // 危险:任何人都可以调用 owner = msg.sender; } function withdraw() public { // 危险:使用 tx.origin require(tx.origin == owner, "Not owner"); payable(msg.sender).transfer(address(this).balance); }}防护措施import "@openzeppelin/contracts/access/Ownable.sol";import "@openzeppelin/contracts/access/AccessControl.sol";// 方案 1:使用 OpenZeppelin Ownablecontract SecureWithOwnable is Ownable { constructor() { // owner 在构造函数中设置 } function secureWithdraw() public onlyOwner { payable(owner()).transfer(address(this).balance); }}// 方案 2:使用 AccessControlcontract SecureWithRoles is AccessControl { bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); constructor() { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(ADMIN_ROLE, msg.sender); } function adminFunction() public onlyRole(ADMIN_ROLE) { // 只有管理员可以执行 }}// 方案 3:正确的构造函数初始化contract ProperInitialization { address public owner; bool private initialized; constructor() { owner = msg.sender; initialized = true; } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); // 使用 msg.sender _; } modifier notInitialized() { require(!initialized, "Already initialized"); _; }}4. 前端运行攻击(Front-Running)漏洞原理攻击者观察内存池中的交易,以更高的 Gas 价格抢先执行。// 存在漏洞的合约contract VulnerableFrontRunning { mapping(bytes32 => uint256) public bids; function placeBid(bytes32 itemId, uint256 amount) external { // 攻击者可以看到内存池中的出价,然后出更高价 require(amount > bids[itemId], "Bid too low"); bids[itemId] = amount; }}防护措施contract SecureAgainstFrontRunning { struct Bid { bytes32 blindedBid; uint256 deposit; } mapping(address => Bid[]) public bids; mapping(bytes32 => address) public highestBidder; uint256 public highestBid; // 阶段 1:提交盲拍(哈希) function bid(bytes32 _blindedBid) external payable { bids[msg.sender].push(Bid({ blindedBid: _blindedBid, deposit: msg.value })); } // 阶段 2:揭示真实出价 function reveal( uint256[] calldata _values, bool[] calldata _fake, bytes32[] calldata _secret ) external { // 验证揭示的出价与提交的哈希匹配 // ... } // 使用 Commit-Reveal 模式 // 或使用密封拍卖}// 另一种方案:使用提交-揭示模式contract CommitRevealAuction { struct Commit { bytes32 commitHash; uint256 deposit; bool revealed; } mapping(address => Commit) public commits; uint256 public revealDeadline; function commitBid(bytes32 _commitHash) external payable { require(block.timestamp < revealDeadline, "Commit period ended"); commits[msg.sender] = Commit({ commitHash: _commitHash, deposit: msg.value, revealed: false }); } function revealBid(uint256 _bid, bytes32 _secret) external { require(block.timestamp >= revealDeadline, "Reveal not started"); Commit storage c = commits[msg.sender]; require(!c.revealed, "Already revealed"); bytes32 hash = keccak256(abi.encodePacked(_bid, _secret)); require(hash == c.commitHash, "Invalid reveal"); c.revealed = true; // 处理出价... }}5. 时间操纵攻击漏洞原理矿工可以操纵区块时间戳(在一定范围内)。// 存在漏洞的合约contract VulnerableTime { uint256 public gameEndTime; constructor() { gameEndTime = block.timestamp + 1 days; } function claimPrize() external { // 矿工可以将时间戳设置得比实际时间早 require(block.timestamp > gameEndTime, "Game not ended"); // 发放奖品... }}防护措施contract SecureTime { uint256 public gameStartBlock; uint256 public constant GAME_DURATION_BLOCKS = 7200; // 约 1 天 constructor() { gameStartBlock = block.number; } // 使用区块号代替时间戳 function claimPrize() external { require( block.number >= gameStartBlock + GAME_DURATION_BLOCKS, "Game not ended" ); // 发放奖品... } // 如果需要使用时间戳,添加缓冲 function claimPrizeWithBuffer() external { // 添加 15 分钟缓冲(约 90 个区块) require( block.timestamp >= gameEndTime + 15 minutes, "Game not ended with buffer" ); }}6. 拒绝服务攻击(DoS)漏洞原理通过耗尽 Gas 或阻止关键功能执行来攻击合约。// 存在漏洞的合约contract VulnerableDoS { address[] public investors; mapping(address => uint256) public balances; // 危险:遍历可能耗尽 Gas function distributeDividends() external { for (uint i = 0; i < investors.length; i++) { // 如果 investors 数组很大,会耗尽 Gas payable(investors[i]).transfer(balances[investors[i]]); } }}// 另一种 DoS:通过 revert 阻止转账contract VulnerableDoS2 { mapping(address => uint256) public balances; function withdraw() external { uint256 amount = balances[msg.sender]; // 如果接收者是恶意合约,会 revert 阻止所有人提款 (bool success, ) = msg.sender.call{value: amount}(""); require(success); balances[msg.sender] = 0; }}防护措施contract SecureAgainstDoS { mapping(address => uint256) public balances; mapping(address => bool) public withdrawn; // 方案 1:拉取模式(Pull)代替推送模式(Push) function withdraw() external { uint256 amount = balances[msg.sender]; require(amount > 0, "No balance"); require(!withdrawn[msg.sender], "Already withdrawn"); withdrawn[msg.sender] = true; balances[msg.sender] = 0; (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed"); } // 方案 2:分页处理 function distributeDividends(uint256 _start, uint256 _end) external { require(_end <= investors.length, "Invalid range"); require(_end - _start <= 100, "Batch too large"); // 限制批次大小 for (uint i = _start; i < _end; i++) { address investor = investors[i]; if (!paid[investor]) { paid[investor] = true; payable(investor).transfer(balances[investor]); } } } // 方案 3:使用 try-catch(Solidity 0.6.0+) function safeTransfer(address _to, uint256 _amount) internal { (bool success, ) = _to.call{value: _amount}(""); if (!success) { // 记录失败,不阻止其他转账 failedTransfers[_to] += _amount; } }}7. 随机数漏洞已在随机数专题中详细讲解,核心要点:不要使用区块哈希、时间戳等链上数据生成随机数使用 Chainlink VRF 等预言机方案使用 Commit-Reveal 模式8. 逻辑漏洞权限绕过// 危险:逻辑错误导致权限绕过contract LogicError { mapping(address => bool) public admins; function addAdmin(address _admin) external { // 错误:缺少权限检查 admins[_admin] = true; }}// 修复contract FixedLogic { mapping(address => bool) public admins; modifier onlyAdmin() { require(admins[msg.sender], "Not admin"); _; } function addAdmin(address _admin) external onlyAdmin { admins[_admin] = true; }}重入变种:跨函数重入contract CrossFunctionReentrancy { mapping(address => uint256) public balances; function transfer(address _to, uint256 _amount) external { require(balances[msg.sender] >= _amount); balances[msg.sender] -= _amount; balances[_to] += _amount; } function withdraw() external { uint256 amount = balances[msg.sender]; require(amount > 0); (bool success, ) = msg.sender.call{value: amount}(""); require(success); // 攻击者可以在 receive 中调用 transfer,然后再次 withdraw balances[msg.sender] = 0; }}// 修复:使用互斥锁contract FixedCrossFunction is ReentrancyGuard { function withdraw() external nonReentrant { // ... }}9. 安全开发最佳实践// 完整的安全合约模板import "@openzeppelin/contracts/security/ReentrancyGuard.sol";import "@openzeppelin/contracts/security/Pausable.sol";import "@openzeppelin/contracts/access/Ownable.sol";import "@openzeppelin/contracts/utils/Address.sol";contract SecureContractTemplate is ReentrancyGuard, Pausable, Ownable { using Address for address; // 1. 使用最新编译器版本 pragma solidity ^0.8.19; // 2. 使用内置溢出检查(0.8.0+) uint256 public counter; // 3. 使用 ReentrancyGuard function withdraw() external nonReentrant whenNotPaused { // ... } // 4. 使用 Pausable 应急暂停 function pause() external onlyOwner { _pause(); } // 5. 正确的访问控制 function adminFunction() external onlyOwner { // ... } // 6. 事件记录 event ActionExecuted(address indexed user, uint256 amount); // 7. 自定义错误(Gas 优化) error InsufficientBalance(uint256 requested, uint256 available); error InvalidAddress(); // 8. 输入验证 function validateInput(address _addr, uint256 _amount) internal pure { if (_addr == address(0)) revert InvalidAddress(); if (_amount == 0) revert InsufficientBalance(_amount, 0); } // 9. 使用 Checks-Effects-Interactions 模式 function secureTransfer(address _to, uint256 _amount) external { // Checks require(balances[msg.sender] >= _amount, "Insufficient balance"); // Effects balances[msg.sender] -= _amount; // Interactions (bool success, ) = _to.call{value: _amount}(""); require(success, "Transfer failed"); } // 10. 接收 ETH 的安全处理 receive() external payable { emit DepositReceived(msg.sender, msg.value); }}10. 安全审计检查清单[ ] 重入攻击防护(Checks-Effects-Interactions、ReentrancyGuard)[ ] 整数溢出检查(使用 0.8.0+ 或 SafeMath)[ ] 访问控制验证(Ownable、AccessControl)[ ] 输入验证(零地址检查、范围检查)[ ] 时间操纵防护(使用区块号代替时间戳)[ ] 随机数安全(使用 VRF)[ ] DoS 防护(拉取模式、分页处理)[ ] 事件完整性(所有状态变更触发事件)[ ] 应急机制(Pausable、升级能力)[ ] 代码审计(Slither、Mythril、Certora)11. 安全工具静态分析:Slither、Mythril、Manticore形式化验证:Certora、K Framework模糊测试:Echidna、Harvey监控:Tenderly、Forta// hardhat 配置安全工具require("@nomiclabs/hardhat-truffle5");require("hardhat-gas-reporter");require("solidity-coverage");module.exports = { solidity: { version: "0.8.19", settings: { optimizer: { enabled: true, runs: 200 } } }, gasReporter: { enabled: true },};​
阅读 0·3月6日 21:47

Solidity 智能合约中如何实现访问控制?有哪些最佳实践?

访问控制是智能合约安全的核心组成部分,确保只有授权用户才能执行特定操作。Solidity 提供了多种实现访问控制的方式。1. 基础访问控制:Only Owner 模式最简单的访问控制模式,只允许合约部署者执行敏感操作。contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); constructor() { owner = msg.sender; emit OwnershipTransferred(address(0), msg.sender); } modifier onlyOwner() { require(msg.sender == owner, "Not the owner"); _; } function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0), "Invalid address"); emit OwnershipTransferred(owner, newOwner); owner = newOwner; } // 只有 owner 可以调用的函数 function withdraw() public onlyOwner { payable(owner).transfer(address(this).balance); }}2. OpenZeppelin AccessControl:角色基础访问控制更灵活、更安全的访问控制方案,支持多角色管理。import "@openzeppelin/contracts/access/AccessControl.sol";contract RoleBasedAccess is AccessControl { // 定义角色 bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); constructor() { // 部署者获得默认 admin 角色 _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(ADMIN_ROLE, msg.sender); _grantRole(MINTER_ROLE, msg.sender); } // 只有 MINTER_ROLE 可以铸造 function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { // 铸造逻辑 } // 只有 PAUSER_ROLE 可以暂停 function pause() public onlyRole(PAUSER_ROLE) { // 暂停逻辑 } // 只有 ADMIN_ROLE 可以设置参数 function setParameter(uint256 param) public onlyRole(ADMIN_ROLE) { // 设置参数逻辑 }}3. 多签访问控制对于高价值合约,使用多签钱包进行访问控制更安全。contract MultiSigControl { address[] public owners; mapping(address => bool) public isOwner; uint256 public requiredConfirmations; struct Transaction { address to; uint256 value; bytes data; bool executed; uint256 confirmations; } Transaction[] public transactions; mapping(uint256 => mapping(address => bool)) public confirmed; modifier onlyOwner() { require(isOwner[msg.sender], "Not an owner"); _; } constructor(address[] memory _owners, uint256 _required) { require(_owners.length > 0, "Owners required"); require(_required > 0 && _required <= _owners.length, "Invalid required"); for (uint i = 0; i < _owners.length; i++) { address owner = _owners[i]; require(owner != address(0), "Invalid owner"); require(!isOwner[owner], "Owner not unique"); isOwner[owner] = true; owners.push(owner); } requiredConfirmations = _required; } function submitTransaction(address _to, uint256 _value, bytes memory _data) public onlyOwner returns (uint256) { uint256 txId = transactions.length; transactions.push(Transaction({ to: _to, value: _value, data: _data, executed: false, confirmations: 0 })); return txId; } function confirmTransaction(uint256 _txId) public onlyOwner { require(_txId < transactions.length, "Invalid tx"); require(!confirmed[_txId][msg.sender], "Already confirmed"); confirmed[_txId][msg.sender] = true; transactions[_txId].confirmations++; if (transactions[_txId].confirmations >= requiredConfirmations) { executeTransaction(_txId); } } function executeTransaction(uint256 _txId) internal { Transaction storage transaction = transactions[_txId]; require(!transaction.executed, "Already executed"); transaction.executed = true; (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data); require(success, "Transaction failed"); }}4. 时间锁访问控制为敏感操作添加时间延迟,给用户反应时间。contract TimelockControl { uint256 public constant DELAY = 2 days; struct PendingAction { bytes32 actionHash; uint256 executeTime; bool executed; } mapping(bytes32 => PendingAction) public pendingActions; event ActionScheduled(bytes32 indexed actionHash, uint256 executeTime); event ActionExecuted(bytes32 indexed actionHash); function scheduleAction(bytes32 actionHash) public onlyOwner { require(pendingActions[actionHash].executeTime == 0, "Already scheduled"); uint256 executeTime = block.timestamp + DELAY; pendingActions[actionHash] = PendingAction({ actionHash: actionHash, executeTime: executeTime, executed: false }); emit ActionScheduled(actionHash, executeTime); } function executeAction(bytes32 actionHash) public { PendingAction storage action = pendingActions[actionHash]; require(action.executeTime > 0, "Not scheduled"); require(block.timestamp >= action.executeTime, "Too early"); require(!action.executed, "Already executed"); action.executed = true; // 执行具体操作 emit ActionExecuted(actionHash); } function cancelAction(bytes32 actionHash) public onlyOwner { require(!pendingActions[actionHash].executed, "Already executed"); delete pendingActions[actionHash]; }}5. 基于代币的访问控制使用代币持有量来控制访问权限,常用于 DAO 治理。contract TokenBasedAccess { IERC20 public governanceToken; uint256 public minTokensRequired; constructor(address _token, uint256 _minTokens) { governanceToken = IERC20(_token); minTokensRequired = _minTokens; } modifier onlyTokenHolder() { require(governanceToken.balanceOf(msg.sender) >= minTokensRequired, "Insufficient tokens"); _; } function propose(bytes memory proposal) public onlyTokenHolder { // 提案逻辑 } function vote(uint256 proposalId, bool support) public onlyTokenHolder { // 投票逻辑 }}6. 组合访问控制模式实际项目中通常需要组合多种访问控制模式。import "@openzeppelin/contracts/access/AccessControl.sol";import "@openzeppelin/contracts/security/Pausable.sol";contract ComprehensiveAccess is AccessControl, Pausable { bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); bytes32 public constant EMERGENCY_ROLE = keccak256("EMERGENCY_ROLE"); mapping(address => bool) public whitelist; bool public whitelistEnabled; constructor() { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(ADMIN_ROLE, msg.sender); _grantRole(OPERATOR_ROLE, msg.sender); _grantRole(EMERGENCY_ROLE, msg.sender); } // 白名单检查 modifier onlyWhitelisted() { require(!whitelistEnabled || whitelist[msg.sender], "Not whitelisted"); _; } // 管理员功能:管理白名单 function addToWhitelist(address user) public onlyRole(ADMIN_ROLE) { whitelist[user] = true; } function removeFromWhitelist(address user) public onlyRole(ADMIN_ROLE) { whitelist[user] = false; } function toggleWhitelist(bool enabled) public onlyRole(ADMIN_ROLE) { whitelistEnabled = enabled; } // 操作员功能:日常操作 function processTransaction(address user, uint256 amount) public onlyRole(OPERATOR_ROLE) onlyWhitelisted whenNotPaused { // 处理交易逻辑 } // 紧急功能:暂停合约 function emergencyPause() public onlyRole(EMERGENCY_ROLE) { _pause(); } function emergencyUnpause() public onlyRole(ADMIN_ROLE) { _unpause(); } // 升级功能:只有 admin function upgrade(address newImplementation) public onlyRole(ADMIN_ROLE) { // 升级逻辑 }}最佳实践总结| 场景 | 推荐方案 | 说明 || ------ | ------------- | ----------- || 简单合约 | Ownable | 单所有者模式,简单有效 || 复杂合约 | AccessControl | 多角色管理,灵活安全 || 高价值合约 | 多签 + 时间锁 | 分散风险,增加安全缓冲 || DAO 治理 | 代币基础 | 去中心化治理 || 生产环境 | 组合模式 | 多种机制结合 |安全注意事项永远不要使用 tx.origin 进行权限检查:// 危险!modifier onlyOwner() { require(tx.origin == owner, "Not owner"); // 错误! _;}// 正确modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _;}权限转移要验证地址:function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0), "Invalid address"); // 必须验证 owner = newOwner;}考虑使用 OpenZeppelin 库:经过审计的标准实现,减少安全风险。定期审计权限设置:检查是否有不必要的权限或权限过大的账户。实施最小权限原则:只授予完成工作所需的最小权限。
阅读 0·3月6日 21:46

Solidity 智能合约中如何实现重入攻击防护?

重入攻击(Reentrancy Attack)是智能合约中最常见且危害最大的安全漏洞之一。攻击者利用合约在状态更新前调用外部合约的特性,递归调用目标合约来重复提取资金。重入攻击原理// 存在漏洞的合约示例contract VulnerableBank { mapping(address => uint256) public balances; function withdraw() public { uint256 amount = balances[msg.sender]; require(amount > 0); // 危险:先转账,后更新状态 (bool success, ) = msg.sender.call{value: amount}(""); require(success); balances[msg.sender] = 0; // 状态更新太晚 }}攻击者合约可以递归调用 withdraw(),在余额被清零前多次提取资金。防护方法 1:Checks-Effects-Interactions 模式这是最推荐的防护方式,遵循先检查、再更新状态、最后交互的顺序。contract SecureBank { mapping(address => uint256) public balances; function withdraw() public { uint256 amount = balances[msg.sender]; require(amount > 0, "Insufficient balance"); // Checks balances[msg.sender] = 0; // Effects:先更新状态 (bool success, ) = msg.sender.call{value: amount}(""); // Interactions require(success, "Transfer failed"); }}防护方法 2:使用重入锁(Reentrancy Guard)OpenZeppelin 提供的 ReentrancyGuard 是最常用的解决方案。import "@openzeppelin/contracts/security/ReentrancyGuard.sol";contract ProtectedBank is ReentrancyGuard { mapping(address => uint256) public balances; function withdraw() public nonReentrant { uint256 amount = balances[msg.sender]; require(amount > 0, "Insufficient balance"); (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed"); balances[msg.sender] = 0; }}防护方法 3:使用互斥锁(Mutex)自定义实现重入锁机制:contract MutexBank { mapping(address => uint256) public balances; bool private locked; modifier noReentrant() { require(!locked, "Reentrant call detected"); locked = true; _; locked = false; } function withdraw() public noReentrant { uint256 amount = balances[msg.sender]; require(amount > 0, "Insufficient balance"); (bool success, ) = msg.sender.call{value: amount}(""); require(success, "Transfer failed"); balances[msg.sender] = 0; }}防护方法 4:使用 transfer 或 send虽然 transfer 和 send 有 Gas 限制(2300 Gas),可以防止重入,但不推荐使用,因为:可能与某些智能合约钱包不兼容Gas 限制可能在未来的以太坊升级中改变// 不推荐的方式function withdraw() public { uint256 amount = balances[msg.sender]; require(amount > 0); balances[msg.sender] = 0; payable(msg.sender).transfer(amount); // Gas 限制 2300}最佳实践总结始终使用 Checks-Effects-Interactions 模式:这是最基础也是最重要的防护使用 OpenZeppelin 的 ReentrancyGuard:对于复杂合约,额外增加一层保护避免使用 call 进行 ETH 转账:如果必须使用,确保状态已更新代码审计:部署前进行专业的安全审计使用静态分析工具:如 Slither、Mythril 等检测重入漏洞检测重入漏洞的工具Slither:静态分析工具,可检测多种漏洞包括重入Mythril:符号执行工具,分析合约安全性Echidna:模糊测试工具,发现边界情况Certora:形式化验证工具,数学证明合约正确性
阅读 0·3月6日 21:46

Solidity 中如何处理错误和异常?自定义错误与 require、assert、revert 的区别是什么?

错误处理是智能合约开发的关键部分。Solidity 提供了多种错误处理机制,从 Solidity 0.8.4 开始引入了自定义错误,大幅优化了 Gas 消耗和错误信息的可读性。1. 错误处理机制概览/*Solidity 中的错误处理:1. require(condition, message) - 用于验证输入和外部条件 - 失败时回滚所有状态变更 - 退还剩余 Gas2. assert(condition) - 用于检查内部不变量 - 失败表示代码有 bug - 消耗所有 Gas(严重错误)3. revert(message) / revert CustomError() - 显式回滚交易 - Solidity 0.8.4+ 支持自定义错误4. try/catch - 捕获外部调用异常 - Solidity 0.6.0+ 支持*/2. require 的使用contract RequireExample { mapping(address => uint256) public balances; // 基本用法 function transfer(address _to, uint256 _amount) external { require(_to != address(0), "Invalid recipient address"); require(_amount > 0, "Amount must be greater than 0"); require(balances[msg.sender] >= _amount, "Insufficient balance"); balances[msg.sender] -= _amount; balances[_to] += _amount; } // 多个条件检查 function complexOperation( address _token, uint256 _amount, uint256 _minAmount, uint256 _deadline ) external { require(_token != address(0), "Invalid token"); require(_amount >= _minAmount, "Amount below minimum"); require(_amount <= 10000 ether, "Amount exceeds maximum"); require(block.timestamp < _deadline, "Transaction expired"); require( _amount % 100 == 0, "Amount must be multiple of 100" ); // 执行操作... } // 带错误消息的 require function withdraw(uint256 _amount) external { uint256 balance = balances[msg.sender]; require( balance >= _amount, string(abi.encodePacked( "Insufficient balance. Available: ", uint2str(balance), ", Requested: ", uint2str(_amount) )) ); balances[msg.sender] -= _amount; payable(msg.sender).transfer(_amount); } // 辅助函数:uint 转 string function uint2str(uint256 _i) internal pure returns (string memory) { if (_i == 0) return "0"; uint256 j = _i; uint256 length; while (j != 0) { length++; j /= 10; } bytes memory bstr = new bytes(length); uint256 k = length; j = _i; while (j != 0) { bstr[--k] = bytes1(uint8(48 + j % 10)); j /= 10; } return string(bstr); }}3. assert 的使用contract AssertExample { uint256 public constant MAX_SUPPLY = 1000000; uint256 public totalSupply; mapping(address => uint256) public balances; // assert 用于检查内部不变量 function mint(address _to, uint256 _amount) external { // 使用 require 检查外部输入 require(_to != address(0), "Invalid address"); require(_amount > 0, "Invalid amount"); uint256 newTotalSupply = totalSupply + _amount; require(newTotalSupply <= MAX_SUPPLY, "Exceeds max supply"); // 状态更新 totalSupply = newTotalSupply; balances[_to] += _amount; // 使用 assert 检查内部不变量 // 如果失败,说明代码有严重 bug assert(totalSupply >= balances[_to]); assert(balances[_to] >= _amount); } // 数学运算后的不变量检查 function divide(uint256 a, uint256 b) external pure returns (uint256) { require(b > 0, "Division by zero"); uint256 result = a / b; // 检查数学不变量 assert(result * b <= a); return result; } // 复杂操作后的状态检查 function complexTransfer( address _from, address _to, uint256 _amount ) external { uint256 fromBalanceBefore = balances[_from]; uint256 toBalanceBefore = balances[_to]; // 执行转账 balances[_from] -= _amount; balances[_to] += _amount; // 检查不变量 assert(balances[_from] == fromBalanceBefore - _amount); assert(balances[_to] == toBalanceBefore + _amount); assert( balances[_from] + balances[_to] == fromBalanceBefore + toBalanceBefore ); }}4. revert 的使用contract RevertExample { // 自定义错误(Solidity 0.8.4+) error InsufficientBalance(uint256 available, uint256 required); error InvalidAddress(address provided); error Unauthorized(address caller, bytes32 requiredRole); error TransferFailed(address from, address to, uint256 amount); error DeadlineExpired(uint256 deadline, uint256 currentTime); error SlippageExceeded(uint256 expected, uint256 actual); mapping(address => uint256) public balances; mapping(address => bytes32) public roles; // 使用自定义错误(Gas 优化) function transferWithCustomError( address _to, uint256 _amount ) external { if (_to == address(0)) { revert InvalidAddress(_to); } uint256 balance = balances[msg.sender]; if (balance < _amount) { revert InsufficientBalance(balance, _amount); } balances[msg.sender] -= _amount; balances[_to] += _amount; } // 带多个参数的自定义错误 function swapTokens( address _tokenIn, address _tokenOut, uint256 _amountIn, uint256 _minAmountOut, uint256 _deadline ) external { if (block.timestamp > _deadline) { revert DeadlineExpired(_deadline, block.timestamp); } // 模拟交换计算 uint256 amountOut = _amountIn * 95 / 100; // 5% 滑点 if (amountOut < _minAmountOut) { revert SlippageExceeded(_minAmountOut, amountOut); } // 执行交换... } // 权限检查 function adminFunction() external view { bytes32 requiredRole = keccak256("ADMIN_ROLE"); if (roles[msg.sender] != requiredRole) { revert Unauthorized(msg.sender, requiredRole); } // 执行管理员操作... } // 传统 revert(字符串消息) function legacyRevert(address _to, uint256 _amount) external { if (_to == address(0)) { revert("Transfer to zero address"); } if (balances[msg.sender] < _amount) { revert("Insufficient balance"); } // 执行转账... }}5. 自定义错误的 Gas 优化contract GasComparison { // 自定义错误 error CustomError(uint256 code); // 使用 require 字符串(消耗更多 Gas) function useRequire(uint256 _value) external pure { require(_value > 0, "Value must be greater than zero"); require(_value < 1000, "Value must be less than 1000"); require(_value % 2 == 0, "Value must be even"); } // 使用自定义错误(节省 Gas) function useCustomError(uint256 _value) external pure { if (_value == 0) revert CustomError(1); if (_value >= 1000) revert CustomError(2); if (_value % 2 != 0) revert CustomError(3); } /* Gas 对比: - require 字符串:约 200-300 gas + 字符串存储成本 - 自定义错误:约 50-100 gas 在频繁调用的函数中,自定义错误可以显著节省 Gas */}// 实际应用中的自定义错误设计contract TokenWithCustomErrors { // 定义所有可能的错误 error TransferFromZeroAddress(); error TransferToZeroAddress(); error TransferAmountExceedsBalance(address sender, uint256 balance, uint256 amount); error ApproveFromZeroAddress(); error ApproveToZeroAddress(); error InsufficientAllowance(address owner, address spender, uint256 allowance, uint256 amount); error MintToZeroAddress(); error BurnFromZeroAddress(); error BurnAmountExceedsBalance(address account, uint256 balance, uint256 amount); error InvalidSender(address sender); error InvalidReceiver(address receiver); error PermitDeadlineExpired(uint256 deadline, uint256 currentTime); error InvalidPermitSignature(address signer, address owner); mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; function transfer(address _to, uint256 _amount) external returns (bool) { address owner = msg.sender; if (owner == address(0)) revert TransferFromZeroAddress(); if (_to == address(0)) revert TransferToZeroAddress(); uint256 fromBalance = _balances[owner]; if (fromBalance < _amount) { revert TransferAmountExceedsBalance(owner, fromBalance, _amount); } _balances[owner] = fromBalance - _amount; _balances[_to] += _amount; return true; } function transferFrom( address _from, address _to, uint256 _amount ) external returns (bool) { if (_from == address(0)) revert TransferFromZeroAddress(); if (_to == address(0)) revert TransferToZeroAddress(); uint256 currentAllowance = _allowances[_from][msg.sender]; if (currentAllowance < _amount) { revert InsufficientAllowance(_from, msg.sender, currentAllowance, _amount); } _allowances[_from][msg.sender] = currentAllowance - _amount; uint256 fromBalance = _balances[_from]; if (fromBalance < _amount) { revert TransferAmountExceedsBalance(_from, fromBalance, _amount); } _balances[_from] = fromBalance - _amount; _balances[_to] += _amount; return true; } function mint(address _to, uint256 _amount) external { if (_to == address(0)) revert MintToZeroAddress(); _totalSupply += _amount; _balances[_to] += _amount; } function burn(uint256 _amount) external { address account = msg.sender; if (account == address(0)) revert BurnFromZeroAddress(); uint256 accountBalance = _balances[account]; if (accountBalance < _amount) { revert BurnAmountExceedsBalance(account, accountBalance, _amount); } _balances[account] = accountBalance - _amount; _totalSupply -= _amount; }}6. try/catch 异常处理contract TryCatchExample { // 外部合约接口 interface IExternalContract { function riskyOperation(uint256 _value) external returns (uint256); function anotherFunction() external view returns (bool); } // 自定义错误 error ExternalCallFailed(address target, bytes reason); error ExternalCallReverted(address target, string reason); error ExternalCallPanic(address target, uint256 code); // 使用 try/catch 处理外部调用 function safeExternalCall( address _target, uint256 _value ) external returns (bool success, uint256 result) { try IExternalContract(_target).riskyOperation(_value) returns (uint256 _result) { // 调用成功 return (true, _result); } catch Error(string memory reason) { // 捕获 revert("string") 错误 revert ExternalCallReverted(_target, reason); } catch Panic(uint256 errorCode) { // 捕获 assert 失败或内部错误 // errorCode: // 0x01: assert 失败 // 0x11: 算术溢出/下溢 // 0x12: 除以零 // 0x21: 转换为无效枚举 // 0x22: 访问错误编码的 storage 字节数组 // 0x31: 空数组 pop // 0x32: 数组越界访问 // 0x41: 内存分配过多 // 0x51: 调用未初始化的内部函数 revert ExternalCallPanic(_target, errorCode); } catch (bytes memory lowLevelData) { // 捕获自定义错误或其他低级错误 revert ExternalCallFailed(_target, lowLevelData); } } // 批量调用,部分失败不影响其他 function batchExternalCalls( address[] calldata _targets, uint256[] calldata _values ) external returns (bool[] memory successes, uint256[] memory results) { require(_targets.length == _values.length, "Length mismatch"); successes = new bool[](_targets.length); results = new uint256[](_targets.length); for (uint i = 0; i < _targets.length; i++) { try IExternalContract(_targets[i]).riskyOperation(_values[i]) returns (uint256 result) { successes[i] = true; results[i] = result; } catch { // 记录失败,继续执行其他调用 successes[i] = false; results[i] = 0; } } return (successes, results); } // 带重试的外部调用 function callWithRetry( address _target, uint256 _value, uint256 _maxAttempts ) external returns (bool success, uint256 result) { for (uint i = 0; i < _maxAttempts; i++) { try IExternalContract(_target).riskyOperation(_value) returns (uint256 _result) { return (true, _result); } catch { // 重试前等待(实际应用中可能需要更复杂的逻辑) if (i == _maxAttempts - 1) { return (false, 0); } } } return (false, 0); } // 带超时检查的外部调用 function callWithTimeout( address _target, uint256 _value, uint256 _deadline ) external returns (bool success, uint256 result) { require(block.timestamp < _deadline, "Deadline passed"); try IExternalContract(_target).riskyOperation(_value) returns (uint256 _result) { return (true, _result); } catch { return (false, 0); } }}7. 错误处理最佳实践contract ErrorHandlingBestPractices { /* 最佳实践总结: 1. require 使用场景: - 验证函数输入参数 - 检查外部条件 - 验证返回值 2. assert 使用场景: - 检查内部不变量 - 验证数学运算结果 - 检查不应该发生的情况 3. revert 使用场景: - 复杂的条件判断 - 需要详细错误信息 - Gas 优化(自定义错误) 4. try/catch 使用场景: - 外部合约调用 - 需要优雅处理失败 - 批量操作 */ // 错误定义 error InvalidInput(string param, string reason); error StateInvariantViolation(string invariant); error ExternalDependencyFailed(address dependency); uint256 public constant MAX_VALUE = 10000; uint256 public constant MIN_VALUE = 1; uint256 public totalValue; mapping(address => uint256) public values; // 综合示例 function complexOperation( address _externalContract, uint256 _input, address _recipient ) external { // 1. 输入验证(require) require(_externalContract != address(0), "Invalid contract"); require(_recipient != address(0), "Invalid recipient"); // 使用自定义错误进行详细验证 if (_input < MIN_VALUE || _input > MAX_VALUE) { revert InvalidInput( "_input", "Must be between MIN_VALUE and MAX_VALUE" ); } // 2. 外部调用(try/catch) uint256 externalResult; try IExternal(_externalContract).getValue() returns (uint256 value) { externalResult = value; } catch Error(string memory reason) { revert ExternalDependencyFailed(_externalContract); } catch { revert ExternalDependencyFailed(_externalContract); } // 3. 计算和状态更新 uint256 oldTotal = totalValue; uint256 newValue = _input + externalResult; values[_recipient] += newValue; totalValue += newValue; // 4. 不变量检查(assert) assert(totalValue >= values[_recipient]); assert(totalValue == oldTotal + newValue); assert(values[_recipient] >= newValue); }}interface IExternal { function getValue() external view returns (uint256);}8. 错误处理模式对比contract ErrorComparison { /* 三种错误处理方式的对比: | 特性 | require | assert | revert | |------|---------|--------|--------| | 使用场景 | 输入验证 | 内部不变量 | 复杂条件 | | Gas 退还 | 是 | 否 | 是 | | 错误信息 | 字符串 | 无 | 自定义错误 | | Gas 消耗 | 中等 | 高 | 低(自定义错误)| | 严重性 | 一般 | 严重(bug)| 一般 | 推荐使用场景: - 频繁调用的函数:使用自定义错误 - 输入验证:使用 require - 内部检查:使用 assert - 外部调用:使用 try/catch */ // 示例:根据不同场景选择错误处理方式 // 场景 1:输入验证 - 使用 require function deposit(uint256 _amount) external { require(_amount > 0, "Amount must be positive"); // ... } // 场景 2:Gas 优化 - 使用自定义错误 error InvalidAmount(uint256 provided, uint256 minimum); function optimizedDeposit(uint256 _amount) external { if (_amount < 100) { revert InvalidAmount(_amount, 100); } // ... } // 场景 3:内部不变量 - 使用 assert function internalOperation() external { uint256 before = totalSupply; // ... 复杂操作 assert(totalSupply >= before); // 不应该减少 } uint256 public totalSupply;}9. 实际项目中的错误处理// OpenZeppelin 风格的错误处理contract ProductionGradeErrors { // 定义模块化的错误 // 通用错误 error ZeroAddress(); error ZeroAmount(); error ExceedsMax(uint256 provided, uint256 max); error InsufficientBalance(uint256 available, uint256 required); error Unauthorized(address caller); error AlreadyInitialized(); error NotInitialized(); // 特定功能错误 error TransferFailed(address from, address to, uint256 amount); error SwapFailed(address tokenIn, address tokenOut, uint256 amount); error StakingPeriodNotEnded(uint256 endTime, uint256 currentTime); error CooldownPeriodActive(uint256 cooldownEnd); // 状态变量 bool private _initialized; address public owner; mapping(address => uint256) public balances; mapping(address => uint256) public stakingEndTime; uint256 public constant COOLDOWN_PERIOD = 7 days; modifier onlyOwner() { if (msg.sender != owner) revert Unauthorized(msg.sender); _; } modifier initializer() { if (_initialized) revert AlreadyInitialized(); _initialized = true; _; } // 初始化函数 function initialize(address _owner) external initializer { if (_owner == address(0)) revert ZeroAddress(); owner = _owner; } // 存款函数 function deposit(uint256 _amount) external { if (_amount == 0) revert ZeroAmount(); balances[msg.sender] += _amount; } // 取款函数 function withdraw(uint256 _amount) external { uint256 balance = balances[msg.sender]; if (balance < _amount) { revert InsufficientBalance(balance, _amount); } // 检查冷却期 uint256 cooldownEnd = stakingEndTime[msg.sender] + COOLDOWN_PERIOD; if (block.timestamp < cooldownEnd) { revert CooldownPeriodActive(cooldownEnd); } balances[msg.sender] = balance - _amount; (bool success, ) = msg.sender.call{value: _amount}(""); if (!success) { revert TransferFailed(address(this), msg.sender, _amount); } } // 质押函数 function stake(uint256 _amount, uint256 _duration) external { if (_amount == 0) revert ZeroAmount(); uint256 balance = balances[msg.sender]; if (balance < _amount) { revert InsufficientBalance(balance, _amount); } uint256 maxDuration = 365 days; if (_duration > maxDuration) { revert ExceedsMax(_duration, maxDuration); } balances[msg.sender] = balance - _amount; stakingEndTime[msg.sender] = block.timestamp + _duration; } // 解质押函数 function unstake() external { uint256 endTime = stakingEndTime[msg.sender]; if (endTime == 0) revert NotInitialized(); if (block.timestamp < endTime) { revert StakingPeriodNotEnded(endTime, block.timestamp); } // 执行解质押... }}10. 总结Solidity 的错误处理机制各有特点:require:用于输入验证和外部条件检查提供清晰的错误信息退还剩余 Gasassert:用于内部不变量检查失败表示代码有 bug消耗所有 Gas(严重错误)revert + 自定义错误:Solidity 0.8.4+ 推荐显著节省 Gas支持参数化错误信息try/catch:处理外部调用异常支持优雅降级批量操作必备最佳实践:频繁调用的函数使用自定义错误输入验证使用 require内部检查使用 assert外部调用使用 try/catch设计清晰、模块化的错误体系
阅读 0·3月6日 21:44