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

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

3月6日 23:07

Assembly(汇编)是 Solidity 中的底层编程方式,允许开发者直接操作 EVM 字节码。虽然它提供了极高的灵活性和 Gas 优化空间,但也增加了代码复杂性和安全风险。

1. 为什么使用 Assembly

solidity
contract AssemblyBenefits { // 1. Gas 优化:跳过 Solidity 的高级抽象 // 2. 访问底层 EVM 特性 // 3. 实现 Solidity 不支持的操作 // 4. 精细控制内存和存储 }

2. Assembly 基础语法

内联汇编(Inline Assembly)

solidity
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) // 返回内存中的值 } } }

数据类型和操作

solidity
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. 内存操作

内存布局

solidity
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. 存储操作

存储布局

solidity
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. 函数调用和返回值

外部调用

solidity
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) } } }

委托调用

solidity
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 优化技巧

常见优化模式

solidity
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. 高级技巧

创建合约

solidity
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 交互

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. 安全注意事项

solidity
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. 常见用例

solidity
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 是强大的底层工具,但需要谨慎使用:

  1. 使用场景

    • Gas 敏感的关键路径
    • 实现 Solidity 不支持的功能
    • 精细控制内存和存储
  2. 最佳实践

    • 仅在必要时使用 Assembly
    • 充分测试和审计
    • 添加详细注释
    • 考虑使用 Yul(更安全的汇编方言)
  3. 安全要点

    • 正确管理内存指针
    • 验证所有外部调用
    • 手动检查溢出
    • 避免覆盖关键存储
  4. 性能权衡

    • Assembly 可以显著节省 Gas
    • 但会增加代码复杂性和维护成本
    • 需要权衡可读性和性能
标签:Solidity