### Assembly and Create2 #### Advanced Smart Contract
Yuet Loo Wong
### Topics - Assembly - Memory - Storage - Array - Create2
## Memory in EVM - An expandable byte-array used to store data - Expanding memory costs gas - Memory is expanded by 256-bit - Volatile, only available during execution
## Memory Access Functions - `MLOAD(o)`: load word from memory onto stack - o: memory offset - `MSTORE(o,v)`: store 256-bit word into memory - o: memory offset - v: value from stack - `MSTORE8(o,v)`: store a byte into memory
## Solidity Memory Layout - Solidity reserves memory slots as follow: - 0x00 - 0x3f (64 bytes): scratch space, for short term usage - 0x40 - 0x5f (32 bytes): free memory pointer - 0x60 - 0x7f (32 bytes): zero slot; initial value for dynamic memory arrays - 0x80: initial value of the free memory pointer -
Solidity Documentation
on Memory Layout
## Memory Example ``` contract Sample { function getData(uint value) public view returns (bytes32 output) { assembly { function allocate(length) -> pos { let freePointer := 0x40 pos := mload(freePointer) mstore(freePointer, add(pos,length)) } let dataSize := 0x20 // 32 bytes let offset := allocate(dataSize) mstore(offset, value) return(offset, dataSize) } } } ```
### String and Bytes in Memory - length in first slot - elements start in second slot - elements are tightly packed - each element do not occupy 32 bytes ``` function getFirstElement( bytes memory data ) public pure returns(bytes32) { assembly { mstore(0, mload(add(data,32))) return(0,32) } } ```
## Storage - State variables stored here - Key-value store - maps 256-bit words to 256-bit words - Persistent storage in an account
## Storage Location -
_slot
: storage slot for the variable -
_offset
: byte offset for the variable
## Slot and Offset Example - Use _slot and _offset to get storage location ``` contract Sample { uint8 data1 = 1; uint8 data2 = 2; uint8 data3 = 3; uint8 data4 = 4; function getSlotOffset() public pure returns(uint slot, uint offset){ assembly { slot := data3_slot offset := data3_offset } } } ```
## SSTORE/SLOAD - sstore(p,v): save a word to storage - p: storage slot - v: value to store - sload(p): load a word from storage to stack
## Storage Layout - Statically-sized variables stored from slot
0
- Variables less than 32 bytes are packed in one slot - Refer to other rules in
Solidity Documentation
## Storage Example ``` contract Sample { uint8 data1 = 1; uint8 data2 = 2; uint8 data3 = 3; uint8 data4 = 4; // get data3 and return it as ouput function getData() public view returns(bytes32){ assembly { let data := sload(data3_slot) let result := and(shr(shl(3,data3_offset), data), 0xff) mstore(0, result) return(0,32) } } } ```
## Array in Storage - Array state variables ``` contract Sample { bool[1] boolArray; // storage slot 0 uint[] array; // storage slot 1 } ```
### Dynamic Array Storage Layout - The array length is in the storage slot 'p' - The nth-element is in slot: keccak256(p) + n
### Storage Example -
Github Source
``` contract DynamicArrayStorage { struct Store { uint8 x; uint8 y; } Store[] data; constructor() public { data.push(Store(0x22, 0x33)); } function getData(uint slot) public view returns (bytes32 result) { assembly { mstore(0, data_slot) result := sload(add(keccak256(0,0x20),slot)) } } } ```
### Dynamic Array Vunerability - Doug Hoyte's
Underhanded Solidity Contest
Winning Solution - ``` contract MerdeToken { address public owner; uint[] public bonusCodes; constructor() public { owner = msg.sender; bonusCodes.length--; // what is the array length now? } function modifyBonusCode(uint index, uint update) public { require(index < bonusCodes.length); bonusCodes[index] = update; } } ```
### Exercise - How to change the
owner
in previous example? -
Hints
: - use modifyBonusCode() - calculate array index j which will wrap around and end up in owner's slot k - j = n - x + k - max storage size = n = 2
256
- storage slot for first array item = x = keccak256(array_slot)
### Array Vunerability Fix - Solidity
v0.6
no longer allows array.length-- - Use pop() to remove array element
### Create2 - A new way to create contract - Introduced by
EIP 1014
### Why Another Create? Need an easier way to `park` an address for a contract
### Contract Address - If create is used, the contract address is: ``` keccak256(rlp([caller, nonce]))[12:] ``` - If create2 is used, the contract address is: ``` keccak256(0xff + caller + salt + keccak256(initcode))[12:] ```
### What is initcode? - Contract creation code - Contract Constructor logic and parameters - Generate the contract runtime bytecode - The bytecode in Remix "Compilation details" for a contract
### Runtime Bytecode - Contract code - No constructor and parameters - Stored on-chain - Also known as Deployed Bytecode -
init code vs runtime code
### Create2 Inputs - create2(v, p, l, s) - v: ether to be sent to the contract during creation - p: memory offset to the contract creation code - l: length of the contract creation code - s:
salt
### Create2 Use Cases Use cases from
Getting the most out of create2
: - State channel/counterfactuality - Contract upgrade - User onboarding
### Create2 Example (Solidity) - require solidity v0.6 ``` contract D { uint public x; constructor(uint a) public payable { x = a; } } contract C { function createSalted(bytes32 salt, uint arg) public { D d = new D{salt: salt}(arg); //create2 } } ```
### Create2 Example (Assembly) ``` contract Factory { function deploy(bytes memory code, bytes32 salt) public returns (address addr) { assembly { addr := create2(0, add(code, 0x20), mload(code), salt) if iszero(extcodesize(addr)) { revert(0, 0) } } } } ```
### Create2 Attack Vector Contracts created with create2 could be replaced with different logic at the same address
### Upgrade: Create2 vs Proxy - Create2 - more gas efficient, no delegate call - more complex, uses selfdestruct - selfdestruct will wipe out storage -
Proxy
pattern is more well-known - simpler and easier to understand - storage persists after upgrade
## Lab Follow the instructions on the readme file of
create2-upgrade