From 4fb3d1381d41982cda9656ccde806ebb0cd3b331 Mon Sep 17 00:00:00 2001 From: David Meister Date: Sat, 25 Jul 2026 19:45:52 +0000 Subject: [PATCH 1/2] test(deploy): close the coverage gaps a mutation pass found Probe every behaviour of LibRainDeploy by breaking one line at a time and running the whole suite. Add tests only for the mutants that survived, and tighten two existing tests whose names claimed coverage their assertions did not deliver. New behaviours pinned: - isStartBlock at block 0 and block 1, using a Base genesis allocation target - isStartBlock compares against the immediately preceding block - findDeployBlock returns the exact deploy block, including when it is the block the fork is at, and restores the fork when it reverts - deployZoltu rejects a codeless result, a failed call whose output buffer holds a live address, the zero address, and never forwards caller value - deployToNetworks accepts a dependency that has code - deployAndBroadcast broadcasts as the address derived from the private key testZoltuFactoryCodehash now covers every supported network rather than Arbitrum alone, so base_sepolia, flare and polygon are exercised at all. testDeployToNetworksMultipleNetworks now asserts the chain left selected and the deployed code hash, so a truncated loop cannot pass. Co-Authored-By: Claude Opus 4.8 --- audit/mutation-test-scans.json | 22 +++ test/src/lib/LibRainDeploy.t.sol | 194 ++++++++++++++++++- test/src/lib/MockAddressRevertingFactory.sol | 18 ++ 3 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 audit/mutation-test-scans.json create mode 100644 test/src/lib/MockAddressRevertingFactory.sol diff --git a/audit/mutation-test-scans.json b/audit/mutation-test-scans.json new file mode 100644 index 0000000..b0da986 --- /dev/null +++ b/audit/mutation-test-scans.json @@ -0,0 +1,22 @@ +[ + { + "timestamp": "2026-07-25T20:20:00Z", + "commit": "4422e291234e776de79d3bd8ffa4249a2a9d2abb", + "publishedTag": "sol-v0.1.4", + "commitsAheadOfTag": 2, + "scope": "whole repo", + "tool": "adversarial-mutation-test", + "skillVersion": "0.27.0", + "summary": { + "units": 1, + "behaviours": 91, + "killedByExistingTests": 67, + "gapsFilled": 18, + "equivalentMutants": 5, + "unkillableDefensiveGuards": 1, + "candidates": 3, + "confirmed": 3, + "filed": ["#16", "#17", "#18"] + } + } +] diff --git a/test/src/lib/LibRainDeploy.t.sol b/test/src/lib/LibRainDeploy.t.sol index c08ec54..6cb51a0 100644 --- a/test/src/lib/LibRainDeploy.t.sol +++ b/test/src/lib/LibRainDeploy.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.25; import {Test} from "forge-std-1.16.1/src/Test.sol"; import {LibRainDeploy} from "../../../src/lib/LibRainDeploy.sol"; +import {MockAddressRevertingFactory} from "./MockAddressRevertingFactory.sol"; import {MockDeployable} from "./MockDeployable.sol"; import {MockReverter} from "./MockReverter.sol"; @@ -12,6 +13,26 @@ import {MockReverter} from "./MockReverter.sol"; /// that need `vm.expectRevert` at the correct call depth, and for functions /// that require a storage mapping reference. contract LibRainDeployTest is Test { + /// Base allocates the OP Stack WETH9 predeploy in its genesis block, so + /// this address has code at block 0. + address constant BASE_GENESIS_PREDEPLOY = 0x4200000000000000000000000000000000000006; + + /// Code hash of the Base genesis WETH9 predeploy, fixed by the genesis + /// allocation and therefore identical at block 0 and block 1. + bytes32 constant BASE_GENESIS_PREDEPLOY_CODEHASH = + 0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c; + + /// The block at which the Zoltu factory first has its code on Base. + uint256 constant ZOLTU_BASE_DEPLOY_BLOCK = 1117029; + + /// Chain id of Arbitrum One. + uint256 constant ARBITRUM_ONE_CHAIN_ID = 42161; + + /// The address the Zoltu factory derives for empty creation code, i.e. + /// CREATE2 over the factory address, a zero salt and the hash of empty + /// creation code. An account is created there but it has no code. + address constant ZOLTU_EMPTY_CREATION_CODE_ADDRESS = 0x5DC93B79FBDD6f26Ed9540597C78eD5893F9aC7A; + /// External wrapper for `isStartBlock` so that it can be called /// externally in tests. /// @param target The contract address to check. @@ -150,10 +171,15 @@ contract LibRainDeployTest is Test { } /// `ZOLTU_FACTORY_CODEHASH` MUST match the actual codehash of the Zoltu - /// factory on a forked network. + /// factory on every supported network. Every name in `supportedNetworks` + /// MUST also be a configured fork alias, otherwise it cannot be deployed + /// to at all. function testZoltuFactoryCodehash() external { - vm.createSelectFork(LibRainDeploy.ARBITRUM_ONE); - assertEq(LibRainDeploy.ZOLTU_FACTORY.codehash, LibRainDeploy.ZOLTU_FACTORY_CODEHASH); + string[] memory networks = LibRainDeploy.supportedNetworks(); + for (uint256 i = 0; i < networks.length; i++) { + vm.createSelectFork(networks[i]); + assertEq(LibRainDeploy.ZOLTU_FACTORY.codehash, LibRainDeploy.ZOLTU_FACTORY_CODEHASH, networks[i]); + } } /// `ZOLTU_FACTORY_BYTECODE` MUST match the actual runtime bytecode of the @@ -239,6 +265,11 @@ contract LibRainDeployTest is Test { dependencies ); assertEq(result, 0xC24016f209562fc151e5Ab7F88694ED5775feb36); + // The fork left selected is the last network in the list, and the + // contract exists there with the expected code, so the loop reached + // beyond the first network. + assertEq(block.chainid, ARBITRUM_ONE_CHAIN_ID); + assertEq(result.codehash, 0xc1a263a0b50505687a5140c7964ec5c947329e7d03410306fee68cc3620c5483); } /// External wrapper for `deployToNetworks` so that `vm.expectRevert` @@ -272,6 +303,14 @@ contract LibRainDeployTest is Test { deployedAddress = LibRainDeploy.deployZoltu(creationCode); } + /// External wrapper for `deployZoltu` that carries value, so that what the + /// library does with the caller's value is observable. + /// @param creationCode The creation code to deploy via the Zoltu factory. + /// @return deployedAddress The address of the deployed contract. + function externalDeployZoltuPayable(bytes memory creationCode) external payable returns (address deployedAddress) { + deployedAddress = LibRainDeploy.deployZoltu(creationCode); + } + /// `deployZoltu` MUST deploy a contract via the Zoltu factory and return /// the deterministic address predicted by the factory's nonce. function testDeployZoltu() external { @@ -469,4 +508,153 @@ contract LibRainDeployTest is Test { ); this.externalDeployToNetworks(networks, address(this), hex"", "", address(0), bytes32(0), dependencies); } + + /// `deployToNetworks` MUST deploy when every dependency has code on the + /// network, i.e. a present dependency is not treated as missing. + function testDeployToNetworksPresentDependencyDeploys() external { + string[] memory networks = new string[](1); + networks[0] = LibRainDeploy.ARBITRUM_ONE; + address[] memory dependencies = new address[](1); + dependencies[0] = LibRainDeploy.ZOLTU_FACTORY; + + address result = this.externalDeployToNetworks( + networks, + address(this), + type(MockDeployable).creationCode, + "test/src/lib/MockDeployable.sol:MockDeployable", + 0xC24016f209562fc151e5Ab7F88694ED5775feb36, + 0xc1a263a0b50505687a5140c7964ec5c947329e7d03410306fee68cc3620c5483, + dependencies + ); + assertEq(result, 0xC24016f209562fc151e5Ab7F88694ED5775feb36); + } + + /// `isStartBlock` MUST return true at block 0 for a target that already + /// has the expected code hash in the genesis allocation. There is no block + /// before genesis, so the code hash at the given block alone decides. + function testIsStartBlockGenesisAllocationAtBlockZero() external { + vm.createSelectFork(LibRainDeploy.BASE); + assertTrue(LibRainDeploy.isStartBlock(vm, BASE_GENESIS_PREDEPLOY, BASE_GENESIS_PREDEPLOY_CODEHASH, 0)); + } + + /// `isStartBlock` MUST return false at block 1 for a target from the + /// genesis allocation, because block 0 already has the same code hash. + function testIsStartBlockGenesisAllocationAtBlockOne() external { + vm.createSelectFork(LibRainDeploy.BASE); + assertFalse(LibRainDeploy.isStartBlock(vm, BASE_GENESIS_PREDEPLOY, BASE_GENESIS_PREDEPLOY_CODEHASH, 1)); + } + + /// `isStartBlock` MUST return false for the block immediately after the + /// deploy block. The block compared against is the immediately preceding + /// one, which already has the expected code hash. + function testIsStartBlockOneBlockAfterDeployBlock() external { + vm.createSelectFork(LibRainDeploy.BASE); + assertFalse( + LibRainDeploy.isStartBlock( + vm, LibRainDeploy.ZOLTU_FACTORY, LibRainDeploy.ZOLTU_FACTORY_CODEHASH, ZOLTU_BASE_DEPLOY_BLOCK + 1 + ) + ); + } + + /// `findDeployBlock` MUST return the exact block at which the target first + /// has the expected code hash, including when that is the block the fork is + /// currently at. + function testFindDeployBlockExactZoltuBaseDeployBlock() external { + vm.createSelectFork(LibRainDeploy.BASE, ZOLTU_BASE_DEPLOY_BLOCK); + assertEq( + LibRainDeploy.findDeployBlock(vm, LibRainDeploy.ZOLTU_FACTORY, LibRainDeploy.ZOLTU_FACTORY_CODEHASH, 0), + ZOLTU_BASE_DEPLOY_BLOCK + ); + } + + /// `findDeployBlock` MUST leave the fork on its original block when it + /// reverts because the target already has the expected code hash at the + /// start block. + function testFindDeployBlockRestoresForkOnDeployedBeforeStartBlockRevert() external { + vm.createSelectFork(LibRainDeploy.BASE); + uint256 originalBlock = block.number; + vm.expectRevert( + abi.encodeWithSelector( + LibRainDeploy.DeployedBeforeStartBlock.selector, LibRainDeploy.ZOLTU_FACTORY, ZOLTU_BASE_DEPLOY_BLOCK + ) + ); + this.externalFindDeployBlock( + LibRainDeploy.ZOLTU_FACTORY, LibRainDeploy.ZOLTU_FACTORY_CODEHASH, ZOLTU_BASE_DEPLOY_BLOCK + ); + assertEq(block.number, originalBlock); + } + + /// `deployZoltu` MUST revert when the factory call succeeds but leaves no + /// code at the resulting address. Empty creation code creates an account + /// with no runtime code, which is not a deployment. + function testDeployZoltuRevertsEmptyCreationCode() external { + vm.createSelectFork(LibRainDeploy.ARBITRUM_ONE); + vm.expectRevert( + abi.encodeWithSelector(LibRainDeploy.DeployFailed.selector, true, ZOLTU_EMPTY_CREATION_CODE_ADDRESS) + ); + this.externalDeployZoltu(hex""); + } + + /// `deployZoltu` MUST NOT report a deployment when the factory call fails, + /// even when the failed call leaves the address of a contract that does + /// have code in the call output buffer. + function testDeployZoltuRevertsWhenFactoryCallFailsWithAddressData() external { + vm.createSelectFork(LibRainDeploy.ARBITRUM_ONE); + vm.etch(LibRainDeploy.ZOLTU_FACTORY, address(new MockAddressRevertingFactory()).code); + assertGt(LibRainDeploy.ZOLTU_FACTORY.code.length, 0); + + vm.expectPartialRevert(LibRainDeploy.DeployFailed.selector); + this.externalDeployZoltu(type(MockDeployable).creationCode); + } + + /// `deployZoltu` MUST NOT report the zero address as a deployment, even + /// when the zero address has code. + function testDeployZoltuRevertsZeroAddressWithCode() external { + vm.createSelectFork(LibRainDeploy.ARBITRUM_ONE); + // A call to an address with no code succeeds and returns nothing, so + // the factory yields the zero address. + vm.etch(LibRainDeploy.ZOLTU_FACTORY, hex""); + vm.etch(address(0), hex"00"); + assertGt(address(0).code.length, 0); + + vm.expectRevert(abi.encodeWithSelector(LibRainDeploy.DeployFailed.selector, true, address(0))); + this.externalDeployZoltu(type(MockDeployable).creationCode); + } + + /// `deployZoltu` MUST NOT forward the caller's value to the factory. The + /// deployed mock has a non payable constructor, so forwarded value would + /// fail the deployment outright. + function testDeployZoltuDoesNotForwardValue() external { + vm.createSelectFork(LibRainDeploy.ARBITRUM_ONE); + vm.deal(address(this), 1 ether); + address deployed = this.externalDeployZoltuPayable{value: 1}(type(MockDeployable).creationCode); + assertEq(deployed, 0xC24016f209562fc151e5Ab7F88694ED5775feb36); + assertEq(deployed.balance, 0); + } + + /// `deployAndBroadcast` MUST broadcast as the address derived from the + /// given private key. + function testDeployAndBroadcastUsesDeployerFromPrivateKey() external { + uint256 deployerPrivateKey = 0xA11CE; + address deployer = vm.addr(deployerPrivateKey); + + string[] memory networks = new string[](1); + networks[0] = LibRainDeploy.ARBITRUM_ONE; + address[] memory dependencies = new address[](0); + + vm.createSelectFork(LibRainDeploy.ARBITRUM_ONE); + uint64 nonceBefore = vm.getNonce(deployer); + + this.externalDeployAndBroadcast( + networks, + deployerPrivateKey, + type(MockDeployable).creationCode, + "test/src/lib/MockDeployable.sol:MockDeployable", + 0xC24016f209562fc151e5Ab7F88694ED5775feb36, + 0xc1a263a0b50505687a5140c7964ec5c947329e7d03410306fee68cc3620c5483, + dependencies + ); + + assertEq(vm.getNonce(deployer), nonceBefore + 1); + } } diff --git a/test/src/lib/MockAddressRevertingFactory.sol b/test/src/lib/MockAddressRevertingFactory.sol new file mode 100644 index 0000000..4b38fea --- /dev/null +++ b/test/src/lib/MockAddressRevertingFactory.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity ^0.8.25; + +/// @title MockAddressRevertingFactory +/// Zoltu factory stand-in whose calls always fail, reverting with exactly the +/// twenty bytes of its own address. A caller that reads its call output buffer +/// without checking the call succeeded sees the address of a contract that has +/// code. +contract MockAddressRevertingFactory { + fallback() external { + bytes20 self = bytes20(address(this)); + assembly ("memory-safe") { + mstore(0, self) + revert(0, 20) + } + } +} From 581df8947fa50cd4b336522e254ed47c9ef7251e Mon Sep 17 00:00:00 2001 From: David Meister Date: Fri, 14 Aug 2026 15:03:57 +0000 Subject: [PATCH 2/2] fix(test): pin MockAddressRevertingFactory to an exact compiler version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The concrete mocks all take `=0.8.25` — the mock's bytecode is what the test etches, so a floating pragma makes what is etched a function of whichever compiler happens to be selected. Co-Authored-By: Claude Opus 5 (1M context) --- test/concrete/MockAddressRevertingFactory.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/concrete/MockAddressRevertingFactory.sol b/test/concrete/MockAddressRevertingFactory.sol index 4b38fea..16938ba 100644 --- a/test/concrete/MockAddressRevertingFactory.sol +++ b/test/concrete/MockAddressRevertingFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: LicenseRef-DCL-1.0 // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd -pragma solidity ^0.8.25; +pragma solidity =0.8.25; /// @title MockAddressRevertingFactory /// Zoltu factory stand-in whose calls always fail, reverting with exactly the