Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions audit/mutation-test-scans.json
Original file line number Diff line number Diff line change
@@ -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"]
}
}
]
194 changes: 191 additions & 3 deletions test/src/lib/LibRainDeploy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
}
18 changes: 18 additions & 0 deletions test/src/lib/MockAddressRevertingFactory.sol
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Loading