diff --git a/.gas-snapshot b/.gas-snapshot index c4c5c840..fb1acfe8 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -333,20 +333,20 @@ SSTORE2Test:testFailReadInvalidPointerCustomBounds() (gas: 3099) SSTORE2Test:testFailReadInvalidPointerCustomBounds(address,uint256,uint256,bytes) (runs: 256, μ: 4107, ~: 4130) SSTORE2Test:testFailReadInvalidPointerCustomStartBound() (gas: 3004) SSTORE2Test:testFailReadInvalidPointerCustomStartBound(address,uint256,bytes) (runs: 256, μ: 3980, ~: 3988) -SSTORE2Test:testFailWriteReadCustomBoundsOutOfRange(bytes,uint256,uint256,bytes) (runs: 256, μ: 46236, ~: 43603) -SSTORE2Test:testFailWriteReadCustomStartBoundOutOfRange(bytes,uint256,bytes) (runs: 256, μ: 46017, ~: 43452) -SSTORE2Test:testFailWriteReadEmptyOutOfBounds() (gas: 34470) -SSTORE2Test:testFailWriteReadOutOfBounds() (gas: 34426) +SSTORE2Test:testFailWriteReadCustomBoundsOutOfRange(bytes,uint256,uint256,bytes) (runs: 256, μ: 46128, ~: 43557) +SSTORE2Test:testFailWriteReadCustomStartBoundOutOfRange(bytes,uint256,bytes) (runs: 256, μ: 45907, ~: 43400) +SSTORE2Test:testFailWriteReadEmptyOutOfBounds() (gas: 34476) +SSTORE2Test:testFailWriteReadOutOfBounds() (gas: 34432) SSTORE2Test:testFailWriteReadOutOfStartBound() (gas: 34362) -SSTORE2Test:testWriteRead() (gas: 53497) -SSTORE2Test:testWriteRead(bytes,bytes) (runs: 256, μ: 44019, ~: 41555) -SSTORE2Test:testWriteReadCustomBounds() (gas: 34869) -SSTORE2Test:testWriteReadCustomBounds(bytes,uint256,uint256,bytes) (runs: 256, μ: 29483, ~: 44592) -SSTORE2Test:testWriteReadCustomStartBound() (gas: 34740) -SSTORE2Test:testWriteReadCustomStartBound(bytes,uint256,bytes) (runs: 256, μ: 46484, ~: 44053) -SSTORE2Test:testWriteReadEmptyBound() (gas: 34677) -SSTORE2Test:testWriteReadFullBoundedRead() (gas: 53672) -SSTORE2Test:testWriteReadFullStartBound() (gas: 34764) +SSTORE2Test:testWriteRead() (gas: 53186) +SSTORE2Test:testWriteRead(bytes,bytes) (runs: 256, μ: 43914, ~: 41506) +SSTORE2Test:testWriteReadCustomBounds() (gas: 34872) +SSTORE2Test:testWriteReadCustomBounds(bytes,uint256,uint256,bytes) (runs: 256, μ: 28155, ~: 41182) +SSTORE2Test:testWriteReadCustomStartBound() (gas: 34743) +SSTORE2Test:testWriteReadCustomStartBound(bytes,uint256,bytes) (runs: 256, μ: 46379, ~: 44004) +SSTORE2Test:testWriteReadEmptyBound() (gas: 34680) +SSTORE2Test:testWriteReadFullBoundedRead() (gas: 53361) +SSTORE2Test:testWriteReadFullStartBound() (gas: 34767) SafeCastLibTest:testFailSafeCastTo128() (gas: 321) SafeCastLibTest:testFailSafeCastTo128(uint256) (runs: 256, μ: 443, ~: 443) SafeCastLibTest:testFailSafeCastTo16() (gas: 343) diff --git a/src/utils/SSTORE2.sol b/src/utils/SSTORE2.sol index 23d69803..b76ce60d 100644 --- a/src/utils/SSTORE2.sol +++ b/src/utils/SSTORE2.sol @@ -12,36 +12,42 @@ library SSTORE2 { //////////////////////////////////////////////////////////////*/ function write(bytes memory data) internal returns (address pointer) { - // Prefix the bytecode with a STOP opcode to ensure it cannot be called. - bytes memory runtimeCode = abi.encodePacked(hex"00", data); - - bytes memory creationCode = abi.encodePacked( - //---------------------------------------------------------------------------------------------------------------// - // Opcode | Opcode + Arguments | Description | Stack View // - //---------------------------------------------------------------------------------------------------------------// - // 0x60 | 0x600B | PUSH1 11 | codeOffset // - // 0x59 | 0x59 | MSIZE | 0 codeOffset // - // 0x81 | 0x81 | DUP2 | codeOffset 0 codeOffset // - // 0x38 | 0x38 | CODESIZE | codeSize codeOffset 0 codeOffset // - // 0x03 | 0x03 | SUB | (codeSize - codeOffset) 0 codeOffset // - // 0x80 | 0x80 | DUP | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset // - // 0x92 | 0x92 | SWAP3 | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // - // 0x59 | 0x59 | MSIZE | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // - // 0x39 | 0x39 | CODECOPY | 0 (codeSize - codeOffset) // - // 0xf3 | 0xf3 | RETURN | // - //---------------------------------------------------------------------------------------------------------------// - hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes. - runtimeCode // The bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit. - ); - - /// @solidity memory-safe-assembly - assembly { - // Deploy a new contract with the generated creation code. - // We start 32 bytes into the code to avoid copying the byte length. - pointer := create(0, add(creationCode, 32), mload(creationCode)) + //---------------------------------------------------------------------------------------------------------------// + // Opcode | Opcode + Arguments | Description | Stack View // + //---------------------------------------------------------------------------------------------------------------// + // 0x60 | 0x600B | PUSH1 11 | codeOffset // + // 0x59 | 0x59 | MSIZE | 0 codeOffset // + // 0x81 | 0x81 | DUP2 | codeOffset 0 codeOffset // + // 0x38 | 0x38 | CODESIZE | codeSize codeOffset 0 codeOffset // + // 0x03 | 0x03 | SUB | (codeSize - codeOffset) 0 codeOffset // + // 0x80 | 0x80 | DUP | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset // + // 0x92 | 0x92 | SWAP3 | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // + // 0x59 | 0x59 | MSIZE | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) // + // 0x39 | 0x39 | CODECOPY | 0 (codeSize - codeOffset) // + // 0xf3 | 0xf3 | RETURN | // + //---------------------------------------------------------------------------------------------------------------// + + unchecked { + // allocate + bytes memory creationCode = new bytes(12 + data.length); // 11+1+data.length + assembly { + mstore(creationCode, 0) // set length to 0 + } + // hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes. + // Prefix the bytecode with a STOP (00) opcode to ensure it cannot be called. + _append(creationCode, hex"60_0B_59_81_38_03_80_92_59_39_F3_00"); + // runtimeCode = hex"00"||data is the bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit. + _append(creationCode, data); + + /// @solidity memory-safe-assembly + assembly { + // Deploy a new contract with the generated creation code. + // We start 32 bytes into the code to avoid copying the byte length. + pointer := create(0, add(creationCode, 32), mload(creationCode)) + } + + require(pointer != address(0), "DEPLOYMENT_FAILED"); } - - require(pointer != address(0), "DEPLOYMENT_FAILED"); } /*////////////////////////////////////////////////////////////// @@ -98,4 +104,28 @@ library SSTORE2 { extcodecopy(pointer, add(data, 32), start, size) } } + + // cheaper than bytes concat :) + function _append(bytes memory dst, bytes memory src) private view { + assembly { + // resize + + let priorLength := mload(dst) + + mstore(dst, add(priorLength, mload(src))) + + // copy + + pop( + staticcall( + gas(), + 4, + add(src, 32), // src data start + mload(src), // src length + add(dst, add(32, priorLength)), // dst write ptr + mload(dst) + ) + ) + } + } }