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

面试题手册

Solidity 中 storage、memory 和 calldata 三种数据位置的区别是什么?

在 Solidity 中,storage、memory 和 calldata 是三种不同的数据存储位置,理解它们的区别对于编写高效的智能合约至关重要。1. Storage(存储)特点:永久存储在区块链上,是合约状态变量的默认存储位置成本:最昂贵,需要消耗 Gas 来写入生命周期:永久存在,直到合约被销毁适用场景:需要持久化保存的数据,如用户余额、合约配置等contract Example { uint256 public storedData; // 默认 storage function updateData(uint256 _data) public { storedData = _data; // 写入 storage }}2. Memory(内存)特点:临时存储,函数执行完毕后自动释放成本:中等,比 storage 便宜生命周期:仅在函数执行期间存在适用场景:函数参数、局部变量、临时计算结果function processArray(uint256[] memory _arr) public pure returns (uint256) { uint256 sum = 0; for (uint i = 0; i < _arr.length; i++) { sum += _arr[i]; } return sum;}3. Calldata(调用数据)特点:只读区域,存储函数调用的输入数据成本:最便宜,不消耗额外 Gas生命周期:仅在函数执行期间存在适用场景:外部函数的引用类型参数(数组、结构体等)function externalFunction(uint256[] calldata _data) external pure returns (uint256) { // _data 是只读的,不能修改 return _data.length;}对比总结| 特性 | Storage | Memory | Calldata || ------ | ------- | ------ | -------- || 持久性 | 永久 | 临时 | 临时 || 可修改性 | 可读写 | 可读写 | 只读 || Gas 成本 | 高 | 中 | 低 || 适用场景 | 状态变量 | 局部变量 | 外部函数参数 |最佳实践优先使用 calldata:对于外部函数的引用类型参数,使用 calldata 可以节省 Gas避免不必要的 storage 操作:storage 操作昂贵,尽量减少写入次数内存管理:复杂计算时,合理使用 memory 避免频繁的 storage 访问数据拷贝注意:从 storage 复制到 memory 或 calldata 会消耗 Gas,注意优化
阅读 0·3月7日 19:35

Solidity 中的 view、pure 和 payable 函数修饰符有什么区别?

在 Solidity 中,view、pure 和 payable 是三种重要的函数修饰符,它们定义了函数的行为特性和限制条件。1. View 修饰符定义:声明函数不会修改合约的状态变量,但可以读取状态。特点:可以读取状态变量(storage)不能修改状态变量不能发送 ETH不消耗 Gas(当被外部调用时)contract ViewExample { uint256 public storedData = 100; // view 函数可以读取状态 function getData() public view returns (uint256) { return storedData; // 读取状态变量 } // 错误:view 函数不能修改状态 function setData(uint256 _data) public view { // storedData = _data; // 编译错误! }}2. Pure 修饰符定义:声明函数既不读取也不修改合约状态,仅依赖于输入参数。特点:不能读取状态变量不能修改状态变量不能访问 msg.sender、msg.value 等全局变量不消耗 Gas(当被外部调用时)contract PureExample { uint256 public constant VALUE = 100; // pure 函数只依赖输入参数 function add(uint256 a, uint256 b) public pure returns (uint256) { return a + b; } // pure 函数可以读取常量 function getConstant() public pure returns (uint256) { return VALUE; // 常量不算状态读取 } // 错误:pure 函数不能读取状态变量 function getData() public pure returns (uint256) { // return storedData; // 编译错误! }}3. Payable 修饰符定义:允许函数接收 ETH(以太币)。特点:可以接收 ETH 转账可以读取和修改状态(默认行为)可以访问 msg.value 获取转账金额消耗 Gascontract PayableExample { mapping(address => uint256) public balances; // payable 函数可以接收 ETH function deposit() public payable { require(msg.value > 0, "Must send ETH"); balances[msg.sender] += msg.value; } // 查询合约余额 function getBalance() public view returns (uint256) { return address(this).balance; } // 提取 ETH function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount, "Insufficient balance"); balances[msg.sender] -= amount; payable(msg.sender).transfer(amount); }}修饰符对比表| 特性 | view | pure | payable || ------------ | ---- | ---- | ------- || 读取状态 | ✅ | ❌ | ✅ || 修改状态 | ❌ | ❌ | ✅ || 接收 ETH | ❌ | ❌ | ✅ || 访问 msg.value | ❌ | ❌ | ✅ || Gas 消耗(外部调用) | 无 | 无 | 有 || 适用场景 | 查询数据 | 纯计算 | 资金操作 |组合使用某些修饰符可以组合使用:contract CombinedExample { // payable + view 不能组合,因为 view 不消耗 Gas,而 payable 需要处理转账 // 可以定义接收 ETH 的函数 receive() external payable {} fallback() external payable {} // 计算函数使用 pure function calculateFee(uint256 amount) public pure returns (uint256) { return amount * 5 / 100; // 5% 手续费 } // 查询函数使用 view function getContractBalance() public view returns (uint256) { return address(this).balance; }}实际应用示例contract Bank { mapping(address => uint256) private balances; uint256 public totalDeposits; // payable:接收存款 function deposit() public payable { balances[msg.sender] += msg.value; totalDeposits += msg.value; } // view:查询余额 function getBalance(address user) public view returns (uint256) { return balances[user]; } // pure:计算利息 function calculateInterest(uint256 principal, uint256 rate) public pure returns (uint256) { return principal * rate / 100; } // payable + 其他操作 function withdraw() public payable { uint256 amount = balances[msg.sender]; require(amount > 0, "No balance"); balances[msg.sender] = 0; totalDeposits -= amount; payable(msg.sender).transfer(amount); }}最佳实践优先使用 pure:如果函数不需要读取状态,使用 pure 可以节省 Gas明确使用 view:对于只读操作,明确标记 view 提高代码可读性谨慎使用 payable:确保 payable 函数有适当的访问控制和金额验证避免滥用:不要为了 Gas 优化而错误地使用这些修饰符,可能导致安全问题
阅读 0·3月7日 12:09

Solidity 中如何使用 Assembly 进行底层优化?有哪些注意事项?

Assembly(汇编)是 Solidity 中的底层编程方式,允许开发者直接操作 EVM 字节码。虽然它提供了极高的灵活性和 Gas 优化空间,但也增加了代码复杂性和安全风险。1. 为什么使用 Assemblycontract AssemblyBenefits { // 1. Gas 优化:跳过 Solidity 的高级抽象 // 2. 访问底层 EVM 特性 // 3. 实现 Solidity 不支持的操作 // 4. 精细控制内存和存储}2. Assembly 基础语法内联汇编(Inline Assembly)contract BasicAssembly { // 使用 assembly 关键字 function add(uint256 a, uint256 b) external pure returns (uint256 result) { assembly { // 直接使用 EVM 操作码 result := add(a, b) // 加法操作 } } // 多个操作 function calculate(uint256 x) external pure returns (uint256) { assembly { let y := add(x, 10) // 局部变量 let z := mul(y, 2) // 乘法 mstore(0x00, z) // 存储到内存 return(0x00, 32) // 返回内存中的值 } }}数据类型和操作contract AssemblyDataTypes { // 基本类型操作 function dataTypes() external pure { assembly { // 整数 let a := 100 let b := 0xFF // 十六进制 // 布尔(0 或 1) let flag := 1 // 地址 let addr := 0x1234567890123456789012345678901234567890 // 算术运算 let sum := add(a, b) let diff := sub(a, b) let prod := mul(a, b) let quot := div(a, b) let rem := mod(a, b) // 位运算 let andResult := and(a, b) let orResult := or(a, b) let xorResult := xor(a, b) let notResult := not(a) let shifted := shl(2, a) // 左移 let shiftedR := shr(2, a) // 右移 } }}3. 内存操作内存布局contract MemoryOperations { /* 内存布局: 0x00 - 0x3f (64 bytes): 哈希函数的临时空间 0x40 - 0x5f (32 bytes): 当前分配的内存大小(空闲内存指针) 0x60 - ...: 实际数据存储 */ function memoryBasics() external pure returns (uint256) { assembly { // 读取空闲内存指针 let freePtr := mload(0x40) // 在空闲内存位置存储数据 mstore(freePtr, 12345) // 更新空闲内存指针(32 字节 = 0x20) mstore(0x40, add(freePtr, 0x20)) // 读取内存 let value := mload(freePtr) mstore(0x00, value) return(0x00, 32) } } // 存储多个值 function storeMultiple() external pure returns (uint256, uint256) { assembly { let freePtr := mload(0x40) // 存储多个 32 字节值 mstore(freePtr, 100) mstore(add(freePtr, 0x20), 200) mstore(add(freePtr, 0x40), 300) // 更新空闲内存指针 mstore(0x40, add(freePtr, 0x60)) // 返回前两个值 mstore(0x00, mload(freePtr)) mstore(0x20, mload(add(freePtr, 0x20))) return(0x00, 64) } } // 字节操作 function byteOperations() external pure returns (bytes32) { assembly { let value := 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef // 提取单个字节(从右往左,0-31) let byte0 := byte(31, value) // 最后一个字节 let byte1 := byte(30, value) // 倒数第二个字节 mstore(0x00, byte0) return(0x00, 32) } }}4. 存储操作存储布局contract StorageOperations { uint256 public value1; // slot 0 uint256 public value2; // slot 1 mapping(address => uint256) public balances; // slot 2 address public owner; // slot 3 function storageRead() external view returns (uint256) { assembly { // 读取 slot 0 的值 let v := sload(0) mstore(0x00, v) return(0x00, 32) } } function storageWrite(uint256 _value) external { assembly { // 写入 slot 0 sstore(0, _value) } } // 读取 mapping function readMapping(address _key) external view returns (uint256) { assembly { // mapping 的存储位置计算:keccak256(key . slot) mstore(0x00, _key) mstore(0x20, 2) // balances 在 slot 2 let slot := keccak256(0x00, 0x40) let value := sload(slot) mstore(0x00, value) return(0x00, 32) } } // 写入 mapping function writeMapping(address _key, uint256 _value) external { assembly { mstore(0x00, _key) mstore(0x20, 2) let slot := keccak256(0x00, 0x40) sstore(slot, _value) } } // 读取动态数组 uint256[] public dynamicArray; // slot 4 function readArrayLength() external view returns (uint256) { assembly { // 动态数组的长度存储在声明的 slot let length := sload(4) mstore(0x00, length) return(0x00, 32) } } function readArrayElement(uint256 _index) external view returns (uint256) { assembly { // 数组元素的存储位置:keccak256(slot) + index mstore(0x00, 4) let baseSlot := keccak256(0x00, 0x20) let elementSlot := add(baseSlot, _index) let value := sload(elementSlot) mstore(0x00, value) return(0x00, 32) } }}5. 函数调用和返回值外部调用contract ExternalCalls { function callExternal(address _target, bytes memory _data) external returns (bool success, bytes memory result) { assembly { // 获取数据位置和大小 let dataPtr := add(_data, 0x20) // 跳过长度字段 let dataSize := mload(_data) // 分配内存用于返回数据 let resultPtr := mload(0x40) // 执行调用 success := call( gas(), // 剩余 Gas _target, // 目标地址 0, // 发送的 ETH dataPtr, // 输入数据位置 dataSize, // 输入数据大小 resultPtr, // 返回数据位置 0x40 // 返回数据大小上限 ) // 获取返回数据大小 let resultSize := returndatasize() // 复制返回数据 returndatacopy(resultPtr, 0, resultSize) // 更新空闲内存指针 mstore(0x40, add(resultPtr, resultSize)) // 设置 result 指针和长度 mstore(result, resultSize) mstore(add(result, 0x20), resultPtr) } } // 发送 ETH function sendETH(address _to, uint256 _amount) external returns (bool) { assembly { // 使用 call 发送 ETH let success := call( gas(), _to, _amount, // 发送的 ETH 数量 0, // 无输入数据 0, 0, // 不关心返回数据 0 ) mstore(0x00, success) return(0x00, 32) } } // 静态调用(不修改状态) function staticCall(address _target, bytes memory _data) external view returns (bool success, bytes memory result) { assembly { let dataPtr := add(_data, 0x20) let dataSize := mload(_data) let resultPtr := mload(0x40) success := staticcall( gas(), _target, dataPtr, dataSize, resultPtr, 0x40 ) let resultSize := returndatasize() returndatacopy(resultPtr, 0, resultSize) mstore(0x40, add(resultPtr, resultSize)) mstore(result, resultSize) mstore(add(result, 0x20), resultPtr) } }}委托调用contract DelegateCallExample { address public implementation; uint256 public value; function delegateToImplementation(bytes memory _data) external returns (bytes memory) { assembly { let dataPtr := add(_data, 0x20) let dataSize := mload(_data) let resultPtr := mload(0x40) // 使用 delegatecall let success := delegatecall( gas(), sload(0), // implementation 地址在 slot 0 dataPtr, dataSize, resultPtr, 0x40 ) let resultSize := returndatasize() returndatacopy(resultPtr, 0, resultSize) // 检查调用是否成功 if iszero(success) { revert(resultPtr, resultSize) } return(resultPtr, resultSize) } }}6. Gas 优化技巧常见优化模式contract GasOptimizations { uint256[] public items; // 优化前:使用 Solidity 高级语法 function sumSolidity() external view returns (uint256 total) { for (uint i = 0; i < items.length; i++) { total += items[i]; } } // 优化后:使用 Assembly function sumAssembly() external view returns (uint256 total) { assembly { // 获取数组存储位置 mstore(0x00, items.slot) let baseSlot := keccak256(0x00, 0x20) // 获取数组长度 let length := sload(items.slot) // 遍历数组 for { let i := 0 } lt(i, length) { i := add(i, 1) } { let slot := add(baseSlot, i) let value := sload(slot) total := add(total, value) } mstore(0x00, total) return(0x00, 32) } } // 批量操作优化 function batchTransfer(address[] memory _recipients, uint256[] memory _amounts) external payable { require(_recipients.length == _amounts.length, "Length mismatch"); assembly { let recipientsPtr := add(_recipients, 0x20) let amountsPtr := add(_amounts, 0x20) let length := mload(_recipients) for { let i := 0 } lt(i, length) { i := add(i, 1) } { let recipient := mload(add(recipientsPtr, mul(i, 0x20))) let amount := mload(add(amountsPtr, mul(i, 0x20))) // 使用 call 发送 ETH let success := call( gas(), recipient, amount, 0, 0, 0, 0 ) if iszero(success) { revert(0, 0) } } } } // 短路求和优化 function optimizedSum(uint256[] memory _values) external pure returns (uint256) { assembly { let ptr := add(_values, 0x20) let length := mload(_values) let total := 0 // 使用 unchecked 的加法(Solidity 0.8.0+) for { let i := 0 } lt(i, length) { i := add(i, 1) } { total := add(total, mload(add(ptr, mul(i, 0x20)))) } mstore(0x00, total) return(0x00, 32) } }}7. 高级技巧创建合约contract ContractCreation { function deploy(bytes memory _bytecode) external returns (address addr) { assembly { let size := mload(_bytecode) let ptr := add(_bytecode, 0x20) // 使用 create 部署合约 addr := create(0, ptr, size) // 检查部署是否成功 if iszero(extcodesize(addr)) { revert(0, 0) } } } // 使用 create2 部署(确定性地址) function deployWithSalt(bytes memory _bytecode, bytes32 _salt) external returns (address addr) { assembly { let size := mload(_bytecode) let ptr := add(_bytecode, 0x20) addr := create2(0, ptr, size, _salt) if iszero(extcodesize(addr)) { revert(0, 0) } } } // 计算 create2 地址 function computeAddress( bytes32 _salt, bytes32 _bytecodeHash, address _deployer ) external pure returns (address) { assembly { // 地址 = 最后 20 字节 of keccak256(0xff + deployer + salt + bytecodeHash) mstore(0x00, _deployer) mstore(0x20, _salt) mstore(0x40, _bytecodeHash) let hash := keccak256(0x00, 0x60) mstore(0x00, hash) return(12, 20) // 返回最后 20 字节 } }}内联汇编与 Solidity 交互contract AssemblyInteraction { struct User { uint256 id; address wallet; uint256 balance; } mapping(uint256 => User) public users; // 使用 Assembly 读取结构体 function getUserAssembly(uint256 _id) external view returns (User memory user) { assembly { // 计算 mapping 的存储位置 mstore(0x00, _id) mstore(0x20, users.slot) let baseSlot := keccak256(0x00, 0x40) // 读取结构体的各个字段 // User 结构体占用 3 个 slot let id := sload(baseSlot) let wallet := sload(add(baseSlot, 1)) let balance := sload(add(baseSlot, 2)) // 存储到内存返回 let ptr := mload(0x40) mstore(ptr, id) mstore(add(ptr, 0x20), wallet) mstore(add(ptr, 0x40), balance) // 更新空闲内存指针 mstore(0x40, add(ptr, 0x60)) // 设置返回值 mstore(user, 0x60) // 指向内存位置 mstore(add(user, 0x20), ptr) } } // 条件判断优化 function optimizedCondition(uint256 x) external pure returns (uint256) { assembly { // 使用 switch 进行多条件判断 switch x case 0 { mstore(0x00, 100) } case 1 { mstore(0x00, 200) } default { mstore(0x00, 300) } return(0x00, 32) } }}8. 安全注意事项contract AssemblySafety { /* 安全注意事项: 1. 内存管理:确保正确更新空闲内存指针 2. 存储冲突:避免覆盖重要存储槽位 3. 溢出检查:Assembly 不自动检查溢出 4. 调用验证:检查外部调用返回值 5. 重入防护:手动实现重入锁 */ bool private locked; modifier noReentrant() { require(!locked, "Reentrant call"); locked = true; _; locked = false; } // 安全的 ETH 转账 function safeTransferETH(address _to, uint256 _amount) external noReentrant { assembly { // 检查地址是否有效 if iszero(_to) { revert(0, 0) } // 执行转账 let success := call( gas(), _to, _amount, 0, 0, 0, 0 ) // 验证结果 if iszero(success) { revert(0, 0) } } } // 防止整数溢出 function safeAdd(uint256 a, uint256 b) external pure returns (uint256) { assembly { let result := add(a, b) // 检查溢出:如果 result < a,说明溢出 if lt(result, a) { revert(0, 0) } mstore(0x00, result) return(0x00, 32) } } // 安全的存储写入 function safeStorageWrite(uint256 slot, uint256 value) external { assembly { // 检查 slot 是否在安全范围内 if lt(slot, 100) { revert(0, 0) } sstore(slot, value) } }}9. 常见用例contract AssemblyUseCases { // 1. 高效计算哈希 function efficientHash(bytes memory _data) external pure returns (bytes32) { assembly { let ptr := add(_data, 0x20) let size := mload(_data) let hash := keccak256(ptr, size) mstore(0x00, hash) return(0x00, 32) } } // 2. 检查合约代码 function hasCode(address _addr) external view returns (bool) { assembly { let size := extcodesize(_addr) mstore(0x00, gt(size, 0)) return(0x00, 32) } } // 3. 获取当前合约地址 function currentAddress() external view returns (address) { assembly { mstore(0x00, address()) return(0x00, 32) } } // 4. 获取调用者 function getCaller() external view returns (address) { assembly { mstore(0x00, caller()) return(0x00, 32) } } // 5. 获取调用数据 function getCallData() external pure returns (bytes32) { assembly { // 获取函数选择器(前 4 字节) let selector := calldataload(0) mstore(0x00, selector) return(0x00, 32) } } // 6. 高效数组操作 function findAndRemove(uint256[] storage _arr, uint256 _value) external { assembly { // 获取数组长度 let length := sload(_arr.slot) // 查找元素 mstore(0x00, _arr.slot) let baseSlot := keccak256(0x00, 0x20) for { let i := 0 } lt(i, length) { i := add(i, 1) } { let slot := add(baseSlot, i) if eq(sload(slot), _value) { // 找到元素,用最后一个元素替换 let lastSlot := add(baseSlot, sub(length, 1)) sstore(slot, sload(lastSlot)) // 减少长度 sstore(_arr.slot, sub(length, 1)) // 停止循环 stop() } } } }}10. 总结Assembly 是强大的底层工具,但需要谨慎使用:使用场景:Gas 敏感的关键路径实现 Solidity 不支持的功能精细控制内存和存储最佳实践:仅在必要时使用 Assembly充分测试和审计添加详细注释考虑使用 Yul(更安全的汇编方言)安全要点:正确管理内存指针验证所有外部调用手动检查溢出避免覆盖关键存储性能权衡:Assembly 可以显著节省 Gas但会增加代码复杂性和维护成本需要权衡可读性和性能
阅读 0·3月6日 23:07

Solidity 中如何处理随机数生成?有哪些安全方案?

在 Solidity 中生成随机数是一个复杂的问题,因为区块链是确定性的环境,无法直接生成真正随机的数。以下是各种随机数生成方案及其安全性分析。1. 不安全的随机数生成方式使用区块哈希(不安全)contract InsecureRandom { // 危险:矿工可以操纵区块哈希 function getRandomNumber() public view returns (uint256) { return uint256(keccak256(abi.encodePacked(blockhash(block.number - 1)))); } // 危险:矿工可以操纵时间戳 function getRandomFromTimestamp() public view returns (uint256) { return uint256(keccak256(abi.encodePacked(block.timestamp))); } // 危险:所有参数都可被矿工操纵 function getRandomInsecure() public view returns (uint256) { return uint256(keccak256(abi.encodePacked( block.timestamp, block.difficulty, msg.sender ))); }}为什么这些方式不安全?矿工可以操纵 blockhash、block.timestamp、block.difficulty攻击者可以预测结果并选择性提交交易所有链上数据都是公开可见的2. Commit-Reveal 方案这是一种两阶段提交方案,可以防止前置交易攻击。contract CommitReveal { struct Commit { bytes32 commitHash; uint256 revealDeadline; bool revealed; } mapping(address => Commit) public commits; // 第一阶段:提交哈希 function commit(bytes32 _commitHash) external { require(commits[msg.sender].commitHash == bytes32(0), "Already committed"); commits[msg.sender] = Commit({ commitHash: _commitHash, revealDeadline: block.number + 10, revealed: false }); } // 第二阶段:揭示原始值 function reveal(uint256 _secret, uint256 _guess) external { Commit storage userCommit = commits[msg.sender]; require(userCommit.commitHash != bytes32(0), "No commit found"); require(!userCommit.revealed, "Already revealed"); require(block.number <= userCommit.revealDeadline, "Reveal period ended"); // 验证哈希 bytes32 revealHash = keccak256(abi.encodePacked(_secret, _guess)); require(revealHash == userCommit.commitHash, "Invalid reveal"); userCommit.revealed = true; // 生成随机数 uint256 randomNumber = uint256(keccak256(abi.encodePacked( _secret, blockhash(userCommit.revealDeadline) ))); // 使用随机数... } // 计算提交哈希的辅助函数 function getCommitHash(uint256 _secret, uint256 _guess) external pure returns (bytes32) { return keccak256(abi.encodePacked(_secret, _guess)); }}3. Chainlink VRF(可验证随机函数)Chainlink VRF 是目前最安全的链上随机数解决方案。import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";contract RandomNumberConsumer is VRFConsumerBaseV2 { VRFCoordinatorV2Interface COORDINATOR; // 订阅 ID uint64 s_subscriptionId; // 使用的 Gas 通道 bytes32 keyHash; // 回调 Gas 限制 uint32 callbackGasLimit = 100000; // 请求的确认数 uint16 requestConfirmations = 3; // 请求的随机数数量 uint32 numWords = 1; // 存储请求 ID 和对应的游戏数据 mapping(uint256 => address) public requestToGame; mapping(uint256 => uint256[]) public requestIdToRandomWords; event RandomWordsRequested(uint256 requestId, address game); event RandomWordsFulfilled(uint256 requestId, uint256[] randomWords); constructor( uint64 subscriptionId, address vrfCoordinator, bytes32 _keyHash ) VRFConsumerBaseV2(vrfCoordinator) { COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator); s_subscriptionId = subscriptionId; keyHash = _keyHash; } // 请求随机数 function requestRandomWords() external returns (uint256 requestId) { requestId = COORDINATOR.requestRandomWords( keyHash, s_subscriptionId, requestConfirmations, callbackGasLimit, numWords ); requestToGame[requestId] = msg.sender; emit RandomWordsRequested(requestId, msg.sender); } // Chainlink 回调函数 function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { requestIdToRandomWords[requestId] = randomWords; emit RandomWordsFulfilled(requestId, randomWords); // 使用随机数处理游戏逻辑 address game = requestToGame[requestId]; // ... }}4. 使用预言机(Oracle)通过预言机获取链外随机数。interface IOracle { function requestRandomNumber() external returns (uint256 requestId); function getRandomNumber(uint256 requestId) external view returns (uint256);}contract OracleRandom { IOracle public oracle; mapping(uint256 => bool) public usedRandomNumbers; event RandomNumberRequested(uint256 requestId); event RandomNumberReceived(uint256 randomNumber); constructor(address _oracle) { oracle = IOracle(_oracle); } function requestRandom() external returns (uint256 requestId) { requestId = oracle.requestRandomNumber(); emit RandomNumberRequested(requestId); return requestId; } function useRandom(uint256 requestId) external returns (uint256) { uint256 randomNumber = oracle.getRandomNumber(requestId); require(!usedRandomNumbers[randomNumber], "Random number already used"); usedRandomNumbers[randomNumber] = true; emit RandomNumberReceived(randomNumber); return randomNumber; }}5. 多方计算(MPC)方案通过多个参与方共同生成随机数。contract MPCRandom { struct Contribution { bytes32 hash; uint256 value; bool revealed; } mapping(address => Contribution) public contributions; address[] public participants; uint256 public revealDeadline; bool public randomGenerated; uint256 public randomNumber; event ContributionSubmitted(address participant); event RandomNumberGenerated(uint256 randomNumber); modifier onlyParticipant() { require(isParticipant(msg.sender), "Not a participant"); _; } function isParticipant(address _addr) public view returns (bool) { for (uint i = 0; i < participants.length; i++) { if (participants[i] == _addr) return true; } return false; } // 第一阶段:提交贡献的哈希 function submitHash(bytes32 _hash) external onlyParticipant { require(block.number < revealDeadline, "Submission period ended"); require(contributions[msg.sender].hash == bytes32(0), "Already submitted"); contributions[msg.sender].hash = _hash; emit ContributionSubmitted(msg.sender); } // 第二阶段:揭示贡献值 function reveal(uint256 _value, uint256 _nonce) external onlyParticipant { require(block.number >= revealDeadline, "Reveal not started"); require(!contributions[msg.sender].revealed, "Already revealed"); bytes32 expectedHash = keccak256(abi.encodePacked(_value, _nonce)); require(expectedHash == contributions[msg.sender].hash, "Invalid reveal"); contributions[msg.sender].value = _value; contributions[msg.sender].revealed = true; } // 生成随机数 function generateRandom() external { require(block.number > revealDeadline + 10, "Wait for all reveals"); require(!randomGenerated, "Random already generated"); uint256 combined = 0; for (uint i = 0; i < participants.length; i++) { if (contributions[participants[i]].revealed) { combined ^= contributions[participants[i]].value; } } randomNumber = uint256(keccak256(abi.encodePacked(combined, blockhash(block.number - 1)))); randomGenerated = true; emit RandomNumberGenerated(randomNumber); }}6. 随机数使用场景NFT 随机铸造contract NFTRandomMint is VRFConsumerBaseV2 { // ... VRF 设置 ... struct MintRequest { address minter; uint256 tokenId; bool fulfilled; } mapping(uint256 => MintRequest) public mintRequests; uint256 public nextTokenId; function requestMint() external returns (uint256 requestId) { requestId = requestRandomWords(); mintRequests[requestId] = MintRequest({ minter: msg.sender, tokenId: nextTokenId++, fulfilled: false }); } function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { MintRequest storage request = mintRequests[requestId]; request.fulfilled = true; // 使用随机数确定 NFT 属性 uint256 randomness = randomWords[0]; uint256 rarity = randomness % 100; // 0-99 // 铸造 NFT _safeMint(request.minter, request.tokenId); // 设置属性 if (rarity < 5) { _setTokenRarity(request.tokenId, "Legendary"); } else if (rarity < 20) { _setTokenRarity(request.tokenId, "Epic"); } else if (rarity < 50) { _setTokenRarity(request.tokenId, "Rare"); } else { _setTokenRarity(request.tokenId, "Common"); } }}游戏随机结果contract DiceGame is VRFConsumerBaseV2 { struct Game { address player; uint256 bet; uint256 predictedNumber; bool fulfilled; uint256 result; } mapping(uint256 => Game) public games; event GameStarted(uint256 requestId, address player, uint256 bet); event GameFinished(uint256 requestId, uint256 result, bool won); function play(uint256 _predictedNumber) external payable returns (uint256 requestId) { require(msg.value >= 0.01 ether, "Minimum bet is 0.01 ETH"); require(_predictedNumber >= 1 && _predictedNumber <= 6, "Predict 1-6"); requestId = requestRandomWords(); games[requestId] = Game({ player: msg.sender, bet: msg.value, predictedNumber: _predictedNumber, fulfilled: false, result: 0 }); emit GameStarted(requestId, msg.sender, msg.value); } function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override { Game storage game = games[requestId]; game.fulfilled = true; // 生成 1-6 的骰子结果 game.result = (randomWords[0] % 6) + 1; bool won = (game.result == game.predictedNumber); if (won) { // 支付奖金(6倍赔率) payable(game.player).transfer(game.bet * 6); } emit GameFinished(requestId, game.result, won); }}7. 方案对比| 方案 | 安全性 | 成本 | 延迟 | 适用场景 || ------------- | --- | -- | -- | ----- || 区块哈希 | 低 | 免费 | 即时 | 测试环境 || Commit-Reveal | 中 | 低 | 高 | 简单游戏 || Chainlink VRF | 高 | 中等 | 中等 | 生产环境 || 预言机 | 中 | 中等 | 中等 | 特定需求 || MPC | 高 | 高 | 高 | 高价值场景 |8. 最佳实践生产环境使用 Chainlink VRF:最安全、最可靠的解决方案避免使用链上数据生成随机数:所有链上数据都可被操纵实现重试机制:处理 VRF 回调失败的情况限制随机数使用范围:避免重复使用同一随机数添加时间锁:给用户提供退出时间// 安全的随机数使用模式contract SafeRandomUsage { uint256 public constant MAX_RANDOM = 10000; mapping(uint256 => bool) public usedRandoms; function useRandom(uint256 _random) internal returns (uint256) { uint256 normalized = _random % MAX_RANDOM; require(!usedRandoms[normalized], "Random already used"); usedRandoms[normalized] = true; return normalized; }}​
阅读 0·3月6日 23:05

Solidity 中 delegatecall 和 call 的区别是什么?代理合约如何实现?

delegatecall 和 call 是 Solidity 中用于合约间调用的两个重要底层函数,理解它们的区别对于实现代理合约和可升级合约至关重要。1. call 的基本用法call 是最常用的低级调用函数,在目标合约的上下文中执行代码。contract CallExample { // 使用 call 发送 ETH function sendEther(address payable recipient) public payable { (bool success, ) = recipient.call{value: msg.value}(""); require(success, "Transfer failed"); } // 使用 call 调用函数 function callFunction(address target, uint256 value) public returns (bool) { // 编码函数调用:function setValue(uint256) bytes memory data = abi.encodeWithSignature("setValue(uint256)", value); (bool success, bytes memory returnData) = target.call(data); require(success, "Call failed"); return success; } // 使用 call 调用并获取返回值 function callAndGetResult(address target) public view returns (uint256) { bytes memory data = abi.encodeWithSignature("getValue()"); (bool success, bytes memory returnData) = target.staticcall(data); require(success, "Call failed"); return abi.decode(returnData, (uint256)); }}2. delegatecall 的基本用法delegatecall 在调用者的上下文中执行目标合约的代码,保持 msg.sender 和 msg.value 不变。contract DelegatecallExample { uint256 public value; // 这个变量会被目标合约代码修改 // 使用 delegatecall 执行逻辑合约代码 function executeLogic(address logicContract, uint256 newValue) public { bytes memory data = abi.encodeWithSignature("setValue(uint256)", newValue); (bool success, ) = logicContract.delegatecall(data); require(success, "Delegatecall failed"); }}// 逻辑合约contract LogicContract { uint256 public value; // 注意:这个变量布局必须与代理合约一致 function setValue(uint256 newValue) public { value = newValue; // 修改的是调用者(代理合约)的 storage } function getValue() public view returns (uint256) { return value; }}3. call 与 delegatecall 对比| 特性 | call | delegatecall || ---------- | ------------- | -------------- || 执行上下文 | 目标合约 | 调用者合约 || msg.sender | 当前合约地址 | 原始调用者地址 || msg.value | 传递的值 | 原始调用值 || storage 访问 | 目标合约的 storage | 调用者合约的 storage || 代码执行位置 | 目标合约地址 | 目标合约地址 || 使用场景 | 普通调用、转账 | 代理合约、库合约 |4. 代理合约实现基础代理合约( EIP-1967 )contract Proxy { // EIP-1967 标准存储槽 // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1) bytes32 private constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; constructor(address _implementation) { _setImplementation(_implementation); _setAdmin(msg.sender); } modifier onlyAdmin() { require(msg.sender == _getAdmin(), "Not admin"); _; } // 获取实现合约地址 function _getImplementation() internal view returns (address impl) { assembly { impl := sload(IMPLEMENTATION_SLOT) } } // 设置实现合约地址 function _setImplementation(address _implementation) internal { assembly { sstore(IMPLEMENTATION_SLOT, _implementation) } } // 获取管理员地址 function _getAdmin() internal view returns (address adm) { assembly { adm := sload(ADMIN_SLOT) } } // 设置管理员地址 function _setAdmin(address _admin) internal { assembly { sstore(ADMIN_SLOT, _admin) } } // 升级实现合约 function upgradeTo(address _newImplementation) public onlyAdmin { _setImplementation(_newImplementation); } // 获取当前实现地址(便于查询) function implementation() public view returns (address) { return _getImplementation(); } // 回退函数:将所有调用委托给实现合约 fallback() external payable { _delegate(_getImplementation()); } receive() external payable { _delegate(_getImplementation()); } // 核心委托逻辑 function _delegate(address _implementation) internal { assembly { // 复制 msg.data calldatacopy(0, 0, calldatasize()) // 执行 delegatecall let result := delegatecall( gas(), _implementation, 0, calldatasize(), 0, 0 ) // 复制返回数据 returndatacopy(0, 0, returndatasize()) // 根据结果返回或回滚 switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } }}逻辑合约示例// 逻辑合约 V1contract LogicV1 { uint256 public value; address public implementation; // 与代理合约存储布局一致 address public admin; function setValue(uint256 _value) public { value = _value; } function getValue() public view returns (uint256) { return value; }}// 逻辑合约 V2(升级版本)contract LogicV2 { uint256 public value; address public implementation; address public admin; // 新增功能 function setValue(uint256 _value) public { value = _value * 2; // 新逻辑:值翻倍 } function getValue() public view returns (uint256) { return value; } // 新增函数 function increment() public { value++; }}5. 透明代理模式透明代理解决了代理合约和实现合约函数选择器冲突的问题。contract TransparentProxy { bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; bytes32 private constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; constructor(address _implementation, address _admin) { _setImplementation(_implementation); _setAdmin(_admin); } modifier ifAdmin() { if (msg.sender == _getAdmin()) { _; } else { _fallback(); } } // 管理员函数(只有管理员可以调用) function upgradeTo(address _newImplementation) external ifAdmin { _setImplementation(_newImplementation); } function admin() external ifAdmin returns (address) { return _getAdmin(); } function implementation() external ifAdmin returns (address) { return _getImplementation(); } // 修改管理员 function changeAdmin(address _newAdmin) external ifAdmin { _setAdmin(_newAdmin); } // 非管理员调用会执行到这里 function _fallback() internal { _delegate(_getImplementation()); } fallback() external payable { _fallback(); } receive() external payable { _fallback(); } // 存储操作函数 function _getImplementation() internal view returns (address impl) { assembly { impl := sload(IMPLEMENTATION_SLOT) } } function _setImplementation(address _implementation) internal { assembly { sstore(IMPLEMENTATION_SLOT, _implementation) } } function _getAdmin() internal view returns (address adm) { assembly { adm := sload(ADMIN_SLOT) } } function _setAdmin(address _admin) internal { assembly { sstore(ADMIN_SLOT, _admin) } } function _delegate(address _implementation) internal { assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } }}6. UUPS 代理模式UUPS(Universal Upgradeable Proxy Standard)将升级逻辑放在实现合约中。// UUPS 代理contract UUPSProxy { bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; constructor(address _implementation) { _setImplementation(_implementation); } function _getImplementation() internal view returns (address impl) { assembly { impl := sload(IMPLEMENTATION_SLOT) } } function _setImplementation(address _implementation) internal { assembly { sstore(IMPLEMENTATION_SLOT, _implementation) } } fallback() external payable { _delegate(_getImplementation()); } receive() external payable { _delegate(_getImplementation()); } function _delegate(address _implementation) internal { assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } }}// UUPS 实现合约接口interface IUUPS { function upgradeTo(address newImplementation) external; function proxiableUUID() external view returns (bytes32);}// UUPS 实现合约contract UUPSImplementation is IUUPS { bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; uint256 public value; address public implementation; address public owner; modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } constructor() { owner = msg.sender; } function upgradeTo(address newImplementation) external onlyOwner { _authorizeUpgrade(newImplementation); _upgradeTo(newImplementation); } function proxiableUUID() external pure returns (bytes32) { return IMPLEMENTATION_SLOT; } function _authorizeUpgrade(address newImplementation) internal virtual { // 可以在这里添加额外的升级验证逻辑 } function _upgradeTo(address newImplementation) internal { assembly { sstore(IMPLEMENTATION_SLOT, newImplementation) } } function setValue(uint256 _value) public { value = _value; }}7. 存储布局注意事项// 错误的存储布局示例contract WrongLayoutV1 { uint256 public value; // slot 0 address public owner; // slot 1}contract WrongLayoutV2 { address public owner; // slot 0 - 错误!应该是 slot 1 uint256 public value; // slot 1 - 错误!应该是 slot 0 uint256 public newValue; // slot 2}// 正确的存储布局contract CorrectLayoutV1 { uint256 public value; // slot 0 address public owner; // slot 1}contract CorrectLayoutV2 { uint256 public value; // slot 0 - 保持相同 address public owner; // slot 1 - 保持相同 uint256 public newValue; // slot 2 - 新增变量放在最后}8. 最佳实践使用标准存储槽:遵循 EIP-1967 标准避免存储冲突保持存储布局一致:升级时不能改变已有变量的顺序使用 OpenZeppelin:使用经过审计的标准实现测试升级流程:在测试网上充分测试升级过程考虑钻石模式:对于超大型合约,考虑使用 EIP-2535 钻石标准// 使用 OpenZeppelin 的代理import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";// 推荐:使用 UUPS 模式import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";import "@openzeppelin/contracts/access/Ownable.sol";contract MyContract is UUPSUpgradeable, Ownable { function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}}​
阅读 0·3月6日 21:55

Solidity 中签名验证(ECDSA)的原理和实现方式是什么?

ECDSA(椭圆曲线数字签名算法)是以太坊中用于验证交易和消息签名的核心密码学算法。在 Solidity 中实现签名验证对于实现元交易、免 Gas 交易、权限验证等场景非常重要。1. ECDSA 基本原理以太坊使用 secp256k1 椭圆曲线进行签名,签名包含三个部分:r:签名的 x 坐标s:签名的证明v:恢复标识符(27 或 28,或 0/1)// 签名结构struct Signature { bytes32 r; bytes32 s; uint8 v;}2. 基础签名验证使用 OpenZeppelin 的 ECDSA 库import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";contract SignatureVerification { using ECDSA for bytes32; // 验证签名者地址 function verifySignature( bytes32 messageHash, bytes memory signature ) public pure returns (address signer) { // 恢复签名者地址 signer = messageHash.recover(signature); return signer; } // 验证签名是否有效 function isValidSignature( bytes32 messageHash, bytes memory signature, address expectedSigner ) public pure returns (bool) { address recoveredSigner = messageHash.recover(signature); return recoveredSigner == expectedSigner; }}3. 消息哈希处理以太坊标准消息格式contract MessageHashing { // 方式 1:使用 keccak256 直接哈希 function getMessageHash( address _to, uint256 _amount, uint256 _nonce ) public pure returns (bytes32) { return keccak256(abi.encodePacked(_to, _amount, _nonce)); } // 方式 2:使用标准以太坊消息格式(推荐) function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) { // 按照以太坊标准添加前缀 // "\x19Ethereum Signed Message:\n32" + messageHash return keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", _messageHash )); } // 完整的签名验证流程 function verify( address _signer, address _to, uint256 _amount, uint256 _nonce, bytes memory signature ) public pure returns (bool) { // 1. 构建消息哈希 bytes32 messageHash = getMessageHash(_to, _amount, _nonce); // 2. 添加以太坊消息前缀 bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); // 3. 恢复签名者地址 address recoveredSigner = recoverSigner(ethSignedMessageHash, signature); // 4. 验证签名者 return recoveredSigner == _signer; } function recoverSigner( bytes32 _ethSignedMessageHash, bytes memory _signature ) public pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); return ecrecover(_ethSignedMessageHash, v, r, s); } function splitSignature(bytes memory sig) public 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))) } // 调整 v 值(MetaMask 等钱包通常返回 27/28) if (v < 27) { v += 27; } }}4. 元交易(Meta-Transaction)实现元交易允许用户在不支付 Gas 的情况下执行交易。import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";import "@openzeppelin/contracts/security/ReentrancyGuard.sol";contract MetaTransaction is ReentrancyGuard { using ECDSA for bytes32; // 防止重放攻击的 nonce mapping(address => uint256) public nonces; // 执行元交易的地址(可以支付 Gas) address public relayer; // 域分隔符(EIP-712) bytes32 public DOMAIN_SEPARATOR; // EIP-712 类型哈希 bytes32 public constant META_TRANSACTION_TYPEHASH = keccak256("MetaTransaction(address from,address to,uint256 value,uint256 nonce,uint256 data)"); event MetaTransactionExecuted( address indexed from, address indexed to, bytes functionSignature, uint256 nonce ); constructor() { relayer = msg.sender; // 构建域分隔符 DOMAIN_SEPARATOR = keccak256(abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("MetaTransaction")), keccak256(bytes("1")), block.chainid, address(this) )); } // 执行元交易 function executeMetaTransaction( address from, address to, bytes memory functionSignature, bytes32 sigR, bytes32 sigS, uint8 sigV ) external payable nonReentrant returns (bytes memory) { require(msg.sender == relayer, "Only relayer can execute"); // 构建 EIP-712 结构化数据哈希 bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, keccak256(abi.encode( META_TRANSACTION_TYPEHASH, from, to, msg.value, nonces[from], keccak256(functionSignature) )) )); // 恢复签名者 address signer = ecrecover(digest, sigV, sigR, sigS); require(signer == from, "Invalid signature"); require(signer != address(0), "Zero address signer"); // 增加 nonce 防止重放 nonces[from]++; // 执行目标调用 (bool success, bytes memory returnData) = to.call{value: msg.value}(functionSignature); require(success, "Meta transaction failed"); emit MetaTransactionExecuted(from, to, functionSignature, nonces[from] - 1); return returnData; } function getNonce(address from) external view returns (uint256) { return nonces[from]; }}5. EIP-712 结构化数据签名EIP-712 提供了更友好的签名体验,用户在签名时可以看到结构化数据。import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";contract EIP712Example is EIP712 { using ECDSA for bytes32; // 定义 Permit 类型哈希 bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); mapping(address => uint256) public nonces; constructor() EIP712("MyToken", "1") {} // 构建域分隔符 function DOMAIN_SEPARATOR() external view returns (bytes32) { return _domainSeparatorV4(); } // 验证 Permit 签名 function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external { require(block.timestamp <= deadline, "Permit expired"); bytes32 structHash = keccak256(abi.encode( PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline )); bytes32 hash = _hashTypedDataV4(structHash); address signer = hash.recover(v, r, s); require(signer == owner, "Invalid signature"); // 执行授权逻辑 _approve(owner, spender, value); } function _approve(address owner, address spender, uint256 value) internal { // 实现授权逻辑 }}6. 多签钱包实现contract MultiSigWallet { using ECDSA for bytes32; address[] public owners; mapping(address => bool) public isOwner; uint256 public requiredSignatures; struct Transaction { address to; uint256 value; bytes data; bool executed; uint256 signatureCount; } Transaction[] public transactions; mapping(uint256 => mapping(address => bool)) public signatures; event TransactionSubmitted(uint256 indexed txId, address indexed to, uint256 value); event TransactionSigned(uint256 indexed txId, address indexed signer); event TransactionExecuted(uint256 indexed txId); 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); } requiredSignatures = _required; } // 提交交易并验证签名 function submitTransaction( address _to, uint256 _value, bytes memory _data, bytes[] memory _signatures ) external onlyOwner returns (uint256 txId) { require(_signatures.length >= requiredSignatures, "Insufficient signatures"); // 构建交易哈希 bytes32 txHash = keccak256(abi.encodePacked( address(this), _to, _value, keccak256(_data), transactions.length )); bytes32 ethSignedHash = keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", txHash )); // 验证每个签名 address[] memory signers = new address[](_signatures.length); for (uint i = 0; i < _signatures.length; i++) { address signer = recoverSigner(ethSignedHash, _signatures[i]); require(isOwner[signer], "Signer not an owner"); // 检查重复签名 for (uint j = 0; j < i; j++) { require(signers[j] != signer, "Duplicate signature"); } signers[i] = signer; } txId = transactions.length; transactions.push(Transaction({ to: _to, value: _value, data: _data, executed: false, signatureCount: _signatures.length })); emit TransactionSubmitted(txId, _to, _value); // 自动执行 executeTransaction(txId); } function executeTransaction(uint256 _txId) internal { Transaction storage transaction = transactions[_txId]; require(!transaction.executed, "Already executed"); require(transaction.signatureCount >= requiredSignatures, "Insufficient signatures"); transaction.executed = true; (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data); require(success, "Transaction failed"); emit TransactionExecuted(_txId); } function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) internal pure returns (address) { return _ethSignedMessageHash.recover(_signature); }}7. 签名重放攻击防护contract ReplayProtection { using ECDSA for bytes32; // 已使用的签名记录 mapping(bytes32 => bool) public usedSignatures; // 用户的 nonce mapping(address => uint256) public nonces; // 链 ID uint256 public chainId; constructor() { chainId = block.chainid; } // 安全的签名验证(包含链 ID 和合约地址) function verifyWithReplayProtection( address _signer, bytes memory _signature, bytes memory _data ) external returns (bool) { uint256 nonce = nonces[_signer]; // 构建包含防重放信息的消息 bytes32 messageHash = keccak256(abi.encodePacked( chainId, address(this), _signer, nonce, _data )); bytes32 ethSignedMessageHash = keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", messageHash )); // 检查签名是否已使用 require(!usedSignatures[ethSignedMessageHash], "Signature already used"); // 恢复签名者 address recoveredSigner = ethSignedMessageHash.recover(_signature); require(recoveredSigner == _signer, "Invalid signature"); // 标记签名已使用 usedSignatures[ethSignedMessageHash] = true; // 增加 nonce nonces[_signer]++; return true; }}8. 签名验证最佳实践始终使用标准以太坊消息格式:添加 \x19Ethereum Signed Message:\n32 前缀防止重放攻击:使用 nonce 和链 ID验证签名长度:确保签名长度为 65 字节检查签名者地址:确保恢复的地址不是零地址使用 EIP-712:提供更好的用户体验和安全性考虑使用 OpenZeppelin:经过审计的标准实现// 完整的签名验证最佳实践contract BestPracticeSignature { using ECDSA for bytes32; bytes32 public constant EIP712_DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 public immutable DOMAIN_SEPARATOR; constructor() { DOMAIN_SEPARATOR = keccak256(abi.encode( EIP712_DOMAIN_TYPEHASH, keccak256(bytes("BestPractice")), keccak256(bytes("1")), block.chainid, address(this) )); } function verifyEIP712Signature( bytes32 structHash, bytes memory signature, address expectedSigner ) external view returns (bool) { bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, structHash )); address signer = digest.recover(signature); return signer == expectedSigner && signer != address(0); }}​
阅读 0·3月6日 21:52

Solidity 中 ERC20 和 ERC721 代币标准的核心实现原理是什么?

ERC20 和 ERC721 是以太坊上最广泛使用的代币标准,分别代表同质化代币和非同质化代币(NFT)。理解它们的核心实现原理对于开发 DeFi 和 NFT 项目至关重要。1. ERC20 标准详解ERC20 定义了同质化代币的标准接口,每个代币都是可互换的。ERC20 接口定义interface IERC20 { // 查询总供应量 function totalSupply() external view returns (uint256); // 查询账户余额 function balanceOf(address account) external view returns (uint256); // 查询授权额度 function allowance(address owner, address spender) external view returns (uint256); // 转账 function transfer(address to, uint256 amount) external returns (bool); // 授权 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);}ERC20 完整实现contract ERC20 is IERC20 { // 状态变量 mapping(address => uint256) private _balances; mapping(address => mapping(address => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; uint8 private _decimals; // 构造函数 constructor(string memory name_, string memory symbol_, uint8 decimals_) { _name = name_; _symbol = symbol_; _decimals = decimals_; } // 查询函数 function name() public view returns (string memory) { return _name; } function symbol() public view returns (string memory) { return _symbol; } function decimals() public view returns (uint8) { return _decimals; } function totalSupply() public view override returns (uint256) { return _totalSupply; } function balanceOf(address account) public view override returns (uint256) { return _balances[account]; } function allowance(address owner, address spender) public view override returns (uint256) { return _allowances[owner][spender]; } // 转账函数 function transfer(address to, uint256 amount) public override returns (bool) { address owner = msg.sender; _transfer(owner, to, amount); return true; } // 授权函数 function approve(address spender, uint256 amount) public override returns (bool) { address owner = msg.sender; _approve(owner, spender, amount); return true; } // 授权转账 function transferFrom(address from, address to, uint256 amount) public override returns (bool) { address spender = msg.sender; _spendAllowance(from, spender, amount); _transfer(from, to, amount); return true; } // 内部转账逻辑 function _transfer(address from, address to, uint256 amount) internal { require(from != address(0), "ERC20: transfer from zero address"); require(to != address(0), "ERC20: transfer to zero address"); uint256 fromBalance = _balances[from]; require(fromBalance >= amount, "ERC20: insufficient balance"); unchecked { _balances[from] = fromBalance - amount; _balances[to] += amount; } emit Transfer(from, to, amount); } // 内部授权逻辑 function _approve(address owner, address spender, uint256 amount) internal { require(owner != address(0), "ERC20: approve from zero address"); require(spender != address(0), "ERC20: approve to zero address"); _allowances[owner][spender] = amount; emit Approval(owner, spender, amount); } // 消耗授权额度 function _spendAllowance(address owner, address spender, uint256 amount) internal { uint256 currentAllowance = allowance(owner, spender); if (currentAllowance != type(uint256).max) { require(currentAllowance >= amount, "ERC20: insufficient allowance"); unchecked { _approve(owner, spender, currentAllowance - amount); } } } // 铸造代币(内部函数) function _mint(address account, uint256 amount) internal { require(account != address(0), "ERC20: mint to zero address"); _totalSupply += amount; unchecked { _balances[account] += amount; } emit Transfer(address(0), account, amount); } // 销毁代币(内部函数) function _burn(address account, uint256 amount) internal { require(account != address(0), "ERC20: burn from zero address"); uint256 accountBalance = _balances[account]; require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); unchecked { _balances[account] = accountBalance - amount; _totalSupply -= amount; } emit Transfer(account, address(0), amount); }}ERC20 扩展功能// 可暂停的 ERC20import "@openzeppelin/contracts/security/Pausable.sol";contract PausableERC20 is ERC20, Pausable { constructor() ERC20("PausableToken", "PTK", 18) {} function _beforeTokenTransfer(address from, address to, uint256 amount) internal override whenNotPaused { super._beforeTokenTransfer(from, to, amount); } function pause() public onlyOwner { _pause(); } function unpause() public onlyOwner { _unpause(); }}// 可销毁的 ERC20contract BurnableERC20 is ERC20 { constructor() ERC20("BurnableToken", "BTK", 18) {} function burn(uint256 amount) public { _burn(msg.sender, amount); } function burnFrom(address account, uint256 amount) public { _spendAllowance(account, msg.sender, amount); _burn(account, amount); }}2. ERC721 标准详解ERC721 定义了非同质化代币(NFT)的标准,每个代币都是唯一的。ERC721 接口定义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 safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; // 转账 function transferFrom(address from, address to, uint256 tokenId) external; // 授权 function approve(address to, uint256 tokenId) external; function setApprovalForAll(address operator, bool approved) external; // 查询授权 function getApproved(uint256 tokenId) external view returns (address operator); function isApprovedForAll(address owner, address operator) external view returns (bool); // 事件 event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); event ApprovalForAll(address indexed owner, address indexed operator, bool approved);}// 元数据接口interface IERC721Metadata { function name() external view returns (string memory); function symbol() external view returns (string memory); function tokenURI(uint256 tokenId) external view returns (string memory);}ERC721 完整实现contract ERC721 is IERC721, IERC721Metadata { // Token name string private _name; // Token symbol string private _symbol; // Token ID 到所有者地址的映射 mapping(uint256 => address) private _owners; // 所有者地址到代币数量的映射 mapping(address => uint256) private _balances; // Token ID 到授权地址的映射 mapping(uint256 => address) private _tokenApprovals; // 所有者到操作员授权的映射 mapping(address => mapping(address => bool)) private _operatorApprovals; // Token ID 到 token URI 的映射 mapping(uint256 => string) private _tokenURIs; constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } function name() public view override returns (string memory) { return _name; } function symbol() public view override returns (string memory) { return _symbol; } function tokenURI(uint256 tokenId) public view override returns (string memory) { require(_exists(tokenId), "ERC721: URI query for nonexistent token"); return _tokenURIs[tokenId]; } function balanceOf(address owner) public view override returns (uint256) { require(owner != address(0), "ERC721: balance query for zero address"); return _balances[owner]; } function ownerOf(uint256 tokenId) public view override returns (address) { address owner = _owners[tokenId]; require(owner != address(0), "ERC721: owner query for nonexistent token"); return owner; } function approve(address to, uint256 tokenId) public override { address owner = ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require( msg.sender == owner || isApprovedForAll(owner, msg.sender), "ERC721: approve caller is not owner nor approved for all" ); _approve(to, tokenId); } function getApproved(uint256 tokenId) public view override returns (address) { require(_exists(tokenId), "ERC721: approved query for nonexistent token"); return _tokenApprovals[tokenId]; } function setApprovalForAll(address operator, bool approved) public override { require(operator != msg.sender, "ERC721: approve to caller"); _operatorApprovals[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function isApprovedForAll(address owner, address operator) public view override returns (bool) { return _operatorApprovals[owner][operator]; } function transferFrom(address from, address to, uint256 tokenId) public override { require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _transfer(from, to, tokenId); } function safeTransferFrom(address from, address to, uint256 tokenId) public override { safeTransferFrom(from, to, tokenId, ""); } function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory _data ) public override { require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _safeTransfer(from, to, tokenId, _data); } // 内部函数 function _exists(uint256 tokenId) internal view returns (bool) { return _owners[tokenId] != address(0); } function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { require(_exists(tokenId), "ERC721: operator query for nonexistent token"); address owner = ownerOf(tokenId); return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); } function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal { _transfer(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } function _transfer(address from, address to, uint256 tokenId) internal { require(ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); require(to != address(0), "ERC721: transfer to the zero address"); // 清除授权 _approve(address(0), tokenId); _balances[from] -= 1; _balances[to] += 1; _owners[tokenId] = to; emit Transfer(from, to, tokenId); } function _approve(address to, uint256 tokenId) internal { _tokenApprovals[tokenId] = to; emit Approval(ownerOf(tokenId), to, tokenId); } // 铸造函数 function _mint(address to, uint256 tokenId) internal { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted"); _balances[to] += 1; _owners[tokenId] = to; emit Transfer(address(0), to, tokenId); } // 安全铸造 function _safeMint(address to, uint256 tokenId) internal { _safeMint(to, tokenId, ""); } function _safeMint(address to, uint256 tokenId, bytes memory _data) internal { _mint(to, tokenId); require( _checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer" ); } // 销毁函数 function _burn(uint256 tokenId) internal { address owner = ownerOf(tokenId); _approve(address(0), tokenId); _balances[owner] -= 1; delete _owners[tokenId]; emit Transfer(owner, address(0), tokenId); } // 检查接收者是否实现了 ERC721Receiver 接口 function _checkOnERC721Received( address from, address to, uint256 tokenId, bytes memory _data ) private returns (bool) { if (to.code.length > 0) { try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data) returns (bytes4 retval) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert("ERC721: transfer to non ERC721Receiver implementer"); } else { assembly { revert(add(32, reason), mload(reason)) } } } } return true; }}// ERC721Receiver 接口interface IERC721Receiver { function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4);}3. ERC20 vs ERC721 对比| 特性 | ERC20 | ERC721 || ---- | ------------------------ | ------------------------------------------------------------ || 代币类型 | 同质化 | 非同质化 || 互换性 | 可互换 | 唯一不可互换 || 余额查询 | balanceOf(address) | balanceOf(address) + ownerOf(tokenId) || 转账 | transfer(to, amount) | transferFrom(from, to, tokenId) || 授权 | approve(spender, amount) | approve(to, tokenId) + setApprovalForAll(operator, approved) || 使用场景 | 货币、积分、治理代币 | 数字艺术品、游戏道具、身份凭证 |4. 实际应用示例完整的 ERC20 Tokenimport "@openzeppelin/contracts/token/ERC20/ERC20.sol";import "@openzeppelin/contracts/access/Ownable.sol";contract MyToken is ERC20, Ownable { constructor(uint256 initialSupply) ERC20("MyToken", "MTK") { _mint(msg.sender, initialSupply * 10 ** decimals()); } function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); }}完整的 NFT 合约import "@openzeppelin/contracts/token/ERC721/ERC721.sol";import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";import "@openzeppelin/contracts/access/Ownable.sol";contract MyNFT is ERC721URIStorage, Ownable { uint256 private _tokenIds; uint256 public mintPrice = 0.01 ether; uint256 public maxSupply = 10000; string public baseURI; constructor(string memory _baseURI) ERC721("MyNFT", "MNFT") { baseURI = _baseURI; } function mint(string memory tokenURI) public payable returns (uint256) { require(msg.value >= mintPrice, "Insufficient payment"); require(_tokenIds < maxSupply, "Max supply reached"); _tokenIds++; uint256 newTokenId = _tokenIds; _mint(msg.sender, newTokenId); _setTokenURI(newTokenId, tokenURI); return newTokenId; } function withdraw() public onlyOwner { payable(owner()).transfer(address(this).balance); } function _baseURI() internal view override returns (string memory) { return baseURI; }}5. 安全注意事项整数溢出:使用 Solidity 0.8+ 或 SafeMath 库重入攻击:遵循 Checks-Effects-Interactions 模式授权管理:正确处理 approve 和 allowance零地址检查:防止代币发送到零地址事件发射:所有状态变更都应触发事件6. 扩展标准ERC777:更高级的代币标准,支持钩子函数ERC1155:多代币标准,支持同质化和非同质化代币ERC2981:NFT 版税标准ERC4907:NFT 租赁标准
阅读 0·3月6日 21:51

Solidity 智能合约如何进行 Gas 优化?有哪些常见的优化技巧?

Gas 优化是 Solidity 开发中的关键技能,直接影响合约的执行成本和用户体验。以下是系统性的 Gas 优化策略和技巧。1. 存储优化Storage 是最昂贵的资源,优化存储使用是 Gas 优化的首要任务。使用合适的数据类型contract StorageOptimization { // 不推荐:使用 uint256 存储小数值 uint256 public smallValue; // 占用 32 字节 // 推荐:使用更小的数据类型 uint128 public value128; // 占用 16 字节 uint64 public value64; // 占用 8 字节 uint32 public value32; // 占用 4 字节 uint8 public value8; // 占用 1 字节 // 最佳:打包存储变量 uint128 public balance; // slot 0: 16 字节 uint32 public timestamp; // slot 0: 4 字节 uint16 public status; // slot 0: 2 字节 address public owner; // slot 0: 20 字节 - 需要 slot 1 // 注意:uint128 + uint32 + uint16 = 22 字节,address 需要新的 slot}存储变量打包contract PackingExample { // 未优化:占用 3 个 storage slot struct Unoptimized { uint256 a; // slot 0 uint128 b; // slot 1 uint128 c; // slot 2 } // 优化后:只占用 2 个 storage slot struct Optimized { uint128 b; // slot 0 (16 bytes) uint128 c; // slot 0 (16 bytes) - 打包在一起 uint256 a; // slot 1 } // 最佳实践:按大小排序 struct BestPractice { uint128 a; // 16 bytes uint128 b; // 16 bytes - 与 a 共用一个 slot uint64 c; // 8 bytes uint64 d; // 8 bytes - 与 c 共用 uint32 e; // 4 bytes uint32 f; // 4 bytes - 与 e 共用 }}使用 memory 代替 storagecontract MemoryVsStorage { uint256[] public data; // 昂贵:多次 storage 访问 function expensiveSum() public view returns (uint256) { uint256 sum = 0; for (uint i = 0; i < data.length; i++) { sum += data[i]; // 每次循环都访问 storage } return sum; } // 优化:先加载到 memory function optimizedSum() public view returns (uint256) { uint256[] memory localData = data; // 一次性加载 uint256 sum = 0; for (uint i = 0; i < localData.length; i++) { sum += localData[i]; // 访问 memory,便宜很多 } return sum; }}2. 变量和计算优化使用常量和不变量contract ConstantOptimization { // 昂贵:storage 变量 uint256 public constantValue = 1000; // 优化:常量(不占用 storage) uint256 public constant CONSTANT_VALUE = 1000; // 更优:不可变量(编译时确定,不占用 storage) uint256 public immutable immutableValue; constructor(uint256 _value) { immutableValue = _value; } // 使用常量计算 function calculate(uint256 amount) public pure returns (uint256) { return amount * CONSTANT_VALUE / 10000; }}短路求优contract ShortCircuit { // 优化:利用短路求值 function checkConditions(bool a, bool b, bool c) public pure returns (bool) { // 将最可能为 false 的条件放在前面 return a && b && c; } // 优化:|| 运算符 function checkOr(bool a, bool b, bool c) public pure returns (bool) { // 将最可能为 true 的条件放在前面 return a || b || c; }}3. 循环优化循环变量优化contract LoopOptimization { uint256[] public data; // 未优化:每次循环都访问 storage function unoptimizedLoop() public view returns (uint256) { uint256 sum = 0; for (uint256 i = 0; i < data.length; i++) { sum += data[i]; } return sum; } // 优化 1:缓存数组长度 function optimizedLoop1() public view returns (uint256) { uint256 sum = 0; uint256 len = data.length; // 缓存长度 for (uint256 i = 0; i < len; i++) { sum += data[i]; } return sum; } // 优化 2:使用 unchecked 和 ++i function optimizedLoop2() public view returns (uint256) { uint256 sum = 0; uint256 len = data.length; for (uint256 i = 0; i < len; ) { sum += data[i]; unchecked { ++i; } // 使用 unchecked 和前置递增 } return sum; } // 优化 3:使用 memory 数组 function optimizedLoop3() public view returns (uint256) { uint256[] memory localData = data; uint256 sum = 0; uint256 len = localData.length; for (uint256 i = 0; i < len; ) { sum += localData[i]; unchecked { ++i; } } return sum; }}4. 函数优化使用 calldatacontract CalldataOptimization { // 昂贵:使用 memory function processMemory(uint256[] memory data) external pure returns (uint256) { uint256 sum = 0; for (uint i = 0; i < data.length; i++) { sum += data[i]; } return sum; } // 优化:使用 calldata(只读,不复制) function processCalldata(uint256[] calldata data) external pure returns (uint256) { uint256 sum = 0; for (uint i = 0; i < data.length; i++) { sum += data[i]; } return sum; }}函数修饰符优化contract ModifierOptimization { // 昂贵:修饰符代码内联到每个函数 modifier expensiveCheck() { require(msg.sender == owner, "Not owner"); require(!paused, "Paused"); require(balance > 0, "No balance"); _; } // 优化:将检查提取为内部函数 modifier optimizedCheck() { _checkAccess(); _; } function _checkAccess() internal view { require(msg.sender == owner, "Not owner"); require(!paused, "Paused"); require(balance > 0, "No balance"); } // 这样编译器可以更有效地优化 function action1() external optimizedCheck { } function action2() external optimizedCheck { } function action3() external optimizedCheck { }}5. 错误处理优化自定义错误(Solidity 0.8.4+)// 文件: ErrorOptimization.sol// 开源许可: MITpragma solidity ^0.8.4;contract ErrorOptimization { address public owner; uint256 public balance; // 定义自定义错误(比 require 字符串更省 Gas) error NotOwner(address caller); error InsufficientBalance(uint256 requested, uint256 available); error InvalidAmount(uint256 amount); modifier onlyOwner() { if (msg.sender != owner) { revert NotOwner(msg.sender); } _; } function withdraw(uint256 amount) external onlyOwner { if (amount == 0) { revert InvalidAmount(amount); } if (amount > balance) { revert InsufficientBalance(amount, balance); } balance -= amount; payable(msg.sender).transfer(amount); }}6. 事件和日志优化contract EventOptimization { // 昂贵:存储大量数据 mapping(uint256 => Transaction) public transactions; struct Transaction { address from; address to; uint256 amount; uint256 timestamp; string metadata; } // 优化:使用事件代替 storage event TransactionExecuted( address indexed from, address indexed to, uint256 amount, uint256 timestamp ); function executeTransaction(address to, uint256 amount) external { // 执行业务逻辑... // 使用事件记录(比 storage 便宜得多) emit TransactionExecuted(msg.sender, to, amount, block.timestamp); }}7. 库的使用// 使用库减少代码重复library SafeMath { function add(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { uint256 c = a + b; require(c >= a, "Addition overflow"); return c; } }}contract UsingLibrary { using SafeMath for uint256; function calculate(uint256 a, uint256 b) public pure returns (uint256) { return a.add(b); // 使用库函数 }}8. 位运算优化contract BitwiseOptimization { // 使用位运算代替数学运算 // 乘以/除以 2 的幂 function multiplyBy2(uint256 x) public pure returns (uint256) { return x << 1; // 等同于 x * 2 } function divideBy2(uint256 x) public pure returns (uint256) { return x >> 1; // 等同于 x / 2 } // 检查是否为 2 的幂 function isPowerOf2(uint256 x) public pure returns (bool) { return x > 0 && (x & (x - 1)) == 0; } // 使用位掩码存储多个布尔值 uint256 private flags; uint256 constant FLAG_PAUSED = 1 << 0; // 1 uint256 constant FLAG_FINALIZED = 1 << 1; // 2 uint256 constant FLAG_APPROVED = 1 << 2; // 4 function setFlag(uint256 flag) internal { flags |= flag; } function clearFlag(uint256 flag) internal { flags &= ~flag; } function hasFlag(uint256 flag) internal view returns (bool) { return (flags & flag) != 0; }}9. 编译器优化使用优化器// hardhat.config.jsmodule.exports = { solidity: { version: "0.8.19", settings: { optimizer: { enabled: true, runs: 200 // 根据合约调用频率调整 } } }};10. Gas 优化对比表| 优化技术 | Gas 节省 | 适用场景 || ------------ | --------------- | ------ || 存储打包 | 20,000 Gas/slot | 多个小变量 || 使用常量 | 20,000 Gas | 固定值 || 使用 calldata | 3,800 Gas/32字节 | 外部函数参数 || 自定义错误 | ~50 Gas | 错误处理 || unchecked | ~80 Gas/操作 | 数学运算 || 事件替代 storage | ~19,000 Gas | 日志记录 || 短路求值 | 变量 | 条件判断 |11. 优化工具hardhat-gas-reporter: 报告每个测试的 Gas 消耗eth-gas-reporter: 详细的 Gas 分析报告Remix IDE: 内置 Gas 估算Tenderly: Gas 分析和优化建议// hardhat.config.jsrequire("hardhat-gas-reporter");module.exports = { gasReporter: { enabled: true, currency: "USD", gasPrice: 21 }};12. 最佳实践总结优先优化存储:Storage 操作是最昂贵的使用合适的数据类型:避免过度分配利用编译器优化:启用优化器并调整 runs 参数批量操作:减少函数调用次数使用事件:代替不必要的 storage 写入延迟计算:尽可能在需要时计算而非存储定期审计:使用工具检查 Gas 消耗记住:过度优化可能降低代码可读性,需要在性能和可维护性之间找到平衡。
阅读 0·3月6日 21:51

Solidity 中的 Library 和 Contract 有什么区别?如何正确使用 Library?

Library(库)是 Solidity 中一种特殊的代码复用机制,与 Contract(合约)有本质区别。正确使用 Library 可以节省 Gas、提高代码复用性。1. Library 和 Contract 的核心区别| 特性 | Library | Contract || ------ | ---------------- | --------- || 部署 | 可以独立部署或内联 | 必须独立部署 || 状态变量 | 不能有状态变量 | 可以有状态变量 || 继承 | 不能被继承 | 可以被继承 || ETH 接收 | 不能接收 ETH | 可以接收 ETH || 销毁 | 不能自销毁 | 可以自销毁 || 调用方式 | DELEGATECALL 或内联 | CALL || 使用场景 | 工具函数、复用代码 | 业务逻辑、状态管理 |2. Library 的基本用法独立函数库// 文件: MathLibrary.sol// 纯函数库,所有函数都是 purelibrary MathLibrary { // 计算最大公约数 function gcd(uint256 a, uint256 b) internal pure returns (uint256) { while (b != 0) { uint256 temp = b; b = a % b; a = temp; } return a; } // 计算平方根 function sqrt(uint256 x) internal pure returns (uint256) { if (x == 0) return 0; uint256 z = (x + 1) / 2; uint256 y = x; while (z < y) { y = z; z = (x / z + z) / 2; } return y; } // 计算幂 function pow(uint256 base, uint256 exponent) internal pure returns (uint256) { if (exponent == 0) return 1; if (exponent == 1) return base; uint256 result = 1; uint256 b = base; uint256 e = exponent; while (e > 0) { if (e & 1 == 1) { result *= b; } b *= b; e >>= 1; } return result; }}// 使用库contract Calculator { // 使用 using for 语法 using MathLibrary for uint256; function calculate(uint256 a, uint256 b) external pure returns (uint256, uint256, uint256) { uint256 gcdResult = MathLibrary.gcd(a, b); uint256 sqrtResult = a.sqrt(); // 使用 using for 语法 uint256 powResult = a.pow(b); // 使用 using for 语法 return (gcdResult, sqrtResult, powResult); }}结构体库// 文件: ArrayLibrary.sollibrary ArrayLibrary { // 查找元素索引 function find(uint256[] storage arr, uint256 value) internal view returns (int256) { for (uint i = 0; i < arr.length; i++) { if (arr[i] == value) { return int256(i); } } return -1; } // 删除元素(保持顺序) function removeAt(uint256[] storage arr, uint256 index) internal { require(index < arr.length, "Index out of bounds"); for (uint i = index; i < arr.length - 1; i++) { arr[i] = arr[i + 1]; } arr.pop(); } // 删除元素(不保持顺序,更高效) function quickRemove(uint256[] storage arr, uint256 index) internal { require(index < arr.length, "Index out of bounds"); if (index != arr.length - 1) { arr[index] = arr[arr.length - 1]; } arr.pop(); } // 插入排序 function sort(uint256[] storage arr) internal { for (uint i = 1; i < arr.length; i++) { uint256 key = arr[i]; int256 j = int256(i) - 1; while (j >= 0 && arr[uint256(j)] > key) { arr[uint256(j) + 1] = arr[uint256(j)]; j--; } arr[uint256(j) + 1] = key; } } // 检查是否包含 function contains(uint256[] storage arr, uint256 value) internal view returns (bool) { for (uint i = 0; i < arr.length; i++) { if (arr[i] == value) { return true; } } return false; }}// 使用结构体库contract DataManager { using ArrayLibrary for uint256[]; uint256[] public data; function addData(uint256 value) external { data.push(value); } function removeData(uint256 index) external { data.quickRemove(index); // 调用库函数 } function findData(uint256 value) external view returns (int256) { return data.find(value); // 调用库函数 } function sortData() external { data.sort(); // 调用库函数 }}3. Library 的高级用法使用 storage 的库// 文件: MappingLibrary.sollibrary MappingLibrary { // 为 mapping 添加迭代功能 struct IterableMapping { mapping(address => uint256) data; address[] keys; mapping(address => uint256) keyIndex; } function set(IterableMapping storage self, address key, uint256 value) internal { if (self.keyIndex[key] == 0) { // 新键 self.keys.push(key); self.keyIndex[key] = self.keys.length; } self.data[key] = value; } function get(IterableMapping storage self, address key) internal view returns (uint256) { return self.data[key]; } function remove(IterableMapping storage self, address key) internal { require(self.keyIndex[key] != 0, "Key not found"); uint256 index = self.keyIndex[key] - 1; address lastKey = self.keys[self.keys.length - 1]; // 用最后一个元素替换要删除的元素 self.keys[index] = lastKey; self.keyIndex[lastKey] = index + 1; self.keys.pop(); delete self.keyIndex[key]; delete self.data[key]; } function contains(IterableMapping storage self, address key) internal view returns (bool) { return self.keyIndex[key] != 0; } function size(IterableMapping storage self) internal view returns (uint256) { return self.keys.length; } function getKeyAt(IterableMapping storage self, uint256 index) internal view returns (address) { require(index < self.keys.length, "Index out of bounds"); return self.keys[index]; }}// 使用可迭代映射contract TokenHolder { using MappingLibrary for MappingLibrary.IterableMapping; MappingLibrary.IterableMapping private balances; function setBalance(address holder, uint256 amount) external { balances.set(holder, amount); } function getBalance(address holder) external view returns (uint256) { return balances.get(holder); } function removeHolder(address holder) external { balances.remove(holder); } function getAllHolders() external view returns (address[] memory) { address[] memory holders = new address[](balances.size()); for (uint i = 0; i < balances.size(); i++) { holders[i] = balances.getKeyAt(i); } return holders; }}4. 常用的标准库SafeMath(Solidity \< 0.8.0)// 文件: SafeMath.sollibrary SafeMath { function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "SafeMath: subtraction 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, "SafeMath: multiplication overflow"); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { require(b > 0, "SafeMath: division by zero"); return a / b; } function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0, "SafeMath: modulo by zero"); return a % b; }}// 使用 SafeMathcontract SafeCalculator { using SafeMath for uint256; function safeAdd(uint256 a, uint256 b) external pure returns (uint256) { return a.add(b); // 使用库函数 }}Address 库// OpenZeppelin 的 Address 库library Address { function isContract(address account) internal view returns (bool) { return account.code.length > 0; } function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Transfer failed"); } function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.call(data); if (!success) { if (returndata.length > 0) { assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } return returndata; }}// 使用 Address 库contract ContractInteractor { using Address for address; function checkIsContract(address account) external view returns (bool) { return account.isContract(); } function safeTransfer(address payable recipient, uint256 amount) external { recipient.sendValue(amount); }}5. Library 的部署方式内联库(Internal 函数)// 所有函数都是 internal,代码会被内联到调用合约中library InlineLibrary { function internalFunction(uint256 x) internal pure returns (uint256) { return x * 2; }}// 使用时不需要部署库,代码直接内联contract UsingInlineLibrary { using InlineLibrary for uint256; function double(uint256 x) external pure returns (uint256) { return x.internalFunction(); }}外部库(External 函数)// 包含 external 函数,需要单独部署library ExternalLibrary { function externalFunction(uint256 x) external pure returns (uint256) { return x * 2; }}// 使用时需要链接已部署的库地址contract UsingExternalLibrary { function double(uint256 x) external pure returns (uint256) { return ExternalLibrary.externalFunction(x); }}6. Library 的最佳实践命名规范// 库名使用 PascalCase,以 Library 结尾(可选但推荐)library StringLibrary { // 函数名使用 camelCase function toUpperCase(string memory str) internal pure returns (string memory) { // 实现... }}函数可见性library VisibilityExample { // internal:代码内联,不需要部署库(推荐) function internalFunc() internal pure returns (uint256) { return 1; } // external:需要部署库,通过 DELEGATECALL 调用 function externalFunc() external pure returns (uint256) { return 2; } // public:可以内部调用也可以外部调用 function publicFunc() public pure returns (uint256) { return 3; } // private:只能在库内部使用 function privateFunc() private pure returns (uint256) { return 4; }}使用 using forlibrary EnhancedMath { function square(uint256 x) internal pure returns (uint256) { return x * x; } function cube(uint256 x) internal pure returns (uint256) { return x * x * x; }}contract MathUser { // 方式 1:对特定类型使用 using for using EnhancedMath for uint256; function calculate(uint256 x) external pure returns (uint256, uint256) { return (x.square(), x.cube()); } // 方式 2:全局使用(所有 uint256) using EnhancedMath for *;}7. Library 的实际应用案例字符串处理库library StringUtils { function length(string memory str) internal pure returns (uint256) { return bytes(str).length; } function concat(string memory a, string memory b) internal pure returns (string memory) { bytes memory ba = bytes(a); bytes memory bb = bytes(b); bytes memory result = new bytes(ba.length + bb.length); for (uint i = 0; i < ba.length; i++) { result[i] = ba[i]; } for (uint i = 0; i < bb.length; i++) { result[ba.length + i] = bb[i]; } return string(result); } function substring( string memory str, uint256 start, uint256 length ) internal pure returns (string memory) { bytes memory strBytes = bytes(str); require(start + length <= strBytes.length, "Out of bounds"); bytes memory result = new bytes(length); for (uint i = 0; i < length; i++) { result[i] = strBytes[start + i]; } return string(result); } function compare(string memory a, string memory b) internal pure returns (int256) { bytes memory ba = bytes(a); bytes memory bb = bytes(b); uint256 minLength = ba.length < bb.length ? ba.length : bb.length; for (uint i = 0; i < minLength; i++) { if (ba[i] < bb[i]) return -1; if (ba[i] > bb[i]) return 1; } if (ba.length < bb.length) return -1; if (ba.length > bb.length) return 1; return 0; }}// 使用字符串库contract StringProcessor { using StringUtils for string; function processString(string memory input) external pure returns ( uint256 len, string memory sub, int256 cmp ) { len = input.length(); sub = input.substring(0, 5); cmp = input.compare("Hello"); }}8. Library 的 Gas 优化// 优化前:多次重复代码contract BeforeOptimization { function operation1(uint256 x) external pure returns (uint256) { // 复杂计算... return x * 2 + 1; } function operation2(uint256 x) external pure returns (uint256) { // 相同复杂计算... return x * 2 + 1; }}// 优化后:使用库复用代码library OptimizedMath { function complexCalculation(uint256 x) internal pure returns (uint256) { return x * 2 + 1; }}contract AfterOptimization { using OptimizedMath for uint256; function operation1(uint256 x) external pure returns (uint256) { return x.complexCalculation(); } function operation2(uint256 x) external pure returns (uint256) { return x.complexCalculation(); }}9. 总结Library 是 Solidity 中强大的代码复用工具:使用场景:工具函数、数据结构扩展、代码复用部署方式:internal 函数内联,external 函数需要部署最佳实践:优先使用 internal 函数使用 using for 语法提高可读性遵循命名规范利用 OpenZeppelin 等标准库Gas 优化:减少代码重复,提高部署效率
阅读 0·3月6日 21:50

Solidity 中的内联汇编(Inline Assembly)如何使用?有哪些注意事项?

Solidity 内联汇编允许开发者直接编写 EVM 汇编代码,用于优化 Gas 成本、执行底层操作或访问 Solidity 无法直接提供的功能。1. 基础语法Solidity 支持两种汇编方言:assembly { ... }(推荐)和 assembly { ... }。contract AssemblyBasic { function add(uint256 a, uint256 b) public pure returns (uint256) { uint256 result; assembly { // 使用 mload 和 mstore 操作内存 result := add(a, b) // := 是赋值操作符 } return result; } function getCaller() public view returns (address) { address caller; assembly { caller := caller() // 获取调用者地址 } return caller; }}2. 访问变量汇编代码可以读写 Solidity 变量,但需要注意变量类型和存储位置。contract VariableAccess { uint256 public storedValue; // storage 变量 function manipulateVariables(uint256 input) public returns (uint256) { uint256 localVar = 10; // memory 变量 assembly { // 读取 storage 变量 let stored := sload(storedValue.slot) // 写入 storage 变量 sstore(storedValue.slot, add(stored, input)) // 修改 memory 变量(注意:需要知道内存位置) // localVar 在内存中的位置需要计算 } return storedValue; } // 更安全的变量访问方式 function safeAccess(uint256 newValue) public { assembly { // 使用 .slot 访问 storage 变量位置 sstore(storedValue.slot, newValue) // 使用 .offset 访问结构体成员偏移 } }}3. 内存操作EVM 内存是一个线性字节数组,需要手动管理。contract MemoryOperations { function memoryDemo() public pure returns (bytes32) { bytes32 result; assembly { // 在内存位置 0x00 存储 32 字节数据 mstore(0x00, 0x1234567890abcdef) // 从内存位置 0x00 读取 32 字节 result := mload(0x00) // 只存储 1 字节(使用 mstore8) mstore8(0x20, 0xff) } return result; } function copyMemory(bytes memory data) public pure returns (bytes memory) { bytes memory result = new bytes(data.length); assembly { // 获取 data 的内存位置(跳过 32 字节长度前缀) let dataPtr := add(data, 0x20) // 获取 result 的内存位置 let resultPtr := add(result, 0x20) // 数据长度 let len := mload(data) // 复制内存 for { let i := 0 } lt(i, len) { i := add(i, 0x20) } { mstore(add(resultPtr, i), mload(add(dataPtr, i))) } } return result; }}4. 存储操作Storage 是永久存储,操作成本很高。contract StorageOperations { uint256 public value; mapping(address => uint256) public balances; function storageDemo(address user) public { assembly { // 简单 storage 变量 let slot := value.slot let current := sload(slot) sstore(slot, add(current, 1)) // mapping 的 storage 位置计算 // mapping[key] 的位置 = keccak256(key . slot) mstore(0x00, user) mstore(0x20, balances.slot) let mappingSlot := keccak256(0x00, 0x40) // 读取 mapping 值 let balance := sload(mappingSlot) // 写入 mapping 值 sstore(mappingSlot, add(balance, 100)) } }}5. 函数调用和返回contract FunctionCalls { function externalCall(address target, bytes memory data) public returns (bytes memory) { bytes memory result; assembly { // 获取 data 的长度和指针 let dataLen := mload(data) let dataPtr := add(data, 0x20) // 分配返回数据内存 result := mload(0x40) // 获取空闲内存指针 // 调用外部合约 let success := call( gas(), // 传递所有可用 gas target, // 目标地址 0, // 发送的 ETH 数量 dataPtr, // 输入数据指针 dataLen, // 输入数据长度 result, // 返回数据指针 0x40 // 返回数据最大长度 ) // 检查调用是否成功 if iszero(success) { revert(0, 0) } // 更新空闲内存指针 mstore(0x40, add(result, 0x60)) } return result; } // 使用 delegatecall function delegateCall(address target, bytes memory data) public returns (bytes memory) { bytes memory result; assembly { let dataLen := mload(data) let dataPtr := add(data, 0x20) result := mload(0x40) let success := delegatecall( gas(), target, dataPtr, dataLen, result, 0x40 ) if iszero(success) { revert(0, 0) } mstore(0x40, add(result, 0x60)) } return result; }}6. 条件判断和循环contract ControlFlow { function findMax(uint256[] memory arr) public pure returns (uint256) { require(arr.length > 0, "Empty array"); uint256 max; assembly { // 数组长度 let len := mload(arr) // 数组数据开始位置 let dataPtr := add(arr, 0x20) // 初始最大值设为第一个元素 max := mload(dataPtr) // 循环遍历 for { let i := 1 } lt(i, len) { i := add(i, 1) } { // 当前元素位置 let elemPtr := add(dataPtr, mul(i, 0x20)) let elem := mload(elemPtr) // 如果当前元素更大,更新最大值 if gt(elem, max) { max := elem } } } return max; } function conditional(uint256 x) public pure returns (uint256) { uint256 result; assembly { // if-else 逻辑 switch gt(x, 10) case 1 { result := mul(x, 2) } default { result := add(x, 5) } } return result; }}7. 实用优化示例Gas 优化的字符串拼接contract StringOptimization { // Solidity 方式(Gas 较高) function concatSolidity(string memory a, string memory b) public pure returns (string memory) { return string(abi.encodePacked(a, b)); } // 汇编方式(Gas 更低) function concatAssembly(string memory a, string memory b) public pure returns (string memory result) { assembly { let aLen := mload(a) let bLen := mload(b) let totalLen := add(aLen, bLen) // 分配内存 result := mload(0x40) mstore(result, totalLen) // 复制 a let aPtr := add(a, 0x20) let resultPtr := add(result, 0x20) // 使用 identity 预编译合约进行内存复制(更高效) // 或者手动复制 for { let i := 0 } lt(i, aLen) { i := add(i, 0x20) } { mstore(add(resultPtr, i), mload(add(aPtr, i))) } // 复制 b let bPtr := add(b, 0x20) let bResultPtr := add(resultPtr, aLen) for { let i := 0 } lt(i, bLen) { i := add(i, 0x20) } { mstore(add(bResultPtr, i), mload(add(bPtr, i))) } // 更新空闲内存指针 mstore(0x40, add(add(resultPtr, totalLen), 0x20)) } }}高效的数组操作contract ArrayOptimization { // 高效的数组元素删除(不保持顺序) function removeElement(uint256[] storage arr, uint256 index) internal { require(index < arr.length, "Invalid index"); assembly { // 获取数组长度位置 let lenSlot := arr.slot let len := sload(lenSlot) // 如果不是最后一个元素,用最后一个元素替换 if lt(add(index, 1), len) { // 计算存储位置 let lastIndex := sub(len, 1) let indexSlot := add(keccak256(lenSlot, 0x20), index) let lastSlot := add(keccak256(lenSlot, 0x20), lastIndex) // 替换 sstore(indexSlot, sload(lastSlot)) } // 减少长度 sstore(lenSlot, sub(len, 1)) } }}8. 安全注意事项危险操作示例contract AssemblyDangers { // 危险:直接操作 storage 可能导致数据损坏 function dangerousStorageWrite(uint256 slot, uint256 value) public { assembly { sstore(slot, value) // 可以写入任意 slot! } } // 危险:内存溢出 function dangerousMemory() public pure { assembly { // 写入超出分配范围的内存 mstore(0x1000000, 1) // 可能覆盖其他数据 } } // 危险:未检查的外部调用 function dangerousCall(address target) public { assembly { let success := call(gas(), target, 0, 0, 0, 0, 0) // 没有检查 success! } }}安全使用指南contract SafeAssembly { // 安全:验证输入 function safeStorageWrite(bytes32 slot, uint256 value) public { // 只允许写入特定 slot require(slot == keccak256("allowed_slot"), "Invalid slot"); assembly { sstore(slot, value) } } // 安全:检查内存边界 function safeMemoryOperation(bytes memory data) public pure returns (bytes32) { require(data.length >= 32, "Data too short"); bytes32 result; assembly { result := mload(add(data, 0x20)) } return result; } // 安全:检查外部调用结果 function safeExternalCall(address target, bytes memory data) public returns (bool) { bool success; assembly { let dataLen := mload(data) let dataPtr := add(data, 0x20) success := call( gas(), target, 0, dataPtr, dataLen, 0, 0 ) } return success; }}9. 常见使用场景| 场景 | 汇编优势 | 示例 || ------ | ------------ | ------- || Gas 优化 | 减少操作码 | 自定义存储布局 || 底层操作 | 访问 EVM 特性 | 调用预编译合约 || 复杂计算 | 更高效的算法 | 加密操作 || 代理合约 | delegatecall | 可升级合约 || 数据编码 | 自定义序列化 | 紧凑存储 |10. 最佳实践仅在必要时使用汇编:Solidity 编译器已经很优化充分测试:汇编代码难以调试,需要更多测试添加详细注释:汇编代码可读性差,需要解释使用常量:避免魔法数字验证所有输入:防止意外行为考虑使用库:将汇编封装在库中复用审计必不可少:汇编代码必须经过专业审计library SafeAssembly { // 将汇编操作封装在库中 function safeDelegateCall(address target, bytes memory data) internal returns (bool success, bytes memory result) { // 实现... }}​
阅读 0·3月6日 21:50