From 151bbe0583dd98d03efe17996e083a978d839aa5 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Fri, 23 Jan 2026 21:04:29 +0400 Subject: [PATCH 1/6] update deps and add check bytecode hash --- flake.lock | 59 ++++++++++++++++++++----------- foundry.lock | 4 +-- lib/forge-std | 2 +- lib/rain.solmem | 2 +- src/lib/LibExtrospectBytecode.sol | 25 +++++++++++++ 5 files changed, 67 insertions(+), 25 deletions(-) diff --git a/flake.lock b/flake.lock index 894a168..6c8f51a 100644 --- a/flake.lock +++ b/flake.lock @@ -75,11 +75,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1758705030, - "narHash": "sha256-zYM8PiEXANNrtjfyGUc7w37/D/kCynp0cQS+wCQ77GI=", + "lastModified": 1768892915, + "narHash": "sha256-2+KHmLLjUg9vNzINfGaiFNP07gKz94Af09FcefKHUuE=", "owner": "shazow", "repo": "foundry.nix", - "rev": "b59a55014050110170023e3e1c277c1d4a2f055b", + "rev": "edf14357ad1816ac39469ae493f898200352d77d", "type": "github" }, "original": { @@ -102,13 +102,29 @@ "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": 1758711836, - "narHash": "sha256-uBqPg7wNX2v6YUdTswH7wWU8wqb60cFZx0tHaWTGF30=", + "lastModified": 1768987938, + "narHash": "sha256-pvqBRTCRvwFM0Nm7aCQfqS0whDZ7WwoXUUTwvOXqus4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "46f97b78e825ae762c0224e3983c47687436a498", + "rev": "95d8e2129409fd37b390c966b434eee3034f75e3", "type": "github" }, "original": { @@ -135,11 +151,11 @@ }, "nixpkgs_4": { "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": { @@ -154,15 +170,16 @@ "flake-utils": "flake-utils_2", "foundry": "foundry", "nixpkgs": "nixpkgs_2", + "nixpkgs-old": "nixpkgs-old", "rust-overlay": "rust-overlay", "solc": "solc" }, "locked": { - "lastModified": 1760460761, - "narHash": "sha256-IHvwnmphDaOyZnzvObwOoDQlA9nzym2ZUxe9K/5vs0U=", + "lastModified": 1768991906, + "narHash": "sha256-W8AgvEKkz4BWTHDK3AGwk/vvVFtIF7UtHuZI8Ecv2qI=", "owner": "rainprotocol", "repo": "rainix", - "rev": "add0d8a1fd76ce0e65b962c952e9252257876465", + "rev": "5a9f13de1318b512ef118db6d0e7f9dbafe5205f", "type": "github" }, "original": { @@ -182,11 +199,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1758681214, - "narHash": "sha256-8cW731vev6kfr58cILO2ZsjHwaPhm88dQ8Q6nTSjP9I=", + "lastModified": 1768963622, + "narHash": "sha256-n6VHiUgrYD9yjagzG6ncVVqFbVTsKCI54tR9PNAFCo0=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "b12ed88d8d33d4f3cbc842bf29fad93bb1437299", + "rev": "2ef5b3362af585a83bafd34e7fc9b1f388c2e5e2", "type": "github" }, "original": { @@ -202,11 +219,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": { @@ -218,13 +235,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" } }, "systems": { diff --git a/foundry.lock b/foundry.lock index da484af..460d81d 100644 --- a/foundry.lock +++ b/foundry.lock @@ -1,8 +1,8 @@ { "lib/forge-std": { - "rev": "b8f065fda83b8cd94a6b2fec8fcd911dc3b444fd" + "rev": "1801b0541f4fda118a10798fd3486bb7051c5dd6" }, "lib/rain.solmem": { - "rev": "228b35c6725877e7fbcd2432b4c692357f16f510" + "rev": "26bce6197383f193e35326bab4d4424cf6eafde7" } } \ 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 228b35c..26bce61 160000 --- a/lib/rain.solmem +++ b/lib/rain.solmem @@ -1 +1 @@ -Subproject commit 228b35c6725877e7fbcd2432b4c692357f16f510 +Subproject commit 26bce6197383f193e35326bab4d4424cf6eafde7 diff --git a/src/lib/LibExtrospectBytecode.sol b/src/lib/LibExtrospectBytecode.sol index 8980239..501c325 100644 --- a/src/lib/LibExtrospectBytecode.sol +++ b/src/lib/LibExtrospectBytecode.sol @@ -12,6 +12,14 @@ import {EVM_OP_JUMPDEST, HALTING_BITMAP} from "./EVMOpcodes.sol"; library LibExtrospectBytecode { using LibBytes for bytes; + /// Thrown when bytecode metadata is not trimmed as expected. + error MetadataNotTrimmed(); + + /// Thrown when the bytecode hash does not match the expected value. + /// @param expected The expected bytecode hash. + /// @param actual The actual bytecode hash. + error BytecodeHashMismatch(bytes32 expected, bytes32 actual); + /// https://docs.soliditylang.org/en/latest/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode /// /// The encoding is not super complex, but requires having a CBOR decoder to @@ -59,6 +67,23 @@ library LibExtrospectBytecode { } } + /// Checks that the bytecode of an account, after trimming Solidity CBOR + /// metadata, matches an expected hash. Reverts if the metadata was not + /// trimmed or if the hash does not match after trimming. + /// @param account The account whose bytecode to check. + /// @param expected The expected hash of the trimmed bytecode. + function checkCBORTrimmedBytecodeHash(address account, bytes32 expected) internal view { + bytes memory bytecode = account.code; + bool didTrim = LibExtrospectBytecode.trimSolidityCBORMetadata(bytecode); + if (!didTrim) { + revert MetadataNotTrimmed(); + } + bytes32 actual = keccak256(bytecode); + if (expected != actual) { + revert BytecodeHashMismatch(expected, actual); + } + } + /// Scans for opcodes that are reachable during execution of a contract. /// Adapted from https://github.com/MrLuit/selfdestruct-detect/blob/master/src/index.ts /// @param bytecode The bytecode to scan. From cff6ef2257f35c47b331e4f8d53411b9f9dfe541 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Fri, 23 Jan 2026 21:36:26 +0400 Subject: [PATCH 2/6] fix tests --- .gitignore | 3 +- src/interface/IExtrospectInterpreterV1.sol | 41 ++++++++++--------- src/lib/EVMOpcodes.sol | 4 +- src/lib/LibExtrospectERC1167Proxy.sol | 6 ++- test/lib/LibExtrospectTestProd.sol | 13 ++++++ ...ytecode.checkCBORTrimmedBytecodeHash.t.sol | 21 ++++++++++ ...code.scanEVMOpcodesPresentInBytecode.t.sol | 2 +- ...de.scanEVMOpcodesReachableInBytecode.t.sol | 30 +++++++------- ...ectBytecode.trimSolidityCBORMetadata.t.sol | 2 +- 9 files changed, 80 insertions(+), 42 deletions(-) create mode 100644 test/lib/LibExtrospectTestProd.sol create mode 100644 test/src/lib/LibExtrospectBytecode.checkCBORTrimmedBytecodeHash.t.sol diff --git a/.gitignore b/.gitignore index bf3d16b..aaa3300 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ out -cache \ No newline at end of file +cache +.env \ No newline at end of file diff --git a/src/interface/IExtrospectInterpreterV1.sol b/src/interface/IExtrospectInterpreterV1.sol index 1ca4acb..c4160bd 100644 --- a/src/interface/IExtrospectInterpreterV1.sol +++ b/src/interface/IExtrospectInterpreterV1.sol @@ -21,29 +21,30 @@ import { /// @dev https://eips.ethereum.org/EIPS/eip-214#specification //forge-lint: disable-next-line(incorrect-shift) uint256 constant NON_STATIC_OPS = (1 << uint256(EVM_OP_CREATE)) | (1 << uint256(EVM_OP_CREATE2)) -//forge-lint: disable-next-line(incorrect-shift) -| (1 << uint256(EVM_OP_LOG0)) | (1 << uint256(EVM_OP_LOG1)) | (1 << uint256(EVM_OP_LOG2)) | (1 << uint256(EVM_OP_LOG3)) -//forge-lint: disable-next-line(incorrect-shift) -| (1 << uint256(EVM_OP_LOG4)) | (1 << uint256(EVM_OP_SSTORE)) | (1 << uint256(EVM_OP_SELFDESTRUCT)) -//forge-lint: disable-next-line(incorrect-shift) -| (1 << uint256(EVM_OP_CALL)); + //forge-lint: disable-next-line(incorrect-shift) + | (1 << uint256(EVM_OP_LOG0)) | (1 << uint256(EVM_OP_LOG1)) | (1 << uint256(EVM_OP_LOG2)) + | (1 << uint256(EVM_OP_LOG3)) + //forge-lint: disable-next-line(incorrect-shift) + | (1 << uint256(EVM_OP_LOG4)) | (1 << uint256(EVM_OP_SSTORE)) | (1 << uint256(EVM_OP_SELFDESTRUCT)) + //forge-lint: disable-next-line(incorrect-shift) + | (1 << uint256(EVM_OP_CALL)); /// @dev The interpreter ops allowlist is stricter than the static ops list. uint256 constant INTERPRETER_DISALLOWED_OPS = NON_STATIC_OPS -// Interpreter cannot store so it has no reason to load from storage. -//forge-lint: disable-next-line(incorrect-shift) -| (1 << uint256(EVM_OP_SLOAD)) -// Interpreter MUST NOT delegate call as we have no idea what could run and -// it could easily mutate the interpreter if allowed. -//forge-lint: disable-next-line(incorrect-shift) -| (1 << uint256(EVM_OP_DELEGATECALL)) -// Interpreter MUST use static call only. -//forge-lint: disable-next-line(incorrect-shift) -| (1 << uint256(EVM_OP_CALLCODE)) -// Interpreter MUST use static call only. -// Redundant with static list for clarity as static list allows 0 value calls. -//forge-lint: disable-next-line(incorrect-shift) -| (1 << uint256(EVM_OP_CALL)); + // Interpreter cannot store so it has no reason to load from storage. + //forge-lint: disable-next-line(incorrect-shift) + | (1 << uint256(EVM_OP_SLOAD)) + // Interpreter MUST NOT delegate call as we have no idea what could run and + // it could easily mutate the interpreter if allowed. + //forge-lint: disable-next-line(incorrect-shift) + | (1 << uint256(EVM_OP_DELEGATECALL)) + // Interpreter MUST use static call only. + //forge-lint: disable-next-line(incorrect-shift) + | (1 << uint256(EVM_OP_CALLCODE)) + // Interpreter MUST use static call only. + // Redundant with static list for clarity as static list allows 0 value calls. + //forge-lint: disable-next-line(incorrect-shift) + | (1 << uint256(EVM_OP_CALL)); /// @title IExtrospectInterpreterV1 /// @notice External functions for offchain processing to determine if an diff --git a/src/lib/EVMOpcodes.sol b/src/lib/EVMOpcodes.sol index 5a3577f..5f27f29 100644 --- a/src/lib/EVMOpcodes.sol +++ b/src/lib/EVMOpcodes.sol @@ -171,5 +171,5 @@ uint8 constant EVM_OP_SELFDESTRUCT = 0xFF; //forge-lint: disable-next-line(incorrect-shift) uint256 constant HALTING_BITMAP = (1 << EVM_OP_STOP) | (1 << EVM_OP_RETURN) | (1 << EVM_OP_REVERT) -//forge-lint: disable-next-line(incorrect-shift) -| (1 << EVM_OP_INVALID) | (1 << EVM_OP_SELFDESTRUCT); + //forge-lint: disable-next-line(incorrect-shift) + | (1 << EVM_OP_INVALID) | (1 << EVM_OP_SELFDESTRUCT); diff --git a/src/lib/LibExtrospectERC1167Proxy.sol b/src/lib/LibExtrospectERC1167Proxy.sol index 0ee8d24..cd7794b 100644 --- a/src/lib/LibExtrospectERC1167Proxy.sol +++ b/src/lib/LibExtrospectERC1167Proxy.sol @@ -81,8 +81,10 @@ library LibExtrospectERC1167Proxy { uint256 implementationAddressOffset = ERC1167_IMPLEMENTATION_ADDRESS_OFFSET; uint256 implementationAddressMask = type(uint160).max; assembly ("memory-safe") { - implementationAddress := - and(mload(add(bytecode, implementationAddressOffset)), implementationAddressMask) + implementationAddress := and( + mload(add(bytecode, implementationAddressOffset)), + implementationAddressMask + ) } } } diff --git a/test/lib/LibExtrospectTestProd.sol b/test/lib/LibExtrospectTestProd.sol new file mode 100644 index 0000000..c525b02 --- /dev/null +++ b/test/lib/LibExtrospectTestProd.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity ^0.8.25; + +import {Vm} from "forge-std/StdCheats.sol"; + +library LibExtrospectTestProd { + uint256 constant PROD_TEST_BLOCK_NUMBER_ARBITRUM = 424447965; + + function createSelectForkArbitrum(Vm vm) internal { + vm.createSelectFork(vm.envString("RPC_URL_ARBITRUM_FORK"), PROD_TEST_BLOCK_NUMBER_ARBITRUM); + } +} diff --git a/test/src/lib/LibExtrospectBytecode.checkCBORTrimmedBytecodeHash.t.sol b/test/src/lib/LibExtrospectBytecode.checkCBORTrimmedBytecodeHash.t.sol new file mode 100644 index 0000000..7e7cfe4 --- /dev/null +++ b/test/src/lib/LibExtrospectBytecode.checkCBORTrimmedBytecodeHash.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {Test} from "forge-std/Test.sol"; +import {LibExtrospectTestProd} from "test/lib/LibExtrospectTestProd.sol"; +import {LibExtrospectBytecode} from "src/lib/LibExtrospectBytecode.sol"; + +contract LibExtrospectBytecodeCheckCBORTrimmedBytecodeHashTest is Test { + address constant PROD_ARBITRUM_CLONE_FACTORY_ADDRESS_V1 = address(0xe01Db32B1E03976b24e3A948A560f4b97Dd732dA); + bytes32 constant PROD_ARBITRUM_CLONE_FACTORY_CODEHASH_V1 = + bytes32(0x7b085ca3e5c659da29caf26d23e7b72fd4fdbc59aa6b5611cf3918c4586ec73a); + + function testCheckCBORTrimmedBytecodeHashSuccess() public { + LibExtrospectTestProd.createSelectForkArbitrum(vm); + + LibExtrospectBytecode.checkCBORTrimmedBytecodeHash( + PROD_ARBITRUM_CLONE_FACTORY_ADDRESS_V1, PROD_ARBITRUM_CLONE_FACTORY_CODEHASH_V1 + ); + } +} diff --git a/test/src/lib/LibExtrospectBytecode.scanEVMOpcodesPresentInBytecode.t.sol b/test/src/lib/LibExtrospectBytecode.scanEVMOpcodesPresentInBytecode.t.sol index aa04e18..9459769 100644 --- a/test/src/lib/LibExtrospectBytecode.scanEVMOpcodesPresentInBytecode.t.sol +++ b/test/src/lib/LibExtrospectBytecode.scanEVMOpcodesPresentInBytecode.t.sol @@ -7,7 +7,7 @@ import {Test} from "forge-std/Test.sol"; import {LibBytes, LibExtrospectBytecode} from "src/lib/LibExtrospectBytecode.sol"; import {LibExtrospectionSlow} from "test/lib/LibExtrospectionSlow.sol"; -contract LibExtrospectScanEVMOpcodesPresentInBytecodeTest is Test { +contract LibExtrospectBytecodeScanEVMOpcodesPresentInBytecodeTest is Test { using LibBytes for bytes; function testScanEVMOpcodesPresentSimple() public pure { diff --git a/test/src/lib/LibExtrospectBytecode.scanEVMOpcodesReachableInBytecode.t.sol b/test/src/lib/LibExtrospectBytecode.scanEVMOpcodesReachableInBytecode.t.sol index f383550..cc61a39 100644 --- a/test/src/lib/LibExtrospectBytecode.scanEVMOpcodesReachableInBytecode.t.sol +++ b/test/src/lib/LibExtrospectBytecode.scanEVMOpcodesReachableInBytecode.t.sol @@ -71,21 +71,21 @@ contract LibExtrospectScanEVMOpcodesReachableInBytecodeTest is Test { // 0x14 //forge-lint: disable-next-line(incorrect-shift) (1 << EVM_OP_EQ) - // 0xfd - //forge-lint: disable-next-line(incorrect-shift) - | (1 << EVM_OP_REVERT) - // 0x5b - //forge-lint: disable-next-line(incorrect-shift) - | (1 << EVM_OP_JUMPDEST) - // 0x09 - //forge-lint: disable-next-line(incorrect-shift) - | (1 << EVM_OP_MULMOD) - // 0x0a - //forge-lint: disable-next-line(incorrect-shift) - | (1 << EVM_OP_EXP) - // 0x0b - //forge-lint: disable-next-line(incorrect-shift) - | (1 << EVM_OP_SIGNEXTEND) + // 0xfd + //forge-lint: disable-next-line(incorrect-shift) + | (1 << EVM_OP_REVERT) + // 0x5b + //forge-lint: disable-next-line(incorrect-shift) + | (1 << EVM_OP_JUMPDEST) + // 0x09 + //forge-lint: disable-next-line(incorrect-shift) + | (1 << EVM_OP_MULMOD) + // 0x0a + //forge-lint: disable-next-line(incorrect-shift) + | (1 << EVM_OP_EXP) + // 0x0b + //forge-lint: disable-next-line(incorrect-shift) + | (1 << EVM_OP_SIGNEXTEND) ); } diff --git a/test/src/lib/LibExtrospectBytecode.trimSolidityCBORMetadata.t.sol b/test/src/lib/LibExtrospectBytecode.trimSolidityCBORMetadata.t.sol index 54cc039..1a897d7 100644 --- a/test/src/lib/LibExtrospectBytecode.trimSolidityCBORMetadata.t.sol +++ b/test/src/lib/LibExtrospectBytecode.trimSolidityCBORMetadata.t.sol @@ -42,7 +42,7 @@ contract LibExtrospectBytecodeTrimSolidityCBORMetadataTest is Test { } bytes32 before = keccak256(bytecode); - assertEq(LibExtrospectBytecode.trimSolidityCBORMetadata(bytecode), false); + vm.assume(!LibExtrospectBytecode.trimSolidityCBORMetadata(bytecode)); assertEq(keccak256(bytecode), before); // Now add the metadata. From 29023f3a4d32104982fbb4b09b9fd6f06688ef74 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Fri, 23 Jan 2026 21:38:46 +0400 Subject: [PATCH 3/6] fix tests --- .gas-snapshot | 33 ++++++++++--------- .../concrete/Extrospection.ERC1167Proxy.t.sol | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 24fe983..30e0630 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,9 +1,9 @@ -ExtrospectionBytecodeTest:testBytecode(address) (runs: 2048, μ: 308793, ~: 308847) -ExtrospectionBytecodeTest:testBytecodeHash(address) (runs: 2048, μ: 303472, ~: 303531) -ExtrospectionBytecodeTest:testScanEVMOpcodesPresentInAccount(address) (runs: 2048, μ: 303697, ~: 303224) -ExtrospectionBytecodeTest:testScanEVMOpcodesReachableInAccount(address) (runs: 2048, μ: 303952, ~: 303265) -ExtrospectionERC1167ProxyTest:testExtrospectionERC1167ProxyFailure(address,bytes) (runs: 2014, μ: 305214, ~: 305199) -ExtrospectionERC1167ProxyTest:testExtrospectionERC1167ProxySuccess(address,address) (runs: 2014, μ: 305264, ~: 305264) +ExtrospectionBytecodeTest:testBytecode(address) (runs: 2048, μ: 308822, ~: 308847) +ExtrospectionBytecodeTest:testBytecodeHash(address) (runs: 2048, μ: 303501, ~: 303531) +ExtrospectionBytecodeTest:testScanEVMOpcodesPresentInAccount(address) (runs: 2048, μ: 303726, ~: 303224) +ExtrospectionBytecodeTest:testScanEVMOpcodesReachableInAccount(address) (runs: 2048, μ: 303981, ~: 303265) +ExtrospectionERC1167ProxyTest:testExtrospectionERC1167ProxyFailure(address,bytes) (runs: 2048, μ: 305417, ~: 305332) +ExtrospectionERC1167ProxyTest:testExtrospectionERC1167ProxySuccess(address,address) (runs: 2048, μ: 305264, ~: 305264) ExtrospectionInterpreterTest:testCallCodeooor() (gas: 353111) ExtrospectionInterpreterTest:testCallooor() (gas: 353110) ExtrospectionInterpreterTest:testCreate2ooor() (gas: 352015) @@ -18,27 +18,28 @@ ExtrospectionInterpreterTest:testNoopooor() (gas: 335193) ExtrospectionInterpreterTest:testSLoadooor() (gas: 353360) ExtrospectionInterpreterTest:testSStoreooor() (gas: 349547) ExtrospectionInterpreterTest:testSelfDestructooor() (gas: 348804) -LibExtrospectBytecodeTrimSolidityCBORMetadataTest:testTrimSolidityCBORMetadataBytecodeContrived(bytes) (runs: 2048, μ: 6406, ~: 6324) -LibExtrospectBytecodeTrimSolidityCBORMetadataTest:testTrimSolidityCBORMetadataBytecodeShort(bytes) (runs: 1468, μ: 3555, ~: 3555) +LibExtrospectBytecodeCheckCBORTrimmedBytecodeHashTest:testCheckCBORTrimmedBytecodeHashSuccess() (gas: 7562) +LibExtrospectBytecodeScanEVMOpcodesPresentInBytecodeTest:testScanEVMOpcodesPresentPush1() (gas: 715) +LibExtrospectBytecodeScanEVMOpcodesPresentInBytecodeTest:testScanEVMOpcodesPresentReference(bytes) (runs: 2048, μ: 34088, ~: 10497) +LibExtrospectBytecodeScanEVMOpcodesPresentInBytecodeTest:testScanEVMOpcodesPresentSimple() (gas: 879) +LibExtrospectBytecodeTrimSolidityCBORMetadataTest:testTrimSolidityCBORMetadataBytecodeContrived(bytes) (runs: 2048, μ: 6802, ~: 6603) +LibExtrospectBytecodeTrimSolidityCBORMetadataTest:testTrimSolidityCBORMetadataBytecodeShort(bytes) (runs: 2048, μ: 3555, ~: 3555) LibExtrospectBytecodeTrimSolidityCBORMetadataTest:testTrimSolidityCBORMetdataBytecodeReal() (gas: 3963) LibExtrospectERC1167ProxyTest:testIsERC1167ProxyGasFailLength() (gas: 365) LibExtrospectERC1167ProxyTest:testIsERC1167ProxyGasFailPrefix() (gas: 684) LibExtrospectERC1167ProxyTest:testIsERC1167ProxyGasFailSuffix() (gas: 663) LibExtrospectERC1167ProxyTest:testIsERC1167ProxyGasSuccess() (gas: 662) -LibExtrospectERC1167ProxyTest:testIsERC1167ProxyLength(bytes) (runs: 2037, μ: 3901, ~: 3898) -LibExtrospectERC1167ProxyTest:testIsERC1167ProxyPrefixFail(bytes,address) (runs: 2048, μ: 4292, ~: 4282) -LibExtrospectERC1167ProxyTest:testIsERC1167ProxySlowFail(bytes) (runs: 2048, μ: 3883, ~: 3847) +LibExtrospectERC1167ProxyTest:testIsERC1167ProxyLength(bytes) (runs: 2048, μ: 3920, ~: 3898) +LibExtrospectERC1167ProxyTest:testIsERC1167ProxyPrefixFail(bytes,address) (runs: 2048, μ: 4381, ~: 4282) +LibExtrospectERC1167ProxyTest:testIsERC1167ProxySlowFail(bytes) (runs: 2048, μ: 3952, ~: 3847) LibExtrospectERC1167ProxyTest:testIsERC1167ProxySlowSuccess(address) (runs: 2048, μ: 12367, ~: 12367) LibExtrospectERC1167ProxyTest:testIsERC1167ProxySuccess(address) (runs: 2048, μ: 1332, ~: 1332) -LibExtrospectERC1167ProxyTest:testIsERC1167ProxySuffixFail(bytes,address) (runs: 2048, μ: 4311, ~: 4299) -LibExtrospectScanEVMOpcodesPresentInBytecodeTest:testScanEVMOpcodesPresentPush1() (gas: 715) -LibExtrospectScanEVMOpcodesPresentInBytecodeTest:testScanEVMOpcodesPresentReference(bytes) (runs: 2048, μ: 10334, ~: 12897) -LibExtrospectScanEVMOpcodesPresentInBytecodeTest:testScanEVMOpcodesPresentSimple() (gas: 879) +LibExtrospectERC1167ProxyTest:testIsERC1167ProxySuffixFail(bytes,address) (runs: 2048, μ: 4396, ~: 4299) LibExtrospectScanEVMOpcodesReachableInBytecodeTest:testScanEVMOpcodesReachableInvalid() (gas: 1142) LibExtrospectScanEVMOpcodesReachableInBytecodeTest:testScanEVMOpcodesReachableJumpdest() (gas: 1954) LibExtrospectScanEVMOpcodesReachableInBytecodeTest:testScanEVMOpcodesReachablePush1() (gas: 789) LibExtrospectScanEVMOpcodesReachableInBytecodeTest:testScanEVMOpcodesReachablePush4() (gas: 1024) -LibExtrospectScanEVMOpcodesReachableInBytecodeTest:testScanEVMOpcodesReachableReference(bytes) (runs: 2048, μ: 11882, ~: 12259) +LibExtrospectScanEVMOpcodesReachableInBytecodeTest:testScanEVMOpcodesReachableReference(bytes) (runs: 2048, μ: 41242, ~: 12259) LibExtrospectScanEVMOpcodesReachableInBytecodeTest:testScanEVMOpcodesReachableReportedFalsePositive() (gas: 1713108) LibExtrospectScanEVMOpcodesReachableInBytecodeTest:testScanEVMOpcodesReachableReportedFalsePositiveBytecode() (gas: 1664573) LibExtrospectScanEVMOpcodesReachableInBytecodeTest:testScanEVMOpcodesReachableReturn() (gas: 1122) diff --git a/test/src/concrete/Extrospection.ERC1167Proxy.t.sol b/test/src/concrete/Extrospection.ERC1167Proxy.t.sol index d45bd33..89d8bec 100644 --- a/test/src/concrete/Extrospection.ERC1167Proxy.t.sol +++ b/test/src/concrete/Extrospection.ERC1167Proxy.t.sol @@ -26,7 +26,7 @@ contract ExtrospectionERC1167ProxyTest is Test { // Proxy can't be extrospection, otherwise we'll etch over it. vm.assume(proxy != address(extrospection)); // Proxy can't be a precompile either. - vm.assume(uint160(proxy) > 10); + vm.assume(uint160(proxy) > type(uint160).max / 2); // Force incorrect proxy implementation into the proxy address. vm.etch(proxy, bytecode); From 435b3f28f1c232864f24b6803406f9c73f62d86d Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 25 Jan 2026 22:40:41 +0400 Subject: [PATCH 4/6] wip on fixing test --- flake.lock | 24 ++++++------- src/lib/LibExtrospectBytecode.sol | 2 ++ test/lib/LibExtrospectTestProd.sol | 2 +- .../concrete/Extrospection.ERC1167Proxy.t.sol | 8 ++++- ...ytecode.checkCBORTrimmedBytecodeHash.t.sol | 35 ++++++++++++++++++- 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/flake.lock b/flake.lock index 6c8f51a..9a12e0c 100644 --- a/flake.lock +++ b/flake.lock @@ -75,11 +75,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1768892915, - "narHash": "sha256-2+KHmLLjUg9vNzINfGaiFNP07gKz94Af09FcefKHUuE=", + "lastModified": 1769324704, + "narHash": "sha256-aef15vEgiMEls1hTMt46rJuKNSO2cIOfiP99patq9yc=", "owner": "shazow", "repo": "foundry.nix", - "rev": "edf14357ad1816ac39469ae493f898200352d77d", + "rev": "e830409ba1bdecdc5ef9a1ec92660fc2da9bc68d", "type": "github" }, "original": { @@ -120,11 +120,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1768987938, - "narHash": "sha256-pvqBRTCRvwFM0Nm7aCQfqS0whDZ7WwoXUUTwvOXqus4=", + "lastModified": 1769364508, + "narHash": "sha256-Wy8EVYSLq5Fb/rYH3LRxAMCnW75f9hOg2562AXVFmPk=", "owner": "nixos", "repo": "nixpkgs", - "rev": "95d8e2129409fd37b390c966b434eee3034f75e3", + "rev": "6077bc4fb29be43d525984f63b69d37b9b1e62fe", "type": "github" }, "original": { @@ -175,11 +175,11 @@ "solc": "solc" }, "locked": { - "lastModified": 1768991906, - "narHash": "sha256-W8AgvEKkz4BWTHDK3AGwk/vvVFtIF7UtHuZI8Ecv2qI=", + "lastModified": 1769366341, + "narHash": "sha256-jeYOweTuJdKshW9lqVoNxvl4+flyRzWxEctRGabTW/8=", "owner": "rainprotocol", "repo": "rainix", - "rev": "5a9f13de1318b512ef118db6d0e7f9dbafe5205f", + "rev": "e7bfe9c39d2de818eac241f88ecabc69e86ed734", "type": "github" }, "original": { @@ -199,11 +199,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1768963622, - "narHash": "sha256-n6VHiUgrYD9yjagzG6ncVVqFbVTsKCI54tR9PNAFCo0=", + "lastModified": 1769309768, + "narHash": "sha256-AbOIlNO+JoqRJkK1VrnDXhxuX6CrdtIu2hSuy4pxi3g=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "2ef5b3362af585a83bafd34e7fc9b1f388c2e5e2", + "rev": "140c9dc582cb73ada2d63a2180524fcaa744fad5", "type": "github" }, "original": { diff --git a/src/lib/LibExtrospectBytecode.sol b/src/lib/LibExtrospectBytecode.sol index 501c325..60e7695 100644 --- a/src/lib/LibExtrospectBytecode.sol +++ b/src/lib/LibExtrospectBytecode.sol @@ -45,6 +45,8 @@ library LibExtrospectBytecode { /// parts still have constant length. /// /// NOTE bytecode is mutated in place. + /// @param bytecode The bytecode to trim metadata from. + /// @return didTrim Whether metadata was detected and trimmed. //forge-lint: disable-next-line(mixed-case-function) function trimSolidityCBORMetadata(bytes memory bytecode) internal pure returns (bool didTrim) { uint256 length = bytecode.length; diff --git a/test/lib/LibExtrospectTestProd.sol b/test/lib/LibExtrospectTestProd.sol index c525b02..0de79cf 100644 --- a/test/lib/LibExtrospectTestProd.sol +++ b/test/lib/LibExtrospectTestProd.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.25; import {Vm} from "forge-std/StdCheats.sol"; library LibExtrospectTestProd { - uint256 constant PROD_TEST_BLOCK_NUMBER_ARBITRUM = 424447965; + uint256 constant PROD_TEST_BLOCK_NUMBER_ARBITRUM = 424463066; function createSelectForkArbitrum(Vm vm) internal { vm.createSelectFork(vm.envString("RPC_URL_ARBITRUM_FORK"), PROD_TEST_BLOCK_NUMBER_ARBITRUM); diff --git a/test/src/concrete/Extrospection.ERC1167Proxy.t.sol b/test/src/concrete/Extrospection.ERC1167Proxy.t.sol index 89d8bec..4be7178 100644 --- a/test/src/concrete/Extrospection.ERC1167Proxy.t.sol +++ b/test/src/concrete/Extrospection.ERC1167Proxy.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std/Test.sol"; +import {Test, console2} from "forge-std/Test.sol"; import {LibExtrospectERC1167Proxy, ERC1167_PREFIX, ERC1167_SUFFIX} from "src/lib/LibExtrospectERC1167Proxy.sol"; @@ -27,7 +27,13 @@ contract ExtrospectionERC1167ProxyTest is Test { vm.assume(proxy != address(extrospection)); // Proxy can't be a precompile either. vm.assume(uint160(proxy) > type(uint160).max / 2); + vm.assume(proxy.code.length == 0); // Force incorrect proxy implementation into the proxy address. + assembly ("memory-safe") { + mstore(bytecode, 23) + } + + console2.logBytes(bytecode); vm.etch(proxy, bytecode); (bool result, address implementation) = extrospection.isERC1167Proxy(proxy); diff --git a/test/src/lib/LibExtrospectBytecode.checkCBORTrimmedBytecodeHash.t.sol b/test/src/lib/LibExtrospectBytecode.checkCBORTrimmedBytecodeHash.t.sol index 7e7cfe4..24fc95f 100644 --- a/test/src/lib/LibExtrospectBytecode.checkCBORTrimmedBytecodeHash.t.sol +++ b/test/src/lib/LibExtrospectBytecode.checkCBORTrimmedBytecodeHash.t.sol @@ -11,11 +11,44 @@ contract LibExtrospectBytecodeCheckCBORTrimmedBytecodeHashTest is Test { bytes32 constant PROD_ARBITRUM_CLONE_FACTORY_CODEHASH_V1 = bytes32(0x7b085ca3e5c659da29caf26d23e7b72fd4fdbc59aa6b5611cf3918c4586ec73a); - function testCheckCBORTrimmedBytecodeHashSuccess() public { + function externalCheckCBORTrimmedBytecodeHash(address target, bytes32 expectedCodeHash) external view { + LibExtrospectBytecode.checkCBORTrimmedBytecodeHash(target, expectedCodeHash); + } + + function testCheckCBORTrimmedBytecodeHashSuccess() external { LibExtrospectTestProd.createSelectForkArbitrum(vm); LibExtrospectBytecode.checkCBORTrimmedBytecodeHash( PROD_ARBITRUM_CLONE_FACTORY_ADDRESS_V1, PROD_ARBITRUM_CLONE_FACTORY_CODEHASH_V1 ); } + + function testCheckCBORTrimmedBytecodeHashFailure() external { + bytes32 expectedCodeHash = bytes32(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF); + LibExtrospectTestProd.createSelectForkArbitrum(vm); + + bytes32 actualCodeHash = PROD_ARBITRUM_CLONE_FACTORY_CODEHASH_V1; + + vm.expectRevert( + abi.encodeWithSelector( + LibExtrospectBytecode.BytecodeHashMismatch.selector, expectedCodeHash, actualCodeHash + ) + ); + this.externalCheckCBORTrimmedBytecodeHash(PROD_ARBITRUM_CLONE_FACTORY_ADDRESS_V1, expectedCodeHash); + } + + function testCheckCBORTrimmedBytecodeHashMetadataNotTrimmed() external { + bytes32 expectedCodeHash = bytes32(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF); + LibExtrospectTestProd.createSelectForkArbitrum(vm); + + // Use an account that does not have Solidity CBOR metadata and is + // therefore not trimmed. + // This is a deployed rain interpreter contract. + address accountWithoutMetadata = address(0x1Bd4F25881B5A82302Edc07FCa994faa21baec7F); + + // The code hash does not matter because the error for trimming happens + // before the hash is checked. + vm.expectRevert(abi.encodeWithSelector(LibExtrospectBytecode.MetadataNotTrimmed.selector)); + this.externalCheckCBORTrimmedBytecodeHash(accountWithoutMetadata, expectedCodeHash); + } } From d7abb8c64a395c416a8563aa95c52a70201b6883 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 25 Jan 2026 22:54:23 +0400 Subject: [PATCH 5/6] rpc url fork --- .github/workflows/rainix.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/rainix.yaml b/.github/workflows/rainix.yaml index ec7980c..773afec 100644 --- a/.github/workflows/rainix.yaml +++ b/.github/workflows/rainix.yaml @@ -28,4 +28,5 @@ jobs: ETH_RPC_URL: ${{ secrets.CI_DEPLOY_RPC_URL }} ETHERSCAN_API_KEY: ${{ secrets.EXPLORER_VERIFICATION_KEY }} DEPLOY_VERIFIER: 'etherscan' + RPC_URL_ARBITRUM_FORK: ${{ secrets.CI_DEPLOY_ARBITRUM_RPC_URL }} run: nix develop -c ${{ matrix.task }} From 79cd18919da7a5723dabb7c081ce266194ebf270 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 25 Jan 2026 22:58:53 +0400 Subject: [PATCH 6/6] lint --- test/src/concrete/Extrospection.ERC1167Proxy.t.sol | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/src/concrete/Extrospection.ERC1167Proxy.t.sol b/test/src/concrete/Extrospection.ERC1167Proxy.t.sol index 4be7178..e4653ec 100644 --- a/test/src/concrete/Extrospection.ERC1167Proxy.t.sol +++ b/test/src/concrete/Extrospection.ERC1167Proxy.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test, console2} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {LibExtrospectERC1167Proxy, ERC1167_PREFIX, ERC1167_SUFFIX} from "src/lib/LibExtrospectERC1167Proxy.sol"; @@ -28,12 +28,7 @@ contract ExtrospectionERC1167ProxyTest is Test { // Proxy can't be a precompile either. vm.assume(uint160(proxy) > type(uint160).max / 2); vm.assume(proxy.code.length == 0); - // Force incorrect proxy implementation into the proxy address. - assembly ("memory-safe") { - mstore(bytecode, 23) - } - console2.logBytes(bytecode); vm.etch(proxy, bytecode); (bool result, address implementation) = extrospection.isERC1167Proxy(proxy);