diff --git a/.gas-snapshot b/.gas-snapshot index 1fccd61..7b93267 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,9 +1,11 @@ -DataContractTest:testErrorBadAddressRead(address) (runs: 1024, μ: 8865, ~: 8865) -DataContractTest:testNewAddressFuzzData(bytes) (runs: 1024, μ: 86825, ~: 82800) -DataContractTest:testNewAddressFuzzDataDifferent(bytes,bytes) (runs: 1023, μ: 87936, ~: 83281) -DataContractTest:testRoundFuzz(bytes,bytes) (runs: 1024, μ: 46675, ~: 44341) -DataContractTest:testRoundSlice(bytes,uint16,uint16) (runs: 114, μ: 48835, ~: 49038) -DataContractTest:testRoundSliceError(bytes,uint16,uint16) (runs: 910, μ: 48306, ~: 46529) -DataContractTest:testSameReads(bytes) (runs: 1024, μ: 46392, ~: 44315) -DataContractTest:testZeroPrefix(bytes) (runs: 1024, μ: 42023, ~: 40036) -DataContractTest:testZoltu() (gas: 79418) \ No newline at end of file +DataContractTest:testErrorBadAddressRead(address) (runs: 1024, μ: 8771, ~: 8771) +DataContractTest:testNewAddressFuzzData(bytes) (runs: 1024, μ: 85287, ~: 82448) +DataContractTest:testNewAddressFuzzDataDifferent(bytes,bytes) (runs: 1024, μ: 86318, ~: 82982) +DataContractTest:testRoundFuzz(bytes,bytes) (runs: 1024, μ: 45487, ~: 43907) +DataContractTest:testRoundSlice(bytes,uint16,uint16) (runs: 1024, μ: 46636, ~: 44212) +DataContractTest:testRoundSliceError(bytes,uint16,uint16) (runs: 1024, μ: 47523, ~: 46303) +DataContractTest:testSameReads(bytes) (runs: 1024, μ: 45381, ~: 43952) +DataContractTest:testZeroPrefix(bytes) (runs: 1024, μ: 41309, ~: 39893) +DataContractTest:testZoltu() (gas: 78935) +DataContractTest:testZoltuBadZoltu(bytes) (runs: 1024, μ: 1040428879, ~: 1040429085) +DataContractTest:testZoltuNoZoltu(bytes) (runs: 1024, μ: 8598, ~: 8587) \ No newline at end of file diff --git a/flake.lock b/flake.lock index 81c5d4f..4151598 100644 --- a/flake.lock +++ b/flake.lock @@ -182,11 +182,11 @@ "nixpkgs": "nixpkgs_5" }, "locked": { - "lastModified": 1758705030, - "narHash": "sha256-zYM8PiEXANNrtjfyGUc7w37/D/kCynp0cQS+wCQ77GI=", + "lastModified": 1769324704, + "narHash": "sha256-aef15vEgiMEls1hTMt46rJuKNSO2cIOfiP99patq9yc=", "owner": "shazow", "repo": "foundry.nix", - "rev": "b59a55014050110170023e3e1c277c1d4a2f055b", + "rev": "e830409ba1bdecdc5ef9a1ec92660fc2da9bc68d", "type": "github" }, "original": { @@ -209,6 +209,22 @@ "type": "indirect" } }, + "nixpkgs-old": { + "locked": { + "lastModified": 1749104371, + "narHash": "sha256-m2NmOPd6XgBiskmUq/BS9Xxuf3z0ebnGVfSKNAO5NEM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "48975d7f9b9960ed33c4e8561bcce20cc0c2de5b", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "rev": "48975d7f9b9960ed33c4e8561bcce20cc0c2de5b", + "type": "github" + } + }, "nixpkgs_2": { "locked": { "lastModified": 1714764285, @@ -272,11 +288,11 @@ }, "nixpkgs_6": { "locked": { - "lastModified": 1758711836, - "narHash": "sha256-uBqPg7wNX2v6YUdTswH7wWU8wqb60cFZx0tHaWTGF30=", + "lastModified": 1769364508, + "narHash": "sha256-Wy8EVYSLq5Fb/rYH3LRxAMCnW75f9hOg2562AXVFmPk=", "owner": "nixos", "repo": "nixpkgs", - "rev": "46f97b78e825ae762c0224e3983c47687436a498", + "rev": "6077bc4fb29be43d525984f63b69d37b9b1e62fe", "type": "github" }, "original": { @@ -303,11 +319,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1748662220, - "narHash": "sha256-7gGa49iB9nCnFk4h/g9zwjlQAyjtpgcFkODjcOQS0Es=", + "lastModified": 1766653575, + "narHash": "sha256-TPgxCS7+hWc4kPhzkU5dD2M5UuPhLuuaMNZ/IpwKQvI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "59138c7667b7970d205d6a05a8bfa2d78caa3643", + "rev": "3c1016e6acd16ad96053116d0d3043029c9e2649", "type": "github" }, "original": { @@ -363,15 +379,16 @@ "flake-utils": "flake-utils_7", "foundry": "foundry_2", "nixpkgs": "nixpkgs_6", + "nixpkgs-old": "nixpkgs-old", "rust-overlay": "rust-overlay_2", "solc": "solc_2" }, "locked": { - "lastModified": 1760460761, - "narHash": "sha256-IHvwnmphDaOyZnzvObwOoDQlA9nzym2ZUxe9K/5vs0U=", + "lastModified": 1769366341, + "narHash": "sha256-jeYOweTuJdKshW9lqVoNxvl4+flyRzWxEctRGabTW/8=", "owner": "rainlanguage", "repo": "rainix", - "rev": "add0d8a1fd76ce0e65b962c952e9252257876465", + "rev": "e7bfe9c39d2de818eac241f88ecabc69e86ed734", "type": "github" }, "original": { @@ -411,11 +428,11 @@ "nixpkgs": "nixpkgs_7" }, "locked": { - "lastModified": 1758681214, - "narHash": "sha256-8cW731vev6kfr58cILO2ZsjHwaPhm88dQ8Q6nTSjP9I=", + "lastModified": 1769309768, + "narHash": "sha256-AbOIlNO+JoqRJkK1VrnDXhxuX6CrdtIu2hSuy4pxi3g=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "b12ed88d8d33d4f3cbc842bf29fad93bb1437299", + "rev": "140c9dc582cb73ada2d63a2180524fcaa744fad5", "type": "github" }, "original": { @@ -446,13 +463,13 @@ "solc-macos-amd64-list-json": { "flake": false, "locked": { - "narHash": "sha256-AvITkfpNYgCypXuLJyqco0li+unVw39BAfdOZvd/SPE=", + "narHash": "sha256-P+ZslplK4cQ/wnV/wykVKb+yTCviI0eylA3sk9uHmRo=", "type": "file", - "url": "https://github.com/argotorg/solc-bin/raw/26fc3fd/macosx-amd64/list.json" + "url": "https://github.com/argotorg/solc-bin/raw/a11f1ad/macosx-amd64/list.json" }, "original": { "type": "file", - "url": "https://github.com/argotorg/solc-bin/raw/26fc3fd/macosx-amd64/list.json" + "url": "https://github.com/argotorg/solc-bin/raw/a11f1ad/macosx-amd64/list.json" } }, "solc_2": { @@ -462,11 +479,11 @@ "solc-macos-amd64-list-json": "solc-macos-amd64-list-json" }, "locked": { - "lastModified": 1756368702, - "narHash": "sha256-cqEHv7uCV0LibmQphyiXZ1+jYtGjMNb9Pae4tfcAcF8=", + "lastModified": 1768831671, + "narHash": "sha256-0mmlYRtZK+eomevkQCCH7PL8QlSuALZQsjLroCWGE08=", "owner": "hellwolf", "repo": "solc.nix", - "rev": "d83e90df2fa8359a690f6baabf76099432193c3f", + "rev": "80ad871b93d15c7bccf71617f78f73c2d291a9c7", "type": "github" }, "original": { diff --git a/foundry.lock b/foundry.lock index 7563306..309db48 100644 --- a/foundry.lock +++ b/foundry.lock @@ -1,8 +1,8 @@ { "lib/forge-std": { - "rev": "b8f065fda83b8cd94a6b2fec8fcd911dc3b444fd" + "rev": "1801b0541f4fda118a10798fd3486bb7051c5dd6" }, "lib/rain.solmem": { - "rev": "f28da2f09d42d154783cb53c138a7ef2f3a9eb4a" + "rev": "2e47e41af85a711d837b2518656db0efdae814c2" } } \ No newline at end of file diff --git a/lib/forge-std b/lib/forge-std index b8f065f..1801b05 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit b8f065fda83b8cd94a6b2fec8fcd911dc3b444fd +Subproject commit 1801b0541f4fda118a10798fd3486bb7051c5dd6 diff --git a/lib/rain.solmem b/lib/rain.solmem index f28da2f..2e47e41 160000 --- a/lib/rain.solmem +++ b/lib/rain.solmem @@ -1 +1 @@ -Subproject commit f28da2f09d42d154783cb53c138a7ef2f3a9eb4a +Subproject commit 2e47e41af85a711d837b2518656db0efdae814c2 diff --git a/src/lib/LibDataContract.sol b/src/lib/LibDataContract.sol index 338dffe..08aed85 100644 --- a/src/lib/LibDataContract.sol +++ b/src/lib/LibDataContract.sol @@ -6,7 +6,7 @@ pragma solidity ^0.8.25; import {LibPointer, Pointer} from "../../lib/rain.solmem/src/lib/LibPointer.sol"; import {WriteError, ReadError} from "../error/ErrDataContract.sol"; -/// @dev SSTORE2 Verbatim reference +/// @dev SSTORE2 Verbatim original reference /// https://github.com/0xsequence/sstore2/blob/master/contracts/utils/Bytecode.sol#L15 /// /// 0x00 0x63 0x63XXXXXX PUSH4 _code.length size @@ -18,6 +18,9 @@ import {WriteError, ReadError} from "../error/ErrDataContract.sol"; /// 0x06 0xf3 0xf3 RETURN /// /// +/// The assembly below is a modified version of this original reference according +/// to the notes following. +/// /// However note that 00 is also prepended (although docs say append) so there's /// an additional byte that isn't described above. /// https://github.com/0xsequence/sstore2/blob/master/contracts/SSTORE2.sol#L25 @@ -30,12 +33,27 @@ import {WriteError, ReadError} from "../error/ErrDataContract.sol"; /// This also changes the 0x600e to 0x600c as we've reduced prefix size by 2 /// relative to reference implementation. /// https://github.com/0xsequence/sstore2/pull/5/files +/// +/// The final modified bytecode is therefore: +/// 0x61 0x61XXXX PUSH2 _code.length size +/// 0x80 0x80 DUP1 size size +/// 0x60 0x600c PUSH1 12 12 size size +/// 0x60 0x6000 PUSH1 00 0 12 size size +/// 0x39 0x39 CODECOPY size +/// 0x60 0x6000 PUSH1 00 0 size +/// 0xf3 0xf3 RETURN +/// 0x00 0x00 +/// uint256 constant BASE_PREFIX = 0x61_0000_80_600C_6000_39_6000_F3_00_00000000000000000000000000000000000000; /// @dev Length of the prefix that converts in memory data to a deployable /// contract. uint256 constant PREFIX_BYTES_LENGTH = 13; +/// @dev Zoltu deterministic deployment proxy address. +/// https://github.com/Zoltu/deterministic-deployment-proxy?tab=readme-ov-file#proxy-address +address constant ZOLTU_PROXY_ADDRESS = 0x7A0D94F55792C434d74a40883C6ed8545E406D12; + /// A container is a region of memory that is directly deployable with `create`, /// without length prefixes or other Solidity type trappings. Where the length is /// needed, such as in `write` it can be read as bytes `[1,2]` from the prefix. @@ -116,16 +134,17 @@ library LibDataContract { address pointer; uint256 prefixLength = PREFIX_BYTES_LENGTH; assembly ("memory-safe") { - pointer := - create( - 0, - container, - add( - prefixLength, - // Read length out of prefix. - and(0xFFFF, shr(232, mload(container))) - ) + pointer := create( + 0, + container, + add( + prefixLength, + // Read length out of prefix. + // Sub 1 as length stored is +1 to include the 0x00 prefix + // byte. + sub(and(0xFFFF, shr(232, mload(container))), 1) ) + ) } // Zero address means create failed. if (pointer == address(0)) revert WriteError(); @@ -137,27 +156,29 @@ library LibDataContract { /// will be the same on all networks and for all callers for the same data. /// https://github.com/Zoltu/deterministic-deployment-proxy function writeZoltu(DataContractMemoryContainer container) internal returns (address deployedAddress) { + address zoltu = ZOLTU_PROXY_ADDRESS; uint256 prefixLength = PREFIX_BYTES_LENGTH; bool success; assembly ("memory-safe") { mstore(0, 0) - success := - call( - gas(), - 0x7A0D94F55792C434d74a40883C6ed8545E406D12, - 0, - container, - add( - prefixLength, - // Read length out of prefix. - and(0xFFFF, shr(232, mload(container))) - ), - 12, - 20 - ) + success := call( + gas(), + zoltu, + 0, + container, + add( + prefixLength, + // Read length out of prefix. + // Sub 1 as length stored is +1 to include the 0x00 prefix + // byte. + sub(and(0xFFFF, shr(232, mload(container))), 1) + ), + 12, + 20 + ) deployedAddress := mload(0) } - if (!success) revert WriteError(); + if (deployedAddress == address(0) || !success) revert WriteError(); } /// Reads data back from a previously deployed container. diff --git a/test/lib/LibDataContract.t.sol b/test/lib/LibDataContract.t.sol index 8ff6583..c657682 100644 --- a/test/lib/LibDataContract.t.sol +++ b/test/lib/LibDataContract.t.sol @@ -7,7 +7,13 @@ import {LibMemCpy} from "rain.solmem/lib/LibMemCpy.sol"; import {LibBytes} from "rain.solmem/lib/LibBytes.sol"; import { - LibPointer, Pointer, DataContractMemoryContainer, LibDataContract, ReadError + LibPointer, + Pointer, + DataContractMemoryContainer, + LibDataContract, + ReadError, + WriteError, + ZOLTU_PROXY_ADDRESS } from "src/lib/LibDataContract.sol"; /// @title DataContractTest @@ -21,14 +27,16 @@ contract DataContractTest is Test { return LibDataContract.read(datacontract); } - function readSliceExternal(address datacontract, uint16 start, uint16 length) - external - view - returns (bytes memory) - { + function readSliceExternal(address datacontract, uint16 start, uint16 length) external view returns (bytes memory) { return LibDataContract.readSlice(datacontract, start, length); } + function writeZoltuExternal(bytes memory data) external returns (address) { + (DataContractMemoryContainer container, Pointer pointer) = LibDataContract.newContainer(data.length); + LibMemCpy.unsafeCopyBytesTo(data.dataPointer(), pointer, data.length); + return LibDataContract.writeZoltu(container); + } + /// Writing any data to a contract then reading it back without corrupting /// memory or the data itself. function testRoundFuzz(bytes memory data, bytes memory garbage) public { @@ -171,14 +179,34 @@ contract DataContractTest is Test { address datacontractAlpha = LibDataContract.writeZoltu(container); - assertEq(datacontractAlpha, 0x7B5220368D7460A84bCFCCB0616f77E61e5302e2); + assertEq(datacontractAlpha, 0x1Cf89F16784b780E549105B04e80D5196E13C4Af); assertEq(keccak256(data), keccak256(LibDataContract.read(datacontractAlpha))); vm.createSelectFork(vm.envString("CI_FORK_AVALANCHE_RPC_URL")); address datacontractBeta = LibDataContract.writeZoltu(container); - assertEq(datacontractBeta, 0x7B5220368D7460A84bCFCCB0616f77E61e5302e2); + assertEq(datacontractBeta, 0x1Cf89F16784b780E549105B04e80D5196E13C4Af); assertEq(keccak256(data), keccak256(LibDataContract.read(datacontractBeta))); } + + /// Check that if we use zoltu without the zoltu proxy existing that we + /// revert. + function testZoltuNoZoltu(bytes memory data) external { + vm.assume(ZOLTU_PROXY_ADDRESS.code.length == 0); + vm.expectRevert(abi.encodeWithSelector(WriteError.selector)); + this.writeZoltuExternal(data); + } + + /// Check that if zoltu exists but returns not success we revert. + function testZoltuBadZoltu(bytes memory data) external { + vm.assume(ZOLTU_PROXY_ADDRESS.code.length == 0); + vm.etch( + ZOLTU_PROXY_ADDRESS, + // revert opcode. + hex"fd" + ); + vm.expectRevert(abi.encodeWithSelector(WriteError.selector)); + this.writeZoltuExternal(data); + } }