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

How to use Inline Assembly in Solidity and what are the precautions?

3月6日 21:50

Solidity inline assembly allows developers to write EVM assembly code directly for optimizing Gas costs, performing low-level operations, or accessing features that Solidity cannot directly provide.

1. Basic Syntax

Solidity supports two assembly dialects: assembly { ... } (recommended) and assembly { ... }.

solidity
contract AssemblyBasic { function add(uint256 a, uint256 b) public pure returns (uint256) { uint256 result; assembly { // Use mload and mstore to manipulate memory result := add(a, b) // := is the assignment operator } return result; } function getCaller() public view returns (address) { address caller; assembly { caller := caller() // Get caller address } return caller; } }

2. Accessing Variables

Assembly code can read and write Solidity variables, but need to pay attention to variable types and storage locations.

solidity
contract VariableAccess { uint256 public storedValue; // storage variable function manipulateVariables(uint256 input) public returns (uint256) { uint256 localVar = 10; // memory variable assembly { // Read storage variable let stored := sload(storedValue.slot) // Write storage variable sstore(storedValue.slot, add(stored, input)) // Modify memory variable (note: need to know memory location) // localVar's position in memory needs to be calculated } return storedValue; } // Safer variable access method function safeAccess(uint256 newValue) public { assembly { // Use .slot to access storage variable location sstore(storedValue.slot, newValue) // Use .offset to access struct member offset } } }

3. Memory Operations

EVM memory is a linear byte array that needs to be managed manually.

solidity
contract MemoryOperations { function memoryDemo() public pure returns (bytes32) { bytes32 result; assembly { // Store 32 bytes of data at memory position 0x00 mstore(0x00, 0x1234567890abcdef) // Read 32 bytes from memory position 0x00 result := mload(0x00) // Store only 1 byte (using mstore8) mstore8(0x20, 0xff) } return result; } function copyMemory(bytes memory data) public pure returns (bytes memory) { bytes memory result = new bytes(data.length); assembly { // Get data's memory position (skip 32-byte length prefix) let dataPtr := add(data, 0x20) // Get result's memory position let resultPtr := add(result, 0x20) // Data length let len := mload(data) // Copy memory for { let i := 0 } lt(i, len) { i := add(i, 0x20) } { mstore(add(resultPtr, i), mload(add(dataPtr, i))) } } return result; } }

4. Storage Operations

Storage is permanent storage with high operation costs.

solidity
contract StorageOperations { uint256 public value; mapping(address => uint256) public balances; function storageDemo(address user) public { assembly { // Simple storage variable let slot := value.slot let current := sload(slot) sstore(slot, add(current, 1)) // Mapping storage location calculation // mapping[key] location = keccak256(key . slot) mstore(0x00, user) mstore(0x20, balances.slot) let mappingSlot := keccak256(0x00, 0x40) // Read mapping value let balance := sload(mappingSlot) // Write mapping value sstore(mappingSlot, add(balance, 100)) } } }

5. Function Calls and Returns

solidity
contract FunctionCalls { function externalCall(address target, bytes memory data) public returns (bytes memory) { bytes memory result; assembly { // Get data length and pointer let dataLen := mload(data) let dataPtr := add(data, 0x20) // Allocate return data memory result := mload(0x40) // Get free memory pointer // Call external contract let success := call( gas(), // Pass all available gas target, // Target address 0, // ETH amount to send dataPtr, // Input data pointer dataLen, // Input data length result, // Return data pointer 0x40 // Maximum return data length ) // Check if call was successful if iszero(success) { revert(0, 0) } // Update free memory pointer mstore(0x40, add(result, 0x60)) } return result; } // Using 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. Conditional Judgments and Loops

solidity
contract ControlFlow { function findMax(uint256[] memory arr) public pure returns (uint256) { require(arr.length > 0, "Empty array"); uint256 max; assembly { // Array length let len := mload(arr) // Array data start position let dataPtr := add(arr, 0x20) // Set initial max to first element max := mload(dataPtr) // Loop through for { let i := 1 } lt(i, len) { i := add(i, 1) } { // Current element position let elemPtr := add(dataPtr, mul(i, 0x20)) let elem := mload(elemPtr) // If current element is larger, update max if gt(elem, max) { max := elem } } } return max; } function conditional(uint256 x) public pure returns (uint256) { uint256 result; assembly { // if-else logic switch gt(x, 10) case 1 { result := mul(x, 2) } default { result := add(x, 5) } } return result; } }

7. Practical Optimization Examples

Gas-Optimized String Concatenation

solidity
contract StringOptimization { // Solidity way (higher Gas) function concatSolidity(string memory a, string memory b) public pure returns (string memory) { return string(abi.encodePacked(a, b)); } // Assembly way (lower 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) // Allocate memory result := mload(0x40) mstore(result, totalLen) // Copy a let aPtr := add(a, 0x20) let resultPtr := add(result, 0x20) // Use identity precompiled contract for memory copy (more efficient) // Or copy manually for { let i := 0 } lt(i, aLen) { i := add(i, 0x20) } { mstore(add(resultPtr, i), mload(add(aPtr, i))) } // Copy 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))) } // Update free memory pointer mstore(0x40, add(add(resultPtr, totalLen), 0x20)) } } }

Efficient Array Operations

solidity
contract ArrayOptimization { // Efficient array element removal (doesn't maintain order) function removeElement(uint256[] storage arr, uint256 index) internal { require(index < arr.length, "Invalid index"); assembly { // Get array length position let lenSlot := arr.slot let len := sload(lenSlot) // If not the last element, replace with last element if lt(add(index, 1), len) { // Calculate storage position let lastIndex := sub(len, 1) let indexSlot := add(keccak256(lenSlot, 0x20), index) let lastSlot := add(keccak256(lenSlot, 0x20), lastIndex) // Replace sstore(indexSlot, sload(lastSlot)) } // Decrease length sstore(lenSlot, sub(len, 1)) } } }

8. Security Considerations

Dangerous Operation Examples

solidity
contract AssemblyDangers { // Dangerous: Direct storage manipulation may cause data corruption function dangerousStorageWrite(uint256 slot, uint256 value) public { assembly { sstore(slot, value) // Can write to any slot! } } // Dangerous: Memory overflow function dangerousMemory() public pure { assembly { // Write beyond allocated memory range mstore(0x1000000, 1) // May overwrite other data } } // Dangerous: Unchecked external calls function dangerousCall(address target) public { assembly { let success := call(gas(), target, 0, 0, 0, 0, 0) // Not checking success! } } }

Safe Usage Guidelines

solidity
contract SafeAssembly { // Safe: Validate input function safeStorageWrite(bytes32 slot, uint256 value) public { // Only allow writing to specific slots require(slot == keccak256("allowed_slot"), "Invalid slot"); assembly { sstore(slot, value) } } // Safe: Check memory boundaries 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; } // Safe: Check external call results 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. Common Usage Scenarios

ScenarioAssembly AdvantageExample
Gas optimizationReduce opcodesCustom storage layout
Low-level operationsAccess EVM featuresCall precompiled contracts
Complex calculationsMore efficient algorithmsCryptographic operations
Proxy contractsdelegatecallUpgradeable contracts
Data encodingCustom serializationCompact storage

10. Best Practices

  1. Only use assembly when necessary: Solidity compiler is already quite optimized
  2. Test thoroughly: Assembly code is hard to debug, needs more testing
  3. Add detailed comments: Assembly code has poor readability, needs explanation
  4. Use constants: Avoid magic numbers
  5. Validate all inputs: Prevent unexpected behavior
  6. Consider using libraries: Encapsulate assembly in libraries for reuse
  7. Audit is essential: Assembly code must undergo professional audit
solidity
library SafeAssembly { // Encapsulate assembly operations in library function safeDelegateCall(address target, bytes memory data) internal returns (bool success, bytes memory result) { // Implementation... } }

标签:Solidity