Skip to content

Commit 8d27b58

Browse files
Vectorizedemo-eth
andauthored
✨ Deterministic SSTORE2 (#303)
Co-authored-by: James Wenzel <[email protected]>
1 parent 0a933f1 commit 8d27b58

File tree

3 files changed

+96
-12
lines changed

3 files changed

+96
-12
lines changed

.gas-snapshot

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -422,11 +422,11 @@ MulticallableTest:testMulticallablePreservesMsgSender() (gas: 11118)
422422
MulticallableTest:testMulticallablePreservesMsgValue() (gas: 37569)
423423
MulticallableTest:testMulticallablePreservesMsgValueUsedTwice() (gas: 39417)
424424
MulticallableTest:testMulticallableReturnDataIsProperlyEncoded() (gas: 11634)
425-
MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 9708, ~: 7470)
425+
MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(string,string,uint256) (runs: 256, μ: 9637, ~: 7464)
426426
MulticallableTest:testMulticallableReturnDataIsProperlyEncoded(uint256,uint256,uint256,uint256) (runs: 256, μ: 11802, ~: 11802)
427427
MulticallableTest:testMulticallableRevertWithCustomError() (gas: 10234)
428428
MulticallableTest:testMulticallableRevertWithMessage() (gas: 11946)
429-
MulticallableTest:testMulticallableRevertWithMessage(string) (runs: 256, μ: 12609, ~: 12655)
429+
MulticallableTest:testMulticallableRevertWithMessage(string) (runs: 256, μ: 12611, ~: 12655)
430430
MulticallableTest:testMulticallableRevertWithNothing() (gas: 10100)
431431
MulticallableTest:testMulticallableWithNoData() (gas: 6290)
432432
OwnableRolesTest:testGrantAndRemoveRolesDirect(address,uint256,uint256) (runs: 256, μ: 37138, ~: 40212)
@@ -458,26 +458,27 @@ OwnableRolesTest:testTransferOwnership() (gas: 19569)
458458
OwnableRolesTest:testTransferOwnership(address,bool,bool) (runs: 256, μ: 13821, ~: 13035)
459459
OwnableRolesTest:test__codesize() (gas: 20728)
460460
SSTORE2Test:testReadInvalidPointerCustomBoundsReverts() (gas: 3197)
461-
SSTORE2Test:testReadInvalidPointerCustomBoundsReverts(address,uint256,uint256) (runs: 256, μ: 724242, ~: 638383)
461+
SSTORE2Test:testReadInvalidPointerCustomBoundsReverts(address,uint256,uint256) (runs: 256, μ: 718904, ~: 624429)
462462
SSTORE2Test:testReadInvalidPointerCustomStartBoundReverts() (gas: 3241)
463-
SSTORE2Test:testReadInvalidPointerCustomStartBoundReverts(address,uint256) (runs: 256, μ: 726004, ~: 638486)
464-
SSTORE2Test:testReadInvalidPointerRevert(address) (runs: 256, μ: 750001, ~: 638362)
463+
SSTORE2Test:testReadInvalidPointerCustomStartBoundReverts(address,uint256) (runs: 256, μ: 745800, ~: 624451)
464+
SSTORE2Test:testReadInvalidPointerRevert(address) (runs: 256, μ: 670217, ~: 624561)
465465
SSTORE2Test:testReadInvalidPointerReverts() (gas: 3215)
466466
SSTORE2Test:testWriteRead() (gas: 76106)
467-
SSTORE2Test:testWriteRead(bytes) (runs: 256, μ: 801187, ~: 682523)
467+
SSTORE2Test:testWriteRead(bytes) (runs: 256, μ: 770750, ~: 668136)
468468
SSTORE2Test:testWriteReadCustomBounds() (gas: 34609)
469-
SSTORE2Test:testWriteReadCustomBounds(bytes,uint256,uint256) (runs: 256, μ: 743319, ~: 671096)
470-
SSTORE2Test:testWriteReadCustomBoundsOutOfRangeReverts(bytes,uint256,uint256) (runs: 256, μ: 802916, ~: 676174)
469+
SSTORE2Test:testWriteReadCustomBounds(bytes,uint256,uint256) (runs: 256, μ: 700018, ~: 655118)
470+
SSTORE2Test:testWriteReadCustomBoundsOutOfRangeReverts(bytes,uint256,uint256) (runs: 256, μ: 788965, ~: 662281)
471471
SSTORE2Test:testWriteReadCustomStartBound() (gas: 34843)
472-
SSTORE2Test:testWriteReadCustomStartBound(bytes,uint256) (runs: 256, μ: 814983, ~: 679249)
473-
SSTORE2Test:testWriteReadCustomStartBoundOutOfRangeReverts(bytes,uint256) (runs: 256, μ: 757497, ~: 675943)
472+
SSTORE2Test:testWriteReadCustomStartBound(bytes,uint256) (runs: 256, μ: 792462, ~: 665571)
473+
SSTORE2Test:testWriteReadCustomStartBoundOutOfRangeReverts(bytes,uint256) (runs: 256, μ: 721337, ~: 662060)
474+
SSTORE2Test:testWriteReadDeterministic(bytes) (runs: 256, μ: 872386, ~: 741858)
474475
SSTORE2Test:testWriteReadEmptyBound() (gas: 33835)
475476
SSTORE2Test:testWriteReadEmptyOutOfBoundsReverts() (gas: 36473)
476477
SSTORE2Test:testWriteReadFullBoundedRead() (gas: 76146)
477478
SSTORE2Test:testWriteReadFullStartBound() (gas: 35075)
478479
SSTORE2Test:testWriteReadOutOfBoundsReverts() (gas: 36473)
479480
SSTORE2Test:testWriteReadOutOfStartBoundReverts() (gas: 36477)
480-
SSTORE2Test:test__codesize() (gas: 8362)
481+
SSTORE2Test:test__codesize() (gas: 10082)
481482
SafeCastLibTest:testSafeCastToInt(int256) (runs: 256, μ: 4443, ~: 3415)
482483
SafeCastLibTest:testSafeCastToIntBench() (gas: 383456)
483484
SafeCastLibTest:testSafeCastToUint() (gas: 10560)

src/utils/SSTORE2.sol

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ library SSTORE2 {
3434

3535
/// @dev Writes `data` into the bytecode of a storage contract and returns its address.
3636
function write(bytes memory data) internal returns (address pointer) {
37-
// Note: The assembly block below does not expand the memory.
3837
/// @solidity memory-safe-assembly
3938
assembly {
4039
let originalDataLength := mload(data)
@@ -83,6 +82,73 @@ library SSTORE2 {
8382
}
8483
}
8584

85+
/// @dev Writes `data` into the bytecode of a storage contract with `salt`
86+
/// and returns its deterministic address.
87+
function writeDeterministic(bytes memory data, bytes32 salt)
88+
internal
89+
returns (address pointer)
90+
{
91+
/// @solidity memory-safe-assembly
92+
assembly {
93+
let originalDataLength := mload(data)
94+
let dataSize := add(originalDataLength, DATA_OFFSET)
95+
96+
mstore(data, or(0x61000080600a3d393df300, shl(0x40, dataSize)))
97+
98+
// Deploy a new contract with the generated creation code.
99+
pointer := create2(0, add(data, 0x15), add(dataSize, 0xa), salt)
100+
101+
// If `pointer` is zero, revert.
102+
if iszero(pointer) {
103+
// Store the function selector of `DeploymentFailed()`.
104+
mstore(0x00, 0x30116425)
105+
// Revert with (offset, size).
106+
revert(0x1c, 0x04)
107+
}
108+
109+
// Restore original length of the variable size `data`.
110+
mstore(data, originalDataLength)
111+
}
112+
}
113+
114+
/// @dev Returns the initialization code hash of the storage contract for `data`.
115+
/// Used for mining vanity addresses with create2crunch.
116+
function initCodeHash(bytes memory data) internal pure returns (bytes32 hash) {
117+
/// @solidity memory-safe-assembly
118+
assembly {
119+
let originalDataLength := mload(data)
120+
let dataSize := add(originalDataLength, DATA_OFFSET)
121+
122+
mstore(data, or(0x61000080600a3d393df300, shl(0x40, dataSize)))
123+
124+
hash := keccak256(add(data, 0x15), add(dataSize, 0xa))
125+
126+
// Restore original length of the variable size `data`.
127+
mstore(data, originalDataLength)
128+
}
129+
}
130+
131+
/// @dev Returns the address of the storage contract for `data`
132+
/// deployed with `salt` by `deployer`.
133+
function predictDeterministicAddress(bytes memory data, bytes32 salt, address deployer)
134+
internal
135+
pure
136+
returns (address predicted)
137+
{
138+
bytes32 hash = initCodeHash(data);
139+
/// @solidity memory-safe-assembly
140+
assembly {
141+
// Compute and store the bytecode hash.
142+
mstore8(0x00, 0xff) // Write the prefix.
143+
mstore(0x35, hash)
144+
mstore(0x01, shl(96, deployer))
145+
mstore(0x15, salt)
146+
predicted := keccak256(0x00, 0x55)
147+
// Restore the part of the free memory pointer that has been overwritten.
148+
mstore(0x35, 0)
149+
}
150+
}
151+
86152
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
87153
/* READ LOGIC */
88154
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

test/SSTORE2.t.sol

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,21 @@ contract SSTORE2Test is TestPlus {
152152
vm.expectRevert(SSTORE2.ReadOutOfBounds.selector);
153153
SSTORE2.read(pointer, startIndex, endIndex);
154154
}
155+
156+
function testWriteReadDeterministic(bytes calldata testBytes) public brutalizeMemory {
157+
bytes32 salt = bytes32(_random());
158+
address deployer = address(this);
159+
if (_random() % 8 == 0) {
160+
(, deployer) = _randomSigner();
161+
}
162+
vm.prank(deployer);
163+
address deterministicPointer = SSTORE2.writeDeterministic(testBytes, salt);
164+
assertEq(SSTORE2.read(deterministicPointer), testBytes);
165+
assertEq(
166+
SSTORE2.predictDeterministicAddress(testBytes, salt, deployer), deterministicPointer
167+
);
168+
169+
address pointer = SSTORE2.write(testBytes);
170+
assertEq(pointer.code, deterministicPointer.code);
171+
}
155172
}

0 commit comments

Comments
 (0)