From f34502c3dfd27297a4f4f7075319ce93c12d86c1 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 16:03:10 -0400 Subject: [PATCH 001/135] clean SystemDeploy.t.sol --- test/deploy/SystemDeploy.t.sol | 45 +++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index f45b456d..fcc9a40d 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -141,7 +141,8 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { } function test_upgrade_withoutManagerDelegatecall_succeeds() public { - SystemDeploy.DeployOutput memory output = systemDeploy.deploy(_defaultDeployInput()); + SystemDeploy.DeployInput memory input = _defaultDeployInput(); + SystemDeploy.DeployOutput memory output = systemDeploy.deploy(input); SystemDeploy.UpgradeOutput memory upgradeOutput = systemDeploy.upgrade( SystemDeploy.UpgradeInput({ @@ -155,7 +156,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { assertFalse(upgradeOutput.superchainConfigUpgraded, "superchain already current"); assertTrue(upgradeOutput.chainUpgraded, "chain upgraded"); _assertUpgradedProxyImplementations(output); - assertValidStandardSystem(_expected(output, _defaultDeployInput())); + assertValidStandardSystem(_expected(output, input)); } function test_deploy_reusingImplementations_doesNotSaveZeroImplementationOnlyArtifacts() public { @@ -239,8 +240,12 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { assertEq(impls.aggregateVerifierImpl, aggregateVerifierAddr, "aggregate verifier impl"); assertEq(impls.teeVerifierImpl, teeVerifierAddr, "tee verifier impl"); assertEq(impls.zkVerifierImpl, zkVerifierAddr, "zk verifier impl"); - assertEq(address(_output.opChain.nitroEnclaveVerifier), _input.implementationsInput.nitroEnclaveVerifier); - assertEq(address(_output.opChain.sp1Verifier), address(_input.implementationsInput.sp1Verifier)); + assertEq( + address(_output.opChain.nitroEnclaveVerifier), + _input.implementationsInput.nitroEnclaveVerifier, + "nitro enclave verifier" + ); + assertEq(address(_output.opChain.sp1Verifier), address(_input.implementationsInput.sp1Verifier), "sp1 verifier"); assertEq( _output.opChain.opChainProxyAdmin.getProxyImplementation(teeProverRegistryProxyAddr), impls.teeProverRegistryImpl, @@ -259,15 +264,23 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { address(_output.opChain.anchorStateRegistryProxy), "aggregate verifier asr" ); - assertEq(address(aggregateVerifier.DISPUTE_GAME_FACTORY()), address(_output.opChain.disputeGameFactoryProxy)); - assertEq(address(aggregateVerifier.DELAYED_WETH()), address(_output.opChain.delayedWETHProxy)); - assertEq(address(aggregateVerifier.TEE_VERIFIER()), teeVerifierAddr); - assertEq(address(aggregateVerifier.ZK_VERIFIER()), zkVerifierAddr); - assertEq(aggregateVerifier.TEE_IMAGE_HASH(), _input.implementationsInput.teeImageHash); - assertEq(aggregateVerifier.ZK_RANGE_HASH(), _input.implementationsInput.zkRangeHash); - assertEq(aggregateVerifier.ZK_AGGREGATE_HASH(), _input.implementationsInput.zkAggregationHash); - assertEq(aggregateVerifier.CONFIG_HASH(), _input.implementationsInput.multiproofConfigHash); - assertEq(aggregateVerifier.L2_CHAIN_ID(), _input.opChainInput.l2ChainId); + assertEq( + address(aggregateVerifier.DISPUTE_GAME_FACTORY()), + address(_output.opChain.disputeGameFactoryProxy), + "aggregate verifier dgf" + ); + assertEq(address(aggregateVerifier.DELAYED_WETH()), address(_output.opChain.delayedWETHProxy), "delayed weth"); + assertEq(address(aggregateVerifier.TEE_VERIFIER()), teeVerifierAddr, "aggregate verifier tee"); + assertEq(address(aggregateVerifier.ZK_VERIFIER()), zkVerifierAddr, "aggregate verifier zk"); + assertEq(aggregateVerifier.TEE_IMAGE_HASH(), _input.implementationsInput.teeImageHash, "tee image hash"); + assertEq(aggregateVerifier.ZK_RANGE_HASH(), _input.implementationsInput.zkRangeHash, "zk range hash"); + assertEq( + aggregateVerifier.ZK_AGGREGATE_HASH(), _input.implementationsInput.zkAggregationHash, "zk aggregation hash" + ); + assertEq( + aggregateVerifier.CONFIG_HASH(), _input.implementationsInput.multiproofConfigHash, "multiproof config hash" + ); + assertEq(aggregateVerifier.L2_CHAIN_ID(), _input.opChainInput.l2ChainId, "l2 chain id"); TEEProverRegistry teeProverRegistry = TEEProverRegistry(teeProverRegistryProxyAddr); assertEq(teeProverRegistry.owner(), _input.opChainInput.roles.opChainProxyAdminOwner, "tee registry owner"); @@ -290,7 +303,11 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { teeProverRegistryProxyAddr, "tee verifier registry" ); - assertEq(address(ZKVerifier(zkVerifierAddr).SP1_VERIFIER()), address(_input.implementationsInput.sp1Verifier)); + assertEq( + address(ZKVerifier(zkVerifierAddr).SP1_VERIFIER()), + address(_input.implementationsInput.sp1Verifier), + "zk verifier sp1" + ); } function _assertUpgradedProxyImplementations(SystemDeploy.DeployOutput memory _output) internal view { From ae48f5af81ae306c896c014c208560692b196fd3 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 16:22:22 -0400 Subject: [PATCH 002/135] clean SystemDeploy.t.sol --- test/deploy/SystemDeploy.t.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index fcc9a40d..200a60c0 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -76,6 +76,8 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { assertEq(output.superchainProxyAdmin.owner(), _superchainProxyAdminOwner, "proxy admin owner"); assertEq(output.superchainConfigProxy.guardian(), _guardian, "proxy guardian"); assertEq(output.superchainConfigImpl.guardian(), _guardian, "impl guardian"); + assertEq(output.superchainConfigProxy.incidentResponder(), _incidentResponder, "proxy incident responder"); + assertEq(output.superchainConfigImpl.incidentResponder(), _incidentResponder, "impl incident responder"); vm.startPrank(address(0)); assertEq( @@ -93,15 +95,13 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { function test_deploySuperchain_nullInput_reverts() public { SystemDeploy.SuperchainInput memory input = SystemDeploy.SuperchainInput({ - guardian: guardian, incidentResponder: address(0), superchainProxyAdminOwner: owner + guardian: guardian, incidentResponder: incidentResponder, superchainProxyAdminOwner: address(0) }); - - input.superchainProxyAdminOwner = address(0); vm.expectRevert(abi.encodeWithSelector(SystemDeploy.InvalidRoleAddress.selector, "superchainProxyAdminOwner")); systemDeploy.deploySuperchain(input); input = SystemDeploy.SuperchainInput({ - guardian: address(0), incidentResponder: address(0), superchainProxyAdminOwner: owner + guardian: address(0), incidentResponder: incidentResponder, superchainProxyAdminOwner: owner }); vm.expectRevert(abi.encodeWithSelector(SystemDeploy.InvalidRoleAddress.selector, "guardian")); systemDeploy.deploySuperchain(input); @@ -109,7 +109,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { function test_deploySuperchain_reuseAddresses_succeeds() public { SystemDeploy.SuperchainInput memory input = SystemDeploy.SuperchainInput({ - guardian: guardian, incidentResponder: address(0), superchainProxyAdminOwner: owner + guardian: guardian, incidentResponder: incidentResponder, superchainProxyAdminOwner: owner }); SystemDeploy.SuperchainOutput memory output0 = systemDeploy.deploySuperchain(input); From 49c25946b7872e88974be07fd58b5e371b57f321 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 16:39:19 -0400 Subject: [PATCH 003/135] clean SystemDeploy.t.sol --- test/deploy/SystemDeploy.t.sol | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index 200a60c0..f0489a43 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -8,16 +8,14 @@ import { SystemDeploy } from "scripts/deploy/SystemDeploy.s.sol"; import { Types } from "scripts/libraries/Types.sol"; import { SystemDeployAssertions } from "test/deploy/SystemDeployAssertions.sol"; -import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; -import { IProxy } from "interfaces/universal/IProxy.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; import { ZKVerifier } from "src/L1/proofs/zk/ZKVerifier.sol"; import { GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; -import { Claim } from "src/libraries/bridge/LibUDT.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; contract MockNitroEnclaveVerifier { address public proofSubmitter; @@ -47,7 +45,6 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { MockSP1Verifier internal sp1Verifier; uint256 internal l2ChainId = 901; - Claim internal absolutePrestate = Claim.wrap(0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c); function setUp() public { systemDeploy = new SystemDeploy(); @@ -79,18 +76,16 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { assertEq(output.superchainConfigProxy.incidentResponder(), _incidentResponder, "proxy incident responder"); assertEq(output.superchainConfigImpl.incidentResponder(), _incidentResponder, "impl incident responder"); - vm.startPrank(address(0)); assertEq( - IProxy(payable(address(output.superchainConfigProxy))).implementation(), + EIP1967Helper.getImplementation(address(output.superchainConfigProxy)), address(output.superchainConfigImpl), "implementation" ); assertEq( - IProxy(payable(address(output.superchainConfigProxy))).admin(), + EIP1967Helper.getAdmin(address(output.superchainConfigProxy)), address(output.superchainProxyAdmin), "admin" ); - vm.stopPrank(); } function test_deploySuperchain_nullInput_reverts() public { @@ -160,9 +155,9 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { } function test_deploy_reusingImplementations_doesNotSaveZeroImplementationOnlyArtifacts() public { - SystemDeploy.DeployOutput memory output = systemDeploy.deploy(_defaultDeployInput()); - SystemDeploy.DeployInput memory input = _defaultDeployInput(); + SystemDeploy.DeployOutput memory output = systemDeploy.deploy(input); + input.saveArtifacts = true; input.superchainConfigProxy = output.superchain.superchainConfigProxy; input.implementations = output.impls; From 8e15c7c3eef85fa422d54d61edca7c03c9a3d877 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 16:44:59 -0400 Subject: [PATCH 004/135] clean SystemDeploy.t.sol --- test/deploy/SystemDeploy.t.sol | 60 ++++------------------------------ 1 file changed, 7 insertions(+), 53 deletions(-) diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index f0489a43..ce454425 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -150,7 +150,13 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { assertFalse(upgradeOutput.superchainConfigUpgraded, "superchain already current"); assertTrue(upgradeOutput.chainUpgraded, "chain upgraded"); - _assertUpgradedProxyImplementations(output); + assertEq( + output.superchain.superchainProxyAdmin.getProxyImplementation( + address(output.superchain.superchainConfigProxy) + ), + output.impls.superchainConfigImpl, + "superchain config impl" + ); assertValidStandardSystem(_expected(output, input)); } @@ -305,58 +311,6 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { ); } - function _assertUpgradedProxyImplementations(SystemDeploy.DeployOutput memory _output) internal view { - IProxyAdmin superchainProxyAdmin = _output.superchain.superchainProxyAdmin; - IProxyAdmin opChainProxyAdmin = _output.opChain.opChainProxyAdmin; - Types.Implementations memory impls = _output.impls; - - assertEq( - superchainProxyAdmin.getProxyImplementation(address(_output.superchain.superchainConfigProxy)), - impls.superchainConfigImpl, - "superchain config impl" - ); - assertEq( - opChainProxyAdmin.getProxyImplementation(address(_output.opChain.systemConfigProxy)), - impls.systemConfigImpl, - "system config impl" - ); - assertEq( - opChainProxyAdmin.getProxyImplementation(address(_output.opChain.optimismPortalProxy)), - impls.optimismPortalImpl, - "portal impl" - ); - assertEq( - opChainProxyAdmin.getProxyImplementation(address(_output.opChain.anchorStateRegistryProxy)), - impls.anchorStateRegistryImpl, - "anchor state registry impl" - ); - assertEq( - opChainProxyAdmin.getProxyImplementation(address(_output.opChain.optimismMintableERC20FactoryProxy)), - impls.optimismMintableERC20FactoryImpl, - "erc20 factory impl" - ); - assertEq( - opChainProxyAdmin.getProxyImplementation(address(_output.opChain.disputeGameFactoryProxy)), - impls.disputeGameFactoryImpl, - "dispute game factory impl" - ); - assertEq( - opChainProxyAdmin.getProxyImplementation(address(_output.opChain.l1CrossDomainMessengerProxy)), - impls.l1CrossDomainMessengerImpl, - "messenger impl" - ); - assertEq( - opChainProxyAdmin.getProxyImplementation(address(_output.opChain.l1StandardBridgeProxy)), - impls.l1StandardBridgeImpl, - "standard bridge impl" - ); - assertEq( - opChainProxyAdmin.getProxyImplementation(address(_output.opChain.l1ERC721BridgeProxy)), - impls.l1ERC721BridgeImpl, - "erc721 bridge impl" - ); - } - function _expected( SystemDeploy.DeployOutput memory _output, SystemDeploy.DeployInput memory _input From 3f9d3a255457da49bde6c4a4b94c13d3591280b7 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 16:55:37 -0400 Subject: [PATCH 005/135] clean SystemDeploy.t.sol --- test/deploy/SystemDeploy.t.sol | 47 +++------------------------------- 1 file changed, 4 insertions(+), 43 deletions(-) diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index ce454425..a4c5af72 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -10,7 +10,6 @@ import { SystemDeployAssertions } from "test/deploy/SystemDeployAssertions.sol"; import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; import { ZKVerifier } from "src/L1/proofs/zk/ZKVerifier.sol"; @@ -82,9 +81,7 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { "implementation" ); assertEq( - EIP1967Helper.getAdmin(address(output.superchainConfigProxy)), - address(output.superchainProxyAdmin), - "admin" + EIP1967Helper.getAdmin(address(output.superchainConfigProxy)), address(output.superchainProxyAdmin), "admin" ); } @@ -151,9 +148,8 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { assertFalse(upgradeOutput.superchainConfigUpgraded, "superchain already current"); assertTrue(upgradeOutput.chainUpgraded, "chain upgraded"); assertEq( - output.superchain.superchainProxyAdmin.getProxyImplementation( - address(output.superchain.superchainConfigProxy) - ), + output.superchain.superchainProxyAdmin + .getProxyImplementation(address(output.superchain.superchainConfigProxy)), output.impls.superchainConfigImpl, "superchain config impl" ); @@ -226,19 +222,14 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { internal view { - GameType gameType = GameType.wrap(uint32(_input.implementationsInput.multiproofGameType)); - address aggregateVerifierAddr = address(_output.opChain.aggregateVerifier); address teeProverRegistryProxyAddr = address(_output.opChain.teeProverRegistryProxy); address teeVerifierAddr = address(_output.opChain.teeVerifier); address zkVerifierAddr = address(_output.opChain.zkVerifier); Types.Implementations memory impls = _output.impls; - assertNotEq(aggregateVerifierAddr, address(0), "aggregate verifier"); assertNotEq(teeProverRegistryProxyAddr, address(0), "tee prover registry proxy"); assertNotEq(impls.teeProverRegistryImpl, address(0), "tee prover registry impl"); - assertNotEq(teeVerifierAddr, address(0), "tee verifier"); - assertNotEq(zkVerifierAddr, address(0), "zk verifier"); - assertEq(impls.aggregateVerifierImpl, aggregateVerifierAddr, "aggregate verifier impl"); + assertEq(impls.aggregateVerifierImpl, address(_output.opChain.aggregateVerifier), "aggregate verifier impl"); assertEq(impls.teeVerifierImpl, teeVerifierAddr, "tee verifier impl"); assertEq(impls.zkVerifierImpl, zkVerifierAddr, "zk verifier impl"); assertEq( @@ -253,36 +244,6 @@ contract SystemDeploy_Test is Test, SystemDeployAssertions { "tee registry proxy impl" ); - assertEq( - address(_output.opChain.disputeGameFactoryProxy.gameImpls(gameType)), - aggregateVerifierAddr, - "multiproof game impl" - ); - - AggregateVerifier aggregateVerifier = AggregateVerifier(aggregateVerifierAddr); - assertEq( - address(aggregateVerifier.anchorStateRegistry()), - address(_output.opChain.anchorStateRegistryProxy), - "aggregate verifier asr" - ); - assertEq( - address(aggregateVerifier.DISPUTE_GAME_FACTORY()), - address(_output.opChain.disputeGameFactoryProxy), - "aggregate verifier dgf" - ); - assertEq(address(aggregateVerifier.DELAYED_WETH()), address(_output.opChain.delayedWETHProxy), "delayed weth"); - assertEq(address(aggregateVerifier.TEE_VERIFIER()), teeVerifierAddr, "aggregate verifier tee"); - assertEq(address(aggregateVerifier.ZK_VERIFIER()), zkVerifierAddr, "aggregate verifier zk"); - assertEq(aggregateVerifier.TEE_IMAGE_HASH(), _input.implementationsInput.teeImageHash, "tee image hash"); - assertEq(aggregateVerifier.ZK_RANGE_HASH(), _input.implementationsInput.zkRangeHash, "zk range hash"); - assertEq( - aggregateVerifier.ZK_AGGREGATE_HASH(), _input.implementationsInput.zkAggregationHash, "zk aggregation hash" - ); - assertEq( - aggregateVerifier.CONFIG_HASH(), _input.implementationsInput.multiproofConfigHash, "multiproof config hash" - ); - assertEq(aggregateVerifier.L2_CHAIN_ID(), _input.opChainInput.l2ChainId, "l2 chain id"); - TEEProverRegistry teeProverRegistry = TEEProverRegistry(teeProverRegistryProxyAddr); assertEq(teeProverRegistry.owner(), _input.opChainInput.roles.opChainProxyAdminOwner, "tee registry owner"); assertEq(teeProverRegistry.manager(), _input.opChainInput.roles.opChainProxyAdminOwner, "tee registry manager"); From 4a3da72b1dc7b52c38c9e1b737a94f54c71fdec6 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 16:59:27 -0400 Subject: [PATCH 006/135] clean SystemDeploy.t.sol --- test/deploy/SystemDeploy.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/deploy/SystemDeploy.t.sol b/test/deploy/SystemDeploy.t.sol index a4c5af72..6a94439d 100644 --- a/test/deploy/SystemDeploy.t.sol +++ b/test/deploy/SystemDeploy.t.sol @@ -9,7 +9,6 @@ import { Types } from "scripts/libraries/Types.sol"; import { SystemDeployAssertions } from "test/deploy/SystemDeployAssertions.sol"; import { ISP1Verifier } from "interfaces/L1/proofs/zk/ISP1Verifier.sol"; -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; import { ZKVerifier } from "src/L1/proofs/zk/ZKVerifier.sol"; From e7ab3b269389d76224524512cbc1b0123d8fa8a9 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 17:19:31 -0400 Subject: [PATCH 007/135] clean SystemDeployAssertions.sol --- test/deploy/SystemDeployAssertions.sol | 36 ++++++++++---------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/test/deploy/SystemDeployAssertions.sol b/test/deploy/SystemDeployAssertions.sol index 6b805ee8..51f6d7b6 100644 --- a/test/deploy/SystemDeployAssertions.sol +++ b/test/deploy/SystemDeployAssertions.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { Test, console } from "lib/forge-std/src/Test.sol"; +import { Test } from "lib/forge-std/src/Test.sol"; import { Types } from "scripts/libraries/Types.sol"; @@ -272,19 +272,14 @@ abstract contract SystemDeployAssertions is Test { private view { - string memory prefix = "AV-DWETH"; + assertEq(_version(address(_weth)), _version(_expected.implementations.delayedWETHImpl), "AV-DWETH-10"); assertEq( - _version(address(_weth)), _version(_expected.implementations.delayedWETHImpl), string.concat(prefix, "-10") + _proxyAdmin.getProxyImplementation(address(_weth)), _expected.implementations.delayedWETHImpl, "AV-DWETH-20" ); - assertEq( - _proxyAdmin.getProxyImplementation(address(_weth)), - _expected.implementations.delayedWETHImpl, - string.concat(prefix, "-20") - ); - assertEq(_weth.proxyAdminOwner(), _expected.proxyAdminOwner, string.concat(prefix, "-30")); - assertEq(_weth.delay(), _expected.withdrawalDelaySeconds, string.concat(prefix, "-40")); - assertEq(address(_weth.systemConfig()), address(_expected.systemConfig), string.concat(prefix, "-50")); - assertEq(address(_proxyAdminFor(address(_weth))), address(_proxyAdmin), string.concat(prefix, "-60")); + assertEq(_weth.proxyAdminOwner(), _expected.proxyAdminOwner, "AV-DWETH-30"); + assertEq(_weth.delay(), _expected.withdrawalDelaySeconds, "AV-DWETH-40"); + assertEq(address(_weth.systemConfig()), address(_expected.systemConfig), "AV-DWETH-50"); + assertEq(address(_proxyAdminFor(address(_weth))), address(_proxyAdmin), "AV-DWETH-60"); } function _assertAnchorStateRegistry( @@ -296,21 +291,16 @@ abstract contract SystemDeployAssertions is Test { private view { - string memory prefix = "AV-ANCHORP"; - assertEq( - _version(address(_asr)), - _version(_expected.implementations.anchorStateRegistryImpl), - string.concat(prefix, "-10") - ); + assertEq(_version(address(_asr)), _version(_expected.implementations.anchorStateRegistryImpl), "AV-ANCHORP-10"); assertEq( _proxyAdmin.getProxyImplementation(address(_asr)), _expected.implementations.anchorStateRegistryImpl, - string.concat(prefix, "-20") + "AV-ANCHORP-20" ); - assertEq(address(_asr.disputeGameFactory()), address(_factory), string.concat(prefix, "-30")); - assertEq(address(_asr.systemConfig()), address(_expected.systemConfig), string.concat(prefix, "-40")); - assertEq(address(_proxyAdminFor(address(_asr))), address(_proxyAdmin), string.concat(prefix, "-50")); - assertGt(_asr.retirementTimestamp(), 0, string.concat(prefix, "-60")); + assertEq(address(_asr.disputeGameFactory()), address(_factory), "AV-ANCHORP-30"); + assertEq(address(_asr.systemConfig()), address(_expected.systemConfig), "AV-ANCHORP-40"); + assertEq(address(_proxyAdminFor(address(_asr))), address(_proxyAdmin), "AV-ANCHORP-50"); + assertGt(_asr.retirementTimestamp(), 0, "AV-ANCHORP-60"); } function _assertETHLockbox(ExpectedSystemDeployState memory _expected, IProxyAdmin _proxyAdmin) private view { From 92d3955d94abc73a24e6537790322fa93406f5b0 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 17:33:58 -0400 Subject: [PATCH 008/135] clean SystemDeployAssertions.sol --- test/deploy/SystemDeployAssertions.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/deploy/SystemDeployAssertions.sol b/test/deploy/SystemDeployAssertions.sol index 51f6d7b6..fb3aa1c1 100644 --- a/test/deploy/SystemDeployAssertions.sol +++ b/test/deploy/SystemDeployAssertions.sol @@ -172,8 +172,7 @@ abstract contract SystemDeployAssertions is Test { ); assertEq(address(portal.disputeGameFactory()), address(dgf), "PORTAL-30"); assertEq(address(portal.systemConfig()), address(sysCfg), "PORTAL-40"); - IDisputeGame av = dgf.gameImpls(_expected.multiproofGameType); - assertEq(address(portal.anchorStateRegistry()), address(av.anchorStateRegistry()), "PORTAL-50"); + assertEq(address(portal.anchorStateRegistry()), address(_expected.anchorStateRegistry), "PORTAL-50"); assertEq(portal.l2Sender(), Constants.DEFAULT_L2_SENDER, "PORTAL-80"); assertEq(address(_proxyAdminFor(address(portal))), address(_proxyAdmin), "PORTAL-90"); } @@ -208,9 +207,7 @@ abstract contract SystemDeployAssertions is Test { IDisputeGame game = factory.gameImpls(_gameType); assertNotEq(address(game), address(0), "AV-10"); - address expectedImpl = _expected.implementations.aggregateVerifierImpl; - assertEq(address(game), expectedImpl, "AV-15"); - assertEq(_version(address(game)), _version(expectedImpl), "AV-20"); + assertEq(address(game), _expected.implementations.aggregateVerifierImpl, "AV-15"); _assertGameArgsAndContracts({ _expected: _expected, From 0f81dd30e15ba51b03c1f375224a03073fb03bc2 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 17:45:37 -0400 Subject: [PATCH 009/135] clean CrossDomainMessenger.t.sol invariant --- test/invariants/CrossDomainMessenger.t.sol | 111 +++++++++------------ 1 file changed, 46 insertions(+), 65 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index bf68e695..50d2ec41 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -13,12 +13,10 @@ import { Hashing } from "src/libraries/Hashing.sol"; import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; contract RelayActor is StdUtils { - // Storage slot of the l2Sender - uint256 senderSlotIndex; + address internal constant IDENTITY_PRECOMPILE = address(0x04); - uint256 public numHashes; bytes32[] public hashes; - bool public reverted = false; + bool public reverted; IOptimismPortal2 op; IL1CrossDomainMessenger xdm; @@ -30,31 +28,30 @@ contract RelayActor is StdUtils { xdm = _xdm; vm = _vm; doFail = _doFail; - senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot; + + // Set op.l2Sender() once to the L2 Cross Domain Messenger. Nothing in the fuzzed + // surface modifies this slot, so we don't need to re-write it on every relay. + uint256 senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot; + vm.store( + address(_op), bytes32(senderSlotIndex), bytes32(uint256(uint160(Predeploys.L2_CROSS_DOMAIN_MESSENGER))) + ); + } + + function hashesLength() external view returns (uint256) { + return hashes.length; } /// @notice Relays a message to the `L1CrossDomainMessenger` with a random `version`, /// and `_message`. function relay(uint8 _version, uint8 _value, bytes memory _message) external { - address target = address(0x04); // ID precompile - address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; - - // Set the minimum gas limit to the cost of the identity precompile's execution for - // the given message. - // ID Precompile cost can be determined by calculating: 15 + 3 * data_word_length - uint32 minGasLimit = uint32(15 + 3 * ((_message.length + 31) / 32)); - - // set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(op), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); - - // Restrict version to the range of [0, 1] + // Restrict version to [0, 1] and value to [0, 1] (variance with/without value; + // the ID precompile accepts value). _version = _version % 2; - - // Restrict the value to the range of [0, 1] - // This is just so we get variance of calls with and without value. The ID precompile - // will not reject value being sent to it. _value = _value % 2; + // ID precompile gas cost: 15 + 3 * data_word_length. + uint32 minGasLimit = uint32(15 + 3 * ((_message.length + 31) / 32)); + // For the failure case, we use an impossibly large minGasLimit so that the hasMinGas // check always fails regardless of available gas. We provide baseGas-level gas (enough // for relayMessage's overhead) to avoid OOG reverts. Limiting gas directly is fragile @@ -64,31 +61,27 @@ contract RelayActor is StdUtils { uint32 relayMinGasLimit = doFail ? type(uint32).max : minGasLimit; uint256 gas = xdm.baseGas(_message, minGasLimit); - // Compute the cross domain message hash and store it in `hashes`. - // The `relayMessage` function will always encode the message as a version 1 - // message after checking that the V0 hash has not already been relayed. - bytes32 _hash = Hashing.hashCrossDomainMessageV1( - Encoding.encodeVersionedNonce(0, _version), sender, target, _value, relayMinGasLimit, _message - ); + // `relayMessage` always re-encodes as a v1 hash after checking the v0 hash hasn't been + // relayed, so the v1 hash is what we track. + uint256 nonce = Encoding.encodeVersionedNonce(0, _version); + address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; + bytes32 _hash = + Hashing.hashCrossDomainMessageV1(nonce, sender, IDENTITY_PRECOMPILE, _value, relayMinGasLimit, _message); hashes.push(_hash); - numHashes += 1; // Make sure we've got a fresh message. vm.assume(xdm.successfulMessages(_hash) == false && xdm.failedMessages(_hash) == false); - // Act as the optimism portal and call `relayMessage` on the `L1CrossDomainMessenger` with - // the outer min gas limit. vm.startPrank(address(op)); if (!doFail) { - vm.expectCallMinGas(address(0x04), _value, minGasLimit, _message); + vm.expectCallMinGas(IDENTITY_PRECOMPILE, _value, minGasLimit, _message); } try xdm.relayMessage{ gas: gas, value: _value }( - Encoding.encodeVersionedNonce(0, _version), sender, target, _value, relayMinGasLimit, _message + nonce, sender, IDENTITY_PRECOMPILE, _value, relayMinGasLimit, _message ) { } catch { - // If any of these calls revert, set `reverted` to true to fail the invariant test. - // NOTE: This is to get around forge's invariant fuzzer ignoring reverted calls - // to this function. + // Forge's invariant fuzzer ignores reverted target calls, so we surface the failure + // by flipping a flag the invariant asserts on. reverted = true; } vm.stopPrank(); @@ -99,38 +92,43 @@ contract XDM_MinGasLimits is CommonTest { RelayActor actor; function init(bool doFail) public virtual { - // Set up the `L1CrossDomainMessenger` and `OptimismPortal` contracts. super.setUp(); - // Deploy a relay actor actor = new RelayActor(optimismPortal2, l1CrossDomainMessenger, vm, doFail); - // Give the portal some ether to send to `relayMessage` + // Give the portal some ether to send to `relayMessage`. vm.deal(address(optimismPortal2), type(uint128).max); - // Target the `RelayActor` contract targetContract(address(actor)); - // Don't allow the estimation address to be the sender excludeSender(Constants.ESTIMATION_ADDRESS); - // Don't allow the predeploys to be the senders + // Don't allow the predeploys to be the senders. uint160 prefix = uint160(0x420) << 148; - for (uint256 i = 0; i < 2048; i++) { - address addr = address(prefix | uint160(i)); - excludeContract(addr); + for (uint256 i = 0; i < Predeploys.PREDEPLOY_COUNT; i++) { + excludeContract(address(prefix | uint160(i))); } - // Target the actor's `relay` function bytes4[] memory selectors = new bytes4[](1); selectors[0] = actor.relay.selector; targetSelector(FuzzSelector({ addr: address(actor), selectors: selectors })); } + + /// @dev Asserts every relayed hash landed in the expected mapping. `expectSuccess = true` + /// checks `successfulMessages`; `false` checks `failedMessages`. + function _assertHashes(bool expectSuccess) internal view { + uint256 length = actor.hashesLength(); + for (uint256 i = 0; i < length; ++i) { + bytes32 hash = actor.hashes(i); + assertEq(l1CrossDomainMessenger.successfulMessages(hash), expectSuccess); + assertEq(l1CrossDomainMessenger.failedMessages(hash), !expectSuccess); + } + assertFalse(actor.reverted()); + } } contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits { function setUp() public override { - // Don't fail super.init(false); } @@ -148,21 +146,12 @@ contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits { /// - The inner min gas limit is for the call from the /// `L1CrossDomainMessenger` to the target contract. function invariant_minGasLimits() external view { - uint256 length = actor.numHashes(); - for (uint256 i = 0; i < length; ++i) { - bytes32 hash = actor.hashes(i); - // The message hash is set in the successfulMessages mapping - assertTrue(l1CrossDomainMessenger.successfulMessages(hash)); - // The message hash is not set in the failedMessages mapping - assertFalse(l1CrossDomainMessenger.failedMessages(hash)); - } - assertFalse(actor.reverted()); + _assertHashes(true); } } contract XDM_MinGasLimits_Reverts is XDM_MinGasLimits { function setUp() public override { - // Do fail super.init(true); } @@ -181,14 +170,6 @@ contract XDM_MinGasLimits_Reverts is XDM_MinGasLimits { /// - The inner min gas limit is for the call from the /// `L1CrossDomainMessenger` to the target contract. function invariant_minGasLimits() external view { - uint256 length = actor.numHashes(); - for (uint256 i = 0; i < length; ++i) { - bytes32 hash = actor.hashes(i); - // The message hash is not set in the successfulMessages mapping - assertFalse(l1CrossDomainMessenger.successfulMessages(hash)); - // The message hash is set in the failedMessages mapping - assertTrue(l1CrossDomainMessenger.failedMessages(hash)); - } - assertFalse(actor.reverted()); + _assertHashes(false); } } From 2501e7becf37399756c6ca428120406c29528237 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 17:52:43 -0400 Subject: [PATCH 010/135] clean CrossDomainMessenger.t.sol invariant --- test/invariants/CrossDomainMessenger.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 50d2ec41..42a95f7a 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -21,7 +21,7 @@ contract RelayActor is StdUtils { IOptimismPortal2 op; IL1CrossDomainMessenger xdm; Vm vm; - bool doFail; + bool internal immutable doFail; constructor(IOptimismPortal2 _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) { op = _op; From 22ce7db7fb1ac62c0885850312ebec9f82834d66 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 17:56:17 -0400 Subject: [PATCH 011/135] clean CrossDomainMessenger.t.sol invariant --- test/invariants/CrossDomainMessenger.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 42a95f7a..28c3dbef 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -18,9 +18,9 @@ contract RelayActor is StdUtils { bytes32[] public hashes; bool public reverted; - IOptimismPortal2 op; - IL1CrossDomainMessenger xdm; - Vm vm; + IOptimismPortal2 internal immutable op; + IL1CrossDomainMessenger internal immutable xdm; + Vm internal immutable vm; bool internal immutable doFail; constructor(IOptimismPortal2 _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) { From d4593d4cdee0ccbfd9619acc2cdf075648eedf5f Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 17:59:57 -0400 Subject: [PATCH 012/135] clean CrossDomainMessenger.t.sol invariant --- test/invariants/CrossDomainMessenger.t.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 28c3dbef..958cc333 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -44,8 +44,8 @@ contract RelayActor is StdUtils { /// @notice Relays a message to the `L1CrossDomainMessenger` with a random `version`, /// and `_message`. function relay(uint8 _version, uint8 _value, bytes memory _message) external { - // Restrict version to [0, 1] and value to [0, 1] (variance with/without value; - // the ID precompile accepts value). + // Vary value between 0 and 1 to exercise the with/without-value paths; the ID + // precompile accepts value. _version = _version % 2; _value = _value % 2; @@ -69,7 +69,6 @@ contract RelayActor is StdUtils { Hashing.hashCrossDomainMessageV1(nonce, sender, IDENTITY_PRECOMPILE, _value, relayMinGasLimit, _message); hashes.push(_hash); - // Make sure we've got a fresh message. vm.assume(xdm.successfulMessages(_hash) == false && xdm.failedMessages(_hash) == false); vm.startPrank(address(op)); From 374e79fe48ad1c6c67f50f9e522035efd898e065 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 18:08:55 -0400 Subject: [PATCH 013/135] clean CrossDomainMessenger.t.sol invariant --- test/invariants/CrossDomainMessenger.t.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 958cc333..8851345a 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -41,8 +41,7 @@ contract RelayActor is StdUtils { return hashes.length; } - /// @notice Relays a message to the `L1CrossDomainMessenger` with a random `version`, - /// and `_message`. + /// @notice Relays a fuzzed message to the `L1CrossDomainMessenger`. function relay(uint8 _version, uint8 _value, bytes memory _message) external { // Vary value between 0 and 1 to exercise the with/without-value paths; the ID // precompile accepts value. @@ -67,9 +66,9 @@ contract RelayActor is StdUtils { address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; bytes32 _hash = Hashing.hashCrossDomainMessageV1(nonce, sender, IDENTITY_PRECOMPILE, _value, relayMinGasLimit, _message); - hashes.push(_hash); vm.assume(xdm.successfulMessages(_hash) == false && xdm.failedMessages(_hash) == false); + hashes.push(_hash); vm.startPrank(address(op)); if (!doFail) { From ab848fbe140160bc80abe23b9239c3f6e28faac9 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:07:07 -0400 Subject: [PATCH 014/135] Refactor CrossDomainMessenger.t.sol: remove unused StdUtils import, add unexpectedMessageStatus flag, and streamline hash relay message logic. --- test/invariants/CrossDomainMessenger.t.sol | 97 ++++++++++------------ 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 8851345a..9e9d49cc 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { StdUtils } from "lib/forge-std/src/StdUtils.sol"; import { Vm } from "lib/forge-std/src/Vm.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; @@ -12,11 +11,11 @@ import { Encoding } from "src/libraries/Encoding.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; -contract RelayActor is StdUtils { +contract RelayActor { address internal constant IDENTITY_PRECOMPILE = address(0x04); - bytes32[] public hashes; bool public reverted; + bool public unexpectedMessageStatus; IOptimismPortal2 internal immutable op; IL1CrossDomainMessenger internal immutable xdm; @@ -32,13 +31,27 @@ contract RelayActor is StdUtils { // Set op.l2Sender() once to the L2 Cross Domain Messenger. Nothing in the fuzzed // surface modifies this slot, so we don't need to re-write it on every relay. uint256 senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot; - vm.store( - address(_op), bytes32(senderSlotIndex), bytes32(uint256(uint160(Predeploys.L2_CROSS_DOMAIN_MESSENGER))) - ); + vm.store(address(_op), bytes32(senderSlotIndex), bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER))); } - function hashesLength() external view returns (uint256) { - return hashes.length; + function _hashRelayMessage( + uint256 _nonce, + uint256 _value, + uint256 _minGasLimit, + bytes memory _message + ) + internal + pure + returns (bytes32) + { + return Hashing.hashCrossDomainMessageV1({ + _nonce: _nonce, + _sender: Predeploys.L2_CROSS_DOMAIN_MESSENGER, + _target: IDENTITY_PRECOMPILE, + _value: _value, + _gasLimit: _minGasLimit, + _data: _message + }); } /// @notice Relays a fuzzed message to the `L1CrossDomainMessenger`. @@ -62,20 +75,19 @@ contract RelayActor is StdUtils { // `relayMessage` always re-encodes as a v1 hash after checking the v0 hash hasn't been // relayed, so the v1 hash is what we track. - uint256 nonce = Encoding.encodeVersionedNonce(0, _version); - address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; - bytes32 _hash = - Hashing.hashCrossDomainMessageV1(nonce, sender, IDENTITY_PRECOMPILE, _value, relayMinGasLimit, _message); + uint256 nonce = Encoding.encodeVersionedNonce({ _nonce: 0, _version: _version }); - vm.assume(xdm.successfulMessages(_hash) == false && xdm.failedMessages(_hash) == false); - hashes.push(_hash); + { + bytes32 initialHash = _hashRelayMessage(nonce, _value, relayMinGasLimit, _message); + vm.assume(xdm.successfulMessages(initialHash) == false && xdm.failedMessages(initialHash) == false); + } vm.startPrank(address(op)); if (!doFail) { vm.expectCallMinGas(IDENTITY_PRECOMPILE, _value, minGasLimit, _message); } try xdm.relayMessage{ gas: gas, value: _value }( - nonce, sender, IDENTITY_PRECOMPILE, _value, relayMinGasLimit, _message + nonce, Predeploys.L2_CROSS_DOMAIN_MESSENGER, IDENTITY_PRECOMPILE, _value, relayMinGasLimit, _message ) { } catch { // Forge's invariant fuzzer ignores reverted target calls, so we surface the failure @@ -83,6 +95,10 @@ contract RelayActor is StdUtils { reverted = true; } vm.stopPrank(); + + bytes32 hash = _hashRelayMessage(nonce, _value, relayMinGasLimit, _message); + unexpectedMessageStatus = + unexpectedMessageStatus || xdm.successfulMessages(hash) == doFail || xdm.failedMessages(hash) != doFail; } } @@ -112,15 +128,9 @@ contract XDM_MinGasLimits is CommonTest { targetSelector(FuzzSelector({ addr: address(actor), selectors: selectors })); } - /// @dev Asserts every relayed hash landed in the expected mapping. `expectSuccess = true` - /// checks `successfulMessages`; `false` checks `failedMessages`. - function _assertHashes(bool expectSuccess) internal view { - uint256 length = actor.hashesLength(); - for (uint256 i = 0; i < length; ++i) { - bytes32 hash = actor.hashes(i); - assertEq(l1CrossDomainMessenger.successfulMessages(hash), expectSuccess); - assertEq(l1CrossDomainMessenger.failedMessages(hash), !expectSuccess); - } + /// @dev The actor records any relay that reverts or lands in the wrong message-status mapping. + function _assertRelayResults() internal view { + assertFalse(actor.unexpectedMessageStatus()); assertFalse(actor.reverted()); } } @@ -130,21 +140,10 @@ contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits { super.init(false); } - /// @custom:invariant A call to `relayMessage` should succeed if at least the minimum gas limit - /// can be supplied to the target context, there is enough gas to complete - /// execution of `relayMessage` after the target context's execution is - /// finished, and the target context did not revert. - /// - /// There are two minimum gas limits here: - /// - /// - The outer min gas limit is for the call from the `OptimismPortal` to the - /// `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's - /// `baseGas` function with the `message` and inner limit. - /// - /// - The inner min gas limit is for the call from the - /// `L1CrossDomainMessenger` to the target contract. - function invariant_minGasLimits() external view { - _assertHashes(true); + /// @custom:invariant `relayMessage` should succeed when the outer call has base gas and the + /// target can receive the inner minimum gas limit. + function invariant_relayMessage_forwardsMinGas_succeeds() external view { + _assertRelayResults(); } } @@ -153,21 +152,9 @@ contract XDM_MinGasLimits_Reverts is XDM_MinGasLimits { super.init(true); } - /// @custom:invariant A call to `relayMessage` should assign the message hash to the - /// `failedMessages` mapping if not enough gas is supplied to forward - /// `minGasLimit` to the target context or if there is not enough gas to - /// complete execution of `relayMessage` after the target context's execution - /// is finished. - /// - /// There are two minimum gas limits here: - /// - /// - The outer min gas limit is for the call from the `OptimismPortal` to the - /// `L1CrossDomainMessenger`, and it can be retrieved by calling the xdm's - /// `baseGas` function with the `message` and inner limit. - /// - /// - The inner min gas limit is for the call from the - /// `L1CrossDomainMessenger` to the target contract. - function invariant_minGasLimits() external view { - _assertHashes(false); + /// @custom:invariant `relayMessage` should mark the message failed when the inner minimum gas + /// limit is too large to forward to the target. + function invariant_relayMessage_insufficientMinGas_fails() external view { + _assertRelayResults(); } } From 876b2d830d5088e41b5407ee52b52cb9ae9f83f4 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:12:43 -0400 Subject: [PATCH 015/135] Refactor CrossDomainMessenger.t.sol: simplify hash relay message logic and update init function visibility. --- test/invariants/CrossDomainMessenger.t.sol | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 9e9d49cc..5097dbd0 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -77,10 +77,8 @@ contract RelayActor { // relayed, so the v1 hash is what we track. uint256 nonce = Encoding.encodeVersionedNonce({ _nonce: 0, _version: _version }); - { - bytes32 initialHash = _hashRelayMessage(nonce, _value, relayMinGasLimit, _message); - vm.assume(xdm.successfulMessages(initialHash) == false && xdm.failedMessages(initialHash) == false); - } + bytes32 relayMessageHash = _hashRelayMessage(nonce, _value, relayMinGasLimit, _message); + vm.assume(!xdm.successfulMessages(relayMessageHash) && !xdm.failedMessages(relayMessageHash)); vm.startPrank(address(op)); if (!doFail) { @@ -96,16 +94,15 @@ contract RelayActor { } vm.stopPrank(); - bytes32 hash = _hashRelayMessage(nonce, _value, relayMinGasLimit, _message); - unexpectedMessageStatus = - unexpectedMessageStatus || xdm.successfulMessages(hash) == doFail || xdm.failedMessages(hash) != doFail; + unexpectedMessageStatus = unexpectedMessageStatus || xdm.successfulMessages(relayMessageHash) == doFail + || xdm.failedMessages(relayMessageHash) != doFail; } } contract XDM_MinGasLimits is CommonTest { RelayActor actor; - function init(bool doFail) public virtual { + function init(bool doFail) internal { super.setUp(); actor = new RelayActor(optimismPortal2, l1CrossDomainMessenger, vm, doFail); From 6edaaf32233f23ff5d1eb950348560c1ab55778a Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:19:03 -0400 Subject: [PATCH 016/135] Refactor CrossDomainMessenger.t.sol: update constructor to accept address instead of IOptimismPortal2, streamline message relay logic, and improve status mismatch handling. --- test/invariants/CrossDomainMessenger.t.sol | 58 +++++++--------------- 1 file changed, 19 insertions(+), 39 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 5097dbd0..197f753d 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -2,11 +2,9 @@ pragma solidity 0.8.15; import { Vm } from "lib/forge-std/src/Vm.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import { Constants } from "src/libraries/Constants.sol"; import { Encoding } from "src/libraries/Encoding.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; @@ -17,12 +15,12 @@ contract RelayActor { bool public reverted; bool public unexpectedMessageStatus; - IOptimismPortal2 internal immutable op; + address internal immutable op; IL1CrossDomainMessenger internal immutable xdm; Vm internal immutable vm; bool internal immutable doFail; - constructor(IOptimismPortal2 _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) { + constructor(address _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) { op = _op; xdm = _xdm; vm = _vm; @@ -31,27 +29,7 @@ contract RelayActor { // Set op.l2Sender() once to the L2 Cross Domain Messenger. Nothing in the fuzzed // surface modifies this slot, so we don't need to re-write it on every relay. uint256 senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot; - vm.store(address(_op), bytes32(senderSlotIndex), bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER))); - } - - function _hashRelayMessage( - uint256 _nonce, - uint256 _value, - uint256 _minGasLimit, - bytes memory _message - ) - internal - pure - returns (bytes32) - { - return Hashing.hashCrossDomainMessageV1({ - _nonce: _nonce, - _sender: Predeploys.L2_CROSS_DOMAIN_MESSENGER, - _target: IDENTITY_PRECOMPILE, - _value: _value, - _gasLimit: _minGasLimit, - _data: _message - }); + vm.store(_op, bytes32(senderSlotIndex), bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER))); } /// @notice Relays a fuzzed message to the `L1CrossDomainMessenger`. @@ -77,13 +55,20 @@ contract RelayActor { // relayed, so the v1 hash is what we track. uint256 nonce = Encoding.encodeVersionedNonce({ _nonce: 0, _version: _version }); - bytes32 relayMessageHash = _hashRelayMessage(nonce, _value, relayMinGasLimit, _message); + bytes32 relayMessageHash = Hashing.hashCrossDomainMessageV1({ + _nonce: nonce, + _sender: Predeploys.L2_CROSS_DOMAIN_MESSENGER, + _target: IDENTITY_PRECOMPILE, + _value: _value, + _gasLimit: relayMinGasLimit, + _data: _message + }); vm.assume(!xdm.successfulMessages(relayMessageHash) && !xdm.failedMessages(relayMessageHash)); - vm.startPrank(address(op)); if (!doFail) { vm.expectCallMinGas(IDENTITY_PRECOMPILE, _value, minGasLimit, _message); } + vm.prank(op); try xdm.relayMessage{ gas: gas, value: _value }( nonce, Predeploys.L2_CROSS_DOMAIN_MESSENGER, IDENTITY_PRECOMPILE, _value, relayMinGasLimit, _message ) { } @@ -92,10 +77,12 @@ contract RelayActor { // by flipping a flag the invariant asserts on. reverted = true; } - vm.stopPrank(); - unexpectedMessageStatus = unexpectedMessageStatus || xdm.successfulMessages(relayMessageHash) == doFail - || xdm.failedMessages(relayMessageHash) != doFail; + bool statusMismatch = + xdm.successfulMessages(relayMessageHash) == doFail || xdm.failedMessages(relayMessageHash) != doFail; + if (statusMismatch) { + unexpectedMessageStatus = true; + } } } @@ -105,21 +92,14 @@ contract XDM_MinGasLimits is CommonTest { function init(bool doFail) internal { super.setUp(); - actor = new RelayActor(optimismPortal2, l1CrossDomainMessenger, vm, doFail); + actor = new RelayActor(address(optimismPortal2), l1CrossDomainMessenger, vm, doFail); // Give the portal some ether to send to `relayMessage`. vm.deal(address(optimismPortal2), type(uint128).max); + targetSender(address(this)); targetContract(address(actor)); - excludeSender(Constants.ESTIMATION_ADDRESS); - - // Don't allow the predeploys to be the senders. - uint160 prefix = uint160(0x420) << 148; - for (uint256 i = 0; i < Predeploys.PREDEPLOY_COUNT; i++) { - excludeContract(address(prefix | uint160(i))); - } - bytes4[] memory selectors = new bytes4[](1); selectors[0] = actor.relay.selector; targetSelector(FuzzSelector({ addr: address(actor), selectors: selectors })); From f696026ba4567780d23bd117063672668245c9ed Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:25:10 -0400 Subject: [PATCH 017/135] Enhance CrossDomainMessenger.t.sol: add comments for clarity, improve status mismatch handling by consolidating logic for unexpectedMessageStatus. --- test/invariants/CrossDomainMessenger.t.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 197f753d..9f6e8b5c 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -12,6 +12,7 @@ import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; contract RelayActor { address internal constant IDENTITY_PRECOMPILE = address(0x04); + // Sticky flags make any bad relay visible to the invariant after the handler returns. bool public reverted; bool public unexpectedMessageStatus; @@ -32,10 +33,8 @@ contract RelayActor { vm.store(_op, bytes32(senderSlotIndex), bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER))); } - /// @notice Relays a fuzzed message to the `L1CrossDomainMessenger`. function relay(uint8 _version, uint8 _value, bytes memory _message) external { - // Vary value between 0 and 1 to exercise the with/without-value paths; the ID - // precompile accepts value. + // Exercise both nonce versions and with/without-value paths; the ID precompile accepts value. _version = _version % 2; _value = _value % 2; @@ -80,9 +79,7 @@ contract RelayActor { bool statusMismatch = xdm.successfulMessages(relayMessageHash) == doFail || xdm.failedMessages(relayMessageHash) != doFail; - if (statusMismatch) { - unexpectedMessageStatus = true; - } + unexpectedMessageStatus = unexpectedMessageStatus || statusMismatch; } } From 91522729b5045f71a1de4172dd24d644f18601ac Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:29:11 -0400 Subject: [PATCH 018/135] Refactor CrossDomainMessenger.t.sol: update comments for clarity, restore gas calculation logic, and remove redundant comments in relay function. --- test/invariants/CrossDomainMessenger.t.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 9f6e8b5c..1238099e 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -12,7 +12,7 @@ import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; contract RelayActor { address internal constant IDENTITY_PRECOMPILE = address(0x04); - // Sticky flags make any bad relay visible to the invariant after the handler returns. + // Invariant handlers ignore target-call reverts, so failures must persist after relay returns. bool public reverted; bool public unexpectedMessageStatus; @@ -34,7 +34,6 @@ contract RelayActor { } function relay(uint8 _version, uint8 _value, bytes memory _message) external { - // Exercise both nonce versions and with/without-value paths; the ID precompile accepts value. _version = _version % 2; _value = _value % 2; @@ -48,7 +47,6 @@ contract RelayActor { // OptimismPortal) leaves a razor-thin window between "enough to not OOG" and // "not enough for hasMinGas to pass". uint32 relayMinGasLimit = doFail ? type(uint32).max : minGasLimit; - uint256 gas = xdm.baseGas(_message, minGasLimit); // `relayMessage` always re-encodes as a v1 hash after checking the v0 hash hasn't been // relayed, so the v1 hash is what we track. @@ -64,6 +62,8 @@ contract RelayActor { }); vm.assume(!xdm.successfulMessages(relayMessageHash) && !xdm.failedMessages(relayMessageHash)); + uint256 gas = xdm.baseGas(_message, minGasLimit); + if (!doFail) { vm.expectCallMinGas(IDENTITY_PRECOMPILE, _value, minGasLimit, _message); } @@ -91,7 +91,6 @@ contract XDM_MinGasLimits is CommonTest { actor = new RelayActor(address(optimismPortal2), l1CrossDomainMessenger, vm, doFail); - // Give the portal some ether to send to `relayMessage`. vm.deal(address(optimismPortal2), type(uint128).max); targetSender(address(this)); From b8dab8ca292fdcb1745e2870cab0e8993e8a9cc7 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:31:21 -0400 Subject: [PATCH 019/135] Update CrossDomainMessenger.t.sol: modify RelayActor constructor to accept l2SenderSlot parameter, enhancing flexibility in slot management during message relay. --- test/invariants/CrossDomainMessenger.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index 1238099e..bce211b2 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -21,7 +21,7 @@ contract RelayActor { Vm internal immutable vm; bool internal immutable doFail; - constructor(address _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) { + constructor(address _op, IL1CrossDomainMessenger _xdm, Vm _vm, bytes32 _l2SenderSlot, bool _doFail) { op = _op; xdm = _xdm; vm = _vm; @@ -29,8 +29,7 @@ contract RelayActor { // Set op.l2Sender() once to the L2 Cross Domain Messenger. Nothing in the fuzzed // surface modifies this slot, so we don't need to re-write it on every relay. - uint256 senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot; - vm.store(_op, bytes32(senderSlotIndex), bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER))); + vm.store(_op, _l2SenderSlot, bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER))); } function relay(uint8 _version, uint8 _value, bytes memory _message) external { @@ -89,7 +88,8 @@ contract XDM_MinGasLimits is CommonTest { function init(bool doFail) internal { super.setUp(); - actor = new RelayActor(address(optimismPortal2), l1CrossDomainMessenger, vm, doFail); + bytes32 l2SenderSlot = bytes32(ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot); + actor = new RelayActor(address(optimismPortal2), l1CrossDomainMessenger, vm, l2SenderSlot, doFail); vm.deal(address(optimismPortal2), type(uint128).max); From 76742f3d2ac85b64d9da328b99656a3ae1ec9d08 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:35:30 -0400 Subject: [PATCH 020/135] Refactor CrossDomainMessenger.t.sol: update RelayActor to include MAX_MESSAGE_SIZE constant, streamline error handling by replacing reverted and unexpectedMessageStatus flags with a single badRelayResult flag, and simplify constructor parameters. --- test/invariants/CrossDomainMessenger.t.sol | 28 ++++++++++------------ 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index bce211b2..a72f03f6 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -11,28 +11,26 @@ import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; contract RelayActor { address internal constant IDENTITY_PRECOMPILE = address(0x04); + uint256 internal constant MAX_MESSAGE_SIZE = 1000; // Invariant handlers ignore target-call reverts, so failures must persist after relay returns. - bool public reverted; - bool public unexpectedMessageStatus; + bool public badRelayResult; address internal immutable op; IL1CrossDomainMessenger internal immutable xdm; Vm internal immutable vm; bool internal immutable doFail; - constructor(address _op, IL1CrossDomainMessenger _xdm, Vm _vm, bytes32 _l2SenderSlot, bool _doFail) { + constructor(address _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) { op = _op; xdm = _xdm; vm = _vm; doFail = _doFail; - - // Set op.l2Sender() once to the L2 Cross Domain Messenger. Nothing in the fuzzed - // surface modifies this slot, so we don't need to re-write it on every relay. - vm.store(_op, _l2SenderSlot, bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER))); } function relay(uint8 _version, uint8 _value, bytes memory _message) external { + vm.assume(_message.length <= MAX_MESSAGE_SIZE); + _version = _version % 2; _value = _value % 2; @@ -73,12 +71,12 @@ contract RelayActor { catch { // Forge's invariant fuzzer ignores reverted target calls, so we surface the failure // by flipping a flag the invariant asserts on. - reverted = true; + badRelayResult = true; } - bool statusMismatch = - xdm.successfulMessages(relayMessageHash) == doFail || xdm.failedMessages(relayMessageHash) != doFail; - unexpectedMessageStatus = unexpectedMessageStatus || statusMismatch; + if (xdm.successfulMessages(relayMessageHash) == doFail || xdm.failedMessages(relayMessageHash) != doFail) { + badRelayResult = true; + } } } @@ -89,11 +87,11 @@ contract XDM_MinGasLimits is CommonTest { super.setUp(); bytes32 l2SenderSlot = bytes32(ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot); - actor = new RelayActor(address(optimismPortal2), l1CrossDomainMessenger, vm, l2SenderSlot, doFail); + vm.store(address(optimismPortal2), l2SenderSlot, bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER))); + actor = new RelayActor(address(optimismPortal2), l1CrossDomainMessenger, vm, doFail); vm.deal(address(optimismPortal2), type(uint128).max); - targetSender(address(this)); targetContract(address(actor)); bytes4[] memory selectors = new bytes4[](1); @@ -101,10 +99,8 @@ contract XDM_MinGasLimits is CommonTest { targetSelector(FuzzSelector({ addr: address(actor), selectors: selectors })); } - /// @dev The actor records any relay that reverts or lands in the wrong message-status mapping. function _assertRelayResults() internal view { - assertFalse(actor.unexpectedMessageStatus()); - assertFalse(actor.reverted()); + assertFalse(actor.badRelayResult()); } } From 6dca3bfbe7f5df41e36a6862d9a3c08d3da038b8 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:38:20 -0400 Subject: [PATCH 021/135] Refactor CrossDomainMessenger.t.sol: rename doFail to shouldFail for clarity, update constructor and relay logic to improve readability, and enhance slot management in the init function. --- test/invariants/CrossDomainMessenger.t.sol | 32 +++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index a72f03f6..e5f04f0a 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -7,7 +7,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Encoding } from "src/libraries/Encoding.sol"; import { Hashing } from "src/libraries/Hashing.sol"; -import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; +import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; contract RelayActor { address internal constant IDENTITY_PRECOMPILE = address(0x04); @@ -19,13 +19,13 @@ contract RelayActor { address internal immutable op; IL1CrossDomainMessenger internal immutable xdm; Vm internal immutable vm; - bool internal immutable doFail; + bool internal immutable shouldFail; - constructor(address _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _doFail) { + constructor(address _op, IL1CrossDomainMessenger _xdm, Vm _vm, bool _shouldFail) { op = _op; xdm = _xdm; vm = _vm; - doFail = _doFail; + shouldFail = _shouldFail; } function relay(uint8 _version, uint8 _value, bytes memory _message) external { @@ -43,7 +43,7 @@ contract RelayActor { // because the proxy-to-proxy call overhead (SystemConfig → SuperchainConfig, // OptimismPortal) leaves a razor-thin window between "enough to not OOG" and // "not enough for hasMinGas to pass". - uint32 relayMinGasLimit = doFail ? type(uint32).max : minGasLimit; + uint32 relayMinGasLimit = shouldFail ? type(uint32).max : minGasLimit; // `relayMessage` always re-encodes as a v1 hash after checking the v0 hash hasn't been // relayed, so the v1 hash is what we track. @@ -61,7 +61,7 @@ contract RelayActor { uint256 gas = xdm.baseGas(_message, minGasLimit); - if (!doFail) { + if (!shouldFail) { vm.expectCallMinGas(IDENTITY_PRECOMPILE, _value, minGasLimit, _message); } vm.prank(op); @@ -74,7 +74,9 @@ contract RelayActor { badRelayResult = true; } - if (xdm.successfulMessages(relayMessageHash) == doFail || xdm.failedMessages(relayMessageHash) != doFail) { + bool relaySucceeded = xdm.successfulMessages(relayMessageHash); + bool relayFailed = xdm.failedMessages(relayMessageHash); + if (relaySucceeded == shouldFail || relayFailed != shouldFail) { badRelayResult = true; } } @@ -83,12 +85,16 @@ contract RelayActor { contract XDM_MinGasLimits is CommonTest { RelayActor actor; - function init(bool doFail) internal { + function _init(bool _shouldFail) internal { super.setUp(); - bytes32 l2SenderSlot = bytes32(ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot); - vm.store(address(optimismPortal2), l2SenderSlot, bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER))); - actor = new RelayActor(address(optimismPortal2), l1CrossDomainMessenger, vm, doFail); + StorageSlot memory l2SenderSlot = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender"); + vm.store( + address(optimismPortal2), + bytes32(l2SenderSlot.slot), + bytes32(abi.encode(Predeploys.L2_CROSS_DOMAIN_MESSENGER)) + ); + actor = new RelayActor(address(optimismPortal2), l1CrossDomainMessenger, vm, _shouldFail); vm.deal(address(optimismPortal2), type(uint128).max); @@ -106,7 +112,7 @@ contract XDM_MinGasLimits is CommonTest { contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits { function setUp() public override { - super.init(false); + super._init(false); } /// @custom:invariant `relayMessage` should succeed when the outer call has base gas and the @@ -118,7 +124,7 @@ contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits { contract XDM_MinGasLimits_Reverts is XDM_MinGasLimits { function setUp() public override { - super.init(true); + super._init(true); } /// @custom:invariant `relayMessage` should mark the message failed when the inner minimum gas From 9e958e89a3ecef34c1b0cdd942ceae099d91606b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:41:23 -0400 Subject: [PATCH 022/135] Refactor CrossDomainMessenger.t.sol: replace internal _assertRelayResults function with direct assertion in invariant tests for improved clarity and simplicity. --- test/invariants/CrossDomainMessenger.t.sol | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/invariants/CrossDomainMessenger.t.sol b/test/invariants/CrossDomainMessenger.t.sol index e5f04f0a..ec1ca218 100644 --- a/test/invariants/CrossDomainMessenger.t.sol +++ b/test/invariants/CrossDomainMessenger.t.sol @@ -104,10 +104,6 @@ contract XDM_MinGasLimits is CommonTest { selectors[0] = actor.relay.selector; targetSelector(FuzzSelector({ addr: address(actor), selectors: selectors })); } - - function _assertRelayResults() internal view { - assertFalse(actor.badRelayResult()); - } } contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits { @@ -118,7 +114,7 @@ contract XDM_MinGasLimits_Succeeds is XDM_MinGasLimits { /// @custom:invariant `relayMessage` should succeed when the outer call has base gas and the /// target can receive the inner minimum gas limit. function invariant_relayMessage_forwardsMinGas_succeeds() external view { - _assertRelayResults(); + assertFalse(actor.badRelayResult()); } } @@ -130,6 +126,6 @@ contract XDM_MinGasLimits_Reverts is XDM_MinGasLimits { /// @custom:invariant `relayMessage` should mark the message failed when the inner minimum gas /// limit is too large to forward to the target. function invariant_relayMessage_insufficientMinGas_fails() external view { - _assertRelayResults(); + assertFalse(actor.badRelayResult()); } } From b0b9947d5d258b76c24559aedd16fb5e93f90dac Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 19:49:39 -0400 Subject: [PATCH 023/135] Refactor SafeCall.t.sol: consolidate invariant tests into a base contract, streamline setup logic, and enhance clarity in gas handling for safe calls. --- test/invariants/SafeCall.t.sol | 93 ++++++++++++++-------------------- 1 file changed, 39 insertions(+), 54 deletions(-) diff --git a/test/invariants/SafeCall.t.sol b/test/invariants/SafeCall.t.sol index ea255d1b..98e8a904 100644 --- a/test/invariants/SafeCall.t.sol +++ b/test/invariants/SafeCall.t.sol @@ -6,52 +6,39 @@ import { Test } from "lib/forge-std/src/Test.sol"; import { Vm } from "lib/forge-std/src/Vm.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; -contract SafeCall_Succeeds_Invariants is Test { +abstract contract SafeCall_Invariants is Test { SafeCaller_Actor actor; - function setUp() public { - // Create a new safe caller actor. - actor = new SafeCaller_Actor(vm, false); - - // Set the caller to this contract + function _init(bool _shouldFail) internal { + actor = new SafeCaller_Actor(vm, _shouldFail); targetSender(address(this)); - - // Target the safe caller actor. targetContract(address(actor)); - - // Give the actor some ETH to work with vm.deal(address(actor), type(uint128).max); } + function performSafeCallMinGas(address to, uint64 minGas) external payable { + SafeCall.callWithMinGas(to, minGas, msg.value, hex""); + } +} + +contract SafeCall_Succeeds_Invariants is SafeCall_Invariants { + function setUp() public { + _init(false); + } + /// @custom:invariant If `callWithMinGas` performs a call, then it must always /// provide at least the specified minimum gas limit to the subcontext. /// /// If the check for remaining gas in `SafeCall.callWithMinGas` passes, the /// subcontext of the call below it must be provided at least `minGas` gas. function invariant_callWithMinGas_alwaysForwardsMinGas_succeeds() public view { - assertEq(actor.numCalls(), 0, "no failed calls allowed"); - } - - function performSafeCallMinGas(address to, uint64 minGas) external payable { - SafeCall.callWithMinGas(to, minGas, msg.value, hex""); + assertFalse(actor.badCallResult()); } } -contract SafeCall_Fails_Invariants is Test { - SafeCaller_Actor actor; - +contract SafeCall_Fails_Invariants is SafeCall_Invariants { function setUp() public { - // Create a new safe caller actor. - actor = new SafeCaller_Actor(vm, true); - - // Set the caller to this contract - targetSender(address(this)); - - // Target the safe caller actor. - targetContract(address(actor)); - - // Give the actor some ETH to work with - vm.deal(address(actor), type(uint128).max); + _init(true); } /// @custom:invariant `callWithMinGas` reverts if there is not enough gas to pass @@ -61,49 +48,47 @@ contract SafeCall_Fails_Invariants is Test { /// `callWithMinGas` can provide the specified minimum gas limit /// to the subcontext of the call, then `callWithMinGas` must revert. function invariant_callWithMinGas_neverForwardsMinGas_reverts() public view { - assertEq(actor.numCalls(), 0, "no successful calls allowed"); - } - - function performSafeCallMinGas(address to, uint64 minGas) external payable { - SafeCall.callWithMinGas(to, minGas, msg.value, hex""); + assertFalse(actor.badCallResult()); } } contract SafeCaller_Actor is StdUtils { - bool internal immutable FAILS; + address internal constant CONSOLE = 0x000000000000000000636F6e736F6c652e6c6f67; + uint64 internal constant MIN_MIN_GAS = 2_500; + uint64 internal constant MAX_MIN_GAS = uint64(type(uint48).max); + uint64 internal constant CALL_WITH_MIN_GAS_OVERHEAD = 40_000; + uint64 internal constant SAFE_CALL_BUFFER = 1_000; + + Vm internal immutable vm; + bool internal immutable shouldFail; - Vm internal vm; - uint256 public numCalls; + bool public badCallResult; - constructor(Vm _vm, bool _fails) { + constructor(Vm _vm, bool _shouldFail) { vm = _vm; - FAILS = _fails; + shouldFail = _shouldFail; } function performSafeCallMinGas(uint64 gas, uint64 minGas, address to, uint8 value) external { // Only send to EOAs - we exclude the console as it has no code but reverts when called // with a selector that doesn't exist due to the foundry hook. - vm.assume(to.code.length == 0 && to != 0x000000000000000000636F6e736F6c652e6c6f67); + vm.assume(to.code.length == 0 && to != CONSOLE); - // Bound the minimum gas amount to [2500, type(uint48).max] - minGas = uint64(bound(minGas, 2500, type(uint48).max)); - if (FAILS) { - // Bound the gas passed to [minGas, ((minGas * 64) / 63)] - gas = uint64(bound(gas, minGas, (minGas * 64) / 63)); + minGas = uint64(bound(minGas, MIN_MIN_GAS, MAX_MIN_GAS)); + uint64 minCallGas = (minGas * 64) / 63; + if (shouldFail) { + gas = uint64(bound(gas, minGas, minCallGas)); } else { - // Bound the gas passed to - // [((minGas * 64) / 63) + 40_000 + 1000, type(uint64).max] - // The extra 1000 gas is to account for the gas used by the `SafeCall.call` call - // itself. - gas = uint64(bound(gas, ((minGas * 64) / 63) + 40_000 + 1000, type(uint64).max)); + gas = uint64(bound(gas, minCallGas + CALL_WITH_MIN_GAS_OVERHEAD + SAFE_CALL_BUFFER, type(uint64).max)); + vm.expectCallMinGas(to, value, minGas, hex""); } - vm.expectCallMinGas(to, value, minGas, hex""); bool success = SafeCall.call( - msg.sender, gas, value, abi.encodeCall(SafeCall_Succeeds_Invariants.performSafeCallMinGas, (to, minGas)) + msg.sender, gas, value, abi.encodeCall(SafeCall_Invariants.performSafeCallMinGas, (to, minGas)) ); - if (success && FAILS) numCalls++; - if (!FAILS && !success) numCalls++; + if (success == shouldFail) { + badCallResult = true; + } } } From 8dda521290e385564a27c8faa04bc4b485b29884 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:02:20 -0400 Subject: [PATCH 024/135] Enhance SafeCall.t.sol: add target selector for performSafeCallMinGas to improve invariant testing and clarify gas handling in safe calls. --- test/invariants/SafeCall.t.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/invariants/SafeCall.t.sol b/test/invariants/SafeCall.t.sol index 98e8a904..f6f48432 100644 --- a/test/invariants/SafeCall.t.sol +++ b/test/invariants/SafeCall.t.sol @@ -13,6 +13,11 @@ abstract contract SafeCall_Invariants is Test { actor = new SafeCaller_Actor(vm, _shouldFail); targetSender(address(this)); targetContract(address(actor)); + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = actor.performSafeCallMinGas.selector; + targetSelector(FuzzSelector({ addr: address(actor), selectors: selectors })); + vm.deal(address(actor), type(uint128).max); } @@ -62,6 +67,7 @@ contract SafeCaller_Actor is StdUtils { Vm internal immutable vm; bool internal immutable shouldFail; + // Invariant handlers ignore target-call reverts, so failures must persist after the call. bool public badCallResult; constructor(Vm _vm, bool _shouldFail) { From ff84f3c875a1788f6ac906a65f3335c5ac684caa Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:07:37 -0400 Subject: [PATCH 025/135] Refactor SafeCall.t.sol: simplify SafeCaller_Actor constructor by removing Vm dependency, update invariant comments for clarity, and streamline gas handling logic in invariant tests. --- test/invariants/SafeCall.t.sol | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/test/invariants/SafeCall.t.sol b/test/invariants/SafeCall.t.sol index f6f48432..8de9c55b 100644 --- a/test/invariants/SafeCall.t.sol +++ b/test/invariants/SafeCall.t.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { StdUtils } from "lib/forge-std/src/StdUtils.sol"; import { Test } from "lib/forge-std/src/Test.sol"; -import { Vm } from "lib/forge-std/src/Vm.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; abstract contract SafeCall_Invariants is Test { SafeCaller_Actor actor; function _init(bool _shouldFail) internal { - actor = new SafeCaller_Actor(vm, _shouldFail); + actor = new SafeCaller_Actor(_shouldFail); targetSender(address(this)); targetContract(address(actor)); @@ -31,11 +29,7 @@ contract SafeCall_Succeeds_Invariants is SafeCall_Invariants { _init(false); } - /// @custom:invariant If `callWithMinGas` performs a call, then it must always - /// provide at least the specified minimum gas limit to the subcontext. - /// - /// If the check for remaining gas in `SafeCall.callWithMinGas` passes, the - /// subcontext of the call below it must be provided at least `minGas` gas. + /// @custom:invariant Successful calls always forward at least the requested minimum gas. function invariant_callWithMinGas_alwaysForwardsMinGas_succeeds() public view { assertFalse(actor.badCallResult()); } @@ -46,39 +40,29 @@ contract SafeCall_Fails_Invariants is SafeCall_Invariants { _init(true); } - /// @custom:invariant `callWithMinGas` reverts if there is not enough gas to pass - /// to the subcontext. - /// - /// If there is not enough gas in the callframe to ensure that - /// `callWithMinGas` can provide the specified minimum gas limit - /// to the subcontext of the call, then `callWithMinGas` must revert. + /// @custom:invariant Calls revert when the frame cannot provide the requested minimum gas. function invariant_callWithMinGas_neverForwardsMinGas_reverts() public view { assertFalse(actor.badCallResult()); } } -contract SafeCaller_Actor is StdUtils { - address internal constant CONSOLE = 0x000000000000000000636F6e736F6c652e6c6f67; +contract SafeCaller_Actor is Test { uint64 internal constant MIN_MIN_GAS = 2_500; uint64 internal constant MAX_MIN_GAS = uint64(type(uint48).max); uint64 internal constant CALL_WITH_MIN_GAS_OVERHEAD = 40_000; uint64 internal constant SAFE_CALL_BUFFER = 1_000; - Vm internal immutable vm; bool internal immutable shouldFail; // Invariant handlers ignore target-call reverts, so failures must persist after the call. bool public badCallResult; - constructor(Vm _vm, bool _shouldFail) { - vm = _vm; + constructor(bool _shouldFail) { shouldFail = _shouldFail; } function performSafeCallMinGas(uint64 gas, uint64 minGas, address to, uint8 value) external { - // Only send to EOAs - we exclude the console as it has no code but reverts when called - // with a selector that doesn't exist due to the foundry hook. - vm.assume(to.code.length == 0 && to != CONSOLE); + assumeUnusedAddress(to); minGas = uint64(bound(minGas, MIN_MIN_GAS, MAX_MIN_GAS)); uint64 minCallGas = (minGas * 64) / 63; From 1d540c1227fa7515a599f377e189c08e13c07b1c Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:12:54 -0400 Subject: [PATCH 026/135] Refactor SafeCall.t.sol: update SafeCaller_Actor constructor to accept harness address and failure expectation, rename performSafeCallMinGas to callWithMinGas for clarity, and improve gas handling logic in safe calls. --- test/invariants/SafeCall.t.sol | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/test/invariants/SafeCall.t.sol b/test/invariants/SafeCall.t.sol index 8de9c55b..05e86309 100644 --- a/test/invariants/SafeCall.t.sol +++ b/test/invariants/SafeCall.t.sol @@ -7,9 +7,8 @@ import { SafeCall } from "src/libraries/SafeCall.sol"; abstract contract SafeCall_Invariants is Test { SafeCaller_Actor actor; - function _init(bool _shouldFail) internal { - actor = new SafeCaller_Actor(_shouldFail); - targetSender(address(this)); + function _init(bool _expectCallToFail) internal { + actor = new SafeCaller_Actor(address(this), _expectCallToFail); targetContract(address(actor)); bytes4[] memory selectors = new bytes4[](1); @@ -19,7 +18,7 @@ abstract contract SafeCall_Invariants is Test { vm.deal(address(actor), type(uint128).max); } - function performSafeCallMinGas(address to, uint64 minGas) external payable { + function callWithMinGas(address to, uint64 minGas) external payable { SafeCall.callWithMinGas(to, minGas, msg.value, hex""); } } @@ -48,17 +47,18 @@ contract SafeCall_Fails_Invariants is SafeCall_Invariants { contract SafeCaller_Actor is Test { uint64 internal constant MIN_MIN_GAS = 2_500; - uint64 internal constant MAX_MIN_GAS = uint64(type(uint48).max); - uint64 internal constant CALL_WITH_MIN_GAS_OVERHEAD = 40_000; - uint64 internal constant SAFE_CALL_BUFFER = 1_000; + uint64 internal constant MAX_MIN_GAS = type(uint64).max / 64; + uint64 internal constant CALL_WITH_MIN_GAS_BUFFER = 40_000 + 1_000; - bool internal immutable shouldFail; + address internal immutable safeCallHarness; + bool internal immutable expectCallToFail; // Invariant handlers ignore target-call reverts, so failures must persist after the call. bool public badCallResult; - constructor(bool _shouldFail) { - shouldFail = _shouldFail; + constructor(address _safeCallHarness, bool _expectCallToFail) { + safeCallHarness = _safeCallHarness; + expectCallToFail = _expectCallToFail; } function performSafeCallMinGas(uint64 gas, uint64 minGas, address to, uint8 value) external { @@ -66,18 +66,19 @@ contract SafeCaller_Actor is Test { minGas = uint64(bound(minGas, MIN_MIN_GAS, MAX_MIN_GAS)); uint64 minCallGas = (minGas * 64) / 63; - if (shouldFail) { + if (expectCallToFail) { gas = uint64(bound(gas, minGas, minCallGas)); } else { - gas = uint64(bound(gas, minCallGas + CALL_WITH_MIN_GAS_OVERHEAD + SAFE_CALL_BUFFER, type(uint64).max)); + gas = uint64(bound(gas, minCallGas + CALL_WITH_MIN_GAS_BUFFER, type(uint64).max)); vm.expectCallMinGas(to, value, minGas, hex""); } bool success = SafeCall.call( - msg.sender, gas, value, abi.encodeCall(SafeCall_Invariants.performSafeCallMinGas, (to, minGas)) + safeCallHarness, gas, value, abi.encodeCall(SafeCall_Invariants.callWithMinGas, (to, minGas)) ); - if (success == shouldFail) { + bool expectedSuccess = !expectCallToFail; + if (success != expectedSuccess) { badCallResult = true; } } From a4606859d6ed655cb445def751ce36e519955308 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:29:18 -0400 Subject: [PATCH 027/135] improve SafeCall.t.sol comments --- test/invariants/SafeCall.t.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/invariants/SafeCall.t.sol b/test/invariants/SafeCall.t.sol index 05e86309..7a522be7 100644 --- a/test/invariants/SafeCall.t.sol +++ b/test/invariants/SafeCall.t.sol @@ -29,7 +29,7 @@ contract SafeCall_Succeeds_Invariants is SafeCall_Invariants { } /// @custom:invariant Successful calls always forward at least the requested minimum gas. - function invariant_callWithMinGas_alwaysForwardsMinGas_succeeds() public view { + function invariant_callWithMinGas_alwaysForwardsMinGas_succeeds() external view { assertFalse(actor.badCallResult()); } } @@ -40,14 +40,18 @@ contract SafeCall_Fails_Invariants is SafeCall_Invariants { } /// @custom:invariant Calls revert when the frame cannot provide the requested minimum gas. - function invariant_callWithMinGas_neverForwardsMinGas_reverts() public view { + function invariant_callWithMinGas_neverForwardsMinGas_reverts() external view { assertFalse(actor.badCallResult()); } } contract SafeCaller_Actor is Test { uint64 internal constant MIN_MIN_GAS = 2_500; + + // Keep the EIP-150 min-gas calculation inside uint64 bounds. uint64 internal constant MAX_MIN_GAS = type(uint64).max / 64; + + // `callWithMinGas` reserves 40k call overhead; the extra 1k covers the harness frame. uint64 internal constant CALL_WITH_MIN_GAS_BUFFER = 40_000 + 1_000; address internal immutable safeCallHarness; From 405adb7437a2de726b0f4df692a32915f57b1596 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:34:51 -0400 Subject: [PATCH 028/135] Refactor AggregateVerifierTest.t.sol: streamline test initialization by replacing repetitive assertions with helper functions, improving readability and maintainability of test cases. --- test/L1/proofs/AggregateVerifier.t.sol | 309 +++++++++++-------------- 1 file changed, 138 insertions(+), 171 deletions(-) diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index 5b0de174..23c9b892 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -16,64 +16,29 @@ import { BaseTest } from "./BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { function testInitializeWithTEEProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + Claim rootClaim = _advanceL2BlockAndClaim(); bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof ); - assertEq(game.wasRespectedGameTypeWhenCreated(), true); - assertEq(address(game.teeProver()), TEE_PROVER); - assertEq(address(game.zkProver()), address(0)); - assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); - assertEq(game.l2SequenceNumber(), currentL2BlockNumber); - assertEq(game.rootClaim().raw(), rootClaim.raw()); - assertEq(game.parentAddress(), address(anchorStateRegistry)); - assertEq(game.gameType().raw(), AGGREGATE_VERIFIER_GAME_TYPE.raw()); - assertEq(game.gameCreator(), TEE_PROVER); - assertEq( - game.extraData(), - abi.encodePacked(currentL2BlockNumber, address(anchorStateRegistry), game.intermediateOutputRoots()) - ); - assertEq(game.bondRecipient(), TEE_PROVER); - assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); - assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); - assertEq(game.proofCount(), 1); + _assertInitializedGame(game, rootClaim, TEE_PROVER, TEE_PROVER, address(0)); } function testInitializeWithZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + Claim rootClaim = _advanceL2BlockAndClaim(); bytes memory proof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); AggregateVerifier game = _createAggregateVerifierGame( ZK_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof ); - assertEq(game.wasRespectedGameTypeWhenCreated(), true); - assertEq(address(game.teeProver()), address(0)); - assertEq(address(game.zkProver()), ZK_PROVER); - assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); - assertEq(game.l2SequenceNumber(), currentL2BlockNumber); - assertEq(game.rootClaim().raw(), rootClaim.raw()); - assertEq(game.parentAddress(), address(anchorStateRegistry)); - assertEq(game.gameType().raw(), AGGREGATE_VERIFIER_GAME_TYPE.raw()); - assertEq(game.gameCreator(), ZK_PROVER); - assertEq( - game.extraData(), - abi.encodePacked(currentL2BlockNumber, address(anchorStateRegistry), game.intermediateOutputRoots()) - ); - assertEq(game.bondRecipient(), ZK_PROVER); - assertEq(anchorStateRegistry.isGameProper(IDisputeGame(address(game))), true); - assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); - assertEq(game.proofCount(), 1); + _assertInitializedGame(game, rootClaim, ZK_PROVER, address(0), ZK_PROVER); } function testInitializeFailsIfInvalidCallDataSize() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + Claim rootClaim = _advanceL2BlockAndClaim(); vm.deal(TEE_PROVER, INIT_BOND); bytes memory extraData = ""; @@ -85,72 +50,48 @@ contract AggregateVerifierTest is BaseTest { } function testUpdatingAnchorStateRegistryWithTEEProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + Claim rootClaim = _advanceL2BlockAndClaim(); bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof ); - // Cannot claim bond before game is over vm.expectRevert(GameNotResolved.selector); game.claimCredit(); - // Resolve after 7 days - vm.warp(block.timestamp + 7 days); + vm.warp(block.timestamp + _slowFinalizationDelay()); game.resolve(); - assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + _assertStatus(game, GameStatus.DEFENDER_WINS); - // Unlock and reclaim bond after resolving - uint256 balanceBefore = game.gameCreator().balance; - game.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game.claimCredit(); - assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game)), 0); + _claimCreditAfterDelay(game); - // Update AnchorStateRegistry vm.warp(block.timestamp + 1); game.closeGame(); - (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(root.raw(), rootClaim.raw()); - assertEq(l2SequenceNumber, currentL2BlockNumber); + _assertAnchorRoot(rootClaim); } function testUpdatingAnchorStateRegistryWithZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + Claim rootClaim = _advanceL2BlockAndClaim(); bytes memory proof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); AggregateVerifier game = _createAggregateVerifierGame( ZK_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof ); - // Resolve after 7 days - vm.warp(block.timestamp + 7 days); + vm.warp(block.timestamp + _slowFinalizationDelay()); game.resolve(); - assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + _assertStatus(game, GameStatus.DEFENDER_WINS); - // Unlock and reclaim bond after delay - uint256 balanceBefore = game.gameCreator().balance; - game.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game.claimCredit(); - assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game)), 0); + _claimCreditAfterDelay(game); - // Update AnchorStateRegistry vm.warp(block.timestamp + 1); game.closeGame(); - (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(root.raw(), rootClaim.raw()); - assertEq(l2SequenceNumber, currentL2BlockNumber); + _assertAnchorRoot(rootClaim); } function testUpdatingAnchorStateRegistryWithBothProofs() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + Claim rootClaim = _advanceL2BlockAndClaim(); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); @@ -161,30 +102,19 @@ contract AggregateVerifierTest is BaseTest { _provideProof(game, ZK_PROVER, zkProof); assertEq(game.proofCount(), 2); - // Resolve after 1 day (FAST_FINALIZATION_DELAY with 2 proofs) - vm.warp(block.timestamp + 1 days); + vm.warp(block.timestamp + _fastFinalizationDelay()); game.resolve(); - assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + _assertStatus(game, GameStatus.DEFENDER_WINS); - // Update AnchorStateRegistry vm.warp(block.timestamp + 1); game.closeGame(); - (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(root.raw(), rootClaim.raw()); - assertEq(l2SequenceNumber, currentL2BlockNumber); + _assertAnchorRoot(rootClaim); - // Unlock and reclaim bond after delay - uint256 balanceBefore = game.gameCreator().balance; - game.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game.claimCredit(); - assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game)), 0); + _claimCreditAfterDelay(game); } function testProofCannotIncreaseExpectedResolution() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + Claim rootClaim = _advanceL2BlockAndClaim(); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); @@ -193,29 +123,24 @@ contract AggregateVerifierTest is BaseTest { ); Timestamp originalExpectedResolution = game.expectedResolution(); - assertEq(originalExpectedResolution.raw(), block.timestamp + 7 days); + assertEq(originalExpectedResolution.raw(), block.timestamp + _slowFinalizationDelay()); - vm.warp(block.timestamp + 7 days - 1); - // Cannot resolve yet + vm.warp(block.timestamp + _slowFinalizationDelay() - 1); vm.expectRevert(AggregateVerifier.GameNotOver.selector); game.resolve(); - // Provide ZK proof _provideProof(game, ZK_PROVER, zkProof); - // Proof should not have increased expected resolution Timestamp expectedResolution = game.expectedResolution(); assertEq(expectedResolution.raw(), originalExpectedResolution.raw()); - // Resolve after 1 second vm.warp(block.timestamp + 1); game.resolve(); - assertEq(uint8(game.status()), uint8(GameStatus.DEFENDER_WINS)); + _assertStatus(game, GameStatus.DEFENDER_WINS); } function testCannotCreateSameProposal() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + Claim rootClaim = _advanceL2BlockAndClaim(); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); @@ -223,12 +148,12 @@ contract AggregateVerifierTest is BaseTest { TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), teeProof ); - Hash uuid = factory.getGameUUID( + Hash gameId = factory.getGameUUID( AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, abi.encodePacked(currentL2BlockNumber, address(anchorStateRegistry), game.intermediateOutputRoots()) ); - vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.GameAlreadyExists.selector, uuid)); + vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.GameAlreadyExists.selector, gameId)); _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), zkProof); } @@ -262,14 +187,11 @@ contract AggregateVerifierTest is BaseTest { } function testVerifyFailsWithL1OriginInFuture() public { - currentL2BlockNumber += BLOCK_INTERVAL; - // Use a future block number + Claim rootClaim = _advanceL2BlockAndClaim(); uint256 l1OriginNumber = block.number + 1; - bytes32 l1OriginHash = bytes32(uint256(1)); // Fake hash - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes32 l1OriginHash = bytes32(uint256(1)); - bytes memory proofBytes = - abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); + bytes memory proofBytes = _teeProof(l1OriginHash, l1OriginNumber, rootClaim); vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number) @@ -280,18 +202,14 @@ contract AggregateVerifierTest is BaseTest { } function testVerifyFailsWithL1OriginTooOld() public { - currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = _advanceL2BlockAndClaim(); - // Roll forward many blocks to make old blocks unavailable vm.roll(block.number + 300); - // Use a block number that's too old (outside both blockhash window and EIP-2935 window) uint256 l1OriginNumber = 1; - bytes32 l1OriginHash = bytes32(uint256(1)); // Fake hash - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes32 l1OriginHash = bytes32(uint256(1)); - bytes memory proofBytes = - abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); + bytes memory proofBytes = _teeProof(l1OriginHash, l1OriginNumber, rootClaim); vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number)); _createAggregateVerifierGame( @@ -300,13 +218,11 @@ contract AggregateVerifierTest is BaseTest { } function testVerifyFailsWithL1OriginHashMismatch() public { - currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = _advanceL2BlockAndClaim(); uint256 l1OriginNumber = block.number - 1; - bytes32 wrongHash = bytes32(uint256(0xdeadbeef)); // Wrong hash - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + bytes32 wrongHash = bytes32(uint256(0xdeadbeef)); - bytes memory proofBytes = - abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), wrongHash, l1OriginNumber, rootClaim.raw()); + bytes memory proofBytes = _teeProof(wrongHash, l1OriginNumber, rootClaim); bytes32 actualHash = blockhash(l1OriginNumber); vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginHashMismatch.selector, wrongHash, actualHash)); @@ -316,18 +232,14 @@ contract AggregateVerifierTest is BaseTest { } function testVerifyWithBlockhashWindow() public { - currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = _advanceL2BlockAndClaim(); - // Test verification within the 256 block window vm.roll(block.number + 100); - // Use a block that's within the 256 block window uint256 l1OriginNumber = block.number - 50; bytes32 l1OriginHash = blockhash(l1OriginNumber); - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - bytes memory proofBytes = - abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); + bytes memory proofBytes = _teeProof(l1OriginHash, l1OriginNumber, rootClaim); _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes @@ -335,25 +247,16 @@ contract AggregateVerifierTest is BaseTest { } function testVerifyWithEIP2935Window() public { - currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = _advanceL2BlockAndClaim(); - // Roll forward past the 256 blockhash window vm.roll(block.number + 300); - // Use a block that's outside blockhash window but within EIP-2935 window - uint256 l1OriginNumber = block.number - 260; // 260 > 256, so blockhash() returns 0 + uint256 l1OriginNumber = block.number - 260; bytes32 expectedHash = keccak256(abi.encodePacked("mock-blockhash", l1OriginNumber)); - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); - // Mock the EIP-2935 contract response - vm.mockCall( - 0x0000F90827F1C53a10cb7A02335B175320002935, // EIP-2935 contract address - abi.encode(l1OriginNumber), // raw 32-byte calldata - abi.encode(expectedHash) // returns the blockhash - ); + vm.mockCall(_eip2935Contract(), abi.encode(l1OriginNumber), abi.encode(expectedHash)); - bytes memory proofBytes = - abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), expectedHash, l1OriginNumber, rootClaim.raw()); + bytes memory proofBytes = _teeProof(expectedHash, l1OriginNumber, rootClaim); _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes @@ -361,43 +264,91 @@ contract AggregateVerifierTest is BaseTest { } function testDeployWithInvalidBlockIntervals() public { - // Case 1: BLOCK_INTERVAL is 0 vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 0, INTERMEDIATE_BLOCK_INTERVAL) ); - new AggregateVerifier( - AGGREGATE_VERIFIER_GAME_TYPE, - IAnchorStateRegistry(address(anchorStateRegistry)), - IDelayedWETH(payable(address(delayedWETH))), - IVerifier(address(teeVerifier)), - IVerifier(address(zkVerifier)), - TEE_IMAGE_HASH, - AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), - CONFIG_HASH, - L2_CHAIN_ID, - 0, - INTERMEDIATE_BLOCK_INTERVAL - ); + _deployAggregateVerifierWithIntervals(0, INTERMEDIATE_BLOCK_INTERVAL); - // Case 2: INTERMEDIATE_BLOCK_INTERVAL is 0 vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, BLOCK_INTERVAL, 0)); - new AggregateVerifier( - AGGREGATE_VERIFIER_GAME_TYPE, - IAnchorStateRegistry(address(anchorStateRegistry)), - IDelayedWETH(payable(address(delayedWETH))), - IVerifier(address(teeVerifier)), - IVerifier(address(zkVerifier)), - TEE_IMAGE_HASH, - AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), - CONFIG_HASH, - L2_CHAIN_ID, - BLOCK_INTERVAL, - 0 - ); + _deployAggregateVerifierWithIntervals(BLOCK_INTERVAL, 0); - // Case 3: BLOCK_INTERVAL is not divisible by INTERMEDIATE_BLOCK_INTERVAL vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 3, 2)); - new AggregateVerifier( + _deployAggregateVerifierWithIntervals(3, 2); + } + + function _advanceL2BlockAndClaim() private returns (Claim rootClaim) { + currentL2BlockNumber += BLOCK_INTERVAL; + return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); + } + + function _assertInitializedGame( + AggregateVerifier game, + Claim rootClaim, + address expectedCreator, + address expectedTeeProver, + address expectedZkProver + ) + private + view + { + assertTrue(game.wasRespectedGameTypeWhenCreated()); + assertEq(game.teeProver(), expectedTeeProver); + assertEq(game.zkProver(), expectedZkProver); + _assertStatus(game, GameStatus.IN_PROGRESS); + assertEq(game.l2SequenceNumber(), currentL2BlockNumber); + assertEq(game.rootClaim().raw(), rootClaim.raw()); + assertEq(game.parentAddress(), address(anchorStateRegistry)); + assertEq(game.gameType().raw(), AGGREGATE_VERIFIER_GAME_TYPE.raw()); + assertEq(game.gameCreator(), expectedCreator); + assertEq( + game.extraData(), + abi.encodePacked(currentL2BlockNumber, address(anchorStateRegistry), game.intermediateOutputRoots()) + ); + assertEq(game.bondRecipient(), expectedCreator); + assertTrue(anchorStateRegistry.isGameProper(IDisputeGame(address(game)))); + assertEq(delayedWETH.balanceOf(address(game)), INIT_BOND); + assertEq(game.proofCount(), 1); + } + + function _assertStatus(AggregateVerifier game, GameStatus expectedStatus) private view { + assertEq(uint8(game.status()), uint8(expectedStatus)); + } + + function _assertAnchorRoot(Claim rootClaim) private view { + (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), rootClaim.raw()); + assertEq(l2SequenceNumber, currentL2BlockNumber); + } + + function _claimCreditAfterDelay(AggregateVerifier game) private { + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); + } + + function _teeProof( + bytes32 l1OriginHash, + uint256 l1OriginNumber, + Claim rootClaim + ) + private + pure + returns (bytes memory) + { + return abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); + } + + function _deployAggregateVerifierWithIntervals( + uint256 blockInterval, + uint256 intermediateBlockInterval + ) + private + returns (AggregateVerifier) + { + return new AggregateVerifier( AGGREGATE_VERIFIER_GAME_TYPE, IAnchorStateRegistry(address(anchorStateRegistry)), IDelayedWETH(payable(address(delayedWETH))), @@ -407,8 +358,24 @@ contract AggregateVerifierTest is BaseTest { AggregateVerifier.ZkHashes(ZK_RANGE_HASH, ZK_AGGREGATE_HASH), CONFIG_HASH, L2_CHAIN_ID, - 3, - 2 + blockInterval, + intermediateBlockInterval ); } + + function _aggregateVerifierImpl() private view returns (AggregateVerifier) { + return AggregateVerifier(address(factory.gameImpls(AGGREGATE_VERIFIER_GAME_TYPE))); + } + + function _slowFinalizationDelay() private view returns (uint256) { + return _aggregateVerifierImpl().SLOW_FINALIZATION_DELAY(); + } + + function _fastFinalizationDelay() private view returns (uint256) { + return _aggregateVerifierImpl().FAST_FINALIZATION_DELAY(); + } + + function _eip2935Contract() private view returns (address) { + return _aggregateVerifierImpl().EIP2935_CONTRACT(); + } } From a7f34983828aa30f4751e077e9f12a3d1f53ef8e Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:38:37 -0400 Subject: [PATCH 029/135] Refactor AggregateVerifierTest.t.sol: streamline test cases by introducing helper functions for game initialization and assertion, enhancing code readability and maintainability. --- test/L1/proofs/AggregateVerifier.t.sol | 98 ++++++++++++++++---------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index 23c9b892..0bb3688f 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -15,26 +15,21 @@ import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; import { BaseTest } from "./BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { - function testInitializeWithTEEProof() public { - Claim rootClaim = _advanceL2BlockAndClaim(); - bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof - ); + AggregateVerifier private aggregateVerifierImpl; - _assertInitializedGame(game, rootClaim, TEE_PROVER, TEE_PROVER, address(0)); + function setUp() public override { + super.setUp(); + aggregateVerifierImpl = AggregateVerifier(address(factory.gameImpls(AGGREGATE_VERIFIER_GAME_TYPE))); } - function testInitializeWithZKProof() public { - Claim rootClaim = _advanceL2BlockAndClaim(); - bytes memory proof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - - AggregateVerifier game = _createAggregateVerifierGame( - ZK_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof + function testInitializeWithTEEProof() public { + _createAndAssertInitializedGame( + "tee-proof", AggregateVerifier.ProofType.TEE, TEE_PROVER, TEE_PROVER, address(0) ); + } - _assertInitializedGame(game, rootClaim, ZK_PROVER, address(0), ZK_PROVER); + function testInitializeWithZKProof() public { + _createAndAssertInitializedGame("zk-proof", AggregateVerifier.ProofType.ZK, ZK_PROVER, address(0), ZK_PROVER); } function testInitializeFailsIfInvalidCallDataSize() public { @@ -117,22 +112,21 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = _advanceL2BlockAndClaim(); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + uint256 slowDelay = _slowFinalizationDelay(); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), teeProof ); Timestamp originalExpectedResolution = game.expectedResolution(); - assertEq(originalExpectedResolution.raw(), block.timestamp + _slowFinalizationDelay()); + assertEq(originalExpectedResolution.raw(), block.timestamp + slowDelay); - vm.warp(block.timestamp + _slowFinalizationDelay() - 1); + vm.warp(block.timestamp + slowDelay - 1); vm.expectRevert(AggregateVerifier.GameNotOver.selector); game.resolve(); _provideProof(game, ZK_PROVER, zkProof); - - Timestamp expectedResolution = game.expectedResolution(); - assertEq(expectedResolution.raw(), originalExpectedResolution.raw()); + assertEq(game.expectedResolution().raw(), originalExpectedResolution.raw()); vm.warp(block.timestamp + 1); game.resolve(); @@ -148,11 +142,7 @@ contract AggregateVerifierTest is BaseTest { TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), teeProof ); - Hash gameId = factory.getGameUUID( - AGGREGATE_VERIFIER_GAME_TYPE, - rootClaim, - abi.encodePacked(currentL2BlockNumber, address(anchorStateRegistry), game.intermediateOutputRoots()) - ); + Hash gameId = factory.getGameUUID(AGGREGATE_VERIFIER_GAME_TYPE, rootClaim, game.extraData()); vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.GameAlreadyExists.selector, gameId)); _createAggregateVerifierGame(ZK_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), zkProof); } @@ -193,12 +183,11 @@ contract AggregateVerifierTest is BaseTest { bytes memory proofBytes = _teeProof(l1OriginHash, l1OriginNumber, rootClaim); - vm.expectRevert( + _expectCreateGameRevertsForTeeProof( + rootClaim, + proofBytes, abi.encodeWithSelector(AggregateVerifier.L1OriginInFuture.selector, l1OriginNumber, block.number) ); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes - ); } function testVerifyFailsWithL1OriginTooOld() public { @@ -211,9 +200,10 @@ contract AggregateVerifierTest is BaseTest { bytes memory proofBytes = _teeProof(l1OriginHash, l1OriginNumber, rootClaim); - vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number)); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes + _expectCreateGameRevertsForTeeProof( + rootClaim, + proofBytes, + abi.encodeWithSelector(AggregateVerifier.L1OriginTooOld.selector, l1OriginNumber, block.number) ); } @@ -225,9 +215,10 @@ contract AggregateVerifierTest is BaseTest { bytes memory proofBytes = _teeProof(wrongHash, l1OriginNumber, rootClaim); bytes32 actualHash = blockhash(l1OriginNumber); - vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.L1OriginHashMismatch.selector, wrongHash, actualHash)); - _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes + _expectCreateGameRevertsForTeeProof( + rootClaim, + proofBytes, + abi.encodeWithSelector(AggregateVerifier.L1OriginHashMismatch.selector, wrongHash, actualHash) ); } @@ -281,6 +272,25 @@ contract AggregateVerifierTest is BaseTest { return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber))); } + function _createAndAssertInitializedGame( + bytes memory proofSalt, + AggregateVerifier.ProofType proofType, + address prover, + address expectedTeeProver, + address expectedZkProver + ) + private + { + Claim rootClaim = _advanceL2BlockAndClaim(); + bytes memory proof = _generateProof(proofSalt, proofType); + + AggregateVerifier game = _createAggregateVerifierGame( + prover, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof + ); + + _assertInitializedGame(game, rootClaim, prover, expectedTeeProver, expectedZkProver); + } + function _assertInitializedGame( AggregateVerifier game, Claim rootClaim, @@ -300,9 +310,10 @@ contract AggregateVerifierTest is BaseTest { assertEq(game.parentAddress(), address(anchorStateRegistry)); assertEq(game.gameType().raw(), AGGREGATE_VERIFIER_GAME_TYPE.raw()); assertEq(game.gameCreator(), expectedCreator); + bytes memory intermediateOutputRoots = game.intermediateOutputRoots(); assertEq( game.extraData(), - abi.encodePacked(currentL2BlockNumber, address(anchorStateRegistry), game.intermediateOutputRoots()) + abi.encodePacked(currentL2BlockNumber, address(anchorStateRegistry), intermediateOutputRoots) ); assertEq(game.bondRecipient(), expectedCreator); assertTrue(anchorStateRegistry.isGameProper(IDisputeGame(address(game)))); @@ -329,6 +340,19 @@ contract AggregateVerifierTest is BaseTest { assertEq(delayedWETH.balanceOf(address(game)), 0); } + function _expectCreateGameRevertsForTeeProof( + Claim rootClaim, + bytes memory proofBytes, + bytes memory revertData + ) + private + { + vm.expectRevert(revertData); + _createAggregateVerifierGame( + TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proofBytes + ); + } + function _teeProof( bytes32 l1OriginHash, uint256 l1OriginNumber, @@ -364,7 +388,7 @@ contract AggregateVerifierTest is BaseTest { } function _aggregateVerifierImpl() private view returns (AggregateVerifier) { - return AggregateVerifier(address(factory.gameImpls(AGGREGATE_VERIFIER_GAME_TYPE))); + return aggregateVerifierImpl; } function _slowFinalizationDelay() private view returns (uint256) { From 8c868187024852e1dacf1fe2c4d48d8cc20c7f1a Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:41:04 -0400 Subject: [PATCH 030/135] Refactor AggregateVerifierTest.t.sol: replace direct calls to delay and contract address with constants from aggregateVerifierImpl, enhancing code clarity and reducing redundancy. --- test/L1/proofs/AggregateVerifier.t.sol | 52 +++++++++++--------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index 0bb3688f..46a2d189 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -55,7 +55,7 @@ contract AggregateVerifierTest is BaseTest { vm.expectRevert(GameNotResolved.selector); game.claimCredit(); - vm.warp(block.timestamp + _slowFinalizationDelay()); + vm.warp(block.timestamp + aggregateVerifierImpl.SLOW_FINALIZATION_DELAY()); game.resolve(); _assertStatus(game, GameStatus.DEFENDER_WINS); @@ -74,7 +74,7 @@ contract AggregateVerifierTest is BaseTest { ZK_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof ); - vm.warp(block.timestamp + _slowFinalizationDelay()); + vm.warp(block.timestamp + aggregateVerifierImpl.SLOW_FINALIZATION_DELAY()); game.resolve(); _assertStatus(game, GameStatus.DEFENDER_WINS); @@ -97,7 +97,7 @@ contract AggregateVerifierTest is BaseTest { _provideProof(game, ZK_PROVER, zkProof); assertEq(game.proofCount(), 2); - vm.warp(block.timestamp + _fastFinalizationDelay()); + vm.warp(block.timestamp + aggregateVerifierImpl.FAST_FINALIZATION_DELAY()); game.resolve(); _assertStatus(game, GameStatus.DEFENDER_WINS); @@ -112,7 +112,7 @@ contract AggregateVerifierTest is BaseTest { Claim rootClaim = _advanceL2BlockAndClaim(); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - uint256 slowDelay = _slowFinalizationDelay(); + uint256 slowDelay = aggregateVerifierImpl.SLOW_FINALIZATION_DELAY(); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), teeProof @@ -245,7 +245,7 @@ contract AggregateVerifierTest is BaseTest { uint256 l1OriginNumber = block.number - 260; bytes32 expectedHash = keccak256(abi.encodePacked("mock-blockhash", l1OriginNumber)); - vm.mockCall(_eip2935Contract(), abi.encode(l1OriginNumber), abi.encode(expectedHash)); + vm.mockCall(aggregateVerifierImpl.EIP2935_CONTRACT(), abi.encode(l1OriginNumber), abi.encode(expectedHash)); bytes memory proofBytes = _teeProof(expectedHash, l1OriginNumber, rootClaim); @@ -255,16 +255,9 @@ contract AggregateVerifierTest is BaseTest { } function testDeployWithInvalidBlockIntervals() public { - vm.expectRevert( - abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 0, INTERMEDIATE_BLOCK_INTERVAL) - ); - _deployAggregateVerifierWithIntervals(0, INTERMEDIATE_BLOCK_INTERVAL); - - vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, BLOCK_INTERVAL, 0)); - _deployAggregateVerifierWithIntervals(BLOCK_INTERVAL, 0); - - vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.InvalidBlockInterval.selector, 3, 2)); - _deployAggregateVerifierWithIntervals(3, 2); + _expectDeployWithInvalidBlockIntervalsReverts(0, INTERMEDIATE_BLOCK_INTERVAL); + _expectDeployWithInvalidBlockIntervalsReverts(BLOCK_INTERVAL, 0); + _expectDeployWithInvalidBlockIntervalsReverts(3, 2); } function _advanceL2BlockAndClaim() private returns (Claim rootClaim) { @@ -365,6 +358,20 @@ contract AggregateVerifierTest is BaseTest { return abi.encodePacked(uint8(AggregateVerifier.ProofType.TEE), l1OriginHash, l1OriginNumber, rootClaim.raw()); } + function _expectDeployWithInvalidBlockIntervalsReverts( + uint256 blockInterval, + uint256 intermediateBlockInterval + ) + private + { + vm.expectRevert( + abi.encodeWithSelector( + AggregateVerifier.InvalidBlockInterval.selector, blockInterval, intermediateBlockInterval + ) + ); + _deployAggregateVerifierWithIntervals(blockInterval, intermediateBlockInterval); + } + function _deployAggregateVerifierWithIntervals( uint256 blockInterval, uint256 intermediateBlockInterval @@ -387,19 +394,4 @@ contract AggregateVerifierTest is BaseTest { ); } - function _aggregateVerifierImpl() private view returns (AggregateVerifier) { - return aggregateVerifierImpl; - } - - function _slowFinalizationDelay() private view returns (uint256) { - return _aggregateVerifierImpl().SLOW_FINALIZATION_DELAY(); - } - - function _fastFinalizationDelay() private view returns (uint256) { - return _aggregateVerifierImpl().FAST_FINALIZATION_DELAY(); - } - - function _eip2935Contract() private view returns (address) { - return _aggregateVerifierImpl().EIP2935_CONTRACT(); - } } From 5c1146f43664223f91b72f073b38d304a52baf6a Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:47:31 -0400 Subject: [PATCH 031/135] Refactor AnchorStateRegistry.t.sol: introduce helper functions for mocking game status and streamline assertions, improving code clarity and maintainability of test cases. --- test/L1/proofs/AnchorStateRegistry.t.sol | 291 +++++++---------------- 1 file changed, 84 insertions(+), 207 deletions(-) diff --git a/test/L1/proofs/AnchorStateRegistry.t.sol b/test/L1/proofs/AnchorStateRegistry.t.sol index 604f7521..26f64602 100644 --- a/test/L1/proofs/AnchorStateRegistry.t.sol +++ b/test/L1/proofs/AnchorStateRegistry.t.sol @@ -58,11 +58,39 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { ); } - function skipIfForkTest(string memory) public pure { } - function _setPaused(bool _paused) internal { vm.mockCall(address(systemConfig), abi.encodeCall(systemConfig.paused, ()), abi.encode(_paused)); } + + function _mockGameStatus(GameStatus _status) internal { + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(_status)); + } + + function _mockGameResolvedAt(uint256 _timestamp) internal { + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_timestamp)); + } + + function _mockGamePastFinalityWithStatus(GameStatus _status) internal { + _mockGameResolvedAt(block.timestamp); + vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); + _mockGameStatus(_status); + } + + function _mockGameWasRespected(bool _wasRespected) internal { + vm.mockCall( + address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(_wasRespected) + ); + } + + function _mockGameNotRegistered() internal { + vm.mockCall( + address(disputeGameFactory), + abi.encodeCall( + disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData()) + ), + abi.encode(address(0), 0) + ); + } } /// @title AnchorStateRegistry_Version_Test @@ -79,17 +107,15 @@ contract AnchorStateRegistry_Version_Test is AnchorStateRegistry_TestInit { contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { /// @notice Tests that initialization is successful. function test_initialize_succeeds() public view { - skipIfForkTest("State has changed since initialization on a forked network."); - // Verify starting anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); assertEq(root.raw(), keccak256(abi.encode(uint256(0)))); assertEq(l2BlockNumber, 0); // Verify contract addresses. - assert(anchorStateRegistry.systemConfig() == systemConfig); - assert(anchorStateRegistry.disputeGameFactory() == disputeGameFactory); - assert(anchorStateRegistry.superchainConfig() == superchainConfig); + assertEq(address(anchorStateRegistry.systemConfig()), address(systemConfig)); + assertEq(address(anchorStateRegistry.disputeGameFactory()), address(disputeGameFactory)); + assertEq(address(anchorStateRegistry.superchainConfig()), address(superchainConfig)); } /// @notice Tests that the initializer value is correct. Trivial test for normal @@ -109,8 +135,6 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { /// @notice Tests that the retirement timestamp is set on the first initialization. function test_initialize_setsRetirementTimestamp_succeeds() public { - skipIfForkTest("State has changed since initialization on a forked network."); - (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); GameType startingGameType = anchorStateRegistry.respectedGameType(); @@ -139,8 +163,6 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { /// @notice Tests that the retirement timestamp is unchanged during re-initialization. function test_initialize_reinitializationDoesNotChangeRetirementTimestamp_succeeds() public { - skipIfForkTest("State has changed since initialization on a forked network."); - (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); GameType startingGameType = anchorStateRegistry.respectedGameType(); @@ -393,10 +415,7 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit /// @notice Tests that getAnchorRoot will return the value of the starting anchor root when no /// anchor game exists yet. function test_getAnchorRoot_noAnchorGame_succeeds() public view { - skipIfForkTest("On a forked network, there would most likely be an anchor game already."); - - // Assert that we nave no anchor game yet. - assert(address(anchorStateRegistry.anchorGame()) == address(0)); + assertEq(address(anchorStateRegistry.anchorGame()), address(0)); // We should get the starting anchor root back. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); @@ -406,12 +425,7 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit /// @notice Tests that getAnchorRoot will return the correct anchor root if an anchor game exists. function test_getAnchorRoot_anchorGameExists_succeeds() public { - // Mock the game to be resolved. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); - - // Mock the game to be the defender wins. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); + _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); // Set the anchor game to the game proxy. anchorStateRegistry.setAnchorState(gameProxy); @@ -425,12 +439,7 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit /// @notice Tests that getAnchorRoot will return the latest anchor root even if the superchain /// is paused. function test_getAnchorRoot_superchainPaused_succeeds() public { - // Mock the game to be resolved. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); - - // Mock the game to be the defender wins. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); + _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); // Set the anchor game to the game proxy. anchorStateRegistry.setAnchorState(gameProxy); @@ -446,12 +455,7 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit /// @notice Tests that getAnchorRoot returns even if the anchor game is blacklisted. function test_getAnchorRoot_blacklistedGame_succeeds() public { - // Mock the game to be resolved. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); - - // Mock the game to be the defender wins. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); + _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); // Set the anchor game to the game proxy. anchorStateRegistry.setAnchorState(gameProxy); @@ -473,12 +477,7 @@ contract AnchorStateRegistry_GetStartingAnchorRoot_Test is AnchorStateRegistry_T /// @notice Tests that getStartingAnchorRoot remains unchanged even if the current anchor root /// changes. function test_getStartingAnchorRoot_afterUpdate_succeeds() public { - // Mock the game to be resolved. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); - - // Mock the game to be the defender wins. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); + _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); // Mock the game's L2 block number to be greater than the starting anchor root block number. vm.mockCall( @@ -524,14 +523,7 @@ contract AnchorStateRegistry_IsGameRegistered_Test is AnchorStateRegistry_TestIn /// @notice Tests that isGameRegistered will return false if the game is not registered. function test_isGameRegistered_isNotFactoryRegistered_succeeds() public { - // Mock the DisputeGameFactory to make it seem that the game was not registered. - vm.mockCall( - address(disputeGameFactory), - abi.encodeCall( - disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData()) - ), - abi.encode(address(0), 0) - ); + _mockGameNotRegistered(); // Game should not be registered. assertFalse(anchorStateRegistry.isGameRegistered(gameProxy)); @@ -560,18 +552,14 @@ contract AnchorStateRegistry_IsGameRespected_Test is AnchorStateRegistry_TestIni /// @notice Tests that isGameRespected will return true if the game is of the respected game /// type. function test_isGameRespected_isRespected_succeeds() public { - // Mock that the game was respected. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); + _mockGameWasRespected(true); assertTrue(anchorStateRegistry.isGameRespected(gameProxy)); } /// @notice Tests that isGameRespected will return false if the game is not of the respected /// game type. function test_isGameRespected_isNotRespected_succeeds() public { - // Mock that the game was not respected. - vm.mockCall( - address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(false) - ); + _mockGameWasRespected(false); assertFalse(anchorStateRegistry.isGameRespected(gameProxy)); } } @@ -649,11 +637,8 @@ contract AnchorStateRegistry_IsGameResolved_Test is AnchorStateRegistry_TestInit // Bound resolvedAt to be less than or equal to current timestamp. _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp); - // Mock the resolvedAt timestamp. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp)); - - // Mock the status to be CHALLENGER_WINS. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + _mockGameResolvedAt(_resolvedAtTimestamp); + _mockGameStatus(GameStatus.CHALLENGER_WINS); // Game should be resolved. assertTrue(anchorStateRegistry.isGameResolved(gameProxy)); @@ -665,11 +650,8 @@ contract AnchorStateRegistry_IsGameResolved_Test is AnchorStateRegistry_TestInit // Bound resolvedAt to be less than or equal to current timestamp. _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp); - // Mock the resolvedAt timestamp. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp)); - - // Mock the status to be DEFENDER_WINS. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); + _mockGameResolvedAt(_resolvedAtTimestamp); + _mockGameStatus(GameStatus.DEFENDER_WINS); // Game should be resolved. assertTrue(anchorStateRegistry.isGameResolved(gameProxy)); @@ -682,11 +664,8 @@ contract AnchorStateRegistry_IsGameResolved_Test is AnchorStateRegistry_TestInit // Bound resolvedAt to be less than or equal to current timestamp. _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp); - // Mock the resolvedAt timestamp. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp)); - - // Mock the status to be IN_PROGRESS. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS)); + _mockGameResolvedAt(_resolvedAtTimestamp); + _mockGameStatus(GameStatus.IN_PROGRESS); // Game should not be resolved. assertFalse(anchorStateRegistry.isGameResolved(gameProxy)); @@ -704,32 +683,15 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_TestInit { /// @notice Tests that isGameProper will return false if the game is not registered. function test_isGameProper_isNotFactoryRegistered_succeeds() public { - // Mock the DisputeGameFactory to make it seem that the game was not registered. - vm.mockCall( - address(disputeGameFactory), - abi.encodeCall( - disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData()) - ), - abi.encode(address(0), 0) - ); + _mockGameNotRegistered(); assertFalse(anchorStateRegistry.isGameProper(gameProxy)); } - /// @notice Tests that isGameProper will return false if the game is not the respected game + /// @notice Tests that isGameProper still returns true if the game was not the respected game /// type. - /// @param _gameType The game type to use for the test. - function testFuzz_isGameProper_anyGameType_succeeds(GameType _gameType) public { - if (_gameType.raw() == gameProxy.gameType().raw()) { - _gameType = GameType.wrap(_gameType.raw() + 1); - } - - // Mock that the game was not respected. - vm.mockCall( - address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(false) - ); - - // Still a proper game. + function test_isGameProper_notRespected_succeeds() public { + _mockGameWasRespected(false); assertTrue(anchorStateRegistry.isGameProper(gameProxy)); } @@ -784,11 +746,8 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds() - 1); - // Mock the resolvedAt timestamp. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp)); - - // Mock the status to be DEFENDER_WINS. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); + _mockGameResolvedAt(_resolvedAtTimestamp); + _mockGameStatus(GameStatus.DEFENDER_WINS); // Game should be finalized. assertTrue(anchorStateRegistry.isGameFinalized(gameProxy)); @@ -807,8 +766,7 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni block.timestamp ); - // Mock the resolvedAt timestamp. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp)); + _mockGameResolvedAt(_resolvedAtTimestamp); // Game should not be finalized. assertFalse(anchorStateRegistry.isGameFinalized(gameProxy)); @@ -819,8 +777,7 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni // Warp forward by disputeGameFinalityDelaySeconds. vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); - // Mock the status call to be IN_PROGRESS. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS)); + _mockGameStatus(GameStatus.IN_PROGRESS); // Game should not be finalized. assertFalse(anchorStateRegistry.isGameFinalized(gameProxy)); @@ -840,52 +797,32 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds() - 1); - // Mock that the game was respected. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); - - // Mock the resolvedAt timestamp. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp)); - - // Mock the status to be DEFENDER_WINS. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); + _mockGameWasRespected(true); + _mockGameResolvedAt(_resolvedAtTimestamp); + _mockGameStatus(GameStatus.DEFENDER_WINS); // Claim should be valid. assertTrue(anchorStateRegistry.isGameClaimValid(gameProxy)); } /// @notice Tests that isGameClaimValid will return false if the game is not registered. - function testFuzz_isGameClaimValid_notRegistered_succeeds() public { - // Mock the DisputeGameFactory to make it seem that the game was not registered. - vm.mockCall( - address(disputeGameFactory), - abi.encodeCall( - disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData()) - ), - abi.encode(address(0), 0) - ); + function test_isGameClaimValid_notRegistered_succeeds() public { + _mockGameNotRegistered(); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); } /// @notice Tests that isGameClaimValid will return false if the game is not respected. - /// @param _gameType The game type to use for the test. - function testFuzz_isGameClaimValid_isNotRespected_succeeds(GameType _gameType) public { - if (_gameType.raw() == gameProxy.gameType().raw()) { - _gameType = GameType.wrap(_gameType.raw() + 1); - } - - // Mock that the game was not respected. - vm.mockCall( - address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(false) - ); + function test_isGameClaimValid_isNotRespected_succeeds() public { + _mockGameWasRespected(false); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); } /// @notice Tests that isGameClaimValid will return false if the game is blacklisted. - function testFuzz_isGameClaimValid_isBlacklisted_succeeds() public { + function test_isGameClaimValid_isBlacklisted_succeeds() public { // Mock the disputeGameBlacklist call to return true. vm.mockCall( address(anchorStateRegistry), @@ -899,7 +836,7 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @notice Tests that isGameClaimValid will return false if the game is retired. /// @param _createdAtTimestamp The createdAt timestamp to use for the test. - function testFuzz_isGameClaimValid_isRetired_succeeds(uint256 _createdAtTimestamp) public { + function testFuzz_isGameClaimValid_isRetired_succeeds(uint64 _createdAtTimestamp) public { // Set the retirement timestamp to now. vm.prank(superchainConfig.guardian()); anchorStateRegistry.updateRetirementTimestamp(); @@ -915,9 +852,8 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn } /// @notice Tests that isGameClaimValid will return false if the game is not resolved. - function testFuzz_isGameClaimValid_notResolved_succeeds() public { - // Mock the status to be IN_PROGRESS. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS)); + function test_isGameClaimValid_notResolved_succeeds() public { + _mockGameStatus(GameStatus.IN_PROGRESS); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); @@ -936,8 +872,7 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn block.timestamp ); - // Mock the resolvedAt timestamp. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(_resolvedAtTimestamp)); + _mockGameResolvedAt(_resolvedAtTimestamp); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); @@ -970,15 +905,8 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the l2BlockNumber call. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - // Mock the DEFENDER_WINS state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); - - // Mock that the game was respected. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); - - // Mock the resolvedAt timestamp and fast forward to beyond the delay. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); + _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); + _mockGameWasRespected(true); // Update the anchor state. vm.prank(address(gameProxy)); @@ -1009,15 +937,8 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the l2BlockNumber call. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - // Mock the DEFENDER_WINS state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); - - // Mock that the game was respected. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); - - // Mock the resolvedAt timestamp and fast forward to beyond the delay. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); + _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); + _mockGameWasRespected(true); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -1042,20 +963,9 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the l2BlockNumber call. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - // Mock the DEFENDER_WINS state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); - - // Mock that the game was respected. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); - - // Mock the DisputeGameFactory to make it seem that the game was not registered. - vm.mockCall( - address(disputeGameFactory), - abi.encodeCall( - disputeGameFactory.games, (gameProxy.gameType(), gameProxy.rootClaim(), gameProxy.extraData()) - ), - abi.encode(address(0), 0) - ); + _mockGameStatus(GameStatus.DEFENDER_WINS); + _mockGameWasRespected(true); + _mockGameNotRegistered(); // Try to update the anchor state. vm.prank(superchainConfig.guardian()); @@ -1081,15 +991,8 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the l2BlockNumber call. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - // Mock the CHALLENGER_WINS state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); - - // Mock that the game was respected. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); - - // Mock the resolvedAt timestamp and fast forward to beyond the delay. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); + _mockGamePastFinalityWithStatus(GameStatus.CHALLENGER_WINS); + _mockGameWasRespected(true); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -1115,15 +1018,8 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the l2BlockNumber call. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - // Mock the CHALLENGER_WINS state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.IN_PROGRESS)); - - // Mock that the game was respected. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); - - // Mock the resolvedAt timestamp and fast forward to beyond the delay. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); + _mockGamePastFinalityWithStatus(GameStatus.IN_PROGRESS); + _mockGameWasRespected(true); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -1148,17 +1044,8 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Mock the l2BlockNumber call. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - // Mock the DEFENDER_WINS state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); - - // Mock the resolvedAt timestamp and fast forward to beyond the delay. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); - - // Mock that the game was not respected when created. - vm.mockCall( - address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(false) - ); + _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); + _mockGameWasRespected(false); // Try to update the anchor state. vm.prank(address(gameProxy)); @@ -1181,15 +1068,8 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Bound the new block number. _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max); - // Mock the DEFENDER_WINS state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); - - // Mock that the game was respected. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); - - // Mock the resolvedAt timestamp and fast forward to beyond the delay. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); + _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); + _mockGameWasRespected(true); // Blacklist the game. vm.prank(superchainConfig.guardian()); @@ -1215,11 +1095,8 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Bound the new block number. _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max); - // Mock the DEFENDER_WINS state. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); - - // Mock that the game was respected. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); + _mockGameStatus(GameStatus.DEFENDER_WINS); + _mockGameWasRespected(true); // Set the retirement timestamp. vm.prank(superchainConfig.guardian()); From 2f4df04b036ec1570131a5fc688b011bde38448b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:53:13 -0400 Subject: [PATCH 032/135] Refactor AnchorStateRegistry.t.sol: introduce additional helper functions for initialization, state updates, and assertions, enhancing test clarity and maintainability. --- test/L1/proofs/AnchorStateRegistry.t.sol | 240 ++++++++++------------- 1 file changed, 103 insertions(+), 137 deletions(-) diff --git a/test/L1/proofs/AnchorStateRegistry.t.sol b/test/L1/proofs/AnchorStateRegistry.t.sol index 26f64602..dc472520 100644 --- a/test/L1/proofs/AnchorStateRegistry.t.sol +++ b/test/L1/proofs/AnchorStateRegistry.t.sol @@ -91,6 +91,66 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { abi.encode(address(0), 0) ); } + + function _initializeWithDummyStartingAnchorRoot() internal { + anchorStateRegistry.initialize( + systemConfig, + disputeGameFactory, + Proposal({ + root: Hash.wrap(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF), l2SequenceNumber: 0 + }), + GameType.wrap(0) + ); + } + + function _assumeNotGuardian(address _caller) internal view { + vm.assume(_caller != superchainConfig.guardian()); + } + + function _updateRetirementTimestampAsGuardian() internal { + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + } + + function _blacklistDisputeGameAsGuardian(IDisputeGame _game) internal { + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(_game); + } + + function _mockGameCreatedAt(uint64 _createdAtTimestamp) internal { + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); + } + + function _mockDisputeGameBlacklist(IDisputeGame _game, bool _blacklisted) internal { + vm.mockCall( + address(anchorStateRegistry), + abi.encodeCall(anchorStateRegistry.disputeGameBlacklist, (_game)), + abi.encode(_blacklisted) + ); + } + + function _setAnchorStateToGameProxy() internal { + _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); + anchorStateRegistry.setAnchorState(gameProxy); + } + + function _assertAnchorRootEqGame(IDisputeGame _game) internal view { + (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), _game.rootClaim().raw()); + assertEq(l2BlockNumber, _game.l2SequenceNumber()); + } + + function _assertCurrentAnchorRootEq(Hash _root, uint256 _l2BlockNumber) internal view { + (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(updatedL2BlockNumber, _l2BlockNumber); + assertEq(updatedRoot.raw(), _root.raw()); + } + + function _assertAnchorEq(GameType _gameType, Hash _root, uint256 _l2BlockNumber) internal view { + (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(_gameType); + assertEq(updatedL2BlockNumber, _l2BlockNumber); + assertEq(updatedRoot.raw(), _root.raw()); + } } /// @title AnchorStateRegistry_Version_Test @@ -171,8 +231,7 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { uint256 initialTimestamp = block.timestamp + 200; vm.warp(initialTimestamp); - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.updateRetirementTimestamp(); + _updateRetirementTimestampAsGuardian(); uint64 originalTimestamp = anchorStateRegistry.retirementTimestamp(); uint256 reinitTimestamp = block.timestamp + 200; @@ -194,14 +253,7 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { /// @notice Tests that initialization cannot be done twice function test_initialize_twice_reverts() public { vm.expectRevert("Initializable: contract is already initialized"); - anchorStateRegistry.initialize( - systemConfig, - disputeGameFactory, - Proposal({ - root: Hash.wrap(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF), l2SequenceNumber: 0 - }), - GameType.wrap(0) - ); + _initializeWithDummyStartingAnchorRoot(); } /// @notice Tests that initialization reverts if called by a non-proxy admin or owner. @@ -223,14 +275,7 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { // Call the `initialize` function with the sender vm.prank(_sender); - anchorStateRegistry.initialize( - systemConfig, - disputeGameFactory, - Proposal({ - root: Hash.wrap(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF), l2SequenceNumber: 0 - }), - GameType.wrap(0) - ); + _initializeWithDummyStartingAnchorRoot(); } } @@ -273,10 +318,8 @@ contract AnchorStateRegistry_SetRespectedGameType_Test is AnchorStateRegistry_Te /// @param _gameType The game type to attempt to set /// @param _caller The address attempting to call the function function testFuzz_setRespectedGameType_notGuardian_reverts(GameType _gameType, address _caller) public { - // Ensure caller is not the guardian - vm.assume(_caller != superchainConfig.guardian()); + _assumeNotGuardian(_caller); - // Attempt to call as non-guardian vm.prank(_caller); vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); anchorStateRegistry.setRespectedGameType(_gameType); @@ -288,11 +331,9 @@ contract AnchorStateRegistry_SetRespectedGameType_Test is AnchorStateRegistry_Te contract AnchorStateRegistry_UpdateRetirementTimestamp_Test is AnchorStateRegistry_TestInit { /// @notice Tests that updateRetirementTimestamp succeeds when called by the guardian function test_updateRetirementTimestamp_succeeds() public { - // Call as guardian - vm.prank(superchainConfig.guardian()); vm.expectEmit(address(anchorStateRegistry)); emit RetirementTimestampSet(block.timestamp); - anchorStateRegistry.updateRetirementTimestamp(); + _updateRetirementTimestampAsGuardian(); // Verify the timestamp was set assertEq(anchorStateRegistry.retirementTimestamp(), block.timestamp); @@ -300,17 +341,14 @@ contract AnchorStateRegistry_UpdateRetirementTimestamp_Test is AnchorStateRegist /// @notice Tests that updateRetirementTimestamp can be called multiple times by the guardian function test_updateRetirementTimestamp_multipleUpdates_succeeds() public { - // First update - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.updateRetirementTimestamp(); + _updateRetirementTimestampAsGuardian(); uint64 firstTimestamp = anchorStateRegistry.retirementTimestamp(); // Warp forward and update again vm.warp(block.timestamp + 1000); - vm.prank(superchainConfig.guardian()); vm.expectEmit(address(anchorStateRegistry)); emit RetirementTimestampSet(block.timestamp); - anchorStateRegistry.updateRetirementTimestamp(); + _updateRetirementTimestampAsGuardian(); // Verify the timestamp was updated assertEq(anchorStateRegistry.retirementTimestamp(), block.timestamp); @@ -320,10 +358,8 @@ contract AnchorStateRegistry_UpdateRetirementTimestamp_Test is AnchorStateRegist /// @notice Tests that updateRetirementTimestamp reverts when not called by the guardian /// @param _caller The address attempting to call the function function testFuzz_updateRetirementTimestamp_notGuardian_reverts(address _caller) public { - // Ensure caller is not the guardian - vm.assume(_caller != superchainConfig.guardian()); + _assumeNotGuardian(_caller); - // Attempt to call as non-guardian vm.prank(_caller); vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); anchorStateRegistry.updateRetirementTimestamp(); @@ -335,11 +371,9 @@ contract AnchorStateRegistry_UpdateRetirementTimestamp_Test is AnchorStateRegist contract AnchorStateRegistry_BlacklistDisputeGame_Test is AnchorStateRegistry_TestInit { /// @notice Tests that blacklistDisputeGame succeeds when called by the guardian function test_blacklistDisputeGame_succeeds() public { - // Call as guardian - vm.prank(superchainConfig.guardian()); vm.expectEmit(address(anchorStateRegistry)); emit DisputeGameBlacklisted(gameProxy); - anchorStateRegistry.blacklistDisputeGame(gameProxy); + _blacklistDisputeGameAsGuardian(gameProxy); // Verify the game was blacklisted assertTrue(anchorStateRegistry.disputeGameBlacklist(gameProxy)); @@ -364,10 +398,8 @@ contract AnchorStateRegistry_BlacklistDisputeGame_Test is AnchorStateRegistry_Te /// @notice Tests that blacklistDisputeGame reverts when not called by the guardian /// @param _caller The address attempting to call the function function testFuzz_blacklistDisputeGame_notGuardian_reverts(address _caller) public { - // Ensure caller is not the guardian - vm.assume(_caller != superchainConfig.guardian()); + _assumeNotGuardian(_caller); - // Attempt to call as non-guardian vm.prank(_caller); vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); anchorStateRegistry.blacklistDisputeGame(gameProxy); @@ -425,49 +457,29 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_TestInit /// @notice Tests that getAnchorRoot will return the correct anchor root if an anchor game exists. function test_getAnchorRoot_anchorGameExists_succeeds() public { - _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); - - // Set the anchor game to the game proxy. - anchorStateRegistry.setAnchorState(gameProxy); - - // We should get the anchor root back. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(root.raw(), gameProxy.rootClaim().raw()); - assertEq(l2BlockNumber, gameProxy.l2SequenceNumber()); + _setAnchorStateToGameProxy(); + _assertAnchorRootEqGame(gameProxy); } /// @notice Tests that getAnchorRoot will return the latest anchor root even if the superchain /// is paused. function test_getAnchorRoot_superchainPaused_succeeds() public { - _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); - - // Set the anchor game to the game proxy. - anchorStateRegistry.setAnchorState(gameProxy); + _setAnchorStateToGameProxy(); // Pause the superchain. _setPaused(true); - // We should get the anchor root back. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(root.raw(), gameProxy.rootClaim().raw()); - assertEq(l2BlockNumber, gameProxy.l2SequenceNumber()); + _assertAnchorRootEqGame(gameProxy); } /// @notice Tests that getAnchorRoot returns even if the anchor game is blacklisted. function test_getAnchorRoot_blacklistedGame_succeeds() public { - _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); - - // Set the anchor game to the game proxy. - anchorStateRegistry.setAnchorState(gameProxy); + _setAnchorStateToGameProxy(); // Blacklist the game. - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.blacklistDisputeGame(gameProxy); + _blacklistDisputeGameAsGuardian(gameProxy); - // Get the anchor root. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(root.raw(), gameProxy.rootClaim().raw()); - assertEq(l2BlockNumber, gameProxy.l2SequenceNumber()); + _assertAnchorRootEqGame(gameProxy); } } @@ -570,8 +582,7 @@ contract AnchorStateRegistry_IsGameBlacklisted_Test is AnchorStateRegistry_TestI /// @notice Tests that isGameBlacklisted will return true if the game is blacklisted. function test_isGameBlacklisted_isActuallyBlacklisted_succeeds() public { // Blacklist the game. - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.blacklistDisputeGame(gameProxy); + _blacklistDisputeGameAsGuardian(gameProxy); // Should return true. assertTrue(anchorStateRegistry.isGameBlacklisted(gameProxy)); @@ -579,12 +590,7 @@ contract AnchorStateRegistry_IsGameBlacklisted_Test is AnchorStateRegistry_TestI /// @notice Tests that isGameBlacklisted will return false if the game is not blacklisted. function test_isGameBlacklisted_isNotBlacklisted_succeeds() public { - // Mock the disputeGameBlacklist call to return false. - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(anchorStateRegistry.disputeGameBlacklist, (gameProxy)), - abi.encode(false) - ); + _mockDisputeGameBlacklist(gameProxy, false); assertFalse(anchorStateRegistry.isGameBlacklisted(gameProxy)); } } @@ -596,14 +602,13 @@ contract AnchorStateRegistry_IsGameRetired_Test is AnchorStateRegistry_TestInit /// @param _createdAtTimestamp The createdAt timestamp to use for the test. function testFuzz_isGameRetired_isRetired_succeeds(uint64 _createdAtTimestamp) public { // Set the retirement timestamp to now. - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.updateRetirementTimestamp(); + _updateRetirementTimestampAsGuardian(); // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); // Mock the createdAt call. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); + _mockGameCreatedAt(_createdAtTimestamp); // Game should be retired. assertTrue(anchorStateRegistry.isGameRetired(gameProxy)); @@ -613,15 +618,14 @@ contract AnchorStateRegistry_IsGameRetired_Test is AnchorStateRegistry_TestInit /// @param _createdAtTimestamp The createdAt timestamp to use for the test. function testFuzz_isGameRetired_isNotRetired_succeeds(uint64 _createdAtTimestamp) public { // Set the retirement timestamp to now. - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.updateRetirementTimestamp(); + _updateRetirementTimestampAsGuardian(); // Make sure createdAt timestamp is greater than the retirementTimestamp. _createdAtTimestamp = uint64(bound(_createdAtTimestamp, anchorStateRegistry.retirementTimestamp() + 1, type(uint64).max)); // Mock the call to createdAt. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); + _mockGameCreatedAt(_createdAtTimestamp); // Game should not be retired. assertFalse(anchorStateRegistry.isGameRetired(gameProxy)); @@ -698,8 +702,7 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_TestInit { /// @notice Tests that isGameProper will return false if the game is blacklisted. function test_isGameProper_isBlacklisted_succeeds() public { // Blacklist the game. - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.blacklistDisputeGame(gameProxy); + _blacklistDisputeGameAsGuardian(gameProxy); // Should return false. assertFalse(anchorStateRegistry.isGameProper(gameProxy)); @@ -718,14 +721,13 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_TestInit { /// @param _createdAtTimestamp The createdAt timestamp to use for the test. function testFuzz_isGameProper_isRetired_succeeds(uint64 _createdAtTimestamp) public { // Set the retirement timestamp to now. - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.updateRetirementTimestamp(); + _updateRetirementTimestampAsGuardian(); // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); // Mock the call to createdAt. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); + _mockGameCreatedAt(_createdAtTimestamp); // Game should not be proper. assertFalse(anchorStateRegistry.isGameProper(gameProxy)); @@ -755,7 +757,7 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni /// @notice Tests that isGameFinalized will return false if the game is not finalized. /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. - function testFuzz_isGameFinalized_isNotAirgapped_succeeds(uint256 _resolvedAtTimestamp) public { + function testFuzz_isGameFinalized_isNotFinalized_succeeds(uint256 _resolvedAtTimestamp) public { // Warp forward by disputeGameFinalityDelaySeconds. vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); @@ -823,12 +825,7 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @notice Tests that isGameClaimValid will return false if the game is blacklisted. function test_isGameClaimValid_isBlacklisted_succeeds() public { - // Mock the disputeGameBlacklist call to return true. - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(anchorStateRegistry.disputeGameBlacklist, (gameProxy)), - abi.encode(true) - ); + _mockDisputeGameBlacklist(gameProxy, true); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); @@ -838,14 +835,13 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @param _createdAtTimestamp The createdAt timestamp to use for the test. function testFuzz_isGameClaimValid_isRetired_succeeds(uint64 _createdAtTimestamp) public { // Set the retirement timestamp to now. - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.updateRetirementTimestamp(); + _updateRetirementTimestampAsGuardian(); // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); // Mock the call to createdAt. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); + _mockGameCreatedAt(_createdAtTimestamp); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); @@ -859,9 +855,9 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); } - /// @notice Tests that isGameClaimValid will return false if the game is not airgapped. + /// @notice Tests that isGameClaimValid will return false if the game is not finalized. /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. - function testFuzz_isGameClaimValid_notAirgapped_succeeds(uint256 _resolvedAtTimestamp) public { + function testFuzz_isGameClaimValid_notFinalized_succeeds(uint256 _resolvedAtTimestamp) public { // Warp forward by disputeGameFinalityDelaySeconds. vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); @@ -896,9 +892,6 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit /// respected game type. /// @param _l2BlockNumber The L2 block number to use for the game. function testFuzz_setAnchorState_validNewerState_succeeds(uint256 _l2BlockNumber) public { - // Grab block number of the existing anchor root. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - // Bound the new block number. _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max); @@ -915,7 +908,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit anchorStateRegistry.setAnchorState(gameProxy); // Confirm that the anchor state is now the same as the game state. - (root, l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); + (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); assertEq(l2BlockNumber, gameProxy.l2SequenceNumber()); assertEq(root.raw(), gameProxy.rootClaim().raw()); @@ -945,10 +938,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); anchorStateRegistry.setAnchorState(gameProxy); - // Confirm that the anchor state has not updated. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); + _assertCurrentAnchorRootEq(root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is not registered. @@ -972,10 +962,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); anchorStateRegistry.setAnchorState(gameProxy); - // Confirm that the anchor state has not updated. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); + _assertCurrentAnchorRootEq(root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is valid and the game status is @@ -999,10 +986,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); anchorStateRegistry.setAnchorState(gameProxy); - // Confirm that the anchor state has not updated. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); + _assertCurrentAnchorRootEq(root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is valid and the game status is @@ -1026,10 +1010,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); anchorStateRegistry.setAnchorState(gameProxy); - // Confirm that the anchor state has not updated. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); + _assertCurrentAnchorRootEq(root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is not respected. @@ -1052,10 +1033,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); anchorStateRegistry.setAnchorState(gameProxy); - // Confirm that the anchor state has not updated. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); + _assertAnchorEq(gameProxy.gameType(), root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is valid and the game is @@ -1072,18 +1050,14 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit _mockGameWasRespected(true); // Blacklist the game. - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.blacklistDisputeGame(gameProxy); + _blacklistDisputeGameAsGuardian(gameProxy); // Update the anchor state. vm.prank(address(gameProxy)); vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); anchorStateRegistry.setAnchorState(gameProxy); - // Confirm that the anchor state has not updated. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); + _assertAnchorEq(gameProxy.gameType(), root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is retired. @@ -1099,25 +1073,17 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit _mockGameWasRespected(true); // Set the retirement timestamp. - vm.prank(superchainConfig.guardian()); - anchorStateRegistry.updateRetirementTimestamp(); + _updateRetirementTimestampAsGuardian(); // Mock the call to createdAt. - vm.mockCall( - address(gameProxy), - abi.encodeCall(gameProxy.createdAt, ()), - abi.encode(anchorStateRegistry.retirementTimestamp() - 1) - ); + _mockGameCreatedAt(anchorStateRegistry.retirementTimestamp() - 1); // Update the anchor state. vm.prank(address(gameProxy)); vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); anchorStateRegistry.setAnchorState(gameProxy); - // Confirm that the anchor state has not updated. - (Hash updatedRoot, uint256 updatedL2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - assertEq(updatedL2BlockNumber, l2BlockNumber); - assertEq(updatedRoot.raw(), root.raw()); + _assertAnchorEq(gameProxy.gameType(), root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the superchain is paused. From de5d31e55acd229a82ed02e0752de08db4587fe8 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 20:59:22 -0400 Subject: [PATCH 033/135] Refactor AnchorStateRegistry.t.sol: add helper functions for unauthorized guardian checks and game state mocking, improving test clarity and maintainability. --- test/L1/proofs/AnchorStateRegistry.t.sol | 209 +++++++---------------- 1 file changed, 57 insertions(+), 152 deletions(-) diff --git a/test/L1/proofs/AnchorStateRegistry.t.sol b/test/L1/proofs/AnchorStateRegistry.t.sol index dc472520..01fe1a36 100644 --- a/test/L1/proofs/AnchorStateRegistry.t.sol +++ b/test/L1/proofs/AnchorStateRegistry.t.sol @@ -107,6 +107,12 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { vm.assume(_caller != superchainConfig.guardian()); } + function _expectUnauthorizedGuardianRevert(address _caller) internal { + _assumeNotGuardian(_caller); + vm.prank(_caller); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + } + function _updateRetirementTimestampAsGuardian() internal { vm.prank(superchainConfig.guardian()); anchorStateRegistry.updateRetirementTimestamp(); @@ -121,12 +127,21 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); } - function _mockDisputeGameBlacklist(IDisputeGame _game, bool _blacklisted) internal { - vm.mockCall( - address(anchorStateRegistry), - abi.encodeCall(anchorStateRegistry.disputeGameBlacklist, (_game)), - abi.encode(_blacklisted) - ); + function _mockRetiredGame(uint64 _createdAtTimestamp) internal returns (uint64) { + _updateRetirementTimestampAsGuardian(); + _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); + _mockGameCreatedAt(_createdAtTimestamp); + return _createdAtTimestamp; + } + + function _mockGameL2SequenceNumber(uint256 _l2BlockNumber) internal { + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); + } + + function _expectSetAnchorStateInvalid(IDisputeGame _game, address _caller) internal { + vm.prank(_caller); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); + anchorStateRegistry.setAnchorState(_game); } function _setAnchorStateToGameProxy() internal { @@ -318,10 +333,7 @@ contract AnchorStateRegistry_SetRespectedGameType_Test is AnchorStateRegistry_Te /// @param _gameType The game type to attempt to set /// @param _caller The address attempting to call the function function testFuzz_setRespectedGameType_notGuardian_reverts(GameType _gameType, address _caller) public { - _assumeNotGuardian(_caller); - - vm.prank(_caller); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + _expectUnauthorizedGuardianRevert(_caller); anchorStateRegistry.setRespectedGameType(_gameType); } } @@ -351,17 +363,15 @@ contract AnchorStateRegistry_UpdateRetirementTimestamp_Test is AnchorStateRegist _updateRetirementTimestampAsGuardian(); // Verify the timestamp was updated - assertEq(anchorStateRegistry.retirementTimestamp(), block.timestamp); - assertGt(anchorStateRegistry.retirementTimestamp(), firstTimestamp); + uint64 secondTimestamp = anchorStateRegistry.retirementTimestamp(); + assertEq(secondTimestamp, block.timestamp); + assertGt(secondTimestamp, firstTimestamp); } /// @notice Tests that updateRetirementTimestamp reverts when not called by the guardian /// @param _caller The address attempting to call the function function testFuzz_updateRetirementTimestamp_notGuardian_reverts(address _caller) public { - _assumeNotGuardian(_caller); - - vm.prank(_caller); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + _expectUnauthorizedGuardianRevert(_caller); anchorStateRegistry.updateRetirementTimestamp(); } } @@ -382,7 +392,7 @@ contract AnchorStateRegistry_BlacklistDisputeGame_Test is AnchorStateRegistry_Te /// @notice Tests that multiple games can be blacklisted function test_blacklistDisputeGame_multipleGames_succeeds() public { // Create a second game proxy - IDisputeGame secondGame = IDisputeGame(address(0x123)); + IDisputeGame secondGame = IDisputeGame(makeAddr("second-game")); // Blacklist both games vm.startPrank(superchainConfig.guardian()); @@ -398,10 +408,7 @@ contract AnchorStateRegistry_BlacklistDisputeGame_Test is AnchorStateRegistry_Te /// @notice Tests that blacklistDisputeGame reverts when not called by the guardian /// @param _caller The address attempting to call the function function testFuzz_blacklistDisputeGame_notGuardian_reverts(address _caller) public { - _assumeNotGuardian(_caller); - - vm.prank(_caller); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + _expectUnauthorizedGuardianRevert(_caller); anchorStateRegistry.blacklistDisputeGame(gameProxy); } @@ -490,18 +497,14 @@ contract AnchorStateRegistry_GetStartingAnchorRoot_Test is AnchorStateRegistry_T /// changes. function test_getStartingAnchorRoot_afterUpdate_succeeds() public { _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); + uint256 expectedL2BlockNumber = validL2BlockNumber + 1; + Claim expectedRoot = Claim.wrap(keccak256(abi.encode(123))); // Mock the game's L2 block number to be greater than the starting anchor root block number. - vm.mockCall( - address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(validL2BlockNumber + 1) - ); + _mockGameL2SequenceNumber(expectedL2BlockNumber); // Mock the game's anchor root to be different from the starting anchor root. - vm.mockCall( - address(gameProxy), - abi.encodeCall(gameProxy.rootClaim, ()), - abi.encode(Claim.wrap(keccak256(abi.encode(123)))) - ); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.rootClaim, ()), abi.encode(expectedRoot)); // Set the anchor game to the game proxy. anchorStateRegistry.setAnchorState(gameProxy); @@ -511,8 +514,8 @@ contract AnchorStateRegistry_GetStartingAnchorRoot_Test is AnchorStateRegistry_T // Verify the CURRENT anchor root has changed. (Hash currentRoot, uint256 currentL2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(currentRoot.raw(), gameProxy.rootClaim().raw()); - assertEq(currentL2BlockNumber, gameProxy.l2SequenceNumber()); + assertEq(currentRoot.raw(), expectedRoot.raw()); + assertEq(currentL2BlockNumber, expectedL2BlockNumber); // Verify the STARTING anchor root has NOT changed. Proposal memory startingAnchorRootAfterUpdate = anchorStateRegistry.getStartingAnchorRoot(); @@ -589,8 +592,7 @@ contract AnchorStateRegistry_IsGameBlacklisted_Test is AnchorStateRegistry_TestI } /// @notice Tests that isGameBlacklisted will return false if the game is not blacklisted. - function test_isGameBlacklisted_isNotBlacklisted_succeeds() public { - _mockDisputeGameBlacklist(gameProxy, false); + function test_isGameBlacklisted_isNotBlacklisted_succeeds() public view { assertFalse(anchorStateRegistry.isGameBlacklisted(gameProxy)); } } @@ -601,14 +603,7 @@ contract AnchorStateRegistry_IsGameRetired_Test is AnchorStateRegistry_TestInit /// @notice Tests that isGameRetired will return true if the game is retired. /// @param _createdAtTimestamp The createdAt timestamp to use for the test. function testFuzz_isGameRetired_isRetired_succeeds(uint64 _createdAtTimestamp) public { - // Set the retirement timestamp to now. - _updateRetirementTimestampAsGuardian(); - - // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. - _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); - - // Mock the createdAt call. - _mockGameCreatedAt(_createdAtTimestamp); + _mockRetiredGame(_createdAtTimestamp); // Game should be retired. assertTrue(anchorStateRegistry.isGameRetired(gameProxy)); @@ -720,14 +715,7 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_TestInit { /// @notice Tests that isGameProper will return false if the game is retired. /// @param _createdAtTimestamp The createdAt timestamp to use for the test. function testFuzz_isGameProper_isRetired_succeeds(uint64 _createdAtTimestamp) public { - // Set the retirement timestamp to now. - _updateRetirementTimestampAsGuardian(); - - // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. - _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); - - // Mock the call to createdAt. - _mockGameCreatedAt(_createdAtTimestamp); + _mockRetiredGame(_createdAtTimestamp); // Game should not be proper. assertFalse(anchorStateRegistry.isGameProper(gameProxy)); @@ -825,7 +813,7 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @notice Tests that isGameClaimValid will return false if the game is blacklisted. function test_isGameClaimValid_isBlacklisted_succeeds() public { - _mockDisputeGameBlacklist(gameProxy, true); + _blacklistDisputeGameAsGuardian(gameProxy); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); @@ -834,14 +822,7 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @notice Tests that isGameClaimValid will return false if the game is retired. /// @param _createdAtTimestamp The createdAt timestamp to use for the test. function testFuzz_isGameClaimValid_isRetired_succeeds(uint64 _createdAtTimestamp) public { - // Set the retirement timestamp to now. - _updateRetirementTimestampAsGuardian(); - - // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. - _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); - - // Mock the call to createdAt. - _mockGameCreatedAt(_createdAtTimestamp); + _mockRetiredGame(_createdAtTimestamp); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); @@ -896,7 +877,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max); // Mock the l2BlockNumber call. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); + _mockGameL2SequenceNumber(_l2BlockNumber); _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); _mockGameWasRespected(true); @@ -908,9 +889,7 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit anchorStateRegistry.setAnchorState(gameProxy); // Confirm that the anchor state is now the same as the game state. - (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - assertEq(l2BlockNumber, gameProxy.l2SequenceNumber()); - assertEq(root.raw(), gameProxy.rootClaim().raw()); + _assertAnchorRootEqGame(gameProxy); // Confirm that the anchor game is now set. IDisputeGame anchorGame = anchorStateRegistry.anchorGame(); @@ -928,160 +907,89 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit _l2BlockNumber = bound(_l2BlockNumber, 0, l2BlockNumber); // Mock the l2BlockNumber call. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); + _mockGameL2SequenceNumber(_l2BlockNumber); _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); _mockGameWasRespected(true); - // Try to update the anchor state. - vm.prank(address(gameProxy)); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); - anchorStateRegistry.setAnchorState(gameProxy); + _expectSetAnchorStateInvalid(gameProxy, address(gameProxy)); _assertCurrentAnchorRootEq(root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is not registered. - /// @param _l2BlockNumber The L2 block number to use for the game. - function testFuzz_setAnchorState_notFactoryRegisteredGame_fails(uint256 _l2BlockNumber) public { + function test_setAnchorState_notFactoryRegisteredGame_fails() public { // Grab block number of the existing anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - // Bound the new block number. - _l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber, type(uint256).max); - - // Mock the l2BlockNumber call. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - - _mockGameStatus(GameStatus.DEFENDER_WINS); - _mockGameWasRespected(true); _mockGameNotRegistered(); - // Try to update the anchor state. - vm.prank(superchainConfig.guardian()); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); - anchorStateRegistry.setAnchorState(gameProxy); + _expectSetAnchorStateInvalid(gameProxy, superchainConfig.guardian()); _assertCurrentAnchorRootEq(root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is valid and the game status is /// CHALLENGER_WINS. - /// @param _l2BlockNumber The L2 block number to use for the game. - function testFuzz_setAnchorState_challengerWins_fails(uint256 _l2BlockNumber) public { + function test_setAnchorState_challengerWins_fails() public { // Grab block number of the existing anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - // Bound the new block number. - _l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber, type(uint256).max); - - // Mock the l2BlockNumber call. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - _mockGamePastFinalityWithStatus(GameStatus.CHALLENGER_WINS); _mockGameWasRespected(true); - // Try to update the anchor state. - vm.prank(address(gameProxy)); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); - anchorStateRegistry.setAnchorState(gameProxy); + _expectSetAnchorStateInvalid(gameProxy, address(gameProxy)); _assertCurrentAnchorRootEq(root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is valid and the game status is /// IN_PROGRESS. - /// @param _l2BlockNumber The L2 block number to use for the game. - function testFuzz_setAnchorState_inProgress_fails(uint256 _l2BlockNumber) public { + function test_setAnchorState_inProgress_fails() public { // Grab block number of the existing anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - // Bound the new block number. - _l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber, type(uint256).max); - - // Mock the l2BlockNumber call. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - _mockGamePastFinalityWithStatus(GameStatus.IN_PROGRESS); _mockGameWasRespected(true); - // Try to update the anchor state. - vm.prank(address(gameProxy)); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); - anchorStateRegistry.setAnchorState(gameProxy); + _expectSetAnchorStateInvalid(gameProxy, address(gameProxy)); _assertCurrentAnchorRootEq(root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is not respected. - /// @param _l2BlockNumber The L2 block number to use for the game. - function testFuzz_setAnchorState_isNotRespectedGame_fails(uint256 _l2BlockNumber) public { + function test_setAnchorState_isNotRespectedGame_fails() public { // Grab block number of the existing anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.anchors(gameProxy.gameType()); - // Bound the new block number. - _l2BlockNumber = bound(_l2BlockNumber, l2BlockNumber, type(uint256).max); - - // Mock the l2BlockNumber call. - vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.l2SequenceNumber, ()), abi.encode(_l2BlockNumber)); - - _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); _mockGameWasRespected(false); - // Try to update the anchor state. - vm.prank(address(gameProxy)); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); - anchorStateRegistry.setAnchorState(gameProxy); + _expectSetAnchorStateInvalid(gameProxy, address(gameProxy)); _assertAnchorEq(gameProxy.gameType(), root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is valid and the game is /// blacklisted. - /// @param _l2BlockNumber The L2 block number to use for the game. - function testFuzz_setAnchorState_blacklistedGame_fails(uint256 _l2BlockNumber) public { + function test_setAnchorState_blacklistedGame_fails() public { // Grab block number of the existing anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - // Bound the new block number. - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max); - - _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); - _mockGameWasRespected(true); - // Blacklist the game. _blacklistDisputeGameAsGuardian(gameProxy); - // Update the anchor state. - vm.prank(address(gameProxy)); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); - anchorStateRegistry.setAnchorState(gameProxy); + _expectSetAnchorStateInvalid(gameProxy, address(gameProxy)); _assertAnchorEq(gameProxy.gameType(), root, l2BlockNumber); } /// @notice Tests that setAnchorState will revert if the game is retired. - /// @param _l2BlockNumber The L2 block number to use for the game. - function testFuzz_setAnchorState_retiredGame_fails(uint256 _l2BlockNumber) public { + function test_setAnchorState_retiredGame_fails() public { // Grab block number of the existing anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); - // Bound the new block number. - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max); - - _mockGameStatus(GameStatus.DEFENDER_WINS); - _mockGameWasRespected(true); - - // Set the retirement timestamp. - _updateRetirementTimestampAsGuardian(); - - // Mock the call to createdAt. - _mockGameCreatedAt(anchorStateRegistry.retirementTimestamp() - 1); - - // Update the anchor state. - vm.prank(address(gameProxy)); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); - anchorStateRegistry.setAnchorState(gameProxy); + _mockRetiredGame(0); + _expectSetAnchorStateInvalid(gameProxy, address(gameProxy)); _assertAnchorEq(gameProxy.gameType(), root, l2BlockNumber); } @@ -1091,9 +999,6 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit // Pause the superchain. _setPaused(true); - // Update the anchor state. - vm.prank(address(gameProxy)); - vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); - anchorStateRegistry.setAnchorState(gameProxy); + _expectSetAnchorStateInvalid(gameProxy, address(gameProxy)); } } From 99af85bf98c92ae0e60e6bfae209d9b5115b881e Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 21:03:01 -0400 Subject: [PATCH 034/135] Refactor AnchorStateRegistry.t.sol: introduce constants for anchor state registry and enhance helper functions for finality delay, improving test clarity and maintainability. --- test/L1/proofs/AnchorStateRegistry.t.sol | 123 ++++++++++------------- 1 file changed, 55 insertions(+), 68 deletions(-) diff --git a/test/L1/proofs/AnchorStateRegistry.t.sol b/test/L1/proofs/AnchorStateRegistry.t.sol index 01fe1a36..0f794f96 100644 --- a/test/L1/proofs/AnchorStateRegistry.t.sol +++ b/test/L1/proofs/AnchorStateRegistry.t.sol @@ -22,6 +22,10 @@ import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; /// @title AnchorStateRegistry_TestInit /// @notice Reusable test initialization for `AnchorStateRegistry` tests. abstract contract AnchorStateRegistry_TestInit is BaseTest { + string internal constant ANCHOR_STATE_REGISTRY_NAME = "AnchorStateRegistry"; + bytes32 internal constant DUMMY_STARTING_ANCHOR_ROOT = + 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF; + IDisputeGameFactory disputeGameFactory; ISuperchainConfig superchainConfig; IDisputeGame gameProxy; @@ -72,10 +76,16 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { function _mockGamePastFinalityWithStatus(GameStatus _status) internal { _mockGameResolvedAt(block.timestamp); - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds() + 1); + _warpByFinalityDelay(); + vm.warp(block.timestamp + 1); _mockGameStatus(_status); } + function _warpByFinalityDelay() internal returns (uint256 finalityDelay_) { + finalityDelay_ = anchorStateRegistry.disputeGameFinalityDelaySeconds(); + vm.warp(block.timestamp + finalityDelay_); + } + function _mockGameWasRespected(bool _wasRespected) internal { vm.mockCall( address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(_wasRespected) @@ -96,9 +106,7 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { anchorStateRegistry.initialize( systemConfig, disputeGameFactory, - Proposal({ - root: Hash.wrap(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF), l2SequenceNumber: 0 - }), + Proposal({ root: Hash.wrap(DUMMY_STARTING_ANCHOR_ROOT), l2SequenceNumber: 0 }), GameType.wrap(0) ); } @@ -129,9 +137,9 @@ abstract contract AnchorStateRegistry_TestInit is BaseTest { function _mockRetiredGame(uint64 _createdAtTimestamp) internal returns (uint64) { _updateRetirementTimestampAsGuardian(); - _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); - _mockGameCreatedAt(_createdAtTimestamp); - return _createdAtTimestamp; + uint64 createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); + _mockGameCreatedAt(createdAtTimestamp); + return createdAtTimestamp; } function _mockGameL2SequenceNumber(uint256 _l2BlockNumber) internal { @@ -180,6 +188,16 @@ contract AnchorStateRegistry_Version_Test is AnchorStateRegistry_TestInit { /// @title AnchorStateRegistry_Initialize_Test /// @notice Tests the `initialize` function of the `AnchorStateRegistry` contract. contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { + StorageSlot internal initializedSlot; + StorageSlot internal retirementTimestampSlot; + + function setUp() public override { + super.setUp(); + + initializedSlot = ForgeArtifacts.getSlot(ANCHOR_STATE_REGISTRY_NAME, "_initialized"); + retirementTimestampSlot = ForgeArtifacts.getSlot(ANCHOR_STATE_REGISTRY_NAME, "retirementTimestamp"); + } + /// @notice Tests that initialization is successful. function test_initialize_succeeds() public view { // Verify starting anchor root. @@ -197,14 +215,9 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { /// initialization but confirms that the initValue is not incremented incorrectly if /// an upgrade function is not present. function test_initialize_correctInitializerValue_succeeds() public view { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("AnchorStateRegistry", "_initialized"); - - // Get the initializer value. - bytes32 slotVal = vm.load(address(anchorStateRegistry), bytes32(slot.slot)); + bytes32 slotVal = vm.load(address(anchorStateRegistry), bytes32(initializedSlot.slot)); uint8 val = uint8(uint256(slotVal) & 0xFF); - // Assert that the initializer value matches the expected value. assertEq(val, anchorStateRegistry.initVersion()); } @@ -213,13 +226,11 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); GameType startingGameType = anchorStateRegistry.respectedGameType(); - StorageSlot memory initSlot = ForgeArtifacts.getSlot("AnchorStateRegistry", "_initialized"); - StorageSlot memory retirementSlot = ForgeArtifacts.getSlot("AnchorStateRegistry", "retirementTimestamp"); address proxyAdminOwner = anchorStateRegistry.proxyAdminOwner(); // Reset initialization and retirement timestamp state. - vm.store(address(anchorStateRegistry), bytes32(initSlot.slot), bytes32(0)); - vm.store(address(anchorStateRegistry), bytes32(retirementSlot.slot), bytes32(0)); + vm.store(address(anchorStateRegistry), bytes32(initializedSlot.slot), bytes32(0)); + vm.store(address(anchorStateRegistry), bytes32(retirementTimestampSlot.slot), bytes32(0)); uint256 newTimestamp = block.timestamp + 100; vm.warp(newTimestamp); @@ -241,7 +252,6 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { (Hash root, uint256 l2SequenceNumber) = anchorStateRegistry.getAnchorRoot(); GameType startingGameType = anchorStateRegistry.respectedGameType(); - StorageSlot memory initSlot = ForgeArtifacts.getSlot("AnchorStateRegistry", "_initialized"); address proxyAdminOwner = anchorStateRegistry.proxyAdminOwner(); uint256 initialTimestamp = block.timestamp + 200; @@ -252,7 +262,7 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { uint256 reinitTimestamp = block.timestamp + 200; vm.warp(reinitTimestamp); - vm.store(address(anchorStateRegistry), bytes32(initSlot.slot), bytes32(0)); + vm.store(address(anchorStateRegistry), bytes32(initializedSlot.slot), bytes32(0)); vm.prank(proxyAdminOwner); anchorStateRegistry.initialize( @@ -279,11 +289,8 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_TestInit { _sender != address(anchorStateRegistry.proxyAdmin()) && _sender != anchorStateRegistry.proxyAdminOwner() ); - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("AnchorStateRegistry", "_initialized"); - // Set the initialized slot to 0. - vm.store(address(anchorStateRegistry), bytes32(slot.slot), bytes32(0)); + vm.store(address(anchorStateRegistry), bytes32(initializedSlot.slot), bytes32(0)); // Expect the revert with `ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner` selector. vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); @@ -499,6 +506,7 @@ contract AnchorStateRegistry_GetStartingAnchorRoot_Test is AnchorStateRegistry_T _mockGamePastFinalityWithStatus(GameStatus.DEFENDER_WINS); uint256 expectedL2BlockNumber = validL2BlockNumber + 1; Claim expectedRoot = Claim.wrap(keccak256(abi.encode(123))); + Proposal memory startingAnchorRootBeforeUpdate = anchorStateRegistry.getStartingAnchorRoot(); // Mock the game's L2 block number to be greater than the starting anchor root block number. _mockGameL2SequenceNumber(expectedL2BlockNumber); @@ -509,9 +517,6 @@ contract AnchorStateRegistry_GetStartingAnchorRoot_Test is AnchorStateRegistry_T // Set the anchor game to the game proxy. anchorStateRegistry.setAnchorState(gameProxy); - // Grab the value of the starting anchor root before the update. - Proposal memory startingAnchorRootBeforeUpdate = anchorStateRegistry.getStartingAnchorRoot(); - // Verify the CURRENT anchor root has changed. (Hash currentRoot, uint256 currentL2BlockNumber) = anchorStateRegistry.getAnchorRoot(); assertEq(currentRoot.raw(), expectedRoot.raw()); @@ -522,9 +527,8 @@ contract AnchorStateRegistry_GetStartingAnchorRoot_Test is AnchorStateRegistry_T assertEq(startingAnchorRootAfterUpdate.root.raw(), startingAnchorRootBeforeUpdate.root.raw()); assertEq(startingAnchorRootAfterUpdate.l2SequenceNumber, startingAnchorRootBeforeUpdate.l2SequenceNumber); - // Explicitly assert they are different (assuming the new game has different values). - assertFalse(currentRoot.raw() == startingAnchorRootAfterUpdate.root.raw()); - assertFalse(currentL2BlockNumber == startingAnchorRootAfterUpdate.l2SequenceNumber); + assertNotEq(currentRoot.raw(), startingAnchorRootAfterUpdate.root.raw()); + assertNotEq(currentL2BlockNumber, startingAnchorRootAfterUpdate.l2SequenceNumber); } } @@ -630,14 +634,16 @@ contract AnchorStateRegistry_IsGameRetired_Test is AnchorStateRegistry_TestInit /// @title AnchorStateRegistry_IsGameResolved_Test /// @notice Tests the `isGameResolved` function of the `AnchorStateRegistry` contract. contract AnchorStateRegistry_IsGameResolved_Test is AnchorStateRegistry_TestInit { + function _mockResolvedGame(uint256 _resolvedAtTimestamp, GameStatus _status) internal { + _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp); + _mockGameResolvedAt(_resolvedAtTimestamp); + _mockGameStatus(_status); + } + /// @notice Tests that isGameResolved will return true if the game is resolved. /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameResolved_challengerWins_succeeds(uint256 _resolvedAtTimestamp) public { - // Bound resolvedAt to be less than or equal to current timestamp. - _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp); - - _mockGameResolvedAt(_resolvedAtTimestamp); - _mockGameStatus(GameStatus.CHALLENGER_WINS); + _mockResolvedGame(_resolvedAtTimestamp, GameStatus.CHALLENGER_WINS); // Game should be resolved. assertTrue(anchorStateRegistry.isGameResolved(gameProxy)); @@ -646,11 +652,7 @@ contract AnchorStateRegistry_IsGameResolved_Test is AnchorStateRegistry_TestInit /// @notice Tests that isGameResolved will return true if the game is resolved. /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameResolved_defenderWins_succeeds(uint256 _resolvedAtTimestamp) public { - // Bound resolvedAt to be less than or equal to current timestamp. - _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp); - - _mockGameResolvedAt(_resolvedAtTimestamp); - _mockGameStatus(GameStatus.DEFENDER_WINS); + _mockResolvedGame(_resolvedAtTimestamp, GameStatus.DEFENDER_WINS); // Game should be resolved. assertTrue(anchorStateRegistry.isGameResolved(gameProxy)); @@ -660,11 +662,7 @@ contract AnchorStateRegistry_IsGameResolved_Test is AnchorStateRegistry_TestInit /// resolved. /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameResolved_inProgressNotResolved_succeeds(uint256 _resolvedAtTimestamp) public { - // Bound resolvedAt to be less than or equal to current timestamp. - _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp); - - _mockGameResolvedAt(_resolvedAtTimestamp); - _mockGameStatus(GameStatus.IN_PROGRESS); + _mockResolvedGame(_resolvedAtTimestamp, GameStatus.IN_PROGRESS); // Game should not be resolved. assertFalse(anchorStateRegistry.isGameResolved(gameProxy)); @@ -728,13 +726,11 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni /// @notice Tests that isGameFinalized will return true if the game is finalized. /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameFinalized_isFinalized_succeeds(uint256 _resolvedAtTimestamp) public { - // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); + uint256 finalityDelay = _warpByFinalityDelay(); // Bound resolvedAt to be at least disputeGameFinalityDelaySeconds in the past. // Must be greater than 0. - _resolvedAtTimestamp = - bound(_resolvedAtTimestamp, 1, block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds() - 1); + _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp - finalityDelay - 1); _mockGameResolvedAt(_resolvedAtTimestamp); _mockGameStatus(GameStatus.DEFENDER_WINS); @@ -746,15 +742,10 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni /// @notice Tests that isGameFinalized will return false if the game is not finalized. /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameFinalized_isNotFinalized_succeeds(uint256 _resolvedAtTimestamp) public { - // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); + uint256 finalityDelay = _warpByFinalityDelay(); // Bound resolvedAt to be less than disputeGameFinalityDelaySeconds in the past. - _resolvedAtTimestamp = bound( - _resolvedAtTimestamp, - block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds(), - block.timestamp - ); + _resolvedAtTimestamp = bound(_resolvedAtTimestamp, block.timestamp - finalityDelay, block.timestamp); _mockGameResolvedAt(_resolvedAtTimestamp); @@ -764,8 +755,7 @@ contract AnchorStateRegistry_IsGameFinalized_Test is AnchorStateRegistry_TestIni /// @notice Tests that isGameFinalized will return false if the game is not resolved. function test_isGameFinalized_isNotResolved_succeeds() public { - // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); + _warpByFinalityDelay(); _mockGameStatus(GameStatus.IN_PROGRESS); @@ -780,12 +770,10 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @notice Tests that isGameClaimValid will return true if the game claim is valid. /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameClaimValid_claimIsValid_succeeds(uint256 _resolvedAtTimestamp) public { - // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); + uint256 finalityDelay = _warpByFinalityDelay(); // Bound resolvedAt to be at least disputeGameFinalityDelaySeconds in the past. - _resolvedAtTimestamp = - bound(_resolvedAtTimestamp, 1, block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds() - 1); + _resolvedAtTimestamp = bound(_resolvedAtTimestamp, 1, block.timestamp - finalityDelay - 1); _mockGameWasRespected(true); _mockGameResolvedAt(_resolvedAtTimestamp); @@ -839,15 +827,10 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_TestIn /// @notice Tests that isGameClaimValid will return false if the game is not finalized. /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. function testFuzz_isGameClaimValid_notFinalized_succeeds(uint256 _resolvedAtTimestamp) public { - // Warp forward by disputeGameFinalityDelaySeconds. - vm.warp(block.timestamp + anchorStateRegistry.disputeGameFinalityDelaySeconds()); + uint256 finalityDelay = _warpByFinalityDelay(); // Bound resolvedAt to be less than disputeGameFinalityDelaySeconds in the past. - _resolvedAtTimestamp = bound( - _resolvedAtTimestamp, - block.timestamp - anchorStateRegistry.disputeGameFinalityDelaySeconds(), - block.timestamp - ); + _resolvedAtTimestamp = bound(_resolvedAtTimestamp, block.timestamp - finalityDelay, block.timestamp); _mockGameResolvedAt(_resolvedAtTimestamp); @@ -996,9 +979,13 @@ contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_TestInit /// @notice Tests that setAnchorState will revert if the superchain is paused. function test_setAnchorState_superchainPaused_fails() public { + (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); + // Pause the superchain. _setPaused(true); _expectSetAnchorStateInvalid(gameProxy, address(gameProxy)); + + _assertCurrentAnchorRootEq(root, l2BlockNumber); } } From 83beebde0f845efb9011dd7e70e7eac6f2eb09e1 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 21:03:38 -0400 Subject: [PATCH 035/135] fmt --- test/L1/proofs/AggregateVerifier.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index 46a2d189..d708e4d4 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -393,5 +393,4 @@ contract AggregateVerifierTest is BaseTest { intermediateBlockInterval ); } - } From 8c7fbc02fbf2ec46ec226b5fa29eb81803a95386 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 21:06:36 -0400 Subject: [PATCH 036/135] Refactor Challenge.t.sol: streamline test cases by introducing helper functions for claim creation and proof generation, improving code clarity and maintainability. --- test/L1/proofs/Challenge.t.sol | 165 +++++++++++++-------------------- 1 file changed, 65 insertions(+), 100 deletions(-) diff --git a/test/L1/proofs/Challenge.t.sol b/test/L1/proofs/Challenge.t.sol index 977518aa..1ece1b62 100644 --- a/test/L1/proofs/Challenge.t.sol +++ b/test/L1/proofs/Challenge.t.sol @@ -2,9 +2,8 @@ pragma solidity 0.8.15; import { ClaimAlreadyResolved } from "src/libraries/bridge/Errors.sol"; -import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { GameStatus, Hash } from "src/libraries/bridge/Types.sol"; +import { GameStatus } from "src/libraries/bridge/Types.sol"; import { Claim } from "src/libraries/bridge/LibUDT.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; @@ -13,232 +12,194 @@ import { Verifier } from "src/L1/proofs/Verifier.sol"; import { BaseTest } from "./BaseTest.t.sol"; contract ChallengeTest is BaseTest { - function testChallengeTEEProofWithZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; + uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; - // Create game with TEE proof - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + function testChallengeTEEProofWithZKProof() public { + Claim rootClaim1 = _advanceL2BlockAndClaim("tee"); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof ); - // Challenge game with ZK proof - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + Claim rootClaim2 = _claim("zk"); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); vm.prank(ZK_PROVER); - game.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + game.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); // 2 proofs so that it can decrease to 1 if ZK is nullified and then the TEE proof can resolve assertEq(game.proofCount(), 2); - // Resolve after SLOW_FINALIZATION_DELAY vm.warp(block.timestamp + 7 days); game.resolve(); assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(game.bondRecipient(), ZK_PROVER); - uint256 balanceBefore = ZK_PROVER.balance; - game.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game.claimCredit(); - assertEq(ZK_PROVER.balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game)), 0); + _claimCreditAfterDelay(game, ZK_PROVER); } function testChallengeFailsIfNoTEEProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - // Create first game with ZK proof (no TEE proof) - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + Claim rootClaim1 = _advanceL2BlockAndClaim("zk1"); bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); AggregateVerifier game1 = _createAggregateVerifierGame( ZK_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), zkProof1 ); - // Challenge game with ZK proof bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) ); - game1.challenge(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim1.raw()); + game1.challenge(zkProof2, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim1.raw()); } function testChallengeFailsIfNotZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + Claim rootClaim1 = _advanceL2BlockAndClaim("tee1"); bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof1 ); - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + Claim rootClaim2 = _claim("tee2"); bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); vm.expectRevert(AggregateVerifier.InvalidProofType.selector); - game1.challenge(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + game1.challenge(teeProof2, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); } function testChallengeFailsIfGameAlreadyResolved() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + Claim rootClaim1 = _advanceL2BlockAndClaim("tee"); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game1 = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof ); - // Resolve game1 vm.warp(block.timestamp + 7 days + 1); game1.resolve(); - // Try to challenge game1 - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); + Claim rootClaim2 = _claim("zk1"); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); vm.expectRevert(ClaimAlreadyResolved.selector); - game1.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + game1.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); } function testChallengeFailsIfParentGameStatusIsChallenged() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - // create parent game - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + Claim rootClaim1 = _advanceL2BlockAndClaim("tee"); bytes memory parentProof = _generateProof("parent-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier parentGame = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), parentProof ); - currentL2BlockNumber += BLOCK_INTERVAL; - - // create child game - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + Claim rootClaim2 = _advanceL2BlockAndClaim("tee2"); bytes memory childProof = _generateProof("child-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier childGame = _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, address(parentGame), childProof); - // blacklist parent game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); - // challenge child game with ZK proof - Claim rootClaim3 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + Claim rootClaim3 = _claim("zk"); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); - childGame.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim3.raw()); + childGame.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim3.raw()); } function testChallengeFailsIfGameItselfIsBlacklisted() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + Claim rootClaim = _advanceL2BlockAndClaim("tee"); bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof ); - // blacklist game anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); - // challenge game - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + Claim rootClaim2 = _claim("zk"); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); vm.expectRevert(AggregateVerifier.InvalidGame.selector); - game.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + game.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); } function testChallengeFailsAfterTEENullification() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); + Claim rootClaim1 = _advanceL2BlockAndClaim("tee1"); bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); AggregateVerifier game = _createAggregateVerifierGame( TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof1 ); - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); + Claim rootClaim2 = _claim("tee2"); bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + game.nullify(teeProof2, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); - // challenge game — TEE proof was nullified, so MissingProof(TEE) is expected - Claim rootClaim3 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); + // TEE was nullified, so a ZK challenge cannot find the required TEE proof. + Claim rootClaim3 = _claim("zk"); bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) ); - game.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim3.raw()); + game.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim3.raw()); } function testChallengeFailsAfterZKNullification() public { - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee"))); + Claim rootClaim1 = _advanceL2BlockAndClaim("tee"); bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); - // create game with both proofs AggregateVerifier game = _createAggregateVerifierGame( ZK_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof ); game.verifyProposalProof(zkProof1); - // nullify ZK proof - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); + Claim rootClaim2 = _claim("zk2"); bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); - game.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + game.nullify(zkProof2, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); - // challenge game — ZK is nullified so Nullified() is expected - Claim rootClaim3 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk3"))); + // ZK was nullified globally, so another ZK challenge proof is rejected by the verifier. + Claim rootClaim3 = _claim("zk3"); bytes memory zkProof3 = _generateProof("zk-proof-3", AggregateVerifier.ProofType.ZK); vm.expectRevert(Verifier.Nullified.selector); - game.challenge(zkProof3, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim3.raw()); + game.challenge(zkProof3, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim3.raw()); } /// @notice A TEE+ZK challenge on game A is cleared when another game nullifies the shared ZK verifier; A then /// resolves as defender after `SLOW_FINALIZATION_DELAY`. function testChallengeRemovedWhenZkVerifierNullifiedByOtherGame() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee-challenge"))); + Claim rootClaimA = _advanceL2BlockAndClaim("tee-challenge"); bytes memory teeProofA = _generateProof("tee-ch-a", AggregateVerifier.ProofType.TEE); AggregateVerifier gameA = _createAggregateVerifierGame( TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA ); - Claim rootChallenge = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-challenge"))); + Claim rootChallenge = _claim("zk-challenge"); bytes memory zkChallenge = _generateProof("zk-challenge", AggregateVerifier.ProofType.ZK); vm.prank(ZK_PROVER); - gameA.challenge(zkChallenge, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootChallenge.raw()); + gameA.challenge(zkChallenge, LAST_INTERMEDIATE_ROOT_INDEX, rootChallenge.raw()); assertEq(gameA.proofCount(), 2); assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-only-b"))); + Claim rootClaimB = _advanceL2BlockAndClaim("zk-only-b"); bytes memory zkProofB = _generateProof("zk-init-b", AggregateVerifier.ProofType.ZK); AggregateVerifier gameB = _createAggregateVerifierGame(ZK_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), zkProofB); - Claim rootNullifyB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-nullify-b"))); + Claim rootNullifyB = _claim("zk-nullify-b"); bytes memory zkNullifyB = _generateProof("zk-nullify-b", AggregateVerifier.ProofType.ZK); - uint256 lastIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; - gameB.nullify(zkNullifyB, lastIdx, rootNullifyB.raw()); + gameB.nullify(zkNullifyB, LAST_INTERMEDIATE_ROOT_INDEX, rootNullifyB.raw()); assertTrue(zkVerifier.nullified()); assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); @@ -250,44 +211,35 @@ contract ChallengeTest is BaseTest { assertEq(uint8(gameA.resolve()), uint8(GameStatus.DEFENDER_WINS)); assertEq(gameA.bondRecipient(), TEE_PROVER); - uint256 balanceBefore = TEE_PROVER.balance; - gameA.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - gameA.claimCredit(); - assertEq(TEE_PROVER.balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(gameA)), 0); + _claimCreditAfterDelay(gameA, TEE_PROVER); } /// @notice Game A is created with TEE and challenged with ZK. Another game nullifies the shared TEE verifier. /// The first `resolve` persists the TEE refutation; after `SLOW_FINALIZATION_DELAY`, A finalizes as /// challenger wins and the bond goes to the ZK challenger. function testChallengeWinsWhenSharedTeeVerifierNullifiedByOtherGame() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee-challenge-tee-null"))); + Claim rootClaimA = _advanceL2BlockAndClaim("tee-challenge-tee-null"); bytes memory teeProofA = _generateProof("tee-proof-a", AggregateVerifier.ProofType.TEE); AggregateVerifier gameA = _createAggregateVerifierGame( TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA ); - Claim rootChallenge = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-challenge"))); + Claim rootChallenge = _claim("zk-challenge"); bytes memory zkChallenge = _generateProof("zk-challenge", AggregateVerifier.ProofType.ZK); vm.prank(ZK_PROVER); - gameA.challenge(zkChallenge, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootChallenge.raw()); + gameA.challenge(zkChallenge, LAST_INTERMEDIATE_ROOT_INDEX, rootChallenge.raw()); assertEq(gameA.proofCount(), 2); assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee-only-b"))); + Claim rootClaimB = _advanceL2BlockAndClaim("tee-only-b"); bytes memory teeProofB = _generateProof("tee-init-b", AggregateVerifier.ProofType.TEE); AggregateVerifier gameB = _createAggregateVerifierGame(TEE_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), teeProofB); - Claim rootNullifyB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee-nullify-b"))); + Claim rootNullifyB = _claim("tee-nullify-b"); bytes memory teeNullifyB = _generateProof("tee-nullify-b", AggregateVerifier.ProofType.TEE); - uint256 lastIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; - gameB.nullify(teeNullifyB, lastIdx, rootNullifyB.raw()); + gameB.nullify(teeNullifyB, LAST_INTERMEDIATE_ROOT_INDEX, rootNullifyB.raw()); assertTrue(teeVerifier.nullified()); assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); @@ -300,11 +252,24 @@ contract ChallengeTest is BaseTest { assertEq(uint8(gameA.resolve()), uint8(GameStatus.CHALLENGER_WINS)); assertEq(gameA.bondRecipient(), ZK_PROVER); - uint256 balanceBefore = ZK_PROVER.balance; - gameA.claimCredit(); + _claimCreditAfterDelay(gameA, ZK_PROVER); + } + + function _advanceL2BlockAndClaim(bytes memory salt) private returns (Claim) { + currentL2BlockNumber += BLOCK_INTERVAL; + return _claim(salt); + } + + function _claim(bytes memory salt) private view returns (Claim) { + return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, salt))); + } + + function _claimCreditAfterDelay(AggregateVerifier game, address recipient) private { + uint256 balanceBefore = recipient.balance; + game.claimCredit(); vm.warp(block.timestamp + DELAYED_WETH_DELAY); - gameA.claimCredit(); - assertEq(ZK_PROVER.balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(gameA)), 0); + game.claimCredit(); + assertEq(recipient.balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); } } From 5171e6826855dea0cf3aff12d54553939fa027f2 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 21:14:00 -0400 Subject: [PATCH 037/135] Refactor Challenge.t.sol: simplify test cases by consolidating game creation and proof handling into helper functions, enhancing code clarity and maintainability. --- test/L1/proofs/Challenge.t.sol | 234 +++++++++++++-------------------- 1 file changed, 91 insertions(+), 143 deletions(-) diff --git a/test/L1/proofs/Challenge.t.sol b/test/L1/proofs/Challenge.t.sol index 1ece1b62..c95f71c9 100644 --- a/test/L1/proofs/Challenge.t.sol +++ b/test/L1/proofs/Challenge.t.sol @@ -15,246 +15,186 @@ contract ChallengeTest is BaseTest { uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; function testChallengeTEEProofWithZKProof() public { - Claim rootClaim1 = _advanceL2BlockAndClaim("tee"); - bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + AggregateVerifier game = + _createGame(TEE_PROVER, "tee", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof - ); - - Claim rootClaim2 = _claim("zk"); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - - vm.prank(ZK_PROVER); - game.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); - - assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + _challengeWithZk(game, "zk"); + _assertStatus(game, GameStatus.IN_PROGRESS); // 2 proofs so that it can decrease to 1 if ZK is nullified and then the TEE proof can resolve assertEq(game.proofCount(), 2); - vm.warp(block.timestamp + 7 days); - game.resolve(); - - assertEq(uint8(game.status()), uint8(GameStatus.CHALLENGER_WINS)); + vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY()); + _assertResolveStatus(game, GameStatus.CHALLENGER_WINS); assertEq(game.bondRecipient(), ZK_PROVER); _claimCreditAfterDelay(game, ZK_PROVER); } function testChallengeFailsIfNoTEEProof() public { - Claim rootClaim1 = _advanceL2BlockAndClaim("zk1"); - bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); - - AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), zkProof1 - ); - - bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); + AggregateVerifier game = + _createGame(ZK_PROVER, "zk1", "zk-proof-1", AggregateVerifier.ProofType.ZK, address(anchorStateRegistry)); vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) ); - game1.challenge(zkProof2, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim1.raw()); + game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); } function testChallengeFailsIfNotZKProof() public { - Claim rootClaim1 = _advanceL2BlockAndClaim("tee1"); - bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof1 + AggregateVerifier game = _createGame( + TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - Claim rootClaim2 = _claim("tee2"); - bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - vm.expectRevert(AggregateVerifier.InvalidProofType.selector); - game1.challenge(teeProof2, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); + game.challenge(_proofOfType(AggregateVerifier.ProofType.TEE), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); } function testChallengeFailsIfGameAlreadyResolved() public { - Claim rootClaim1 = _advanceL2BlockAndClaim("tee"); - bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); + AggregateVerifier game = + _createGame(TEE_PROVER, "tee", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof - ); - - vm.warp(block.timestamp + 7 days + 1); - game1.resolve(); - - Claim rootClaim2 = _claim("zk1"); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY() + 1); + game.resolve(); vm.expectRevert(ClaimAlreadyResolved.selector); - game1.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); + game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); } function testChallengeFailsIfParentGameStatusIsChallenged() public { - Claim rootClaim1 = _advanceL2BlockAndClaim("tee"); - bytes memory parentProof = _generateProof("parent-proof", AggregateVerifier.ProofType.TEE); - - AggregateVerifier parentGame = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), parentProof + AggregateVerifier parentGame = _createGame( + TEE_PROVER, "tee", "parent-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - Claim rootClaim2 = _advanceL2BlockAndClaim("tee2"); - bytes memory childProof = _generateProof("child-proof", AggregateVerifier.ProofType.TEE); - AggregateVerifier childGame = - _createAggregateVerifierGame(TEE_PROVER, rootClaim2, currentL2BlockNumber, address(parentGame), childProof); + _createGame(TEE_PROVER, "tee2", "child-proof", AggregateVerifier.ProofType.TEE, address(parentGame)); anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); - Claim rootClaim3 = _claim("zk"); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); - childGame.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim3.raw()); + childGame.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); } function testChallengeFailsIfGameItselfIsBlacklisted() public { - Claim rootClaim = _advanceL2BlockAndClaim("tee"); - bytes memory proof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim, currentL2BlockNumber, address(anchorStateRegistry), proof - ); + AggregateVerifier game = + _createGame(TEE_PROVER, "tee", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); - Claim rootClaim2 = _claim("zk"); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - vm.expectRevert(AggregateVerifier.InvalidGame.selector); - game.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); + game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); } function testChallengeFailsAfterTEENullification() public { - Claim rootClaim1 = _advanceL2BlockAndClaim("tee1"); - bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof1 + AggregateVerifier game = _createGame( + TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - Claim rootClaim2 = _claim("tee2"); - bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - - game.nullify(teeProof2, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); - + _nullify(game, AggregateVerifier.ProofType.TEE, "tee2"); // TEE was nullified, so a ZK challenge cannot find the required TEE proof. - Claim rootClaim3 = _claim("zk"); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) ); - game.challenge(zkProof, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim3.raw()); + game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); } function testChallengeFailsAfterZKNullification() public { - Claim rootClaim1 = _advanceL2BlockAndClaim("tee"); - bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); - - AggregateVerifier game = _createAggregateVerifierGame( - ZK_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof - ); - game.verifyProposalProof(zkProof1); - - Claim rootClaim2 = _claim("zk2"); - bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); - game.nullify(zkProof2, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim2.raw()); + AggregateVerifier game = + _createGame(ZK_PROVER, "tee", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); + _provideProof(game, ZK_PROVER, _proofOfType(AggregateVerifier.ProofType.ZK)); + _nullify(game, AggregateVerifier.ProofType.ZK, "zk2"); // ZK was nullified globally, so another ZK challenge proof is rejected by the verifier. - Claim rootClaim3 = _claim("zk3"); - bytes memory zkProof3 = _generateProof("zk-proof-3", AggregateVerifier.ProofType.ZK); - vm.expectRevert(Verifier.Nullified.selector); - game.challenge(zkProof3, LAST_INTERMEDIATE_ROOT_INDEX, rootClaim3.raw()); + game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, _claim("zk3").raw()); } - /// @notice A TEE+ZK challenge on game A is cleared when another game nullifies the shared ZK verifier; A then - /// resolves as defender after `SLOW_FINALIZATION_DELAY`. function testChallengeRemovedWhenZkVerifierNullifiedByOtherGame() public { - Claim rootClaimA = _advanceL2BlockAndClaim("tee-challenge"); - bytes memory teeProofA = _generateProof("tee-ch-a", AggregateVerifier.ProofType.TEE); - AggregateVerifier gameA = _createAggregateVerifierGame( - TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA + AggregateVerifier gameA = _createGame( + TEE_PROVER, "tee-challenge", "tee-ch-a", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - Claim rootChallenge = _claim("zk-challenge"); - bytes memory zkChallenge = _generateProof("zk-challenge", AggregateVerifier.ProofType.ZK); - vm.prank(ZK_PROVER); - gameA.challenge(zkChallenge, LAST_INTERMEDIATE_ROOT_INDEX, rootChallenge.raw()); - + _challengeWithZk(gameA, "zk-challenge"); assertEq(gameA.proofCount(), 2); assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); - Claim rootClaimB = _advanceL2BlockAndClaim("zk-only-b"); - bytes memory zkProofB = _generateProof("zk-init-b", AggregateVerifier.ProofType.ZK); AggregateVerifier gameB = - _createAggregateVerifierGame(ZK_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), zkProofB); - - Claim rootNullifyB = _claim("zk-nullify-b"); - bytes memory zkNullifyB = _generateProof("zk-nullify-b", AggregateVerifier.ProofType.ZK); - gameB.nullify(zkNullifyB, LAST_INTERMEDIATE_ROOT_INDEX, rootNullifyB.raw()); + _createGame(ZK_PROVER, "zk-only-b", "zk-init-b", AggregateVerifier.ProofType.ZK, address(gameA)); + _nullify(gameB, AggregateVerifier.ProofType.ZK, "zk-nullify-b"); assertTrue(zkVerifier.nullified()); - assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); + _assertResolveStatus(gameA, GameStatus.IN_PROGRESS); assertEq(gameA.proofCount(), 1); assertEq(gameA.counteredByIntermediateRootIndexPlusOne(), 0); assertEq(address(gameA.zkProver()), address(0)); - vm.warp(block.timestamp + 7 days); - assertEq(uint8(gameA.resolve()), uint8(GameStatus.DEFENDER_WINS)); + vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); + _assertResolveStatus(gameA, GameStatus.DEFENDER_WINS); assertEq(gameA.bondRecipient(), TEE_PROVER); _claimCreditAfterDelay(gameA, TEE_PROVER); } - /// @notice Game A is created with TEE and challenged with ZK. Another game nullifies the shared TEE verifier. - /// The first `resolve` persists the TEE refutation; after `SLOW_FINALIZATION_DELAY`, A finalizes as - /// challenger wins and the bond goes to the ZK challenger. function testChallengeWinsWhenSharedTeeVerifierNullifiedByOtherGame() public { - Claim rootClaimA = _advanceL2BlockAndClaim("tee-challenge-tee-null"); - bytes memory teeProofA = _generateProof("tee-proof-a", AggregateVerifier.ProofType.TEE); - AggregateVerifier gameA = _createAggregateVerifierGame( - TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA + AggregateVerifier gameA = _createGame( + TEE_PROVER, + "tee-challenge-tee-null", + "tee-proof-a", + AggregateVerifier.ProofType.TEE, + address(anchorStateRegistry) ); - Claim rootChallenge = _claim("zk-challenge"); - bytes memory zkChallenge = _generateProof("zk-challenge", AggregateVerifier.ProofType.ZK); - vm.prank(ZK_PROVER); - gameA.challenge(zkChallenge, LAST_INTERMEDIATE_ROOT_INDEX, rootChallenge.raw()); - + _challengeWithZk(gameA, "zk-challenge"); assertEq(gameA.proofCount(), 2); assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); - Claim rootClaimB = _advanceL2BlockAndClaim("tee-only-b"); - bytes memory teeProofB = _generateProof("tee-init-b", AggregateVerifier.ProofType.TEE); AggregateVerifier gameB = - _createAggregateVerifierGame(TEE_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), teeProofB); - - Claim rootNullifyB = _claim("tee-nullify-b"); - bytes memory teeNullifyB = _generateProof("tee-nullify-b", AggregateVerifier.ProofType.TEE); - gameB.nullify(teeNullifyB, LAST_INTERMEDIATE_ROOT_INDEX, rootNullifyB.raw()); + _createGame(TEE_PROVER, "tee-only-b", "tee-init-b", AggregateVerifier.ProofType.TEE, address(gameA)); + _nullify(gameB, AggregateVerifier.ProofType.TEE, "tee-nullify-b"); assertTrue(teeVerifier.nullified()); - assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); + _assertResolveStatus(gameA, GameStatus.IN_PROGRESS); assertEq(gameA.proofCount(), 1); assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); assertEq(address(gameA.teeProver()), address(0)); assertEq(gameA.zkProver(), ZK_PROVER); - vm.warp(block.timestamp + 7 days); - assertEq(uint8(gameA.resolve()), uint8(GameStatus.CHALLENGER_WINS)); + vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); + _assertResolveStatus(gameA, GameStatus.CHALLENGER_WINS); assertEq(gameA.bondRecipient(), ZK_PROVER); _claimCreditAfterDelay(gameA, ZK_PROVER); } + function _createGame( + address prover, + bytes memory claimSalt, + bytes memory proofSalt, + AggregateVerifier.ProofType proofType, + address parent + ) + private + returns (AggregateVerifier) + { + Claim rootClaim = _advanceL2BlockAndClaim(claimSalt); + bytes memory proof = _generateProof(proofSalt, proofType); + return _createAggregateVerifierGame(prover, rootClaim, currentL2BlockNumber, parent, proof); + } + + function _challengeWithZk(AggregateVerifier game, bytes memory claimSalt) private { + vm.prank(ZK_PROVER); + game.challenge( + _proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw() + ); + } + + function _nullify(AggregateVerifier game, AggregateVerifier.ProofType proofType, bytes memory claimSalt) private { + game.nullify(_proofOfType(proofType), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw()); + } + + function _proofOfType(AggregateVerifier.ProofType proofType) private pure returns (bytes memory) { + return abi.encodePacked(uint8(proofType), bytes1(0)); + } + function _advanceL2BlockAndClaim(bytes memory salt) private returns (Claim) { currentL2BlockNumber += BLOCK_INTERVAL; return _claim(salt); @@ -264,6 +204,14 @@ contract ChallengeTest is BaseTest { return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, salt))); } + function _assertStatus(AggregateVerifier game, GameStatus expectedStatus) private view { + assertEq(uint8(game.status()), uint8(expectedStatus)); + } + + function _assertResolveStatus(AggregateVerifier game, GameStatus expectedStatus) private { + assertEq(uint8(game.resolve()), uint8(expectedStatus)); + } + function _claimCreditAfterDelay(AggregateVerifier game, address recipient) private { uint256 balanceBefore = recipient.balance; game.claimCredit(); From 3254bc8c6d02b0f9e7f4ab98d7a0bc2bdf00eddb Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 21:18:40 -0400 Subject: [PATCH 038/135] Refactor Challenge.t.sol: enhance test clarity by replacing direct game challenge calls with a dedicated helper function, improving code maintainability and readability. --- test/L1/proofs/Challenge.t.sol | 83 ++++++++++++++++------------------ 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/test/L1/proofs/Challenge.t.sol b/test/L1/proofs/Challenge.t.sol index c95f71c9..0aca7e4d 100644 --- a/test/L1/proofs/Challenge.t.sol +++ b/test/L1/proofs/Challenge.t.sol @@ -19,15 +19,10 @@ contract ChallengeTest is BaseTest { _createGame(TEE_PROVER, "tee", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); _challengeWithZk(game, "zk"); - _assertStatus(game, GameStatus.IN_PROGRESS); - // 2 proofs so that it can decrease to 1 if ZK is nullified and then the TEE proof can resolve + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); assertEq(game.proofCount(), 2); - vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY()); - _assertResolveStatus(game, GameStatus.CHALLENGER_WINS); - assertEq(game.bondRecipient(), ZK_PROVER); - - _claimCreditAfterDelay(game, ZK_PROVER); + _resolveAfterSlowDelayAndClaim(game, GameStatus.CHALLENGER_WINS, ZK_PROVER); } function testChallengeFailsIfNoTEEProof() public { @@ -37,7 +32,7 @@ contract ChallengeTest is BaseTest { vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) ); - game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); + _challenge(game, AggregateVerifier.ProofType.ZK, bytes32(0)); } function testChallengeFailsIfNotZKProof() public { @@ -46,7 +41,7 @@ contract ChallengeTest is BaseTest { ); vm.expectRevert(AggregateVerifier.InvalidProofType.selector); - game.challenge(_proofOfType(AggregateVerifier.ProofType.TEE), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); + _challenge(game, AggregateVerifier.ProofType.TEE, bytes32(0)); } function testChallengeFailsIfGameAlreadyResolved() public { @@ -57,7 +52,7 @@ contract ChallengeTest is BaseTest { game.resolve(); vm.expectRevert(ClaimAlreadyResolved.selector); - game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); + _challenge(game, AggregateVerifier.ProofType.ZK, bytes32(0)); } function testChallengeFailsIfParentGameStatusIsChallenged() public { @@ -71,7 +66,7 @@ contract ChallengeTest is BaseTest { anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(parentGame))); vm.expectRevert(AggregateVerifier.InvalidParentGame.selector); - childGame.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); + _challenge(childGame, AggregateVerifier.ProofType.ZK, bytes32(0)); } function testChallengeFailsIfGameItselfIsBlacklisted() public { @@ -81,7 +76,7 @@ contract ChallengeTest is BaseTest { anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); vm.expectRevert(AggregateVerifier.InvalidGame.selector); - game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); + _challenge(game, AggregateVerifier.ProofType.ZK, bytes32(0)); } function testChallengeFailsAfterTEENullification() public { @@ -90,11 +85,10 @@ contract ChallengeTest is BaseTest { ); _nullify(game, AggregateVerifier.ProofType.TEE, "tee2"); - // TEE was nullified, so a ZK challenge cannot find the required TEE proof. vm.expectRevert( abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.TEE) ); - game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, bytes32(0)); + _challenge(game, AggregateVerifier.ProofType.ZK, bytes32(0)); } function testChallengeFailsAfterZKNullification() public { @@ -103,9 +97,8 @@ contract ChallengeTest is BaseTest { _provideProof(game, ZK_PROVER, _proofOfType(AggregateVerifier.ProofType.ZK)); _nullify(game, AggregateVerifier.ProofType.ZK, "zk2"); - // ZK was nullified globally, so another ZK challenge proof is rejected by the verifier. vm.expectRevert(Verifier.Nullified.selector); - game.challenge(_proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, _claim("zk3").raw()); + _challenge(game, AggregateVerifier.ProofType.ZK, _claim("zk3").raw()); } function testChallengeRemovedWhenZkVerifierNullifiedByOtherGame() public { @@ -114,24 +107,19 @@ contract ChallengeTest is BaseTest { ); _challengeWithZk(gameA, "zk-challenge"); - assertEq(gameA.proofCount(), 2); - assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); + _assertChallengeRecorded(gameA); AggregateVerifier gameB = _createGame(ZK_PROVER, "zk-only-b", "zk-init-b", AggregateVerifier.ProofType.ZK, address(gameA)); _nullify(gameB, AggregateVerifier.ProofType.ZK, "zk-nullify-b"); assertTrue(zkVerifier.nullified()); - _assertResolveStatus(gameA, GameStatus.IN_PROGRESS); + _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); assertEq(gameA.proofCount(), 1); assertEq(gameA.counteredByIntermediateRootIndexPlusOne(), 0); assertEq(address(gameA.zkProver()), address(0)); - vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); - _assertResolveStatus(gameA, GameStatus.DEFENDER_WINS); - assertEq(gameA.bondRecipient(), TEE_PROVER); - - _claimCreditAfterDelay(gameA, TEE_PROVER); + _resolveAfterSlowDelayAndClaim(gameA, GameStatus.DEFENDER_WINS, TEE_PROVER); } function testChallengeWinsWhenSharedTeeVerifierNullifiedByOtherGame() public { @@ -144,25 +132,20 @@ contract ChallengeTest is BaseTest { ); _challengeWithZk(gameA, "zk-challenge"); - assertEq(gameA.proofCount(), 2); - assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); + _assertChallengeRecorded(gameA); AggregateVerifier gameB = _createGame(TEE_PROVER, "tee-only-b", "tee-init-b", AggregateVerifier.ProofType.TEE, address(gameA)); _nullify(gameB, AggregateVerifier.ProofType.TEE, "tee-nullify-b"); assertTrue(teeVerifier.nullified()); - _assertResolveStatus(gameA, GameStatus.IN_PROGRESS); + _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); assertEq(gameA.proofCount(), 1); assertGt(gameA.counteredByIntermediateRootIndexPlusOne(), 0); assertEq(address(gameA.teeProver()), address(0)); assertEq(gameA.zkProver(), ZK_PROVER); - vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); - _assertResolveStatus(gameA, GameStatus.CHALLENGER_WINS); - assertEq(gameA.bondRecipient(), ZK_PROVER); - - _claimCreditAfterDelay(gameA, ZK_PROVER); + _resolveAfterSlowDelayAndClaim(gameA, GameStatus.CHALLENGER_WINS, ZK_PROVER); } function _createGame( @@ -175,16 +158,19 @@ contract ChallengeTest is BaseTest { private returns (AggregateVerifier) { - Claim rootClaim = _advanceL2BlockAndClaim(claimSalt); + currentL2BlockNumber += BLOCK_INTERVAL; + Claim rootClaim = _claim(claimSalt); bytes memory proof = _generateProof(proofSalt, proofType); return _createAggregateVerifierGame(prover, rootClaim, currentL2BlockNumber, parent, proof); } + function _challenge(AggregateVerifier game, AggregateVerifier.ProofType proofType, bytes32 claimRoot) private { + game.challenge(_proofOfType(proofType), LAST_INTERMEDIATE_ROOT_INDEX, claimRoot); + } + function _challengeWithZk(AggregateVerifier game, bytes memory claimSalt) private { vm.prank(ZK_PROVER); - game.challenge( - _proofOfType(AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw() - ); + _challenge(game, AggregateVerifier.ProofType.ZK, _claim(claimSalt).raw()); } function _nullify(AggregateVerifier game, AggregateVerifier.ProofType proofType, bytes memory claimSalt) private { @@ -195,23 +181,32 @@ contract ChallengeTest is BaseTest { return abi.encodePacked(uint8(proofType), bytes1(0)); } - function _advanceL2BlockAndClaim(bytes memory salt) private returns (Claim) { - currentL2BlockNumber += BLOCK_INTERVAL; - return _claim(salt); - } - function _claim(bytes memory salt) private view returns (Claim) { return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, salt))); } - function _assertStatus(AggregateVerifier game, GameStatus expectedStatus) private view { - assertEq(uint8(game.status()), uint8(expectedStatus)); + function _assertChallengeRecorded(AggregateVerifier game) private view { + assertEq(game.proofCount(), 2); + assertGt(game.counteredByIntermediateRootIndexPlusOne(), 0); } - function _assertResolveStatus(AggregateVerifier game, GameStatus expectedStatus) private { + function _resolveAndAssertStatus(AggregateVerifier game, GameStatus expectedStatus) private { assertEq(uint8(game.resolve()), uint8(expectedStatus)); } + function _resolveAfterSlowDelayAndClaim( + AggregateVerifier game, + GameStatus expectedStatus, + address recipient + ) + private + { + vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY()); + _resolveAndAssertStatus(game, expectedStatus); + assertEq(game.bondRecipient(), recipient); + _claimCreditAfterDelay(game, recipient); + } + function _claimCreditAfterDelay(AggregateVerifier game, address recipient) private { uint256 balanceBefore = recipient.balance; game.claimCredit(); From d2e2e90fe27ed7429d24a1bec930900b307b6cf6 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 21:25:46 -0400 Subject: [PATCH 039/135] Refactor DelayedWETH.t.sol: rename test initialization contract for clarity, introduce constants for default values, and enhance helper functions for improved test maintainability and readability. --- test/L1/proofs/DelayedWETH.t.sol | 342 +++++++++++-------------------- 1 file changed, 121 insertions(+), 221 deletions(-) diff --git a/test/L1/proofs/DelayedWETH.t.sol b/test/L1/proofs/DelayedWETH.t.sol index 9a3e65cb..5105e273 100644 --- a/test/L1/proofs/DelayedWETH.t.sol +++ b/test/L1/proofs/DelayedWETH.t.sol @@ -7,8 +7,6 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; import { Burn } from "src/libraries/Burn.sol"; -import "src/libraries/bridge/Types.sol"; -import "src/libraries/bridge/Errors.sol"; // Interfaces import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; @@ -26,7 +24,7 @@ contract DelayedWETH_FallbackGasUser_Harness { gas = _gas; } - /// @notice Burn gas on fallback; + /// @notice Burn gas on fallback. fallback() external payable { Burn.gas(gas); } @@ -51,23 +49,28 @@ contract DelayedWETH_FallbackReverter_Harness { } } -/// @title DelayedWETH_TestInit -/// @notice Reusable test initialization for `DelayedWETH` tests. -abstract contract DelayedWETH_TestInit is CommonTest { +/// @title DelayedWETH_TestBase +/// @notice Reusable test utilities for `DelayedWETH` tests. +abstract contract DelayedWETH_TestBase is CommonTest { + address internal constant DUMMY_SYSTEM_CONFIG = address(1234); + uint256 internal constant DEFAULT_AMOUNT = 1 ether; + event Approval(address indexed src, address indexed guy, uint256 wad); - event Transfer(address indexed src, address indexed dst, uint256 wad); - event Deposit(address indexed dst, uint256 wad); event Withdrawal(address indexed src, uint256 wad); - event Unwrap(address indexed src, uint256 wad); - - function setUp() public virtual override { - super.setUp(); - } } /// @title DelayedWETH_Initialize_Test /// @notice Tests the `initialize` function of the `DelayedWETH` contract. -contract DelayedWETH_Initialize_Test is DelayedWETH_TestInit { +contract DelayedWETH_Initialize_Test is DelayedWETH_TestBase { + bytes32 internal initializedSlot; + + function setUp() public override { + super.setUp(); + + StorageSlot memory slot = ForgeArtifacts.getSlot("DelayedWETH", "_initialized"); + initializedSlot = bytes32(slot.slot); + } + /// @notice Tests that initialization is successful. function test_initialize_succeeds() public view { assertEq(delayedWeth.proxyAdminOwner(), proxyAdminOwner); @@ -79,14 +82,9 @@ contract DelayedWETH_Initialize_Test is DelayedWETH_TestInit { /// but confirms that the initValue is not incremented incorrectly if an upgrade /// function is not present. function test_initialize_correctInitializerValue_succeeds() public view { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("DelayedWETH", "_initialized"); - - // Get the initializer value. - bytes32 slotVal = vm.load(address(delayedWeth), bytes32(slot.slot)); + bytes32 slotVal = vm.load(address(delayedWeth), initializedSlot); uint8 val = uint8(uint256(slotVal) & 0xFF); - // Assert that the initializer value matches the expected value. assertEq(val, delayedWeth.initVersion()); } @@ -94,32 +92,24 @@ contract DelayedWETH_Initialize_Test is DelayedWETH_TestInit { /// owner. /// @param _sender The address of the sender to test. function testFuzz_initialize_notProxyAdminOrProxyAdminOwner_reverts(address _sender) public { - // Prank as the not ProxyAdmin or ProxyAdmin owner. vm.assume(_sender != address(delayedWeth.proxyAdmin()) && _sender != delayedWeth.proxyAdminOwner()); - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("DelayedWETH", "_initialized"); - - // Set the initialized slot to 0. - vm.store(address(delayedWeth), bytes32(slot.slot), bytes32(0)); + vm.store(address(delayedWeth), initializedSlot, bytes32(0)); - // Expect the revert with `ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner` selector. vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); - - // Call the `initialize` function with the sender. vm.prank(_sender); - delayedWeth.initialize(ISystemConfig(address(1234))); + delayedWeth.initialize(ISystemConfig(DUMMY_SYSTEM_CONFIG)); } } /// @title DelayedWETH_Unlock_Test /// @notice Tests the `unlock` function of the `DelayedWETH` contract. -contract DelayedWETH_Unlock_Test is DelayedWETH_TestInit { +contract DelayedWETH_Unlock_Test is DelayedWETH_TestBase { /// @notice Tests that unlocking once is successful. function test_unlock_once_succeeds() public { - delayedWeth.unlock(alice, 1 ether); + delayedWeth.unlock(alice, DEFAULT_AMOUNT); (uint256 amount, uint256 timestamp) = delayedWeth.withdrawals(address(this), alice); - assertEq(amount, 1 ether); + assertEq(amount, DEFAULT_AMOUNT); assertEq(timestamp, block.timestamp); } @@ -127,55 +117,77 @@ contract DelayedWETH_Unlock_Test is DelayedWETH_TestInit { function test_unlock_twice_succeeds() public { // Unlock once. uint256 ts = block.timestamp; - delayedWeth.unlock(alice, 1 ether); + delayedWeth.unlock(alice, DEFAULT_AMOUNT); (uint256 amount1, uint256 timestamp1) = delayedWeth.withdrawals(address(this), alice); - assertEq(amount1, 1 ether); + assertEq(amount1, DEFAULT_AMOUNT); assertEq(timestamp1, ts); // Go forward in time. vm.warp(ts + 1); // Unlock again works. - delayedWeth.unlock(alice, 1 ether); + delayedWeth.unlock(alice, DEFAULT_AMOUNT); (uint256 amount2, uint256 timestamp2) = delayedWeth.withdrawals(address(this), alice); - assertEq(amount2, 2 ether); + assertEq(amount2, 2 * DEFAULT_AMOUNT); assertEq(timestamp2, ts + 1); } } /// @title DelayedWETH_Withdraw_Test /// @notice Tests the `withdraw` function of the `DelayedWETH` contract. -contract DelayedWETH_Withdraw_Test is DelayedWETH_TestInit { - /// @notice Tests that withdrawing while unlocked and delay has passed is successful. - function test_withdraw_whileUnlocked_succeeds() public { - // Deposit some WETH. +contract DelayedWETH_Withdraw_Test is DelayedWETH_TestBase { + function _depositAlice(uint256 _amount) internal returns (uint256 balanceAfterDeposit_) { vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); - uint256 balance = address(alice).balance; + delayedWeth.deposit{ value: _amount }(); + balanceAfterDeposit_ = address(alice).balance; + } - // Unlock the withdrawal. + function _unlockAlice(uint256 _amount) internal { vm.prank(alice); - delayedWeth.unlock(alice, 1 ether); + delayedWeth.unlock(alice, _amount); + } - // Wait for the delay. + function _warpPastDelay() internal { vm.warp(block.timestamp + delayedWeth.delay() + 1); + } + + function _warpBeforeDelay() internal { + vm.warp(block.timestamp + delayedWeth.delay() - 1); + } + + function _pauseSuperchain() internal { + vm.prank(optimismPortal2.guardian()); + superchainConfig.pause(address(0)); + } + + function _prepareUnlockedWithdrawal() internal returns (uint256 balanceAfterDeposit_) { + balanceAfterDeposit_ = _depositAlice(DEFAULT_AMOUNT); + _unlockAlice(DEFAULT_AMOUNT); + _warpPastDelay(); + } + + function _preparePausedWithdrawal() internal { + _depositAlice(DEFAULT_AMOUNT); + _unlockAlice(DEFAULT_AMOUNT); + _warpPastDelay(); + _pauseSuperchain(); + } + + /// @notice Tests that withdrawing while unlocked and delay has passed is successful. + function test_withdraw_whileUnlocked_succeeds() public { + uint256 balance = _prepareUnlockedWithdrawal(); - // Withdraw the WETH. vm.expectEmit(true, true, false, false); - emit Withdrawal(address(alice), 1 ether); + emit Withdrawal(address(alice), DEFAULT_AMOUNT); vm.prank(alice); - delayedWeth.withdraw(1 ether); - assertEq(address(alice).balance, balance + 1 ether); + delayedWeth.withdraw(DEFAULT_AMOUNT); + assertEq(address(alice).balance, balance + DEFAULT_AMOUNT); } /// @notice Tests that withdrawing when unlock was not called fails. function test_withdraw_whileLocked_fails() public { - // Deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); - uint256 balance = address(alice).balance; + uint256 balance = _depositAlice(DEFAULT_AMOUNT); - // Withdraw fails when unlock not called. vm.expectRevert("DelayedWETH: withdrawal not unlocked"); vm.prank(alice); delayedWeth.withdraw(0 ether); @@ -184,100 +196,50 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_TestInit { /// @notice Tests that withdrawing while locked and delay has not passed fails. function test_withdraw_whileLockedNotLongEnough_fails() public { - // Deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); - uint256 balance = address(alice).balance; - - // Call unlock. - vm.prank(alice); - delayedWeth.unlock(alice, 1 ether); + uint256 balance = _depositAlice(DEFAULT_AMOUNT); + _unlockAlice(DEFAULT_AMOUNT); + _warpBeforeDelay(); - // Wait for the delay, but not long enough. - vm.warp(block.timestamp + delayedWeth.delay() - 1); - - // Withdraw fails when delay not met. vm.expectRevert("DelayedWETH: withdrawal delay not met"); vm.prank(alice); - delayedWeth.withdraw(1 ether); + delayedWeth.withdraw(DEFAULT_AMOUNT); assertEq(address(alice).balance, balance); } /// @notice Tests that withdrawing more than unlocked amount fails. function test_withdraw_tooMuch_fails() public { - // Deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); - uint256 balance = address(alice).balance; + uint256 balance = _prepareUnlockedWithdrawal(); - // Unlock the withdrawal. - vm.prank(alice); - delayedWeth.unlock(alice, 1 ether); - - // Wait for the delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1); - - // Withdraw too much fails. vm.expectRevert("DelayedWETH: insufficient unlocked withdrawal"); vm.prank(alice); - delayedWeth.withdraw(2 ether); + delayedWeth.withdraw(2 * DEFAULT_AMOUNT); assertEq(address(alice).balance, balance); } /// @notice Tests that withdrawing while paused fails. function test_withdraw_whenPaused_fails() public { - // Deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); + _preparePausedWithdrawal(); - // Unlock the withdrawal. - vm.prank(alice); - delayedWeth.unlock(alice, 1 ether); - - // Wait for the delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1); - - // Pause the contract. - address guardian = optimismPortal2.guardian(); - vm.prank(guardian); - superchainConfig.pause(address(0)); - - // Withdraw fails. vm.expectRevert("DelayedWETH: contract is paused"); vm.prank(alice); - delayedWeth.withdraw(1 ether); + delayedWeth.withdraw(DEFAULT_AMOUNT); } /// @notice Tests that withdrawing while unlocked and delay has passed is successful. function test_withdraw_withdrawFromWhileUnlocked_succeeds() public { - // Deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); - uint256 balance = address(alice).balance; - - // Unlock the withdrawal. - vm.prank(alice); - delayedWeth.unlock(alice, 1 ether); - - // Wait for the delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1); + uint256 balance = _prepareUnlockedWithdrawal(); - // Withdraw the WETH. vm.expectEmit(true, true, false, false); - emit Withdrawal(address(alice), 1 ether); + emit Withdrawal(address(alice), DEFAULT_AMOUNT); vm.prank(alice); - delayedWeth.withdraw(alice, 1 ether); - assertEq(address(alice).balance, balance + 1 ether); + delayedWeth.withdraw(alice, DEFAULT_AMOUNT); + assertEq(address(alice).balance, balance + DEFAULT_AMOUNT); } /// @notice Tests that withdrawing when unlock was not called fails. function test_withdraw_withdrawFromWhileLocked_fails() public { - // Deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); - uint256 balance = address(alice).balance; + uint256 balance = _depositAlice(DEFAULT_AMOUNT); - // Withdraw fails when unlock not called. vm.expectRevert("DelayedWETH: withdrawal not unlocked"); vm.prank(alice); delayedWeth.withdraw(alice, 0 ether); @@ -286,74 +248,45 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_TestInit { /// @notice Tests that withdrawing while locked and delay has not passed fails. function test_withdraw_withdrawFromWhileLockedNotLongEnough_fails() public { - // Deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); - uint256 balance = address(alice).balance; - - // Call unlock. - vm.prank(alice); - delayedWeth.unlock(alice, 1 ether); - - // Wait for the delay, but not long enough. - vm.warp(block.timestamp + delayedWeth.delay() - 1); + uint256 balance = _depositAlice(DEFAULT_AMOUNT); + _unlockAlice(DEFAULT_AMOUNT); + _warpBeforeDelay(); - // Withdraw fails when delay not met. vm.expectRevert("DelayedWETH: withdrawal delay not met"); vm.prank(alice); - delayedWeth.withdraw(alice, 1 ether); + delayedWeth.withdraw(alice, DEFAULT_AMOUNT); assertEq(address(alice).balance, balance); } /// @notice Tests that withdrawing more than unlocked amount fails. function test_withdraw_withdrawFromTooMuch_fails() public { - // Deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); - uint256 balance = address(alice).balance; - - // Unlock the withdrawal. - vm.prank(alice); - delayedWeth.unlock(alice, 1 ether); - - // Wait for the delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1); + uint256 balance = _prepareUnlockedWithdrawal(); - // Withdraw too much fails. vm.expectRevert("DelayedWETH: insufficient unlocked withdrawal"); vm.prank(alice); - delayedWeth.withdraw(alice, 2 ether); + delayedWeth.withdraw(alice, 2 * DEFAULT_AMOUNT); assertEq(address(alice).balance, balance); } /// @notice Tests that withdrawing while paused fails. function test_withdraw_withdrawFromWhenPaused_fails() public { - // Deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: 1 ether }(); - - // Unlock the withdrawal. - vm.prank(alice); - delayedWeth.unlock(alice, 1 ether); + _preparePausedWithdrawal(); - // Wait for the delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1); - - // Pause the contract. - address guardian = optimismPortal2.guardian(); - vm.prank(guardian); - superchainConfig.pause(address(0)); - - // Withdraw fails. vm.expectRevert("DelayedWETH: contract is paused"); vm.prank(alice); - delayedWeth.withdraw(alice, 1 ether); + delayedWeth.withdraw(alice, DEFAULT_AMOUNT); } } /// @title DelayedWETH_Recover_Test /// @notice Tests the `recover` function of the `DelayedWETH` contract. -contract DelayedWETH_Recover_Test is DelayedWETH_TestInit { +contract DelayedWETH_Recover_Test is DelayedWETH_TestBase { + uint256 internal constant MAX_FALLBACK_GAS_USAGE = 20_000_000; + + function _mockProxyAdminOwner(address _owner) internal { + vm.mockCall(address(proxyAdmin), abi.encodeCall(IProxyAdmin.owner, ()), abi.encode(_owner)); + } + /// @notice Tests that recovering WETH succeeds. Makes sure that doing so succeeds with any /// amount of ETH in the contract and any amount of gas used in the fallback function /// up to a maximum of 20,000,000 gas. Owner contract should never be using that much @@ -361,135 +294,102 @@ contract DelayedWETH_Recover_Test is DelayedWETH_TestInit { /// @param _amount Amount of WETH to recover. /// @param _fallbackGasUsage Amount of gas to use in the fallback function. function testFuzz_recover_succeeds(uint256 _amount, uint256 _fallbackGasUsage) public { - // Assume - _fallbackGasUsage = bound(_fallbackGasUsage, 0, 20000000); + _fallbackGasUsage = bound(_fallbackGasUsage, 0, MAX_FALLBACK_GAS_USAGE); - // Set up the gas burner. DelayedWETH_FallbackGasUser_Harness gasUser = new DelayedWETH_FallbackGasUser_Harness(_fallbackGasUsage); - // Mock owner to return the gas user. - vm.mockCall(address(proxyAdmin), abi.encodeCall(IProxyAdmin.owner, ()), abi.encode(address(gasUser))); + _mockProxyAdminOwner(address(gasUser)); - // Give the contract some WETH to recover. vm.deal(address(delayedWeth), _amount); - // Record the initial balance. uint256 initialBalance = address(gasUser).balance; - // Recover the WETH. vm.prank(address(gasUser)); delayedWeth.recover(_amount); - // Verify the WETH was recovered. assertEq(address(delayedWeth).balance, 0); assertEq(address(gasUser).balance, initialBalance + _amount); } /// @notice Tests that recovering WETH by non-owner fails. function test_recover_byNonOwner_fails() public { - // Pretend to be a non-owner. vm.prank(alice); - // Recover fails. vm.expectRevert("DelayedWETH: not owner"); - delayedWeth.recover(1 ether); + delayedWeth.recover(DEFAULT_AMOUNT); } /// @notice Tests that recovering more than the balance recovers what it can. function test_recover_moreThanBalance_succeeds() public { - // Mock owner to return alice. - vm.mockCall(address(proxyAdmin), abi.encodeCall(IProxyAdmin.owner, ()), abi.encode(alice)); + _mockProxyAdminOwner(alice); - // Give the contract some WETH to recover. vm.deal(address(delayedWeth), 0.5 ether); - // Record the initial balance. uint256 initialBalance = address(alice).balance; - // Recover the WETH. vm.prank(alice); - delayedWeth.recover(1 ether); + delayedWeth.recover(DEFAULT_AMOUNT); - // Verify the WETH was recovered. assertEq(address(delayedWeth).balance, 0); assertEq(address(alice).balance, initialBalance + 0.5 ether); } /// @notice Tests that recover reverts when recipient reverts. function test_recover_whenRecipientReverts_fails() public { - // Set up the reverter. DelayedWETH_FallbackReverter_Harness reverter = new DelayedWETH_FallbackReverter_Harness(); - // Mock owner to return the reverter. - vm.mockCall(address(proxyAdmin), abi.encodeCall(IProxyAdmin.owner, ()), abi.encode(address(reverter))); + _mockProxyAdminOwner(address(reverter)); - // Give the contract some WETH to recover. - vm.deal(address(delayedWeth), 1 ether); + vm.deal(address(delayedWeth), DEFAULT_AMOUNT); - // Recover fails. vm.expectRevert("DelayedWETH: recover failed"); vm.prank(address(reverter)); - delayedWeth.recover(1 ether); + delayedWeth.recover(DEFAULT_AMOUNT); } } /// @title DelayedWETH_Hold_Test /// @notice Tests the `hold` function of the `DelayedWETH` contract. -contract DelayedWETH_Hold_Test is DelayedWETH_TestInit { - /// @notice Tests that holding WETH succeeds. - function test_hold_byOwner_succeeds() public { - uint256 amount = 1 ether; - - // Pretend to be alice and deposit some WETH. +contract DelayedWETH_Hold_Test is DelayedWETH_TestBase { + function _depositAliceAndExpectHoldApproval(uint256 _amount) internal returns (uint256 initialBalance_) { vm.prank(alice); - delayedWeth.deposit{ value: amount }(); + delayedWeth.deposit{ value: _amount }(); - // Get our balance before. - uint256 initialBalance = delayedWeth.balanceOf(address(proxyAdminOwner)); + initialBalance_ = delayedWeth.balanceOf(address(proxyAdminOwner)); - // Hold some WETH. vm.expectEmit(true, true, true, false); - emit Approval(alice, address(proxyAdminOwner), amount); + emit Approval(alice, address(proxyAdminOwner), _amount); + } + + /// @notice Tests that holding WETH succeeds. + function test_hold_byOwner_succeeds() public { + uint256 initialBalance = _depositAliceAndExpectHoldApproval(DEFAULT_AMOUNT); + vm.prank(proxyAdminOwner); - delayedWeth.hold(alice, amount); + delayedWeth.hold(alice, DEFAULT_AMOUNT); - // Get our balance after. uint256 finalBalance = delayedWeth.balanceOf(address(proxyAdminOwner)); - // Verify the transfer. - assertEq(finalBalance, initialBalance + amount); + assertEq(finalBalance, initialBalance + DEFAULT_AMOUNT); } + /// @notice Tests that holding all WETH succeeds when the amount is omitted. function test_hold_withoutAmount_succeeds() public { - uint256 amount = 1 ether; - - // Pretend to be alice and deposit some WETH. - vm.prank(alice); - delayedWeth.deposit{ value: amount }(); + uint256 initialBalance = _depositAliceAndExpectHoldApproval(DEFAULT_AMOUNT); - // Get our balance before. - uint256 initialBalance = delayedWeth.balanceOf(address(proxyAdminOwner)); - - // Hold some WETH. - vm.expectEmit(true, true, true, false); - emit Approval(alice, address(proxyAdminOwner), amount); vm.prank(proxyAdminOwner); - delayedWeth.hold(alice); // without amount parameter + delayedWeth.hold(alice); - // Get our balance after. uint256 finalBalance = delayedWeth.balanceOf(address(proxyAdminOwner)); - // Verify the transfer. - assertEq(finalBalance, initialBalance + amount); + assertEq(finalBalance, initialBalance + DEFAULT_AMOUNT); } /// @notice Tests that holding WETH by non-owner fails. function test_hold_byNonOwner_fails() public { - // Pretend to be a non-owner. vm.prank(alice); - // Hold fails. vm.expectRevert("DelayedWETH: not owner"); - delayedWeth.hold(bob, 1 ether); + delayedWeth.hold(bob, DEFAULT_AMOUNT); } } From ceeb3bca039090b83cca7a54bde6628be92834dc Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 15 May 2026 21:29:55 -0400 Subject: [PATCH 040/135] Refactor DelayedWETH.t.sol: change gas variable to immutable for efficiency, simplify withdrawal assertions, and enhance test clarity by removing redundant comments. --- test/L1/proofs/DelayedWETH.t.sol | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test/L1/proofs/DelayedWETH.t.sol b/test/L1/proofs/DelayedWETH.t.sol index 5105e273..138249fe 100644 --- a/test/L1/proofs/DelayedWETH.t.sol +++ b/test/L1/proofs/DelayedWETH.t.sol @@ -17,7 +17,7 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; /// @notice Contract that burns gas in the fallback function. contract DelayedWETH_FallbackGasUser_Harness { /// @notice Amount of gas to use in the fallback function. - uint256 public gas; + uint256 public immutable gas; /// @param _gas Amount of gas to use in the fallback function. constructor(uint256 _gas) { @@ -115,17 +115,14 @@ contract DelayedWETH_Unlock_Test is DelayedWETH_TestBase { /// @notice Tests that unlocking twice is successful and timestamp/amount is updated. function test_unlock_twice_succeeds() public { - // Unlock once. uint256 ts = block.timestamp; delayedWeth.unlock(alice, DEFAULT_AMOUNT); (uint256 amount1, uint256 timestamp1) = delayedWeth.withdrawals(address(this), alice); assertEq(amount1, DEFAULT_AMOUNT); assertEq(timestamp1, ts); - // Go forward in time. vm.warp(ts + 1); - // Unlock again works. delayedWeth.unlock(alice, DEFAULT_AMOUNT); (uint256 amount2, uint256 timestamp2) = delayedWeth.withdrawals(address(this), alice); assertEq(amount2, 2 * DEFAULT_AMOUNT); @@ -368,9 +365,7 @@ contract DelayedWETH_Hold_Test is DelayedWETH_TestBase { vm.prank(proxyAdminOwner); delayedWeth.hold(alice, DEFAULT_AMOUNT); - uint256 finalBalance = delayedWeth.balanceOf(address(proxyAdminOwner)); - - assertEq(finalBalance, initialBalance + DEFAULT_AMOUNT); + assertEq(delayedWeth.balanceOf(address(proxyAdminOwner)), initialBalance + DEFAULT_AMOUNT); } /// @notice Tests that holding all WETH succeeds when the amount is omitted. @@ -380,9 +375,7 @@ contract DelayedWETH_Hold_Test is DelayedWETH_TestBase { vm.prank(proxyAdminOwner); delayedWeth.hold(alice); - uint256 finalBalance = delayedWeth.balanceOf(address(proxyAdminOwner)); - - assertEq(finalBalance, initialBalance + DEFAULT_AMOUNT); + assertEq(delayedWeth.balanceOf(address(proxyAdminOwner)), initialBalance + DEFAULT_AMOUNT); } /// @notice Tests that holding WETH by non-owner fails. From dcd7bc79fc1c1279c6402cf63823889b8a2039ae Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 07:06:49 -0400 Subject: [PATCH 041/135] Refactor DelayedWETH.t.sol: remove unused fallback harness contracts, streamline withdrawal tests, and enhance gas recovery tests with improved helper functions for clarity and maintainability. --- test/L1/proofs/DelayedWETH.t.sol | 113 ++++++++++--------------------- 1 file changed, 35 insertions(+), 78 deletions(-) diff --git a/test/L1/proofs/DelayedWETH.t.sol b/test/L1/proofs/DelayedWETH.t.sol index 138249fe..13129953 100644 --- a/test/L1/proofs/DelayedWETH.t.sol +++ b/test/L1/proofs/DelayedWETH.t.sol @@ -3,52 +3,16 @@ pragma solidity ^0.8.15; // Testing import { CommonTest } from "test/setup/CommonTest.sol"; +import { GasBurner, Reverter } from "test/mocks/Callers.sol"; // Libraries import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; -import { Burn } from "src/libraries/Burn.sol"; // Interfaces import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -/// @title DelayedWETH_FallbackGasUser_Harness -/// @notice Contract that burns gas in the fallback function. -contract DelayedWETH_FallbackGasUser_Harness { - /// @notice Amount of gas to use in the fallback function. - uint256 public immutable gas; - - /// @param _gas Amount of gas to use in the fallback function. - constructor(uint256 _gas) { - gas = _gas; - } - - /// @notice Burn gas on fallback. - fallback() external payable { - Burn.gas(gas); - } - - /// @notice Burn gas on receive. - receive() external payable { - Burn.gas(gas); - } -} - -/// @title DelayedWETH_FallbackReverter_Harness -/// @notice Contract that reverts in the fallback function. -contract DelayedWETH_FallbackReverter_Harness { - /// @notice Revert on fallback. - fallback() external payable { - revert("FallbackReverter: revert"); - } - - /// @notice Revert on receive. - receive() external payable { - revert("FallbackReverter: revert"); - } -} - /// @title DelayedWETH_TestBase /// @notice Reusable test utilities for `DelayedWETH` tests. abstract contract DelayedWETH_TestBase is CommonTest { @@ -57,6 +21,12 @@ abstract contract DelayedWETH_TestBase is CommonTest { event Approval(address indexed src, address indexed guy, uint256 wad); event Withdrawal(address indexed src, uint256 wad); + + function _depositAlice(uint256 _amount) internal returns (uint256 balanceAfterDeposit_) { + vm.prank(alice); + delayedWeth.deposit{ value: _amount }(); + balanceAfterDeposit_ = address(alice).balance; + } } /// @title DelayedWETH_Initialize_Test @@ -133,12 +103,6 @@ contract DelayedWETH_Unlock_Test is DelayedWETH_TestBase { /// @title DelayedWETH_Withdraw_Test /// @notice Tests the `withdraw` function of the `DelayedWETH` contract. contract DelayedWETH_Withdraw_Test is DelayedWETH_TestBase { - function _depositAlice(uint256 _amount) internal returns (uint256 balanceAfterDeposit_) { - vm.prank(alice); - delayedWeth.deposit{ value: _amount }(); - balanceAfterDeposit_ = address(alice).balance; - } - function _unlockAlice(uint256 _amount) internal { vm.prank(alice); delayedWeth.unlock(alice, _amount); @@ -153,7 +117,7 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_TestBase { } function _pauseSuperchain() internal { - vm.prank(optimismPortal2.guardian()); + vm.prank(superchainConfig.guardian()); superchainConfig.pause(address(0)); } @@ -163,18 +127,11 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_TestBase { _warpPastDelay(); } - function _preparePausedWithdrawal() internal { - _depositAlice(DEFAULT_AMOUNT); - _unlockAlice(DEFAULT_AMOUNT); - _warpPastDelay(); - _pauseSuperchain(); - } - /// @notice Tests that withdrawing while unlocked and delay has passed is successful. function test_withdraw_whileUnlocked_succeeds() public { uint256 balance = _prepareUnlockedWithdrawal(); - vm.expectEmit(true, true, false, false); + vm.expectEmit(address(delayedWeth)); emit Withdrawal(address(alice), DEFAULT_AMOUNT); vm.prank(alice); delayedWeth.withdraw(DEFAULT_AMOUNT); @@ -215,7 +172,7 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_TestBase { /// @notice Tests that withdrawing while paused fails. function test_withdraw_whenPaused_fails() public { - _preparePausedWithdrawal(); + _pauseSuperchain(); vm.expectRevert("DelayedWETH: contract is paused"); vm.prank(alice); @@ -226,7 +183,7 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_TestBase { function test_withdraw_withdrawFromWhileUnlocked_succeeds() public { uint256 balance = _prepareUnlockedWithdrawal(); - vm.expectEmit(true, true, false, false); + vm.expectEmit(address(delayedWeth)); emit Withdrawal(address(alice), DEFAULT_AMOUNT); vm.prank(alice); delayedWeth.withdraw(alice, DEFAULT_AMOUNT); @@ -267,7 +224,7 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_TestBase { /// @notice Tests that withdrawing while paused fails. function test_withdraw_withdrawFromWhenPaused_fails() public { - _preparePausedWithdrawal(); + _pauseSuperchain(); vm.expectRevert("DelayedWETH: contract is paused"); vm.prank(alice); @@ -284,16 +241,8 @@ contract DelayedWETH_Recover_Test is DelayedWETH_TestBase { vm.mockCall(address(proxyAdmin), abi.encodeCall(IProxyAdmin.owner, ()), abi.encode(_owner)); } - /// @notice Tests that recovering WETH succeeds. Makes sure that doing so succeeds with any - /// amount of ETH in the contract and any amount of gas used in the fallback function - /// up to a maximum of 20,000,000 gas. Owner contract should never be using that much - /// gas but we might as well set a very large upper bound for ourselves. - /// @param _amount Amount of WETH to recover. - /// @param _fallbackGasUsage Amount of gas to use in the fallback function. - function testFuzz_recover_succeeds(uint256 _amount, uint256 _fallbackGasUsage) public { - _fallbackGasUsage = bound(_fallbackGasUsage, 0, MAX_FALLBACK_GAS_USAGE); - - DelayedWETH_FallbackGasUser_Harness gasUser = new DelayedWETH_FallbackGasUser_Harness(_fallbackGasUsage); + function _recoverToGasBurner(uint256 _amount, uint256 _fallbackGasUsage) internal { + GasBurner gasUser = new GasBurner(_fallbackGasUsage + 500); _mockProxyAdminOwner(address(gasUser)); @@ -308,6 +257,18 @@ contract DelayedWETH_Recover_Test is DelayedWETH_TestBase { assertEq(address(gasUser).balance, initialBalance + _amount); } + /// @notice Tests that recovering WETH succeeds. Makes sure that doing so succeeds with any + /// amount of ETH in the contract. + /// @param _amount Amount of WETH to recover. + function testFuzz_recover_succeeds(uint256 _amount) public { + _recoverToGasBurner(_amount, 0); + } + + /// @notice Tests that recovering WETH succeeds when the recipient uses the maximum allowed fallback gas. + function test_recover_withMaxFallbackGas_succeeds() public { + _recoverToGasBurner(DEFAULT_AMOUNT, MAX_FALLBACK_GAS_USAGE); + } + /// @notice Tests that recovering WETH by non-owner fails. function test_recover_byNonOwner_fails() public { vm.prank(alice); @@ -333,7 +294,7 @@ contract DelayedWETH_Recover_Test is DelayedWETH_TestBase { /// @notice Tests that recover reverts when recipient reverts. function test_recover_whenRecipientReverts_fails() public { - DelayedWETH_FallbackReverter_Harness reverter = new DelayedWETH_FallbackReverter_Harness(); + Reverter reverter = new Reverter(); _mockProxyAdminOwner(address(reverter)); @@ -348,20 +309,13 @@ contract DelayedWETH_Recover_Test is DelayedWETH_TestBase { /// @title DelayedWETH_Hold_Test /// @notice Tests the `hold` function of the `DelayedWETH` contract. contract DelayedWETH_Hold_Test is DelayedWETH_TestBase { - function _depositAliceAndExpectHoldApproval(uint256 _amount) internal returns (uint256 initialBalance_) { - vm.prank(alice); - delayedWeth.deposit{ value: _amount }(); - - initialBalance_ = delayedWeth.balanceOf(address(proxyAdminOwner)); - - vm.expectEmit(true, true, true, false); - emit Approval(alice, address(proxyAdminOwner), _amount); - } - /// @notice Tests that holding WETH succeeds. function test_hold_byOwner_succeeds() public { - uint256 initialBalance = _depositAliceAndExpectHoldApproval(DEFAULT_AMOUNT); + _depositAlice(DEFAULT_AMOUNT); + uint256 initialBalance = delayedWeth.balanceOf(address(proxyAdminOwner)); + vm.expectEmit(address(delayedWeth)); + emit Approval(alice, address(proxyAdminOwner), DEFAULT_AMOUNT); vm.prank(proxyAdminOwner); delayedWeth.hold(alice, DEFAULT_AMOUNT); @@ -370,8 +324,11 @@ contract DelayedWETH_Hold_Test is DelayedWETH_TestBase { /// @notice Tests that holding all WETH succeeds when the amount is omitted. function test_hold_withoutAmount_succeeds() public { - uint256 initialBalance = _depositAliceAndExpectHoldApproval(DEFAULT_AMOUNT); + _depositAlice(DEFAULT_AMOUNT); + uint256 initialBalance = delayedWeth.balanceOf(address(proxyAdminOwner)); + vm.expectEmit(address(delayedWeth)); + emit Approval(alice, address(proxyAdminOwner), DEFAULT_AMOUNT); vm.prank(proxyAdminOwner); delayedWeth.hold(alice); From 3efd7edd2416745204745f7f852f1e9d99e70462 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 07:17:16 -0400 Subject: [PATCH 042/135] Refactor DisputeGameFactory.t.sol: introduce constants for default values, streamline game setup logic with helper functions, and enhance test clarity by improving initialization handling. --- test/L1/proofs/DisputeGameFactory.t.sol | 185 ++++++------------------ 1 file changed, 47 insertions(+), 138 deletions(-) diff --git a/test/L1/proofs/DisputeGameFactory.t.sol b/test/L1/proofs/DisputeGameFactory.t.sol index dfd9a233..a9f0fdcf 100644 --- a/test/L1/proofs/DisputeGameFactory.t.sol +++ b/test/L1/proofs/DisputeGameFactory.t.sol @@ -8,9 +8,8 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; // Libraries -import { Timestamp } from "src/libraries/bridge/LibUDT.sol"; +import { Claim, Timestamp } from "src/libraries/bridge/LibUDT.sol"; import "src/libraries/bridge/Types.sol"; -import "src/libraries/bridge/Errors.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; // Interfaces @@ -44,6 +43,10 @@ contract DisputeGameFactory_FakeClone_Harness { /// @title DisputeGameFactory_TestInit /// @notice Reusable test initialization for `DisputeGameFactory` tests. abstract contract DisputeGameFactory_TestInit is CommonTest { + uint256 internal constant DEFAULT_INIT_BOND = 0.08 ether; + uint256 internal constant L2_CHAIN_ID = 111; + uint32 internal constant MAX_GAME_TYPE = 8; + DisputeGameFactory_FakeClone_Harness fakeClone; event DisputeGameCreated(address indexed disputeProxy, GameType indexed gameType, Claim indexed rootClaim); @@ -51,8 +54,6 @@ abstract contract DisputeGameFactory_TestInit is CommonTest { event ImplementationArgsSet(GameType indexed gameType, bytes args); event InitBondUpdated(GameType indexed gameType, uint256 indexed newBond); - uint256 l2ChainId = 111; - function setUp() public virtual override { super.setUp(); fakeClone = new DisputeGameFactory_FakeClone_Harness(); @@ -63,21 +64,13 @@ abstract contract DisputeGameFactory_TestInit is CommonTest { } function _setGame(address _gameImpl, GameType _gameType) internal { - _setGame(_gameImpl, _gameType, false, ""); - } - - function _setGame(address _gameImpl, GameType _gameType, bytes memory _implArgs) internal { - _setGame(_gameImpl, _gameType, true, _implArgs); + _setGame(_gameImpl, _gameType, DEFAULT_INIT_BOND); } - function _setGame(address _gameImpl, GameType _gameType, bool _hasImplArgs, bytes memory _implArgs) internal { + function _setGame(address _gameImpl, GameType _gameType, uint256 _initBond) internal { vm.startPrank(disputeGameFactory.owner()); - if (_hasImplArgs) { - disputeGameFactory.setImplementation(_gameType, IDisputeGame(_gameImpl), _implArgs); - } else { - disputeGameFactory.setImplementation(_gameType, IDisputeGame(_gameImpl)); - } - disputeGameFactory.setInitBond(_gameType, 0.08 ether); + disputeGameFactory.setImplementation(_gameType, IDisputeGame(_gameImpl)); + disputeGameFactory.setInitBond(_gameType, _initBond); vm.stopPrank(); } } @@ -85,25 +78,26 @@ abstract contract DisputeGameFactory_TestInit is CommonTest { /// @title DisputeGameFactory_Initialize_Test /// @notice Tests the `initialize` function of the `DisputeGameFactory` contract. contract DisputeGameFactory_Initialize_Test is DisputeGameFactory_TestInit { + bytes32 internal initializedSlot; + + function setUp() public override { + super.setUp(); + + StorageSlot memory slot = ForgeArtifacts.getSlot("DisputeGameFactory", "_initialized"); + initializedSlot = bytes32(slot.slot); + } + /// @notice Tests that initialization reverts if called by a non-proxy admin or proxy admin /// owner. /// @param _sender The address of the sender to test. function testFuzz_initialize_notProxyAdminOrProxyAdminOwner_reverts(address _sender) public { - // Prank as the not ProxyAdmin or ProxyAdmin owner. vm.assume( _sender != address(disputeGameFactory.proxyAdmin()) && _sender != disputeGameFactory.proxyAdminOwner() ); - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("DisputeGameFactory", "_initialized"); - - // Set the initialized slot to 0. - vm.store(address(disputeGameFactory), bytes32(slot.slot), bytes32(0)); + vm.store(address(disputeGameFactory), initializedSlot, bytes32(0)); - // Expect the revert with `ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner` selector. vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); - - // Call the `initialize` function with the sender. vm.prank(_sender); disputeGameFactory.initialize(address(1234)); } @@ -112,14 +106,9 @@ contract DisputeGameFactory_Initialize_Test is DisputeGameFactory_TestInit { /// but confirms that the initValue is not incremented incorrectly if an upgrade /// function is not present. function test_initialize_correctInitializerValue_succeeds() public view { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("DisputeGameFactory", "_initialized"); - - // Get the initializer value. - bytes32 slotVal = vm.load(address(disputeGameFactory), bytes32(slot.slot)); + bytes32 slotVal = vm.load(address(disputeGameFactory), initializedSlot); uint8 val = uint8(uint256(slotVal) & 0xFF); - // Assert that the initializer value matches the expected value. assertEq(val, disputeGameFactory.initVersion()); } } @@ -137,17 +126,8 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { ) public { - // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible - // values. - uint32 maxGameType = 8; - GameType gt = GameType.wrap(uint8(bound(gameType, 0, maxGameType))); - - // Set all three implementations to the same `FakeClone` contract. - for (uint8 i; i < maxGameType + 1; i++) { - GameType lgt = GameType.wrap(i); - disputeGameFactory.setImplementation(lgt, IDisputeGame(address(fakeClone))); - disputeGameFactory.setInitBond(lgt, _value); - } + GameType gt = GameType.wrap(uint8(bound(gameType, 0, MAX_GAME_TYPE))); + _setGame(address(fakeClone), gt, _value); vm.deal(address(this), _value); @@ -159,7 +139,6 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { (IDisputeGame game, Timestamp timestamp) = disputeGameFactory.games(gt, rootClaim, extraData); - // Ensure that the dispute game was assigned to the `disputeGames` mapping. assertEq(address(game), address(proxy)); assertEq(Timestamp.unwrap(timestamp), block.timestamp); assertEq(disputeGameFactory.gameCount(), gameCountBefore + 1); @@ -168,7 +147,6 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { assertEq(address(game2), address(proxy)); assertEq(Timestamp.unwrap(timestamp2), block.timestamp); - // Ensure that the game proxy received the bonded ETH. assertEq(address(proxy).balance, _value); } @@ -181,63 +159,43 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { ) public { - // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible - // values. GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2))); + _setGame(address(fakeClone), gt, 1 ether); - // Set all three implementations to the same `FakeClone` contract. - for (uint8 i; i < 3; i++) { - GameType lgt = GameType.wrap(i); - disputeGameFactory.setImplementation(lgt, IDisputeGame(address(fakeClone))); - disputeGameFactory.setInitBond(lgt, 1 ether); - } - - vm.expectRevert(IncorrectBondAmount.selector); + vm.expectRevert(IDisputeGameFactory.IncorrectBondAmount.selector); disputeGameFactory.create(gt, rootClaim, extraData); } /// @notice Tests that the `create` function reverts when there is no implementation set for /// the given `GameType`. function testFuzz_create_noImpl_reverts(uint32 gameType, Claim rootClaim, bytes calldata extraData) public { - // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible - // values. We skip over game type = 0, since the deploy script set the implementation for - // that game type. - uint32 maxGameType = 8; - GameType gt = GameType.wrap(uint32(bound(gameType, maxGameType + 1, type(uint32).max))); + // Game type 0 is configured by the deploy script, so choose an out-of-range type. + GameType gt = GameType.wrap(uint32(bound(gameType, MAX_GAME_TYPE + 1, type(uint32).max))); - vm.expectRevert(abi.encodeWithSelector(NoImplementation.selector, gt)); + vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.NoImplementation.selector, gt)); disputeGameFactory.create(gt, rootClaim, extraData); } /// @notice Tests that the `create` function reverts when there exists a dispute game with the /// same UUID. function testFuzz_create_sameUUID_reverts(uint32 gameType, Claim rootClaim, bytes calldata extraData) public { - // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible - // values. - uint32 maxGameType = 8; - GameType gt = GameType.wrap(uint8(bound(gameType, 0, maxGameType))); - - // Set all three implementations to the same `FakeClone` contract. - for (uint8 i; i < maxGameType + 1; i++) { - disputeGameFactory.setImplementation(GameType.wrap(i), IDisputeGame(address(fakeClone))); - } + GameType gt = GameType.wrap(uint8(bound(gameType, 0, MAX_GAME_TYPE))); + disputeGameFactory.setImplementation(gt, IDisputeGame(address(fakeClone))); uint256 bondAmount = disputeGameFactory.initBonds(gt); - // Create our first dispute game - this should succeed. vm.expectEmit(false, true, true, false); emit DisputeGameCreated(address(0), gt, rootClaim); IDisputeGame proxy = disputeGameFactory.create{ value: bondAmount }(gt, rootClaim, extraData); (IDisputeGame game, Timestamp timestamp) = disputeGameFactory.games(gt, rootClaim, extraData); - // Ensure that the dispute game was assigned to the `disputeGames` mapping. assertEq(address(game), address(proxy)); assertEq(Timestamp.unwrap(timestamp), block.timestamp); - // Ensure that the `create` function reverts when called with parameters that would result - // in the same UUID. vm.expectRevert( - abi.encodeWithSelector(GameAlreadyExists.selector, disputeGameFactory.getGameUUID(gt, rootClaim, extraData)) + abi.encodeWithSelector( + IDisputeGameFactory.GameAlreadyExists.selector, disputeGameFactory.getGameUUID(gt, rootClaim, extraData) + ) ); disputeGameFactory.create{ value: bondAmount }(gt, rootClaim, extraData); } @@ -254,7 +212,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { bytes32(uint256(1)), AggregateVerifier.ZkHashes(bytes32(uint256(2)), bytes32(uint256(3))), bytes32(uint256(4)), - l2ChainId, + L2_CHAIN_ID, 100, 10 ); @@ -277,31 +235,26 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { uint256 bondAmount = disputeGameFactory.initBonds(GameTypes.AGGREGATE_VERIFIER); vm.deal(address(this), bondAmount); - // Create the game IDisputeGame proxy = disputeGameFactory.createWithInitData{ value: bondAmount }( GameTypes.AGGREGATE_VERIFIER, rootClaim, extraData, proof ); - // Verify the game was created and stored (IDisputeGame game, Timestamp timestamp) = disputeGameFactory.games(GameTypes.AGGREGATE_VERIFIER, rootClaim, extraData); assertEq(address(game), address(proxy)); assertEq(Timestamp.unwrap(timestamp), block.timestamp); - // Verify the game has the correct parameters via CWIA AggregateVerifier gameV2 = AggregateVerifier(address(proxy)); - // Test CWIA getters assertEq(Claim.unwrap(gameV2.rootClaim()), Claim.unwrap(rootClaim)); assertEq(gameV2.extraData(), extraData); - assertEq(gameV2.L2_CHAIN_ID(), l2ChainId); + assertEq(gameV2.L2_CHAIN_ID(), L2_CHAIN_ID); assertEq(address(gameV2.gameCreator()), address(this)); assertEq(gameV2.l2SequenceNumber(), startingRoot.l2SequenceNumber + 100); assertEq(gameV2.parentAddress(), address(anchorStateRegistry)); assertEq(address(gameV2.DELAYED_WETH()), address(delayedWeth)); assertEq(address(gameV2.anchorStateRegistry()), address(anchorStateRegistry)); - // Test Constructor args assertEq(GameType.unwrap(gameV2.gameType()), GameType.unwrap(GameTypes.AGGREGATE_VERIFIER)); assertEq(gameV2.BLOCK_INTERVAL(), 100); assertEq(gameV2.INTERMEDIATE_BLOCK_INTERVAL(), 10); @@ -317,16 +270,13 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni vm.expectEmit(true, true, true, true, address(disputeGameFactory)); emit ImplementationSet(address(1), GameTypes.AGGREGATE_VERIFIER); - // Set the implementation for the `GameTypes.AGGREGATE_VERIFIER` enum value. disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1))); - // Ensure that the implementation for the `GameTypes.AGGREGATE_VERIFIER` enum value is set. assertEq(address(disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER)), address(1)); } /// @notice Tests that the `setImplementation` function reverts when called by a non-owner. function test_setImplementation_notOwner_reverts() public { - // Ensure that the `setImplementation` function reverts when called by a non-owner. vm.prank(address(0)); vm.expectRevert("Ownable: caller is not the owner"); disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1))); @@ -335,27 +285,24 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni /// @notice Tests that the `setImplementation` function with args properly sets the implementation /// and args for a given `GameType`. function test_setImplementation_withArgs_succeeds() public { - address fakeGame = address(1); + address gameImpl = address(1); bytes memory args = abi.encodePacked( bytes32(hex"dead"), // 32 bytes address(0xbeef), // 20 bytes anchorStateRegistry, // 20 bytes delayedWeth, // 20 bytes - l2ChainId // 32 bytes (l2ChainId) + L2_CHAIN_ID // 32 bytes ); vm.expectEmit(true, true, true, true, address(disputeGameFactory)); - emit ImplementationSet(address(1), GameTypes.AGGREGATE_VERIFIER); + emit ImplementationSet(gameImpl, GameTypes.AGGREGATE_VERIFIER); vm.expectEmit(true, true, true, true, address(disputeGameFactory)); emit ImplementationArgsSet(GameTypes.AGGREGATE_VERIFIER, args); - // Set the implementation and args for the `GameTypes.AGGREGATE_VERIFIER` enum value. - disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(fakeGame), args); + disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(gameImpl), args); - // Ensure that the implementation for the `GameTypes.AGGREGATE_VERIFIER` enum value is set. - assertEq(address(disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER)), address(1)); - // Ensure that the args for the `GameTypes.AGGREGATE_VERIFIER` enum value are set. + assertEq(address(disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER)), gameImpl); assertEq(disputeGameFactory.gameArgs(GameTypes.AGGREGATE_VERIFIER), args); } @@ -363,7 +310,6 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni function test_setImplementationArgs_notOwner_reverts() public { bytes memory args = abi.encode(uint256(123), address(0xdead)); - // Ensure that the `setImplementation` function reverts when called by a non-owner. vm.prank(address(0)); vm.expectRevert("Ownable: caller is not the owner"); disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1)), args); @@ -379,25 +325,20 @@ contract DisputeGameFactory_SetInitBond_Test is DisputeGameFactory_TestInit { vm.expectEmit(true, true, true, true, address(disputeGameFactory)); emit InitBondUpdated(GameTypes.AGGREGATE_VERIFIER, 1 ether); - // Set the init bond for the `GameTypes.AGGREGATE_VERIFIER` enum value. disputeGameFactory.setInitBond(GameTypes.AGGREGATE_VERIFIER, 1 ether); - // Ensure that the init bond for the `GameTypes.AGGREGATE_VERIFIER` enum value is set. assertEq(disputeGameFactory.initBonds(GameTypes.AGGREGATE_VERIFIER), 1 ether); vm.expectEmit(true, true, true, true, address(disputeGameFactory)); emit InitBondUpdated(GameTypes.AGGREGATE_VERIFIER, 2 ether); - // Set the init bond for the `GameTypes.AGGREGATE_VERIFIER` enum value. disputeGameFactory.setInitBond(GameTypes.AGGREGATE_VERIFIER, 2 ether); - // Ensure that the init bond for the `GameTypes.AGGREGATE_VERIFIER` enum value is set. assertEq(disputeGameFactory.initBonds(GameTypes.AGGREGATE_VERIFIER), 2 ether); } /// @notice Tests that the `setInitBond` function reverts when called by a non-owner. function test_setInitBond_notOwner_reverts() public { - // Ensure that the `setInitBond` function reverts when called by a non-owner. vm.prank(address(0)); vm.expectRevert("Ownable: caller is not the owner"); disputeGameFactory.setInitBond(GameTypes.AGGREGATE_VERIFIER, 1 ether); @@ -410,8 +351,6 @@ contract DisputeGameFactory_GetGameUUID_Test is DisputeGameFactory_TestInit { /// @notice Tests that the `getGameUUID` function returns the correct hash when comparing /// against the keccak256 hash of the abi-encoded parameters. function testDiff_getGameUUID_succeeds(uint32 gameType, Claim rootClaim, bytes calldata extraData) public view { - // Ensure that the `gameType` is within the bounds of the `GameType` enum's possible - // values. GameType gt = GameType.wrap(uint8(bound(gameType, 0, 2))); assertEq( @@ -427,30 +366,17 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit function setUp() public override { super.setUp(); - // Set three implementations to the same `FakeClone` contract. for (uint8 i; i < 3; i++) { - GameType lgt = GameType.wrap(i); - disputeGameFactory.setImplementation(lgt, IDisputeGame(address(fakeClone))); - disputeGameFactory.setInitBond(lgt, 0); + _setGame(address(fakeClone), GameType.wrap(i), 0); } } /// @notice Tests that `findLatestGames` returns an empty array when the passed starting index /// is greater than or equal to the game count. - function testFuzz_findLatestGames_greaterThanLength_succeeds(uint256 _start) public { - // Creation count should be 32 for normal tests, 5 for upgrade tests. - uint256 creationCount = isForkTest() ? 5 : 32; - - // Create some dispute games of varying game types. - for (uint256 i; i < creationCount; i++) { - disputeGameFactory.create(GameType.wrap(uint8(i % 2)), Claim.wrap(bytes32(i)), abi.encode(i)); - } - - // Bound the starting index to a number greater than the length of the game list. + function testFuzz_findLatestGames_greaterThanLength_succeeds(uint256 _start) public view { uint256 gameCount = disputeGameFactory.gameCount(); _start = bound(_start, gameCount, type(uint256).max); - // The array's length should always be 0. IDisputeGameFactory.GameSearchResult[] memory games = disputeGameFactory.findLatestGames(GameTypes.AGGREGATE_VERIFIER, _start, 1); assertEq(games.length, 0); @@ -458,12 +384,7 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit /// @notice Tests that `findLatestGames` returns the correct games. function test_findLatestGames_static_succeeds() public { - // Creation count should be 32 for normal tests, 5 for upgrade tests. - uint256 creationCount = isForkTest() ? 5 : 32; - - // Create some dispute games of varying game types, repeatedly iterating over the game - // types 0, 1, 2. - for (uint256 i; i < creationCount; i++) { + for (uint256 i; i < 5; i++) { disputeGameFactory.create(GameType.wrap(uint8(i % 3)), Claim.wrap(bytes32(i)), abi.encode(i)); } @@ -473,33 +394,27 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit uint256 start = gameCount - 1; - // Find type 1 games. games = disputeGameFactory.findLatestGames(GameType.wrap(1), start, 1); assertEq(games.length, 1); - // The type 1 game should be the last one added. assertEq(games[0].index, start); - (GameType gameType, Timestamp createdAt, address game) = games[0].metadata.unpack(); + (GameType gameType, Timestamp createdAt,) = games[0].metadata.unpack(); assertEq(gameType.raw(), 1); assertEq(createdAt.raw(), block.timestamp); - // Find type 0 games. games = disputeGameFactory.findLatestGames(GameType.wrap(0), start, 1); assertEq(games.length, 1); - // The type 0 game should be the second to last one added. assertEq(games[0].index, start - 1); - (gameType, createdAt, game) = games[0].metadata.unpack(); + (gameType, createdAt,) = games[0].metadata.unpack(); assertEq(gameType.raw(), 0); assertEq(createdAt.raw(), block.timestamp); - // Find type 2 games. games = disputeGameFactory.findLatestGames(GameType.wrap(2), start, 1); assertEq(games.length, 1); - // The type 2 game should be the third to last one added. assertEq(games[0].index, start - 2); - (gameType, createdAt, game) = games[0].metadata.unpack(); + (gameType, createdAt,) = games[0].metadata.unpack(); assertEq(gameType.raw(), 2); assertEq(createdAt.raw(), block.timestamp); } @@ -507,7 +422,7 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit /// @notice Tests that `findLatestGames` returns the correct games, if there are less than `_n` /// games of the given type available. function test_findLatestGames_lessThanNAvailable_succeeds() public { - // Need to clear out the length of the game list on forked list to avoid massive iteration. + // Fork tests inherit existing games, which would make this bounded scan unnecessarily large. if (isForkTest()) { vm.store( address(disputeGameFactory), @@ -516,22 +431,18 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit ); } - // Create some dispute games of varying game types. disputeGameFactory.create(GameType.wrap(1), Claim.wrap(bytes32(0)), abi.encode(0)); disputeGameFactory.create(GameType.wrap(1), Claim.wrap(bytes32(uint256(1))), abi.encode(1)); for (uint256 i; i < 1 << 3; i++) { disputeGameFactory.create(GameType.wrap(0), Claim.wrap(bytes32(i)), abi.encode(i)); } - // Grab the existing game count. uint256 gameCount = disputeGameFactory.gameCount(); - // Try to find 5 games of type 2, but there are none. IDisputeGameFactory.GameSearchResult[] memory games; games = disputeGameFactory.findLatestGames(GameType.wrap(2), gameCount - 1, 5); assertEq(games.length, 0); - // Try to find 2 games of type 1, but there are only 2. games = disputeGameFactory.findLatestGames(GameType.wrap(1), gameCount - 1, 5); assertEq(games.length, 2); assertEq(games[0].index, 1); @@ -547,17 +458,15 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit ) public { - _numGames = bound(_numGames, 0, isForkTest() ? 5 : 256); + _numGames = bound(_numGames, 0, isForkTest() ? 5 : 32); _numSearchedGames = bound(_numSearchedGames, 0, _numGames); _n = bound(_n, 0, _numSearchedGames); - // Create `_numGames` dispute games, with at least `_numSearchedGames` games. for (uint256 i; i < _numGames; i++) { uint32 gameType = i < _numSearchedGames ? 0 : 1; disputeGameFactory.create(GameType.wrap(gameType), Claim.wrap(bytes32(i)), abi.encode(i)); } - // Ensure that the correct number of games are returned. uint256 start = _numGames == 0 ? 0 : _numGames - 1; IDisputeGameFactory.GameSearchResult[] memory games = disputeGameFactory.findLatestGames(GameType.wrap(0), start, _n); From 48bcc610d1240b7b91942040490a9f2183234e25 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 07:25:47 -0400 Subject: [PATCH 043/135] Refactor DisputeGameFactory.t.sol: introduce constants for aggregate block intervals, enhance game creation assertions with a dedicated helper function, and improve test clarity by streamlining initialization logic. --- test/L1/proofs/DisputeGameFactory.t.sol | 151 ++++++++++++------------ 1 file changed, 78 insertions(+), 73 deletions(-) diff --git a/test/L1/proofs/DisputeGameFactory.t.sol b/test/L1/proofs/DisputeGameFactory.t.sol index a9f0fdcf..491cbeb1 100644 --- a/test/L1/proofs/DisputeGameFactory.t.sol +++ b/test/L1/proofs/DisputeGameFactory.t.sol @@ -45,7 +45,10 @@ contract DisputeGameFactory_FakeClone_Harness { abstract contract DisputeGameFactory_TestInit is CommonTest { uint256 internal constant DEFAULT_INIT_BOND = 0.08 ether; uint256 internal constant L2_CHAIN_ID = 111; + uint256 internal constant AGGREGATE_BLOCK_INTERVAL = 100; + uint256 internal constant AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL = 10; uint32 internal constant MAX_GAME_TYPE = 8; + address internal constant NON_OWNER = address(0xBEEF); DisputeGameFactory_FakeClone_Harness fakeClone; @@ -73,6 +76,34 @@ abstract contract DisputeGameFactory_TestInit is CommonTest { disputeGameFactory.setInitBond(_gameType, _initBond); vm.stopPrank(); } + + function _expectOwnableRevertFrom(address _caller) internal { + vm.prank(_caller); + vm.expectRevert("Ownable: caller is not the owner"); + } + + function _assertCreatedGame( + GameType _gameType, + Claim _rootClaim, + bytes memory _extraData, + IDisputeGame _proxy, + uint256 _index + ) + internal + view + { + (IDisputeGame game, Timestamp timestamp) = disputeGameFactory.games(_gameType, _rootClaim, _extraData); + + assertEq(address(game), address(_proxy)); + assertEq(Timestamp.unwrap(timestamp), block.timestamp); + assertEq(disputeGameFactory.gameCount(), _index + 1); + + (GameType gameType, Timestamp timestampAtIndex, IDisputeGame gameAtIndex) = + disputeGameFactory.gameAtIndex(_index); + assertEq(GameType.unwrap(gameType), GameType.unwrap(_gameType)); + assertEq(address(gameAtIndex), address(_proxy)); + assertEq(Timestamp.unwrap(timestampAtIndex), block.timestamp); + } } /// @title DisputeGameFactory_Initialize_Test @@ -137,16 +168,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { emit DisputeGameCreated(address(0), gt, rootClaim); IDisputeGame proxy = disputeGameFactory.create{ value: _value }(gt, rootClaim, extraData); - (IDisputeGame game, Timestamp timestamp) = disputeGameFactory.games(gt, rootClaim, extraData); - - assertEq(address(game), address(proxy)); - assertEq(Timestamp.unwrap(timestamp), block.timestamp); - assertEq(disputeGameFactory.gameCount(), gameCountBefore + 1); - - (, Timestamp timestamp2, IDisputeGame game2) = disputeGameFactory.gameAtIndex(gameCountBefore); - assertEq(address(game2), address(proxy)); - assertEq(Timestamp.unwrap(timestamp2), block.timestamp); - + _assertCreatedGame(gt, rootClaim, extraData, proxy, gameCountBefore); assertEq(address(proxy).balance, _value); } @@ -169,8 +191,8 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { /// @notice Tests that the `create` function reverts when there is no implementation set for /// the given `GameType`. function testFuzz_create_noImpl_reverts(uint32 gameType, Claim rootClaim, bytes calldata extraData) public { - // Game type 0 is configured by the deploy script, so choose an out-of-range type. - GameType gt = GameType.wrap(uint32(bound(gameType, MAX_GAME_TYPE + 1, type(uint32).max))); + GameType gt = GameType.wrap(gameType); + vm.assume(address(disputeGameFactory.gameImpls(gt)) == address(0)); vm.expectRevert(abi.encodeWithSelector(IDisputeGameFactory.NoImplementation.selector, gt)); disputeGameFactory.create(gt, rootClaim, extraData); @@ -186,11 +208,9 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { vm.expectEmit(false, true, true, false); emit DisputeGameCreated(address(0), gt, rootClaim); + uint256 gameCountBefore = disputeGameFactory.gameCount(); IDisputeGame proxy = disputeGameFactory.create{ value: bondAmount }(gt, rootClaim, extraData); - - (IDisputeGame game, Timestamp timestamp) = disputeGameFactory.games(gt, rootClaim, extraData); - assertEq(address(game), address(proxy)); - assertEq(Timestamp.unwrap(timestamp), block.timestamp); + _assertCreatedGame(gt, rootClaim, extraData, proxy, gameCountBefore); vm.expectRevert( abi.encodeWithSelector( @@ -213,8 +233,8 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { AggregateVerifier.ZkHashes(bytes32(uint256(2)), bytes32(uint256(3))), bytes32(uint256(4)), L2_CHAIN_ID, - 100, - 10 + AGGREGATE_BLOCK_INTERVAL, + AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL ); _setGame(address(gameImpl), GameTypes.AGGREGATE_VERIFIER); @@ -222,12 +242,15 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { Proposal memory startingRoot = anchorStateRegistry.getStartingAnchorRoot(); bytes memory intermediateRoots; for (uint256 i = 1; i < gameImpl.intermediateOutputRootsCount(); i++) { - intermediateRoots = - abi.encodePacked(intermediateRoots, keccak256(abi.encode(startingRoot.l2SequenceNumber + 10 * i))); + intermediateRoots = abi.encodePacked( + intermediateRoots, + keccak256(abi.encode(startingRoot.l2SequenceNumber + AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL * i)) + ); } intermediateRoots = abi.encodePacked(intermediateRoots, rootClaim.raw()); - bytes memory extraData = - abi.encodePacked(startingRoot.l2SequenceNumber + 100, address(anchorStateRegistry), intermediateRoots); + bytes memory extraData = abi.encodePacked( + startingRoot.l2SequenceNumber + AGGREGATE_BLOCK_INTERVAL, address(anchorStateRegistry), intermediateRoots + ); bytes memory proof = abi.encodePacked( uint8(AggregateVerifier.ProofType.TEE), blockhash(block.number - 1), block.number - 1, bytes32(0) ); @@ -251,13 +274,13 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { assertEq(gameV2.extraData(), extraData); assertEq(gameV2.L2_CHAIN_ID(), L2_CHAIN_ID); assertEq(address(gameV2.gameCreator()), address(this)); - assertEq(gameV2.l2SequenceNumber(), startingRoot.l2SequenceNumber + 100); + assertEq(gameV2.l2SequenceNumber(), startingRoot.l2SequenceNumber + AGGREGATE_BLOCK_INTERVAL); assertEq(gameV2.parentAddress(), address(anchorStateRegistry)); assertEq(address(gameV2.DELAYED_WETH()), address(delayedWeth)); assertEq(address(gameV2.anchorStateRegistry()), address(anchorStateRegistry)); assertEq(GameType.unwrap(gameV2.gameType()), GameType.unwrap(GameTypes.AGGREGATE_VERIFIER)); - assertEq(gameV2.BLOCK_INTERVAL(), 100); - assertEq(gameV2.INTERMEDIATE_BLOCK_INTERVAL(), 10); + assertEq(gameV2.BLOCK_INTERVAL(), AGGREGATE_BLOCK_INTERVAL); + assertEq(gameV2.INTERMEDIATE_BLOCK_INTERVAL(), AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL); } } @@ -277,8 +300,7 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni /// @notice Tests that the `setImplementation` function reverts when called by a non-owner. function test_setImplementation_notOwner_reverts() public { - vm.prank(address(0)); - vm.expectRevert("Ownable: caller is not the owner"); + _expectOwnableRevertFrom(NON_OWNER); disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1))); } @@ -310,8 +332,7 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni function test_setImplementationArgs_notOwner_reverts() public { bytes memory args = abi.encode(uint256(123), address(0xdead)); - vm.prank(address(0)); - vm.expectRevert("Ownable: caller is not the owner"); + _expectOwnableRevertFrom(NON_OWNER); disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1)), args); } } @@ -339,8 +360,7 @@ contract DisputeGameFactory_SetInitBond_Test is DisputeGameFactory_TestInit { /// @notice Tests that the `setInitBond` function reverts when called by a non-owner. function test_setInitBond_notOwner_reverts() public { - vm.prank(address(0)); - vm.expectRevert("Ownable: caller is not the owner"); + _expectOwnableRevertFrom(NON_OWNER); disputeGameFactory.setInitBond(GameTypes.AGGREGATE_VERIFIER, 1 ether); } } @@ -363,6 +383,9 @@ contract DisputeGameFactory_GetGameUUID_Test is DisputeGameFactory_TestInit { /// @title DisputeGameFactory_FindLatestGames_Test /// @notice Tests the `findLatestGames` function of the `DisputeGameFactory` contract. contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit { + GameType internal constant FIND_LATEST_SEARCH_GAME_TYPE = GameType.wrap(type(uint32).max); + GameType internal constant FIND_LATEST_OTHER_GAME_TYPE = GameType.wrap(type(uint32).max - 1); + function setUp() public override { super.setUp(); @@ -390,63 +413,36 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit uint256 gameCount = disputeGameFactory.gameCount(); - IDisputeGameFactory.GameSearchResult[] memory games; - uint256 start = gameCount - 1; - games = disputeGameFactory.findLatestGames(GameType.wrap(1), start, 1); - assertEq(games.length, 1); - - assertEq(games[0].index, start); - (GameType gameType, Timestamp createdAt,) = games[0].metadata.unpack(); - assertEq(gameType.raw(), 1); - assertEq(createdAt.raw(), block.timestamp); - - games = disputeGameFactory.findLatestGames(GameType.wrap(0), start, 1); - assertEq(games.length, 1); - - assertEq(games[0].index, start - 1); - (gameType, createdAt,) = games[0].metadata.unpack(); - assertEq(gameType.raw(), 0); - assertEq(createdAt.raw(), block.timestamp); - - games = disputeGameFactory.findLatestGames(GameType.wrap(2), start, 1); - assertEq(games.length, 1); - - assertEq(games[0].index, start - 2); - (gameType, createdAt,) = games[0].metadata.unpack(); - assertEq(gameType.raw(), 2); - assertEq(createdAt.raw(), block.timestamp); + _assertLatestGameResult(GameType.wrap(1), start, start); + _assertLatestGameResult(GameType.wrap(0), start, start - 1); + _assertLatestGameResult(GameType.wrap(2), start, start - 2); } /// @notice Tests that `findLatestGames` returns the correct games, if there are less than `_n` /// games of the given type available. function test_findLatestGames_lessThanNAvailable_succeeds() public { - // Fork tests inherit existing games, which would make this bounded scan unnecessarily large. - if (isForkTest()) { - vm.store( - address(disputeGameFactory), - bytes32(ForgeArtifacts.getSlot("DisputeGameFactory", "_disputeGameList").slot), - bytes32(0) - ); - } + _setGame(address(fakeClone), FIND_LATEST_SEARCH_GAME_TYPE, 0); + _setGame(address(fakeClone), FIND_LATEST_OTHER_GAME_TYPE, 0); - disputeGameFactory.create(GameType.wrap(1), Claim.wrap(bytes32(0)), abi.encode(0)); - disputeGameFactory.create(GameType.wrap(1), Claim.wrap(bytes32(uint256(1))), abi.encode(1)); + uint256 firstSearchGameIndex = disputeGameFactory.gameCount(); + disputeGameFactory.create(FIND_LATEST_SEARCH_GAME_TYPE, Claim.wrap(bytes32(0)), abi.encode(0)); + disputeGameFactory.create(FIND_LATEST_SEARCH_GAME_TYPE, Claim.wrap(bytes32(uint256(1))), abi.encode(1)); for (uint256 i; i < 1 << 3; i++) { - disputeGameFactory.create(GameType.wrap(0), Claim.wrap(bytes32(i)), abi.encode(i)); + disputeGameFactory.create(FIND_LATEST_OTHER_GAME_TYPE, Claim.wrap(bytes32(i)), abi.encode(i)); } uint256 gameCount = disputeGameFactory.gameCount(); IDisputeGameFactory.GameSearchResult[] memory games; - games = disputeGameFactory.findLatestGames(GameType.wrap(2), gameCount - 1, 5); + games = disputeGameFactory.findLatestGames(GameType.wrap(type(uint32).max - 2), gameCount - 1, 5); assertEq(games.length, 0); - games = disputeGameFactory.findLatestGames(GameType.wrap(1), gameCount - 1, 5); + games = disputeGameFactory.findLatestGames(FIND_LATEST_SEARCH_GAME_TYPE, gameCount - 1, 5); assertEq(games.length, 2); - assertEq(games[0].index, 1); - assertEq(games[1].index, 0); + assertEq(games[0].index, firstSearchGameIndex + 1); + assertEq(games[1].index, firstSearchGameIndex); } /// @notice Tests that the expected number of games are returned when `findLatestGames` is @@ -472,6 +468,16 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_TestInit disputeGameFactory.findLatestGames(GameType.wrap(0), start, _n); assertEq(games.length, _n); } + + function _assertLatestGameResult(GameType _gameType, uint256 _start, uint256 _expectedIndex) internal view { + IDisputeGameFactory.GameSearchResult[] memory games = disputeGameFactory.findLatestGames(_gameType, _start, 1); + assertEq(games.length, 1); + + assertEq(games[0].index, _expectedIndex); + (GameType gameType, Timestamp createdAt,) = games[0].metadata.unpack(); + assertEq(GameType.unwrap(gameType), GameType.unwrap(_gameType)); + assertEq(Timestamp.unwrap(createdAt), block.timestamp); + } } /// @title DisputeGameFactory_Uncategorized_Test @@ -491,8 +497,7 @@ contract DisputeGameFactory_Uncategorized_Test is DisputeGameFactory_TestInit { /// @notice Tests that the `transferOwnership` function reverts when called by a non-owner. function test_transferOwnership_notOwner_reverts() public { - vm.prank(address(0)); - vm.expectRevert("Ownable: caller is not the owner"); + _expectOwnableRevertFrom(NON_OWNER); disputeGameFactory.transferOwnership(address(1)); } } From e317b44314db828c102d65acec65c5616fb85c9b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 07:32:48 -0400 Subject: [PATCH 044/135] Refactor DisputeGameFactory.t.sol: rename ownership revert expectation function for clarity, streamline initialization logic with a dedicated helper function, and enhance game creation assertions for improved test maintainability. --- test/L1/proofs/DisputeGameFactory.t.sol | 34 ++++++++++--------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/test/L1/proofs/DisputeGameFactory.t.sol b/test/L1/proofs/DisputeGameFactory.t.sol index 491cbeb1..6cab78c2 100644 --- a/test/L1/proofs/DisputeGameFactory.t.sol +++ b/test/L1/proofs/DisputeGameFactory.t.sol @@ -77,8 +77,8 @@ abstract contract DisputeGameFactory_TestInit is CommonTest { vm.stopPrank(); } - function _expectOwnableRevertFrom(address _caller) internal { - vm.prank(_caller); + function _expectNonOwnerRevert() internal { + vm.prank(NON_OWNER); vm.expectRevert("Ownable: caller is not the owner"); } @@ -109,13 +109,9 @@ abstract contract DisputeGameFactory_TestInit is CommonTest { /// @title DisputeGameFactory_Initialize_Test /// @notice Tests the `initialize` function of the `DisputeGameFactory` contract. contract DisputeGameFactory_Initialize_Test is DisputeGameFactory_TestInit { - bytes32 internal initializedSlot; - - function setUp() public override { - super.setUp(); - + function _initializedSlot() internal view returns (bytes32) { StorageSlot memory slot = ForgeArtifacts.getSlot("DisputeGameFactory", "_initialized"); - initializedSlot = bytes32(slot.slot); + return bytes32(slot.slot); } /// @notice Tests that initialization reverts if called by a non-proxy admin or proxy admin @@ -126,7 +122,7 @@ contract DisputeGameFactory_Initialize_Test is DisputeGameFactory_TestInit { _sender != address(disputeGameFactory.proxyAdmin()) && _sender != disputeGameFactory.proxyAdminOwner() ); - vm.store(address(disputeGameFactory), initializedSlot, bytes32(0)); + vm.store(address(disputeGameFactory), _initializedSlot(), bytes32(0)); vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); vm.prank(_sender); @@ -137,7 +133,7 @@ contract DisputeGameFactory_Initialize_Test is DisputeGameFactory_TestInit { /// but confirms that the initValue is not incremented incorrectly if an upgrade /// function is not present. function test_initialize_correctInitializerValue_succeeds() public view { - bytes32 slotVal = vm.load(address(disputeGameFactory), initializedSlot); + bytes32 slotVal = vm.load(address(disputeGameFactory), _initializedSlot()); uint8 val = uint8(uint256(slotVal) & 0xFF); assertEq(val, disputeGameFactory.initVersion()); @@ -241,7 +237,8 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { Claim rootClaim = Claim.wrap(bytes32(hex"beef")); Proposal memory startingRoot = anchorStateRegistry.getStartingAnchorRoot(); bytes memory intermediateRoots; - for (uint256 i = 1; i < gameImpl.intermediateOutputRootsCount(); i++) { + uint256 intermediateRootsCount = gameImpl.intermediateOutputRootsCount(); + for (uint256 i = 1; i < intermediateRootsCount; i++) { intermediateRoots = abi.encodePacked( intermediateRoots, keccak256(abi.encode(startingRoot.l2SequenceNumber + AGGREGATE_INTERMEDIATE_BLOCK_INTERVAL * i)) @@ -258,15 +255,12 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_TestInit { uint256 bondAmount = disputeGameFactory.initBonds(GameTypes.AGGREGATE_VERIFIER); vm.deal(address(this), bondAmount); + uint256 gameCountBefore = disputeGameFactory.gameCount(); IDisputeGame proxy = disputeGameFactory.createWithInitData{ value: bondAmount }( GameTypes.AGGREGATE_VERIFIER, rootClaim, extraData, proof ); - (IDisputeGame game, Timestamp timestamp) = - disputeGameFactory.games(GameTypes.AGGREGATE_VERIFIER, rootClaim, extraData); - - assertEq(address(game), address(proxy)); - assertEq(Timestamp.unwrap(timestamp), block.timestamp); + _assertCreatedGame(GameTypes.AGGREGATE_VERIFIER, rootClaim, extraData, proxy, gameCountBefore); AggregateVerifier gameV2 = AggregateVerifier(address(proxy)); @@ -300,7 +294,7 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni /// @notice Tests that the `setImplementation` function reverts when called by a non-owner. function test_setImplementation_notOwner_reverts() public { - _expectOwnableRevertFrom(NON_OWNER); + _expectNonOwnerRevert(); disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1))); } @@ -332,7 +326,7 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_TestIni function test_setImplementationArgs_notOwner_reverts() public { bytes memory args = abi.encode(uint256(123), address(0xdead)); - _expectOwnableRevertFrom(NON_OWNER); + _expectNonOwnerRevert(); disputeGameFactory.setImplementation(GameTypes.AGGREGATE_VERIFIER, IDisputeGame(address(1)), args); } } @@ -360,7 +354,7 @@ contract DisputeGameFactory_SetInitBond_Test is DisputeGameFactory_TestInit { /// @notice Tests that the `setInitBond` function reverts when called by a non-owner. function test_setInitBond_notOwner_reverts() public { - _expectOwnableRevertFrom(NON_OWNER); + _expectNonOwnerRevert(); disputeGameFactory.setInitBond(GameTypes.AGGREGATE_VERIFIER, 1 ether); } } @@ -497,7 +491,7 @@ contract DisputeGameFactory_Uncategorized_Test is DisputeGameFactory_TestInit { /// @notice Tests that the `transferOwnership` function reverts when called by a non-owner. function test_transferOwnership_notOwner_reverts() public { - _expectOwnableRevertFrom(NON_OWNER); + _expectNonOwnerRevert(); disputeGameFactory.transferOwnership(address(1)); } } From b02dce7a1793fbb47b4bb7cc76cd73c4aeb56087 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 07:38:19 -0400 Subject: [PATCH 045/135] Refactor NitroEnclaveVerifier.t.sol: update variable visibility to internal for better encapsulation, introduce new constants for proof selectors, and streamline proof handling in verification tests for improved clarity and maintainability. --- test/L1/proofs/NitroEnclaveVerifier.t.sol | 201 +++++++++------------- 1 file changed, 79 insertions(+), 122 deletions(-) diff --git a/test/L1/proofs/NitroEnclaveVerifier.t.sol b/test/L1/proofs/NitroEnclaveVerifier.t.sol index 246c4d0f..20bbd9fe 100644 --- a/test/L1/proofs/NitroEnclaveVerifier.t.sol +++ b/test/L1/proofs/NitroEnclaveVerifier.t.sol @@ -15,29 +15,32 @@ import { import { NitroEnclaveVerifier } from "src/L1/proofs/tee/NitroEnclaveVerifier.sol"; contract NitroEnclaveVerifierTest is Test { - NitroEnclaveVerifier public verifier; - - address public owner; - address public submitter; - address public revokerAddr; - address public mockRiscZeroVerifier; - address public mockSP1Verifier; - - bytes32 public constant ROOT_CERT = keccak256("root-cert"); - bytes32 public constant INTERMEDIATE_CERT_1 = keccak256("intermediate-cert-1"); - bytes32 public constant INTERMEDIATE_CERT_2 = keccak256("intermediate-cert-2"); - bytes32 public constant VERIFIER_ID = keccak256("verifier-id"); - bytes32 public constant AGGREGATOR_ID = keccak256("aggregator-id"); - bytes32 public constant VERIFIER_PROOF_ID = keccak256("verifier-proof-id"); - - uint64 public constant MAX_TIME_DIFF = 3600; // 1 hour + NitroEnclaveVerifier internal verifier; + + address internal owner; + address internal submitter; + address internal revokerAddr; + address internal mockRiscZeroVerifier; + address internal mockSP1Verifier; + + bytes32 internal constant ROOT_CERT = keccak256("root-cert"); + bytes32 internal constant INTERMEDIATE_CERT_1 = keccak256("intermediate-cert-1"); + bytes32 internal constant INTERMEDIATE_CERT_2 = keccak256("intermediate-cert-2"); + bytes32 internal constant VERIFIER_ID = keccak256("verifier-id"); + bytes32 internal constant AGGREGATOR_ID = keccak256("aggregator-id"); + bytes32 internal constant VERIFIER_PROOF_ID = keccak256("verifier-proof-id"); + bytes4 internal constant DEFAULT_PROOF_SELECTOR = bytes4(0); + bytes4 internal constant RISC_ZERO_VERIFY_SELECTOR = bytes4(keccak256("verify(bytes,bytes32,bytes32)")); + bytes4 internal constant SP1_VERIFY_PROOF_SELECTOR = bytes4(keccak256("verifyProof(bytes32,bytes,bytes)")); + + uint64 internal constant MAX_TIME_DIFF = 3600; // 1 hour // Realistic timestamp so timestamp validation tests work correctly uint256 internal constant REALISTIC_TIMESTAMP = 1_700_000_000; // Expiry timestamps for test certs (well after REALISTIC_TIMESTAMP) uint64 internal constant INTERMEDIATE_CERT_1_EXPIRY = 1_800_000_000; // ~2027 - uint64 internal constant INTERMEDIATE_CERT_2_EXPIRY = 1_750_000_000; // ~2025 + uint64 internal constant ROOT_CERT_EXPIRY = 1_900_000_000; uint64 internal constant NEW_LEAF_CERT_EXPIRY = 1_700_100_000; // ~28 hours after REALISTIC_TIMESTAMP function setUp() public { @@ -251,12 +254,6 @@ contract NitroEnclaveVerifierTest is Test { assertEq(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1), 0); } - function testOwnerCanStillRevokeCert() public { - assertGt(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1), 0); - verifier.revokeCert(INTERMEDIATE_CERT_1); - assertEq(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1), 0); - } - function testSetRevoker() public { address newRevoker = makeAddr("new-revoker"); verifier.setRevoker(newRevoker); @@ -267,7 +264,6 @@ contract NitroEnclaveVerifierTest is Test { verifier.setRevoker(address(0)); assertEq(verifier.revoker(), address(0)); - // Now revoker (zero address) can't call revokeCert vm.prank(revokerAddr); vm.expectRevert(NitroEnclaveVerifier.CallerNotOwnerOrRevoker.selector); verifier.revokeCert(INTERMEDIATE_CERT_1); @@ -488,14 +484,13 @@ contract NitroEnclaveVerifierTest is Test { // ============ verify — ZkVerifierNotConfigured ============ function testVerifyRevertsIfZkVerifierNotConfigured() public { - // Set up config WITHOUT a zkVerifier address (zero) ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: address(0) }); verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); VerifierJournal memory journal = _createSuccessJournal(); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); vm.prank(submitter); vm.expectRevert( @@ -507,7 +502,6 @@ contract NitroEnclaveVerifierTest is Test { // ============ verify — Unknown_Zk_Coprocessor ============ function testVerifyRevertsForUnknownCoprocessor() public { - // Use ZkCoProcessorType.Unknown (0) — not RiscZero or Succinct ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier }); @@ -515,7 +509,7 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); vm.prank(submitter); vm.expectRevert(NitroEnclaveVerifier.Unknown_Zk_Coprocessor.selector); @@ -527,13 +521,13 @@ contract NitroEnclaveVerifierTest is Test { function testVerifyRevertsIfRouteFrozen() public { _setUpRiscZeroConfig(); - bytes4 selector = bytes4(0); // matches the selector in our proofBytes + bytes4 selector = DEFAULT_PROOF_SELECTOR; verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("route-v")); verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); VerifierJournal memory journal = _createSuccessJournal(); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); vm.prank(submitter); vm.expectRevert( @@ -549,7 +543,7 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -565,7 +559,7 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); journal.certs[0] = keccak256("wrong-root"); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -581,7 +575,7 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); journal.trustedCertsPrefixLen = 0; bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -598,7 +592,7 @@ contract NitroEnclaveVerifierTest is Test { // Replace trusted intermediate with untrusted one, but keep trustedCertsPrefixLen = 2 journal.certs[1] = keccak256("untrusted-intermediate"); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -615,7 +609,7 @@ contract NitroEnclaveVerifierTest is Test { // Set timestamp far in the past — more than maxTimeDiff seconds ago (in ms) journal.timestamp = uint64(block.timestamp - MAX_TIME_DIFF - 1) * 1000; bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -632,7 +626,7 @@ contract NitroEnclaveVerifierTest is Test { // Set timestamp in the future (converted to ms) journal.timestamp = uint64(block.timestamp + 100) * 1000; bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -648,24 +642,10 @@ contract NitroEnclaveVerifierTest is Test { bytes32 newCert = keccak256("new-leaf-cert"); assertEq(verifier.trustedIntermediateCerts(newCert), 0); - VerifierJournal memory journal = _createSuccessJournal(); - // Add a new cert beyond the trusted prefix that will get cached - bytes32[] memory certs = new bytes32[](3); - certs[0] = ROOT_CERT; - certs[1] = INTERMEDIATE_CERT_1; - certs[2] = newCert; - journal.certs = certs; - - uint64[] memory expiries = new uint64[](3); - expiries[0] = INTERMEDIATE_CERT_1_EXPIRY + 100_000_000; // root expiry (doesn't matter for caching) - expiries[1] = INTERMEDIATE_CERT_1_EXPIRY; - expiries[2] = NEW_LEAF_CERT_EXPIRY; - journal.certExpiries = expiries; - - journal.trustedCertsPrefixLen = 2; // only root + 1 intermediate are pre-trusted + VerifierJournal memory journal = _createSuccessJournalWithLeaf(newCert, NEW_LEAF_CERT_EXPIRY); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -681,7 +661,7 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); journal.result = VerificationResult.IntermediateCertsNotTrusted; bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -694,11 +674,9 @@ contract NitroEnclaveVerifierTest is Test { // ============ verify — Succinct SP1 happy path ============ function testVerifySuccessfulJournalSP1() public { - _setUpSP1Config(); - VerifierJournal memory journal = _createSuccessJournal(); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockSP1Verify(VERIFIER_ID, output, proofBytes); @@ -708,17 +686,12 @@ contract NitroEnclaveVerifierTest is Test { assertEq(uint8(result.result), uint8(VerificationResult.Success)); } - function testVerifyRevertsIfNotProofSubmitterSP1() public { - vm.expectRevert(NitroEnclaveVerifier.CallerNotProofSubmitter.selector); - verifier.verify("", ZkCoProcessorType.Succinct, ""); - } - function testVerifyRevertsIfZkVerifierNotConfiguredSP1() public { ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: address(0) }); verifier.setZkConfiguration(ZkCoProcessorType.Succinct, config, VERIFIER_PROOF_ID); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); vm.prank(submitter); vm.expectRevert( @@ -746,7 +719,7 @@ contract NitroEnclaveVerifierTest is Test { BatchVerifierJournal({ verifierVk: VERIFIER_PROOF_ID, outputs: outputs }); bytes memory output = abi.encode(batchJournal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); @@ -768,7 +741,7 @@ contract NitroEnclaveVerifierTest is Test { BatchVerifierJournal memory batchJournal = BatchVerifierJournal({ verifierVk: wrongVk, outputs: outputs }); bytes memory output = abi.encode(batchJournal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); @@ -780,8 +753,6 @@ contract NitroEnclaveVerifierTest is Test { } function testBatchVerifySuccessSP1() public { - _setUpSP1Config(); - VerifierJournal memory journal = _createSuccessJournal(); VerifierJournal[] memory outputs = new VerifierJournal[](1); outputs[0] = journal; @@ -790,7 +761,7 @@ contract NitroEnclaveVerifierTest is Test { BatchVerifierJournal({ verifierVk: VERIFIER_PROOF_ID, outputs: outputs }); bytes memory output = abi.encode(batchJournal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockSP1Verify(AGGREGATOR_ID, output, proofBytes); @@ -808,9 +779,8 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); - // Revoke the intermediate cert before verification verifier.revokeCert(INTERMEDIATE_CERT_1); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -830,10 +800,9 @@ contract NitroEnclaveVerifierTest is Test { vm.warp(INTERMEDIATE_CERT_1_EXPIRY + 1); VerifierJournal memory journal = _createSuccessJournal(); - // Update timestamp to be valid at the new block.timestamp journal.timestamp = uint64(block.timestamp - 1) * 1000; bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -852,7 +821,7 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); journal.timestamp = uint64(block.timestamp - 1) * 1000; bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -868,22 +837,10 @@ contract NitroEnclaveVerifierTest is Test { bytes32 expiredLeaf = keccak256("expired-untrusted-leaf"); - VerifierJournal memory journal = _createSuccessJournal(); - bytes32[] memory certs = new bytes32[](3); - certs[0] = ROOT_CERT; - certs[1] = INTERMEDIATE_CERT_1; - certs[2] = expiredLeaf; - journal.certs = certs; - - uint64[] memory expiries = new uint64[](3); - expiries[0] = INTERMEDIATE_CERT_1_EXPIRY + 100_000_000; - expiries[1] = INTERMEDIATE_CERT_1_EXPIRY; - expiries[2] = uint64(block.timestamp - 1); - journal.certExpiries = expiries; - journal.trustedCertsPrefixLen = 2; + VerifierJournal memory journal = _createSuccessJournalWithLeaf(expiredLeaf, uint64(block.timestamp - 1)); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -913,23 +870,10 @@ contract NitroEnclaveVerifierTest is Test { bytes32 newCert = keccak256("brand-new-cert"); uint64 newCertExpiry = uint64(REALISTIC_TIMESTAMP + 86_400); // 1 day from now - VerifierJournal memory journal = _createSuccessJournal(); - bytes32[] memory certs = new bytes32[](3); - certs[0] = ROOT_CERT; - certs[1] = INTERMEDIATE_CERT_1; - certs[2] = newCert; - journal.certs = certs; - - uint64[] memory expiries = new uint64[](3); - expiries[0] = INTERMEDIATE_CERT_1_EXPIRY + 100_000_000; - expiries[1] = INTERMEDIATE_CERT_1_EXPIRY; - expiries[2] = newCertExpiry; - journal.certExpiries = expiries; - - journal.trustedCertsPrefixLen = 2; + VerifierJournal memory journal = _createSuccessJournalWithLeaf(newCert, newCertExpiry); bytes memory output = abi.encode(journal); - bytes memory proofBytes = abi.encodePacked(bytes4(0), bytes32(0)); + bytes memory proofBytes = _proofBytes(); _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); @@ -939,12 +883,6 @@ contract NitroEnclaveVerifierTest is Test { assertEq(verifier.trustedIntermediateCerts(newCert), newCertExpiry); } - function testRevokeCertSetsExpiryToZero() public { - assertEq(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1), INTERMEDIATE_CERT_1_EXPIRY); - verifier.revokeCert(INTERMEDIATE_CERT_1); - assertEq(verifier.trustedIntermediateCerts(INTERMEDIATE_CERT_1), 0); - } - // ============ Helpers ============ function _setUpRiscZeroConfig() internal { @@ -954,10 +892,12 @@ contract NitroEnclaveVerifierTest is Test { verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); } - function _setUpSP1Config() internal { - ZkCoProcessorConfig memory config = - ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockSP1Verifier }); - verifier.setZkConfiguration(ZkCoProcessorType.Succinct, config, VERIFIER_PROOF_ID); + function _proofBytes() internal pure returns (bytes memory) { + return _proofBytes(DEFAULT_PROOF_SELECTOR); + } + + function _proofBytes(bytes4 selector) internal pure returns (bytes memory) { + return abi.encodePacked(selector, bytes32(0)); } function _createSuccessJournal() internal view returns (VerifierJournal memory) { @@ -966,7 +906,7 @@ contract NitroEnclaveVerifierTest is Test { certs[1] = INTERMEDIATE_CERT_1; uint64[] memory expiries = new uint64[](2); - expiries[0] = INTERMEDIATE_CERT_1_EXPIRY + 100_000_000; // root expiry (far future) + expiries[0] = ROOT_CERT_EXPIRY; expiries[1] = INTERMEDIATE_CERT_1_EXPIRY; Pcr[] memory pcrs = new Pcr[](0); @@ -985,25 +925,42 @@ contract NitroEnclaveVerifierTest is Test { }); } + function _createSuccessJournalWithLeaf( + bytes32 leafCert, + uint64 leafExpiry + ) + internal + view + returns (VerifierJournal memory) + { + VerifierJournal memory journal = _createSuccessJournal(); + + bytes32[] memory certs = new bytes32[](3); + certs[0] = ROOT_CERT; + certs[1] = INTERMEDIATE_CERT_1; + certs[2] = leafCert; + journal.certs = certs; + + uint64[] memory expiries = new uint64[](3); + expiries[0] = ROOT_CERT_EXPIRY; + expiries[1] = INTERMEDIATE_CERT_1_EXPIRY; + expiries[2] = leafExpiry; + journal.certExpiries = expiries; + + return journal; + } + function _mockRiscZeroVerify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal { - // IRiscZeroVerifier.verify(proofBytes, programId, sha256(output)) vm.mockCall( mockRiscZeroVerifier, - abi.encodeWithSelector( - bytes4(keccak256("verify(bytes,bytes32,bytes32)")), proofBytes, programId, sha256(output) - ), + abi.encodeWithSelector(RISC_ZERO_VERIFY_SELECTOR, proofBytes, programId, sha256(output)), "" ); } function _mockSP1Verify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal { - // ISP1Verifier.verifyProof(programVKey, publicValues, proofBytes) vm.mockCall( - mockSP1Verifier, - abi.encodeWithSelector( - bytes4(keccak256("verifyProof(bytes32,bytes,bytes)")), programId, output, proofBytes - ), - "" + mockSP1Verifier, abi.encodeWithSelector(SP1_VERIFY_PROOF_SELECTOR, programId, output, proofBytes), "" ); } } From 94315581fdba347976360827d639807fcdb685e3 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 07:48:21 -0400 Subject: [PATCH 046/135] Refactor NitroEnclaveVerifier.t.sol: introduce new proof selector constants, streamline revert expectations with a dedicated helper function, and enhance configuration setup for improved test clarity and maintainability. --- test/L1/proofs/NitroEnclaveVerifier.t.sol | 334 ++++++++-------------- 1 file changed, 127 insertions(+), 207 deletions(-) diff --git a/test/L1/proofs/NitroEnclaveVerifier.t.sol b/test/L1/proofs/NitroEnclaveVerifier.t.sol index 20bbd9fe..d4876cf0 100644 --- a/test/L1/proofs/NitroEnclaveVerifier.t.sol +++ b/test/L1/proofs/NitroEnclaveVerifier.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import { Test } from "lib/forge-std/src/Test.sol"; +import { Ownable } from "lib/solady/src/auth/Ownable.sol"; import { ZkCoProcessorType, @@ -30,6 +31,8 @@ contract NitroEnclaveVerifierTest is Test { bytes32 internal constant AGGREGATOR_ID = keccak256("aggregator-id"); bytes32 internal constant VERIFIER_PROOF_ID = keccak256("verifier-proof-id"); bytes4 internal constant DEFAULT_PROOF_SELECTOR = bytes4(0); + bytes4 internal constant NON_DEFAULT_PROOF_SELECTOR = 0x00000001; + bytes4 internal constant TEST_ROUTE_SELECTOR = bytes4(keccak256("test")); bytes4 internal constant RISC_ZERO_VERIFY_SELECTOR = bytes4(keccak256("verify(bytes,bytes32,bytes32)")); bytes4 internal constant SP1_VERIFY_PROOF_SELECTOR = bytes4(keccak256("verifyProof(bytes32,bytes,bytes)")); @@ -58,8 +61,7 @@ contract NitroEnclaveVerifierTest is Test { uint64[] memory trustedCertExpiries = new uint64[](1); trustedCertExpiries[0] = INTERMEDIATE_CERT_1_EXPIRY; - ZkCoProcessorConfig memory zkCfg = - ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockSP1Verifier }); + ZkCoProcessorConfig memory zkCfg = _zkConfig(mockSP1Verifier); verifier = new NitroEnclaveVerifier( owner, @@ -131,8 +133,7 @@ contract NitroEnclaveVerifierTest is Test { } function testSetRootCertRevertsIfNotOwner() public { - vm.prank(submitter); - vm.expectRevert(); + _expectNotOwnerRevert(submitter); verifier.setRootCert(keccak256("bad")); } @@ -150,8 +151,7 @@ contract NitroEnclaveVerifierTest is Test { } function testSetMaxTimeDiffRevertsIfNotOwner() public { - vm.prank(submitter); - vm.expectRevert(); + _expectNotOwnerRevert(submitter); verifier.setMaxTimeDiff(7200); } @@ -169,17 +169,14 @@ contract NitroEnclaveVerifierTest is Test { } function testSetProofSubmitterRevertsIfNotOwner() public { - vm.prank(submitter); - vm.expectRevert(); + _expectNotOwnerRevert(submitter); verifier.setProofSubmitter(makeAddr("anyone")); } // ============ setZkConfiguration Tests ============ function testSetZkConfiguration() public { - ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ - verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier - }); + ZkCoProcessorConfig memory config = _zkConfig(mockRiscZeroVerifier); verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); @@ -192,12 +189,9 @@ contract NitroEnclaveVerifierTest is Test { } function testSetZkConfigurationRevertsIfNotOwner() public { - ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ - verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier - }); + ZkCoProcessorConfig memory config = _zkConfig(mockRiscZeroVerifier); - vm.prank(submitter); - vm.expectRevert(); + _expectNotOwnerRevert(submitter); verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); } @@ -230,8 +224,7 @@ contract NitroEnclaveVerifierTest is Test { function testConstructorAcceptsZeroRevoker() public { bytes32[] memory certs = new bytes32[](0); uint64[] memory expiries = new uint64[](0); - ZkCoProcessorConfig memory zkCfg = - ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockSP1Verifier }); + ZkCoProcessorConfig memory zkCfg = _zkConfig(mockSP1Verifier); NitroEnclaveVerifier v = new NitroEnclaveVerifier( owner, MAX_TIME_DIFF, @@ -277,14 +270,12 @@ contract NitroEnclaveVerifierTest is Test { } function testSetRevokerRevertsIfNotOwner() public { - vm.prank(submitter); - vm.expectRevert(); + _expectNotOwnerRevert(submitter); verifier.setRevoker(makeAddr("anyone")); } function testSetRevokerRevertsIfCalledByRevoker() public { - vm.prank(revokerAddr); - vm.expectRevert(); + _expectNotOwnerRevert(revokerAddr); verifier.setRevoker(makeAddr("anyone")); } @@ -320,8 +311,7 @@ contract NitroEnclaveVerifierTest is Test { function testUpdateVerifierIdRevertsIfNotOwner() public { _setUpRiscZeroConfig(); - vm.prank(submitter); - vm.expectRevert(); + _expectNotOwnerRevert(submitter); verifier.updateVerifierId(ZkCoProcessorType.RiscZero, keccak256("new"), keccak256("proof")); } @@ -355,81 +345,79 @@ contract NitroEnclaveVerifierTest is Test { function testUpdateAggregatorIdRevertsIfNotOwner() public { _setUpRiscZeroConfig(); - vm.prank(submitter); - vm.expectRevert(); + _expectNotOwnerRevert(submitter); verifier.updateAggregatorId(ZkCoProcessorType.RiscZero, keccak256("new")); } // ============ addVerifyRoute / freezeVerifyRoute Tests ============ function testAddVerifyRoute() public { - bytes4 selector = bytes4(keccak256("test")); address routeVerifier = makeAddr("route-verifier"); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); - assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, selector), routeVerifier); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, routeVerifier); + assertEq(verifier.getZkVerifier(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR), routeVerifier); } function testAddVerifyRouteRevertsIfZeroAddress() public { vm.expectRevert(NitroEnclaveVerifier.ZeroVerifierAddress.selector); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, bytes4(uint32(0x01)), address(0)); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, NON_DEFAULT_PROOF_SELECTOR, address(0)); } function testAddVerifyRouteRevertsIfFrozenSentinel() public { vm.expectRevert(NitroEnclaveVerifier.InvalidVerifierAddress.selector); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, bytes4(uint32(0x01)), address(0xdead)); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, NON_DEFAULT_PROOF_SELECTOR, address(0xdead)); } function testAddVerifyRouteRevertsIfNotOwner() public { - vm.prank(submitter); - vm.expectRevert(); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, bytes4(keccak256("test")), makeAddr("v")); + _expectNotOwnerRevert(submitter); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, makeAddr("v")); } function testFreezeVerifyRoute() public { - bytes4 selector = bytes4(keccak256("test")); address routeVerifier = makeAddr("route-verifier"); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); - verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, routeVerifier); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); vm.expectRevert( - abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + abi.encodeWithSelector( + NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR + ) ); - verifier.getZkVerifier(ZkCoProcessorType.RiscZero, selector); + verifier.getZkVerifier(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); } function testAddVerifyRouteRevertsIfFrozen() public { - bytes4 selector = bytes4(keccak256("test")); address routeVerifier = makeAddr("route-verifier"); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); - verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, routeVerifier); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); vm.expectRevert( - abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + abi.encodeWithSelector( + NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR + ) ); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, routeVerifier); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, routeVerifier); } function testFreezeVerifyRouteRevertsIfAlreadyFrozen() public { - bytes4 selector = bytes4(keccak256("test")); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("v")); - verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, makeAddr("v")); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); vm.expectRevert( - abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) + abi.encodeWithSelector( + NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR + ) ); - verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); } function testFreezeVerifyRouteRevertsIfNotOwner() public { - bytes4 selector = bytes4(keccak256("test")); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("v")); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, makeAddr("v")); - vm.prank(submitter); - vm.expectRevert(); - verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); + _expectNotOwnerRevert(submitter); + verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); } // ============ getZkVerifier Tests ============ @@ -484,36 +472,29 @@ contract NitroEnclaveVerifierTest is Test { // ============ verify — ZkVerifierNotConfigured ============ function testVerifyRevertsIfZkVerifierNotConfigured() public { - ZkCoProcessorConfig memory config = - ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: address(0) }); + ZkCoProcessorConfig memory config = _zkConfig(address(0)); verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); - VerifierJournal memory journal = _createSuccessJournal(); - bytes memory output = abi.encode(journal); bytes memory proofBytes = _proofBytes(); vm.prank(submitter); vm.expectRevert( abi.encodeWithSelector(NitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.RiscZero) ); - verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + verifier.verify("", ZkCoProcessorType.RiscZero, proofBytes); } // ============ verify — Unknown_Zk_Coprocessor ============ function testVerifyRevertsForUnknownCoprocessor() public { - ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ - verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier - }); + ZkCoProcessorConfig memory config = _zkConfig(mockRiscZeroVerifier); verifier.setZkConfiguration(ZkCoProcessorType.Unknown, config, VERIFIER_PROOF_ID); - VerifierJournal memory journal = _createSuccessJournal(); - bytes memory output = abi.encode(journal); bytes memory proofBytes = _proofBytes(); vm.prank(submitter); vm.expectRevert(NitroEnclaveVerifier.Unknown_Zk_Coprocessor.selector); - verifier.verify(output, ZkCoProcessorType.Unknown, proofBytes); + verifier.verify("", ZkCoProcessorType.Unknown, proofBytes); } // ============ verify — ZkRouteFrozen during verify() ============ @@ -525,15 +506,13 @@ contract NitroEnclaveVerifierTest is Test { verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, selector, makeAddr("route-v")); verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, selector); - VerifierJournal memory journal = _createSuccessJournal(); - bytes memory output = abi.encode(journal); bytes memory proofBytes = _proofBytes(); vm.prank(submitter); vm.expectRevert( abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) ); - verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + verifier.verify("", ZkCoProcessorType.RiscZero, proofBytes); } // ============ verify — RiscZero happy path ============ @@ -541,16 +520,9 @@ contract NitroEnclaveVerifierTest is Test { function testVerifySuccessfulJournal() public { _setUpRiscZeroConfig(); - VerifierJournal memory journal = _createSuccessJournal(); - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); + VerifierJournal memory result = _verifyRiscZeroJournal(_createSuccessJournal()); - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); - - assertEq(uint8(result.result), uint8(VerificationResult.Success)); + _assertVerificationResult(result, VerificationResult.Success); } function testVerifyJournalRootCertNotTrusted() public { @@ -558,15 +530,9 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); journal.certs[0] = keccak256("wrong-root"); - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); - - assertEq(uint8(result.result), uint8(VerificationResult.RootCertNotTrusted)); + _assertVerificationResult(result, VerificationResult.RootCertNotTrusted); } function testVerifyJournalRootCertNotTrustedZeroPrefixLen() public { @@ -574,15 +540,9 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); journal.trustedCertsPrefixLen = 0; - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); - - assertEq(uint8(result.result), uint8(VerificationResult.RootCertNotTrusted)); + _assertVerificationResult(result, VerificationResult.RootCertNotTrusted); } function testVerifyJournalIntermediateCertNotTrusted() public { @@ -591,15 +551,9 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); // Replace trusted intermediate with untrusted one, but keep trustedCertsPrefixLen = 2 journal.certs[1] = keccak256("untrusted-intermediate"); - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); - - assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + _assertVerificationResult(result, VerificationResult.IntermediateCertsNotTrusted); } function testVerifyJournalInvalidTimestampTooOld() public { @@ -608,15 +562,9 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); // Set timestamp far in the past — more than maxTimeDiff seconds ago (in ms) journal.timestamp = uint64(block.timestamp - MAX_TIME_DIFF - 1) * 1000; - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); - - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); - - assertEq(uint8(result.result), uint8(VerificationResult.InvalidTimestamp)); + _assertVerificationResult(result, VerificationResult.InvalidTimestamp); } function testVerifyJournalInvalidTimestampFuture() public { @@ -625,15 +573,9 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); // Set timestamp in the future (converted to ms) journal.timestamp = uint64(block.timestamp + 100) * 1000; - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); - - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - assertEq(uint8(result.result), uint8(VerificationResult.InvalidTimestamp)); + _assertVerificationResult(result, VerificationResult.InvalidTimestamp); } function testVerifyCachesNewCerts() public { @@ -643,14 +585,7 @@ contract NitroEnclaveVerifierTest is Test { assertEq(verifier.trustedIntermediateCerts(newCert), 0); VerifierJournal memory journal = _createSuccessJournalWithLeaf(newCert, NEW_LEAF_CERT_EXPIRY); - - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); - - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + _verifyRiscZeroJournal(journal); assertEq(verifier.trustedIntermediateCerts(newCert), NEW_LEAF_CERT_EXPIRY); } @@ -660,15 +595,9 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); journal.result = VerificationResult.IntermediateCertsNotTrusted; - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); - - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + _assertVerificationResult(result, VerificationResult.IntermediateCertsNotTrusted); } // ============ verify — Succinct SP1 happy path ============ @@ -683,12 +612,11 @@ contract NitroEnclaveVerifierTest is Test { vm.prank(submitter); VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes); - assertEq(uint8(result.result), uint8(VerificationResult.Success)); + _assertVerificationResult(result, VerificationResult.Success); } function testVerifyRevertsIfZkVerifierNotConfiguredSP1() public { - ZkCoProcessorConfig memory config = - ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: address(0) }); + ZkCoProcessorConfig memory config = _zkConfig(address(0)); verifier.setZkConfiguration(ZkCoProcessorType.Succinct, config, VERIFIER_PROOF_ID); bytes memory proofBytes = _proofBytes(); @@ -697,7 +625,7 @@ contract NitroEnclaveVerifierTest is Test { vm.expectRevert( abi.encodeWithSelector(NitroEnclaveVerifier.ZkVerifierNotConfigured.selector, ZkCoProcessorType.Succinct) ); - verifier.verify(abi.encode(_createSuccessJournal()), ZkCoProcessorType.Succinct, proofBytes); + verifier.verify("", ZkCoProcessorType.Succinct, proofBytes); } // ============ batchVerify Tests ============ @@ -727,8 +655,8 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal[] memory results = verifier.batchVerify(output, ZkCoProcessorType.RiscZero, proofBytes); assertEq(results.length, 2); - assertEq(uint8(results[0].result), uint8(VerificationResult.Success)); - assertEq(uint8(results[1].result), uint8(VerificationResult.Success)); + _assertVerificationResult(results[0], VerificationResult.Success); + _assertVerificationResult(results[1], VerificationResult.Success); } function testBatchVerifyRevertsIfVerifierVkMismatch() public { @@ -769,7 +697,7 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal[] memory results = verifier.batchVerify(output, ZkCoProcessorType.Succinct, proofBytes); assertEq(results.length, 1); - assertEq(uint8(results[0].result), uint8(VerificationResult.Success)); + _assertVerificationResult(results[0], VerificationResult.Success); } // ============ Revoked Cert Invalidates Journal ============ @@ -778,17 +706,12 @@ contract NitroEnclaveVerifierTest is Test { _setUpRiscZeroConfig(); VerifierJournal memory journal = _createSuccessJournal(); - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); verifier.revokeCert(INTERMEDIATE_CERT_1); - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); - - assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + _assertVerificationResult(result, VerificationResult.IntermediateCertsNotTrusted); } // ============ Expiry-Aware Caching Tests ============ @@ -801,15 +724,9 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); journal.timestamp = uint64(block.timestamp - 1) * 1000; - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); - - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - assertEq(uint8(result.result), uint8(VerificationResult.IntermediateCertsNotTrusted)); + _assertVerificationResult(result, VerificationResult.IntermediateCertsNotTrusted); } function testNonExpiredCachedCertPassesVerification() public { @@ -820,15 +737,9 @@ contract NitroEnclaveVerifierTest is Test { VerifierJournal memory journal = _createSuccessJournal(); journal.timestamp = uint64(block.timestamp - 1) * 1000; - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); - - assertEq(uint8(result.result), uint8(VerificationResult.Success)); + _assertVerificationResult(result, VerificationResult.Success); } // Untrusted chain certs (past trustedCertsPrefixLen): expired journal notAfter => InvalidTimestamp @@ -838,16 +749,9 @@ contract NitroEnclaveVerifierTest is Test { bytes32 expiredLeaf = keccak256("expired-untrusted-leaf"); VerifierJournal memory journal = _createSuccessJournalWithLeaf(expiredLeaf, uint64(block.timestamp - 1)); + VerifierJournal memory result = _verifyRiscZeroJournal(journal); - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); - - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); - - assertEq(uint8(result.result), uint8(VerificationResult.InvalidTimestamp)); + _assertVerificationResult(result, VerificationResult.InvalidTimestamp); assertEq(verifier.trustedIntermediateCerts(expiredLeaf), 0); } @@ -871,14 +775,7 @@ contract NitroEnclaveVerifierTest is Test { uint64 newCertExpiry = uint64(REALISTIC_TIMESTAMP + 86_400); // 1 day from now VerifierJournal memory journal = _createSuccessJournalWithLeaf(newCert, newCertExpiry); - - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); - - _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + _verifyRiscZeroJournal(journal); assertEq(verifier.trustedIntermediateCerts(newCert), newCertExpiry); } @@ -886,18 +783,34 @@ contract NitroEnclaveVerifierTest is Test { // ============ Helpers ============ function _setUpRiscZeroConfig() internal { - ZkCoProcessorConfig memory config = ZkCoProcessorConfig({ - verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: mockRiscZeroVerifier - }); - verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, config, VERIFIER_PROOF_ID); + verifier.setZkConfiguration(ZkCoProcessorType.RiscZero, _zkConfig(mockRiscZeroVerifier), VERIFIER_PROOF_ID); } - function _proofBytes() internal pure returns (bytes memory) { - return _proofBytes(DEFAULT_PROOF_SELECTOR); + function _zkConfig(address zkVerifier) internal pure returns (ZkCoProcessorConfig memory) { + return ZkCoProcessorConfig({ verifierId: VERIFIER_ID, aggregatorId: AGGREGATOR_ID, zkVerifier: zkVerifier }); + } + + function _expectNotOwnerRevert(address caller) internal { + vm.prank(caller); + vm.expectRevert(Ownable.Unauthorized.selector); } - function _proofBytes(bytes4 selector) internal pure returns (bytes memory) { - return abi.encodePacked(selector, bytes32(0)); + function _assertVerificationResult(VerifierJournal memory journal, VerificationResult expected) internal pure { + assertEq(uint8(journal.result), uint8(expected)); + } + + function _verifyRiscZeroJournal(VerifierJournal memory journal) internal returns (VerifierJournal memory) { + bytes memory output = abi.encode(journal); + bytes memory proofBytes = _proofBytes(); + + _mockRiscZeroVerify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + return verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); + } + + function _proofBytes() internal pure returns (bytes memory) { + return abi.encodePacked(DEFAULT_PROOF_SELECTOR, bytes32(0)); } function _createSuccessJournal() internal view returns (VerifierJournal memory) { @@ -909,20 +822,7 @@ contract NitroEnclaveVerifierTest is Test { expiries[0] = ROOT_CERT_EXPIRY; expiries[1] = INTERMEDIATE_CERT_1_EXPIRY; - Pcr[] memory pcrs = new Pcr[](0); - - return VerifierJournal({ - result: VerificationResult.Success, - trustedCertsPrefixLen: 2, - timestamp: uint64(block.timestamp - 1) * 1000, - certs: certs, - certExpiries: expiries, - userData: "", - nonce: "", - publicKey: "", - pcrs: pcrs, - moduleId: "test-module" - }); + return _successJournal(certs, expiries); } function _createSuccessJournalWithLeaf( @@ -933,21 +833,41 @@ contract NitroEnclaveVerifierTest is Test { view returns (VerifierJournal memory) { - VerifierJournal memory journal = _createSuccessJournal(); - bytes32[] memory certs = new bytes32[](3); certs[0] = ROOT_CERT; certs[1] = INTERMEDIATE_CERT_1; certs[2] = leafCert; - journal.certs = certs; uint64[] memory expiries = new uint64[](3); expiries[0] = ROOT_CERT_EXPIRY; expiries[1] = INTERMEDIATE_CERT_1_EXPIRY; expiries[2] = leafExpiry; - journal.certExpiries = expiries; - return journal; + return _successJournal(certs, expiries); + } + + function _successJournal( + bytes32[] memory certs, + uint64[] memory expiries + ) + internal + view + returns (VerifierJournal memory) + { + Pcr[] memory pcrs = new Pcr[](0); + + return VerifierJournal({ + result: VerificationResult.Success, + trustedCertsPrefixLen: 2, + timestamp: uint64(block.timestamp - 1) * 1000, + certs: certs, + certExpiries: expiries, + userData: "", + nonce: "", + publicKey: "", + pcrs: pcrs, + moduleId: "test-module" + }); } function _mockRiscZeroVerify(bytes32 programId, bytes memory output, bytes memory proofBytes) internal { From 8c7fd6c3a8669d50d7336889afe260d7a7f10cd9 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 07:58:35 -0400 Subject: [PATCH 047/135] Refactor NitroEnclaveVerifier.t.sol: introduce a constant for the frozen route sentinel, streamline revert expectations with dedicated helper functions, and enhance test clarity by consolidating proof verification logic. --- test/L1/proofs/NitroEnclaveVerifier.t.sol | 157 +++++++++++----------- 1 file changed, 82 insertions(+), 75 deletions(-) diff --git a/test/L1/proofs/NitroEnclaveVerifier.t.sol b/test/L1/proofs/NitroEnclaveVerifier.t.sol index d4876cf0..34eedbce 100644 --- a/test/L1/proofs/NitroEnclaveVerifier.t.sol +++ b/test/L1/proofs/NitroEnclaveVerifier.t.sol @@ -35,6 +35,7 @@ contract NitroEnclaveVerifierTest is Test { bytes4 internal constant TEST_ROUTE_SELECTOR = bytes4(keccak256("test")); bytes4 internal constant RISC_ZERO_VERIFY_SELECTOR = bytes4(keccak256("verify(bytes,bytes32,bytes32)")); bytes4 internal constant SP1_VERIFY_PROOF_SELECTOR = bytes4(keccak256("verifyProof(bytes32,bytes,bytes)")); + address internal constant FROZEN_ROUTE_SENTINEL = address(0xdead); uint64 internal constant MAX_TIME_DIFF = 3600; // 1 hour @@ -106,7 +107,7 @@ contract NitroEnclaveVerifierTest is Test { function testConstructorRevertsIfCertExpiriesLengthMismatch() public { bytes32[] memory certs = new bytes32[](1); certs[0] = INTERMEDIATE_CERT_1; - uint64[] memory expiries = new uint64[](0); // mismatched length + uint64[] memory expiries = new uint64[](0); ZkCoProcessorConfig memory zkCfg = ZkCoProcessorConfig({ verifierId: bytes32(0), aggregatorId: bytes32(0), zkVerifier: address(0) }); vm.expectRevert(abi.encodeWithSelector(NitroEnclaveVerifier.CertExpiriesLengthMismatch.selector, 1, 0)); @@ -365,7 +366,7 @@ contract NitroEnclaveVerifierTest is Test { function testAddVerifyRouteRevertsIfFrozenSentinel() public { vm.expectRevert(NitroEnclaveVerifier.InvalidVerifierAddress.selector); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, NON_DEFAULT_PROOF_SELECTOR, address(0xdead)); + verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, NON_DEFAULT_PROOF_SELECTOR, FROZEN_ROUTE_SENTINEL); } function testAddVerifyRouteRevertsIfNotOwner() public { @@ -376,28 +377,18 @@ contract NitroEnclaveVerifierTest is Test { function testFreezeVerifyRoute() public { address routeVerifier = makeAddr("route-verifier"); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, routeVerifier); - verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); + _addAndFreezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, routeVerifier); - vm.expectRevert( - abi.encodeWithSelector( - NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR - ) - ); + _expectZkRouteFrozenRevert(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); verifier.getZkVerifier(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); } function testAddVerifyRouteRevertsIfFrozen() public { address routeVerifier = makeAddr("route-verifier"); - verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, routeVerifier); - verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); + _addAndFreezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, routeVerifier); - vm.expectRevert( - abi.encodeWithSelector( - NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR - ) - ); + _expectZkRouteFrozenRevert(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, routeVerifier); } @@ -405,11 +396,7 @@ contract NitroEnclaveVerifierTest is Test { verifier.addVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR, makeAddr("v")); verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); - vm.expectRevert( - abi.encodeWithSelector( - NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR - ) - ); + _expectZkRouteFrozenRevert(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); verifier.freezeVerifyRoute(ZkCoProcessorType.RiscZero, TEST_ROUTE_SELECTOR); } @@ -446,7 +433,7 @@ contract NitroEnclaveVerifierTest is Test { reportCerts[0] = new bytes32[](3); reportCerts[0][0] = ROOT_CERT; reportCerts[0][1] = INTERMEDIATE_CERT_1; - reportCerts[0][2] = INTERMEDIATE_CERT_2; // not trusted + reportCerts[0][2] = INTERMEDIATE_CERT_2; uint8[] memory results = verifier.checkTrustedIntermediateCerts(reportCerts); assertEq(results[0], 2); // root + 1 intermediate trusted @@ -509,9 +496,7 @@ contract NitroEnclaveVerifierTest is Test { bytes memory proofBytes = _proofBytes(); vm.prank(submitter); - vm.expectRevert( - abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, ZkCoProcessorType.RiscZero, selector) - ); + _expectZkRouteFrozenRevert(ZkCoProcessorType.RiscZero, selector); verifier.verify("", ZkCoProcessorType.RiscZero, proofBytes); } @@ -603,14 +588,7 @@ contract NitroEnclaveVerifierTest is Test { // ============ verify — Succinct SP1 happy path ============ function testVerifySuccessfulJournalSP1() public { - VerifierJournal memory journal = _createSuccessJournal(); - bytes memory output = abi.encode(journal); - bytes memory proofBytes = _proofBytes(); - - _mockSP1Verify(VERIFIER_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal memory result = verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes); + VerifierJournal memory result = _verifySP1Journal(_createSuccessJournal()); _assertVerificationResult(result, VerificationResult.Success); } @@ -638,21 +616,7 @@ contract NitroEnclaveVerifierTest is Test { function testBatchVerifySuccess() public { _setUpRiscZeroConfig(); - VerifierJournal memory journal = _createSuccessJournal(); - VerifierJournal[] memory outputs = new VerifierJournal[](2); - outputs[0] = journal; - outputs[1] = journal; - - BatchVerifierJournal memory batchJournal = - BatchVerifierJournal({ verifierVk: VERIFIER_PROOF_ID, outputs: outputs }); - - bytes memory output = abi.encode(batchJournal); - bytes memory proofBytes = _proofBytes(); - - _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal[] memory results = verifier.batchVerify(output, ZkCoProcessorType.RiscZero, proofBytes); + VerifierJournal[] memory results = _batchVerifyRiscZero(_createBatchJournal(VERIFIER_PROOF_ID, 2)); assertEq(results.length, 2); _assertVerificationResult(results[0], VerificationResult.Success); @@ -663,15 +627,8 @@ contract NitroEnclaveVerifierTest is Test { _setUpRiscZeroConfig(); bytes32 wrongVk = keccak256("wrong-vk"); - VerifierJournal[] memory outputs = new VerifierJournal[](1); - outputs[0] = _createSuccessJournal(); - - BatchVerifierJournal memory batchJournal = BatchVerifierJournal({ verifierVk: wrongVk, outputs: outputs }); - - bytes memory output = abi.encode(batchJournal); - bytes memory proofBytes = _proofBytes(); - - _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); + BatchVerifierJournal memory batchJournal = _createBatchJournal(wrongVk, 1); + (bytes memory output, bytes memory proofBytes) = _mockRiscZeroBatchVerify(batchJournal); vm.prank(submitter); vm.expectRevert( @@ -681,20 +638,7 @@ contract NitroEnclaveVerifierTest is Test { } function testBatchVerifySuccessSP1() public { - VerifierJournal memory journal = _createSuccessJournal(); - VerifierJournal[] memory outputs = new VerifierJournal[](1); - outputs[0] = journal; - - BatchVerifierJournal memory batchJournal = - BatchVerifierJournal({ verifierVk: VERIFIER_PROOF_ID, outputs: outputs }); - - bytes memory output = abi.encode(batchJournal); - bytes memory proofBytes = _proofBytes(); - - _mockSP1Verify(AGGREGATOR_ID, output, proofBytes); - - vm.prank(submitter); - VerifierJournal[] memory results = verifier.batchVerify(output, ZkCoProcessorType.Succinct, proofBytes); + VerifierJournal[] memory results = _batchVerifySP1(_createBatchJournal(VERIFIER_PROOF_ID, 1)); assertEq(results.length, 1); _assertVerificationResult(results[0], VerificationResult.Success); @@ -719,7 +663,6 @@ contract NitroEnclaveVerifierTest is Test { function testExpiredCachedCertFailsVerification() public { _setUpRiscZeroConfig(); - // Warp past the intermediate cert's expiry vm.warp(INTERMEDIATE_CERT_1_EXPIRY + 1); VerifierJournal memory journal = _createSuccessJournal(); @@ -732,7 +675,6 @@ contract NitroEnclaveVerifierTest is Test { function testNonExpiredCachedCertPassesVerification() public { _setUpRiscZeroConfig(); - // Warp to just before the intermediate cert's expiry vm.warp(INTERMEDIATE_CERT_1_EXPIRY - 1); VerifierJournal memory journal = _createSuccessJournal(); @@ -756,13 +698,12 @@ contract NitroEnclaveVerifierTest is Test { } function testCheckTrustedIntermediateCertsStopsAtExpiredCert() public { - // Warp past the intermediate cert's expiry vm.warp(INTERMEDIATE_CERT_1_EXPIRY + 1); bytes32[][] memory reportCerts = new bytes32[][](1); reportCerts[0] = new bytes32[](2); reportCerts[0][0] = ROOT_CERT; - reportCerts[0][1] = INTERMEDIATE_CERT_1; // expired + reportCerts[0][1] = INTERMEDIATE_CERT_1; uint8[] memory results = verifier.checkTrustedIntermediateCerts(reportCerts); assertEq(results[0], 1); // only root counted, expired intermediate skipped @@ -795,6 +736,15 @@ contract NitroEnclaveVerifierTest is Test { vm.expectRevert(Ownable.Unauthorized.selector); } + function _expectZkRouteFrozenRevert(ZkCoProcessorType zkType, bytes4 selector) internal { + vm.expectRevert(abi.encodeWithSelector(NitroEnclaveVerifier.ZkRouteFrozen.selector, zkType, selector)); + } + + function _addAndFreezeVerifyRoute(ZkCoProcessorType zkType, bytes4 selector, address routeVerifier) internal { + verifier.addVerifyRoute(zkType, selector, routeVerifier); + verifier.freezeVerifyRoute(zkType, selector); + } + function _assertVerificationResult(VerifierJournal memory journal, VerificationResult expected) internal pure { assertEq(uint8(journal.result), uint8(expected)); } @@ -809,6 +759,63 @@ contract NitroEnclaveVerifierTest is Test { return verifier.verify(output, ZkCoProcessorType.RiscZero, proofBytes); } + function _verifySP1Journal(VerifierJournal memory journal) internal returns (VerifierJournal memory) { + bytes memory output = abi.encode(journal); + bytes memory proofBytes = _proofBytes(); + + _mockSP1Verify(VERIFIER_ID, output, proofBytes); + + vm.prank(submitter); + return verifier.verify(output, ZkCoProcessorType.Succinct, proofBytes); + } + + function _createBatchJournal( + bytes32 verifierVk, + uint256 outputCount + ) + internal + view + returns (BatchVerifierJournal memory) + { + VerifierJournal[] memory outputs = new VerifierJournal[](outputCount); + VerifierJournal memory journal = _createSuccessJournal(); + for (uint256 i; i < outputCount; ++i) { + outputs[i] = journal; + } + + return BatchVerifierJournal({ verifierVk: verifierVk, outputs: outputs }); + } + + function _batchVerifyRiscZero(BatchVerifierJournal memory batchJournal) + internal + returns (VerifierJournal[] memory) + { + (bytes memory output, bytes memory proofBytes) = _mockRiscZeroBatchVerify(batchJournal); + + vm.prank(submitter); + return verifier.batchVerify(output, ZkCoProcessorType.RiscZero, proofBytes); + } + + function _batchVerifySP1(BatchVerifierJournal memory batchJournal) internal returns (VerifierJournal[] memory) { + bytes memory output = abi.encode(batchJournal); + bytes memory proofBytes = _proofBytes(); + + _mockSP1Verify(AGGREGATOR_ID, output, proofBytes); + + vm.prank(submitter); + return verifier.batchVerify(output, ZkCoProcessorType.Succinct, proofBytes); + } + + function _mockRiscZeroBatchVerify(BatchVerifierJournal memory batchJournal) + internal + returns (bytes memory output, bytes memory proofBytes) + { + output = abi.encode(batchJournal); + proofBytes = _proofBytes(); + + _mockRiscZeroVerify(AGGREGATOR_ID, output, proofBytes); + } + function _proofBytes() internal pure returns (bytes memory) { return abi.encodePacked(DEFAULT_PROOF_SELECTOR, bytes32(0)); } From 58ff5698b124ae324c17296e8a216c2d2f878a57 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 08:08:20 -0400 Subject: [PATCH 048/135] Refactor Nullify.t.sol: streamline nullification tests by consolidating proof handling into dedicated helper functions, introduce constants for resolution delays, and enhance test clarity through improved game initialization logic. --- test/L1/proofs/Nullify.t.sol | 281 ++++++++++++++--------------------- 1 file changed, 110 insertions(+), 171 deletions(-) diff --git a/test/L1/proofs/Nullify.t.sol b/test/L1/proofs/Nullify.t.sol index b70b74b8..941dbdef 100644 --- a/test/L1/proofs/Nullify.t.sol +++ b/test/L1/proofs/Nullify.t.sol @@ -10,165 +10,87 @@ import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { BaseTest } from "./BaseTest.t.sol"; contract NullifyTest is BaseTest { - function testNullifyWithTEEProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); + uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; + uint256 private constant NO_PROOF_RESOLUTION_DELAY = 14 days; - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof1 + function testNullifyWithTEEProof() public { + AggregateVerifier game = _createGame( + TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - - game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); - - assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); - assertEq(game.bondRecipient(), TEE_PROVER); - assertEq(game.proofCount(), 0); - assertEq(game.expectedResolution().raw(), type(uint64).max); - - // expectedResolution is uint64.max (no proofs left), so must wait 14 days from creation - vm.warp(block.timestamp + 14 days); + _nullify(game, "tee-proof-2", AggregateVerifier.ProofType.TEE, "tee2"); + _assertNullifiedToNoProofs(game, TEE_PROVER); - uint256 balanceBefore = game.gameCreator().balance; - game.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game.claimCredit(); - assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game)), 0); + vm.warp(block.timestamp + NO_PROOF_RESOLUTION_DELAY); + _claimCreditAfterDelay(game); } function testNullifyWithZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk1"))); - bytes memory zkProof1 = _generateProof("zk-proof-1", AggregateVerifier.ProofType.ZK); - - AggregateVerifier game1 = _createAggregateVerifierGame( - ZK_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), zkProof1 - ); - - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk2"))); - bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); - - game1.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + AggregateVerifier game = + _createGame(ZK_PROVER, "zk1", "zk-proof-1", AggregateVerifier.ProofType.ZK, address(anchorStateRegistry)); - assertEq(uint8(game1.status()), uint8(GameStatus.IN_PROGRESS)); - assertEq(game1.bondRecipient(), ZK_PROVER); - assertEq(game1.proofCount(), 0); - assertEq(game1.expectedResolution().raw(), type(uint64).max); + _nullify(game, "zk-proof-2", AggregateVerifier.ProofType.ZK, "zk2"); + _assertNullifiedToNoProofs(game, ZK_PROVER); - // expectedResolution is uint64.max (no proofs left), so must wait 14 days from creation - vm.warp(block.timestamp + 14 days); - - uint256 balanceBefore = game1.gameCreator().balance; - game1.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game1.claimCredit(); - assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game1)), 0); + vm.warp(block.timestamp + NO_PROOF_RESOLUTION_DELAY); + _claimCreditAfterDelay(game); } function testNullifyWithTEEProofWhenTEEAndZKProofsAreProvided() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof1 + AggregateVerifier game = _createGame( + TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - bytes memory zkProof = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); - game.verifyProposalProof(zkProof); + _provideProof(game, ZK_PROVER, _proof("zk-proof-2", AggregateVerifier.ProofType.ZK)); - assertEq(game.expectedResolution().raw(), block.timestamp + 1 days); + assertEq(game.expectedResolution().raw(), block.timestamp + game.FAST_FINALIZATION_DELAY()); - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); - game.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + _nullify(game, "tee-proof-2", AggregateVerifier.ProofType.TEE, "tee2"); assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); assertEq(game.bondRecipient(), TEE_PROVER); assertEq(game.proofCount(), 1); - assertEq(game.expectedResolution().raw(), block.timestamp + 7 days); + assertEq(game.expectedResolution().raw(), block.timestamp + game.SLOW_FINALIZATION_DELAY()); } function testZKNullifyFailsIfNoZKProof() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof = _generateProof("tee-proof", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof - ); - - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee2"))); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); + AggregateVerifier game = + _createGame(TEE_PROVER, "tee1", "tee-proof", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry)); vm.expectRevert(abi.encodeWithSelector(AggregateVerifier.MissingProof.selector, AggregateVerifier.ProofType.ZK)); - game1.nullify(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + _nullify(game, "zk-proof", AggregateVerifier.ProofType.ZK, "tee2"); } function testNullifyFailsIfGameAlreadyResolved() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof1 + AggregateVerifier game = _createGame( + TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - // Resolve game1 - vm.warp(block.timestamp + 7 days); - game1.resolve(); - - // Try to nullify game1 - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory teeProof2 = _generateProof("tee-proof-2", AggregateVerifier.ProofType.TEE); + vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY()); + game.resolve(); vm.expectRevert(ClaimAlreadyResolved.selector); - game1.nullify(teeProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); + _nullify(game, "tee-proof-2", AggregateVerifier.ProofType.TEE, "zk"); } function testNullifyCanOverrideChallenge() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaim1 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "tee1"))); - bytes memory teeProof1 = _generateProof("tee-proof-1", AggregateVerifier.ProofType.TEE); - - AggregateVerifier game1 = _createAggregateVerifierGame( - TEE_PROVER, rootClaim1, currentL2BlockNumber, address(anchorStateRegistry), teeProof1 + AggregateVerifier game = _createGame( + TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - // Challenge game1 with ZK proof - Claim rootClaim2 = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk"))); - bytes memory zkProof = _generateProof("zk-proof", AggregateVerifier.ProofType.ZK); - vm.prank(ZK_PROVER); - game1.challenge(zkProof, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim2.raw()); - - // Nullify can override challenge - bytes memory zkProof2 = _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK); - game1.nullify(zkProof2, BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1, rootClaim1.raw()); + game.challenge( + _proof("zk-proof", AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, _claim("zk").raw() + ); - assertEq(game1.bondRecipient(), TEE_PROVER); + _nullify(game, "zk-proof-2", AggregateVerifier.ProofType.ZK, "tee1"); - // After nullify, only TEE proof remains; expectedResolution = now + 7 days - vm.warp(block.timestamp + 7 days); - game1.resolve(); + assertEq(game.bondRecipient(), TEE_PROVER); + assertEq(game.expectedResolution().raw(), block.timestamp + game.SLOW_FINALIZATION_DELAY()); - uint256 balanceBefore = game1.gameCreator().balance; - game1.claimCredit(); - vm.warp(block.timestamp + DELAYED_WETH_DELAY); - game1.claimCredit(); - assertEq(game1.gameCreator().balance, balanceBefore + INIT_BOND); - assertEq(delayedWETH.balanceOf(address(game1)), 0); + vm.warp(block.timestamp + game.SLOW_FINALIZATION_DELAY()); + game.resolve(); + _claimCreditAfterDelay(game); } /// @notice `resolve` runs `_updateProofCount`; when the shared TEE verifier was nullified by another game, @@ -176,29 +98,18 @@ contract NullifyTest is BaseTest { /// reverting. @dev All clones share the same `MockVerifier` TEE instance; `Verifier.nullify` requires a proper /// factory game. function testResolveEarlyReturnWhenSharedTeeVerifierNullifiedByAnotherGame() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "game-a"))); - bytes memory teeProofA = _generateProof("tee-proof-a", AggregateVerifier.ProofType.TEE); - AggregateVerifier gameA = _createAggregateVerifierGame( - TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA + AggregateVerifier gameA = _createGame( + TEE_PROVER, "game-a", "tee-proof-a", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "game-b"))); - bytes memory teeProofB = _generateProof("tee-proof-b", AggregateVerifier.ProofType.TEE); AggregateVerifier gameB = - _createAggregateVerifierGame(TEE_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), teeProofB); + _createGame(TEE_PROVER, "game-b", "tee-proof-b", AggregateVerifier.ProofType.TEE, address(gameA)); - vm.warp(block.timestamp + 7 days); + vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); assertTrue(gameA.gameOver()); assertEq(gameA.proofCount(), 1); - Claim rootClaimNullify = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "nullify-b"))); - bytes memory teeProofNullify = _generateProof("tee-nullify-b", AggregateVerifier.ProofType.TEE); - uint256 lastIntermediateIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; - gameB.nullify(teeProofNullify, lastIntermediateIdx, rootClaimNullify.raw()); + _nullify(gameB, "tee-nullify-b", AggregateVerifier.ProofType.TEE, "nullify-b"); assertTrue(teeVerifier.nullified()); assertEq(gameA.proofCount(), 1); @@ -214,29 +125,18 @@ contract NullifyTest is BaseTest { /// @notice Same as `testResolveEarlyReturnWhenSharedTeeVerifierNullifiedByAnotherGame` but for the shared ZK /// verifier. function testResolveEarlyReturnWhenSharedZkVerifierNullifiedByAnotherGame() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-game-a"))); - bytes memory zkProofA = _generateProof("zk-proof-a", AggregateVerifier.ProofType.ZK); - AggregateVerifier gameA = _createAggregateVerifierGame( - ZK_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), zkProofA + AggregateVerifier gameA = _createGame( + ZK_PROVER, "zk-game-a", "zk-proof-a", AggregateVerifier.ProofType.ZK, address(anchorStateRegistry) ); - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-game-b"))); - bytes memory zkProofB = _generateProof("zk-proof-b", AggregateVerifier.ProofType.ZK); AggregateVerifier gameB = - _createAggregateVerifierGame(ZK_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), zkProofB); + _createGame(ZK_PROVER, "zk-game-b", "zk-proof-b", AggregateVerifier.ProofType.ZK, address(gameA)); - vm.warp(block.timestamp + 7 days); + vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); assertTrue(gameA.gameOver()); assertEq(gameA.proofCount(), 1); - Claim rootClaimNullify = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "zk-nullify-b"))); - bytes memory zkProofNullify = _generateProof("zk-nullify-b", AggregateVerifier.ProofType.ZK); - uint256 lastIntermediateIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; - gameB.nullify(zkProofNullify, lastIntermediateIdx, rootClaimNullify.raw()); + _nullify(gameB, "zk-nullify-b", AggregateVerifier.ProofType.ZK, "zk-nullify-b"); assertTrue(zkVerifier.nullified()); assertEq(gameA.proofCount(), 1); @@ -253,42 +153,81 @@ contract NullifyTest is BaseTest { /// `resolve` persists the ZK refutation and returns `IN_PROGRESS`. After `SLOW_FINALIZATION_DELAY` /// (7 days) from that moment, a second `resolve` finalizes with only the TEE proof. function testTwoProofsResolveDelayedAfterExternalVerifierNullify() public { - currentL2BlockNumber += BLOCK_INTERVAL; - - Claim rootClaimA = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "dual-a"))); - bytes memory teeProofA = _generateProof("tee-dual-a", AggregateVerifier.ProofType.TEE); - AggregateVerifier gameA = _createAggregateVerifierGame( - TEE_PROVER, rootClaimA, currentL2BlockNumber, address(anchorStateRegistry), teeProofA + AggregateVerifier gameA = _createGame( + TEE_PROVER, "dual-a", "tee-dual-a", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - bytes memory zkProofA = _generateProof("zk-dual-a", AggregateVerifier.ProofType.ZK); - vm.prank(ZK_PROVER); - gameA.verifyProposalProof(zkProofA); + _provideProof(gameA, ZK_PROVER, _proof("zk-dual-a", AggregateVerifier.ProofType.ZK)); assertEq(gameA.proofCount(), 2); - assertEq(gameA.expectedResolution().raw(), block.timestamp + 1 days); + assertEq(gameA.expectedResolution().raw(), block.timestamp + gameA.FAST_FINALIZATION_DELAY()); - vm.warp(block.timestamp + 1 days); + vm.warp(block.timestamp + gameA.FAST_FINALIZATION_DELAY()); assertTrue(gameA.gameOver()); - currentL2BlockNumber += BLOCK_INTERVAL; - Claim rootClaimB = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "dual-b"))); - bytes memory zkProofB = _generateProof("zk-dual-b", AggregateVerifier.ProofType.ZK); AggregateVerifier gameB = - _createAggregateVerifierGame(ZK_PROVER, rootClaimB, currentL2BlockNumber, address(gameA), zkProofB); + _createGame(ZK_PROVER, "dual-b", "zk-dual-b", AggregateVerifier.ProofType.ZK, address(gameA)); - Claim rootClaimNullify = Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, "dual-nullify-b"))); - bytes memory zkProofNullify = _generateProof("zk-nullify-dual", AggregateVerifier.ProofType.ZK); - uint256 lastIntermediateIdx = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; - gameB.nullify(zkProofNullify, lastIntermediateIdx, rootClaimNullify.raw()); + _nullify(gameB, "zk-nullify-dual", AggregateVerifier.ProofType.ZK, "dual-nullify-b"); assertTrue(zkVerifier.nullified()); assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); assertEq(gameA.proofCount(), 1); - assertEq(gameA.expectedResolution().raw(), block.timestamp + 7 days); + assertEq(gameA.expectedResolution().raw(), block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); - vm.warp(block.timestamp + 7 days); + vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); assertEq(uint8(gameA.resolve()), uint8(GameStatus.DEFENDER_WINS)); assertEq(uint8(gameA.status()), uint8(GameStatus.DEFENDER_WINS)); } + + function _createGame( + address prover, + bytes memory claimSalt, + bytes memory proofSalt, + AggregateVerifier.ProofType proofType, + address parent + ) + private + returns (AggregateVerifier) + { + currentL2BlockNumber += BLOCK_INTERVAL; + return _createAggregateVerifierGame( + prover, _claim(claimSalt), currentL2BlockNumber, parent, _proof(proofSalt, proofType) + ); + } + + function _claim(bytes memory salt) private view returns (Claim) { + return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, salt))); + } + + function _proof(bytes memory salt, AggregateVerifier.ProofType proofType) private view returns (bytes memory) { + return _generateProof(salt, proofType); + } + + function _nullify( + AggregateVerifier game, + bytes memory proofSalt, + AggregateVerifier.ProofType proofType, + bytes memory claimSalt + ) + private + { + game.nullify(_proof(proofSalt, proofType), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw()); + } + + function _assertNullifiedToNoProofs(AggregateVerifier game, address expectedBondRecipient) private view { + assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + assertEq(game.bondRecipient(), expectedBondRecipient); + assertEq(game.proofCount(), 0); + assertEq(game.expectedResolution().raw(), type(uint64).max); + } + + function _claimCreditAfterDelay(AggregateVerifier game) private { + uint256 balanceBefore = game.gameCreator().balance; + game.claimCredit(); + vm.warp(block.timestamp + DELAYED_WETH_DELAY); + game.claimCredit(); + assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(delayedWETH.balanceOf(address(game)), 0); + } } From 89617f320f73b48d17e71c6b23b345433fbacbaf Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 08:13:38 -0400 Subject: [PATCH 049/135] Refactor Nullify.t.sol: replace direct proof handling with dedicated helper functions, improve status assertions for clarity, and streamline game resolution logic for enhanced test maintainability. --- test/L1/proofs/Nullify.t.sol | 38 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/test/L1/proofs/Nullify.t.sol b/test/L1/proofs/Nullify.t.sol index 941dbdef..8ca02b21 100644 --- a/test/L1/proofs/Nullify.t.sol +++ b/test/L1/proofs/Nullify.t.sol @@ -41,13 +41,13 @@ contract NullifyTest is BaseTest { TEE_PROVER, "tee1", "tee-proof-1", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - _provideProof(game, ZK_PROVER, _proof("zk-proof-2", AggregateVerifier.ProofType.ZK)); + _provideProof(game, ZK_PROVER, _generateProof("zk-proof-2", AggregateVerifier.ProofType.ZK)); assertEq(game.expectedResolution().raw(), block.timestamp + game.FAST_FINALIZATION_DELAY()); _nullify(game, "tee-proof-2", AggregateVerifier.ProofType.TEE, "tee2"); - assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + _assertStatus(game, GameStatus.IN_PROGRESS); assertEq(game.bondRecipient(), TEE_PROVER); assertEq(game.proofCount(), 1); assertEq(game.expectedResolution().raw(), block.timestamp + game.SLOW_FINALIZATION_DELAY()); @@ -80,7 +80,7 @@ contract NullifyTest is BaseTest { vm.prank(ZK_PROVER); game.challenge( - _proof("zk-proof", AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, _claim("zk").raw() + _generateProof("zk-proof", AggregateVerifier.ProofType.ZK), LAST_INTERMEDIATE_ROOT_INDEX, _claim("zk").raw() ); _nullify(game, "zk-proof-2", AggregateVerifier.ProofType.ZK, "tee1"); @@ -114,7 +114,7 @@ contract NullifyTest is BaseTest { assertTrue(teeVerifier.nullified()); assertEq(gameA.proofCount(), 1); - assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); + _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); assertEq(gameA.proofCount(), 0); assertEq(gameA.expectedResolution().raw(), type(uint64).max); @@ -122,8 +122,6 @@ contract NullifyTest is BaseTest { gameA.resolve(); } - /// @notice Same as `testResolveEarlyReturnWhenSharedTeeVerifierNullifiedByAnotherGame` but for the shared ZK - /// verifier. function testResolveEarlyReturnWhenSharedZkVerifierNullifiedByAnotherGame() public { AggregateVerifier gameA = _createGame( ZK_PROVER, "zk-game-a", "zk-proof-a", AggregateVerifier.ProofType.ZK, address(anchorStateRegistry) @@ -141,7 +139,7 @@ contract NullifyTest is BaseTest { assertTrue(zkVerifier.nullified()); assertEq(gameA.proofCount(), 1); - assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); + _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); assertEq(gameA.proofCount(), 0); assertEq(gameA.expectedResolution().raw(), type(uint64).max); @@ -157,7 +155,7 @@ contract NullifyTest is BaseTest { TEE_PROVER, "dual-a", "tee-dual-a", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) ); - _provideProof(gameA, ZK_PROVER, _proof("zk-dual-a", AggregateVerifier.ProofType.ZK)); + _provideProof(gameA, ZK_PROVER, _generateProof("zk-dual-a", AggregateVerifier.ProofType.ZK)); assertEq(gameA.proofCount(), 2); assertEq(gameA.expectedResolution().raw(), block.timestamp + gameA.FAST_FINALIZATION_DELAY()); @@ -171,13 +169,13 @@ contract NullifyTest is BaseTest { _nullify(gameB, "zk-nullify-dual", AggregateVerifier.ProofType.ZK, "dual-nullify-b"); assertTrue(zkVerifier.nullified()); - assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); + _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); assertEq(gameA.proofCount(), 1); assertEq(gameA.expectedResolution().raw(), block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); - assertEq(uint8(gameA.resolve()), uint8(GameStatus.DEFENDER_WINS)); - assertEq(uint8(gameA.status()), uint8(GameStatus.DEFENDER_WINS)); + _resolveAndAssertStatus(gameA, GameStatus.DEFENDER_WINS); + _assertStatus(gameA, GameStatus.DEFENDER_WINS); } function _createGame( @@ -192,7 +190,7 @@ contract NullifyTest is BaseTest { { currentL2BlockNumber += BLOCK_INTERVAL; return _createAggregateVerifierGame( - prover, _claim(claimSalt), currentL2BlockNumber, parent, _proof(proofSalt, proofType) + prover, _claim(claimSalt), currentL2BlockNumber, parent, _generateProof(proofSalt, proofType) ); } @@ -200,10 +198,6 @@ contract NullifyTest is BaseTest { return Claim.wrap(keccak256(abi.encode(currentL2BlockNumber, salt))); } - function _proof(bytes memory salt, AggregateVerifier.ProofType proofType) private view returns (bytes memory) { - return _generateProof(salt, proofType); - } - function _nullify( AggregateVerifier game, bytes memory proofSalt, @@ -212,16 +206,24 @@ contract NullifyTest is BaseTest { ) private { - game.nullify(_proof(proofSalt, proofType), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw()); + game.nullify(_generateProof(proofSalt, proofType), LAST_INTERMEDIATE_ROOT_INDEX, _claim(claimSalt).raw()); } function _assertNullifiedToNoProofs(AggregateVerifier game, address expectedBondRecipient) private view { - assertEq(uint8(game.status()), uint8(GameStatus.IN_PROGRESS)); + _assertStatus(game, GameStatus.IN_PROGRESS); assertEq(game.bondRecipient(), expectedBondRecipient); assertEq(game.proofCount(), 0); assertEq(game.expectedResolution().raw(), type(uint64).max); } + function _assertStatus(AggregateVerifier game, GameStatus expectedStatus) private view { + assertEq(uint8(game.status()), uint8(expectedStatus)); + } + + function _resolveAndAssertStatus(AggregateVerifier game, GameStatus expectedStatus) private { + assertEq(uint8(game.resolve()), uint8(expectedStatus)); + } + function _claimCreditAfterDelay(AggregateVerifier game) private { uint256 balanceBefore = game.gameCreator().balance; game.claimCredit(); From 146df37616fbb9e0c67e63fd4eb9f050145b3adf Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 08:20:29 -0400 Subject: [PATCH 050/135] Refactor Nullify.t.sol: rename constant for proof claim delay for clarity, consolidate early return assertions into dedicated helper functions, and enhance test readability by streamlining game resolution logic. --- test/L1/proofs/Nullify.t.sol | 97 +++++++++++++++--------------------- 1 file changed, 40 insertions(+), 57 deletions(-) diff --git a/test/L1/proofs/Nullify.t.sol b/test/L1/proofs/Nullify.t.sol index 8ca02b21..d9f00a1e 100644 --- a/test/L1/proofs/Nullify.t.sol +++ b/test/L1/proofs/Nullify.t.sol @@ -11,7 +11,7 @@ import { BaseTest } from "./BaseTest.t.sol"; contract NullifyTest is BaseTest { uint256 private constant LAST_INTERMEDIATE_ROOT_INDEX = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL - 1; - uint256 private constant NO_PROOF_RESOLUTION_DELAY = 14 days; + uint256 private constant NO_PROOF_CREDIT_CLAIM_DELAY = 14 days; function testNullifyWithTEEProof() public { AggregateVerifier game = _createGame( @@ -21,7 +21,7 @@ contract NullifyTest is BaseTest { _nullify(game, "tee-proof-2", AggregateVerifier.ProofType.TEE, "tee2"); _assertNullifiedToNoProofs(game, TEE_PROVER); - vm.warp(block.timestamp + NO_PROOF_RESOLUTION_DELAY); + vm.warp(block.timestamp + NO_PROOF_CREDIT_CLAIM_DELAY); _claimCreditAfterDelay(game); } @@ -32,7 +32,7 @@ contract NullifyTest is BaseTest { _nullify(game, "zk-proof-2", AggregateVerifier.ProofType.ZK, "zk2"); _assertNullifiedToNoProofs(game, ZK_PROVER); - vm.warp(block.timestamp + NO_PROOF_RESOLUTION_DELAY); + vm.warp(block.timestamp + NO_PROOF_CREDIT_CLAIM_DELAY); _claimCreditAfterDelay(game); } @@ -93,58 +93,12 @@ contract NullifyTest is BaseTest { _claimCreditAfterDelay(game); } - /// @notice `resolve` runs `_updateProofCount`; when the shared TEE verifier was nullified by another game, - /// refutation persists and `resolve` returns early `IN_PROGRESS` (no `Resolved` event) instead of - /// reverting. @dev All clones share the same `MockVerifier` TEE instance; `Verifier.nullify` requires a proper - /// factory game. function testResolveEarlyReturnWhenSharedTeeVerifierNullifiedByAnotherGame() public { - AggregateVerifier gameA = _createGame( - TEE_PROVER, "game-a", "tee-proof-a", AggregateVerifier.ProofType.TEE, address(anchorStateRegistry) - ); - - AggregateVerifier gameB = - _createGame(TEE_PROVER, "game-b", "tee-proof-b", AggregateVerifier.ProofType.TEE, address(gameA)); - - vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); - assertTrue(gameA.gameOver()); - assertEq(gameA.proofCount(), 1); - - _nullify(gameB, "tee-nullify-b", AggregateVerifier.ProofType.TEE, "nullify-b"); - - assertTrue(teeVerifier.nullified()); - assertEq(gameA.proofCount(), 1); - - _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); - assertEq(gameA.proofCount(), 0); - assertEq(gameA.expectedResolution().raw(), type(uint64).max); - - vm.expectRevert(AggregateVerifier.GameNotOver.selector); - gameA.resolve(); + _assertResolveEarlyReturnWhenSharedVerifierNullifiedByAnotherGame(TEE_PROVER, AggregateVerifier.ProofType.TEE); } function testResolveEarlyReturnWhenSharedZkVerifierNullifiedByAnotherGame() public { - AggregateVerifier gameA = _createGame( - ZK_PROVER, "zk-game-a", "zk-proof-a", AggregateVerifier.ProofType.ZK, address(anchorStateRegistry) - ); - - AggregateVerifier gameB = - _createGame(ZK_PROVER, "zk-game-b", "zk-proof-b", AggregateVerifier.ProofType.ZK, address(gameA)); - - vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); - assertTrue(gameA.gameOver()); - assertEq(gameA.proofCount(), 1); - - _nullify(gameB, "zk-nullify-b", AggregateVerifier.ProofType.ZK, "zk-nullify-b"); - - assertTrue(zkVerifier.nullified()); - assertEq(gameA.proofCount(), 1); - - _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); - assertEq(gameA.proofCount(), 0); - assertEq(gameA.expectedResolution().raw(), type(uint64).max); - - vm.expectRevert(AggregateVerifier.GameNotOver.selector); - gameA.resolve(); + _assertResolveEarlyReturnWhenSharedVerifierNullifiedByAnotherGame(ZK_PROVER, AggregateVerifier.ProofType.ZK); } /// @notice With TEE + ZK, the fast window is 1 day. Another game nullifies the shared ZK verifier; the first @@ -169,12 +123,12 @@ contract NullifyTest is BaseTest { _nullify(gameB, "zk-nullify-dual", AggregateVerifier.ProofType.ZK, "dual-nullify-b"); assertTrue(zkVerifier.nullified()); - _resolveAndAssertStatus(gameA, GameStatus.IN_PROGRESS); + assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); assertEq(gameA.proofCount(), 1); assertEq(gameA.expectedResolution().raw(), block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); - _resolveAndAssertStatus(gameA, GameStatus.DEFENDER_WINS); + assertEq(uint8(gameA.resolve()), uint8(GameStatus.DEFENDER_WINS)); _assertStatus(gameA, GameStatus.DEFENDER_WINS); } @@ -220,16 +174,45 @@ contract NullifyTest is BaseTest { assertEq(uint8(game.status()), uint8(expectedStatus)); } - function _resolveAndAssertStatus(AggregateVerifier game, GameStatus expectedStatus) private { - assertEq(uint8(game.resolve()), uint8(expectedStatus)); + /// @notice When a shared verifier is nullified by another game, `resolve` persists the refutation and returns + /// early `IN_PROGRESS` instead of reverting. + function _assertResolveEarlyReturnWhenSharedVerifierNullifiedByAnotherGame( + address prover, + AggregateVerifier.ProofType proofType + ) + private + { + AggregateVerifier gameA = _createGame(prover, "game-a", "proof-a", proofType, address(anchorStateRegistry)); + AggregateVerifier gameB = _createGame(prover, "game-b", "proof-b", proofType, address(gameA)); + + vm.warp(block.timestamp + gameA.SLOW_FINALIZATION_DELAY()); + assertTrue(gameA.gameOver()); + assertEq(gameA.proofCount(), 1); + + _nullify(gameB, "nullify-proof", proofType, "nullify-claim"); + + if (proofType == AggregateVerifier.ProofType.TEE) { + assertTrue(teeVerifier.nullified()); + } else { + assertTrue(zkVerifier.nullified()); + } + assertEq(gameA.proofCount(), 1); + + assertEq(uint8(gameA.resolve()), uint8(GameStatus.IN_PROGRESS)); + assertEq(gameA.proofCount(), 0); + assertEq(gameA.expectedResolution().raw(), type(uint64).max); + + vm.expectRevert(AggregateVerifier.GameNotOver.selector); + gameA.resolve(); } function _claimCreditAfterDelay(AggregateVerifier game) private { - uint256 balanceBefore = game.gameCreator().balance; + address recipient = game.gameCreator(); + uint256 balanceBefore = recipient.balance; game.claimCredit(); vm.warp(block.timestamp + DELAYED_WETH_DELAY); game.claimCredit(); - assertEq(game.gameCreator().balance, balanceBefore + INIT_BOND); + assertEq(recipient.balance, balanceBefore + INIT_BOND); assertEq(delayedWETH.balanceOf(address(game)), 0); } } From 4fb8853dd1fa3c8cb2de059d1228d5e33c346dae Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 08:34:56 -0400 Subject: [PATCH 051/135] Refactor TEEProverRegistry.t.sol: update variable visibility to immutable for better encapsulation, streamline mock contract initialization, and enhance test clarity with dedicated helper functions for managing signers and registry deployment. --- test/L1/proofs/TEEProverRegistry.t.sol | 130 ++++++++++--------------- 1 file changed, 52 insertions(+), 78 deletions(-) diff --git a/test/L1/proofs/TEEProverRegistry.t.sol b/test/L1/proofs/TEEProverRegistry.t.sol index c9c4c74b..84d0992f 100644 --- a/test/L1/proofs/TEEProverRegistry.t.sol +++ b/test/L1/proofs/TEEProverRegistry.t.sol @@ -19,7 +19,7 @@ import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; /// @notice Mock AggregateVerifier that returns a configurable TEE_IMAGE_HASH. contract MockAggregateVerifierForRegistry { - bytes32 public TEE_IMAGE_HASH; + bytes32 public immutable TEE_IMAGE_HASH; constructor(bytes32 imageHash) { TEE_IMAGE_HASH = imageHash; @@ -28,14 +28,14 @@ contract MockAggregateVerifierForRegistry { /// @notice Mock DisputeGameFactory that returns a fixed game implementation. contract MockDisputeGameFactoryForRegistry { - mapping(uint32 => address) internal _impls; + IDisputeGame internal immutable _impl; - function setImpl(uint32 gameType, address impl) external { - _impls[gameType] = impl; + constructor(address impl) { + _impl = IDisputeGame(impl); } - function gameImpls(GameType gameType) external view returns (IDisputeGame) { - return IDisputeGame(_impls[GameType.unwrap(gameType)]); + function gameImpls(GameType) external view returns (IDisputeGame) { + return _impl; } } @@ -49,9 +49,6 @@ contract MockDisputeGameFactoryForRegistry { /// DevTEEProverRegistry inherits from TEEProverRegistry without modifying those functions. contract TEEProverRegistryTest is Test { DevTEEProverRegistry public teeProverRegistry; - ProxyAdmin public proxyAdmin; - MockDisputeGameFactoryForRegistry public mockFactory; - MockAggregateVerifierForRegistry public mockVerifier; address public owner; address public manager; @@ -59,6 +56,8 @@ contract TEEProverRegistryTest is Test { bytes32 public constant TEST_IMAGE_HASH = keccak256("test-image-hash"); uint32 public constant TEST_GAME_TYPE = 621; + string internal constant NOT_OWNER = "OwnableManaged: caller is not the owner"; + string internal constant NOT_OWNER_OR_MANAGER = "OwnableManaged: caller is not the owner or the manager"; // Events must be redeclared here because Solidity 0.8.15 doesn't support // referencing events from other contracts via qualified names (requires 0.8.21+) @@ -71,28 +70,38 @@ contract TEEProverRegistryTest is Test { manager = makeAddr("manager"); unauthorized = makeAddr("unauthorized"); - // Deploy mock factory and verifier - mockVerifier = new MockAggregateVerifierForRegistry(TEST_IMAGE_HASH); - mockFactory = new MockDisputeGameFactoryForRegistry(); - mockFactory.setImpl(TEST_GAME_TYPE, address(mockVerifier)); + teeProverRegistry = _deployRegistry(new address[](0), GameType.wrap(TEST_GAME_TYPE)); + } + + function _deployRegistry(address[] memory proposers, GameType gameType) internal returns (DevTEEProverRegistry) { + MockAggregateVerifierForRegistry verifier = new MockAggregateVerifierForRegistry(TEST_IMAGE_HASH); + MockDisputeGameFactoryForRegistry factory = new MockDisputeGameFactoryForRegistry(address(verifier)); - // Deploy implementation (using DevTEEProverRegistry for test flexibility) DevTEEProverRegistry impl = - new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(mockFactory))); + new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(factory))); - // Deploy proxy admin - proxyAdmin = new ProxyAdmin(address(this)); + ProxyAdmin proxyAdmin = new ProxyAdmin(address(this)); - // Deploy proxy TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( address(impl), address(proxyAdmin), - abi.encodeCall( - TEEProverRegistry.initialize, (owner, manager, new address[](0), GameType.wrap(TEST_GAME_TYPE)) - ) + abi.encodeCall(TEEProverRegistry.initialize, (owner, manager, proposers, gameType)) ); - teeProverRegistry = DevTEEProverRegistry(address(proxy)); + return DevTEEProverRegistry(address(proxy)); + } + + function _addDevSigner(address signer) internal { + vm.prank(owner); + teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); + } + + function _assertContains(address[] memory values, address expected) internal { + for (uint256 i = 0; i < values.length; i++) { + if (values[i] == expected) return; + } + + fail(); } // ============ Initialization Tests ============ @@ -107,26 +116,18 @@ contract TEEProverRegistryTest is Test { address proposer1 = makeAddr("proposer1"); address proposer2 = makeAddr("proposer2"); address proposer3 = makeAddr("proposer3"); - DevTEEProverRegistry impl2 = - new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(1))); - ProxyAdmin proxyAdmin2 = new ProxyAdmin(address(this)); address[] memory proposers = new address[](3); proposers[0] = proposer1; proposers[1] = proposer2; proposers[2] = proposer3; - TransparentUpgradeableProxy proxy2 = new TransparentUpgradeableProxy( - address(impl2), - address(proxyAdmin2), - abi.encodeCall(TEEProverRegistry.initialize, (owner, manager, proposers, GameType.wrap(0))) - ); - DevTEEProverRegistry registry2 = DevTEEProverRegistry(address(proxy2)); + + DevTEEProverRegistry registry2 = _deployRegistry(proposers, GameType.wrap(TEST_GAME_TYPE)); assertTrue(registry2.isValidProposer(proposer1)); assertTrue(registry2.isValidProposer(proposer2)); assertTrue(registry2.isValidProposer(proposer3)); } function testInitializationWithEmptyProposers() public view { - // Default setUp uses an empty proposer array — no proposer should be set assertFalse(teeProverRegistry.isValidProposer(address(0))); } @@ -135,14 +136,10 @@ contract TEEProverRegistryTest is Test { function testDeregisterSignerAsOwner() public { address signer = makeAddr("signer"); - // Add signer via DevTEEProverRegistry - vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); + _addDevSigner(signer); - // Verify signer is registered assertTrue(teeProverRegistry.isValidSigner(signer)); - // Deregister as owner vm.expectEmit(true, false, false, false); emit SignerDeregistered(signer); @@ -155,9 +152,7 @@ contract TEEProverRegistryTest is Test { function testDeregisterSignerAsManager() public { address signer = makeAddr("signer"); - // Add signer via DevTEEProverRegistry - vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); + _addDevSigner(signer); assertTrue(teeProverRegistry.isValidSigner(signer)); @@ -171,7 +166,7 @@ contract TEEProverRegistryTest is Test { address signer = makeAddr("signer"); vm.prank(unauthorized); - vm.expectRevert("OwnableManaged: caller is not the owner or the manager"); + vm.expectRevert(bytes(NOT_OWNER_OR_MANAGER)); teeProverRegistry.deregisterSigner(signer); } @@ -193,11 +188,11 @@ contract TEEProverRegistryTest is Test { address newProposer = makeAddr("proposer"); vm.prank(manager); - vm.expectRevert("OwnableManaged: caller is not the owner"); + vm.expectRevert(bytes(NOT_OWNER)); teeProverRegistry.setProposer(newProposer, true); vm.prank(unauthorized); - vm.expectRevert("OwnableManaged: caller is not the owner"); + vm.expectRevert(bytes(NOT_OWNER)); teeProverRegistry.setProposer(newProposer, true); } @@ -211,8 +206,7 @@ contract TEEProverRegistryTest is Test { function testIsValidSignerReturnsTrueForRegistered() public { address signer = makeAddr("signer"); - vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); + _addDevSigner(signer); assertTrue(teeProverRegistry.isValidSigner(signer)); } @@ -238,11 +232,11 @@ contract TEEProverRegistryTest is Test { address newOwner = makeAddr("newOwner"); vm.prank(manager); - vm.expectRevert("OwnableManaged: caller is not the owner"); + vm.expectRevert(bytes(NOT_OWNER)); teeProverRegistry.transferOwnership(newOwner); vm.prank(unauthorized); - vm.expectRevert("OwnableManaged: caller is not the owner"); + vm.expectRevert(bytes(NOT_OWNER)); teeProverRegistry.transferOwnership(newOwner); } @@ -276,7 +270,7 @@ contract TEEProverRegistryTest is Test { address newManager = makeAddr("newManager"); vm.prank(unauthorized); - vm.expectRevert("OwnableManaged: caller is not the owner or the manager"); + vm.expectRevert(bytes(NOT_OWNER_OR_MANAGER)); teeProverRegistry.transferManagement(newManager); } @@ -327,24 +321,21 @@ contract TEEProverRegistryTest is Test { address signer = makeAddr("dev-signer"); vm.prank(manager); - vm.expectRevert("OwnableManaged: caller is not the owner"); + vm.expectRevert(bytes(NOT_OWNER)); teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); vm.prank(unauthorized); - vm.expectRevert("OwnableManaged: caller is not the owner"); + vm.expectRevert(bytes(NOT_OWNER)); teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); } function testAddDevSignerIdempotent() public { address signer = makeAddr("dev-signer"); - vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); + _addDevSigner(signer); assertTrue(teeProverRegistry.isValidSigner(signer)); - // Adding again should not revert - vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); + _addDevSigner(signer); assertTrue(teeProverRegistry.isValidSigner(signer)); } @@ -374,8 +365,7 @@ contract TEEProverRegistryTest is Test { function testGetRegisteredSignersAfterRegister() public { address signer = makeAddr("signer"); - vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); + _addDevSigner(signer); address[] memory signers = teeProverRegistry.getRegisteredSigners(); assertEq(signers.length, 1); @@ -385,8 +375,7 @@ contract TEEProverRegistryTest is Test { function testGetRegisteredSignersAfterDeregister() public { address signer = makeAddr("signer"); - vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); + _addDevSigner(signer); assertEq(teeProverRegistry.getRegisteredSigners().length, 1); @@ -411,18 +400,9 @@ contract TEEProverRegistryTest is Test { address[] memory signers = teeProverRegistry.getRegisteredSigners(); assertEq(signers.length, 3); - // Verify all three are present (order not guaranteed) - bool foundSigner1; - bool foundSigner2; - bool foundSigner3; - for (uint256 i = 0; i < signers.length; i++) { - if (signers[i] == signer1) foundSigner1 = true; - if (signers[i] == signer2) foundSigner2 = true; - if (signers[i] == signer3) foundSigner3 = true; - } - assertTrue(foundSigner1); - assertTrue(foundSigner2); - assertTrue(foundSigner3); + _assertContains(signers, signer1); + _assertContains(signers, signer2); + _assertContains(signers, signer3); } function testGetRegisteredSignersConsistencyAfterMixedOperations() public { @@ -430,7 +410,6 @@ contract TEEProverRegistryTest is Test { address signer2 = makeAddr("signer-2"); address signer3 = makeAddr("signer-3"); - // Register three signers vm.startPrank(owner); teeProverRegistry.addDevSigner(signer1, TEST_IMAGE_HASH); teeProverRegistry.addDevSigner(signer2, TEST_IMAGE_HASH); @@ -439,33 +418,28 @@ contract TEEProverRegistryTest is Test { assertEq(teeProverRegistry.getRegisteredSigners().length, 3); - // Deregister the middle one vm.prank(manager); teeProverRegistry.deregisterSigner(signer2); address[] memory signers = teeProverRegistry.getRegisteredSigners(); assertEq(signers.length, 2); - // Mapping and set stay consistent for (uint256 i = 0; i < signers.length; i++) { assertTrue(teeProverRegistry.isValidSigner(signers[i])); assertNotEq(signers[i], signer2); } - // Deregistered signer not in mapping either assertFalse(teeProverRegistry.isValidSigner(signer2)); } function testGetRegisteredSignersDeregisterIdempotent() public { address signer = makeAddr("signer"); - vm.prank(owner); - teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); + _addDevSigner(signer); vm.prank(owner); teeProverRegistry.deregisterSigner(signer); - // Deregistering again should not revert and set should still be empty vm.prank(owner); teeProverRegistry.deregisterSigner(signer); From a2a219daa285945b459a98a8eaa9ad4da283a625 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 08:40:14 -0400 Subject: [PATCH 052/135] Refactor TEEProverRegistry.t.sol: remove redundant comments, streamline revert expectations with a dedicated helper function, and enhance test clarity by consolidating signer management logic. --- test/L1/proofs/TEEProverRegistry.t.sol | 111 +++++++------------------ 1 file changed, 30 insertions(+), 81 deletions(-) diff --git a/test/L1/proofs/TEEProverRegistry.t.sol b/test/L1/proofs/TEEProverRegistry.t.sol index 84d0992f..d2692754 100644 --- a/test/L1/proofs/TEEProverRegistry.t.sol +++ b/test/L1/proofs/TEEProverRegistry.t.sol @@ -17,7 +17,6 @@ import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { DevTEEProverRegistry } from "test/mocks/MockDevTEEProverRegistry.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; -/// @notice Mock AggregateVerifier that returns a configurable TEE_IMAGE_HASH. contract MockAggregateVerifierForRegistry { bytes32 public immutable TEE_IMAGE_HASH; @@ -26,7 +25,6 @@ contract MockAggregateVerifierForRegistry { } } -/// @notice Mock DisputeGameFactory that returns a fixed game implementation. contract MockDisputeGameFactoryForRegistry { IDisputeGame internal immutable _impl; @@ -39,14 +37,7 @@ contract MockDisputeGameFactoryForRegistry { } } -/// @notice Tests for TEEProverRegistry and DevTEEProverRegistry contracts. -/// @dev IMPORTANT: This test file uses DevTEEProverRegistry as the implementation because -/// registering signers on the production TEEProverRegistry requires a ZK proof of a valid -/// AWS Nitro attestation, which cannot be generated in a test environment. DevTEEProverRegistry extends -/// TEEProverRegistry with an `addDevSigner` function that bypasses attestation verification, -/// allowing us to test all signer-related functionality. All tests for base TEEProverRegistry -/// functionality (ownership, proposer, etc.) are equally valid since -/// DevTEEProverRegistry inherits from TEEProverRegistry without modifying those functions. +/// @dev Uses DevTEEProverRegistry because production signer registration requires a Nitro attestation proof. contract TEEProverRegistryTest is Test { DevTEEProverRegistry public teeProverRegistry; @@ -96,6 +87,19 @@ contract TEEProverRegistryTest is Test { teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); } + function _addDevSigners(address signer1, address signer2, address signer3) internal { + vm.startPrank(owner); + teeProverRegistry.addDevSigner(signer1, TEST_IMAGE_HASH); + teeProverRegistry.addDevSigner(signer2, TEST_IMAGE_HASH); + teeProverRegistry.addDevSigner(signer3, TEST_IMAGE_HASH); + vm.stopPrank(); + } + + function _expectRevertFrom(address caller, string memory reason) internal { + vm.prank(caller); + vm.expectRevert(bytes(reason)); + } + function _assertContains(address[] memory values, address expected) internal { for (uint256 i = 0; i < values.length; i++) { if (values[i] == expected) return; @@ -104,8 +108,6 @@ contract TEEProverRegistryTest is Test { fail(); } - // ============ Initialization Tests ============ - function testInitialization() public view { assertEq(teeProverRegistry.owner(), owner); assertEq(teeProverRegistry.manager(), manager); @@ -131,15 +133,11 @@ contract TEEProverRegistryTest is Test { assertFalse(teeProverRegistry.isValidProposer(address(0))); } - // ============ Signer Deregistration Tests ============ - function testDeregisterSignerAsOwner() public { address signer = makeAddr("signer"); _addDevSigner(signer); - assertTrue(teeProverRegistry.isValidSigner(signer)); - vm.expectEmit(true, false, false, false); emit SignerDeregistered(signer); @@ -154,8 +152,6 @@ contract TEEProverRegistryTest is Test { _addDevSigner(signer); - assertTrue(teeProverRegistry.isValidSigner(signer)); - vm.prank(manager); teeProverRegistry.deregisterSigner(signer); @@ -165,13 +161,10 @@ contract TEEProverRegistryTest is Test { function testDeregisterSignerFailsIfUnauthorized() public { address signer = makeAddr("signer"); - vm.prank(unauthorized); - vm.expectRevert(bytes(NOT_OWNER_OR_MANAGER)); + _expectRevertFrom(unauthorized, NOT_OWNER_OR_MANAGER); teeProverRegistry.deregisterSigner(signer); } - // ============ Proposer Tests ============ - function testSetProposer() public { address newProposer = makeAddr("proposer"); @@ -187,17 +180,13 @@ contract TEEProverRegistryTest is Test { function testSetProposerFailsIfNotOwner() public { address newProposer = makeAddr("proposer"); - vm.prank(manager); - vm.expectRevert(bytes(NOT_OWNER)); + _expectRevertFrom(manager, NOT_OWNER); teeProverRegistry.setProposer(newProposer, true); - vm.prank(unauthorized); - vm.expectRevert(bytes(NOT_OWNER)); + _expectRevertFrom(unauthorized, NOT_OWNER); teeProverRegistry.setProposer(newProposer, true); } - // ============ isValidSigner Tests ============ - function testIsValidSignerReturnsFalseForUnregistered() public { address unregistered = makeAddr("unregistered"); assertFalse(teeProverRegistry.isValidSigner(unregistered)); @@ -211,14 +200,10 @@ contract TEEProverRegistryTest is Test { assertTrue(teeProverRegistry.isValidSigner(signer)); } - // ============ MAX_AGE Tests ============ - function testMaxAgeConstant() public view { assertEq(teeProverRegistry.MAX_AGE(), 60 minutes); } - // ============ Ownership Transfer Tests ============ - function testTransferOwnership() public { address newOwner = makeAddr("newOwner"); @@ -231,12 +216,10 @@ contract TEEProverRegistryTest is Test { function testTransferOwnershipFailsIfNotOwner() public { address newOwner = makeAddr("newOwner"); - vm.prank(manager); - vm.expectRevert(bytes(NOT_OWNER)); + _expectRevertFrom(manager, NOT_OWNER); teeProverRegistry.transferOwnership(newOwner); - vm.prank(unauthorized); - vm.expectRevert(bytes(NOT_OWNER)); + _expectRevertFrom(unauthorized, NOT_OWNER); teeProverRegistry.transferOwnership(newOwner); } @@ -246,8 +229,6 @@ contract TEEProverRegistryTest is Test { teeProverRegistry.transferOwnership(address(0)); } - // ============ Management Transfer Tests ============ - function testTransferManagementAsOwner() public { address newManager = makeAddr("newManager"); @@ -269,8 +250,7 @@ contract TEEProverRegistryTest is Test { function testTransferManagementFailsIfUnauthorized() public { address newManager = makeAddr("newManager"); - vm.prank(unauthorized); - vm.expectRevert(bytes(NOT_OWNER_OR_MANAGER)); + _expectRevertFrom(unauthorized, NOT_OWNER_OR_MANAGER); teeProverRegistry.transferManagement(newManager); } @@ -280,8 +260,6 @@ contract TEEProverRegistryTest is Test { teeProverRegistry.transferManagement(address(0)); } - // ============ Renounce Tests ============ - function testRenounceOwnership() public { vm.prank(owner); teeProverRegistry.renounceOwnership(); @@ -303,8 +281,6 @@ contract TEEProverRegistryTest is Test { assertEq(teeProverRegistry.manager(), address(0)); } - // ============ DevTEEProverRegistry: addDevSigner Tests ============ - function testAddDevSigner() public { address signer = makeAddr("dev-signer"); @@ -320,12 +296,10 @@ contract TEEProverRegistryTest is Test { function testAddDevSignerFailsIfNotOwner() public { address signer = makeAddr("dev-signer"); - vm.prank(manager); - vm.expectRevert(bytes(NOT_OWNER)); + _expectRevertFrom(manager, NOT_OWNER); teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); - vm.prank(unauthorized); - vm.expectRevert(bytes(NOT_OWNER)); + _expectRevertFrom(unauthorized, NOT_OWNER); teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); } @@ -333,9 +307,8 @@ contract TEEProverRegistryTest is Test { address signer = makeAddr("dev-signer"); _addDevSigner(signer); - assertTrue(teeProverRegistry.isValidSigner(signer)); - _addDevSigner(signer); + assertTrue(teeProverRegistry.isValidSigner(signer)); } @@ -344,22 +317,15 @@ contract TEEProverRegistryTest is Test { address signer2 = makeAddr("dev-signer-2"); address signer3 = makeAddr("dev-signer-3"); - vm.startPrank(owner); - teeProverRegistry.addDevSigner(signer1, TEST_IMAGE_HASH); - teeProverRegistry.addDevSigner(signer2, TEST_IMAGE_HASH); - teeProverRegistry.addDevSigner(signer3, TEST_IMAGE_HASH); - vm.stopPrank(); + _addDevSigners(signer1, signer2, signer3); assertTrue(teeProverRegistry.isValidSigner(signer1)); assertTrue(teeProverRegistry.isValidSigner(signer2)); assertTrue(teeProverRegistry.isValidSigner(signer3)); } - // ============ getRegisteredSigners Tests ============ - function testGetRegisteredSignersEmpty() public view { - address[] memory signers = teeProverRegistry.getRegisteredSigners(); - assertEq(signers.length, 0); + assertEq(teeProverRegistry.getRegisteredSigners().length, 0); } function testGetRegisteredSignersAfterRegister() public { @@ -377,13 +343,10 @@ contract TEEProverRegistryTest is Test { _addDevSigner(signer); - assertEq(teeProverRegistry.getRegisteredSigners().length, 1); - vm.prank(owner); teeProverRegistry.deregisterSigner(signer); - address[] memory signers = teeProverRegistry.getRegisteredSigners(); - assertEq(signers.length, 0); + assertEq(teeProverRegistry.getRegisteredSigners().length, 0); } function testGetRegisteredSignersMultiple() public { @@ -391,11 +354,7 @@ contract TEEProverRegistryTest is Test { address signer2 = makeAddr("signer-2"); address signer3 = makeAddr("signer-3"); - vm.startPrank(owner); - teeProverRegistry.addDevSigner(signer1, TEST_IMAGE_HASH); - teeProverRegistry.addDevSigner(signer2, TEST_IMAGE_HASH); - teeProverRegistry.addDevSigner(signer3, TEST_IMAGE_HASH); - vm.stopPrank(); + _addDevSigners(signer1, signer2, signer3); address[] memory signers = teeProverRegistry.getRegisteredSigners(); assertEq(signers.length, 3); @@ -410,25 +369,15 @@ contract TEEProverRegistryTest is Test { address signer2 = makeAddr("signer-2"); address signer3 = makeAddr("signer-3"); - vm.startPrank(owner); - teeProverRegistry.addDevSigner(signer1, TEST_IMAGE_HASH); - teeProverRegistry.addDevSigner(signer2, TEST_IMAGE_HASH); - teeProverRegistry.addDevSigner(signer3, TEST_IMAGE_HASH); - vm.stopPrank(); - - assertEq(teeProverRegistry.getRegisteredSigners().length, 3); + _addDevSigners(signer1, signer2, signer3); vm.prank(manager); teeProverRegistry.deregisterSigner(signer2); address[] memory signers = teeProverRegistry.getRegisteredSigners(); assertEq(signers.length, 2); - - for (uint256 i = 0; i < signers.length; i++) { - assertTrue(teeProverRegistry.isValidSigner(signers[i])); - assertNotEq(signers[i], signer2); - } - + _assertContains(signers, signer1); + _assertContains(signers, signer3); assertFalse(teeProverRegistry.isValidSigner(signer2)); } From 763d7a86ffa38c5c9535f986f068696a68e3197a Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 08:49:47 -0400 Subject: [PATCH 053/135] Refactor TEEProverRegistry.t.sol: replace constant game type definition with a wrapped type for improved type safety, streamline revert expectation functions for clarity, and enhance test assertions by consolidating address handling in emit expectations. --- test/L1/proofs/TEEProverRegistry.t.sol | 42 ++++++++++++++------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/test/L1/proofs/TEEProverRegistry.t.sol b/test/L1/proofs/TEEProverRegistry.t.sol index d2692754..b0df4960 100644 --- a/test/L1/proofs/TEEProverRegistry.t.sol +++ b/test/L1/proofs/TEEProverRegistry.t.sol @@ -6,7 +6,6 @@ import { Test } from "lib/forge-std/src/Test.sol"; import { TransparentUpgradeableProxy } from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; @@ -46,7 +45,7 @@ contract TEEProverRegistryTest is Test { address public unauthorized; bytes32 public constant TEST_IMAGE_HASH = keccak256("test-image-hash"); - uint32 public constant TEST_GAME_TYPE = 621; + GameType public constant TEST_GAME_TYPE = GameType.wrap(621); string internal constant NOT_OWNER = "OwnableManaged: caller is not the owner"; string internal constant NOT_OWNER_OR_MANAGER = "OwnableManaged: caller is not the owner or the manager"; @@ -61,7 +60,7 @@ contract TEEProverRegistryTest is Test { manager = makeAddr("manager"); unauthorized = makeAddr("unauthorized"); - teeProverRegistry = _deployRegistry(new address[](0), GameType.wrap(TEST_GAME_TYPE)); + teeProverRegistry = _deployRegistry(new address[](0), TEST_GAME_TYPE); } function _deployRegistry(address[] memory proposers, GameType gameType) internal returns (DevTEEProverRegistry) { @@ -71,11 +70,9 @@ contract TEEProverRegistryTest is Test { DevTEEProverRegistry impl = new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(factory))); - ProxyAdmin proxyAdmin = new ProxyAdmin(address(this)); - TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( address(impl), - address(proxyAdmin), + makeAddr("proxy-admin"), abi.encodeCall(TEEProverRegistry.initialize, (owner, manager, proposers, gameType)) ); @@ -95,9 +92,14 @@ contract TEEProverRegistryTest is Test { vm.stopPrank(); } - function _expectRevertFrom(address caller, string memory reason) internal { + function _expectNotOwnerRevert(address caller) internal { + vm.prank(caller); + vm.expectRevert(bytes(NOT_OWNER)); + } + + function _expectNotOwnerOrManagerRevert(address caller) internal { vm.prank(caller); - vm.expectRevert(bytes(reason)); + vm.expectRevert(bytes(NOT_OWNER_OR_MANAGER)); } function _assertContains(address[] memory values, address expected) internal { @@ -123,7 +125,7 @@ contract TEEProverRegistryTest is Test { proposers[1] = proposer2; proposers[2] = proposer3; - DevTEEProverRegistry registry2 = _deployRegistry(proposers, GameType.wrap(TEST_GAME_TYPE)); + DevTEEProverRegistry registry2 = _deployRegistry(proposers, TEST_GAME_TYPE); assertTrue(registry2.isValidProposer(proposer1)); assertTrue(registry2.isValidProposer(proposer2)); assertTrue(registry2.isValidProposer(proposer3)); @@ -138,7 +140,7 @@ contract TEEProverRegistryTest is Test { _addDevSigner(signer); - vm.expectEmit(true, false, false, false); + vm.expectEmit(true, false, false, false, address(teeProverRegistry)); emit SignerDeregistered(signer); vm.prank(owner); @@ -161,14 +163,14 @@ contract TEEProverRegistryTest is Test { function testDeregisterSignerFailsIfUnauthorized() public { address signer = makeAddr("signer"); - _expectRevertFrom(unauthorized, NOT_OWNER_OR_MANAGER); + _expectNotOwnerOrManagerRevert(unauthorized); teeProverRegistry.deregisterSigner(signer); } function testSetProposer() public { address newProposer = makeAddr("proposer"); - vm.expectEmit(true, false, false, false); + vm.expectEmit(true, false, false, false, address(teeProverRegistry)); emit ProposerSet(newProposer, true); vm.prank(owner); @@ -180,10 +182,10 @@ contract TEEProverRegistryTest is Test { function testSetProposerFailsIfNotOwner() public { address newProposer = makeAddr("proposer"); - _expectRevertFrom(manager, NOT_OWNER); + _expectNotOwnerRevert(manager); teeProverRegistry.setProposer(newProposer, true); - _expectRevertFrom(unauthorized, NOT_OWNER); + _expectNotOwnerRevert(unauthorized); teeProverRegistry.setProposer(newProposer, true); } @@ -216,10 +218,10 @@ contract TEEProverRegistryTest is Test { function testTransferOwnershipFailsIfNotOwner() public { address newOwner = makeAddr("newOwner"); - _expectRevertFrom(manager, NOT_OWNER); + _expectNotOwnerRevert(manager); teeProverRegistry.transferOwnership(newOwner); - _expectRevertFrom(unauthorized, NOT_OWNER); + _expectNotOwnerRevert(unauthorized); teeProverRegistry.transferOwnership(newOwner); } @@ -250,7 +252,7 @@ contract TEEProverRegistryTest is Test { function testTransferManagementFailsIfUnauthorized() public { address newManager = makeAddr("newManager"); - _expectRevertFrom(unauthorized, NOT_OWNER_OR_MANAGER); + _expectNotOwnerOrManagerRevert(unauthorized); teeProverRegistry.transferManagement(newManager); } @@ -284,7 +286,7 @@ contract TEEProverRegistryTest is Test { function testAddDevSigner() public { address signer = makeAddr("dev-signer"); - vm.expectEmit(true, false, false, false); + vm.expectEmit(true, false, false, false, address(teeProverRegistry)); emit SignerRegistered(signer); vm.prank(owner); @@ -296,10 +298,10 @@ contract TEEProverRegistryTest is Test { function testAddDevSignerFailsIfNotOwner() public { address signer = makeAddr("dev-signer"); - _expectRevertFrom(manager, NOT_OWNER); + _expectNotOwnerRevert(manager); teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); - _expectRevertFrom(unauthorized, NOT_OWNER); + _expectNotOwnerRevert(unauthorized); teeProverRegistry.addDevSigner(signer, TEST_IMAGE_HASH); } From e2f84c8861cf4c6223b3f87b126b5ee027af103f Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 09:02:00 -0400 Subject: [PATCH 054/135] Refactor TEEVerifier.t.sol: update MockAggregateVerifier to use immutable for TEE_IMAGE_HASH, streamline MockDisputeGameFactory initialization, and enhance test clarity by consolidating proof handling in verification tests. --- test/L1/proofs/TEEVerifier.t.sol | 113 ++++++++----------------------- 1 file changed, 27 insertions(+), 86 deletions(-) diff --git a/test/L1/proofs/TEEVerifier.t.sol b/test/L1/proofs/TEEVerifier.t.sol index 66ab646a..776dc2dd 100644 --- a/test/L1/proofs/TEEVerifier.t.sol +++ b/test/L1/proofs/TEEVerifier.t.sol @@ -6,7 +6,6 @@ import { Test } from "lib/forge-std/src/Test.sol"; import { TransparentUpgradeableProxy } from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { INitroEnclaveVerifier } from "interfaces/L1/proofs/tee/INitroEnclaveVerifier.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; @@ -19,9 +18,9 @@ import { DevTEEProverRegistry } from "test/mocks/MockDevTEEProverRegistry.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; -/// @notice Mock AggregateVerifier that returns a configurable TEE_IMAGE_HASH. +/// @notice Mock AggregateVerifier that returns a fixed TEE_IMAGE_HASH. contract MockAggregateVerifierForVerifier { - bytes32 public TEE_IMAGE_HASH; + bytes32 public immutable TEE_IMAGE_HASH; constructor(bytes32 imageHash) { TEE_IMAGE_HASH = imageHash; @@ -30,69 +29,50 @@ contract MockAggregateVerifierForVerifier { /// @notice Mock DisputeGameFactory that returns a fixed game implementation. contract MockDisputeGameFactoryForVerifier { - mapping(uint32 => address) internal _impls; + IDisputeGame internal immutable _impl; - function setImpl(uint32 gameType_, address impl) external { - _impls[gameType_] = impl; + constructor(address impl) { + _impl = IDisputeGame(impl); } - function gameImpls(GameType gameType_) external view returns (IDisputeGame) { - return IDisputeGame(_impls[GameType.unwrap(gameType_)]); + function gameImpls(GameType) external view returns (IDisputeGame) { + return _impl; } } contract TEEVerifierTest is Test { TEEVerifier public verifier; DevTEEProverRegistry public teeProverRegistry; - ProxyAdmin public proxyAdmin; MockAnchorStateRegistry public anchorStateRegistry; - // Test signer - we'll derive address from private key uint256 internal constant SIGNER_PRIVATE_KEY = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; - address internal signerAddress; - bytes32 internal constant IMAGE_ID = keccak256("test-image-id"); + bytes32 internal constant TEST_JOURNAL = keccak256("test-journal"); uint32 internal constant TEST_GAME_TYPE = 621; address internal immutable PROPOSER = makeAddr("proposer"); - address internal owner; - function setUp() public { - owner = address(this); - - // Derive signer address from private key - signerAddress = vm.addr(SIGNER_PRIVATE_KEY); - - // Deploy mock factory and verifier + address signerAddress = vm.addr(SIGNER_PRIVATE_KEY); MockAggregateVerifierForVerifier mockVerifier = new MockAggregateVerifierForVerifier(IMAGE_ID); - MockDisputeGameFactoryForVerifier mockFactory = new MockDisputeGameFactoryForVerifier(); - mockFactory.setImpl(TEST_GAME_TYPE, address(mockVerifier)); + MockDisputeGameFactoryForVerifier mockFactory = new MockDisputeGameFactoryForVerifier(address(mockVerifier)); - // Deploy implementation (NitroEnclaveVerifier not needed for dev signer tests) + // DevTEEProverRegistry keeps these tests focused on verifier behavior without Nitro attestation setup. DevTEEProverRegistry impl = new DevTEEProverRegistry(INitroEnclaveVerifier(address(0)), IDisputeGameFactory(address(mockFactory))); - // Deploy proxy admin - proxyAdmin = new ProxyAdmin(address(this)); - - // Deploy proxy TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( address(impl), - address(proxyAdmin), + makeAddr("proxy-admin"), abi.encodeCall( - TEEProverRegistry.initialize, (owner, owner, new address[](0), GameType.wrap(TEST_GAME_TYPE)) + TEEProverRegistry.initialize, + (address(this), address(this), new address[](0), GameType.wrap(TEST_GAME_TYPE)) ) ); teeProverRegistry = DevTEEProverRegistry(address(proxy)); - - // Register the signer with the correct image hash teeProverRegistry.addDevSigner(signerAddress, IMAGE_ID); - - // Set the proposer as valid teeProverRegistry.setProposer(PROPOSER, true); - // Deploy TEEVerifier anchorStateRegistry = new MockAnchorStateRegistry(); verifier = new TEEVerifier( TEEProverRegistry(address(teeProverRegistry)), IAnchorStateRegistry(address(anchorStateRegistry)) @@ -100,90 +80,51 @@ contract TEEVerifierTest is Test { } function testVerifyValidSignature() public view { - // Create a journal hash - bytes32 journal = keccak256("test-journal"); - - // Sign the journal with the signer's private key - (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal); - bytes memory signature = abi.encodePacked(r, s, v); - - // Construct proof: proposer(20) + signature(65) = 85 bytes - bytes memory proofBytes = abi.encodePacked(PROPOSER, signature); - - // Verify should return true regardless of imageId (enforced via journal hash, not registry) - bool result = verifier.verify(proofBytes, IMAGE_ID, journal); + bool result = verifier.verify(_proofFor(PROPOSER, SIGNER_PRIVATE_KEY, TEST_JOURNAL), IMAGE_ID, TEST_JOURNAL); assertTrue(result); } function testVerifyFailsWithInvalidSignature() public { - bytes32 journal = keccak256("test-journal"); - - // Create an invalid signature (all zeros except v) - bytes memory invalidSignature = new bytes(65); - invalidSignature[64] = bytes1(uint8(27)); // Set v to 27 - - bytes memory proofBytes = abi.encodePacked(PROPOSER, invalidSignature); + bytes memory proofBytes = abi.encodePacked(PROPOSER, bytes32(0), bytes32(0), uint8(27)); vm.expectRevert(TEEVerifier.InvalidSignature.selector); - verifier.verify(proofBytes, IMAGE_ID, journal); + verifier.verify(proofBytes, IMAGE_ID, TEST_JOURNAL); } function testVerifyFailsWithInvalidProposer() public { - // Create a journal hash - bytes32 journal = keccak256("test-journal"); - - // Sign the journal with the signer's private key - (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal); - bytes memory signature = abi.encodePacked(r, s, v); - - // Construct proof: proposer(20) + signature(65) = 85 bytes - bytes memory proofBytes = abi.encodePacked(address(0), signature); + bytes memory proofBytes = _proofFor(address(0), SIGNER_PRIVATE_KEY, TEST_JOURNAL); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidProposer.selector, address(0))); - verifier.verify(proofBytes, IMAGE_ID, journal); + verifier.verify(proofBytes, IMAGE_ID, TEST_JOURNAL); } function testVerifyFailsWithUnregisteredSigner() public { - // Use a different private key that's not registered uint256 unregisteredKey = 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef; address unregisteredSigner = vm.addr(unregisteredKey); - bytes32 journal = keccak256("test-journal"); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(unregisteredKey, journal); - bytes memory signature = abi.encodePacked(r, s, v); - - bytes memory proofBytes = abi.encodePacked(PROPOSER, signature); - vm.expectRevert(abi.encodeWithSelector(TEEVerifier.InvalidSigner.selector, unregisteredSigner)); - verifier.verify(proofBytes, IMAGE_ID, journal); + verifier.verify(_proofFor(PROPOSER, unregisteredKey, TEST_JOURNAL), IMAGE_ID, TEST_JOURNAL); } function testVerifyFailsWithImageIdMismatch() public { - bytes32 journal = keccak256("test-journal"); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(SIGNER_PRIVATE_KEY, journal); - bytes memory signature = abi.encodePacked(r, s, v); - - bytes memory proofBytes = abi.encodePacked(PROPOSER, signature); - - // Different imageId should fail — signer was registered with IMAGE_ID bytes32 wrongImageId = keccak256("different-image"); vm.expectRevert(abi.encodeWithSelector(TEEVerifier.ImageIdMismatch.selector, IMAGE_ID, wrongImageId)); - verifier.verify(proofBytes, wrongImageId, journal); + verifier.verify(_proofFor(PROPOSER, SIGNER_PRIVATE_KEY, TEST_JOURNAL), wrongImageId, TEST_JOURNAL); } function testVerifyFailsWithInvalidProofFormat() public { - bytes32 journal = keccak256("test-journal"); - - // Proof too short (less than 85 bytes) bytes memory shortProof = new bytes(50); vm.expectRevert(TEEVerifier.InvalidProofFormat.selector); - verifier.verify(shortProof, IMAGE_ID, journal); + verifier.verify(shortProof, IMAGE_ID, TEST_JOURNAL); } function testConstants() public view { assertEq(address(verifier.TEE_PROVER_REGISTRY()), address(teeProverRegistry)); } + + function _proofFor(address proposer, uint256 privateKey, bytes32 journal) internal pure returns (bytes memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, journal); + return abi.encodePacked(proposer, r, s, v); + } } From 5a0adf2549927012e5040c5c1a3c15fb135f1185 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 09:07:14 -0400 Subject: [PATCH 055/135] Refactor TEEVerifier.t.sol: replace constant game type definition with a wrapped type for improved type safety, streamline MockAnchorStateRegistry initialization, and enhance test clarity by consolidating proof verification assertions. --- test/L1/proofs/TEEVerifier.t.sol | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/test/L1/proofs/TEEVerifier.t.sol b/test/L1/proofs/TEEVerifier.t.sol index 776dc2dd..86707ddb 100644 --- a/test/L1/proofs/TEEVerifier.t.sol +++ b/test/L1/proofs/TEEVerifier.t.sol @@ -18,7 +18,6 @@ import { DevTEEProverRegistry } from "test/mocks/MockDevTEEProverRegistry.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; import { TEEVerifier } from "src/L1/proofs/tee/TEEVerifier.sol"; -/// @notice Mock AggregateVerifier that returns a fixed TEE_IMAGE_HASH. contract MockAggregateVerifierForVerifier { bytes32 public immutable TEE_IMAGE_HASH; @@ -27,7 +26,6 @@ contract MockAggregateVerifierForVerifier { } } -/// @notice Mock DisputeGameFactory that returns a fixed game implementation. contract MockDisputeGameFactoryForVerifier { IDisputeGame internal immutable _impl; @@ -43,12 +41,11 @@ contract MockDisputeGameFactoryForVerifier { contract TEEVerifierTest is Test { TEEVerifier public verifier; DevTEEProverRegistry public teeProverRegistry; - MockAnchorStateRegistry public anchorStateRegistry; uint256 internal constant SIGNER_PRIVATE_KEY = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; bytes32 internal constant IMAGE_ID = keccak256("test-image-id"); bytes32 internal constant TEST_JOURNAL = keccak256("test-journal"); - uint32 internal constant TEST_GAME_TYPE = 621; + GameType internal constant TEST_GAME_TYPE = GameType.wrap(621); address internal immutable PROPOSER = makeAddr("proposer"); function setUp() public { @@ -65,7 +62,7 @@ contract TEEVerifierTest is Test { makeAddr("proxy-admin"), abi.encodeCall( TEEProverRegistry.initialize, - (address(this), address(this), new address[](0), GameType.wrap(TEST_GAME_TYPE)) + (address(this), address(this), new address[](0), TEST_GAME_TYPE) ) ); @@ -73,15 +70,14 @@ contract TEEVerifierTest is Test { teeProverRegistry.addDevSigner(signerAddress, IMAGE_ID); teeProverRegistry.setProposer(PROPOSER, true); - anchorStateRegistry = new MockAnchorStateRegistry(); + MockAnchorStateRegistry anchorStateRegistry = new MockAnchorStateRegistry(); verifier = new TEEVerifier( TEEProverRegistry(address(teeProverRegistry)), IAnchorStateRegistry(address(anchorStateRegistry)) ); } function testVerifyValidSignature() public view { - bool result = verifier.verify(_proofFor(PROPOSER, SIGNER_PRIVATE_KEY, TEST_JOURNAL), IMAGE_ID, TEST_JOURNAL); - assertTrue(result); + assertTrue(verifier.verify(_proofFor(PROPOSER, SIGNER_PRIVATE_KEY, TEST_JOURNAL), IMAGE_ID, TEST_JOURNAL)); } function testVerifyFailsWithInvalidSignature() public { From 19cdc80c014cfe20d69dcd10275d0f8916f4168b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 09:54:07 -0400 Subject: [PATCH 056/135] Refactor BaseTest.t.sol: remove redundant comments, streamline variable initialization, and enhance clarity by consolidating intermediate roots generation logic into a dedicated helper function. --- test/L1/proofs/BaseTest.t.sol | 65 ++++++++++++++++------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index 068d62dc..c8f5be20 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -12,8 +12,8 @@ import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -import { GameStatus, GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; -import { Claim, Timestamp } from "src/libraries/bridge/LibUDT.sol"; +import { GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; +import { Claim } from "src/libraries/bridge/LibUDT.sol"; // OpenZeppelin import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; @@ -30,20 +30,21 @@ import { LibClone } from "lib/solady/src/utils/LibClone.sol"; contract BaseTest is Test { using LibClone for address; - // Constants + GameType public constant AGGREGATE_VERIFIER_GAME_TYPE = GameType.wrap(621); uint256 public constant L2_CHAIN_ID = 8453; - // MUST HAVE: BLOCK_INTERVAL % INTERMEDIATE_BLOCK_INTERVAL == 0 + // AggregateVerifier expects evenly spaced intermediate roots. uint256 public constant BLOCK_INTERVAL = 100; uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 public constant INTERMEDIATE_ROOTS_COUNT = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; uint256 public constant INIT_BOND = 1 ether; uint256 public constant DELAYED_WETH_DELAY = 1 days; // Finality delay handled by the AggregateVerifier uint256 public constant FINALITY_DELAY = 0 days; - uint256 public currentL2BlockNumber = 0; + uint256 public currentL2BlockNumber; address public immutable TEE_PROVER = makeAddr("tee-prover"); address public immutable ZK_PROVER = makeAddr("zk-prover"); @@ -68,12 +69,11 @@ contract BaseTest is Test { _deployContractsAndProxies(); _initializeProxies(); - // Deploy the implementations _deployAndSetAggregateVerifier(); anchorStateRegistry.setRespectedGameType(AGGREGATE_VERIFIER_GAME_TYPE); - // Set the timestamp to after the retirement timestamp + // Games created at or before the registry's retirement timestamp are invalid. vm.warp(block.timestamp + 1); } @@ -81,38 +81,30 @@ contract BaseTest is Test { systemConfig = ISystemConfig(makeAddr("system-config")); vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.guardian, ()), abi.encode(address(this))); vm.mockCall(address(systemConfig), abi.encodeCall(ISystemConfig.paused, ()), abi.encode(false)); - // Deploy the relay anchor state registry + AnchorStateRegistry _anchorStateRegistry = new AnchorStateRegistry(FINALITY_DELAY); - // Deploy the delayed WETH DelayedWETH _delayedWETH = new DelayedWETH(DELAYED_WETH_DELAY); - // Deploy the dispute game factory DisputeGameFactory _factory = new DisputeGameFactory(); - // Deploy proxy admin proxyAdmin = new ProxyAdmin(address(this)); - // Deploy proxy for anchor state registry TransparentUpgradeableProxy anchorStateRegistryProxy = new TransparentUpgradeableProxy(address(_anchorStateRegistry), address(proxyAdmin), ""); anchorStateRegistry = AnchorStateRegistry(address(anchorStateRegistryProxy)); - // Deploy proxy for factory TransparentUpgradeableProxy factoryProxy = new TransparentUpgradeableProxy(address(_factory), address(proxyAdmin), ""); factory = DisputeGameFactory(address(factoryProxy)); - // Deploy proxy for delayed WETH TransparentUpgradeableProxy delayedWETHProxy = new TransparentUpgradeableProxy(address(_delayedWETH), address(proxyAdmin), ""); delayedWETH = DelayedWETH(payable(address(delayedWETHProxy))); - // Deploy the verifiers teeVerifier = new MockVerifier(IAnchorStateRegistry(address(anchorStateRegistry))); zkVerifier = new MockVerifier(IAnchorStateRegistry(address(anchorStateRegistry))); } function _initializeProxies() internal { - // Initialize the proxies anchorStateRegistry.initialize( systemConfig, IDisputeGameFactory(address(factory)), @@ -126,7 +118,6 @@ contract BaseTest is Test { } function _deployAndSetAggregateVerifier() internal { - // Deploy the dispute game relay implementation AggregateVerifier aggregateVerifierImpl = new AggregateVerifier( AGGREGATE_VERIFIER_GAME_TYPE, IAnchorStateRegistry(address(anchorStateRegistry)), @@ -141,14 +132,10 @@ contract BaseTest is Test { INTERMEDIATE_BLOCK_INTERVAL ); - // Set the implementation for the aggregate verifier factory.setImplementation(AGGREGATE_VERIFIER_GAME_TYPE, IDisputeGame(address(aggregateVerifierImpl))); - - // Set the bond amount for the aggregate verifier factory.setInitBond(AGGREGATE_VERIFIER_GAME_TYPE, INIT_BOND); } - // Helper function to create a game via factory function _createAggregateVerifierGame( address creator, Claim rootClaim, @@ -159,9 +146,7 @@ contract BaseTest is Test { internal returns (AggregateVerifier game) { - bytes memory intermediateRoots = - abi.encodePacked(_generateIntermediateRootsExceptLast(l2BlockNumber), rootClaim.raw()); - bytes memory extraData = abi.encodePacked(uint256(l2BlockNumber), parentAddress, intermediateRoots); + bytes memory extraData = _aggregateVerifierExtraData(rootClaim, l2BlockNumber, parentAddress); vm.deal(creator, INIT_BOND); vm.prank(creator); @@ -187,9 +172,7 @@ contract BaseTest is Test { returns (AggregateVerifier game) { IDisputeGame impl = factory.gameImpls(AGGREGATE_VERIFIER_GAME_TYPE); - bytes memory intermediateRoots = - abi.encodePacked(_generateIntermediateRootsExceptLast(l2BlockNumber), rootClaim.raw()); - bytes memory extraData = abi.encodePacked(uint256(l2BlockNumber), parentAddress, intermediateRoots); + bytes memory extraData = _aggregateVerifierExtraData(rootClaim, l2BlockNumber, parentAddress); bytes32 l1Head = blockhash(block.number - 1); address clone = address(impl).clone(abi.encodePacked(creator, rootClaim, l1Head, extraData)); vm.deal(creator, INIT_BOND); @@ -217,23 +200,35 @@ contract BaseTest is Test { view returns (bytes memory) { - // Use the previous block hash as l1OriginHash bytes32 l1OriginHash = blockhash(block.number - 1); - // Use the previous block number as l1OriginNumber uint256 l1OriginNumber = block.number - 1; - // Add some padding/signature data (65 bytes minimum for a signature) bytes memory signature = abi.encodePacked(salt, bytes32(0), bytes32(0), uint8(27)); return abi.encodePacked(uint8(proofType), l1OriginHash, l1OriginNumber, signature); } + function _aggregateVerifierExtraData( + Claim rootClaim, + uint256 l2BlockNumber, + address parentAddress + ) + internal + pure + returns (bytes memory) + { + return abi.encodePacked( + uint256(l2BlockNumber), parentAddress, _generateIntermediateRootsExceptLast(l2BlockNumber), rootClaim.raw() + ); + } + function _generateIntermediateRootsExceptLast(uint256 l2BlockNumber) internal pure returns (bytes memory) { - bytes memory intermediateRoots; + bytes memory intermediateRoots = new bytes((INTERMEDIATE_ROOTS_COUNT - 1) * 32); uint256 startingL2BlockNumber = l2BlockNumber - BLOCK_INTERVAL; - for (uint256 i = 1; i < BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; i++) { - intermediateRoots = abi.encodePacked( - intermediateRoots, keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)) - ); + for (uint256 i = 1; i < INTERMEDIATE_ROOTS_COUNT; i++) { + bytes32 root = keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)); + assembly { + mstore(add(add(intermediateRoots, 0x20), mul(sub(i, 1), 0x20)), root) + } } return intermediateRoots; } From f547ea66085ef80e6b0b9b6a317e803e63b9c9e5 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 10:02:17 -0400 Subject: [PATCH 057/135] Enhance AggregateVerifierTest: integrate LibClone for address cloning, implement a new helper function to deploy AggregateVerifier clones without factory registration, and streamline the setup process in BaseTest by consolidating proxy deployments into a dedicated function. --- test/L1/proofs/AggregateVerifier.t.sol | 27 ++++++++++ test/L1/proofs/BaseTest.t.sol | 75 +++++++------------------- 2 files changed, 46 insertions(+), 56 deletions(-) diff --git a/test/L1/proofs/AggregateVerifier.t.sol b/test/L1/proofs/AggregateVerifier.t.sol index d708e4d4..7f631f30 100644 --- a/test/L1/proofs/AggregateVerifier.t.sol +++ b/test/L1/proofs/AggregateVerifier.t.sol @@ -12,9 +12,13 @@ import { Claim, Timestamp } from "src/libraries/bridge/LibUDT.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; +import { LibClone } from "lib/solady/src/utils/LibClone.sol"; + import { BaseTest } from "./BaseTest.t.sol"; contract AggregateVerifierTest is BaseTest { + using LibClone for address; + AggregateVerifier private aggregateVerifierImpl; function setUp() public override { @@ -372,6 +376,29 @@ contract AggregateVerifierTest is BaseTest { _deployAggregateVerifierWithIntervals(blockInterval, intermediateBlockInterval); } + /// @notice Clones the implementation like the factory, but skips `_finalizeGameCreation`. + function _deployAggregateVerifierCloneWithoutFactoryRegistration( + address creator, + Claim rootClaim, + uint256 l2BlockNumber, + address parentAddress, + bytes memory proof + ) + private + returns (AggregateVerifier) + { + IDisputeGame impl = factory.gameImpls(AGGREGATE_VERIFIER_GAME_TYPE); + bytes memory extraData = _aggregateVerifierExtraData(rootClaim, l2BlockNumber, parentAddress); + bytes32 l1Head = blockhash(block.number - 1); + address clone = address(impl).clone(abi.encodePacked(creator, rootClaim, l1Head, extraData)); + + vm.deal(creator, INIT_BOND); + vm.prank(creator); + AggregateVerifier(payable(clone)).initializeWithInitData{ value: INIT_BOND }(proof); + + return AggregateVerifier(payable(clone)); + } + function _deployAggregateVerifierWithIntervals( uint256 blockInterval, uint256 intermediateBlockInterval diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index c8f5be20..9354d3eb 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; import { Test } from "lib/forge-std/src/Test.sol"; -// Optimism import { AnchorStateRegistry } from "src/L1/proofs/AnchorStateRegistry.sol"; import { DelayedWETH } from "src/L1/proofs/DelayedWETH.sol"; import { DisputeGameFactory } from "src/L1/proofs/DisputeGameFactory.sol"; @@ -15,7 +14,6 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { GameType, Hash, Proposal } from "src/libraries/bridge/Types.sol"; import { Claim } from "src/libraries/bridge/LibUDT.sol"; -// OpenZeppelin import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { TransparentUpgradeableProxy @@ -26,18 +24,14 @@ import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; import { MockVerifier } from "test/mocks/MockVerifier.sol"; -import { LibClone } from "lib/solady/src/utils/LibClone.sol"; - contract BaseTest is Test { - using LibClone for address; - GameType public constant AGGREGATE_VERIFIER_GAME_TYPE = GameType.wrap(621); uint256 public constant L2_CHAIN_ID = 8453; // AggregateVerifier expects evenly spaced intermediate roots. uint256 public constant BLOCK_INTERVAL = 100; uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; - uint256 public constant INTERMEDIATE_ROOTS_COUNT = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; + uint256 private constant INTERMEDIATE_ROOTS_COUNT = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; uint256 public constant INIT_BOND = 1 ether; uint256 public constant DELAYED_WETH_DELAY = 1 days; @@ -88,22 +82,18 @@ contract BaseTest is Test { proxyAdmin = new ProxyAdmin(address(this)); - TransparentUpgradeableProxy anchorStateRegistryProxy = - new TransparentUpgradeableProxy(address(_anchorStateRegistry), address(proxyAdmin), ""); - anchorStateRegistry = AnchorStateRegistry(address(anchorStateRegistryProxy)); - - TransparentUpgradeableProxy factoryProxy = - new TransparentUpgradeableProxy(address(_factory), address(proxyAdmin), ""); - factory = DisputeGameFactory(address(factoryProxy)); - - TransparentUpgradeableProxy delayedWETHProxy = - new TransparentUpgradeableProxy(address(_delayedWETH), address(proxyAdmin), ""); - delayedWETH = DelayedWETH(payable(address(delayedWETHProxy))); + anchorStateRegistry = AnchorStateRegistry(_deployProxy(address(_anchorStateRegistry))); + factory = DisputeGameFactory(_deployProxy(address(_factory))); + delayedWETH = DelayedWETH(payable(_deployProxy(address(_delayedWETH)))); teeVerifier = new MockVerifier(IAnchorStateRegistry(address(anchorStateRegistry))); zkVerifier = new MockVerifier(IAnchorStateRegistry(address(anchorStateRegistry))); } + function _deployProxy(address implementation) private returns (address) { + return address(new TransparentUpgradeableProxy(implementation, address(proxyAdmin), "")); + } + function _initializeProxies() internal { anchorStateRegistry.initialize( systemConfig, @@ -159,39 +149,12 @@ contract BaseTest is Test { ); } - /// @notice Clones the `AggregateVerifier` implementation with the same CWIA layout as `DisputeGameFactory`, - /// initializes it, but does not register the game in the factory (no `_finalizeGameCreation`). - function _deployAggregateVerifierCloneWithoutFactoryRegistration( - address creator, - Claim rootClaim, - uint256 l2BlockNumber, - address parentAddress, - bytes memory proof - ) - internal - returns (AggregateVerifier game) - { - IDisputeGame impl = factory.gameImpls(AGGREGATE_VERIFIER_GAME_TYPE); - bytes memory extraData = _aggregateVerifierExtraData(rootClaim, l2BlockNumber, parentAddress); - bytes32 l1Head = blockhash(block.number - 1); - address clone = address(impl).clone(abi.encodePacked(creator, rootClaim, l1Head, extraData)); - vm.deal(creator, INIT_BOND); - vm.prank(creator); - AggregateVerifier(payable(clone)).initializeWithInitData{ value: INIT_BOND }(proof); - return AggregateVerifier(payable(clone)); - } - function _provideProof(AggregateVerifier game, address prover, bytes memory proofBytes) internal { vm.prank(prover); game.verifyProposalProof(proofBytes); } - /// @notice Generates a properly formatted proof for testing. - /// @dev The proof format is: l1OriginHash (32) + l1OriginNumber (32) + additional data. - /// Since MockVerifier always returns true, we just need the correct structure. - /// @param salt A salt to make proofs unique. - /// @param proofType The type of proof to generate. - /// @return proof The formatted proof bytes. + /// @dev Encodes proofType || l1OriginHash || l1OriginNumber || mock verifier payload. function _generateProof( bytes memory salt, AggregateVerifier.ProofType proofType @@ -216,20 +179,20 @@ contract BaseTest is Test { pure returns (bytes memory) { - return abi.encodePacked( - uint256(l2BlockNumber), parentAddress, _generateIntermediateRootsExceptLast(l2BlockNumber), rootClaim.raw() - ); + return + abi.encodePacked( + uint256(l2BlockNumber), parentAddress, _generateIntermediateRoots(l2BlockNumber, rootClaim) + ); } - function _generateIntermediateRootsExceptLast(uint256 l2BlockNumber) internal pure returns (bytes memory) { - bytes memory intermediateRoots = new bytes((INTERMEDIATE_ROOTS_COUNT - 1) * 32); + function _generateIntermediateRoots(uint256 l2BlockNumber, Claim rootClaim) private pure returns (bytes memory) { + bytes memory intermediateRoots = ""; uint256 startingL2BlockNumber = l2BlockNumber - BLOCK_INTERVAL; for (uint256 i = 1; i < INTERMEDIATE_ROOTS_COUNT; i++) { - bytes32 root = keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)); - assembly { - mstore(add(add(intermediateRoots, 0x20), mul(sub(i, 1), 0x20)), root) - } + intermediateRoots = abi.encodePacked( + intermediateRoots, keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)) + ); } - return intermediateRoots; + return abi.encodePacked(intermediateRoots, rootClaim.raw()); } } From e0d834c7173a0af4e9afca7b2c6896f7c74dca01 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 10:07:48 -0400 Subject: [PATCH 058/135] Update BaseTest.t.sol: upgrade Solidity version to 0.8.15, change variable visibility to internal for better encapsulation, and optimize intermediate roots generation logic for improved performance and clarity. --- test/L1/proofs/BaseTest.t.sol | 65 ++++++++++++++++------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/test/L1/proofs/BaseTest.t.sol b/test/L1/proofs/BaseTest.t.sol index 9354d3eb..50184d9a 100644 --- a/test/L1/proofs/BaseTest.t.sol +++ b/test/L1/proofs/BaseTest.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.15; import { Test } from "lib/forge-std/src/Test.sol"; @@ -25,39 +25,38 @@ import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; import { MockVerifier } from "test/mocks/MockVerifier.sol"; contract BaseTest is Test { - GameType public constant AGGREGATE_VERIFIER_GAME_TYPE = GameType.wrap(621); - uint256 public constant L2_CHAIN_ID = 8453; + GameType internal constant AGGREGATE_VERIFIER_GAME_TYPE = GameType.wrap(621); + uint256 internal constant L2_CHAIN_ID = 8453; // AggregateVerifier expects evenly spaced intermediate roots. - uint256 public constant BLOCK_INTERVAL = 100; - uint256 public constant INTERMEDIATE_BLOCK_INTERVAL = 10; + uint256 internal constant BLOCK_INTERVAL = 100; + uint256 internal constant INTERMEDIATE_BLOCK_INTERVAL = 10; uint256 private constant INTERMEDIATE_ROOTS_COUNT = BLOCK_INTERVAL / INTERMEDIATE_BLOCK_INTERVAL; - uint256 public constant INIT_BOND = 1 ether; - uint256 public constant DELAYED_WETH_DELAY = 1 days; + uint256 internal constant INIT_BOND = 1 ether; + uint256 internal constant DELAYED_WETH_DELAY = 1 days; // Finality delay handled by the AggregateVerifier - uint256 public constant FINALITY_DELAY = 0 days; + uint256 internal constant FINALITY_DELAY = 0 days; - uint256 public currentL2BlockNumber; + uint256 internal currentL2BlockNumber; - address public immutable TEE_PROVER = makeAddr("tee-prover"); - address public immutable ZK_PROVER = makeAddr("zk-prover"); - address public immutable ATTACKER = makeAddr("attacker"); + address internal immutable TEE_PROVER = makeAddr("tee-prover"); + address internal immutable ZK_PROVER = makeAddr("zk-prover"); - bytes32 public immutable TEE_IMAGE_HASH = keccak256("tee-image"); - bytes32 public immutable ZK_RANGE_HASH = keccak256("zk-range"); - bytes32 public immutable ZK_AGGREGATE_HASH = keccak256("zk-aggregate"); - bytes32 public immutable CONFIG_HASH = keccak256("config"); + bytes32 internal immutable TEE_IMAGE_HASH = keccak256("tee-image"); + bytes32 internal immutable ZK_RANGE_HASH = keccak256("zk-range"); + bytes32 internal immutable ZK_AGGREGATE_HASH = keccak256("zk-aggregate"); + bytes32 internal immutable CONFIG_HASH = keccak256("config"); - ProxyAdmin public proxyAdmin; - ISystemConfig public systemConfig; + ProxyAdmin internal proxyAdmin; + ISystemConfig internal systemConfig; - DisputeGameFactory public factory; - AnchorStateRegistry public anchorStateRegistry; - DelayedWETH public delayedWETH; + DisputeGameFactory internal factory; + AnchorStateRegistry internal anchorStateRegistry; + DelayedWETH internal delayedWETH; - MockVerifier public teeVerifier; - MockVerifier public zkVerifier; + MockVerifier internal teeVerifier; + MockVerifier internal zkVerifier; function setUp() public virtual { _deployContractsAndProxies(); @@ -163,11 +162,10 @@ contract BaseTest is Test { view returns (bytes memory) { - bytes32 l1OriginHash = blockhash(block.number - 1); uint256 l1OriginNumber = block.number - 1; - bytes memory signature = abi.encodePacked(salt, bytes32(0), bytes32(0), uint8(27)); + bytes32 l1OriginHash = blockhash(l1OriginNumber); - return abi.encodePacked(uint8(proofType), l1OriginHash, l1OriginNumber, signature); + return abi.encodePacked(uint8(proofType), l1OriginHash, l1OriginNumber, salt, bytes32(0), bytes32(0), uint8(27)); } function _aggregateVerifierExtraData( @@ -179,20 +177,17 @@ contract BaseTest is Test { pure returns (bytes memory) { - return - abi.encodePacked( - uint256(l2BlockNumber), parentAddress, _generateIntermediateRoots(l2BlockNumber, rootClaim) - ); + return abi.encodePacked(l2BlockNumber, parentAddress, _generateIntermediateRoots(l2BlockNumber, rootClaim)); } function _generateIntermediateRoots(uint256 l2BlockNumber, Claim rootClaim) private pure returns (bytes memory) { - bytes memory intermediateRoots = ""; + bytes32[] memory intermediateRoots = new bytes32[](INTERMEDIATE_ROOTS_COUNT); uint256 startingL2BlockNumber = l2BlockNumber - BLOCK_INTERVAL; for (uint256 i = 1; i < INTERMEDIATE_ROOTS_COUNT; i++) { - intermediateRoots = abi.encodePacked( - intermediateRoots, keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)) - ); + intermediateRoots[i - 1] = keccak256(abi.encode(startingL2BlockNumber + INTERMEDIATE_BLOCK_INTERVAL * i)); } - return abi.encodePacked(intermediateRoots, rootClaim.raw()); + intermediateRoots[INTERMEDIATE_ROOTS_COUNT - 1] = rootClaim.raw(); + + return abi.encodePacked(intermediateRoots); } } From 25736e0669b275ceccede6b98c88812e47fef589 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 10:08:06 -0400 Subject: [PATCH 059/135] fmt --- test/L1/proofs/TEEVerifier.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/L1/proofs/TEEVerifier.t.sol b/test/L1/proofs/TEEVerifier.t.sol index 86707ddb..56f6968c 100644 --- a/test/L1/proofs/TEEVerifier.t.sol +++ b/test/L1/proofs/TEEVerifier.t.sol @@ -61,8 +61,7 @@ contract TEEVerifierTest is Test { address(impl), makeAddr("proxy-admin"), abi.encodeCall( - TEEProverRegistry.initialize, - (address(this), address(this), new address[](0), TEST_GAME_TYPE) + TEEProverRegistry.initialize, (address(this), address(this), new address[](0), TEST_GAME_TYPE) ) ); From 8ef57aa07804bec8baa4e29796d5084e700478d3 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 16:38:30 -0400 Subject: [PATCH 060/135] Refactor BalanceTrackerTest: streamline variable initialization by using constants for system addresses and target balances, enhance clarity in setup and test functions, and consolidate balance assertions into dedicated helper functions. --- test/L1/BalanceTracker.t.sol | 203 ++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 101 deletions(-) diff --git a/test/L1/BalanceTracker.t.sol b/test/L1/BalanceTracker.t.sol index 94af8c1c..3d77df12 100644 --- a/test/L1/BalanceTracker.t.sol +++ b/test/L1/BalanceTracker.t.sol @@ -17,25 +17,24 @@ contract BalanceTrackerTest is Test { uint256 constant MAX_SYSTEM_ADDRESS_COUNT = 20; uint256 constant INITIAL_BALANCE_TRACKER_BALANCE = 2_000 ether; + uint256 constant BATCH_SENDER_TARGET_BALANCE = 1_000 ether; + uint256 constant L2_OUTPUT_PROPOSER_TARGET_BALANCE = 100 ether; + + address payable constant L1_STANDARD_BRIDGE = payable(address(1000)); + address payable constant PROFIT_WALLET = payable(address(1001)); + address payable constant BATCH_SENDER = payable(address(1002)); + address payable constant L2_OUTPUT_PROPOSER = payable(address(1003)); + address constant PROXY_ADMIN_OWNER = address(2048); - Proxy balanceTrackerProxy; - BalanceTracker balanceTrackerImplementation; BalanceTracker balanceTracker; - address payable l1StandardBridge = payable(address(1000)); - address payable profitWallet = payable(address(1001)); - address payable batchSender = payable(address(1002)); - address payable l2OutputProposer = payable(address(1003)); - uint256 batchSenderTargetBalance = 1_000 ether; - uint256 l2OutputProposerTargetBalance = 100 ether; - address payable[] systemAddresses = [batchSender, l2OutputProposer]; - uint256[] targetBalances = [batchSenderTargetBalance, l2OutputProposerTargetBalance]; - address proxyAdminOwner = address(2048); + address payable[] systemAddresses = [BATCH_SENDER, L2_OUTPUT_PROPOSER]; + uint256[] targetBalances = [BATCH_SENDER_TARGET_BALANCE, L2_OUTPUT_PROPOSER_TARGET_BALANCE]; function setUp() public { - balanceTrackerImplementation = new BalanceTracker(profitWallet); - balanceTrackerProxy = new Proxy(proxyAdminOwner); - vm.prank(proxyAdminOwner); + BalanceTracker balanceTrackerImplementation = new BalanceTracker(PROFIT_WALLET); + Proxy balanceTrackerProxy = new Proxy(PROXY_ADMIN_OWNER); + vm.prank(PROXY_ADMIN_OWNER); balanceTrackerProxy.upgradeTo(address(balanceTrackerImplementation)); balanceTracker = BalanceTracker(payable(address(balanceTrackerProxy))); } @@ -46,10 +45,10 @@ contract BalanceTrackerTest is Test { } function test_constructor_success() external { - balanceTracker = new BalanceTracker(profitWallet); + BalanceTracker tracker = new BalanceTracker(PROFIT_WALLET); - assertEq(balanceTracker.MAX_SYSTEM_ADDRESS_COUNT(), MAX_SYSTEM_ADDRESS_COUNT); - assertEq(balanceTracker.PROFIT_WALLET(), profitWallet); + assertEq(tracker.MAX_SYSTEM_ADDRESS_COUNT(), MAX_SYSTEM_ADDRESS_COUNT); + assertEq(tracker.PROFIT_WALLET(), PROFIT_WALLET); } function test_initializer_fail_systemAddresses_zeroLength() external { @@ -59,12 +58,11 @@ contract BalanceTrackerTest is Test { } function test_initializer_fail_systemAddresses_greaterThanMaxLength() external { - for (; systemAddresses.length <= balanceTracker.MAX_SYSTEM_ADDRESS_COUNT();) { - systemAddresses.push(payable(address(0))); - } + address payable[] memory oversizedSystemAddresses = new address payable[](MAX_SYSTEM_ADDRESS_COUNT + 1); + uint256[] memory oversizedTargetBalances = new uint256[](MAX_SYSTEM_ADDRESS_COUNT + 1); vm.expectRevert("BalanceTracker: systemAddresses cannot have a length greater than 20"); - balanceTracker.initialize(systemAddresses, targetBalances); + balanceTracker.initialize(oversizedSystemAddresses, oversizedTargetBalances); } function test_initializer_fail_systemAddresses_lengthNotEqualToTargetBalancesLength() external { @@ -99,24 +97,20 @@ contract BalanceTrackerTest is Test { function test_processFees_success_cannotBeReentered() external { vm.deal(address(balanceTracker), INITIAL_BALANCE_TRACKER_BALANCE); - uint256 expectedProfitWalletBalance = INITIAL_BALANCE_TRACKER_BALANCE - l2OutputProposerTargetBalance; + uint256 expectedProfitWalletBalance = INITIAL_BALANCE_TRACKER_BALANCE - L2_OUTPUT_PROPOSER_TARGET_BALANCE; address payable reentrancySystemAddress = payable(address(new ReenterProcessFees())); systemAddresses[0] = reentrancySystemAddress; balanceTracker.initialize(systemAddresses, targetBalances); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(reentrancySystemAddress, false, batchSenderTargetBalance, batchSenderTargetBalance); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(l2OutputProposer, true, l2OutputProposerTargetBalance, l2OutputProposerTargetBalance); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit SentProfit(profitWallet, true, expectedProfitWalletBalance); + _expectProcessedFunds(reentrancySystemAddress, false, BATCH_SENDER_TARGET_BALANCE, BATCH_SENDER_TARGET_BALANCE); + _expectProcessedFunds( + L2_OUTPUT_PROPOSER, true, L2_OUTPUT_PROPOSER_TARGET_BALANCE, L2_OUTPUT_PROPOSER_TARGET_BALANCE + ); + _expectSentProfit(expectedProfitWalletBalance); balanceTracker.processFees(); - assertEq(address(balanceTracker).balance, 0); - assertEq(profitWallet.balance, expectedProfitWalletBalance); - assertEq(batchSender.balance, 0); - assertEq(l2OutputProposer.balance, l2OutputProposerTargetBalance); + _assertBalances(0, expectedProfitWalletBalance, 0, L2_OUTPUT_PROPOSER_TARGET_BALANCE); } function test_processFees_fail_whenNotInitialized() external { @@ -127,131 +121,138 @@ contract BalanceTrackerTest is Test { function test_processFees_success_continuesWhenSystemAddressReverts() external { vm.deal(address(balanceTracker), INITIAL_BALANCE_TRACKER_BALANCE); - uint256 expectedProfitWalletBalance = INITIAL_BALANCE_TRACKER_BALANCE - l2OutputProposerTargetBalance; + uint256 expectedProfitWalletBalance = INITIAL_BALANCE_TRACKER_BALANCE - L2_OUTPUT_PROPOSER_TARGET_BALANCE; balanceTracker.initialize(systemAddresses, targetBalances); - vm.mockCallRevert(batchSender, bytes(""), abi.encode("revert message")); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(batchSender, false, batchSenderTargetBalance, batchSenderTargetBalance); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(l2OutputProposer, true, l2OutputProposerTargetBalance, l2OutputProposerTargetBalance); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit SentProfit(profitWallet, true, expectedProfitWalletBalance); + vm.mockCallRevert(BATCH_SENDER, bytes(""), abi.encode("revert message")); + _expectProcessedFunds(BATCH_SENDER, false, BATCH_SENDER_TARGET_BALANCE, BATCH_SENDER_TARGET_BALANCE); + _expectProcessedFunds( + L2_OUTPUT_PROPOSER, true, L2_OUTPUT_PROPOSER_TARGET_BALANCE, L2_OUTPUT_PROPOSER_TARGET_BALANCE + ); + _expectSentProfit(expectedProfitWalletBalance); balanceTracker.processFees(); - assertEq(address(balanceTracker).balance, 0); - assertEq(profitWallet.balance, expectedProfitWalletBalance); - assertEq(batchSender.balance, 0); - assertEq(l2OutputProposer.balance, l2OutputProposerTargetBalance); + _assertBalances(0, expectedProfitWalletBalance, 0, L2_OUTPUT_PROPOSER_TARGET_BALANCE); } function test_processFees_success_fundsSystemAddresses() external { vm.deal(address(balanceTracker), INITIAL_BALANCE_TRACKER_BALANCE); uint256 expectedProfitWalletBalance = - INITIAL_BALANCE_TRACKER_BALANCE - batchSenderTargetBalance - l2OutputProposerTargetBalance; + INITIAL_BALANCE_TRACKER_BALANCE - BATCH_SENDER_TARGET_BALANCE - L2_OUTPUT_PROPOSER_TARGET_BALANCE; balanceTracker.initialize(systemAddresses, targetBalances); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(batchSender, true, batchSenderTargetBalance, batchSenderTargetBalance); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(l2OutputProposer, true, l2OutputProposerTargetBalance, l2OutputProposerTargetBalance); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit SentProfit(profitWallet, true, expectedProfitWalletBalance); + _expectProcessedFunds(BATCH_SENDER, true, BATCH_SENDER_TARGET_BALANCE, BATCH_SENDER_TARGET_BALANCE); + _expectProcessedFunds( + L2_OUTPUT_PROPOSER, true, L2_OUTPUT_PROPOSER_TARGET_BALANCE, L2_OUTPUT_PROPOSER_TARGET_BALANCE + ); + _expectSentProfit(expectedProfitWalletBalance); balanceTracker.processFees(); - assertEq(address(balanceTracker).balance, 0); - assertEq(profitWallet.balance, expectedProfitWalletBalance); - assertEq(batchSender.balance, batchSenderTargetBalance); - assertEq(l2OutputProposer.balance, l2OutputProposerTargetBalance); + _assertBalances(0, expectedProfitWalletBalance, BATCH_SENDER_TARGET_BALANCE, L2_OUTPUT_PROPOSER_TARGET_BALANCE); } function test_processFees_success_noFunds() external { balanceTracker.initialize(systemAddresses, targetBalances); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(batchSender, true, batchSenderTargetBalance, 0); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(l2OutputProposer, true, l2OutputProposerTargetBalance, 0); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit SentProfit(profitWallet, true, 0); + _expectProcessedFunds(BATCH_SENDER, true, BATCH_SENDER_TARGET_BALANCE, 0); + _expectProcessedFunds(L2_OUTPUT_PROPOSER, true, L2_OUTPUT_PROPOSER_TARGET_BALANCE, 0); + _expectSentProfit(0); balanceTracker.processFees(); - assertEq(address(balanceTracker).balance, 0); - assertEq(profitWallet.balance, 0); - assertEq(batchSender.balance, 0); - assertEq(l2OutputProposer.balance, 0); + _assertBalances(0, 0, 0, 0); } function test_processFees_success_partialFunds() external { uint256 partialBalanceTrackerBalance = INITIAL_BALANCE_TRACKER_BALANCE / 3; vm.deal(address(balanceTracker), partialBalanceTrackerBalance); balanceTracker.initialize(systemAddresses, targetBalances); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(batchSender, true, batchSenderTargetBalance, partialBalanceTrackerBalance); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(l2OutputProposer, true, l2OutputProposerTargetBalance, 0); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit SentProfit(profitWallet, true, 0); + _expectProcessedFunds(BATCH_SENDER, true, BATCH_SENDER_TARGET_BALANCE, partialBalanceTrackerBalance); + _expectProcessedFunds(L2_OUTPUT_PROPOSER, true, L2_OUTPUT_PROPOSER_TARGET_BALANCE, 0); + _expectSentProfit(0); balanceTracker.processFees(); - assertEq(address(balanceTracker).balance, 0); - assertEq(profitWallet.balance, 0); - assertEq(batchSender.balance, partialBalanceTrackerBalance); - assertEq(l2OutputProposer.balance, 0); + _assertBalances(0, 0, partialBalanceTrackerBalance, 0); } function test_processFees_success_skipsAddressesAtTargetBalance() external { vm.deal(address(balanceTracker), INITIAL_BALANCE_TRACKER_BALANCE); - vm.deal(batchSender, batchSenderTargetBalance); - vm.deal(l2OutputProposer, l2OutputProposerTargetBalance); + vm.deal(BATCH_SENDER, BATCH_SENDER_TARGET_BALANCE); + vm.deal(L2_OUTPUT_PROPOSER, L2_OUTPUT_PROPOSER_TARGET_BALANCE); balanceTracker.initialize(systemAddresses, targetBalances); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(batchSender, false, 0, 0); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ProcessedFunds(l2OutputProposer, false, 0, 0); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit SentProfit(profitWallet, true, INITIAL_BALANCE_TRACKER_BALANCE); + _expectProcessedFunds(BATCH_SENDER, false, 0, 0); + _expectProcessedFunds(L2_OUTPUT_PROPOSER, false, 0, 0); + _expectSentProfit(INITIAL_BALANCE_TRACKER_BALANCE); balanceTracker.processFees(); - assertEq(address(balanceTracker).balance, 0); - assertEq(profitWallet.balance, INITIAL_BALANCE_TRACKER_BALANCE); - assertEq(batchSender.balance, batchSenderTargetBalance); - assertEq(l2OutputProposer.balance, l2OutputProposerTargetBalance); + _assertBalances( + 0, INITIAL_BALANCE_TRACKER_BALANCE, BATCH_SENDER_TARGET_BALANCE, L2_OUTPUT_PROPOSER_TARGET_BALANCE + ); } function test_processFees_success_maximumSystemAddresses() external { vm.deal(address(balanceTracker), INITIAL_BALANCE_TRACKER_BALANCE); - delete systemAddresses; - delete targetBalances; - for (uint256 i = 0; i < balanceTracker.MAX_SYSTEM_ADDRESS_COUNT(); i++) { - // forge-lint: disable-next-line(unsafe-typecast) - systemAddresses.push(payable(address(uint160(i + 100)))); - targetBalances.push(l2OutputProposerTargetBalance); + address payable[] memory maxSystemAddresses = new address payable[](MAX_SYSTEM_ADDRESS_COUNT); + uint256[] memory maxTargetBalances = new uint256[](MAX_SYSTEM_ADDRESS_COUNT); + for (uint256 i = 0; i < MAX_SYSTEM_ADDRESS_COUNT; i++) { + maxSystemAddresses[i] = payable(vm.addr(i + 100)); + maxTargetBalances[i] = L2_OUTPUT_PROPOSER_TARGET_BALANCE; } - balanceTracker.initialize(systemAddresses, targetBalances); + balanceTracker.initialize(maxSystemAddresses, maxTargetBalances); balanceTracker.processFees(); assertEq(address(balanceTracker).balance, 0); - for (uint256 i = 0; i < balanceTracker.MAX_SYSTEM_ADDRESS_COUNT(); i++) { - assertEq(systemAddresses[i].balance, l2OutputProposerTargetBalance); + for (uint256 i = 0; i < MAX_SYSTEM_ADDRESS_COUNT; i++) { + assertEq(maxSystemAddresses[i].balance, L2_OUTPUT_PROPOSER_TARGET_BALANCE); } - assertEq(profitWallet.balance, 0); + assertEq(PROFIT_WALLET.balance, 0); } function test_receive_success() external { uint256 value = 100; - vm.deal(l1StandardBridge, value); + vm.deal(L1_STANDARD_BRIDGE, value); - vm.prank(l1StandardBridge); - vm.expectEmit(true, true, true, true, address(balanceTracker)); - emit ReceivedFunds(l1StandardBridge, value); + vm.prank(L1_STANDARD_BRIDGE); + vm.expectEmit(address(balanceTracker)); + emit ReceivedFunds(L1_STANDARD_BRIDGE, value); (bool success,) = payable(address(balanceTracker)).call{ value: value }(""); assertTrue(success); assertEq(address(balanceTracker).balance, value); } + + function _expectProcessedFunds( + address systemAddress, + bool success, + uint256 balanceNeeded, + uint256 balanceSent + ) + internal + { + vm.expectEmit(address(balanceTracker)); + emit ProcessedFunds(systemAddress, success, balanceNeeded, balanceSent); + } + + function _expectSentProfit(uint256 balanceSent) internal { + vm.expectEmit(address(balanceTracker)); + emit SentProfit(PROFIT_WALLET, true, balanceSent); + } + + function _assertBalances( + uint256 trackerBalance, + uint256 profitWalletBalance, + uint256 batchSenderBalance, + uint256 l2OutputProposerBalance + ) + internal + view + { + assertEq(address(balanceTracker).balance, trackerBalance); + assertEq(PROFIT_WALLET.balance, profitWalletBalance); + assertEq(BATCH_SENDER.balance, batchSenderBalance); + assertEq(L2_OUTPUT_PROPOSER.balance, l2OutputProposerBalance); + } } From df12f6b7c368061a4bd941a54aa277a79c56e1cd Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 16:56:03 -0400 Subject: [PATCH 061/135] Refactor ETHLockbox tests: remove unused error declaration, streamline portal and lockbox mock setups into dedicated helper functions, and enhance initialization logic for improved clarity and maintainability. --- test/L1/ETHLockbox.t.sol | 132 +++++++++++++++------------------------ 1 file changed, 51 insertions(+), 81 deletions(-) diff --git a/test/L1/ETHLockbox.t.sol b/test/L1/ETHLockbox.t.sol index 40c042a2..1f96d859 100644 --- a/test/L1/ETHLockbox.t.sol +++ b/test/L1/ETHLockbox.t.sol @@ -21,8 +21,6 @@ import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; /// @title ETHLockbox_TestInit /// @notice Base contract that sets up the testing environment for ETHLockbox tests. abstract contract ETHLockbox_TestInit is CommonTest { - error InvalidInitialization(); - event ETHLocked(IOptimismPortal2 indexed portal, uint256 amount); event ETHUnlocked(IOptimismPortal2 indexed portal, uint256 amount); event PortalAuthorized(IOptimismPortal2 indexed portal); @@ -41,6 +39,28 @@ abstract contract ETHLockbox_TestInit is CommonTest { // If the ETHLockbox system feature is not enabled, skip these tests. skipIfSysFeatureDisabled(Features.ETH_LOCKBOX); } + + function _mockPortalSharedOwnerAndSuperchainConfig(IOptimismPortal2 _portal) internal { + vm.mockCall( + address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + vm.mockCall( + address(_portal), abi.encodeCall(IOptimismPortal2.superchainConfig, ()), abi.encode(superchainConfig) + ); + } + + function _authorizePortalIfNeeded(IOptimismPortal2 _portal) internal { + if (!ethLockbox.authorizedPortals(_portal)) { + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(_portal); + } + } + + function _mockLockboxSharedOwner(address _lockbox) internal { + vm.mockCall( + address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + } } /// @title ETHLockbox_Version_Test @@ -56,6 +76,14 @@ contract ETHLockbox_Version_Test is ETHLockbox_TestInit { /// @title ETHLockbox_Initialize_Test /// @notice Test contract for the initialize function. contract ETHLockbox_Initialize_Test is ETHLockbox_TestInit { + StorageSlot internal initializedSlot; + + function setUp() public override { + super.setUp(); + + initializedSlot = ForgeArtifacts.getSlot("ETHLockbox", "_initialized"); + } + /// @notice Tests the superchain config was correctly set during initialization. function test_initialize_succeeds() public view { assertEq(address(ethLockbox.systemConfig().superchainConfig()), address(superchainConfig)); @@ -67,14 +95,9 @@ contract ETHLockbox_Initialize_Test is ETHLockbox_TestInit { /// but confirms that the initValue is not incremented incorrectly if an upgrade /// function is not present. function test_initialize_correctInitializerValue_succeeds() public view { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("ETHLockbox", "_initialized"); - - // Get the initializer value. - bytes32 slotVal = vm.load(address(ethLockbox), bytes32(slot.slot)); + bytes32 slotVal = vm.load(address(ethLockbox), bytes32(initializedSlot.slot)); uint8 val = uint8(uint256(slotVal) & 0xFF); - // Assert that the initializer value matches the expected value. assertEq(val, ethLockbox.initVersion()); } @@ -85,11 +108,8 @@ contract ETHLockbox_Initialize_Test is ETHLockbox_TestInit { // Prank as the not ProxyAdmin or ProxyAdmin owner. vm.assume(_sender != address(proxyAdmin) && _sender != proxyAdminOwner); - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("ETHLockbox", "_initialized"); - // Set the initialized slot to 0. - vm.store(address(ethLockbox), bytes32(slot.slot), bytes32(0)); + vm.store(address(ethLockbox), bytes32(initializedSlot.slot), bytes32(0)); // Expect the revert with `ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner` selector vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); @@ -183,10 +203,9 @@ contract ETHLockbox_AuthorizePortal_Test is ETHLockbox_TestInit { /// @notice Tests the `authorizePortal` function succeeds using the `optimismPortal2` address /// as the portal. function test_authorizePortal_succeeds() public { - // Calculate the correct storage slot for the mapping value - bytes32 mappingSlot = bytes32(uint256(1)); // position on the layout + StorageSlot memory authorizedPortalsSlot = ForgeArtifacts.getSlot("ETHLockbox", "authorizedPortals"); address key = address(optimismPortal2); - bytes32 slot = keccak256(abi.encode(key, mappingSlot)); + bytes32 slot = keccak256(abi.encode(key, bytes32(authorizedPortalsSlot.slot))); // Reset the authorization status to false vm.store(address(ethLockbox), slot, bytes32(0)); @@ -203,21 +222,11 @@ contract ETHLockbox_AuthorizePortal_Test is ETHLockbox_TestInit { assertTrue(ethLockbox.authorizedPortals(optimismPortal2)); } - /// @notice Tests the `authorizeLockbox` function succeeds + /// @notice Tests the `authorizePortal` function succeeds function testFuzz_authorizePortal_succeeds(IOptimismPortal2 _portal) public { assumeNotForgeAddress(address(_portal)); - // Mock the admin owner of the portal to be the same as the current lockbox proxy admin - // owner - vm.mockCall( - address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) - ); - - // Mock the SuperchainConfig on the portal to be the same as the SuperchainConfig on the - // Lockbox. - vm.mockCall( - address(_portal), abi.encodeCall(IOptimismPortal2.superchainConfig, ()), abi.encode(superchainConfig) - ); + _mockPortalSharedOwnerAndSuperchainConfig(_portal); // Expect the `PortalAuthorized` event to be emitted vm.expectEmit(address(ethLockbox)); @@ -245,11 +254,7 @@ contract ETHLockbox_ReceiveLiquidity_Test is ETHLockbox_TestInit { // Deal the value to the lockbox deal(address(_lockbox), _value); - // Mock the admin owner of the lockbox to be the same as the current lockbox proxy admin - // owner - vm.mockCall( - address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) - ); + _mockLockboxSharedOwner(_lockbox); // Authorize the lockbox if needed if (!ethLockbox.authorizedLockboxes(IETHLockbox(_lockbox))) { @@ -323,23 +328,10 @@ contract ETHLockbox_LockETH_Test is ETHLockbox_TestInit { assumeNotForgeAddress(address(_portal)); vm.assume(address(_portal) != address(ethLockbox)); - // Mock the admin owner of the portal to be the same as the current lockbox proxy admin - // owner - vm.mockCall( - address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) - ); - - // Mock the SuperchainConfig on the portal to be the same as the SuperchainConfig on the - // lockbox. - vm.mockCall( - address(_portal), abi.encodeCall(IOptimismPortal2.superchainConfig, ()), abi.encode(superchainConfig) - ); + _mockPortalSharedOwnerAndSuperchainConfig(_portal); // Set the portal as an authorized portal if needed - if (!ethLockbox.authorizedPortals(_portal)) { - vm.prank(proxyAdminOwner); - ethLockbox.authorizePortal(_portal); - } + _authorizePortalIfNeeded(_portal); // Deal the ETH amount to the portal vm.deal(address(_portal), _amount); @@ -450,49 +442,41 @@ contract ETHLockbox_UnlockETH_Test is ETHLockbox_TestInit { /// @notice Tests the ETH is correctly unlocked when the caller is an authorized portal. function testFuzz_unlockETH_multiplePortals_succeeds(IOptimismPortal2 _portal, uint256 _value) public { + // Since on the fork the `_portal` fuzzed address doesn't exist, we skip the test + if (isForkTest()) vm.skip(true); assumeNotForgeAddress(address(_portal)); + vm.assume(address(_portal) != address(ethLockbox)); - // Mock the admin owner of the portal to be the same as the current lockbox proxy admin - // owner - vm.mockCall( - address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) - ); - - // Mock the SuperchainConfig on the portal to be the same as the SuperchainConfig on the - // lockbox. - + _mockPortalSharedOwnerAndSuperchainConfig(_portal); vm.mockCall( - address(_portal), abi.encodeCall(IOptimismPortal2.superchainConfig, ()), abi.encode(superchainConfig) + address(_portal), abi.encodeCall(IOptimismPortal2.l2Sender, ()), abi.encode(Constants.DEFAULT_L2_SENDER) ); // Set the portal as an authorized portal if needed - if (!ethLockbox.authorizedPortals(_portal)) { - vm.prank(proxyAdminOwner); - ethLockbox.authorizePortal(_portal); - } + _authorizePortalIfNeeded(_portal); // Deal the ETH amount to the lockbox vm.deal(address(ethLockbox), _value); // Get the balance of the portal and lockbox before the unlock to compare later on the // assertions - uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(_portal).balance; uint256 lockboxBalanceBefore = address(ethLockbox).balance; // Expect `donateETH` function to be called on Portal - vm.expectCall(address(optimismPortal2), abi.encodeCall(IOptimismPortal2.donateETH, ())); + vm.expectCall(address(_portal), abi.encodeCall(IOptimismPortal2.donateETH, ())); // Look for the emit of the `ETHUnlocked` event vm.expectEmit(address(ethLockbox)); - emit ETHUnlocked(optimismPortal2, _value); + emit ETHUnlocked(_portal, _value); // Call the `unlockETH` function with the portal - vm.prank(address(optimismPortal2)); + vm.prank(address(_portal)); ethLockbox.unlockETH(_value); // Assert the portal's balance increased and the lockbox's balance decreased by the amount // unlocked - assertEq(address(optimismPortal2).balance, portalBalanceBefore + _value); + assertEq(address(_portal).balance, portalBalanceBefore + _value); assertEq(address(ethLockbox).balance, lockboxBalanceBefore - _value); } } @@ -532,11 +516,7 @@ contract ETHLockbox_AuthorizeLockbox_Test is ETHLockbox_TestInit { function testFuzz_authorizeLockbox_succeeds(address _lockbox) public { assumeNotForgeAddress(_lockbox); - // Mock the admin owner of the lockbox to be the same as the current lockbox proxy admin - // owner - vm.mockCall( - address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) - ); + _mockLockboxSharedOwner(_lockbox); // Expect the `LockboxAuthorized` event to be emitted vm.expectEmit(address(ethLockbox)); @@ -611,16 +591,6 @@ contract ETHLockbox_MigrateLiquidity_Test is ETHLockbox_TestInit { vm.prank(proxyAdminOwner); IETHLockbox(destinationLockbox).authorizeLockbox(ethLockbox); - // Mock the calls for checks on the destination lockbox so it can receive the migration - vm.mockCall( - address(destinationLockbox), - abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), - abi.encode(proxyAdminOwner) - ); - vm.mockCall( - address(destinationLockbox), abi.encodeCall(IETHLockbox.authorizedLockboxes, (ethLockbox)), abi.encode(true) - ); - // Deal the balance to both lockboxes deal(address(ethLockbox), _originLockboxBalance); deal(address(destinationLockbox), _destinationLockboxBalance); From dfc461139af8bc5d1165ca4f46fccb1949a08ac6 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 21:28:52 -0400 Subject: [PATCH 062/135] Refactor L1CrossDomainMessenger tests: simplify setup by consolidating initialization logic, enhance clarity with internal variable visibility, and streamline assertions for the initialized value. --- test/L1/L1CrossDomainMessenger.t.sol | 206 ++++++++++++--------------- 1 file changed, 88 insertions(+), 118 deletions(-) diff --git a/test/L1/L1CrossDomainMessenger.t.sol b/test/L1/L1CrossDomainMessenger.t.sol index 2ce66cd5..814a533f 100644 --- a/test/L1/L1CrossDomainMessenger.t.sol +++ b/test/L1/L1CrossDomainMessenger.t.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; import { Reverter, GasBurner } from "test/mocks/Callers.sol"; import { stdError } from "lib/forge-std/src/StdError.sol"; -import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; +import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; // Libraries import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; @@ -41,20 +41,7 @@ contract L1CrossDomainMessenger_Encoding_Harness { /// @title L1CrossDomainMessenger_TestInit /// @notice Reusable test initialization for L1CrossDomainMessenger tests. abstract contract L1CrossDomainMessenger_TestInit is CommonTest { - /// @dev The receiver address - address recipient = address(0xabbaacdc); - - /// @dev The storage slot of the l2Sender - uint256 senderSlotIndex; - - /// @dev Encoding library harness. - L1CrossDomainMessenger_Encoding_Harness encoding; - - function setUp() public virtual override { - super.setUp(); - senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot; - encoding = new L1CrossDomainMessenger_Encoding_Harness(); - } + address internal recipient = address(0xabbaacdc); } /// @title L1CrossDomainMessenger_Constructor_Test @@ -80,6 +67,23 @@ contract L1CrossDomainMessenger_Constructor_Test is L1CrossDomainMessenger_TestI /// @title L1CrossDomainMessenger_Initialize_Test /// @notice Tests for the `initialize` function of the L1CrossDomainMessenger. contract L1CrossDomainMessenger_Initialize_Test is L1CrossDomainMessenger_TestInit { + uint256 internal initializedSlotIndex; + + function setUp() public virtual override { + super.setUp(); + initializedSlotIndex = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized").slot; + } + + function _resetInitialized() internal { + vm.store(address(l1CrossDomainMessenger), bytes32(initializedSlotIndex), bytes32(0)); + } + + function _initializedValue() internal view returns (uint8) { + bytes32 slotVal = vm.load(address(l1CrossDomainMessenger), bytes32(initializedSlotIndex)); + // L1CrossDomainMessenger stores _initialized at byte offset 20. + return uint8((uint256(slotVal) >> 20 * 8) & 0xFF); + } + /// @notice Tests that the proxy is initialized correctly. function test_initialize_succeeds() external view { assertEq(address(l1CrossDomainMessenger.systemConfig()), address(systemConfig)); @@ -94,17 +98,7 @@ contract L1CrossDomainMessenger_Initialize_Test is L1CrossDomainMessenger_TestIn /// initialization but confirms that the initValue is not incremented incorrectly if /// an upgrade function is not present. function test_initialize_correctInitializerValue_succeeds() public view { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized"); - - // Get the initializer value. - // Note that for L1CrossDomainMessenger the initialized value is stored at offset 20 so - // this test is slightly different from other similar tests. - bytes32 slotVal = vm.load(address(l1CrossDomainMessenger), bytes32(slot.slot)); - uint8 val = uint8((uint256(slotVal) >> 20 * 8) & 0xFF); - - // Assert that the initializer value matches the expected value. - assertEq(val, l1CrossDomainMessenger.initVersion()); + assertEq(_initializedValue(), l1CrossDomainMessenger.initVersion()); } /// @notice Tests that the initialize function reverts if called by a non-proxy admin or owner. @@ -113,11 +107,7 @@ contract L1CrossDomainMessenger_Initialize_Test is L1CrossDomainMessenger_TestIn // Prank as the not ProxyAdmin or ProxyAdmin owner. vm.assume(_sender != address(proxyAdmin) && _sender != proxyAdminOwner); - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized"); - - // Set the initialized slot to 0. - vm.store(address(l1CrossDomainMessenger), bytes32(slot.slot), bytes32(0)); + _resetInitialized(); // Expect the revert with `ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner` selector vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); @@ -130,11 +120,7 @@ contract L1CrossDomainMessenger_Initialize_Test is L1CrossDomainMessenger_TestIn /// @notice Fuzz test for initialize with any system config address. /// @param _systemConfig The system config address to test. function testFuzz_initialize_anySystemConfig_succeeds(address _systemConfig) external { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized"); - - // Set the initialized slot to 0. - vm.store(address(l1CrossDomainMessenger), bytes32(slot.slot), bytes32(0)); + _resetInitialized(); // Initialize with the fuzzed system config address vm.prank(address(proxyAdmin)); @@ -148,11 +134,7 @@ contract L1CrossDomainMessenger_Initialize_Test is L1CrossDomainMessenger_TestIn /// @notice Fuzz test for initialize with any portal address. /// @param _portal The portal address to test. function testFuzz_initialize_anyPortal_succeeds(address _portal) external { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized"); - - // Set the initialized slot to 0. - vm.store(address(l1CrossDomainMessenger), bytes32(slot.slot), bytes32(0)); + _resetInitialized(); // Initialize with the fuzzed portal address vm.prank(address(proxyAdmin)); @@ -219,20 +201,19 @@ contract L1CrossDomainMessenger_SendMessage_Test is L1CrossDomainMessenger_TestI /// TODO: this same test needs to be done with the legacy message type /// by setting the message version to 0 function test_sendMessage_succeeds() external { + uint32 minGasLimit = 100; + bytes memory message = hex"ff"; + uint256 nonce = l1CrossDomainMessenger.messageNonce(); + uint64 baseGas = l1CrossDomainMessenger.baseGas(message, minGasLimit); + bytes memory xDomainCalldata = + Encoding.encodeCrossDomainMessage(nonce, alice, recipient, 0, minGasLimit, message); + // deposit transaction on the optimism portal should be called vm.expectCall( address(optimismPortal2), abi.encodeCall( IOptimismPortal2.depositTransaction, - ( - Predeploys.L2_CROSS_DOMAIN_MESSENGER, - 0, - l1CrossDomainMessenger.baseGas(hex"ff", 100), - false, - Encoding.encodeCrossDomainMessage( - l1CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff" - ) - ) + (Predeploys.L2_CROSS_DOMAIN_MESSENGER, 0, baseGas, false, xDomainCalldata) ) ); @@ -243,21 +224,21 @@ contract L1CrossDomainMessenger_SendMessage_Test is L1CrossDomainMessenger_TestI Predeploys.L2_CROSS_DOMAIN_MESSENGER, 0, 0, - l1CrossDomainMessenger.baseGas(hex"ff", 100), + baseGas, false, - Encoding.encodeCrossDomainMessage(l1CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff") + xDomainCalldata ); // SentMessage event vm.expectEmit(address(l1CrossDomainMessenger)); - emit SentMessage(recipient, alice, hex"ff", l1CrossDomainMessenger.messageNonce(), 100); + emit SentMessage(recipient, alice, message, nonce, minGasLimit); // SentMessageExtension1 event vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessageExtension1(alice, 0); vm.prank(alice); - l1CrossDomainMessenger.sendMessage(recipient, hex"ff", uint32(100)); + l1CrossDomainMessenger.sendMessage(recipient, message, minGasLimit); } /// @notice Fuzz test for sendMessage with various gas limits and message data. @@ -326,7 +307,8 @@ contract L1CrossDomainMessenger_SendMessage_Test is L1CrossDomainMessenger_TestI /// but are testing functionality of the CrossDomainMessenger contract that is inherited /// from. contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_TestInit { - // Variables for reentrancy test + uint256 internal senderSlotIndex; + bool reentrancyAttacked; uint256 constant reentrancyMessageValue = 50; bytes reentrancySelector; @@ -336,7 +318,8 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes function setUp() public virtual override { super.setUp(); - // Setup for reentrancy test variables (balance setup moved to specific test) + senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot; + reentrancyTarget = address(this); reentrancySender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; reentrancySelector = abi.encodeCall(this.reinitAndReenter, ()); @@ -350,8 +333,17 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes ); } + function _setPortalL2Sender(address _sender) internal { + vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(_sender))); + } + + function _assertMessageStatus(bytes32 _hash, bool _successful, bool _failed) internal view { + assertEq(l1CrossDomainMessenger.successfulMessages(_hash), _successful); + assertEq(l1CrossDomainMessenger.failedMessages(_hash), _failed); + } + /// @notice This method will be called by the relayed message, and will attempt to reenter the - /// `relayMessage` function exactly one. + /// `relayMessage` function once. function reinitAndReenter() external payable { // only attempt the attack once if (!reentrancyAttacked) { @@ -394,8 +386,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes address target = address(0xabcd); address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; - // Set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); // Expect a revert. vm.expectRevert("CrossDomainMessenger: only version 0 or 1 messages are supported at this time"); @@ -420,8 +411,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes vm.expectCall(target, hex"1111"); - // set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); vm.prank(address(optimismPortal2)); vm.expectEmit(address(l1CrossDomainMessenger)); @@ -441,10 +431,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes hex"1111" ); - // the message hash is in the successfulMessages mapping - assert(l1CrossDomainMessenger.successfulMessages(hash)); - // it is not in the received messages mapping - assertEq(l1CrossDomainMessenger.failedMessages(hash), false); + _assertMessageStatus(hash, true, false); } /// @notice Fuzz test for relaying messages with various parameters. @@ -469,8 +456,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; - // set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); bytes32 hash = Hashing.hashCrossDomainMessage( Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, _target, 0, _minGasLimit, _message @@ -492,8 +478,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; bytes memory message = hex"1111"; - // set the value of op.l2Sender() to be the L2CrossDomainMessenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); // correctly sending as OptimismPortal but amount does not match msg.value vm.deal(address(optimismPortal2), 10 ether); @@ -511,8 +496,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; bytes memory message = hex"1111"; - // set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); // make a failed message vm.etch(target, hex"fe"); @@ -535,7 +519,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; bytes memory message = hex"1111"; - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); vm.prank(address(optimismPortal2)); vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address"); @@ -555,7 +539,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; bytes memory message = hex"1111"; - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); vm.prank(address(optimismPortal2)); vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address"); @@ -571,7 +555,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; bytes memory message = hex"1111"; - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); vm.prank(bob); vm.expectRevert("CrossDomainMessenger: message cannot be replayed"); @@ -662,8 +646,9 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes assertTrue(success, "L2CrossDomainMessenger call should not fail"); // Message should either be in the failed or successful messages mapping. - bool inFailedMessages = l2CrossDomainMessenger.failedMessages(keccak256(encoded)); - bool inSuccessfulMessages = l2CrossDomainMessenger.successfulMessages(keccak256(encoded)); + bytes32 encodedHash = keccak256(encoded); + bool inFailedMessages = l2CrossDomainMessenger.failedMessages(encodedHash); + bool inSuccessfulMessages = l2CrossDomainMessenger.successfulMessages(encodedHash); assertTrue( inFailedMessages || inSuccessfulMessages, "message should be in either failed or successful messages" ); @@ -706,6 +691,8 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes ) external { + L1CrossDomainMessenger_Encoding_Harness encoding = new L1CrossDomainMessenger_Encoding_Harness(); + // Make sure that unexpected nonces aren't being used right now. // Prevents people from forgetting to update this test if a new version is ever used. if (_version > 2) { @@ -745,7 +732,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); vm.prank(address(optimismPortal2)); l1CrossDomainMessenger.relayMessage( Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), address(0), address(0), 0, 0, hex"" @@ -755,9 +742,11 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes l1CrossDomainMessenger.xDomainMessageSender(); } - /// @notice Tests that xDomainMessageSender is never set during sendMessage. - function test_xDomainMessageSender_duringSend_reverts() external { - // XDomainMessageSender is only set during relayMessage, not sendMessage + /// @notice Tests that xDomainMessageSender remains unset after sendMessage. + function test_xDomainMessageSender_afterSend_reverts() external { + vm.prank(alice); + l1CrossDomainMessenger.sendMessage(recipient, hex"1234", 100); + vm.expectRevert("CrossDomainMessenger: xDomainMessageSender is not set"); l1CrossDomainMessenger.xDomainMessageSender(); } @@ -776,8 +765,8 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, value, 0, hex"1111" ); - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); - vm.etch(target, address(new Reverter()).code); + _setPortalL2Sender(sender); + vm.etch(target, type(Reverter).runtimeCode); vm.deal(address(optimismPortal2), value); vm.prank(address(optimismPortal2)); l1CrossDomainMessenger.relayMessage{ value: value }( @@ -791,8 +780,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes assertEq(address(l1CrossDomainMessenger).balance, value); assertEq(address(target).balance, 0); - assertEq(l1CrossDomainMessenger.successfulMessages(hash), false); - assertEq(l1CrossDomainMessenger.failedMessages(hash), true); + _assertMessageStatus(hash, false, true); vm.expectEmit(address(l1CrossDomainMessenger)); @@ -811,8 +799,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes assertEq(address(l1CrossDomainMessenger).balance, 0); assertEq(address(target).balance, value); - assertEq(l1CrossDomainMessenger.successfulMessages(hash), true); - assertEq(l1CrossDomainMessenger.failedMessages(hash), true); + _assertMessageStatus(hash, true, true); } /// @notice Tests that `relayMessage` should successfully call the target contract after the @@ -833,8 +820,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes hex"1111" ); - // Set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); // Target should be called with expected data. vm.expectCall(target, hex"1111"); @@ -854,9 +840,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes hex"1111" ); - // Message was successfully relayed. - assertEq(l1CrossDomainMessenger.successfulMessages(hash), true); - assertEq(l1CrossDomainMessenger.failedMessages(hash), false); + _assertMessageStatus(hash, true, false); } /// @notice Tests that `relayMessage` should revert if the message is already replayed. @@ -875,8 +859,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes hex"1111" ); - // Set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); // Mark legacy message as already relayed. uint256 successfulMessagesSlot = 203; bytes32 oldHash = Hashing.hashCrossDomainMessageV0(target, sender, hex"1111", 0); @@ -897,9 +880,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes hex"1111" ); - // Message was not relayed. - assertEq(l1CrossDomainMessenger.successfulMessages(hash), false); - assertEq(l1CrossDomainMessenger.failedMessages(hash), false); + _assertMessageStatus(hash, false, false); } /// @notice Tests that `relayMessage` can be retried after a failure with a legacy message. @@ -919,11 +900,9 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes hex"1111" ); - // Set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); - // Turn the target into a Reverter. - vm.etch(target, address(new Reverter()).code); + vm.etch(target, type(Reverter).runtimeCode); // Target should be called with expected data. vm.expectCall(target, hex"1111"); @@ -947,8 +926,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes // Message failed. assertEq(address(l1CrossDomainMessenger).balance, value); assertEq(address(target).balance, 0); - assertEq(l1CrossDomainMessenger.successfulMessages(hash), false); - assertEq(l1CrossDomainMessenger.failedMessages(hash), true); + _assertMessageStatus(hash, false, true); // Make the target not revert anymore. vm.etch(target, address(0).code); @@ -974,8 +952,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes // Message was successfully relayed. assertEq(address(l1CrossDomainMessenger).balance, 0); assertEq(address(target).balance, value); - assertEq(l1CrossDomainMessenger.successfulMessages(hash), true); - assertEq(l1CrossDomainMessenger.failedMessages(hash), true); + _assertMessageStatus(hash, true, true); } /// @notice Tests that `relayMessage` cannot be retried after success with a legacy message. @@ -995,8 +972,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes hex"1111" ); - // Set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); // Target should be called with expected data. vm.expectCall(target, hex"1111"); @@ -1020,8 +996,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes // Message was successfully relayed. assertEq(address(l1CrossDomainMessenger).balance, 0); assertEq(address(target).balance, value); - assertEq(l1CrossDomainMessenger.successfulMessages(hash), true); - assertEq(l1CrossDomainMessenger.failedMessages(hash), false); + _assertMessageStatus(hash, true, false); // Expect a revert. vm.expectRevert("CrossDomainMessenger: message cannot be replayed"); @@ -1055,11 +1030,9 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes hex"1111" ); - // Set the value of op.l2Sender() to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); - // Turn the target into a Reverter. - vm.etch(target, address(new Reverter()).code); + vm.etch(target, type(Reverter).runtimeCode); // Target should be called with expected data. vm.expectCall(target, hex"1111"); @@ -1079,8 +1052,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes // Message failed. assertEq(address(l1CrossDomainMessenger).balance, value); assertEq(address(target).balance, 0); - assertEq(l1CrossDomainMessenger.successfulMessages(hash), false); - assertEq(l1CrossDomainMessenger.failedMessages(hash), true); + _assertMessageStatus(hash, false, true); // Make the target not revert anymore. vm.etch(target, address(0).code); @@ -1106,8 +1078,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes // Message was successfully relayed. assertEq(address(l1CrossDomainMessenger).balance, 0); assertEq(address(target).balance, value); - assertEq(l1CrossDomainMessenger.successfulMessages(hash), true); - assertEq(l1CrossDomainMessenger.failedMessages(hash), true); + _assertMessageStatus(hash, true, true); // Expect a revert. vm.expectRevert("CrossDomainMessenger: message has already been relayed"); @@ -1124,8 +1095,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes ); } - /// @notice Tests that the relayMessage function is able to relay a message successfully by - /// calling the target contract. + /// @notice Tests that relayMessage reverts while the messenger is paused. function test_relayMessage_paused_reverts() external { vm.prank(superchainConfig.guardian()); superchainConfig.pause(address(0)); From 2637aceb28e5abd06537e665da59b4c0a2046abc Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 21:36:09 -0400 Subject: [PATCH 063/135] Refactor L1CrossDomainMessenger tests: replace index-based storage slot access with StorageSlot struct for improved clarity and maintainability, and update assertions to reflect new storage access patterns. --- test/L1/L1CrossDomainMessenger.t.sol | 88 ++++++++++------------------ 1 file changed, 32 insertions(+), 56 deletions(-) diff --git a/test/L1/L1CrossDomainMessenger.t.sol b/test/L1/L1CrossDomainMessenger.t.sol index 814a533f..777ac3bb 100644 --- a/test/L1/L1CrossDomainMessenger.t.sol +++ b/test/L1/L1CrossDomainMessenger.t.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; import { Reverter, GasBurner } from "test/mocks/Callers.sol"; import { stdError } from "lib/forge-std/src/StdError.sol"; -import { ForgeArtifacts } from "scripts/libraries/ForgeArtifacts.sol"; +import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; // Libraries import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; @@ -19,29 +19,10 @@ import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; -/// @title L1CrossDomainMessenger_Encoding_Harness -/// @notice A harness contract for testing internal functions of the Encoding library. -contract L1CrossDomainMessenger_Encoding_Harness { - function encodeCrossDomainMessage( - uint256 nonce, - address sender, - address target, - uint256 value, - uint256 gasLimit, - bytes memory data - ) - external - pure - returns (bytes memory) - { - return Encoding.encodeCrossDomainMessage(nonce, sender, target, value, gasLimit, data); - } -} - /// @title L1CrossDomainMessenger_TestInit /// @notice Reusable test initialization for L1CrossDomainMessenger tests. abstract contract L1CrossDomainMessenger_TestInit is CommonTest { - address internal recipient = address(0xabbaacdc); + address internal constant recipient = address(0xabbaacdc); } /// @title L1CrossDomainMessenger_Constructor_Test @@ -67,21 +48,20 @@ contract L1CrossDomainMessenger_Constructor_Test is L1CrossDomainMessenger_TestI /// @title L1CrossDomainMessenger_Initialize_Test /// @notice Tests for the `initialize` function of the L1CrossDomainMessenger. contract L1CrossDomainMessenger_Initialize_Test is L1CrossDomainMessenger_TestInit { - uint256 internal initializedSlotIndex; + StorageSlot internal initializedSlot; function setUp() public virtual override { super.setUp(); - initializedSlotIndex = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized").slot; + initializedSlot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized"); } function _resetInitialized() internal { - vm.store(address(l1CrossDomainMessenger), bytes32(initializedSlotIndex), bytes32(0)); + vm.store(address(l1CrossDomainMessenger), bytes32(initializedSlot.slot), bytes32(0)); } function _initializedValue() internal view returns (uint8) { - bytes32 slotVal = vm.load(address(l1CrossDomainMessenger), bytes32(initializedSlotIndex)); - // L1CrossDomainMessenger stores _initialized at byte offset 20. - return uint8((uint256(slotVal) >> 20 * 8) & 0xFF); + bytes32 slotVal = vm.load(address(l1CrossDomainMessenger), bytes32(initializedSlot.slot)); + return uint8((uint256(slotVal) >> (initializedSlot.offset * 8)) & 0xFF); } /// @notice Tests that the proxy is initialized correctly. @@ -307,7 +287,9 @@ contract L1CrossDomainMessenger_SendMessage_Test is L1CrossDomainMessenger_TestI /// but are testing functionality of the CrossDomainMessenger contract that is inherited /// from. contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_TestInit { - uint256 internal senderSlotIndex; + StorageSlot internal senderSlot; + StorageSlot internal successfulMessagesSlot; + StorageSlot internal failedMessagesSlot; bool reentrancyAttacked; uint256 constant reentrancyMessageValue = 50; @@ -318,7 +300,9 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes function setUp() public virtual override { super.setUp(); - senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot; + senderSlot = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender"); + successfulMessagesSlot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "successfulMessages"); + failedMessagesSlot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "failedMessages"); reentrancyTarget = address(this); reentrancySender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; @@ -334,7 +318,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes } function _setPortalL2Sender(address _sender) internal { - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(_sender))); + vm.store(address(optimismPortal2), bytes32(senderSlot.slot), bytes32(abi.encode(_sender))); } function _assertMessageStatus(bytes32 _hash, bool _successful, bool _failed) internal view { @@ -616,25 +600,29 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes _message ); - // Count the number of non-zero bytes in the message. - uint256 zeroBytesInCalldata = 0; + // Count calldata bytes so the EIP-7623 floor can be checked against actual encoded data. uint256 nonzeroBytesInCalldata = 0; for (uint256 i = 0; i < encoded.length; i++) { if (encoded[i] != bytes1(0)) { nonzeroBytesInCalldata++; - } else { - zeroBytesInCalldata++; } } + uint256 zeroBytesInCalldata = encoded.length - nonzeroBytesInCalldata; + uint256 calldataTokens = zeroBytesInCalldata + nonzeroBytesInCalldata * 4; + uint256 floorCost = l1CrossDomainMessenger.TX_BASE_GAS() + calldataTokens + * (l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD() / 4); + // Base gas must always be sufficient to cover the floor cost from EIP-7623. - assertGt(baseGas, 21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10)); + assertGt(baseGas, floorCost); // Actual gas on L2 will be the base gas minus the intrinsic gas cost. Note that even after // EIP-7623, we still deduct 21k + 16 gas per calldata token from the gas limit before // execution happens. After execution, if the message didn't spend enough in execution gas, // the EVM will floor the cost of the transaction to 21k + 40 gas per calldata token. - uint256 gasSupplied = baseGas - (21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 4)); + uint256 intrinsicCost = l1CrossDomainMessenger.TX_BASE_GAS() + calldataTokens + * (l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD() / 4); + uint256 gasSupplied = baseGas - intrinsicCost; // We'll trigger the L2CrossDomainMessenger as if we're the L1CrossDomainMessenger address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); @@ -690,28 +678,13 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes bytes memory _message ) external + view { - L1CrossDomainMessenger_Encoding_Harness encoding = new L1CrossDomainMessenger_Encoding_Harness(); - - // Make sure that unexpected nonces aren't being used right now. - // Prevents people from forgetting to update this test if a new version is ever used. - if (_version > 2) { - vm.expectRevert("Encoding: unknown cross domain message version"); - encoding.encodeCrossDomainMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: _version }), - _sender, - _target, - _value, - _minGasLimit, - _message - ); - } - // Clamp the version to 0 or 1. _version = _version % 2; // Encode the message. - bytes memory encoded = encoding.encodeCrossDomainMessage( + bytes memory encoded = Encoding.encodeCrossDomainMessage( Encoding.encodeVersionedNonce({ _nonce: _nonce, _version: _version }), _sender, _target, @@ -861,9 +834,8 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes _setPortalL2Sender(sender); // Mark legacy message as already relayed. - uint256 successfulMessagesSlot = 203; bytes32 oldHash = Hashing.hashCrossDomainMessageV0(target, sender, hex"1111", 0); - bytes32 slot = keccak256(abi.encode(oldHash, successfulMessagesSlot)); + bytes32 slot = keccak256(abi.encode(oldHash, successfulMessagesSlot.slot)); vm.store(address(l1CrossDomainMessenger), slot, bytes32(uint256(1))); // Expect revert. @@ -1124,7 +1096,11 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes // A requisite for the attack is that the message has already been attempted and written // to the failedMessages mapping, so that it can be replayed. - vm.store(address(l1CrossDomainMessenger), keccak256(abi.encode(reentrancyHash, 206)), bytes32(uint256(1))); + vm.store( + address(l1CrossDomainMessenger), + keccak256(abi.encode(reentrancyHash, failedMessagesSlot.slot)), + bytes32(uint256(1)) + ); assertTrue(l1CrossDomainMessenger.failedMessages(reentrancyHash)); vm.expectEmit(address(l1CrossDomainMessenger)); From 5657f2a797d23a94f22d4a2a8a1cad1d33548b81 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 21:41:56 -0400 Subject: [PATCH 064/135] Refactor L1CrossDomainMessenger tests: streamline message handling by replacing direct message length checks with slicing, enhance clarity in nonce assertions, and consolidate storage slot access into internal functions for improved maintainability. --- test/L1/L1CrossDomainMessenger.t.sol | 150 ++++++++++----------------- 1 file changed, 56 insertions(+), 94 deletions(-) diff --git a/test/L1/L1CrossDomainMessenger.t.sol b/test/L1/L1CrossDomainMessenger.t.sol index 777ac3bb..29ac08d4 100644 --- a/test/L1/L1CrossDomainMessenger.t.sol +++ b/test/L1/L1CrossDomainMessenger.t.sol @@ -48,18 +48,13 @@ contract L1CrossDomainMessenger_Constructor_Test is L1CrossDomainMessenger_TestI /// @title L1CrossDomainMessenger_Initialize_Test /// @notice Tests for the `initialize` function of the L1CrossDomainMessenger. contract L1CrossDomainMessenger_Initialize_Test is L1CrossDomainMessenger_TestInit { - StorageSlot internal initializedSlot; - - function setUp() public virtual override { - super.setUp(); - initializedSlot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized"); - } - function _resetInitialized() internal { + StorageSlot memory initializedSlot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized"); vm.store(address(l1CrossDomainMessenger), bytes32(initializedSlot.slot), bytes32(0)); } function _initializedValue() internal view returns (uint8) { + StorageSlot memory initializedSlot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "_initialized"); bytes32 slotVal = vm.load(address(l1CrossDomainMessenger), bytes32(initializedSlot.slot)); return uint8((uint256(slotVal) >> (initializedSlot.offset * 8)) & 0xFF); } @@ -234,24 +229,22 @@ contract L1CrossDomainMessenger_SendMessage_Test is L1CrossDomainMessenger_TestI { // Bound gas limit to reasonable range to avoid OutOfGas errors _gasLimit = uint32(bound(uint256(_gasLimit), 21000, 1_000_000)); - // Bound message length to avoid excessive gas costs - vm.assume(_message.length <= 1000); + bytes calldata message = _message[:_message.length > 1000 ? 1000 : _message.length]; vm.assume(_sender != address(0)); uint256 nonceBefore = l1CrossDomainMessenger.messageNonce(); vm.prank(_sender); - l1CrossDomainMessenger.sendMessage(recipient, _message, _gasLimit); + l1CrossDomainMessenger.sendMessage(recipient, message, _gasLimit); - // Verify nonce incremented assertEq(l1CrossDomainMessenger.messageNonce(), nonceBefore + 1); } /// @notice Tests that the sendMessage function is able to send the same message twice. function test_sendMessage_twice_succeeds() external { uint256 nonce = l1CrossDomainMessenger.messageNonce(); - l1CrossDomainMessenger.sendMessage(recipient, hex"aa", uint32(500_000)); - l1CrossDomainMessenger.sendMessage(recipient, hex"aa", uint32(500_000)); + l1CrossDomainMessenger.sendMessage(recipient, hex"aa", 500_000); + l1CrossDomainMessenger.sendMessage(recipient, hex"aa", 500_000); // the nonce increments for each message sent assertEq(nonce + 2, l1CrossDomainMessenger.messageNonce()); } @@ -267,7 +260,6 @@ contract L1CrossDomainMessenger_SendMessage_Test is L1CrossDomainMessenger_TestI vm.prank(alice); l1CrossDomainMessenger.sendMessage(recipient, hex"1234", 0); - // Verify nonce incremented assertEq(l1CrossDomainMessenger.messageNonce(), nonce + 1); } @@ -287,37 +279,30 @@ contract L1CrossDomainMessenger_SendMessage_Test is L1CrossDomainMessenger_TestI /// but are testing functionality of the CrossDomainMessenger contract that is inherited /// from. contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_TestInit { - StorageSlot internal senderSlot; - StorageSlot internal successfulMessagesSlot; - StorageSlot internal failedMessagesSlot; - bool reentrancyAttacked; uint256 constant reentrancyMessageValue = 50; - bytes reentrancySelector; - address reentrancySender; - bytes32 reentrancyHash; - address reentrancyTarget; - - function setUp() public virtual override { - super.setUp(); - senderSlot = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender"); - successfulMessagesSlot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "successfulMessages"); - failedMessagesSlot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "failedMessages"); - - reentrancyTarget = address(this); - reentrancySender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; - reentrancySelector = abi.encodeCall(this.reinitAndReenter, ()); - reentrancyHash = Hashing.hashCrossDomainMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), - reentrancySender, - reentrancyTarget, + + function _versionedNonce(uint16 _version) internal pure returns (uint256) { + return Encoding.encodeVersionedNonce({ _nonce: 0, _version: _version }); + } + + function _reentrancyMessage() internal view returns (bytes memory) { + return abi.encodeCall(this.reinitAndReenter, ()); + } + + function _reentrancyHash() internal view returns (bytes32) { + return Hashing.hashCrossDomainMessage( + _versionedNonce(1), + Predeploys.L2_CROSS_DOMAIN_MESSENGER, + address(this), reentrancyMessageValue, 0, - reentrancySelector + _reentrancyMessage() ); } function _setPortalL2Sender(address _sender) internal { + StorageSlot memory senderSlot = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender"); vm.store(address(optimismPortal2), bytes32(senderSlot.slot), bytes32(abi.encode(_sender))); } @@ -326,6 +311,10 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes assertEq(l1CrossDomainMessenger.failedMessages(_hash), _failed); } + function _assertMessageRecorded(bytes32 _hash) internal view { + assertTrue(l1CrossDomainMessenger.successfulMessages(_hash) || l1CrossDomainMessenger.failedMessages(_hash)); + } + /// @notice This method will be called by the relayed message, and will attempt to reenter the /// `relayMessage` function once. function reinitAndReenter() external payable { @@ -340,14 +329,16 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes // attempt to re-replay the withdrawal vm.expectEmit(address(l1CrossDomainMessenger)); - emit FailedRelayedMessage(reentrancyHash); + bytes32 hash = _reentrancyHash(); + bytes memory message = _reentrancyMessage(); + emit FailedRelayedMessage(hash); l1CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), // nonce - reentrancySender, - reentrancyTarget, + _versionedNonce(1), + Predeploys.L2_CROSS_DOMAIN_MESSENGER, + address(this), reentrancyMessageValue, 0, - reentrancySelector + message ); } } @@ -378,7 +369,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes // Try to relay a v2 message. vm.prank(address(optimismPortal2)); l1CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 2 }), // nonce + _versionedNonce(2), sender, target, 0, // value @@ -400,20 +391,12 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes vm.expectEmit(address(l1CrossDomainMessenger)); - bytes32 hash = Hashing.hashCrossDomainMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, hex"1111" - ); + uint256 nonce = _versionedNonce(1); + bytes32 hash = Hashing.hashCrossDomainMessage(nonce, sender, target, 0, 0, hex"1111"); emit RelayedMessage(hash); - l1CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), // nonce - sender, - target, - 0, // value - 0, - hex"1111" - ); + l1CrossDomainMessenger.relayMessage(nonce, sender, target, 0, 0, hex"1111"); _assertMessageStatus(hash, true, false); } @@ -434,25 +417,20 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes vm.assume(_target != address(optimismPortal2)); vm.assume(_target != address(0)); - // Bound gas limit and message size to avoid OutOfGas errors _minGasLimit = uint32(bound(uint256(_minGasLimit), 0, 100_000)); - vm.assume(_message.length <= 100); + bytes calldata message = _message[:_message.length > 100 ? 100 : _message.length]; address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; _setPortalL2Sender(sender); - bytes32 hash = Hashing.hashCrossDomainMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, _target, 0, _minGasLimit, _message - ); + uint256 nonce = _versionedNonce(1); + bytes32 hash = Hashing.hashCrossDomainMessage(nonce, sender, _target, 0, _minGasLimit, message); vm.prank(address(optimismPortal2)); - l1CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, _target, 0, _minGasLimit, _message - ); + l1CrossDomainMessenger.relayMessage(nonce, sender, _target, 0, _minGasLimit, message); - // Verify message was relayed (either successfully or failed) - assertTrue(l1CrossDomainMessenger.successfulMessages(hash) || l1CrossDomainMessenger.failedMessages(hash)); + _assertMessageRecorded(hash); } /// @notice Tests that `relayMessage` reverts if the caller is optimismPortal2 and the value @@ -535,7 +513,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes /// @notice Tests that `relayMessage` reverts if the message is called by a non-optimismPortal2 /// address and is not a failed message eligible for replay. function test_relayMessage_relayingNewMessageByExternalUser_reverts() external { - address target = address(alice); + address target = alice; address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; bytes memory message = hex"1111"; @@ -820,21 +798,16 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes function test_relayMessage_legacyOldReplay_reverts() external { address target = address(0xabcd); address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER; + uint256 nonce = _versionedNonce(0); // Compute the message hash. - bytes32 hash = Hashing.hashCrossDomainMessageV1( - // Using a legacy nonce with version 0. - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 0 }), - sender, - target, - 0, - 0, - hex"1111" - ); + bytes32 hash = Hashing.hashCrossDomainMessageV1(nonce, sender, target, 0, 0, hex"1111"); _setPortalL2Sender(sender); // Mark legacy message as already relayed. bytes32 oldHash = Hashing.hashCrossDomainMessageV0(target, sender, hex"1111", 0); + StorageSlot memory successfulMessagesSlot = + ForgeArtifacts.getSlot("L1CrossDomainMessenger", "successfulMessages"); bytes32 slot = keccak256(abi.encode(oldHash, successfulMessagesSlot.slot)); vm.store(address(l1CrossDomainMessenger), slot, bytes32(uint256(1))); @@ -843,14 +816,7 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes // Relay the message. vm.prank(address(optimismPortal2)); - l1CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 0 }), // nonce - sender, - target, - 0, // value - 0, - hex"1111" - ); + l1CrossDomainMessenger.relayMessage(nonce, sender, target, 0, 0, hex"1111"); _assertMessageStatus(hash, false, false); } @@ -1093,29 +1059,25 @@ contract L1CrossDomainMessenger_Uncategorized_Test is L1CrossDomainMessenger_Tes uint256 balanceBeforeThis = address(this).balance; uint256 balanceBeforeMessenger = address(l1CrossDomainMessenger).balance; + bytes32 hash = _reentrancyHash(); + bytes memory message = _reentrancyMessage(); + StorageSlot memory failedMessagesSlot = ForgeArtifacts.getSlot("L1CrossDomainMessenger", "failedMessages"); // A requisite for the attack is that the message has already been attempted and written // to the failedMessages mapping, so that it can be replayed. vm.store( - address(l1CrossDomainMessenger), - keccak256(abi.encode(reentrancyHash, failedMessagesSlot.slot)), - bytes32(uint256(1)) + address(l1CrossDomainMessenger), keccak256(abi.encode(hash, failedMessagesSlot.slot)), bytes32(uint256(1)) ); - assertTrue(l1CrossDomainMessenger.failedMessages(reentrancyHash)); + assertTrue(l1CrossDomainMessenger.failedMessages(hash)); vm.expectEmit(address(l1CrossDomainMessenger)); - emit FailedRelayedMessage(reentrancyHash); + emit FailedRelayedMessage(hash); l1CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), // nonce - reentrancySender, - reentrancyTarget, - reentrancyMessageValue, - 0, - reentrancySelector + _versionedNonce(1), Predeploys.L2_CROSS_DOMAIN_MESSENGER, address(this), reentrancyMessageValue, 0, message ); // The message hash is not in the successfulMessages mapping. - assertFalse(l1CrossDomainMessenger.successfulMessages(reentrancyHash)); + assertFalse(l1CrossDomainMessenger.successfulMessages(hash)); // The balance of this contract is unchanged. assertEq(address(this).balance, balanceBeforeThis); // The balance of the messenger contract is unchanged. From 490c9d49dd95ac87f386bbb0619a82d2b9b16a6e Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sat, 16 May 2026 21:52:01 -0400 Subject: [PATCH 065/135] Refactor L1ERC721Bridge tests: introduce internal helper functions for message expectations, token bridging, and state assertions, enhancing clarity and maintainability of the test suite. --- test/L1/L1ERC721Bridge.t.sol | 290 ++++++++++++----------------------- 1 file changed, 102 insertions(+), 188 deletions(-) diff --git a/test/L1/L1ERC721Bridge.t.sol b/test/L1/L1ERC721Bridge.t.sol index 6caf0337..d47c7147 100644 --- a/test/L1/L1ERC721Bridge.t.sol +++ b/test/L1/L1ERC721Bridge.t.sol @@ -67,6 +67,58 @@ abstract contract L1ERC721Bridge_TestInit is CommonTest { vm.prank(alice); localToken.approve(address(l1ERC721Bridge), tokenId); } + + function _expectBridgeMessage(address _to) internal { + bytes memory message = abi.encodeCall( + IL2ERC721Bridge.finalizeBridgeERC721, + (address(remoteToken), address(localToken), alice, _to, tokenId, hex"5678") + ); + + vm.expectCall( + address(l1CrossDomainMessenger), + abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l1ERC721Bridge.otherBridge()), message, 1234)) + ); + } + + function _expectBridgeInitiated(address _to) internal { + vm.expectEmit(true, true, true, true); + emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, _to, tokenId, hex"5678"); + } + + function _mockXDomainMessageSender(address _sender) internal { + vm.mockCall( + address(l1CrossDomainMessenger), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), + abi.encode(_sender) + ); + } + + function _mockOtherBridge() internal { + _mockXDomainMessageSender(address(l1ERC721Bridge.otherBridge())); + } + + function _bridgeToken() internal { + vm.prank(alice, alice); + l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + } + + function _assertTokenEscrowed() internal view { + assertTrue(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId)); + assertEq(localToken.ownerOf(tokenId), address(l1ERC721Bridge)); + } + + function _assertTokenNotEscrowed(address _owner) internal view { + assertFalse(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId)); + assertEq(localToken.ownerOf(tokenId), _owner); + } + + function _setupPausedBridge() internal { + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); + + assertTrue(l1ERC721Bridge.paused()); + _mockOtherBridge(); + } } /// @title L1ERC721Bridge_Constructor_Test @@ -89,6 +141,17 @@ contract L1ERC721Bridge_Constructor_Test is L1ERC721Bridge_TestInit { /// @title L1ERC721Bridge_Initialize_Test /// @notice Test contract for L1ERC721Bridge `initialize` function. contract L1ERC721Bridge_Initialize_Test is L1ERC721Bridge_TestInit { + function _resetInitialized() internal { + StorageSlot memory slot = ForgeArtifacts.getSlot("L1ERC721Bridge", "_initialized"); + vm.store(address(l1ERC721Bridge), bytes32(slot.slot), bytes32(0)); + } + + function _initializedValue() internal view returns (uint8) { + StorageSlot memory slot = ForgeArtifacts.getSlot("L1ERC721Bridge", "_initialized"); + bytes32 slotVal = vm.load(address(l1ERC721Bridge), bytes32(slot.slot)); + return uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF); + } + /// @notice Tests that the proxy is initialized with the correct values. function test_initialize_succeeds() public view { assertEq(address(l1ERC721Bridge.MESSENGER()), address(l1CrossDomainMessenger)); @@ -103,15 +166,7 @@ contract L1ERC721Bridge_Initialize_Test is L1ERC721Bridge_TestInit { /// initialization but confirms that the initValue is not incremented incorrectly if /// an upgrade function is not present. function test_initialize_correctInitializerValue_succeeds() public view { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("L1ERC721Bridge", "_initialized"); - - // Get the initializer value. - bytes32 slotVal = vm.load(address(l1ERC721Bridge), bytes32(slot.slot)); - uint8 val = uint8(uint256(slotVal) & 0xFF); - - // Assert that the initializer value matches the expected value. - assertEq(val, l1ERC721Bridge.initVersion()); + assertEq(_initializedValue(), l1ERC721Bridge.initVersion()); } /// @notice Tests that the initialize function reverts if called by a non-proxy admin or owner. @@ -120,11 +175,7 @@ contract L1ERC721Bridge_Initialize_Test is L1ERC721Bridge_TestInit { // Prank as the not ProxyAdmin or ProxyAdmin owner. vm.assume(_sender != address(proxyAdmin) && _sender != proxyAdminOwner); - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("L1ERC721Bridge", "_initialized"); - - // Set the initialized slot to 0. - vm.store(address(l1ERC721Bridge), bytes32(slot.slot), bytes32(0)); + _resetInitialized(); // Expect the revert with `ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner` selector vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); @@ -188,26 +239,17 @@ contract L1ERC721Bridge_Paused_Test is L1ERC721Bridge_TestInit { contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { /// @notice Tests that the ERC721 bridge successfully finalizes a withdrawal. function test_finalizeBridgeERC721_succeeds() external { - // Bridge the token. - vm.prank(alice, alice); - l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + _bridgeToken(); // Expect an event to be emitted. vm.expectEmit(true, true, true, true); emit ERC721BridgeFinalized(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); - // Finalize a withdrawal. - vm.mockCall( - address(l1CrossDomainMessenger), - abi.encodeCall(l1CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(Predeploys.L2_ERC721_BRIDGE) - ); + _mockOtherBridge(); vm.prank(address(l1CrossDomainMessenger)); l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); - // Token is not locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), false); - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenNotEscrowed(alice); } /// @notice Tests that the ERC721 bridge finalize reverts when not called by the remote bridge. @@ -221,12 +263,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { /// @notice Tests that the ERC721 bridge finalize reverts when not called from the remote /// messenger. function test_finalizeBridgeERC721_notFromRemoteMessenger_reverts() external { - // Finalize a withdrawal. - vm.mockCall( - address(l1CrossDomainMessenger), - abi.encodeCall(l1CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(alice) - ); + _mockXDomainMessageSender(alice); vm.prank(address(l1CrossDomainMessenger)); vm.expectRevert("ERC721Bridge: function can only be called from the other bridge"); l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); @@ -244,12 +281,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { /// @notice Tests that the ERC721 bridge finalize reverts when the local token is set as the /// bridge itself. function test_finalizeBridgeERC721_selfToken_reverts() external { - // Finalize a withdrawal. - vm.mockCall( - address(l1CrossDomainMessenger), - abi.encodeCall(l1CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(Predeploys.L2_ERC721_BRIDGE) - ); + _mockOtherBridge(); vm.prank(address(l1CrossDomainMessenger)); vm.expectRevert("L1ERC721Bridge: local token cannot be self"); l1ERC721Bridge.finalizeBridgeERC721( @@ -260,12 +292,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { /// @notice Tests that the ERC721 bridge finalize reverts when the remote token is not escrowed /// in the L1 bridge. function test_finalizeBridgeERC721_notEscrowed_reverts() external { - // Finalize a withdrawal. - vm.mockCall( - address(l1CrossDomainMessenger), - abi.encodeCall(l1CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(Predeploys.L2_ERC721_BRIDGE) - ); + _mockOtherBridge(); vm.prank(address(l1CrossDomainMessenger)); vm.expectRevert("L1ERC721Bridge: Token ID is not escrowed in the L1 Bridge"); l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); @@ -289,50 +316,29 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { // Ensure the to address is an EOA. vm.assume(_to.code.length == 0); - // Bridge the token first. - vm.prank(alice, alice); - l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); - - // Mock the cross domain messenger. - vm.mockCall( - address(l1CrossDomainMessenger), - abi.encodeCall(l1CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(Predeploys.L2_ERC721_BRIDGE) - ); + _bridgeToken(); + _mockOtherBridge(); // Finalize with random addresses. vm.prank(address(l1CrossDomainMessenger)); l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), _from, _to, tokenId, hex"5678"); - // Verify token was transferred to _to. - assertEq(localToken.ownerOf(tokenId), _to); - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), false); + _assertTokenNotEscrowed(_to); } - /// @notice Ensures that the `bridgeERC721` function reverts when the bridge is paused. + /// @notice Ensures that the `finalizeBridgeERC721` function reverts when the bridge is paused. function test_finalizeBridgeERC721_paused_reverts() external { - /// Sets up the test by pausing the bridge, giving ether to the bridge and mocking the - /// calls to the xDomainMessageSender so that it returns the correct value. - vm.startPrank(systemConfig.superchainConfig().guardian()); - systemConfig.superchainConfig().pause(address(0)); - vm.stopPrank(); - - assertTrue(l1ERC721Bridge.paused()); - vm.mockCall( - address(l1ERC721Bridge.messenger()), - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1ERC721Bridge.otherBridge())) - ); + _setupPausedBridge(); vm.prank(address(l1ERC721Bridge.messenger())); vm.expectRevert("L1ERC721Bridge: paused"); l1ERC721Bridge.finalizeBridgeERC721({ - _localToken: address(0), - _remoteToken: address(0), - _from: address(0), - _to: address(0), - _tokenId: 0, - _extraData: hex"" + _localToken: address(localToken), + _remoteToken: address(remoteToken), + _from: alice, + _to: alice, + _tokenId: tokenId, + _extraData: hex"5678" }); } } @@ -343,68 +349,27 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { /// @notice Tests that the ERC721 can be bridged successfully. function test_bridgeERC721_fromEOA_succeeds() public { - // Expect a call to the messenger. - vm.expectCall( - address(l1CrossDomainMessenger), - abi.encodeCall( - l1CrossDomainMessenger.sendMessage, - ( - address(l2ERC721Bridge), - abi.encodeCall( - IL2ERC721Bridge.finalizeBridgeERC721, - (address(remoteToken), address(localToken), alice, alice, tokenId, hex"5678") - ), - 1234 - ) - ) - ); + _expectBridgeMessage(alice); + _expectBridgeInitiated(alice); - // Expect an event to be emitted. - vm.expectEmit(true, true, true, true); - emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + _bridgeToken(); - // Bridge the token. - vm.prank(alice, alice); - l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); - - // Token is locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), true); - assertEq(localToken.ownerOf(tokenId), address(l1ERC721Bridge)); + _assertTokenEscrowed(); } /// @notice Tests that the ERC721 can be bridged successfully. function test_bridgeERC721_fromEOA7702_succeeds() public { - // Expect a call to the messenger. - vm.expectCall( - address(l1CrossDomainMessenger), - abi.encodeCall( - l1CrossDomainMessenger.sendMessage, - ( - address(l2ERC721Bridge), - abi.encodeCall( - IL2ERC721Bridge.finalizeBridgeERC721, - (address(remoteToken), address(localToken), alice, alice, tokenId, hex"5678") - ), - 1234 - ) - ) - ); - - // Expect an event to be emitted. - vm.expectEmit(true, true, true, true); - emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + _expectBridgeMessage(alice); + _expectBridgeInitiated(alice); // Set alice to have 7702 code (delegation target must be non-zero; address(0) is treated as revocation per // EIP-7702). vm.etch(alice, abi.encodePacked(hex"EF0100", address(1))); - // Bridge the token. vm.prank(alice); l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); - // Token is locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), true); - assertEq(localToken.ownerOf(tokenId), address(l1ERC721Bridge)); + _assertTokenEscrowed(); } /// @notice Tests that the ERC721 bridge reverts for non externally owned accounts. @@ -415,9 +380,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { vm.expectRevert("ERC721Bridge: account is not externally owned"); l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); - // Token is not locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), false); - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenNotEscrowed(alice); } /// @notice Tests that the ERC721 bridge reverts for a zero address local token. @@ -427,9 +390,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args l1ERC721Bridge.bridgeERC721(address(0), address(remoteToken), tokenId, 1234, hex"5678"); - // Token is not locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), false); - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenNotEscrowed(alice); } /// @notice Tests that the ERC721 bridge reverts for a zero address remote token. @@ -439,9 +400,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { vm.expectRevert("L1ERC721Bridge: remote token cannot be address(0)"); l1ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, 1234, hex"5678"); - // Token is not locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), false); - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenNotEscrowed(alice); } /// @notice Tests bridgeERC721 with various gas limits succeeds. @@ -454,9 +413,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { vm.prank(alice, alice); l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, _minGasLimit, hex"5678"); - // Token is locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), true); - assertEq(localToken.ownerOf(tokenId), address(l1ERC721Bridge)); + _assertTokenEscrowed(); } /// @notice Tests that the ERC721 bridge reverts for an incorrect owner. @@ -466,41 +423,20 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { vm.expectRevert("ERC721: transfer from incorrect owner"); l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); - // Token is not locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), false); - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenNotEscrowed(alice); } /// @notice Tests that the ERC721 bridge successfully sends a token to a different address than /// the owner. function test_bridgeERC721To_succeeds() external { - // Expect a call to the messenger. - vm.expectCall( - address(l1CrossDomainMessenger), - abi.encodeCall( - l1CrossDomainMessenger.sendMessage, - ( - address(Predeploys.L2_ERC721_BRIDGE), - abi.encodeCall( - IL2ERC721Bridge.finalizeBridgeERC721, - (address(remoteToken), address(localToken), alice, bob, tokenId, hex"5678") - ), - 1234 - ) - ) - ); - - // Expect an event to be emitted. - vm.expectEmit(true, true, true, true); - emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, bob, tokenId, hex"5678"); + _expectBridgeMessage(bob); + _expectBridgeInitiated(bob); // Bridge the token. vm.prank(alice); l1ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), bob, tokenId, 1234, hex"5678"); - // Token is locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), true); - assertEq(localToken.ownerOf(tokenId), address(l1ERC721Bridge)); + _assertTokenEscrowed(); } /// @notice Tests that the ERC721 bridge reverts for non externally owned accounts when sending @@ -511,9 +447,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args l1ERC721Bridge.bridgeERC721To(address(0), address(remoteToken), bob, tokenId, 1234, hex"5678"); - // Token is not locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), false); - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenNotEscrowed(alice); } /// @notice Tests that the ERC721 bridge reverts for a zero address remote token when sending @@ -524,9 +458,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { vm.expectRevert("L1ERC721Bridge: remote token cannot be address(0)"); l1ERC721Bridge.bridgeERC721To(address(localToken), address(0), bob, tokenId, 1234, hex"5678"); - // Token is not locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), false); - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenNotEscrowed(alice); } /// @notice Tests that the ERC721 bridge reverts for an incorrect owner when sending to a @@ -537,9 +469,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { vm.expectRevert("ERC721: transfer from incorrect owner"); l1ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), bob, tokenId, 1234, hex"5678"); - // Token is not locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), false); - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenNotEscrowed(alice); } /// @notice Tests that `bridgeERC721To` reverts if the to address is the zero address. @@ -555,28 +485,12 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { function testFuzz_bridgeERC721To_validRecipient_succeeds(address _to) external { vm.assume(_to != address(0)); - // Expect a call to the messenger. - vm.expectCall( - address(l1CrossDomainMessenger), - abi.encodeCall( - l1CrossDomainMessenger.sendMessage, - ( - address(Predeploys.L2_ERC721_BRIDGE), - abi.encodeCall( - IL2ERC721Bridge.finalizeBridgeERC721, - (address(remoteToken), address(localToken), alice, _to, tokenId, hex"5678") - ), - 1234 - ) - ) - ); + _expectBridgeMessage(_to); // Bridge the token. vm.prank(alice); l1ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), _to, tokenId, 1234, hex"5678"); - // Token is locked in the bridge. - assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), true); - assertEq(localToken.ownerOf(tokenId), address(l1ERC721Bridge)); + _assertTokenEscrowed(); } } From c99a88d755945db4fb141e7d0239d5cc81dd07da Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 07:18:27 -0400 Subject: [PATCH 066/135] Refactor L1ERC721Bridge tests: introduce DEFAULT_MIN_GAS_LIMIT constant, streamline message handling in bridge functions, and enhance clarity with internal helper functions for extra data management. --- test/L1/L1ERC721Bridge.t.sol | 183 +++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 84 deletions(-) diff --git a/test/L1/L1ERC721Bridge.t.sol b/test/L1/L1ERC721Bridge.t.sol index d47c7147..a2bc1e0f 100644 --- a/test/L1/L1ERC721Bridge.t.sol +++ b/test/L1/L1ERC721Bridge.t.sol @@ -34,6 +34,7 @@ abstract contract L1ERC721Bridge_TestInit is CommonTest { L1ERC721Bridge_TestERC721_Harness internal localToken; L1ERC721Bridge_TestERC721_Harness internal remoteToken; uint256 internal constant tokenId = 1; + uint32 internal constant DEFAULT_MIN_GAS_LIMIT = 1234; event ERC721BridgeInitiated( address indexed localToken, @@ -53,41 +54,34 @@ abstract contract L1ERC721Bridge_TestInit is CommonTest { bytes extraData ); - /// @notice Sets up the testing environment. - function setUp() public override { - super.setUp(); - - localToken = new L1ERC721Bridge_TestERC721_Harness(); - remoteToken = new L1ERC721Bridge_TestERC721_Harness(); - - // Mint alice a token. - localToken.mint(alice, tokenId); - - // Approve the bridge to transfer the token. - vm.prank(alice); - localToken.approve(address(l1ERC721Bridge), tokenId); - } - function _expectBridgeMessage(address _to) internal { bytes memory message = abi.encodeCall( IL2ERC721Bridge.finalizeBridgeERC721, - (address(remoteToken), address(localToken), alice, _to, tokenId, hex"5678") + (address(remoteToken), address(localToken), alice, _to, tokenId, _extraData()) ); vm.expectCall( - address(l1CrossDomainMessenger), - abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l1ERC721Bridge.otherBridge()), message, 1234)) + address(l1ERC721Bridge.messenger()), + abi.encodeCall( + ICrossDomainMessenger.sendMessage, + (address(l1ERC721Bridge.otherBridge()), message, DEFAULT_MIN_GAS_LIMIT) + ) ); } function _expectBridgeInitiated(address _to) internal { - vm.expectEmit(true, true, true, true); - emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, _to, tokenId, hex"5678"); + vm.expectEmit(address(l1ERC721Bridge)); + emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, _to, tokenId, _extraData()); + } + + function _expectBridgeFinalized(address _from, address _to) internal { + vm.expectEmit(address(l1ERC721Bridge)); + emit ERC721BridgeFinalized(address(localToken), address(remoteToken), _from, _to, tokenId, _extraData()); } function _mockXDomainMessageSender(address _sender) internal { vm.mockCall( - address(l1CrossDomainMessenger), + address(l1ERC721Bridge.messenger()), abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), abi.encode(_sender) ); @@ -99,7 +93,13 @@ abstract contract L1ERC721Bridge_TestInit is CommonTest { function _bridgeToken() internal { vm.prank(alice, alice); - l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721( + address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); + } + + function _extraData() internal pure returns (bytes memory) { + return hex"5678"; } function _assertTokenEscrowed() internal view { @@ -111,13 +111,19 @@ abstract contract L1ERC721Bridge_TestInit is CommonTest { assertFalse(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId)); assertEq(localToken.ownerOf(tokenId), _owner); } +} - function _setupPausedBridge() internal { - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); +abstract contract L1ERC721Bridge_Bridge_TestInit is L1ERC721Bridge_TestInit { + function setUp() public virtual override { + super.setUp(); - assertTrue(l1ERC721Bridge.paused()); - _mockOtherBridge(); + localToken = new L1ERC721Bridge_TestERC721_Harness(); + remoteToken = new L1ERC721Bridge_TestERC721_Harness(); + + localToken.mint(alice, tokenId); + + vm.prank(alice); + localToken.approve(address(l1ERC721Bridge), tokenId); } } @@ -236,56 +242,60 @@ contract L1ERC721Bridge_Paused_Test is L1ERC721Bridge_TestInit { /// @title L1ERC721Bridge_FinalizeBridgeERC721_Test /// @notice Test contract for L1ERC721Bridge `finalizeBridgeERC721` function. -contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { +contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestInit { /// @notice Tests that the ERC721 bridge successfully finalizes a withdrawal. function test_finalizeBridgeERC721_succeeds() external { _bridgeToken(); - - // Expect an event to be emitted. - vm.expectEmit(true, true, true, true); - emit ERC721BridgeFinalized(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + _expectBridgeFinalized(alice, alice); _mockOtherBridge(); - vm.prank(address(l1CrossDomainMessenger)); - l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + vm.prank(address(l1ERC721Bridge.messenger())); + l1ERC721Bridge.finalizeBridgeERC721( + address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + ); _assertTokenNotEscrowed(alice); } /// @notice Tests that the ERC721 bridge finalize reverts when not called by the remote bridge. function test_finalizeBridgeERC721_notViaLocalMessenger_reverts() external { - // Finalize a withdrawal. vm.prank(alice); vm.expectRevert("ERC721Bridge: function can only be called from the other bridge"); - l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + l1ERC721Bridge.finalizeBridgeERC721( + address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + ); } /// @notice Tests that the ERC721 bridge finalize reverts when not called from the remote /// messenger. function test_finalizeBridgeERC721_notFromRemoteMessenger_reverts() external { _mockXDomainMessageSender(alice); - vm.prank(address(l1CrossDomainMessenger)); + vm.prank(address(l1ERC721Bridge.messenger())); vm.expectRevert("ERC721Bridge: function can only be called from the other bridge"); - l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + l1ERC721Bridge.finalizeBridgeERC721( + address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + ); } /// @notice Tests finalizeBridgeERC721 reverts with invalid caller address. /// @param _caller Random address that is not the messenger. function testFuzz_finalizeBridgeERC721_invalidCaller_reverts(address _caller) external { - vm.assume(_caller != address(l1CrossDomainMessenger)); + vm.assume(_caller != address(l1ERC721Bridge.messenger())); vm.prank(_caller); vm.expectRevert("ERC721Bridge: function can only be called from the other bridge"); - l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + l1ERC721Bridge.finalizeBridgeERC721( + address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + ); } /// @notice Tests that the ERC721 bridge finalize reverts when the local token is set as the /// bridge itself. function test_finalizeBridgeERC721_selfToken_reverts() external { _mockOtherBridge(); - vm.prank(address(l1CrossDomainMessenger)); + vm.prank(address(l1ERC721Bridge.messenger())); vm.expectRevert("L1ERC721Bridge: local token cannot be self"); l1ERC721Bridge.finalizeBridgeERC721( - address(l1ERC721Bridge), address(remoteToken), alice, alice, tokenId, hex"5678" + address(l1ERC721Bridge), address(remoteToken), alice, alice, tokenId, _extraData() ); } @@ -293,42 +303,41 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { /// in the L1 bridge. function test_finalizeBridgeERC721_notEscrowed_reverts() external { _mockOtherBridge(); - vm.prank(address(l1CrossDomainMessenger)); + vm.prank(address(l1ERC721Bridge.messenger())); vm.expectRevert("L1ERC721Bridge: Token ID is not escrowed in the L1 Bridge"); - l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + l1ERC721Bridge.finalizeBridgeERC721( + address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + ); } /// @notice Tests finalizeBridgeERC721 with random valid addresses succeeds after bridging. /// @param _from Random from address for testing. /// @param _to Random to address for testing. function testFuzz_finalizeBridgeERC721_randomAddresses_succeeds(address _from, address _to) external { - // Avoid special predeploy addresses that might cause initialization issues vm.assume(_from != address(0) && _to != address(0)); - vm.assume( - uint160(_from) < uint160(0x4200000000000000000000000000000000000000) - || uint160(_from) > uint160(0x4200000000000000000000000000000000001000) - ); - vm.assume( - uint160(_to) < uint160(0x4200000000000000000000000000000000000000) - || uint160(_to) > uint160(0x4200000000000000000000000000000000001000) - ); + vm.assume(!Predeploys.isPredeployNamespace(_from)); + vm.assume(!Predeploys.isPredeployNamespace(_to)); - // Ensure the to address is an EOA. vm.assume(_to.code.length == 0); _bridgeToken(); _mockOtherBridge(); - // Finalize with random addresses. - vm.prank(address(l1CrossDomainMessenger)); - l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), _from, _to, tokenId, hex"5678"); + vm.prank(address(l1ERC721Bridge.messenger())); + l1ERC721Bridge.finalizeBridgeERC721( + address(localToken), address(remoteToken), _from, _to, tokenId, _extraData() + ); _assertTokenNotEscrowed(_to); } /// @notice Ensures that the `finalizeBridgeERC721` function reverts when the bridge is paused. function test_finalizeBridgeERC721_paused_reverts() external { - _setupPausedBridge(); + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); + + assertTrue(l1ERC721Bridge.paused()); + _mockOtherBridge(); vm.prank(address(l1ERC721Bridge.messenger())); vm.expectRevert("L1ERC721Bridge: paused"); @@ -338,7 +347,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { _from: alice, _to: alice, _tokenId: tokenId, - _extraData: hex"5678" + _extraData: _extraData() }); } } @@ -346,7 +355,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_TestInit { /// @title L1ERC721Bridge_Uncategorized_Test /// @notice General tests that are not testing any function directly of the `L1ERC721Bridge` /// contract or are testing multiple functions at once. -contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { +contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { /// @notice Tests that the ERC721 can be bridged successfully. function test_bridgeERC721_fromEOA_succeeds() public { _expectBridgeMessage(alice); @@ -367,38 +376,39 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { vm.etch(alice, abi.encodePacked(hex"EF0100", address(1))); vm.prank(alice); - l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721( + address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); _assertTokenEscrowed(); } /// @notice Tests that the ERC721 bridge reverts for non externally owned accounts. function test_bridgeERC721_fromContract_reverts() external { - // Bridge the token. vm.etch(alice, hex"01"); vm.prank(alice); vm.expectRevert("ERC721Bridge: account is not externally owned"); - l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721( + address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); _assertTokenNotEscrowed(alice); } /// @notice Tests that the ERC721 bridge reverts for a zero address local token. function test_bridgeERC721_localTokenZeroAddress_reverts() external { - // Bridge the token. vm.prank(alice, alice); vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - l1ERC721Bridge.bridgeERC721(address(0), address(remoteToken), tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721(address(0), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData()); _assertTokenNotEscrowed(alice); } /// @notice Tests that the ERC721 bridge reverts for a zero address remote token. function test_bridgeERC721_remoteTokenZeroAddress_reverts() external { - // Bridge the token. vm.prank(alice, alice); vm.expectRevert("L1ERC721Bridge: remote token cannot be address(0)"); - l1ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData()); _assertTokenNotEscrowed(alice); } @@ -406,22 +416,21 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { /// @notice Tests bridgeERC721 with various gas limits succeeds. /// @param _minGasLimit Random gas limit for testing. function testFuzz_bridgeERC721_variousGasLimits_succeeds(uint32 _minGasLimit) external { - // Bound gas limit to avoid out of gas errors _minGasLimit = uint32(bound(uint256(_minGasLimit), 21000, 10000000)); - // Bridge the token. vm.prank(alice, alice); - l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, _minGasLimit, hex"5678"); + l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, _minGasLimit, _extraData()); _assertTokenEscrowed(); } /// @notice Tests that the ERC721 bridge reverts for an incorrect owner. function test_bridgeERC721_wrongOwner_reverts() external { - // Bridge the token. vm.prank(bob, bob); vm.expectRevert("ERC721: transfer from incorrect owner"); - l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721( + address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); _assertTokenNotEscrowed(alice); } @@ -432,9 +441,10 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { _expectBridgeMessage(bob); _expectBridgeInitiated(bob); - // Bridge the token. vm.prank(alice); - l1ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), bob, tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721To( + address(localToken), address(remoteToken), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); _assertTokenEscrowed(); } @@ -442,10 +452,11 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { /// @notice Tests that the ERC721 bridge reverts for non externally owned accounts when sending /// to a different address than the owner. function test_bridgeERC721To_localTokenZeroAddress_reverts() external { - // Bridge the token. vm.prank(alice); vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - l1ERC721Bridge.bridgeERC721To(address(0), address(remoteToken), bob, tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721To( + address(0), address(remoteToken), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); _assertTokenNotEscrowed(alice); } @@ -453,10 +464,11 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { /// @notice Tests that the ERC721 bridge reverts for a zero address remote token when sending /// to a different address than the owner. function test_bridgeERC721To_remoteTokenZeroAddress_reverts() external { - // Bridge the token. vm.prank(alice); vm.expectRevert("L1ERC721Bridge: remote token cannot be address(0)"); - l1ERC721Bridge.bridgeERC721To(address(localToken), address(0), bob, tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721To( + address(localToken), address(0), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); _assertTokenNotEscrowed(alice); } @@ -464,20 +476,22 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { /// @notice Tests that the ERC721 bridge reverts for an incorrect owner when sending to a /// different address than the owner. function test_bridgeERC721To_wrongOwner_reverts() external { - // Bridge the token. vm.prank(bob); vm.expectRevert("ERC721: transfer from incorrect owner"); - l1ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), bob, tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721To( + address(localToken), address(remoteToken), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); _assertTokenNotEscrowed(alice); } /// @notice Tests that `bridgeERC721To` reverts if the to address is the zero address. function test_bridgeERC721To_toZeroAddress_reverts() external { - // Bridge the token. vm.prank(bob); vm.expectRevert("ERC721Bridge: nft recipient cannot be address(0)"); - l1ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), address(0), tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721To( + address(localToken), address(remoteToken), address(0), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); } /// @notice Tests bridgeERC721To with random valid recipient addresses. @@ -487,9 +501,10 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_TestInit { _expectBridgeMessage(_to); - // Bridge the token. vm.prank(alice); - l1ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), _to, tokenId, 1234, hex"5678"); + l1ERC721Bridge.bridgeERC721To( + address(localToken), address(remoteToken), _to, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + ); _assertTokenEscrowed(); } From 745140278aa3680b36a8b96d8446dad3d166f4b0 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 07:29:24 -0400 Subject: [PATCH 067/135] Refactor L1ERC721Bridge tests: introduce EXTRA_DATA constant for improved clarity, streamline message handling in bridge functions, and enhance storage slot access with a dedicated variable for initialization state. --- test/L1/L1ERC721Bridge.t.sol | 81 ++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/test/L1/L1ERC721Bridge.t.sol b/test/L1/L1ERC721Bridge.t.sol index a2bc1e0f..1a59d608 100644 --- a/test/L1/L1ERC721Bridge.t.sol +++ b/test/L1/L1ERC721Bridge.t.sol @@ -35,6 +35,7 @@ abstract contract L1ERC721Bridge_TestInit is CommonTest { L1ERC721Bridge_TestERC721_Harness internal remoteToken; uint256 internal constant tokenId = 1; uint32 internal constant DEFAULT_MIN_GAS_LIMIT = 1234; + bytes internal constant EXTRA_DATA = hex"5678"; event ERC721BridgeInitiated( address indexed localToken, @@ -57,7 +58,7 @@ abstract contract L1ERC721Bridge_TestInit is CommonTest { function _expectBridgeMessage(address _to) internal { bytes memory message = abi.encodeCall( IL2ERC721Bridge.finalizeBridgeERC721, - (address(remoteToken), address(localToken), alice, _to, tokenId, _extraData()) + (address(remoteToken), address(localToken), alice, _to, tokenId, EXTRA_DATA) ); vm.expectCall( @@ -71,12 +72,7 @@ abstract contract L1ERC721Bridge_TestInit is CommonTest { function _expectBridgeInitiated(address _to) internal { vm.expectEmit(address(l1ERC721Bridge)); - emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, _to, tokenId, _extraData()); - } - - function _expectBridgeFinalized(address _from, address _to) internal { - vm.expectEmit(address(l1ERC721Bridge)); - emit ERC721BridgeFinalized(address(localToken), address(remoteToken), _from, _to, tokenId, _extraData()); + emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, _to, tokenId, EXTRA_DATA); } function _mockXDomainMessageSender(address _sender) internal { @@ -94,14 +90,10 @@ abstract contract L1ERC721Bridge_TestInit is CommonTest { function _bridgeToken() internal { vm.prank(alice, alice); l1ERC721Bridge.bridgeERC721( - address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA ); } - function _extraData() internal pure returns (bytes memory) { - return hex"5678"; - } - function _assertTokenEscrowed() internal view { assertTrue(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId)); assertEq(localToken.ownerOf(tokenId), address(l1ERC721Bridge)); @@ -147,15 +139,21 @@ contract L1ERC721Bridge_Constructor_Test is L1ERC721Bridge_TestInit { /// @title L1ERC721Bridge_Initialize_Test /// @notice Test contract for L1ERC721Bridge `initialize` function. contract L1ERC721Bridge_Initialize_Test is L1ERC721Bridge_TestInit { + StorageSlot internal initializedSlot; + + function setUp() public override { + super.setUp(); + + initializedSlot = ForgeArtifacts.getSlot("L1ERC721Bridge", "_initialized"); + } + function _resetInitialized() internal { - StorageSlot memory slot = ForgeArtifacts.getSlot("L1ERC721Bridge", "_initialized"); - vm.store(address(l1ERC721Bridge), bytes32(slot.slot), bytes32(0)); + vm.store(address(l1ERC721Bridge), bytes32(initializedSlot.slot), bytes32(0)); } function _initializedValue() internal view returns (uint8) { - StorageSlot memory slot = ForgeArtifacts.getSlot("L1ERC721Bridge", "_initialized"); - bytes32 slotVal = vm.load(address(l1ERC721Bridge), bytes32(slot.slot)); - return uint8((uint256(slotVal) >> (slot.offset * 8)) & 0xFF); + bytes32 slotVal = vm.load(address(l1ERC721Bridge), bytes32(initializedSlot.slot)); + return uint8((uint256(slotVal) >> (initializedSlot.offset * 8)) & 0xFF); } /// @notice Tests that the proxy is initialized with the correct values. @@ -246,12 +244,13 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestI /// @notice Tests that the ERC721 bridge successfully finalizes a withdrawal. function test_finalizeBridgeERC721_succeeds() external { _bridgeToken(); - _expectBridgeFinalized(alice, alice); + vm.expectEmit(address(l1ERC721Bridge)); + emit ERC721BridgeFinalized(address(localToken), address(remoteToken), alice, alice, tokenId, EXTRA_DATA); _mockOtherBridge(); vm.prank(address(l1ERC721Bridge.messenger())); l1ERC721Bridge.finalizeBridgeERC721( - address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + address(localToken), address(remoteToken), alice, alice, tokenId, EXTRA_DATA ); _assertTokenNotEscrowed(alice); @@ -262,7 +261,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestI vm.prank(alice); vm.expectRevert("ERC721Bridge: function can only be called from the other bridge"); l1ERC721Bridge.finalizeBridgeERC721( - address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + address(localToken), address(remoteToken), alice, alice, tokenId, EXTRA_DATA ); } @@ -273,7 +272,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestI vm.prank(address(l1ERC721Bridge.messenger())); vm.expectRevert("ERC721Bridge: function can only be called from the other bridge"); l1ERC721Bridge.finalizeBridgeERC721( - address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + address(localToken), address(remoteToken), alice, alice, tokenId, EXTRA_DATA ); } @@ -284,7 +283,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestI vm.prank(_caller); vm.expectRevert("ERC721Bridge: function can only be called from the other bridge"); l1ERC721Bridge.finalizeBridgeERC721( - address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + address(localToken), address(remoteToken), alice, alice, tokenId, EXTRA_DATA ); } @@ -295,7 +294,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestI vm.prank(address(l1ERC721Bridge.messenger())); vm.expectRevert("L1ERC721Bridge: local token cannot be self"); l1ERC721Bridge.finalizeBridgeERC721( - address(l1ERC721Bridge), address(remoteToken), alice, alice, tokenId, _extraData() + address(l1ERC721Bridge), address(remoteToken), alice, alice, tokenId, EXTRA_DATA ); } @@ -306,7 +305,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestI vm.prank(address(l1ERC721Bridge.messenger())); vm.expectRevert("L1ERC721Bridge: Token ID is not escrowed in the L1 Bridge"); l1ERC721Bridge.finalizeBridgeERC721( - address(localToken), address(remoteToken), alice, alice, tokenId, _extraData() + address(localToken), address(remoteToken), alice, alice, tokenId, EXTRA_DATA ); } @@ -324,9 +323,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestI _mockOtherBridge(); vm.prank(address(l1ERC721Bridge.messenger())); - l1ERC721Bridge.finalizeBridgeERC721( - address(localToken), address(remoteToken), _from, _to, tokenId, _extraData() - ); + l1ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), _from, _to, tokenId, EXTRA_DATA); _assertTokenNotEscrowed(_to); } @@ -347,7 +344,7 @@ contract L1ERC721Bridge_FinalizeBridgeERC721_Test is L1ERC721Bridge_Bridge_TestI _from: alice, _to: alice, _tokenId: tokenId, - _extraData: _extraData() + _extraData: EXTRA_DATA }); } } @@ -377,7 +374,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { vm.prank(alice); l1ERC721Bridge.bridgeERC721( - address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA ); _assertTokenEscrowed(); @@ -389,7 +386,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { vm.prank(alice); vm.expectRevert("ERC721Bridge: account is not externally owned"); l1ERC721Bridge.bridgeERC721( - address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA ); _assertTokenNotEscrowed(alice); @@ -399,7 +396,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { function test_bridgeERC721_localTokenZeroAddress_reverts() external { vm.prank(alice, alice); vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - l1ERC721Bridge.bridgeERC721(address(0), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData()); + l1ERC721Bridge.bridgeERC721(address(0), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); _assertTokenNotEscrowed(alice); } @@ -408,7 +405,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { function test_bridgeERC721_remoteTokenZeroAddress_reverts() external { vm.prank(alice, alice); vm.expectRevert("L1ERC721Bridge: remote token cannot be address(0)"); - l1ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData()); + l1ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); _assertTokenNotEscrowed(alice); } @@ -419,7 +416,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { _minGasLimit = uint32(bound(uint256(_minGasLimit), 21000, 10000000)); vm.prank(alice, alice); - l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, _minGasLimit, _extraData()); + l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, _minGasLimit, EXTRA_DATA); _assertTokenEscrowed(); } @@ -429,7 +426,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { vm.prank(bob, bob); vm.expectRevert("ERC721: transfer from incorrect owner"); l1ERC721Bridge.bridgeERC721( - address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + address(localToken), address(remoteToken), tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA ); _assertTokenNotEscrowed(alice); @@ -443,7 +440,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { vm.prank(alice); l1ERC721Bridge.bridgeERC721To( - address(localToken), address(remoteToken), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + address(localToken), address(remoteToken), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA ); _assertTokenEscrowed(); @@ -454,9 +451,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { function test_bridgeERC721To_localTokenZeroAddress_reverts() external { vm.prank(alice); vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - l1ERC721Bridge.bridgeERC721To( - address(0), address(remoteToken), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() - ); + l1ERC721Bridge.bridgeERC721To(address(0), address(remoteToken), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); _assertTokenNotEscrowed(alice); } @@ -466,9 +461,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { function test_bridgeERC721To_remoteTokenZeroAddress_reverts() external { vm.prank(alice); vm.expectRevert("L1ERC721Bridge: remote token cannot be address(0)"); - l1ERC721Bridge.bridgeERC721To( - address(localToken), address(0), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() - ); + l1ERC721Bridge.bridgeERC721To(address(localToken), address(0), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); _assertTokenNotEscrowed(alice); } @@ -479,7 +472,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { vm.prank(bob); vm.expectRevert("ERC721: transfer from incorrect owner"); l1ERC721Bridge.bridgeERC721To( - address(localToken), address(remoteToken), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + address(localToken), address(remoteToken), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA ); _assertTokenNotEscrowed(alice); @@ -490,7 +483,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { vm.prank(bob); vm.expectRevert("ERC721Bridge: nft recipient cannot be address(0)"); l1ERC721Bridge.bridgeERC721To( - address(localToken), address(remoteToken), address(0), tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + address(localToken), address(remoteToken), address(0), tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA ); } @@ -503,7 +496,7 @@ contract L1ERC721Bridge_Uncategorized_Test is L1ERC721Bridge_Bridge_TestInit { vm.prank(alice); l1ERC721Bridge.bridgeERC721To( - address(localToken), address(remoteToken), _to, tokenId, DEFAULT_MIN_GAS_LIMIT, _extraData() + address(localToken), address(remoteToken), _to, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA ); _assertTokenEscrowed(); From ff108f50a89fb9cda57dfce7b860ad31e8cab7aa Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 07:47:23 -0400 Subject: [PATCH 068/135] Refactor L1StandardBridge tests: introduce internal helper functions for ETH custody assertions and message mocking, streamline event emission expectations, and enhance clarity in test setup for improved maintainability. --- test/L1/L1StandardBridge.t.sol | 199 +++++++++++---------------------- 1 file changed, 65 insertions(+), 134 deletions(-) diff --git a/test/L1/L1StandardBridge.t.sol b/test/L1/L1StandardBridge.t.sol index b0b414b3..22bd6aef 100644 --- a/test/L1/L1StandardBridge.t.sol +++ b/test/L1/L1StandardBridge.t.sol @@ -26,6 +26,34 @@ import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; /// @title L1StandardBridge_TestInit /// @notice Reusable test initialization for `L1StandardBridge` tests. abstract contract L1StandardBridge_TestInit is CommonTest { + function _assertETHBridgeCustody( + uint256 _portalBalanceBefore, + uint256 _ethLockboxBalanceBefore, + uint256 _amount + ) + internal + view + { + if (isSysFeatureEnabled(Features.ETH_LOCKBOX)) { + assertEq(address(optimismPortal2).balance, _portalBalanceBefore); + assertEq(address(ethLockbox).balance, _ethLockboxBalanceBefore + _amount); + } else { + assertEq(address(optimismPortal2).balance, _portalBalanceBefore + _amount); + } + } + + function _mockXDomainMessageSender(address _sender) internal { + vm.mockCall( + address(l1StandardBridge.messenger()), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), + abi.encode(_sender) + ); + } + + function _mockOtherBridge() internal { + _mockXDomainMessageSender(address(l1StandardBridge.OTHER_BRIDGE())); + } + /// @notice Asserts the expected calls and events for bridging ETH depending on whether the /// bridge call is legacy or not. function _preBridgeETH(bool isLegacy, uint256 value) internal { @@ -33,7 +61,6 @@ abstract contract L1StandardBridge_TestInit is CommonTest { assertEq(address(optimismPortal2).balance, 0, "OptimismPortal2 balance should be 0"); } uint256 nonce = l1CrossDomainMessenger.messageNonce(); - uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); bytes memory message = abi.encodeCall(StandardBridge.finalizeBridgeETH, (alice, alice, value, hex"dead")); @@ -68,23 +95,20 @@ abstract contract L1StandardBridge_TestInit is CommonTest { ) ); - bytes memory opaqueData = abi.encodePacked(uint256(value), uint256(value), baseGas, false, innerMessage); - vm.expectEmit(address(l1StandardBridge)); emit ETHDepositInitiated(alice, alice, value, hex"dead"); vm.expectEmit(address(l1StandardBridge)); emit ETHBridgeInitiated(alice, alice, value, hex"dead"); - // OptimismPortal emits a TransactionDeposited event on `depositTransaction` call vm.expectEmit(address(optimismPortal2)); - emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), version, opaqueData); + emitTransactionDeposited( + l1MessengerAliased, address(l2CrossDomainMessenger), value, value, baseGas, false, innerMessage + ); - // SentMessage event emitted by the CrossDomainMessenger vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessage(address(l2StandardBridge), address(l1StandardBridge), message, nonce, 50000); - // SentMessageExtension1 event emitted by the CrossDomainMessenger vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessageExtension1(address(l1StandardBridge), value); @@ -95,7 +119,6 @@ abstract contract L1StandardBridge_TestInit is CommonTest { /// depending on whether the bridge call is legacy or not. function _preBridgeETHTo(bool isLegacy, uint256 value) internal { uint256 nonce = l1CrossDomainMessenger.messageNonce(); - uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); if (isLegacy) { @@ -110,8 +133,6 @@ abstract contract L1StandardBridge_TestInit is CommonTest { bytes memory message = abi.encodeCall(StandardBridge.finalizeBridgeETH, (alice, bob, value, hex"dead")); - // the L1 bridge should call - // L1CrossDomainMessenger.sendMessage vm.expectCall( address(l1CrossDomainMessenger), abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l2StandardBridge), message, 60000)) @@ -131,27 +152,23 @@ abstract contract L1StandardBridge_TestInit is CommonTest { ) ); - bytes memory opaqueData = abi.encodePacked(uint256(value), uint256(value), baseGas, false, innerMessage); - vm.expectEmit(address(l1StandardBridge)); emit ETHDepositInitiated(alice, bob, value, hex"dead"); vm.expectEmit(address(l1StandardBridge)); emit ETHBridgeInitiated(alice, bob, value, hex"dead"); - // OptimismPortal emits a TransactionDeposited event on `depositTransaction` call vm.expectEmit(address(optimismPortal2)); - emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), version, opaqueData); + emitTransactionDeposited( + l1MessengerAliased, address(l2CrossDomainMessenger), value, value, baseGas, false, innerMessage + ); - // SentMessage event emitted by the CrossDomainMessenger vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessage(address(l2StandardBridge), address(l1StandardBridge), message, nonce, 60000); - // SentMessageExtension1 event emitted by the CrossDomainMessenger vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessageExtension1(address(l1StandardBridge), value); - // deposit eth to bob vm.prank(alice, alice); } } @@ -219,7 +236,7 @@ contract L1StandardBridge_Initialize_Test is CommonTest { /// @title L1StandardBridge_Paused_Test /// @notice Tests the `paused` function of the `L1StandardBridge` contract. -contract L1StandardBridge_Paused_Test is CommonTest { +contract L1StandardBridge_Paused_Test is L1StandardBridge_TestInit { /// @notice Sets up the test by pausing the bridge, giving ether to the bridge and mocking the /// calls to the xDomainMessageSender so that it returns the correct value. function _setupPausedBridge() internal { @@ -230,11 +247,7 @@ contract L1StandardBridge_Paused_Test is CommonTest { vm.deal(address(l1StandardBridge.messenger()), 1 ether); - vm.mockCall( - address(l1StandardBridge.messenger()), - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1StandardBridge.otherBridge())) - ); + _mockOtherBridge(); } /// @notice Verifies that the `paused` accessor returns the same value as the `paused` function @@ -322,7 +335,7 @@ contract L1StandardBridge_Paused_Test is CommonTest { /// @title L1StandardBridge_Receive_Test /// @notice Tests the `receive` function of the `L1StandardBridge` contract. -contract L1StandardBridge_Receive_Test is CommonTest { +contract L1StandardBridge_Receive_Test is L1StandardBridge_TestInit { /// @notice Tests receive bridges ETH successfully. function test_receive_succeeds() external { skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); @@ -352,12 +365,7 @@ contract L1StandardBridge_Receive_Test is CommonTest { (bool success,) = address(l1StandardBridge).call{ value: 100 }(hex""); assertEq(success, true); - if (isSysFeatureEnabled(Features.ETH_LOCKBOX)) { - assertEq(address(optimismPortal2).balance, portalBalanceBefore); - assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 100); - } else { - assertEq(address(optimismPortal2).balance, portalBalanceBefore + 100); - } + _assertETHBridgeCustody(portalBalanceBefore, ethLockboxBalanceBefore, 100); } /// @notice Verifies receive function reverts when called by contracts @@ -386,12 +394,7 @@ contract L1StandardBridge_DepositETH_Test is L1StandardBridge_TestInit { uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); - if (isSysFeatureEnabled(Features.ETH_LOCKBOX)) { - assertEq(address(optimismPortal2).balance, portalBalanceBefore); - assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 500); - } else { - assertEq(address(optimismPortal2).balance, portalBalanceBefore + 500); - } + _assertETHBridgeCustody(portalBalanceBefore, ethLockboxBalanceBefore, 500); } /// @notice Tests that depositing ETH succeeds for an EOA using 7702 delegation. @@ -405,12 +408,7 @@ contract L1StandardBridge_DepositETH_Test is L1StandardBridge_TestInit { uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); - if (isSysFeatureEnabled(Features.ETH_LOCKBOX)) { - assertEq(address(optimismPortal2).balance, portalBalanceBefore); - assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 500); - } else { - assertEq(address(optimismPortal2).balance, portalBalanceBefore + 500); - } + _assertETHBridgeCustody(portalBalanceBefore, ethLockboxBalanceBefore, 500); } /// @notice Tests that depositing ETH reverts if the call is not from an EOA. @@ -437,12 +435,7 @@ contract L1StandardBridge_DepositETHTo_Test is L1StandardBridge_TestInit { uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.depositETHTo{ value: 600 }(bob, 60000, hex"dead"); - if (isSysFeatureEnabled(Features.ETH_LOCKBOX)) { - assertEq(address(optimismPortal2).balance, portalBalanceBefore); - assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 600); - } else { - assertEq(address(optimismPortal2).balance, portalBalanceBefore + 600); - } + _assertETHBridgeCustody(portalBalanceBefore, ethLockboxBalanceBefore, 600); } /// @notice Verifies depositETHTo succeeds with various recipients and amounts @@ -461,19 +454,13 @@ contract L1StandardBridge_DepositETHTo_Test is L1StandardBridge_TestInit { vm.prank(alice); l1StandardBridge.depositETHTo{ value: _amount }(_to, 60000, hex"dead"); - if (isSysFeatureEnabled(Features.ETH_LOCKBOX)) { - assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + _amount); - } else { - assertEq(address(optimismPortal2).balance, portalBalanceBefore + _amount); - } + _assertETHBridgeCustody(portalBalanceBefore, ethLockboxBalanceBefore, _amount); } } /// @title L1StandardBridge_DepositERC20_Test /// @notice Tests the `depositERC20` function of the `L1StandardBridge` contract. contract L1StandardBridge_DepositERC20_Test is CommonTest { - using stdStorage for StdStorage; - // depositERC20 // - updates bridge.deposits // - emits ERC20DepositInitiated @@ -487,11 +474,9 @@ contract L1StandardBridge_DepositERC20_Test is CommonTest { /// Only EOA can call depositERC20. function test_depositERC20_succeeds() external { uint256 nonce = l1CrossDomainMessenger.messageNonce(); - uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); - // Deal Alice's ERC20 State - deal(address(L1Token), alice, 100000, true); + deal(address(L1Token), alice, 100000); vm.prank(alice); L1Token.approve(address(l1StandardBridge), type(uint256).max); @@ -521,24 +506,20 @@ contract L1StandardBridge_DepositERC20_Test is CommonTest { ) ); - bytes memory opaqueData = abi.encodePacked(uint256(0), uint256(0), baseGas, false, innerMessage); - - // Should emit both the bedrock and legacy events vm.expectEmit(address(l1StandardBridge)); emit ERC20DepositInitiated(address(L1Token), address(L2Token), alice, alice, 100, hex""); vm.expectEmit(address(l1StandardBridge)); emit ERC20BridgeInitiated(address(L1Token), address(L2Token), alice, alice, 100, hex""); - // OptimismPortal emits a TransactionDeposited event on `depositTransaction` call vm.expectEmit(address(optimismPortal2)); - emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), version, opaqueData); + emitTransactionDeposited( + l1MessengerAliased, address(l2CrossDomainMessenger), 0, 0, baseGas, false, innerMessage + ); - // SentMessage event emitted by the CrossDomainMessenger vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessage(address(l2StandardBridge), address(l1StandardBridge), message, nonce, 10000); - // SentMessageExtension1 event emitted by the CrossDomainMessenger vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessageExtension1(address(l1StandardBridge), 0); @@ -564,7 +545,7 @@ contract L1StandardBridge_DepositERC20_Test is CommonTest { _amount = bound(_amount, 1, 1000000); _gasLimit = uint32(bound(uint256(_gasLimit), 21000, 10000000)); - deal(address(L1Token), alice, _amount, true); + deal(address(L1Token), alice, _amount); vm.prank(alice); L1Token.approve(address(l1StandardBridge), _amount); @@ -584,7 +565,6 @@ contract L1StandardBridge_DepositERC20To_Test is CommonTest { /// Contracts can call depositERC20. function test_depositERC20To_succeeds() external { uint256 nonce = l1CrossDomainMessenger.messageNonce(); - uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); bytes memory message = abi.encodeCall( @@ -597,29 +577,25 @@ contract L1StandardBridge_DepositERC20To_Test is CommonTest { ); uint64 baseGas = l1CrossDomainMessenger.baseGas(message, 10000); - bytes memory opaqueData = abi.encodePacked(uint256(0), uint256(0), baseGas, false, innerMessage); - - deal(address(L1Token), alice, 100000, true); + deal(address(L1Token), alice, 100000); vm.prank(alice); L1Token.approve(address(l1StandardBridge), type(uint256).max); - // Should emit both the bedrock and legacy events vm.expectEmit(address(l1StandardBridge)); emit ERC20DepositInitiated(address(L1Token), address(L2Token), alice, bob, 1000, hex""); vm.expectEmit(address(l1StandardBridge)); emit ERC20BridgeInitiated(address(L1Token), address(L2Token), alice, bob, 1000, hex""); - // OptimismPortal emits a TransactionDeposited event on `depositTransaction` call vm.expectEmit(address(optimismPortal2)); - emit TransactionDeposited(l1MessengerAliased, address(l2CrossDomainMessenger), version, opaqueData); + emitTransactionDeposited( + l1MessengerAliased, address(l2CrossDomainMessenger), 0, 0, baseGas, false, innerMessage + ); - // SentMessage event emitted by the CrossDomainMessenger vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessage(address(l2StandardBridge), address(l1StandardBridge), message, nonce, 10000); - // SentMessageExtension1 event emitted by the CrossDomainMessenger vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessageExtension1(address(l1StandardBridge), 0); @@ -645,7 +621,7 @@ contract L1StandardBridge_DepositERC20To_Test is CommonTest { /// @notice Verifies depositERC20To succeeds with zero amount function test_depositERC20To_zeroAmount_succeeds() external { - deal(address(L1Token), alice, 1000, true); + deal(address(L1Token), alice, 1000); vm.prank(alice); L1Token.approve(address(l1StandardBridge), 0); @@ -657,9 +633,7 @@ contract L1StandardBridge_DepositERC20To_Test is CommonTest { /// @title L1StandardBridge_FinalizeETHWithdrawal_Test /// @notice Tests the `finalizeETHWithdrawal` function of the `L1StandardBridge` contract. -contract L1StandardBridge_FinalizeETHWithdrawal_Test is CommonTest { - using stdStorage for StdStorage; - +contract L1StandardBridge_FinalizeETHWithdrawal_Test is L1StandardBridge_TestInit { /// @notice Tests that finalizing an ETH withdrawal succeeds. /// Emits ETHWithdrawalFinalized event. /// Only callable by the L2 bridge. @@ -674,11 +648,7 @@ contract L1StandardBridge_FinalizeETHWithdrawal_Test is CommonTest { vm.expectCall(alice, hex""); - vm.mockCall( - address(l1StandardBridge.messenger()), - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) - ); + _mockOtherBridge(); // ensure that the messenger has ETH to call with vm.deal(address(l1StandardBridge.messenger()), 100); vm.prank(address(l1StandardBridge.messenger())); @@ -691,7 +661,7 @@ contract L1StandardBridge_FinalizeETHWithdrawal_Test is CommonTest { /// @title L1StandardBridge_FinalizeERC20Withdrawal_Test /// @notice Tests the `finalizeERC20Withdrawal` function of the `L1StandardBridge` contract. -contract L1StandardBridge_FinalizeERC20Withdrawal_Test is CommonTest { +contract L1StandardBridge_FinalizeERC20Withdrawal_Test is L1StandardBridge_TestInit { using stdStorage for StdStorage; /// @notice Tests that finalizing an ERC20 withdrawal succeeds. @@ -699,12 +669,11 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_Test is CommonTest { /// Emits ERC20WithdrawalFinalized event. /// Only callable by the L2 bridge. function test_finalizeERC20Withdrawal_succeeds() external { - deal(address(L1Token), address(l1StandardBridge), 100, true); + deal(address(L1Token), address(l1StandardBridge), 100); uint256 slot = stdstore.target(address(l1StandardBridge)).sig("deposits(address,address)") .with_key(address(L1Token)).with_key(address(L2Token)).find(); - // Give the L1 bridge some ERC20 tokens vm.store(address(l1StandardBridge), bytes32(slot), bytes32(uint256(100))); assertEq(l1StandardBridge.deposits(address(L1Token), address(L2Token)), 100); @@ -716,11 +685,7 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_Test is CommonTest { vm.expectCall(address(L1Token), abi.encodeCall(ERC20.transfer, (alice, 100))); - vm.mockCall( - address(l1StandardBridge.messenger()), - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) - ); + _mockOtherBridge(); vm.prank(address(l1StandardBridge.messenger())); l1StandardBridge.finalizeERC20Withdrawal(address(L1Token), address(L2Token), alice, alice, 100, hex""); @@ -733,11 +698,7 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_Test is CommonTest { function testFuzz_finalizeERC20Withdrawal_notMessenger_reverts(address _caller) external { vm.assume(_caller != address(l1StandardBridge.messenger())); - vm.mockCall( - address(l1StandardBridge.messenger()), - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) - ); + _mockOtherBridge(); vm.expectRevert("StandardBridge: function can only be called from the other bridge"); vm.prank(_caller); l1StandardBridge.finalizeERC20Withdrawal(address(L1Token), address(L2Token), alice, alice, 100, hex""); @@ -746,11 +707,7 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_Test is CommonTest { /// @notice Tests that finalizing an ERC20 withdrawal reverts if the caller is not the L2 /// bridge. function test_finalizeERC20Withdrawal_notOtherBridge_reverts() external { - vm.mockCall( - address(l1StandardBridge.messenger()), - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(0)) - ); + _mockXDomainMessageSender(address(0)); vm.prank(address(l1StandardBridge.messenger())); vm.expectRevert("StandardBridge: function can only be called from the other bridge"); l1StandardBridge.finalizeERC20Withdrawal(address(L1Token), address(L2Token), alice, alice, 100, hex""); @@ -783,12 +740,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.bridgeETH{ value: 500 }(50000, hex"dead"); - if (isSysFeatureEnabled(Features.ETH_LOCKBOX)) { - assertEq(address(optimismPortal2).balance, portalBalanceBefore); - assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 500); - } else { - assertEq(address(optimismPortal2).balance, portalBalanceBefore + 500); - } + _assertETHBridgeCustody(portalBalanceBefore, ethLockboxBalanceBefore, 500); } /// @notice Tests that bridging ETH to a different address succeeds. @@ -803,22 +755,13 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.bridgeETHTo{ value: 600 }(bob, 60000, hex"dead"); - if (isSysFeatureEnabled(Features.ETH_LOCKBOX)) { - assertEq(address(optimismPortal2).balance, portalBalanceBefore); - assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 600); - } else { - assertEq(address(optimismPortal2).balance, portalBalanceBefore + 600); - } + _assertETHBridgeCustody(portalBalanceBefore, ethLockboxBalanceBefore, 600); } /// @notice Tests that finalizing bridged ETH succeeds. function test_finalizeBridgeETH_succeeds() external { address messenger = address(l1StandardBridge.messenger()); - vm.mockCall( - messenger, - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) - ); + _mockOtherBridge(); vm.deal(messenger, 100); vm.prank(messenger); @@ -831,11 +774,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// @notice Tests that finalizing bridged ETH reverts if the amount is incorrect. function test_finalizeBridgeETH_incorrectValue_reverts() external { address messenger = address(l1StandardBridge.messenger()); - vm.mockCall( - messenger, - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) - ); + _mockOtherBridge(); vm.deal(messenger, 100); vm.prank(messenger); vm.expectRevert("StandardBridge: amount sent does not match amount required"); @@ -845,11 +784,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// @notice Tests that finalizing bridged ETH reverts if the destination is the L1 bridge. function test_finalizeBridgeETH_sendToSelf_reverts() external { address messenger = address(l1StandardBridge.messenger()); - vm.mockCall( - messenger, - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) - ); + _mockOtherBridge(); vm.deal(messenger, 100); vm.prank(messenger); vm.expectRevert("StandardBridge: cannot send to self"); @@ -859,11 +794,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// @notice Tests that finalizing bridged ETH reverts if the destination is the messenger. function test_finalizeBridgeETH_sendToMessenger_reverts() external { address messenger = address(l1StandardBridge.messenger()); - vm.mockCall( - messenger, - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1StandardBridge.OTHER_BRIDGE())) - ); + _mockOtherBridge(); vm.deal(messenger, 100); vm.prank(messenger); vm.expectRevert("StandardBridge: cannot send to messenger"); From ec04012df26d1920761a942a577994576ffc6738 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 08:03:07 -0400 Subject: [PATCH 069/135] Refactor L1StandardBridge tests: introduce internal helper functions for managing initialization state, streamline storage slot access, and enhance clarity in assertions for improved maintainability. --- test/L1/L1StandardBridge.t.sol | 62 ++++++++++++++++------------------ 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/test/L1/L1StandardBridge.t.sol b/test/L1/L1StandardBridge.t.sol index 22bd6aef..736ef558 100644 --- a/test/L1/L1StandardBridge.t.sol +++ b/test/L1/L1StandardBridge.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.15; // Testing -import { stdStorage, StdStorage } from "lib/forge-std/src/Test.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; @@ -195,6 +194,23 @@ contract L1StandardBridge_Constructor_Test is CommonTest { /// @title L1StandardBridge_Initialize_Test /// @notice Tests the `initialize` function of the `L1StandardBridge` contract. contract L1StandardBridge_Initialize_Test is CommonTest { + StorageSlot internal initializedSlot; + + function setUp() public override { + super.setUp(); + + initializedSlot = ForgeArtifacts.getSlot("L1StandardBridge", "_initialized"); + } + + function _resetInitialized() internal { + vm.store(address(l1StandardBridge), bytes32(initializedSlot.slot), bytes32(0)); + } + + function _initializedValue() internal view returns (uint8) { + bytes32 slotVal = vm.load(address(l1StandardBridge), bytes32(initializedSlot.slot)); + return uint8((uint256(slotVal) >> (initializedSlot.offset * 8)) & 0xFF); + } + /// @notice Test that the initialize function sets the correct values. function test_initialize_succeeds() external view { assertEq(address(l1StandardBridge.systemConfig()), address(systemConfig)); @@ -210,8 +226,7 @@ contract L1StandardBridge_Initialize_Test is CommonTest { function testFuzz_initialize_notProxyAdminOrProxyAdminOwner_reverts(address _sender) public { vm.assume(_sender != address(proxyAdmin) && _sender != proxyAdminOwner); - StorageSlot memory slot = ForgeArtifacts.getSlot("L1StandardBridge", "_initialized"); - vm.store(address(l1StandardBridge), bytes32(slot.slot), bytes32(0)); + _resetInitialized(); vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); vm.prank(_sender); @@ -222,15 +237,7 @@ contract L1StandardBridge_Initialize_Test is CommonTest { /// but confirms that the initValue is not incremented incorrectly if an upgrade /// function is not present. function test_initialize_correctInitializerValue_succeeds() public view { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("L1StandardBridge", "_initialized"); - - // Get the initializer value. - bytes32 slotVal = vm.load(address(l1StandardBridge), bytes32(slot.slot)); - uint8 val = uint8(uint256(slotVal) & 0xFF); - - // Assert that the initializer value matches the expected value. - assertEq(val, l1StandardBridge.initVersion()); + assertEq(_initializedValue(), l1StandardBridge.initVersion()); } } @@ -461,12 +468,6 @@ contract L1StandardBridge_DepositETHTo_Test is L1StandardBridge_TestInit { /// @title L1StandardBridge_DepositERC20_Test /// @notice Tests the `depositERC20` function of the `L1StandardBridge` contract. contract L1StandardBridge_DepositERC20_Test is CommonTest { - // depositERC20 - // - updates bridge.deposits - // - emits ERC20DepositInitiated - // - calls optimismPortal.depositTransaction - // - only callable by EOA - /// @notice Tests that depositing ERC20 to the bridge succeeds. /// Bridge deposits are updated. /// Emits ERC20DepositInitiated event. @@ -621,10 +622,6 @@ contract L1StandardBridge_DepositERC20To_Test is CommonTest { /// @notice Verifies depositERC20To succeeds with zero amount function test_depositERC20To_zeroAmount_succeeds() external { - deal(address(L1Token), alice, 1000); - vm.prank(alice); - L1Token.approve(address(l1StandardBridge), 0); - vm.prank(alice); l1StandardBridge.depositERC20To(address(L1Token), address(L2Token), bob, 0, 10000, hex""); assertEq(l1StandardBridge.deposits(address(L1Token), address(L2Token)), 0); @@ -662,8 +659,6 @@ contract L1StandardBridge_FinalizeETHWithdrawal_Test is L1StandardBridge_TestIni /// @title L1StandardBridge_FinalizeERC20Withdrawal_Test /// @notice Tests the `finalizeERC20Withdrawal` function of the `L1StandardBridge` contract. contract L1StandardBridge_FinalizeERC20Withdrawal_Test is L1StandardBridge_TestInit { - using stdStorage for StdStorage; - /// @notice Tests that finalizing an ERC20 withdrawal succeeds. /// Bridge deposits are updated. /// Emits ERC20WithdrawalFinalized event. @@ -671,10 +666,11 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_Test is L1StandardBridge_TestI function test_finalizeERC20Withdrawal_succeeds() external { deal(address(L1Token), address(l1StandardBridge), 100); - uint256 slot = stdstore.target(address(l1StandardBridge)).sig("deposits(address,address)") - .with_key(address(L1Token)).with_key(address(L2Token)).find(); + StorageSlot memory depositsSlot = ForgeArtifacts.getSlot("L1StandardBridge", "deposits"); + bytes32 localTokenDepositsSlot = keccak256(abi.encode(address(L1Token), uint256(depositsSlot.slot))); + bytes32 depositSlot = keccak256(abi.encode(address(L2Token), localTokenDepositsSlot)); - vm.store(address(l1StandardBridge), bytes32(slot), bytes32(uint256(100))); + vm.store(address(l1StandardBridge), depositSlot, bytes32(uint256(100))); assertEq(l1StandardBridge.deposits(address(L1Token), address(L2Token)), 100); vm.expectEmit(address(l1StandardBridge)); @@ -720,12 +716,12 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_Test is L1StandardBridge_TestI contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// @notice Test that the accessors return the correct initialized values. function test_getters_succeeds() external view { - assert(l1StandardBridge.l2TokenBridge() == address(l2StandardBridge)); - assert(address(l1StandardBridge.OTHER_BRIDGE()) == address(l2StandardBridge)); - assert(address(l1StandardBridge.messenger()) == address(l1CrossDomainMessenger)); - assert(address(l1StandardBridge.MESSENGER()) == address(l1CrossDomainMessenger)); - assert(l1StandardBridge.systemConfig() == systemConfig); - assert(l1StandardBridge.superchainConfig() == systemConfig.superchainConfig()); + assertEq(l1StandardBridge.l2TokenBridge(), address(l2StandardBridge)); + assertEq(address(l1StandardBridge.OTHER_BRIDGE()), address(l2StandardBridge)); + assertEq(address(l1StandardBridge.messenger()), address(l1CrossDomainMessenger)); + assertEq(address(l1StandardBridge.MESSENGER()), address(l1CrossDomainMessenger)); + assertEq(address(l1StandardBridge.systemConfig()), address(systemConfig)); + assertEq(address(l1StandardBridge.superchainConfig()), address(systemConfig.superchainConfig())); } /// @notice Tests that bridging ETH succeeds. From 1378474f95a71d5aba95838abc7b443f571a5c03 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 08:11:38 -0400 Subject: [PATCH 070/135] Refactor L1StandardBridge tests: introduce internal helper functions for bridge setup and finalization, enhance clarity in paused state management, and streamline assertions for improved maintainability. --- test/L1/L1StandardBridge.t.sol | 58 ++++++++++++++++------------------ 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/test/L1/L1StandardBridge.t.sol b/test/L1/L1StandardBridge.t.sol index 736ef558..20608777 100644 --- a/test/L1/L1StandardBridge.t.sol +++ b/test/L1/L1StandardBridge.t.sol @@ -53,6 +53,12 @@ abstract contract L1StandardBridge_TestInit is CommonTest { _mockXDomainMessageSender(address(l1StandardBridge.OTHER_BRIDGE())); } + function _setupFinalizeBridgeETH() internal returns (address messenger) { + messenger = address(l1StandardBridge.messenger()); + _mockOtherBridge(); + vm.deal(messenger, 100); + } + /// @notice Asserts the expected calls and events for bridging ETH depending on whether the /// bridge call is legacy or not. function _preBridgeETH(bool isLegacy, uint256 value) internal { @@ -244,19 +250,22 @@ contract L1StandardBridge_Initialize_Test is CommonTest { /// @title L1StandardBridge_Paused_Test /// @notice Tests the `paused` function of the `L1StandardBridge` contract. contract L1StandardBridge_Paused_Test is L1StandardBridge_TestInit { - /// @notice Sets up the test by pausing the bridge, giving ether to the bridge and mocking the - /// calls to the xDomainMessageSender so that it returns the correct value. - function _setupPausedBridge() internal { + /// @notice Pauses the bridge and mocks the xDomainMessageSender to return the other bridge. + function _pauseBridge() internal { vm.startPrank(systemConfig.guardian()); systemConfig.superchainConfig().pause(address(0)); vm.stopPrank(); assertTrue(l1StandardBridge.paused()); - vm.deal(address(l1StandardBridge.messenger()), 1 ether); - _mockOtherBridge(); } + /// @notice Pauses the bridge and funds the messenger for payable ETH finalization tests. + function _setupPausedBridge() internal { + _pauseBridge(); + vm.deal(address(l1StandardBridge.messenger()), 1 ether); + } + /// @notice Verifies that the `paused` accessor returns the same value as the `paused` function /// of the `superchainConfig`. function test_paused_succeeds() external view { @@ -309,7 +318,7 @@ contract L1StandardBridge_Paused_Test is L1StandardBridge_TestInit { /// @notice Confirms that the `finalizeERC20Withdrawal` function reverts when the bridge is /// paused. function test_paused_finalizeERC20Withdrawal_reverts() external { - _setupPausedBridge(); + _pauseBridge(); vm.prank(address(l1StandardBridge.messenger())); vm.expectRevert("StandardBridge: paused"); @@ -325,7 +334,7 @@ contract L1StandardBridge_Paused_Test is L1StandardBridge_TestInit { /// @notice Confirms that the `finalizeBridgeERC20` function reverts when the bridge is paused. function test_paused_finalizeBridgeERC20_reverts() external { - _setupPausedBridge(); + _pauseBridge(); vm.prank(address(l1StandardBridge.messenger())); vm.expectRevert("StandardBridge: paused"); @@ -370,7 +379,7 @@ contract L1StandardBridge_Receive_Test is L1StandardBridge_TestInit { vm.prank(alice, alice); (bool success,) = address(l1StandardBridge).call{ value: 100 }(hex""); - assertEq(success, true); + assertTrue(success); _assertETHBridgeCustody(portalBalanceBefore, ethLockboxBalanceBefore, 100); } @@ -380,7 +389,7 @@ contract L1StandardBridge_Receive_Test is L1StandardBridge_TestInit { vm.etch(alice, hex"ffff"); vm.deal(alice, 100); vm.prank(alice); - vm.expectRevert(bytes("StandardBridge: function can only be called from an EOA")); + vm.expectRevert("StandardBridge: function can only be called from an EOA"); (bool revertsAsExpected,) = address(l1StandardBridge).call{ value: 100 }(hex""); assertTrue(revertsAsExpected, "expectRevert: call did not revert"); } @@ -407,8 +416,8 @@ contract L1StandardBridge_DepositETH_Test is L1StandardBridge_TestInit { /// @notice Tests that depositing ETH succeeds for an EOA using 7702 delegation. function test_depositETH_fromEOA7702_succeeds() external { skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); - // Set alice to have 7702 code. - vm.etch(alice, abi.encodePacked(hex"EF0100", address(0))); + // EIP-7702 treats address(0) as revocation, so use a non-zero delegation target. + vm.etch(alice, abi.encodePacked(hex"EF0100", address(1))); _preBridgeETH({ isLegacy: true, value: 500 }); uint256 portalBalanceBefore = address(optimismPortal2).balance; @@ -479,7 +488,7 @@ contract L1StandardBridge_DepositERC20_Test is CommonTest { deal(address(L1Token), alice, 100000); vm.prank(alice); - L1Token.approve(address(l1StandardBridge), type(uint256).max); + L1Token.approve(address(l1StandardBridge), 100); // The l1StandardBridge should transfer alice's tokens to itself vm.expectCall(address(L1Token), abi.encodeCall(ERC20.transferFrom, (alice, address(l1StandardBridge), 100))); @@ -581,7 +590,7 @@ contract L1StandardBridge_DepositERC20To_Test is CommonTest { deal(address(L1Token), alice, 100000); vm.prank(alice); - L1Token.approve(address(l1StandardBridge), type(uint256).max); + L1Token.approve(address(l1StandardBridge), 1000); vm.expectEmit(address(l1StandardBridge)); emit ERC20DepositInitiated(address(L1Token), address(L2Token), alice, bob, 1000, hex""); @@ -645,10 +654,8 @@ contract L1StandardBridge_FinalizeETHWithdrawal_Test is L1StandardBridge_TestIni vm.expectCall(alice, hex""); - _mockOtherBridge(); - // ensure that the messenger has ETH to call with - vm.deal(address(l1StandardBridge.messenger()), 100); - vm.prank(address(l1StandardBridge.messenger())); + address messenger = _setupFinalizeBridgeETH(); + vm.prank(messenger); l1StandardBridge.finalizeETHWithdrawal{ value: 100 }(alice, alice, 100, hex""); assertEq(address(l1StandardBridge.messenger()).balance, 0); @@ -694,7 +701,6 @@ contract L1StandardBridge_FinalizeERC20Withdrawal_Test is L1StandardBridge_TestI function testFuzz_finalizeERC20Withdrawal_notMessenger_reverts(address _caller) external { vm.assume(_caller != address(l1StandardBridge.messenger())); - _mockOtherBridge(); vm.expectRevert("StandardBridge: function can only be called from the other bridge"); vm.prank(_caller); l1StandardBridge.finalizeERC20Withdrawal(address(L1Token), address(L2Token), alice, alice, 100, hex""); @@ -756,9 +762,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// @notice Tests that finalizing bridged ETH succeeds. function test_finalizeBridgeETH_succeeds() external { - address messenger = address(l1StandardBridge.messenger()); - _mockOtherBridge(); - vm.deal(messenger, 100); + address messenger = _setupFinalizeBridgeETH(); vm.prank(messenger); vm.expectEmit(address(l1StandardBridge)); @@ -769,9 +773,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// @notice Tests that finalizing bridged ETH reverts if the amount is incorrect. function test_finalizeBridgeETH_incorrectValue_reverts() external { - address messenger = address(l1StandardBridge.messenger()); - _mockOtherBridge(); - vm.deal(messenger, 100); + address messenger = _setupFinalizeBridgeETH(); vm.prank(messenger); vm.expectRevert("StandardBridge: amount sent does not match amount required"); l1StandardBridge.finalizeBridgeETH{ value: 50 }(alice, alice, 100, hex""); @@ -779,9 +781,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// @notice Tests that finalizing bridged ETH reverts if the destination is the L1 bridge. function test_finalizeBridgeETH_sendToSelf_reverts() external { - address messenger = address(l1StandardBridge.messenger()); - _mockOtherBridge(); - vm.deal(messenger, 100); + address messenger = _setupFinalizeBridgeETH(); vm.prank(messenger); vm.expectRevert("StandardBridge: cannot send to self"); l1StandardBridge.finalizeBridgeETH{ value: 100 }(alice, address(l1StandardBridge), 100, hex""); @@ -789,9 +789,7 @@ contract L1StandardBridge_Uncategorized_Test is L1StandardBridge_TestInit { /// @notice Tests that finalizing bridged ETH reverts if the destination is the messenger. function test_finalizeBridgeETH_sendToMessenger_reverts() external { - address messenger = address(l1StandardBridge.messenger()); - _mockOtherBridge(); - vm.deal(messenger, 100); + address messenger = _setupFinalizeBridgeETH(); vm.prank(messenger); vm.expectRevert("StandardBridge: cannot send to messenger"); l1StandardBridge.finalizeBridgeETH{ value: 100 }(alice, messenger, 100, hex""); From 7b2fa4e0083dbd6ba4bf6bd4bb8bd77e672042ce Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 08:30:51 -0400 Subject: [PATCH 071/135] Refactor OptimismPortal2 tests: change visibility of isUsingLockbox and forceEnableLockbox functions to internal, remove unused assertions, and streamline balance checks for improved clarity and maintainability. --- test/L1/OptimismPortal2.t.sol | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index 5b8120b5..f01af816 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -19,7 +19,6 @@ import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { Constants } from "src/libraries/Constants.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; -import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Features } from "src/libraries/Features.sol"; import { AggregateVerifier } from "src/L1/proofs/AggregateVerifier.sol"; import "src/libraries/bridge/Types.sol"; @@ -32,7 +31,6 @@ import { IAggregateVerifier } from "interfaces/L1/proofs/IAggregateVerifier.sol" import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; -import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; @@ -157,14 +155,14 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { /// @notice Checks if the ETHLockbox feature is enabled. /// @return bool True if the ETHLockbox feature is enabled. - function isUsingLockbox() public view returns (bool) { + function isUsingLockbox() internal view returns (bool) { return systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX) && address(optimismPortal2.ethLockbox()) != address(0); } /// @notice Enables the ETHLockbox feature if not enabled. /// @param _lockbox Address of the lockbox to enable. - function forceEnableLockbox(address _lockbox) public { + function forceEnableLockbox(address _lockbox) internal { if (!isSysFeatureEnabled(Features.ETH_LOCKBOX)) { vm.prank(address(proxyAdmin)); systemConfig.setFeature(Features.ETH_LOCKBOX, true); @@ -200,7 +198,6 @@ contract OptimismPortal2_Constructor_Test is OptimismPortal2_TestInit { assertEq(address(opImpl.anchorStateRegistry()), address(0)); assertEq(address(opImpl.systemConfig()), address(0)); assertEq(opImpl.l2Sender(), address(0)); - assertEq(address(opImpl.anchorStateRegistry()), address(0)); assertEq(address(opImpl.ethLockbox()), address(0)); } } @@ -565,10 +562,8 @@ contract OptimismPortal2_DonateETH_Test is OptimismPortal2_TestInit { optimismPortal2.donateETH{ value: _amount }(); VmSafe.AccountAccess[] memory accountAccesses = vm.stopAndReturnStateDiff(); - // not necessary since it's checked below assertEq(address(optimismPortal2).balance, preBalance + _amount); - // check that the ETHLockbox balance is unchanged assertEq(address(ethLockbox).balance, lockboxBalanceBefore); // 0 for extcodesize of proxy before being called by this test, @@ -578,28 +573,13 @@ contract OptimismPortal2_DonateETH_Test is OptimismPortal2_TestInit { assertEq(uint8(accountAccesses[1].kind), uint8(VmSafe.AccountAccessKind.Call)); assertEq(uint8(accountAccesses[2].kind), uint8(VmSafe.AccountAccessKind.DelegateCall)); - // to of 1 is the optimism portal proxy assertEq(accountAccesses[1].account, address(optimismPortal2)); - - // accessor is the pranked address assertEq(accountAccesses[1].accessor, alice); - - // value is the amount of ETH donated assertEq(accountAccesses[1].value, _amount); - - // old balance is the balance of the optimism portal before the donation assertEq(accountAccesses[1].oldBalance, preBalance); - - // new balance is the balance of the optimism portal after the donation assertEq(accountAccesses[1].newBalance, preBalance + _amount); - - // data is the selector of the donateETH function assertEq(accountAccesses[1].data, abi.encodePacked(optimismPortal2.donateETH.selector)); - - // reverted of alice call to proxy is false assertEq(accountAccesses[1].reverted, false); - - // reverted of delegate call of proxy to impl is false assertEq(accountAccesses[2].reverted, false); // storage accesses of delegate call of proxy to impl is empty (No storage read or write!) From 93d695bf854871624127ccc59a96c5b7fb7f75c1 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 08:40:42 -0400 Subject: [PATCH 072/135] Refactor OptimismPortal2 tests: introduce internal helper functions for withdrawal proof expectations, streamline test cases for improved clarity, and enhance maintainability by reducing redundancy in event emission assertions. --- test/L1/OptimismPortal2.t.sol | 287 ++++++---------------------------- 1 file changed, 45 insertions(+), 242 deletions(-) diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index f01af816..f480d617 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -30,7 +30,6 @@ import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPort import { IAggregateVerifier } from "interfaces/L1/proofs/IAggregateVerifier.sol"; import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; -import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; @@ -141,6 +140,23 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { ); } + function _expectWithdrawalProven(bytes32 _withdrawalHash_, address _proofSubmitter) internal { + vm.expectEmit(true, true, true, true, address(optimismPortal2)); + emit WithdrawalProven(_withdrawalHash_, alice, bob); + vm.expectEmit(true, true, true, true, address(optimismPortal2)); + emit WithdrawalProvenExtension1(_withdrawalHash_, _proofSubmitter); + } + + function _proveDefaultWithdrawal() internal { + _expectWithdrawalProven(_withdrawalHash, address(this)); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + /// @notice Asserts that the reentrant call will revert. function callPortalAndExpectRevert() external payable { vm.expectRevert(IOptimismPortal.OptimismPortal_NoReentrancy.selector); @@ -678,16 +694,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test /// @notice Tests that `proveWithdrawalTransaction` reverts when the withdrawal has already /// been proven, and the new game has the `CHALLENGER_WINS` status. function test_proveWithdrawalTransaction_replayProveDifferentGameChallengerWins_reverts() external { - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Create a new dispute game, and mock both games to be CHALLENGER_WINS. IDisputeGame game2 = _createDisputeGame(Claim.wrap(_outputRoot), 1); @@ -721,7 +728,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test /// does not implement `wasRespectedGameTypeWhenCreated`. function test_proveWithdrawalTransaction_legacyGame_reverts() external { vm.mockCallRevert(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), ""); - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args + vm.expectRevert(bytes("")); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -763,16 +770,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test /// @notice Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game /// proven against has resolved against the favor of the root claim. function test_proveWithdrawalTransaction_replayProveBadProposal_succeeds() external { - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Mock the status of the dispute game we just proved against to be CHALLENGER_WINS. vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); @@ -784,32 +782,14 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test // Warp 1 second into the future so we're not in the same block as the dispute game. vm.warp(block.timestamp + 1 seconds); - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); } /// @notice Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game /// proven against is no longer of the respected game type. function test_proveWithdrawalTransaction_replayRespectedGameTypeChanged_succeeds() external { // Prove the withdrawal against a game with the current respected game type. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Create a new game. IDisputeGame newGame = _createDisputeGame(Claim.wrap(_outputRoot), 1); @@ -829,10 +809,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test vm.warp(block.timestamp + 1 seconds); // Re-proving should be successful against the new game. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); + _expectWithdrawalProven(_withdrawalHash, address(this)); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex + 1, @@ -843,16 +820,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test /// @notice Tests that `proveWithdrawalTransaction` succeeds. function test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() external { - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); } } @@ -920,8 +888,6 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T // Warp beyond the resolution delay. vm.warp(game_noData.expectedResolution().raw() + 1 seconds); - // Fund the portal so that we can withdraw ETH. - vm.store(address(optimismPortal2), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF))); vm.deal(address(optimismPortal2), 0xFFFFFFFF); if (isUsingLockbox()) { vm.deal(address(ethLockbox), 0xFFFFFFFF); @@ -929,10 +895,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T uint256 bobBalanceBefore = bob.balance; - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProven(_withdrawalHash_noData, alice, bob); - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProvenExtension1(_withdrawalHash_noData, address(this)); + _expectWithdrawalProven(_withdrawalHash_noData, address(this)); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex_noData, @@ -955,16 +918,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T function test_finalizeWithdrawalTransaction_provenWithdrawalHashEther_succeeds() external { uint256 bobBalanceBefore = address(bob).balance; - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp and resolve the dispute game. game.resolve(); @@ -989,10 +943,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T vm.warp(block.timestamp + 1); // Prove the withdrawal transaction against the invalid dispute game, as 0xb0b. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(0xb0b)); + _expectWithdrawalProven(_withdrawalHash, address(0xb0b)); vm.prank(address(0xb0b)); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, @@ -1006,16 +957,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T // Prove the withdrawal transaction against the invalid dispute game, as the test contract, against the original // game. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp and resolve the original dispute game. game.resolve(); @@ -1060,16 +1002,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T function test_finalizeWithdrawalTransaction_ifWithdrawalProofNotOldEnough_reverts() external { uint256 bobBalanceBefore = address(bob).balance; - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); vm.expectRevert(IOptimismPortal.OptimismPortal_ProofNotOldEnough.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -1083,16 +1016,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T uint256 bobBalanceBefore = address(bob).balance; // Prove our withdrawal - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp to after the finalization period vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1114,16 +1038,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T uint256 bobBalanceBefore = address(bob).balance; // Prove our withdrawal - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp to after the finalization period vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1145,16 +1060,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T uint256 bobBalanceBefore = address(bob).balance; vm.etch(bob, hex"fe"); // Contract with just the invalid opcode. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Resolve the dispute game. game.resolve(); @@ -1185,16 +1091,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T uint256 bobBalanceBefore = address(bob).balance; vm.etch(bob, hex"fe"); // Contract with just the invalid opcode. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Resolve the dispute game. game.resolve(); @@ -1214,16 +1111,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T /// @notice Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has already /// been finalized. function test_finalizeWithdrawalTransaction_onReplay_reverts() external { - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Resolve the dispute game. game.resolve(); @@ -1479,16 +1367,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T /// @notice Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal's dispute game /// has been blacklisted. function test_finalizeWithdrawalTransaction_blacklisted_reverts() external { - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Resolve the dispute game. game.resolve(); @@ -1505,16 +1384,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T /// @notice Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal's dispute game /// is still in the air gap. function test_finalizeWithdrawalTransaction_gameInAirGap_reverts() external { - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp past the finalization period. vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1535,16 +1405,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T /// @notice Tests that `finalizeWithdrawalTransaction` reverts if the respected game type was /// updated after the dispute game was created. function test_finalizeWithdrawalTransaction_gameOlderThanRespectedGameTypeUpdate_reverts() external { - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp past the finalization period. vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1568,16 +1429,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T /// respected game type when it was created. `proveWithdrawalTransaction` should /// already prevent this, but we remove that assumption here. function test_finalizeWithdrawalTransaction_gameWasNotRespectedGameType_reverts() external { - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp past the finalization period. vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1599,16 +1451,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T /// `proveWithdrawalTransaction` should already prevent this, but we remove that /// assumption here. function test_finalizeWithdrawalTransaction_legacyGame_reverts() external { - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(address(optimismPortal2)); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp past the finalization period. vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1622,8 +1465,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T // Mock the wasRespectedGameTypeWhenCreated call to revert. vm.mockCallRevert(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), ""); - // Should revert. - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args + vm.expectRevert(bytes("")); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1631,16 +1473,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T /// correctness. function test_finalizeWithdrawalTransaction_delayEdges_succeeds() external { // Prove the withdrawal transaction. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Attempt to finalize the withdrawal transaction 1 second before the proof has matured. // This should fail. @@ -1678,22 +1511,10 @@ contract OptimismPortal2_FinalizeWithdrawalTransactionExternalProof_Test is Opti uint256 bobBalanceBefore = address(bob).balance; // Submit the first proof for the withdrawal hash. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Submit a second proof for the same withdrawal hash. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(0xb0b)); + _expectWithdrawalProven(_withdrawalHash, address(0xb0b)); vm.prank(address(0xb0b)); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, @@ -1724,16 +1545,7 @@ contract OptimismPortal2_CheckWithdrawal_Test is OptimismPortal2_TestInit { /// game has been finalized, and the root claim is valid. function test_checkWithdrawal_succeeds() external { // Prove the withdrawal transaction. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp past the finalization period. vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); @@ -1753,16 +1565,7 @@ contract OptimismPortal2_CheckWithdrawal_Test is OptimismPortal2_TestInit { /// @notice Tests that checkWithdrawal reverts if the withdrawal has already been finalized. function test_checkWithdrawal_ifAlreadyFinalized_reverts() external { // Prove the withdrawal transaction. - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp and resolve the dispute game. game.resolve(); From 3f3b8129b6eafdb2d5a2935bc3c5f41cb6b8c63a Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 08:48:24 -0400 Subject: [PATCH 073/135] Refactor OptimismPortal2 tests: introduce internal helper functions for generating intermediate roots and proving withdrawals, streamline dispute resolution process, and enhance clarity and maintainability of test cases. --- test/L1/OptimismPortal2.t.sol | 316 +++++++++++++--------------------- 1 file changed, 121 insertions(+), 195 deletions(-) diff --git a/test/L1/OptimismPortal2.t.sol b/test/L1/OptimismPortal2.t.sol index f480d617..bf93e25f 100644 --- a/test/L1/OptimismPortal2.t.sol +++ b/test/L1/OptimismPortal2.t.sol @@ -119,14 +119,10 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { } function _createDisputeGame(Claim _rootClaim, uint256 _salt) internal returns (IAggregateVerifier game_) { - bytes memory intermediateRoots; AggregateVerifier gameImpl = AggregateVerifier(address(disputeGameFactory.gameImpls(respectedGameType))); - for (uint256 i = 1; i < gameImpl.intermediateOutputRootsCount(); i++) { - intermediateRoots = - abi.encodePacked(intermediateRoots, keccak256(abi.encode(_proposedBlockNumber, i, _salt))); - } - bytes memory extraData = - abi.encodePacked(_proposedBlockNumber, address(anchorStateRegistry), intermediateRoots, _rootClaim.raw()); + bytes memory intermediateRoots = + _generateIntermediateRoots(_rootClaim, _salt, gameImpl.intermediateOutputRootsCount()); + bytes memory extraData = abi.encodePacked(_proposedBlockNumber, address(anchorStateRegistry), intermediateRoots); bytes memory proof = abi.encodePacked( uint8(AggregateVerifier.ProofType.TEE), blockhash(block.number - 1), block.number - 1, bytes32(0) ); @@ -140,6 +136,24 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { ); } + function _generateIntermediateRoots( + Claim _rootClaim, + uint256 _salt, + uint256 _intermediateRootsCount + ) + private + view + returns (bytes memory) + { + bytes32[] memory intermediateRoots = new bytes32[](_intermediateRootsCount); + for (uint256 i = 1; i < _intermediateRootsCount; i++) { + intermediateRoots[i - 1] = keccak256(abi.encode(_proposedBlockNumber, i, _salt)); + } + intermediateRoots[_intermediateRootsCount - 1] = _rootClaim.raw(); + + return abi.encodePacked(intermediateRoots); + } + function _expectWithdrawalProven(bytes32 _withdrawalHash_, address _proofSubmitter) internal { vm.expectEmit(true, true, true, true, address(optimismPortal2)); emit WithdrawalProven(_withdrawalHash_, alice, bob); @@ -157,6 +171,62 @@ abstract contract OptimismPortal2_TestInit is DisputeGameFactory_TestInit { }); } + function _resolveGameAndWarpPastProofMaturity(IDisputeGame _game) internal { + _game.resolve(); + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); + } + + function _proveFuzzedWithdrawal( + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + returns (Types.WithdrawalTransaction memory withdrawalTx_, bytes32 withdrawalHash_) + { + uint256 value = bound(_value, 0, 200_000_000 ether); + vm.deal(address(optimismPortal2), value); + if (isUsingLockbox()) { + vm.deal(address(ethLockbox), value); + } + + uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); + withdrawalTx_ = Types.WithdrawalTransaction({ + nonce: l2ToL1MessagePasser.messageNonce(), + sender: _sender, + target: _target, + value: value, + gasLimit: gasLimit, + data: _data + }); + + ( + bytes32 stateRoot, + bytes32 storageRoot, + bytes32 outputRoot, + bytes32 withdrawalHash, + bytes[] memory withdrawalProof + ) = ffi.getProveWithdrawalTransactionInputs(withdrawalTx_); + withdrawalHash_ = withdrawalHash; + + Types.OutputRootProof memory proof = Types.OutputRootProof({ + version: bytes32(uint256(0)), + stateRoot: stateRoot, + messagePasserStorageRoot: storageRoot, + latestBlockhash: bytes32(uint256(0)) + }); + + assertEq(outputRoot, Hashing.hashOutputRootProof(proof)); + assertEq(withdrawalHash_, Hashing.hashWithdrawal(withdrawalTx_)); + + vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(outputRoot)); + optimismPortal2.proveWithdrawalTransaction(withdrawalTx_, _proposedGameIndex, proof, withdrawalProof); + (IDisputeGame provenGame,) = optimismPortal2.provenWithdrawals(withdrawalHash_, address(this)); + assertTrue(provenGame.rootClaim().raw() != bytes32(0)); + } + /// @notice Asserts that the reentrant call will revert. function callPortalAndExpectRevert() external payable { vm.expectRevert(IOptimismPortal.OptimismPortal_NoReentrancy.selector); @@ -443,13 +513,7 @@ contract OptimismPortal2_NumProofSubmitters_Test is OptimismPortal2_TestInit { function test_numProofSubmitters_provenWithdrawal_succeeds() external { bytes32 withdrawalHash = Hashing.hashWithdrawal(_defaultTx); - // Prove the withdrawal - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); assertEq(optimismPortal2.numProofSubmitters(withdrawalHash), 1); } @@ -459,13 +523,7 @@ contract OptimismPortal2_NumProofSubmitters_Test is OptimismPortal2_TestInit { vm.assume(_prover != address(0) && _prover != address(this)); bytes32 withdrawalHash = Hashing.hashWithdrawal(_defaultTx); - // First proof by this contract - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Second proof by different prover vm.prank(_prover); @@ -700,7 +758,7 @@ contract OptimismPortal2_ProveWithdrawalTransaction_Test is OptimismPortal2_Test IDisputeGame game2 = _createDisputeGame(Claim.wrap(_outputRoot), 1); _proposedGameIndex = disputeGameFactory.gameCount() - 1; vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); - vm.mockCall(address(game2), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + vm.mockCall(address(game2), abi.encodeCall(game2.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidDisputeGame.selector); optimismPortal2.proveWithdrawalTransaction({ @@ -851,9 +909,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T emit WithdrawalProven(_withdrawalHash, alice, bob); optimismPortal2.proveWithdrawalTransaction(_defaultTx, _proposedGameIndex, _outputRootProof, _withdrawalProof); - // Warp and resolve the dispute game. - game.resolve(); - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); + _resolveGameAndWarpPastProofMaturity(game); vm.startPrank(alice, Constants.ESTIMATION_ADDRESS); vm.expectRevert(IOptimismPortal.OptimismPortal_GasEstimation.selector); @@ -866,27 +922,27 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T // Get withdrawal proof data we can use for testing. ( - bytes32 _stateRoot_noData, - bytes32 _storageRoot_noData, - bytes32 _outputRoot_noData, - bytes32 _withdrawalHash_noData, - bytes[] memory _withdrawalProof_noData + bytes32 stateRootNoData, + bytes32 storageRootNoData, + bytes32 outputRootNoData, + bytes32 withdrawalHashNoData, + bytes[] memory withdrawalProofNoData ) = ffi.getProveWithdrawalTransactionInputs(_defaultTx); // Setup a dummy output root proof for reuse. - Types.OutputRootProof memory _outputRootProof_noData = Types.OutputRootProof({ + Types.OutputRootProof memory outputRootProofNoData = Types.OutputRootProof({ version: bytes32(uint256(0)), - stateRoot: _stateRoot_noData, - messagePasserStorageRoot: _storageRoot_noData, + stateRoot: stateRootNoData, + messagePasserStorageRoot: storageRootNoData, latestBlockhash: bytes32(uint256(0)) }); - IAggregateVerifier game_noData = _createDisputeGame(Claim.wrap(_outputRoot_noData), 0); + IAggregateVerifier gameNoData = _createDisputeGame(Claim.wrap(outputRootNoData), 0); - uint256 _proposedGameIndex_noData = disputeGameFactory.gameCount() - 1; + uint256 proposedGameIndexNoData = disputeGameFactory.gameCount() - 1; // Warp beyond the resolution delay. - vm.warp(game_noData.expectedResolution().raw() + 1 seconds); + vm.warp(gameNoData.expectedResolution().raw() + 1 seconds); vm.deal(address(optimismPortal2), 0xFFFFFFFF); if (isUsingLockbox()) { @@ -895,20 +951,18 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T uint256 bobBalanceBefore = bob.balance; - _expectWithdrawalProven(_withdrawalHash_noData, address(this)); + _expectWithdrawalProven(withdrawalHashNoData, address(this)); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex_noData, - _outputRootProof: _outputRootProof_noData, - _withdrawalProof: _withdrawalProof_noData + _disputeGameIndex: proposedGameIndexNoData, + _outputRootProof: outputRootProofNoData, + _withdrawalProof: withdrawalProofNoData }); - // Warp and resolve the dispute game. - game_noData.resolve(); - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); + _resolveGameAndWarpPastProofMaturity(gameNoData); vm.expectEmit(true, true, false, true); - emit WithdrawalFinalized(_withdrawalHash_noData, true); + emit WithdrawalFinalized(withdrawalHashNoData, true); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); assert(bob.balance == bobBalanceBefore + _defaultTx.value); @@ -920,9 +974,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T _proveDefaultWithdrawal(); - // Warp and resolve the dispute game. - game.resolve(); - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); + _resolveGameAndWarpPastProofMaturity(game); vm.expectEmit(true, true, false, true); emit WithdrawalFinalized(_withdrawalHash, true); @@ -953,15 +1005,13 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T }); // Mock the status of the dispute game 0xb0b proves against to be CHALLENGER_WINS. - vm.mockCall(address(secondGame), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + vm.mockCall(address(secondGame), abi.encodeCall(secondGame.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); // Prove the withdrawal transaction against the invalid dispute game, as the test contract, against the original // game. _proveDefaultWithdrawal(); - // Warp and resolve the original dispute game. - game.resolve(); - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); + _resolveGameAndWarpPastProofMaturity(game); // Ensure both proofs are registered successfully. assertEq(optimismPortal2.numProofSubmitters(_withdrawalHash), 2); @@ -1062,10 +1112,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T _proveDefaultWithdrawal(); - // Resolve the dispute game. - game.resolve(); - - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + _resolveGameAndWarpPastProofMaturity(game); vm.expectEmit(true, true, true, true); emit WithdrawalFinalized(_withdrawalHash, false); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -1093,10 +1140,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T _proveDefaultWithdrawal(); - // Resolve the dispute game. - game.resolve(); - - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + _resolveGameAndWarpPastProofMaturity(game); vm.expectEmit(true, true, true, true); emit WithdrawalFinalized(_withdrawalHash, false); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -1113,10 +1157,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T function test_finalizeWithdrawalTransaction_onReplay_reverts() external { _proveDefaultWithdrawal(); - // Resolve the dispute game. - game.resolve(); - - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + _resolveGameAndWarpPastProofMaturity(game); vm.expectEmit(true, true, true, true); emit WithdrawalFinalized(_withdrawalHash, true); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -1153,10 +1194,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T _withdrawalProof: withdrawalProof }); - // Resolve the dispute game. - game.resolve(); - - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + _resolveGameAndWarpPastProofMaturity(game); vm.expectRevert("SafeCall: Not enough gas"); optimismPortal2.finalizeWithdrawalTransaction{ gas: _defaultTx.gasLimit }(_defaultTx); } @@ -1196,10 +1234,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T emit WithdrawalProvenExtension1(withdrawalHash, address(this)); optimismPortal2.proveWithdrawalTransaction(_testTx, _proposedGameIndex, outputRootProof, withdrawalProof); - // Resolve the dispute game. - game.resolve(); - - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + _resolveGameAndWarpPastProofMaturity(game); vm.expectCall(address(this), _testTx.data); vm.expectEmit(true, true, true, true); emit WithdrawalFinalized(withdrawalHash, true); @@ -1228,57 +1263,13 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T && uint160(_target) > 9 // No precompiles (or zero address) ); - // Total ETH supply is currently about 120M ETH. - uint256 value = bound(_value, 0, 200_000_000 ether); - vm.deal(address(optimismPortal2), value); - if (isUsingLockbox()) { - vm.deal(address(ethLockbox), value); - } - - uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); - uint256 nonce = l2ToL1MessagePasser.messageNonce(); - - // Get a withdrawal transaction and mock proof from the differential testing script. - Types.WithdrawalTransaction memory _tx = Types.WithdrawalTransaction({ - nonce: nonce, sender: _sender, target: _target, value: value, gasLimit: gasLimit, data: _data - }); - ( - bytes32 stateRoot, - bytes32 storageRoot, - bytes32 outputRoot, - bytes32 withdrawalHash, - bytes[] memory withdrawalProof - ) = ffi.getProveWithdrawalTransactionInputs(_tx); + (Types.WithdrawalTransaction memory withdrawalTx, bytes32 withdrawalHash) = + _proveFuzzedWithdrawal(_sender, _target, _value, _gasLimit, _data); - // Create the output root proof - Types.OutputRootProof memory proof = Types.OutputRootProof({ - version: bytes32(uint256(0)), - stateRoot: stateRoot, - messagePasserStorageRoot: storageRoot, - latestBlockhash: bytes32(uint256(0)) - }); + _resolveGameAndWarpPastProofMaturity(game); - // Ensure the values returned from ffi are correct - assertEq(outputRoot, Hashing.hashOutputRootProof(proof)); - assertEq(withdrawalHash, Hashing.hashWithdrawal(_tx)); - - // Setup the dispute game to return the output root - vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(outputRoot)); - - // Prove the withdrawal transaction - optimismPortal2.proveWithdrawalTransaction(_tx, _proposedGameIndex, proof, withdrawalProof); - (IDisputeGame _game,) = optimismPortal2.provenWithdrawals(withdrawalHash, address(this)); - assertTrue(_game.rootClaim().raw() != bytes32(0)); - - // Resolve the dispute game - game.resolve(); - - // Warp past the finalization period - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); - - // Finalize the withdrawal transaction - vm.expectCallMinGas(_tx.target, _tx.value, uint64(_tx.gasLimit), _tx.data); - optimismPortal2.finalizeWithdrawalTransaction(_tx); + vm.expectCallMinGas(withdrawalTx.target, withdrawalTx.value, uint64(withdrawalTx.gasLimit), withdrawalTx.data); + optimismPortal2.finalizeWithdrawalTransaction(withdrawalTx); assertTrue(optimismPortal2.finalizedWithdrawals(withdrawalHash)); } @@ -1306,61 +1297,18 @@ contract OptimismPortal2_FinalizeWithdrawalTransaction_Test is OptimismPortal2_T // Bound to prevent changes in retirementTimestamp _newGameType = GameType.wrap(uint32(bound(_newGameType.raw(), 0, type(uint32).max - 1))); - // Total ETH supply is currently about 120M ETH. - uint256 value = bound(_value, 0, 200_000_000 ether); - vm.deal(address(optimismPortal2), value); - if (isUsingLockbox()) { - vm.deal(address(ethLockbox), value); - } - - uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); - uint256 nonce = l2ToL1MessagePasser.messageNonce(); - - // Get a withdrawal transaction and mock proof from the differential testing script. - Types.WithdrawalTransaction memory _tx = Types.WithdrawalTransaction({ - nonce: nonce, sender: _sender, target: _target, value: value, gasLimit: gasLimit, data: _data - }); - ( - bytes32 stateRoot, - bytes32 storageRoot, - bytes32 outputRoot, - bytes32 withdrawalHash, - bytes[] memory withdrawalProof - ) = ffi.getProveWithdrawalTransactionInputs(_tx); - - // Create the output root proof - Types.OutputRootProof memory proof = Types.OutputRootProof({ - version: bytes32(uint256(0)), - stateRoot: stateRoot, - messagePasserStorageRoot: storageRoot, - latestBlockhash: bytes32(uint256(0)) - }); - - // Ensure the values returned from ffi are correct - assertEq(outputRoot, Hashing.hashOutputRootProof(proof)); - assertEq(withdrawalHash, Hashing.hashWithdrawal(_tx)); - - // Setup the dispute game to return the output root - vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(outputRoot)); - - // Prove the withdrawal transaction - optimismPortal2.proveWithdrawalTransaction(_tx, _proposedGameIndex, proof, withdrawalProof); - (IDisputeGame _game,) = optimismPortal2.provenWithdrawals(withdrawalHash, address(this)); - assertTrue(_game.rootClaim().raw() != bytes32(0)); + (Types.WithdrawalTransaction memory withdrawalTx, bytes32 withdrawalHash) = + _proveFuzzedWithdrawal(_sender, _target, _value, _gasLimit, _data); - // Resolve the dispute game - game.resolve(); - - // Warp past the finalization period - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + _resolveGameAndWarpPastProofMaturity(game); // Change the respectedGameType vm.prank(optimismPortal2.guardian()); anchorStateRegistry.setRespectedGameType(_newGameType); // Withdrawal transaction still finalizable - vm.expectCallMinGas(_tx.target, _tx.value, uint64(_tx.gasLimit), _tx.data); - optimismPortal2.finalizeWithdrawalTransaction(_tx); + vm.expectCallMinGas(withdrawalTx.target, withdrawalTx.value, uint64(withdrawalTx.gasLimit), withdrawalTx.data); + optimismPortal2.finalizeWithdrawalTransaction(withdrawalTx); assertTrue(optimismPortal2.finalizedWithdrawals(withdrawalHash)); } @@ -1523,9 +1471,7 @@ contract OptimismPortal2_FinalizeWithdrawalTransactionExternalProof_Test is Opti _withdrawalProof: _withdrawalProof }); - // Warp and resolve the dispute game. - game.resolve(); - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); + _resolveGameAndWarpPastProofMaturity(game); vm.expectEmit(true, true, false, true); emit WithdrawalFinalized(_withdrawalHash, true); @@ -1567,9 +1513,7 @@ contract OptimismPortal2_CheckWithdrawal_Test is OptimismPortal2_TestInit { // Prove the withdrawal transaction. _proveDefaultWithdrawal(); - // Warp and resolve the dispute game. - game.resolve(); - vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + _resolveGameAndWarpPastProofMaturity(game); // Finalize the withdrawal. optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -1590,13 +1534,7 @@ contract OptimismPortal2_CheckWithdrawal_Test is OptimismPortal2_TestInit { /// @notice Tests that checkWithdrawal reverts if the proof timestamp is greater than the game /// creation timestamp. function testFuzz_checkWithdrawal_ifInvalidProofTimestamp_reverts(uint64 _createdAt) external { - // Prove the withdrawal transaction. - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Mock the game creation timestamp to be greater than the proof timestamp. _createdAt = uint64(bound(_createdAt, block.timestamp, type(uint64).max)); @@ -1621,13 +1559,7 @@ contract OptimismPortal2_CheckWithdrawal_Test is OptimismPortal2_TestInit { /// @notice Tests that checkWithdrawal reverts if the proof timestamp is less than the proof /// maturity delay. function test_checkWithdrawal_ifProofNotOldEnough_reverts() external { - // Prove but don't warp ahead past the proof maturity delay. - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Should revert. vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() - 1); @@ -1637,13 +1569,7 @@ contract OptimismPortal2_CheckWithdrawal_Test is OptimismPortal2_TestInit { /// @notice Tests that checkWithdrawal reverts if the root claim is invalid. function test_checkWithdrawal_ifInvalidRootClaim_reverts() external { - // Prove the withdrawal. - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); + _proveDefaultWithdrawal(); // Warp past the proof maturity delay. vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); From c34afb2418ab7c166b4efafafabbd4f3f7535412 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 09:12:56 -0400 Subject: [PATCH 074/135] Refactor ResourceMetering tests: introduce defaultResourceConfig function to streamline resource configuration setup, enhance clarity in contract initialization, and reduce redundancy in resource parameter assertions. --- test/L1/ResourceMetering.t.sol | 249 ++++++++++++++------------------- 1 file changed, 104 insertions(+), 145 deletions(-) diff --git a/test/L1/ResourceMetering.t.sol b/test/L1/ResourceMetering.t.sol index bcf7afce..c66b8910 100644 --- a/test/L1/ResourceMetering.t.sol +++ b/test/L1/ResourceMetering.t.sol @@ -1,32 +1,29 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing -import { Test } from "lib/forge-std/src/Test.sol"; - -// Contracts +import { Test, stdError } from "lib/forge-std/src/Test.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol"; - -// Libraries import { Constants } from "src/libraries/Constants.sol"; - -// Interfaces import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +function defaultResourceConfig() pure returns (ResourceMetering.ResourceConfig memory) { + IResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG(); + return ResourceMetering.ResourceConfig({ + maxResourceLimit: rcfg.maxResourceLimit, + elasticityMultiplier: rcfg.elasticityMultiplier, + baseFeeMaxChangeDenominator: rcfg.baseFeeMaxChangeDenominator, + minimumBaseFee: rcfg.minimumBaseFee, + systemTxMaxGas: rcfg.systemTxMaxGas, + maximumBaseFee: rcfg.maximumBaseFee + }); +} + contract MeterUser is ResourceMetering { ResourceMetering.ResourceConfig public innerConfig; constructor() { initialize(); - IResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG(); - innerConfig = ResourceMetering.ResourceConfig({ - maxResourceLimit: rcfg.maxResourceLimit, - elasticityMultiplier: rcfg.elasticityMultiplier, - baseFeeMaxChangeDenominator: rcfg.baseFeeMaxChangeDenominator, - minimumBaseFee: rcfg.minimumBaseFee, - systemTxMaxGas: rcfg.systemTxMaxGas, - maximumBaseFee: rcfg.maximumBaseFee - }); + innerConfig = defaultResourceConfig(); } function initialize() public initializer { @@ -58,9 +55,6 @@ contract MeterUser is ResourceMetering { /// @notice A simple wrapper around `ResourceMetering` that allows the initial params to be set in /// the constructor. contract CustomMeterUser is ResourceMetering { - uint256 public startGas; - uint256 public endGas; - constructor(uint128 _prevBaseFee, uint64 _prevBoughtGas, uint64 _prevBlockNum) { params = ResourceMetering.ResourceParams({ prevBaseFee: _prevBaseFee, prevBoughtGas: _prevBoughtGas, prevBlockNum: _prevBlockNum @@ -68,15 +62,7 @@ contract CustomMeterUser is ResourceMetering { } function _resourceConfig() internal pure override returns (ResourceMetering.ResourceConfig memory) { - IResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG(); - return ResourceMetering.ResourceConfig({ - maxResourceLimit: rcfg.maxResourceLimit, - elasticityMultiplier: rcfg.elasticityMultiplier, - baseFeeMaxChangeDenominator: rcfg.baseFeeMaxChangeDenominator, - minimumBaseFee: rcfg.minimumBaseFee, - systemTxMaxGas: rcfg.systemTxMaxGas, - maximumBaseFee: rcfg.maximumBaseFee - }); + return defaultResourceConfig(); } function use(uint64 _amount) public returns (uint256) { @@ -135,7 +121,7 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { meter.use(uint64(target)); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); - assertEq(prevBaseFee, 1000000000); + assertEq(prevBaseFee, rcfg.minimumBaseFee); assertEq(prevBoughtGas, target); assertEq(prevBlockNum, initialBlockNum); } @@ -143,13 +129,12 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { /// @notice Tests that the meter params are set correctly for the maximum gas delta. function test_metered_useMax_succeeds() external { ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig(); - uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier); - uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier); + uint64 maxResourceLimit = uint64(rcfg.maxResourceLimit); - meter.use(target * elasticityMultiplier); + meter.use(maxResourceLimit); (, uint64 prevBoughtGas,) = meter.params(); - assertEq(prevBoughtGas, target * elasticityMultiplier); + assertEq(prevBoughtGas, maxResourceLimit); vm.roll(initialBlockNum + 1); meter.use(0); @@ -163,14 +148,13 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { /// computed as 0. function test_metered_denominatorEq1_reverts() external { ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig(); - uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier); - uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier); + uint64 maxResourceLimit = uint64(rcfg.maxResourceLimit); rcfg.baseFeeMaxChangeDenominator = 1; meter.setParams(rcfg); - meter.use(target * elasticityMultiplier); + meter.use(maxResourceLimit); (, uint64 prevBoughtGas,) = meter.params(); - assertEq(prevBoughtGas, target * elasticityMultiplier); + assertEq(prevBoughtGas, maxResourceLimit); vm.roll(initialBlockNum + 2); @@ -181,11 +165,9 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { /// @notice Tests that the metered modifier reverts if the value is greater than allowed. function test_metered_useMoreThanMax_reverts() external { ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig(); - uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier); - uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier); vm.expectRevert(ResourceMetering.OutOfGas.selector); - meter.use(target * elasticityMultiplier + 1); + meter.use(uint64(rcfg.maxResourceLimit) + 1); } /// @notice Tests that resource metering can handle large gaps between deposits. @@ -195,10 +177,8 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { _blockDiff = uint256(bound(_blockDiff, 0, 433576281058164217753225238677900874458690)); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig(); - uint64 target = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier); - uint64 elasticityMultiplier = uint64(rcfg.elasticityMultiplier); - _amount = uint64(bound(_amount, 0, target * elasticityMultiplier)); + _amount = uint64(bound(_amount, 0, rcfg.maxResourceLimit)); vm.roll(initialBlockNum + _blockDiff); meter.use(_amount); @@ -298,36 +278,8 @@ contract ResourceMetering_ResourceMeteringInit_Test is ResourceMetering_TestInit /// information about how much gas is used and how expensive it is in USD terms to purchase /// the deposit gas. contract ArtifactResourceMetering_Metered_Test is Test { - uint128 internal minimumBaseFee; - uint128 internal maximumBaseFee; - uint64 internal maxResourceLimit; - uint64 internal targetResourceLimit; - string internal outfile; - // keccak256(abi.encodeWithSignature("Error(string)", "ResourceMetering: cannot buy more gas - // than available gas limit")) - bytes32 internal cannotBuyMoreGas = 0x84edc668cfd5e050b8999f43ff87a1faaa93e5f935b20bc1dd4d3ff157ccf429; - // keccak256(abi.encodeWithSignature("Panic(uint256)", 0x11)) - bytes32 internal overflowErr = 0x1ca389f2c8264faa4377de9ce8e14d6263ef29c68044a9272d405761bab2db27; - // keccak256(hex"") - bytes32 internal emptyReturnData = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; - - /// @notice Sets up the tests with constants from the ResourceMetering contract. - function setUp() public { - vm.roll(1_000_000); - - MeterUser base = new MeterUser(); - ResourceMetering.ResourceConfig memory rcfg = base.resourceConfig(); - minimumBaseFee = uint128(rcfg.minimumBaseFee); - maximumBaseFee = rcfg.maximumBaseFee; - maxResourceLimit = uint64(rcfg.maxResourceLimit); - targetResourceLimit = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier); - - outfile = string.concat(vm.projectRoot(), "/.resource-metering.csv"); - try vm.removeFile(outfile) { } catch { } - } - /// @notice Generates a CSV file. No more than the L1 block gas limit should be supplied to the /// `meter` function to avoid long execution time. This test is skipped because there /// is no need to run it every time. It generates a CSV file on disk that can be used @@ -336,6 +288,16 @@ contract ArtifactResourceMetering_Metered_Test is Test { function test_meter_generateArtifact_succeeds() external { vm.skip({ skipTest: true }); + vm.roll(1_000_000); + + ResourceMetering.ResourceConfig memory rcfg = defaultResourceConfig(); + uint128 minimumBaseFee = uint128(rcfg.minimumBaseFee); + uint128 maximumBaseFee = rcfg.maximumBaseFee; + uint64 maxResourceLimit = uint64(rcfg.maxResourceLimit); + uint64 targetResourceLimit = uint64(rcfg.maxResourceLimit) / uint64(rcfg.elasticityMultiplier); + outfile = string.concat(vm.projectRoot(), "/.resource-metering.csv"); + try vm.removeFile(outfile) { } catch { } + vm.writeLine( outfile, "prevBaseFee,prevBoughtGas,prevBlockNumDiff,l1BaseFee,requestedGas,gasConsumed,ethPrice,usdCost,success" @@ -349,10 +311,6 @@ contract ArtifactResourceMetering_Metered_Test is Test { prevBaseFees[3] = uint128(100 gwei); prevBaseFees[4] = uint128(200 gwei); - // prevBoughtGas value in ResourceParams - uint64[] memory prevBoughtGases = new uint64[](1); - prevBoughtGases[0] = uint64(0); - // prevBlockNum diff, simulates blocks with no deposits when non zero uint64[] memory prevBlockNumDiffs = new uint64[](2); prevBlockNumDiffs[0] = 0; @@ -378,79 +336,80 @@ contract ArtifactResourceMetering_Metered_Test is Test { // Iterate over all of the test values and run a test for (uint256 i; i < prevBaseFees.length; i++) { - for (uint256 j; j < prevBoughtGases.length; j++) { - for (uint256 k; k < prevBlockNumDiffs.length; k++) { - for (uint256 l; l < requestedGases.length; l++) { - for (uint256 m; m < l1BaseFees.length; m++) { - for (uint256 n; n < ethPrices.length; n++) { - uint256 snapshotId = vm.snapshot(); - - uint128 prevBaseFee = prevBaseFees[i]; - uint64 prevBoughtGas = prevBoughtGases[j]; - uint64 prevBlockNumDiff = prevBlockNumDiffs[k]; - uint64 requestedGas = requestedGases[l]; - uint256 l1BaseFee = l1BaseFees[m]; - uint256 ethPrice = ethPrices[n]; - string memory result = "success"; - - vm.fee(l1BaseFee); - - CustomMeterUser meter = new CustomMeterUser({ - _prevBaseFee: prevBaseFee, - _prevBoughtGas: prevBoughtGas, - _prevBlockNum: uint64(block.number) - }); - - vm.roll(block.number + prevBlockNumDiff); - - // Call the metering code and catch the various - // types of errors. - uint256 gasConsumed = 0; - try meter.use{ gas: 30_000_000 }(requestedGas) returns (uint256 gasConsumed_) { - gasConsumed = gasConsumed_; - } catch (bytes memory err) { - bytes32 hash = keccak256(err); - if (hash == cannotBuyMoreGas) { - result = "ResourceMetering: cannot buy more gas than available gas limit"; - } else if (hash == overflowErr) { - result = "arithmetic overflow/underflow"; - } else if (hash == emptyReturnData) { - result = "out of gas"; - } else { - result = "UNKNOWN ERROR"; + for (uint256 k; k < prevBlockNumDiffs.length; k++) { + for (uint256 l; l < requestedGases.length; l++) { + for (uint256 m; m < l1BaseFees.length; m++) { + for (uint256 n; n < ethPrices.length; n++) { + uint256 snapshotId = vm.snapshot(); + + uint128 prevBaseFee = prevBaseFees[i]; + uint64 prevBoughtGas = 0; + uint64 prevBlockNumDiff = prevBlockNumDiffs[k]; + uint64 requestedGas = requestedGases[l]; + uint256 l1BaseFee = l1BaseFees[m]; + uint256 ethPrice = ethPrices[n]; + string memory result = "success"; + + vm.fee(l1BaseFee); + + CustomMeterUser meter = new CustomMeterUser({ + _prevBaseFee: prevBaseFee, + _prevBoughtGas: prevBoughtGas, + _prevBlockNum: uint64(block.number) + }); + + vm.roll(block.number + prevBlockNumDiff); + + uint256 gasConsumed = 0; + try meter.use{ gas: 30_000_000 }(requestedGas) returns (uint256 gasConsumed_) { + gasConsumed = gasConsumed_; + } catch (bytes memory err) { + bytes4 selector; + if (err.length >= 4) { + assembly { + selector := mload(add(err, 0x20)) } } - // Compute the USD cost of the gas used - uint256 usdCost = (gasConsumed * l1BaseFee * ethPrice) / 1 ether; - - vm.writeLine( - outfile, - string.concat( - vm.toString(prevBaseFee), - ",", - vm.toString(prevBoughtGas), - ",", - vm.toString(prevBlockNumDiff), - ",", - vm.toString(l1BaseFee), - ",", - vm.toString(requestedGas), - ",", - vm.toString(gasConsumed), - ",", - "$", - vm.toString(ethPrice), - ",", - "$", - vm.toString(usdCost), - ",", - result - ) - ); - - assertTrue(vm.revertTo(snapshotId)); + if (selector == ResourceMetering.OutOfGas.selector) { + result = "ResourceMetering: cannot buy more gas than available gas limit"; + } else if (keccak256(err) == keccak256(stdError.arithmeticError)) { + result = "arithmetic overflow/underflow"; + } else if (err.length == 0) { + result = "out of gas"; + } else { + result = "UNKNOWN ERROR"; + } } + + uint256 usdCost = (gasConsumed * l1BaseFee * ethPrice) / 1 ether; + + vm.writeLine( + outfile, + string.concat( + vm.toString(prevBaseFee), + ",", + vm.toString(prevBoughtGas), + ",", + vm.toString(prevBlockNumDiff), + ",", + vm.toString(l1BaseFee), + ",", + vm.toString(requestedGas), + ",", + vm.toString(gasConsumed), + ",", + "$", + vm.toString(ethPrice), + ",", + "$", + vm.toString(usdCost), + ",", + result + ) + ); + + assertTrue(vm.revertTo(snapshotId)); } } } From 7b4c2776937081d7fc140da4d2b15ef33cb0db3f Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 09:20:32 -0400 Subject: [PATCH 075/135] Refactor ResourceMetering tests: streamline resource configuration setup by utilizing assembly for memory management, enhance gas measurement with a new measuredUse function, and update block number assertions for improved clarity and maintainability. --- test/L1/ResourceMetering.t.sol | 73 ++++++++++++---------------------- 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/test/L1/ResourceMetering.t.sol b/test/L1/ResourceMetering.t.sol index c66b8910..20e8cd92 100644 --- a/test/L1/ResourceMetering.t.sol +++ b/test/L1/ResourceMetering.t.sol @@ -6,16 +6,15 @@ import { ResourceMetering } from "src/L1/ResourceMetering.sol"; import { Constants } from "src/libraries/Constants.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; +uint256 constant MAX_EMPTY_BLOCK_GAP = 433_576_281_058_164_217_753_225_238_677_900_874_458_690; + function defaultResourceConfig() pure returns (ResourceMetering.ResourceConfig memory) { IResourceMetering.ResourceConfig memory rcfg = Constants.DEFAULT_RESOURCE_CONFIG(); - return ResourceMetering.ResourceConfig({ - maxResourceLimit: rcfg.maxResourceLimit, - elasticityMultiplier: rcfg.elasticityMultiplier, - baseFeeMaxChangeDenominator: rcfg.baseFeeMaxChangeDenominator, - minimumBaseFee: rcfg.minimumBaseFee, - systemTxMaxGas: rcfg.systemTxMaxGas, - maximumBaseFee: rcfg.maximumBaseFee - }); + ResourceMetering.ResourceConfig memory config; + assembly ("memory-safe") { + config := rcfg + } + return config; } contract MeterUser is ResourceMetering { @@ -40,6 +39,12 @@ contract MeterUser is ResourceMetering { function use(uint64 _amount) public metered(_amount) { } + function measuredUse(uint64 _amount) public returns (uint256) { + uint256 initialGas = gasleft(); + _metered(_amount, initialGas); + return initialGas - gasleft(); + } + function set(uint128 _prevBaseFee, uint64 _prevBoughtGas, uint64 _prevBlockNum) public { params = ResourceMetering.ResourceParams({ prevBaseFee: _prevBaseFee, prevBoughtGas: _prevBoughtGas, prevBlockNum: _prevBlockNum @@ -51,37 +56,14 @@ contract MeterUser is ResourceMetering { } } -/// @title CustomMeterUser -/// @notice A simple wrapper around `ResourceMetering` that allows the initial params to be set in -/// the constructor. -contract CustomMeterUser is ResourceMetering { - constructor(uint128 _prevBaseFee, uint64 _prevBoughtGas, uint64 _prevBlockNum) { - params = ResourceMetering.ResourceParams({ - prevBaseFee: _prevBaseFee, prevBoughtGas: _prevBoughtGas, prevBlockNum: _prevBlockNum - }); - } - - function _resourceConfig() internal pure override returns (ResourceMetering.ResourceConfig memory) { - return defaultResourceConfig(); - } - - function use(uint64 _amount) public returns (uint256) { - uint256 initialGas = gasleft(); - _metered(_amount, initialGas); - return initialGas - gasleft(); - } -} - /// @title ResourceMetering_TestInit /// @notice Reusable test initialization for `ResourceMetering` tests. abstract contract ResourceMetering_TestInit is Test { MeterUser internal meter; - uint64 initialBlockNum; /// @notice Sets up the test contract. function setUp() public { meter = new MeterUser(); - initialBlockNum = uint64(block.number); } } @@ -105,13 +87,14 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { /// @notice Tests that updating after multiple empty blocks maintains correct base fee. function testFuzz_metered_emptyBlocks_succeeds(uint256 _blockDiff) external { _blockDiff = bound(_blockDiff, 1, 100); - vm.roll(initialBlockNum + _blockDiff); + uint256 startBlock = block.number; + vm.roll(startBlock + _blockDiff); meter.use(0); (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum) = meter.params(); assertEq(prevBaseFee, 1 gwei); assertEq(prevBoughtGas, 0); - assertEq(prevBlockNum, initialBlockNum + _blockDiff); + assertEq(prevBlockNum, startBlock + _blockDiff); } /// @notice Tests that updating the gas delta sets the meter params correctly. @@ -123,7 +106,7 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { assertEq(prevBaseFee, rcfg.minimumBaseFee); assertEq(prevBoughtGas, target); - assertEq(prevBlockNum, initialBlockNum); + assertEq(prevBlockNum, block.number); } /// @notice Tests that the meter params are set correctly for the maximum gas delta. @@ -136,7 +119,7 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { (, uint64 prevBoughtGas,) = meter.params(); assertEq(prevBoughtGas, maxResourceLimit); - vm.roll(initialBlockNum + 1); + vm.roll(block.number + 1); meter.use(0); (uint128 postBaseFee,,) = meter.params(); assertEq(postBaseFee, 2125000000); @@ -156,7 +139,7 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { (, uint64 prevBoughtGas,) = meter.params(); assertEq(prevBoughtGas, maxResourceLimit); - vm.roll(initialBlockNum + 2); + vm.roll(block.number + 2); vm.expectRevert("UNDEFINED"); meter.use(0); @@ -172,15 +155,14 @@ contract ResourceMetering_Metered_Test is ResourceMetering_TestInit { /// @notice Tests that resource metering can handle large gaps between deposits. function testFuzz_metered_largeBlockDiff_succeeds(uint64 _amount, uint256 _blockDiff) external { - // This test fails if the following line is commented out. - // At 12 seconds per block, this number is effectively unreachable. - _blockDiff = uint256(bound(_blockDiff, 0, 433576281058164217753225238677900874458690)); + // Bounds the empty block gap to values that keep the base fee decay math in range. + _blockDiff = uint256(bound(_blockDiff, 0, MAX_EMPTY_BLOCK_GAP)); ResourceMetering.ResourceConfig memory rcfg = meter.resourceConfig(); _amount = uint64(bound(_amount, 0, rcfg.maxResourceLimit)); - vm.roll(initialBlockNum + _blockDiff); + vm.roll(block.number + _blockDiff); meter.use(_amount); } @@ -254,7 +236,7 @@ contract ResourceMetering_ResourceMeteringInit_Test is ResourceMetering_TestInit assertEq(prevBaseFee, rcfg.minimumBaseFee); assertEq(prevBoughtGas, 0); - assertEq(prevBlockNum, initialBlockNum); + assertEq(prevBlockNum, block.number); } /// @notice Tests that reinitializing the resource params are set correctly. @@ -352,16 +334,13 @@ contract ArtifactResourceMetering_Metered_Test is Test { vm.fee(l1BaseFee); - CustomMeterUser meter = new CustomMeterUser({ - _prevBaseFee: prevBaseFee, - _prevBoughtGas: prevBoughtGas, - _prevBlockNum: uint64(block.number) - }); + MeterUser meter = new MeterUser(); + meter.set(prevBaseFee, prevBoughtGas, uint64(block.number)); vm.roll(block.number + prevBlockNumDiff); uint256 gasConsumed = 0; - try meter.use{ gas: 30_000_000 }(requestedGas) returns (uint256 gasConsumed_) { + try meter.measuredUse{ gas: 30_000_000 }(requestedGas) returns (uint256 gasConsumed_) { gasConsumed = gasConsumed_; } catch (bytes memory err) { bytes4 selector; From 0cfdd005f595689aed52440887950f679f58f03e Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 09:39:37 -0400 Subject: [PATCH 076/135] Refactor SuperchainConfig tests: introduce internal helper function for pausing as guardian, replace hardcoded pause expiry with constant, and streamline pause-related assertions for improved clarity and maintainability. --- test/L1/SuperchainConfig.t.sol | 127 +++++++++++---------------------- 1 file changed, 42 insertions(+), 85 deletions(-) diff --git a/test/L1/SuperchainConfig.t.sol b/test/L1/SuperchainConfig.t.sol index 95f5b1bc..8b6ec06d 100644 --- a/test/L1/SuperchainConfig.t.sol +++ b/test/L1/SuperchainConfig.t.sol @@ -10,10 +10,17 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; /// @title SuperchainConfig_TestInit /// @notice Initialization contract for SuperchainConfig tests. abstract contract SuperchainConfig_TestInit is CommonTest { + uint256 internal constant PAUSE_EXPIRY = 7_884_000; + function setUp() public virtual override { super.setUp(); skipIfForkTest("SuperchainConfig_TestInit: cannot test initialization on forked network"); } + + function _pauseAsGuardian(address _identifier) internal { + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(_identifier); + } } /// @title SuperchainConfig_Constructor_Test @@ -31,7 +38,7 @@ contract SuperchainConfig_Constructor_Test is SuperchainConfig_TestInit { contract SuperchainConfig_PauseExpiry_Test is SuperchainConfig_TestInit { /// @notice Tests that `pauseExpiry` returns the correct constant value. function test_pauseExpiry_succeeds() external view { - assertEq(superchainConfig.pauseExpiry(), 7_884_000); + assertEq(superchainConfig.pauseExpiry(), PAUSE_EXPIRY); } } @@ -40,67 +47,41 @@ contract SuperchainConfig_PauseExpiry_Test is SuperchainConfig_TestInit { contract SuperchainConfig_Paused_Test is SuperchainConfig_TestInit { /// @notice Tests that `paused` returns true when the specific identifier is paused. /// @param _identifier The identifier to test. - function testFuzz_paused_specificIdentifier_succeeds(address _identifier) external { - // Assume the identifier is not the zero address. + /// @param _other The unpaused identifier to test. + function testFuzz_paused_specificIdentifier_succeeds(address _identifier, address _other) external { vm.assume(_identifier != address(0)); + vm.assume(_other != _identifier); - // Pause with the specific identifier. - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); - - // Assert that the specific identifier is paused. + _pauseAsGuardian(_identifier); assertTrue(superchainConfig.paused(_identifier)); - - // Pick a random address that is not the identifier. - address other = vm.randomAddress(); - while (other == _identifier) { - other = vm.randomAddress(); - } - - // Assert that the other address is not paused. - assertFalse(superchainConfig.paused(other)); + assertFalse(superchainConfig.paused(_other)); } /// @notice Tests that `paused` returns true when the global superchain system is paused. function test_paused_global_succeeds() external { - // Pause the global superchain system. - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); + _pauseAsGuardian(address(0)); - // Assert that the global superchain system is paused. assertTrue(superchainConfig.paused()); assertTrue(superchainConfig.paused(address(0))); - - // Pick a random address that is not the zero address. - address other = vm.randomAddress(); - while (other == address(0)) { - other = vm.randomAddress(); - } - - // Assert that the other address is not paused. - assertFalse(superchainConfig.paused(other)); + assertFalse(superchainConfig.paused(address(1))); } /// @notice Tests that `paused` returns false after pause expires. /// @param _identifier The identifier to test. function testFuzz_paused_expired_succeeds(address _identifier) external { - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); + _pauseAsGuardian(_identifier); assertTrue(superchainConfig.paused(_identifier)); - // Warp past expiry - vm.warp(block.timestamp + superchainConfig.pauseExpiry() + 1); + vm.warp(block.timestamp + PAUSE_EXPIRY + 1); assertFalse(superchainConfig.paused(_identifier)); } /// @notice Tests that `paused` returns true just before expiry. /// @param _identifier The identifier to test. function testFuzz_paused_beforeExpiry_succeeds(address _identifier) external { - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); + _pauseAsGuardian(_identifier); - // Warp to just before expiry - vm.warp(block.timestamp + superchainConfig.pauseExpiry() - 1); + vm.warp(block.timestamp + PAUSE_EXPIRY - 1); assertTrue(superchainConfig.paused(_identifier)); } } @@ -130,18 +111,15 @@ contract SuperchainConfig_Pause_Test is SuperchainConfig_TestInit { vm.expectRevert(ISuperchainConfig.SuperchainConfig_OnlyGuardianOrIncidentResponder.selector); vm.prank(_caller); superchainConfig.pause(address(this)); - - assertFalse(superchainConfig.paused(address(this))); } /// @notice Tests that `pause` reverts when the identifier is already used. /// @param _identifier The identifier to test. function testFuzz_pause_alreadyUsed_reverts(address _identifier) external { - vm.startPrank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); + _pauseAsGuardian(_identifier); + vm.prank(superchainConfig.guardian()); vm.expectRevert(abi.encodeWithSelector(ISuperchainConfig.SuperchainConfig_AlreadyPaused.selector, _identifier)); - superchainConfig.pause(_identifier); } } @@ -152,12 +130,12 @@ contract SuperchainConfig_Unpause_Test is SuperchainConfig_TestInit { /// @notice Tests that `unpause` successfully unpauses when called by the guardian. /// @param _identifier The identifier to test. function testFuzz_unpause_succeeds(address _identifier) external { - vm.startPrank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); + _pauseAsGuardian(_identifier); assertTrue(superchainConfig.paused(_identifier)); vm.expectEmit(address(superchainConfig)); emit Unpaused(_identifier); + vm.prank(superchainConfig.guardian()); superchainConfig.unpause(_identifier); assertFalse(superchainConfig.paused(_identifier)); @@ -168,15 +146,12 @@ contract SuperchainConfig_Unpause_Test is SuperchainConfig_TestInit { function testFuzz_unpause_notGuardian_reverts(address _caller) external { vm.assume(_caller != superchainConfig.guardian()); - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(this)); + _pauseAsGuardian(address(this)); assertTrue(superchainConfig.paused(address(this))); vm.expectRevert(ISuperchainConfig.SuperchainConfig_OnlyGuardian.selector); vm.prank(_caller); superchainConfig.unpause(address(this)); - - assertTrue(superchainConfig.paused(address(this))); } } @@ -186,14 +161,14 @@ contract SuperchainConfig_Extend_Test is SuperchainConfig_TestInit { /// @notice Tests that `extend` successfully resets and re-pauses an identifier. /// @param _identifier The identifier to test. function testFuzz_extend_succeeds(address _identifier) external { - vm.startPrank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); + _pauseAsGuardian(_identifier); uint256 firstPauseTimestamp = block.timestamp; vm.warp(block.timestamp + 1); vm.expectEmit(address(superchainConfig)); emit PauseExtended(_identifier); + vm.prank(superchainConfig.guardian()); superchainConfig.extend(_identifier); assertTrue(superchainConfig.pauseTimestamps(_identifier) > firstPauseTimestamp); assertTrue(superchainConfig.paused(_identifier)); @@ -204,11 +179,10 @@ contract SuperchainConfig_Extend_Test is SuperchainConfig_TestInit { function testFuzz_extend_notGuardian_reverts(address _caller) external { vm.assume(_caller != superchainConfig.guardian()); - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(this)); + _pauseAsGuardian(address(this)); - vm.prank(_caller); vm.expectRevert(ISuperchainConfig.SuperchainConfig_OnlyGuardian.selector); + vm.prank(_caller); superchainConfig.extend(address(this)); } @@ -235,23 +209,19 @@ contract SuperchainConfig_Pausable_Test is SuperchainConfig_TestInit { /// @notice Tests that `pausable` returns false when the identifier is paused. /// @param _identifier The identifier to test. function testFuzz_pausable_paused_succeeds(address _identifier) external { - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); + _pauseAsGuardian(_identifier); assertFalse(superchainConfig.pausable(_identifier)); } /// @notice Tests that `pausable` returns false even after pause expires. /// @param _identifier The identifier to test. function testFuzz_pausable_expired_succeeds(address _identifier) external { - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); + _pauseAsGuardian(_identifier); - // Warp past expiry - vm.warp(block.timestamp + superchainConfig.pauseExpiry() + 1); + vm.warp(block.timestamp + PAUSE_EXPIRY + 1); - // pausable() should still return false because timestamp is set + // Expired pauses remain unpausable because the timestamp stays set. assertFalse(superchainConfig.pausable(_identifier)); - // But paused() should return false because pause expired assertFalse(superchainConfig.paused(_identifier)); } } @@ -277,18 +247,17 @@ contract SuperchainConfig_PauseTimestamps_Test is SuperchainConfig_TestInit { /// @notice Tests that `pauseTimestamps` returns the correct timestamp for paused identifiers. /// @param _identifier The identifier to test. function testFuzz_pauseTimestamps_paused_succeeds(address _identifier) external { - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); + _pauseAsGuardian(_identifier); assertEq(superchainConfig.pauseTimestamps(_identifier), block.timestamp); } /// @notice Tests that `pauseTimestamps` returns 0 after unpausing. /// @param _identifier The identifier to test. function testFuzz_pauseTimestamps_afterUnpause_succeeds(address _identifier) external { - vm.startPrank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); + _pauseAsGuardian(_identifier); assertTrue(superchainConfig.pauseTimestamps(_identifier) != 0); + vm.prank(superchainConfig.guardian()); superchainConfig.unpause(_identifier); assertEq(superchainConfig.pauseTimestamps(_identifier), 0); } @@ -314,39 +283,27 @@ contract SuperchainConfig_Expiration_Test is SuperchainConfig_TestInit { /// @notice Tests that `expiration` returns the correct timestamp when the identifier is /// paused. function test_expiration_paused_succeeds() external { - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(this)); - uint256 expectedExpiration = block.timestamp + superchainConfig.pauseExpiry(); - assertEq(superchainConfig.expiration(address(this)), expectedExpiration); + _pauseAsGuardian(address(this)); + assertEq(superchainConfig.expiration(address(this)), block.timestamp + PAUSE_EXPIRY); } /// @notice Tests that `expiration` returns the updated timestamp after extending the pause. function test_expiration_afterExtend_succeeds() external { - vm.startPrank(superchainConfig.guardian()); - superchainConfig.pause(address(this)); - uint256 firstExpiration = superchainConfig.expiration(address(this)); - - // Warp forward in time + _pauseAsGuardian(address(this)); vm.warp(block.timestamp + 100); - // Extend the pause + vm.prank(superchainConfig.guardian()); superchainConfig.extend(address(this)); - uint256 newExpiration = superchainConfig.expiration(address(this)); - assertTrue(newExpiration > firstExpiration); - assertEq(newExpiration, block.timestamp + superchainConfig.pauseExpiry()); + assertEq(superchainConfig.expiration(address(this)), block.timestamp + PAUSE_EXPIRY); } /// @notice Tests that `expiration` works correctly with fuzzed identifiers. /// @param _identifier The identifier to test. function testFuzz_expiration_succeeds(address _identifier) external { - // Test unpaused state assertEq(superchainConfig.expiration(_identifier), 0); - // Test paused state - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(_identifier); - uint256 expectedExpiration = block.timestamp + superchainConfig.pauseExpiry(); - assertEq(superchainConfig.expiration(_identifier), expectedExpiration); + _pauseAsGuardian(_identifier); + assertEq(superchainConfig.expiration(_identifier), block.timestamp + PAUSE_EXPIRY); } } From 132808e374f1155ed1c098b4b12cf2d4e2e89cc8 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 09:51:36 -0400 Subject: [PATCH 077/135] Refactor SystemConfig tests: introduce internal helper functions for resetting initialization state and configuring system parameters, streamline assertions for improved clarity and maintainability. --- test/L1/SystemConfig.t.sol | 214 ++++++++++++------------------------- 1 file changed, 70 insertions(+), 144 deletions(-) diff --git a/test/L1/SystemConfig.t.sol b/test/L1/SystemConfig.t.sol index f51bfc8b..796edaed 100644 --- a/test/L1/SystemConfig.t.sol +++ b/test/L1/SystemConfig.t.sol @@ -24,6 +24,7 @@ abstract contract SystemConfig_TestInit is CommonTest { event ConfigUpdate(uint256 indexed version, ISystemConfig.UpdateType indexed updateType, bytes data); bytes32 public constant EXAMPLE_FEATURE = "EXAMPLE_FEATURE"; + bytes32 internal constant INITIALIZED_SLOT = bytes32(0); address owner; bytes32 batcherHash; @@ -45,6 +46,42 @@ abstract contract SystemConfig_TestInit is CommonTest { systemConfigImpl = EIP1967Helper.getImplementation(address(systemConfig)); optimismMintableERC20Factory = artifacts.mustGetAddress("OptimismMintableERC20FactoryProxy"); } + + function _resetInitialized() internal { + vm.store(address(systemConfig), INITIALIZED_SLOT, bytes32(0)); + } + + function _initializedValue() internal view returns (uint8) { + bytes32 slotVal = vm.load(address(systemConfig), INITIALIZED_SLOT); + return uint8(uint256(slotVal) & 0xFF); + } + + function _emptyAddresses() internal pure returns (ISystemConfig.Addresses memory) { + return ISystemConfig.Addresses({ + l1CrossDomainMessenger: address(0), + l1ERC721Bridge: address(0), + l1StandardBridge: address(0), + optimismPortal: address(0), + optimismMintableERC20Factory: address(0), + delayedWETH: address(0) + }); + } + + function _initializeSystemConfig(uint64 _gasLimit, IResourceMetering.ResourceConfig memory _config) internal { + systemConfig.initialize({ + _owner: alice, + _basefeeScalar: basefeeScalar, + _blobbasefeeScalar: blobbasefeeScalar, + _batcherHash: bytes32(hex"abcd"), + _gasLimit: _gasLimit, + _unsafeBlockSigner: address(1), + _config: _config, + _batchInbox: address(0), + _addresses: _emptyAddresses(), + _l2ChainId: 1234, + _superchainConfig: ISuperchainConfig(address(0)) + }); + } } /// @title SystemConfig_Version_Test @@ -139,48 +176,18 @@ contract SystemConfig_Initialize_Test is SystemConfig_TestInit { function test_initialize_lowGasLimit_reverts() external { uint64 minimumGasLimit = systemConfig.minimumGasLimit(); - // Wipe out the initialized slot so the proxy can be initialized again - vm.store(address(systemConfig), bytes32(0), bytes32(0)); - - address admin = address(uint160(uint256(vm.load(address(systemConfig), Constants.PROXY_OWNER_ADDRESS)))); - vm.prank(admin); + _resetInitialized(); + vm.prank(address(systemConfig.proxyAdmin())); vm.expectRevert("SystemConfig: gas limit too low"); - systemConfig.initialize({ - _owner: alice, - _basefeeScalar: basefeeScalar, - _blobbasefeeScalar: blobbasefeeScalar, - _batcherHash: bytes32(hex"abcd"), - _gasLimit: minimumGasLimit - 1, - _unsafeBlockSigner: address(1), - _config: Constants.DEFAULT_RESOURCE_CONFIG(), - _batchInbox: address(0), - _addresses: ISystemConfig.Addresses({ - l1CrossDomainMessenger: address(0), - l1ERC721Bridge: address(0), - l1StandardBridge: address(0), - optimismPortal: address(0), - optimismMintableERC20Factory: address(0), - delayedWETH: address(0) - }), - _l2ChainId: 1234, - _superchainConfig: ISuperchainConfig(address(0)) - }); + _initializeSystemConfig(minimumGasLimit - 1, Constants.DEFAULT_RESOURCE_CONFIG()); } /// @notice Tests that the initializer value is correct. Trivial test for normal /// initialization but confirms that the initValue is not incremented incorrectly if /// an upgrade function is not present. function test_initialize_correctInitializerValue_succeeds() public view { - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("SystemConfig", "_initialized"); - - // Get the initializer value. - bytes32 slotVal = vm.load(address(systemConfig), bytes32(slot.slot)); - uint8 val = uint8(uint256(slotVal) & 0xFF); - - // Assert that the initializer value matches the expected value. - assertEq(val, systemConfig.initVersion()); + assertEq(_initializedValue(), systemConfig.initVersion()); } /// @notice Tests that `initialize` reverts if called by a non-proxy admin or owner. @@ -189,40 +196,16 @@ contract SystemConfig_Initialize_Test is SystemConfig_TestInit { // Prank as the not ProxyAdmin or ProxyAdmin owner. vm.assume(_sender != address(systemConfig.proxyAdmin()) && _sender != systemConfig.proxyAdminOwner()); - // Get the slot for _initialized. - StorageSlot memory slot = ForgeArtifacts.getSlot("SystemConfig", "_initialized"); - - // Set the initialized slot to 0. - vm.store(address(systemConfig), bytes32(slot.slot), bytes32(0)); + _resetInitialized(); // Get the minimum gas limit. uint64 minimumGasLimit = systemConfig.minimumGasLimit(); // Expect the revert with `ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner` selector. + vm.prank(_sender); vm.expectRevert(IProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); - // Call the `initialize` function with the sender - vm.prank(_sender); - systemConfig.initialize({ - _owner: alice, - _basefeeScalar: basefeeScalar, - _blobbasefeeScalar: blobbasefeeScalar, - _batcherHash: bytes32(hex"abcd"), - _gasLimit: minimumGasLimit - 1, - _unsafeBlockSigner: address(1), - _config: Constants.DEFAULT_RESOURCE_CONFIG(), - _batchInbox: address(0), - _addresses: ISystemConfig.Addresses({ - l1CrossDomainMessenger: address(0), - l1ERC721Bridge: address(0), - l1StandardBridge: address(0), - optimismPortal: address(0), - optimismMintableERC20Factory: address(0), - delayedWETH: address(0) - }), - _l2ChainId: 1234, - _superchainConfig: ISuperchainConfig(address(0)) - }); + _initializeSystemConfig(minimumGasLimit - 1, Constants.DEFAULT_RESOURCE_CONFIG()); } } @@ -231,65 +214,25 @@ contract SystemConfig_Initialize_Test is SystemConfig_TestInit { contract SystemConfig_StartBlock_Test is SystemConfig_TestInit { /// @notice Tests that startBlock is updated correctly when it's zero. function test_startBlock_update_succeeds() external { - // Wipe out the initialized slot so the proxy can be initialized again - vm.store(address(systemConfig), bytes32(0), bytes32(0)); + _resetInitialized(); // Set slot startBlock to zero vm.store(address(systemConfig), systemConfig.START_BLOCK_SLOT(), bytes32(uint256(0))); // Initialize and check that StartBlock updates to current block number vm.prank(address(systemConfig.proxyAdmin())); - systemConfig.initialize({ - _owner: alice, - _basefeeScalar: basefeeScalar, - _blobbasefeeScalar: blobbasefeeScalar, - _batcherHash: bytes32(hex"abcd"), - _gasLimit: gasLimit, - _unsafeBlockSigner: address(1), - _config: Constants.DEFAULT_RESOURCE_CONFIG(), - _batchInbox: address(0), - _addresses: ISystemConfig.Addresses({ - l1CrossDomainMessenger: address(0), - l1ERC721Bridge: address(0), - l1StandardBridge: address(0), - optimismPortal: address(0), - optimismMintableERC20Factory: address(0), - delayedWETH: address(0) - }), - _l2ChainId: 1234, - _superchainConfig: ISuperchainConfig(address(0)) - }); + _initializeSystemConfig(gasLimit, Constants.DEFAULT_RESOURCE_CONFIG()); assertEq(systemConfig.startBlock(), block.number); } /// @notice Tests that startBlock is not updated when it's not zero. function test_startBlock_update_fails() external { - // Wipe out the initialized slot so the proxy can be initialized again - vm.store(address(systemConfig), bytes32(0), bytes32(0)); + _resetInitialized(); // Set slot startBlock to non-zero value 1 vm.store(address(systemConfig), systemConfig.START_BLOCK_SLOT(), bytes32(uint256(1))); // Initialize and check that StartBlock doesn't update vm.prank(address(systemConfig.proxyAdmin())); - systemConfig.initialize({ - _owner: alice, - _basefeeScalar: basefeeScalar, - _blobbasefeeScalar: blobbasefeeScalar, - _batcherHash: bytes32(hex"abcd"), - _gasLimit: gasLimit, - _unsafeBlockSigner: address(1), - _config: Constants.DEFAULT_RESOURCE_CONFIG(), - _batchInbox: address(0), - _addresses: ISystemConfig.Addresses({ - l1CrossDomainMessenger: address(0), - l1ERC721Bridge: address(0), - l1StandardBridge: address(0), - optimismPortal: address(0), - optimismMintableERC20Factory: address(0), - delayedWETH: address(0) - }), - _l2ChainId: 1234, - _superchainConfig: ISuperchainConfig(address(0)) - }); + _initializeSystemConfig(gasLimit, Constants.DEFAULT_RESOURCE_CONFIG()); assertEq(systemConfig.startBlock(), 1); } } @@ -392,8 +335,7 @@ contract SystemConfig_SetGasConfigEcotone_Test is SystemConfig_TestInit { } function testFuzz_setGasConfigEcotone_succeeds(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external { - bytes32 encoded = - ffi.encodeScalarEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar }); + uint256 encoded = (uint256(0x01) << 248) | (uint256(_blobbasefeeScalar) << 32) | _basefeeScalar; vm.expectEmit(address(systemConfig)); emit ConfigUpdate(0, ISystemConfig.UpdateType.FEE_SCALARS, abi.encode(systemConfig.overhead(), encoded)); @@ -402,11 +344,10 @@ contract SystemConfig_SetGasConfigEcotone_Test is SystemConfig_TestInit { systemConfig.setGasConfigEcotone({ _basefeeScalar: _basefeeScalar, _blobbasefeeScalar: _blobbasefeeScalar }); assertEq(systemConfig.basefeeScalar(), _basefeeScalar); assertEq(systemConfig.blobbasefeeScalar(), _blobbasefeeScalar); - assertEq(systemConfig.scalar(), uint256(encoded)); + assertEq(systemConfig.scalar(), encoded); - (uint32 basefeeScalar, uint32 blobbbasefeeScalar) = ffi.decodeScalarEcotone(encoded); - assertEq(uint256(basefeeScalar), uint256(_basefeeScalar)); - assertEq(uint256(blobbbasefeeScalar), uint256(_blobbasefeeScalar)); + assertEq(uint32(encoded), _basefeeScalar); + assertEq(uint32(encoded >> 32), _blobbasefeeScalar); } } @@ -579,9 +520,8 @@ contract SystemConfig_SetResourceConfig_Test is SystemConfig_TestInit { ) internal { - // Wipe out the initialized slot so the proxy can be initialized again - vm.store(address(systemConfig), bytes32(0), bytes32(0)); - // Fetch the current gas limit + _resetInitialized(); + uint64 gasLimit = systemConfig.gasLimit(); vm.prank(address(systemConfig.proxyAdmin())); @@ -595,14 +535,7 @@ contract SystemConfig_SetResourceConfig_Test is SystemConfig_TestInit { _unsafeBlockSigner: address(0), _config: config, _batchInbox: address(0), - _addresses: ISystemConfig.Addresses({ - l1CrossDomainMessenger: address(0), - l1ERC721Bridge: address(0), - l1StandardBridge: address(0), - optimismPortal: address(0), - optimismMintableERC20Factory: address(0), - delayedWETH: address(0) - }), + _addresses: _emptyAddresses(), _l2ChainId: 1234, _superchainConfig: ISuperchainConfig(address(0)) }); @@ -700,6 +633,15 @@ contract SystemConfig_Paused_Test is SystemConfig_TestInit { contract SystemConfig_SetFeature_Test is SystemConfig_TestInit { event FeatureSet(bytes32 indexed feature, bool indexed enabled); + function _enableEthLockboxIfDisabled(address proxyAdmin) internal { + if (!systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)) { + vm.prank(proxyAdmin); + systemConfig.setFeature(Features.ETH_LOCKBOX, true); + } + + assertTrue(systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)); + } + /// @notice Tests that `setFeature` reverts if the caller is not ProxyAdmin or ProxyAdmin owner. /// @param _sender The address to test. function testFuzz_setFeature_notProxyAdminOrProxyAdminOwner_reverts(address _sender) external { @@ -789,7 +731,7 @@ contract SystemConfig_SetFeature_Test is SystemConfig_TestInit { function test_setFeature_alreadyDisabled_reverts() external { vm.prank(address(systemConfig.proxyAdmin())); vm.expectRevert(ISystemConfig.SystemConfig_InvalidFeatureState.selector); - systemConfig.setFeature("EXAMPLE FEATURE", false); + systemConfig.setFeature(EXAMPLE_FEATURE, false); } /// @notice Tests that disabling ETH_LOCKBOX reverts if the OptimismPortal has a non-zero @@ -797,12 +739,7 @@ contract SystemConfig_SetFeature_Test is SystemConfig_TestInit { function test_setFeature_ethLockboxDisableWhileConfigured_reverts() external { address proxyAdmin = address(systemConfig.proxyAdmin()); - // Ensure ETH_LOCKBOX is enabled first (no pause active in fresh setup). - if (!systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)) { - vm.prank(proxyAdmin); - systemConfig.setFeature(Features.ETH_LOCKBOX, true); - assertTrue(systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)); - } + _enableEthLockboxIfDisabled(proxyAdmin); // Force the portal to have a configured ETHLockbox address. StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "ethLockbox"); @@ -818,12 +755,7 @@ contract SystemConfig_SetFeature_Test is SystemConfig_TestInit { function test_setFeature_ethLockboxEnableWhilePaused_reverts() external { address proxyAdmin = address(systemConfig.proxyAdmin()); - // Ensure ETH_LOCKBOX is enabled first (no pause active in fresh setup). - if (!systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)) { - vm.prank(proxyAdmin); - systemConfig.setFeature(Features.ETH_LOCKBOX, true); - assertTrue(systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)); - } + _enableEthLockboxIfDisabled(proxyAdmin); // Pause globally. vm.prank(superchainConfig.guardian()); @@ -839,12 +771,7 @@ contract SystemConfig_SetFeature_Test is SystemConfig_TestInit { function test_setFeature_ethLockboxDisableWhilePaused_reverts() external { address proxyAdmin = address(systemConfig.proxyAdmin()); - // Ensure ETH_LOCKBOX is enabled first. - if (!systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)) { - vm.prank(proxyAdmin); - systemConfig.setFeature(Features.ETH_LOCKBOX, true); - assertTrue(systemConfig.isFeatureEnabled(Features.ETH_LOCKBOX)); - } + _enableEthLockboxIfDisabled(proxyAdmin); // Pause globally. vm.prank(superchainConfig.guardian()); @@ -897,9 +824,8 @@ contract SystemConfig_Guardian_Test is SystemConfig_TestInit { } } -/// @title SystemConfig_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the `SystemConfig` contract -/// are testing multiple functions at once. +/// @title SystemConfig_SuperchainConfig_Test +/// @notice Test contract for SystemConfig `superchainConfig` function. contract SystemConfig_SuperchainConfig_Test is SystemConfig_TestInit { /// @notice Tests that `superchainConfig()` returns the correct address. function test_superchainConfig_succeeds() external view { From bcef5745924ba6a74096815492c6b26bb46068b7 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 11:22:54 -0400 Subject: [PATCH 078/135] Refactor BaseFeeVault tests: remove hardcoded feeVaultName assignment to enhance clarity and maintainability of test setup. --- test/L2/BaseFeeVault.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/L2/BaseFeeVault.t.sol b/test/L2/BaseFeeVault.t.sol index 48b48648..4abf4424 100644 --- a/test/L2/BaseFeeVault.t.sol +++ b/test/L2/BaseFeeVault.t.sol @@ -18,7 +18,6 @@ contract BaseFeeVault_Uncategorized_Test is FeeVault_Uncategorized_Test { function setUp() public virtual override { super.setUp(); recipient = deploy.cfg().baseFeeVaultRecipient(); - feeVaultName = "BaseFeeVault"; minWithdrawalAmount = deploy.cfg().baseFeeVaultMinimumWithdrawalAmount(); feeVault = IFeeVault(payable(Predeploys.BASE_FEE_VAULT)); withdrawalNetwork = Types.WithdrawalNetwork(uint8(deploy.cfg().baseFeeVaultWithdrawalNetwork())); From b6ac06b092162a85117c6cd0b08b791c1c9b9f8e Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 11:31:18 -0400 Subject: [PATCH 079/135] Refactor FeeDisburser tests: introduce constants for wallet addresses, enhance clarity with internal helper functions for mocking vault withdrawals and bridge expectations, and streamline assertions for improved maintainability. --- test/L2/FeeDisburser.t.sol | 468 ++++++++++++------------------------- 1 file changed, 153 insertions(+), 315 deletions(-) diff --git a/test/L2/FeeDisburser.t.sol b/test/L2/FeeDisburser.t.sol index fea12580..75287a33 100644 --- a/test/L2/FeeDisburser.t.sol +++ b/test/L2/FeeDisburser.t.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.25; import { Test } from "lib/forge-std/src/Test.sol"; -import { console } from "lib/forge-std/src/console.sol"; import { FeeDisburser } from "src/L2/FeeDisburser.sol"; import { IFeeVault, Types } from "interfaces/L2/IFeeVault.sol"; +import { IStandardBridge } from "interfaces/universal/IStandardBridge.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; /// @title FeeDisburserTest @@ -20,18 +20,19 @@ contract FeeDisburserTest is Test { uint32 constant WITHDRAWAL_MIN_GAS = 35_000; uint256 constant DEFAULT_MIN_WITHDRAWAL = 10 ether; uint256 constant DEFAULT_DISBURSEMENT_INTERVAL = 24 hours; + uint256 constant MAX_TEST_FEE = 10 ** 27; // Test addresses - address payable l1Wallet = payable(address(0x1001)); - address alice = address(0xA11CE); - address bob = address(0xB0B); + address payable constant L1_WALLET = payable(address(0x1001)); + address constant ALICE = address(0xA11CE); + address constant BOB = address(0xB0B); // Contract instances FeeDisburser feeDisburser; function setUp() public virtual { // Deploy FeeDisburser - feeDisburser = new FeeDisburser(l1Wallet, DEFAULT_DISBURSEMENT_INTERVAL); + feeDisburser = new FeeDisburser(L1_WALLET, DEFAULT_DISBURSEMENT_INTERVAL); // Warp time to allow first disbursement vm.warp(DEFAULT_DISBURSEMENT_INTERVAL + 1); @@ -57,27 +58,43 @@ contract FeeDisburserTest is Test { ) internal { - vm.mockCall(vault, abi.encodeWithSelector(IFeeVault.WITHDRAWAL_NETWORK.selector), abi.encode(network)); - vm.mockCall(vault, abi.encodeWithSelector(IFeeVault.RECIPIENT.selector), abi.encode(recipient)); - vm.mockCall(vault, abi.encodeWithSelector(IFeeVault.MIN_WITHDRAWAL_AMOUNT.selector), abi.encode(minWithdrawal)); - } - - /// @notice Helper to setup a FeeVault withdrawal (vault sends ETH to recipient on withdraw()) - function _setupVaultWithdrawal(address vault, uint256 balance) internal { - vm.deal(vault, balance); - // Mock withdraw() to send ETH to the feeDisburser - vm.mockCall(vault, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(balance)); - // Make the vault actually send ETH on the mock call - vm.deal(address(feeDisburser), address(feeDisburser).balance + balance); - } - - /// @notice Helper to setup bridge mock - function _mockBridge() internal { - vm.mockCall( - Predeploys.L2_STANDARD_BRIDGE, - abi.encodeWithSignature("bridgeETHTo(address,uint32,bytes)", l1Wallet, WITHDRAWAL_MIN_GAS, bytes("")), - bytes("") - ); + vm.mockCall(vault, abi.encodeCall(IFeeVault.WITHDRAWAL_NETWORK, ()), abi.encode(network)); + vm.mockCall(vault, abi.encodeCall(IFeeVault.RECIPIENT, ()), abi.encode(recipient)); + vm.mockCall(vault, abi.encodeCall(IFeeVault.MIN_WITHDRAWAL_AMOUNT, ()), abi.encode(minWithdrawal)); + } + + function _mockVaultWithdrawal(address vault, uint256 amount) internal { + vm.deal(vault, amount); + vm.mockCall(vault, abi.encodeCall(IFeeVault.withdraw, ()), abi.encode(amount)); + vm.deal(address(feeDisburser), address(feeDisburser).balance + amount); + } + + function _expectBridgeETH(uint256 amount) internal { + bytes memory bridgeCall = + abi.encodeCall(IStandardBridge.bridgeETHTo, (L1_WALLET, WITHDRAWAL_MIN_GAS, bytes(""))); + + vm.mockCall(Predeploys.L2_STANDARD_BRIDGE, bridgeCall, bytes("")); + vm.expectCall(Predeploys.L2_STANDARD_BRIDGE, amount, bridgeCall); + } + + function _resetAfterMockedBridge() internal { + // vm.mockCall returns without moving ETH, so mirror the real bridge transfer between disbursements. + vm.deal(address(feeDisburser), 0); + } + + function _expectFeesDisbursed(uint256 totalFees) internal { + vm.expectEmit(address(feeDisburser)); + emit FeesDisbursed(block.timestamp, 0, totalFees); + } + + function _expectFeesReceived(address sender, uint256 amount) internal { + vm.expectEmit(address(feeDisburser)); + emit FeesReceived(sender, amount); + } + + function _expectNoFeesCollected() internal { + vm.expectEmit(address(feeDisburser)); + emit NoFeesCollected(); } // ============================================================ @@ -85,22 +102,22 @@ contract FeeDisburserTest is Test { // ============================================================ function test_constructor_success() public { - FeeDisburser newDisburser = new FeeDisburser(l1Wallet, DEFAULT_DISBURSEMENT_INTERVAL); + FeeDisburser newDisburser = new FeeDisburser(L1_WALLET, DEFAULT_DISBURSEMENT_INTERVAL); - assertEq(newDisburser.L1_WALLET(), l1Wallet); + assertEq(newDisburser.L1_WALLET(), L1_WALLET); assertEq(newDisburser.FEE_DISBURSEMENT_INTERVAL(), DEFAULT_DISBURSEMENT_INTERVAL); assertEq(newDisburser.WITHDRAWAL_MIN_GAS(), WITHDRAWAL_MIN_GAS); assertEq(newDisburser.lastDisbursementTime(), 0); } function test_constructor_success_exactlyMinimumInterval() public { - FeeDisburser newDisburser = new FeeDisburser(l1Wallet, 24 hours); + FeeDisburser newDisburser = new FeeDisburser(L1_WALLET, 24 hours); assertEq(newDisburser.FEE_DISBURSEMENT_INTERVAL(), 24 hours); } function test_constructor_success_largeInterval() public { uint256 largeInterval = 365 days; - FeeDisburser newDisburser = new FeeDisburser(l1Wallet, largeInterval); + FeeDisburser newDisburser = new FeeDisburser(L1_WALLET, largeInterval); assertEq(newDisburser.FEE_DISBURSEMENT_INTERVAL(), largeInterval); } @@ -111,12 +128,12 @@ contract FeeDisburserTest is Test { function test_constructor_revert_intervalTooLow() public { vm.expectRevert(FeeDisburser.IntervalTooLow.selector); - new FeeDisburser(l1Wallet, 24 hours - 1); + new FeeDisburser(L1_WALLET, 24 hours - 1); } function test_constructor_revert_intervalZero() public { vm.expectRevert(FeeDisburser.IntervalTooLow.selector); - new FeeDisburser(l1Wallet, 0); + new FeeDisburser(L1_WALLET, 0); } // ============================================================ @@ -129,8 +146,7 @@ contract FeeDisburserTest is Test { vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit NoFeesCollected(); + _expectNoFeesCollected(); feeDisburser.disburseFees(); @@ -141,21 +157,13 @@ contract FeeDisburserTest is Test { function test_disburseFees_success_sequencerFeesOnly() public { uint256 feeAmount = DEFAULT_MIN_WITHDRAWAL; - // Setup sequencer vault withdrawal - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, feeAmount); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(feeAmount) - ); - vm.deal(address(feeDisburser), feeAmount); - - // Other vaults below minimum + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, feeAmount); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); - _mockBridge(); + _expectBridgeETH(feeAmount); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesDisbursed(block.timestamp, 0, feeAmount); + _expectFeesDisbursed(feeAmount); feeDisburser.disburseFees(); @@ -165,19 +173,13 @@ contract FeeDisburserTest is Test { function test_disburseFees_success_baseFeesOnly() public { uint256 feeAmount = DEFAULT_MIN_WITHDRAWAL; - // Setup base vault withdrawal vm.deal(Predeploys.SEQUENCER_FEE_WALLET, 0); - vm.deal(Predeploys.BASE_FEE_VAULT, feeAmount); - vm.mockCall( - Predeploys.BASE_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(feeAmount) - ); - vm.deal(address(feeDisburser), feeAmount); + _mockVaultWithdrawal(Predeploys.BASE_FEE_VAULT, feeAmount); vm.deal(Predeploys.L1_FEE_VAULT, 0); - _mockBridge(); + _expectBridgeETH(feeAmount); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesDisbursed(block.timestamp, 0, feeAmount); + _expectFeesDisbursed(feeAmount); feeDisburser.disburseFees(); @@ -187,17 +189,13 @@ contract FeeDisburserTest is Test { function test_disburseFees_success_l1FeesOnly() public { uint256 feeAmount = DEFAULT_MIN_WITHDRAWAL; - // Setup l1 fee vault withdrawal vm.deal(Predeploys.SEQUENCER_FEE_WALLET, 0); vm.deal(Predeploys.BASE_FEE_VAULT, 0); - vm.deal(Predeploys.L1_FEE_VAULT, feeAmount); - vm.mockCall(Predeploys.L1_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(feeAmount)); - vm.deal(address(feeDisburser), feeAmount); + _mockVaultWithdrawal(Predeploys.L1_FEE_VAULT, feeAmount); - _mockBridge(); + _expectBridgeETH(feeAmount); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesDisbursed(block.timestamp, 0, feeAmount); + _expectFeesDisbursed(feeAmount); feeDisburser.disburseFees(); @@ -210,29 +208,13 @@ contract FeeDisburserTest is Test { uint256 l1Fees = DEFAULT_MIN_WITHDRAWAL * 3; uint256 totalFees = sequencerFees + baseFees + l1Fees; - // Setup all vault withdrawals - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, sequencerFees); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(sequencerFees) - ); - - vm.deal(Predeploys.BASE_FEE_VAULT, baseFees); - vm.mockCall( - Predeploys.BASE_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(baseFees) - ); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, sequencerFees); + _mockVaultWithdrawal(Predeploys.BASE_FEE_VAULT, baseFees); + _mockVaultWithdrawal(Predeploys.L1_FEE_VAULT, l1Fees); - vm.deal(Predeploys.L1_FEE_VAULT, l1Fees); - vm.mockCall(Predeploys.L1_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(l1Fees)); + _expectBridgeETH(totalFees); - // FeeDisburser receives all the fees - vm.deal(address(feeDisburser), totalFees); - - _mockBridge(); - - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesDisbursed(block.timestamp, 0, totalFees); + _expectFeesDisbursed(totalFees); feeDisburser.disburseFees(); @@ -245,8 +227,7 @@ contract FeeDisburserTest is Test { vm.deal(Predeploys.BASE_FEE_VAULT, DEFAULT_MIN_WITHDRAWAL - 1); vm.deal(Predeploys.L1_FEE_VAULT, DEFAULT_MIN_WITHDRAWAL - 1); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit NoFeesCollected(); + _expectNoFeesCollected(); feeDisburser.disburseFees(); @@ -257,39 +238,24 @@ contract FeeDisburserTest is Test { // Only sequencer vault has enough uint256 sequencerFees = DEFAULT_MIN_WITHDRAWAL; - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, sequencerFees); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(sequencerFees) - ); - vm.deal(address(feeDisburser), sequencerFees); - + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, sequencerFees); vm.deal(Predeploys.BASE_FEE_VAULT, DEFAULT_MIN_WITHDRAWAL - 1); vm.deal(Predeploys.L1_FEE_VAULT, DEFAULT_MIN_WITHDRAWAL - 1); - _mockBridge(); + _expectBridgeETH(sequencerFees); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesDisbursed(block.timestamp, 0, sequencerFees); + _expectFeesDisbursed(sequencerFees); feeDisburser.disburseFees(); } function test_disburseFees_revert_intervalNotReached() public { // First successful disbursal - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); - + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); - _mockBridge(); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); @@ -300,17 +266,11 @@ contract FeeDisburserTest is Test { function test_disburseFees_revert_intervalNotReached_oneSecondBefore() public { // First disbursal - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); - _mockBridge(); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); @@ -323,54 +283,41 @@ contract FeeDisburserTest is Test { function test_disburseFees_success_exactlyAtInterval() public { // First disbursal - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); - _mockBridge(); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); uint256 firstDisbursementTime = block.timestamp; + _resetAfterMockedBridge(); // Warp exactly to the interval vm.warp(firstDisbursementTime + DEFAULT_DISBURSEMENT_INTERVAL); // Fund vaults again - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); // Should succeed + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); assertEq(feeDisburser.lastDisbursementTime(), firstDisbursementTime + DEFAULT_DISBURSEMENT_INTERVAL); } function test_disburseFees_success_multipleDisbursements() public { - _mockBridge(); - - uint256 currentTime = block.timestamp; for (uint256 i = 0; i < 5; i++) { - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); - assertEq(feeDisburser.lastDisbursementTime(), currentTime); + assertEq(feeDisburser.lastDisbursementTime(), block.timestamp); + _resetAfterMockedBridge(); // Warp past interval for next iteration - currentTime += DEFAULT_DISBURSEMENT_INTERVAL; - vm.warp(currentTime); + vm.warp(block.timestamp + DEFAULT_DISBURSEMENT_INTERVAL); } } @@ -385,8 +332,8 @@ contract FeeDisburserTest is Test { } function test_disburseFees_revert_feeVaultWrongRecipient() public { - // Mock sequencer vault with wrong recipient (alice instead of feeDisburser) - _mockFeeVault(Predeploys.SEQUENCER_FEE_WALLET, alice, DEFAULT_MIN_WITHDRAWAL, Types.WithdrawalNetwork.L2); + // Mock sequencer vault with wrong recipient (ALICE instead of feeDisburser) + _mockFeeVault(Predeploys.SEQUENCER_FEE_WALLET, ALICE, DEFAULT_MIN_WITHDRAWAL, Types.WithdrawalNetwork.L2); vm.expectRevert(FeeDisburser.FeeVaultMustWithdrawToFeeDisburser.selector); feeDisburser.disburseFees(); @@ -414,7 +361,7 @@ contract FeeDisburserTest is Test { vm.deal(Predeploys.BASE_FEE_VAULT, 0); // L1 vault has wrong recipient - _mockFeeVault(Predeploys.L1_FEE_VAULT, bob, DEFAULT_MIN_WITHDRAWAL, Types.WithdrawalNetwork.L2); + _mockFeeVault(Predeploys.L1_FEE_VAULT, BOB, DEFAULT_MIN_WITHDRAWAL, Types.WithdrawalNetwork.L2); vm.expectRevert(FeeDisburser.FeeVaultMustWithdrawToFeeDisburser.selector); feeDisburser.disburseFees(); @@ -426,12 +373,11 @@ contract FeeDisburserTest is Test { function test_receive_success_anyAddress() public { uint256 amount = 1 ether; - vm.deal(alice, amount); + vm.deal(ALICE, amount); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesReceived(alice, amount); + _expectFeesReceived(ALICE, amount); - vm.prank(alice); + vm.prank(ALICE); (bool success,) = address(feeDisburser).call{ value: amount }(""); assertTrue(success); @@ -442,8 +388,7 @@ contract FeeDisburserTest is Test { uint256 amount = 5 ether; vm.deal(Predeploys.SEQUENCER_FEE_WALLET, amount); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesReceived(Predeploys.SEQUENCER_FEE_WALLET, amount); + _expectFeesReceived(Predeploys.SEQUENCER_FEE_WALLET, amount); vm.prank(Predeploys.SEQUENCER_FEE_WALLET); (bool success,) = address(feeDisburser).call{ value: amount }(""); @@ -454,8 +399,7 @@ contract FeeDisburserTest is Test { uint256 amount = 5 ether; vm.deal(Predeploys.BASE_FEE_VAULT, amount); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesReceived(Predeploys.BASE_FEE_VAULT, amount); + _expectFeesReceived(Predeploys.BASE_FEE_VAULT, amount); vm.prank(Predeploys.BASE_FEE_VAULT); (bool success,) = address(feeDisburser).call{ value: amount }(""); @@ -466,8 +410,7 @@ contract FeeDisburserTest is Test { uint256 amount = 5 ether; vm.deal(Predeploys.L1_FEE_VAULT, amount); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesReceived(Predeploys.L1_FEE_VAULT, amount); + _expectFeesReceived(Predeploys.L1_FEE_VAULT, amount); vm.prank(Predeploys.L1_FEE_VAULT); (bool success,) = address(feeDisburser).call{ value: amount }(""); @@ -475,10 +418,9 @@ contract FeeDisburserTest is Test { } function test_receive_success_zeroValue() public { - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesReceived(alice, 0); + _expectFeesReceived(ALICE, 0); - vm.prank(alice); + vm.prank(ALICE); (bool success,) = address(feeDisburser).call{ value: 0 }(""); assertTrue(success); } @@ -489,9 +431,9 @@ contract FeeDisburserTest is Test { for (uint256 i = 1; i <= 5; i++) { uint256 amount = i * 1 ether; totalExpected += amount; - vm.deal(alice, amount); + vm.deal(ALICE, amount); - vm.prank(alice); + vm.prank(ALICE); (bool success,) = address(feeDisburser).call{ value: amount }(""); assertTrue(success); } @@ -517,14 +459,14 @@ contract FeeDisburserTest is Test { vm.assume(_interval < 24 hours); vm.expectRevert(FeeDisburser.IntervalTooLow.selector); - new FeeDisburser(l1Wallet, _interval); + new FeeDisburser(L1_WALLET, _interval); } function testFuzz_disburseFees_varyingAmounts(uint256 _sequencerFees, uint256 _baseFees, uint256 _l1Fees) public { // Bound fees to reasonable values to avoid overflow - _sequencerFees = bound(_sequencerFees, 0, 10 ** 27); - _baseFees = bound(_baseFees, 0, 10 ** 27); - _l1Fees = bound(_l1Fees, 0, 10 ** 27); + _sequencerFees = bound(_sequencerFees, 0, MAX_TEST_FEE); + _baseFees = bound(_baseFees, 0, MAX_TEST_FEE); + _l1Fees = bound(_l1Fees, 0, MAX_TEST_FEE); // Setup vault balances and mocks vm.deal(Predeploys.SEQUENCER_FEE_WALLET, _sequencerFees); @@ -534,37 +476,23 @@ contract FeeDisburserTest is Test { // Calculate expected total (only vaults >= MIN_WITHDRAWAL_AMOUNT will be withdrawn) uint256 expectedTotal = 0; if (_sequencerFees >= DEFAULT_MIN_WITHDRAWAL) { - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(_sequencerFees) - ); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, _sequencerFees); expectedTotal += _sequencerFees; } if (_baseFees >= DEFAULT_MIN_WITHDRAWAL) { - vm.mockCall( - Predeploys.BASE_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(_baseFees) - ); + _mockVaultWithdrawal(Predeploys.BASE_FEE_VAULT, _baseFees); expectedTotal += _baseFees; } if (_l1Fees >= DEFAULT_MIN_WITHDRAWAL) { - vm.mockCall( - Predeploys.L1_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(_l1Fees) - ); + _mockVaultWithdrawal(Predeploys.L1_FEE_VAULT, _l1Fees); expectedTotal += _l1Fees; } - // Give FeeDisburser the expected total - vm.deal(address(feeDisburser), expectedTotal); - - _mockBridge(); - if (expectedTotal == 0) { - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit NoFeesCollected(); + _expectNoFeesCollected(); } else { - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesDisbursed(block.timestamp, 0, expectedTotal); + _expectBridgeETH(expectedTotal); + _expectFeesDisbursed(expectedTotal); } feeDisburser.disburseFees(); @@ -577,38 +505,29 @@ contract FeeDisburserTest is Test { } function testFuzz_disburseFees_multipleIntervals(uint8 _numDisbursements) public { - vm.assume(_numDisbursements > 0 && _numDisbursements <= 50); - - _mockBridge(); - - uint256 currentTime = block.timestamp; - for (uint256 i = 0; i < _numDisbursements; i++) { - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + uint256 numDisbursements = bound(_numDisbursements, 1, 10); + + for (uint256 i = 0; i < numDisbursements; i++) { + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); - assertEq(feeDisburser.lastDisbursementTime(), currentTime); + assertEq(feeDisburser.lastDisbursementTime(), block.timestamp); + _resetAfterMockedBridge(); // Warp past interval for next iteration - currentTime += DEFAULT_DISBURSEMENT_INTERVAL; - vm.warp(currentTime); + vm.warp(block.timestamp + DEFAULT_DISBURSEMENT_INTERVAL); } } function testFuzz_receive_anyAmount(address _sender, uint256 _amount) public { vm.assume(_sender != address(0)); - _amount = bound(_amount, 0, 10 ** 27); + _amount = bound(_amount, 0, MAX_TEST_FEE); vm.deal(_sender, _amount); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesReceived(_sender, _amount); + _expectFeesReceived(_sender, _amount); vm.prank(_sender); (bool success,) = address(feeDisburser).call{ value: _amount }(""); @@ -619,35 +538,30 @@ contract FeeDisburserTest is Test { function testFuzz_intervalEnforcement(uint256 _timeWarp) public { // First disbursal - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); - _mockBridge(); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); uint256 lastTime = feeDisburser.lastDisbursementTime(); + _resetAfterMockedBridge(); // Bound time warp to avoid overflow _timeWarp = bound(_timeWarp, 0, 365 days * 100); vm.warp(lastTime + _timeWarp); // Fund again - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); if (_timeWarp < DEFAULT_DISBURSEMENT_INTERVAL) { vm.expectRevert(FeeDisburser.IntervalNotReached.selector); feeDisburser.disburseFees(); } else { // Should succeed + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); assertEq(feeDisburser.lastDisbursementTime(), lastTime + _timeWarp); } @@ -664,33 +578,17 @@ contract FeeDisburserTest is Test { uint256 l1Fees = 200 ether; uint256 totalFees = sequencerFees + baseFees + l1Fees; - // Setup all vault withdrawals - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, sequencerFees); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(sequencerFees) - ); - - vm.deal(Predeploys.BASE_FEE_VAULT, baseFees); - vm.mockCall( - Predeploys.BASE_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(baseFees) - ); - - vm.deal(Predeploys.L1_FEE_VAULT, l1Fees); - vm.mockCall(Predeploys.L1_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(l1Fees)); - - // FeeDisburser receives all the fees - vm.deal(address(feeDisburser), totalFees); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, sequencerFees); + _mockVaultWithdrawal(Predeploys.BASE_FEE_VAULT, baseFees); + _mockVaultWithdrawal(Predeploys.L1_FEE_VAULT, l1Fees); - _mockBridge(); + _expectBridgeETH(totalFees); // Verify initial state assertEq(feeDisburser.lastDisbursementTime(), 0); // Execute disbursement - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesDisbursed(block.timestamp, 0, totalFees); + _expectFeesDisbursed(totalFees); feeDisburser.disburseFees(); @@ -725,20 +623,13 @@ contract FeeDisburserTest is Test { function test_edge_exactMinWithdrawalAmount() public { // Test with exactly the minimum withdrawal amount - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); - _mockBridge(); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesDisbursed(block.timestamp, 0, DEFAULT_MIN_WITHDRAWAL); + _expectFeesDisbursed(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); } @@ -749,36 +640,24 @@ contract FeeDisburserTest is Test { vm.deal(Predeploys.BASE_FEE_VAULT, DEFAULT_MIN_WITHDRAWAL - 1); vm.deal(Predeploys.L1_FEE_VAULT, DEFAULT_MIN_WITHDRAWAL - 1); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit NoFeesCollected(); + _expectNoFeesCollected(); feeDisburser.disburseFees(); } function test_edge_veryLargeFees() public { // Test with very large fee amounts - uint256 largeFee = 10 ** 27; // 1 billion ETH - - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, largeFee); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(largeFee) - ); - - vm.deal(Predeploys.BASE_FEE_VAULT, largeFee); - vm.mockCall( - Predeploys.BASE_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(largeFee) - ); + uint256 largeFee = MAX_TEST_FEE; // 1 billion ETH - vm.deal(Predeploys.L1_FEE_VAULT, largeFee); - vm.mockCall(Predeploys.L1_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(largeFee)); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, largeFee); + _mockVaultWithdrawal(Predeploys.BASE_FEE_VAULT, largeFee); + _mockVaultWithdrawal(Predeploys.L1_FEE_VAULT, largeFee); uint256 totalFees = largeFee * 3; - vm.deal(address(feeDisburser), totalFees); - _mockBridge(); + _expectBridgeETH(totalFees); - vm.expectEmit(true, true, true, true, address(feeDisburser)); - emit FeesDisbursed(block.timestamp, 0, totalFees); + _expectFeesDisbursed(totalFees); feeDisburser.disburseFees(); } @@ -788,17 +667,11 @@ contract FeeDisburserTest is Test { uint256 maxSafeTimestamp = type(uint256).max - DEFAULT_DISBURSEMENT_INTERVAL; vm.warp(maxSafeTimestamp); - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); - _mockBridge(); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); assertEq(feeDisburser.lastDisbursementTime(), maxSafeTimestamp); @@ -809,54 +682,38 @@ contract FeeDisburserTest is Test { // ============================================================ function test_invariant_lastDisbursementTimeNeverDecreases() public { - _mockBridge(); - uint256 previousTime = 0; - uint256 currentWarpTime = block.timestamp; for (uint256 i = 0; i < 10; i++) { - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); uint256 currentTime = feeDisburser.lastDisbursementTime(); assertTrue(currentTime >= previousTime, "lastDisbursementTime decreased!"); previousTime = currentTime; + _resetAfterMockedBridge(); - currentWarpTime += DEFAULT_DISBURSEMENT_INTERVAL; - vm.warp(currentWarpTime); + vm.warp(block.timestamp + DEFAULT_DISBURSEMENT_INTERVAL); } } function test_invariant_l1WalletNeverChanges() public { address initialL1Wallet = feeDisburser.L1_WALLET(); - _mockBridge(); - - uint256 currentWarpTime = block.timestamp; // Perform multiple operations for (uint256 i = 0; i < 5; i++) { - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); - currentWarpTime += DEFAULT_DISBURSEMENT_INTERVAL; - vm.warp(currentWarpTime); + _resetAfterMockedBridge(); + vm.warp(block.timestamp + DEFAULT_DISBURSEMENT_INTERVAL); } assertEq(feeDisburser.L1_WALLET(), initialL1Wallet, "L1_WALLET changed!"); @@ -865,24 +722,16 @@ contract FeeDisburserTest is Test { function test_invariant_disbursementIntervalNeverChanges() public { uint256 initialInterval = feeDisburser.FEE_DISBURSEMENT_INTERVAL(); - _mockBridge(); - - uint256 currentWarpTime = block.timestamp; // Perform multiple operations for (uint256 i = 0; i < 5; i++) { - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, - abi.encodeWithSelector(IFeeVault.withdraw.selector), - abi.encode(DEFAULT_MIN_WITHDRAWAL) - ); - vm.deal(address(feeDisburser), DEFAULT_MIN_WITHDRAWAL); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, DEFAULT_MIN_WITHDRAWAL); vm.deal(Predeploys.BASE_FEE_VAULT, 0); vm.deal(Predeploys.L1_FEE_VAULT, 0); + _expectBridgeETH(DEFAULT_MIN_WITHDRAWAL); feeDisburser.disburseFees(); - currentWarpTime += DEFAULT_DISBURSEMENT_INTERVAL; - vm.warp(currentWarpTime); + _resetAfterMockedBridge(); + vm.warp(block.timestamp + DEFAULT_DISBURSEMENT_INTERVAL); } assertEq(feeDisburser.FEE_DISBURSEMENT_INTERVAL(), initialInterval, "FEE_DISBURSEMENT_INTERVAL changed!"); @@ -901,33 +750,22 @@ contract FeeDisburserTest is Test { feeDisburser.disburseFees(); uint256 gasUsed = gasBefore - gasleft(); - // Log for visibility (optional) - console.log("Gas used for no-fee disbursement:", gasUsed); assertTrue(gasUsed < 100_000, "Gas usage too high for no-fee case"); } function test_gas_disburseFees_allVaults() public { uint256 fee = DEFAULT_MIN_WITHDRAWAL; - vm.deal(Predeploys.SEQUENCER_FEE_WALLET, fee); - vm.mockCall( - Predeploys.SEQUENCER_FEE_WALLET, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(fee) - ); - - vm.deal(Predeploys.BASE_FEE_VAULT, fee); - vm.mockCall(Predeploys.BASE_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(fee)); - - vm.deal(Predeploys.L1_FEE_VAULT, fee); - vm.mockCall(Predeploys.L1_FEE_VAULT, abi.encodeWithSelector(IFeeVault.withdraw.selector), abi.encode(fee)); + _mockVaultWithdrawal(Predeploys.SEQUENCER_FEE_WALLET, fee); + _mockVaultWithdrawal(Predeploys.BASE_FEE_VAULT, fee); + _mockVaultWithdrawal(Predeploys.L1_FEE_VAULT, fee); - vm.deal(address(feeDisburser), fee * 3); - _mockBridge(); + _expectBridgeETH(fee * 3); uint256 gasBefore = gasleft(); feeDisburser.disburseFees(); uint256 gasUsed = gasBefore - gasleft(); - console.log("Gas used for all-vault disbursement:", gasUsed); assertTrue(gasUsed < 200_000, "Gas usage too high for all-vault case"); } } From 5236f281d878331fb7e111df089caf1e827b284e Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 11:40:03 -0400 Subject: [PATCH 080/135] Refactor FeeVault tests: remove unused IProxyAdmin import, introduce constants for withdrawal gas limit, and enhance clarity with internal helper functions for event expectations and assertions, improving maintainability. --- test/L2/FeeVault.t.sol | 142 ++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 93 deletions(-) diff --git a/test/L2/FeeVault.t.sol b/test/L2/FeeVault.t.sol index e6faca53..d424c4c2 100644 --- a/test/L2/FeeVault.t.sol +++ b/test/L2/FeeVault.t.sol @@ -6,7 +6,6 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { Reverter } from "test/mocks/Callers.sol"; // Interfaces -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IFeeVault } from "interfaces/L2/IFeeVault.sol"; import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; @@ -17,9 +16,11 @@ import { Predeploys } from "src/libraries/Predeploys.sol"; import { Features } from "src/libraries/Features.sol"; /// @title FeeVault_Uncategorized_Test -/// @notice Abstract test contract for fee feeVault testing. +/// @notice Abstract test contract for fee vault testing. /// Subclasses can override the feeVault-specific variables. abstract contract FeeVault_Uncategorized_Test is CommonTest { + uint32 internal constant WITHDRAWAL_MIN_GAS = 400_000; + // Variables that can be overridden by concrete test contracts address recipient; IFeeVault feeVault; @@ -29,11 +30,17 @@ abstract contract FeeVault_Uncategorized_Test is CommonTest { /// @notice Helper function to set up L2 withdrawal configuration. function _setupL2Withdrawal() internal { - // Set the withdrawal network to L2 - vm.prank(IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); + vm.prank(proxyAdminOwner); feeVault.setWithdrawalNetwork(Types.WithdrawalNetwork.L2); } + function _expectWithdrawalEvents(uint256 _amount, address _recipient, Types.WithdrawalNetwork _network) internal { + vm.expectEmit(address(feeVault)); + emit Withdrawal(_amount, _recipient, address(this)); + vm.expectEmit(address(feeVault)); + emit Withdrawal(_amount, _recipient, address(this), _network); + } + /// @notice Tests that the initialize function succeeds. function test_initialize_succeeds() external view { assertEq(feeVault.recipient(), recipient); @@ -63,19 +70,17 @@ abstract contract FeeVault_Uncategorized_Test is CommonTest { vm.prank(alice); (bool success,) = address(feeVault).call{ value: 100 }(hex""); - assertEq(success, true); + assertTrue(success); assertEq(address(feeVault).balance, balance + 100); } /// @notice Tests that `withdraw` reverts if the balance is less than the minimum withdrawal /// amount. function testFuzz_withdraw_notEnough_reverts(uint256 _minWithdrawalAmount) external { - // Set the minimum withdrawal amount _minWithdrawalAmount = bound(_minWithdrawalAmount, 1, type(uint256).max); - vm.prank(IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); + vm.prank(proxyAdminOwner); feeVault.setMinWithdrawalAmount(_minWithdrawalAmount); - // Set the balance to be less than the minimum withdrawal amount vm.deal(address(feeVault), _minWithdrawalAmount - 1); vm.expectRevert("FeeVault: withdrawal amount must be greater than minimum withdrawal amount"); @@ -86,61 +91,38 @@ abstract contract FeeVault_Uncategorized_Test is CommonTest { function test_withdraw_toL1_succeeds() external { skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); - // Setup L1 withdrawal - vm.prank(IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); + vm.prank(proxyAdminOwner); feeVault.setWithdrawalNetwork(Types.WithdrawalNetwork.L1); - // Set recipient - vm.prank(IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); - feeVault.setRecipient(recipient); - - // Set minimum withdrawal amount - vm.prank(IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); - feeVault.setMinWithdrawalAmount(minWithdrawalAmount); - - // Set the balance to be greater than the minimum withdrawal amount - uint256 amount = feeVault.minWithdrawalAmount() + 1; + uint256 amount = minWithdrawalAmount + 1; vm.deal(address(feeVault), amount); - // No ether has been withdrawn yet assertEq(feeVault.totalProcessed(), 0); + _expectWithdrawalEvents(amount, recipient, Types.WithdrawalNetwork.L1); - vm.expectEmit(address(address(feeVault))); - emit Withdrawal(address(feeVault).balance, recipient, address(this)); - vm.expectEmit(address(address(feeVault))); - emit Withdrawal(address(feeVault).balance, recipient, address(this), Types.WithdrawalNetwork.L1); - - // The entire feeVault's balance is withdrawn vm.expectCall( Predeploys.L2_TO_L1_MESSAGE_PASSER, - address(feeVault).balance, - abi.encodeCall(IL2ToL1MessagePasser.initiateWithdrawal, (recipient, 400_000, hex"")) + amount, + abi.encodeCall(IL2ToL1MessagePasser.initiateWithdrawal, (recipient, WITHDRAWAL_MIN_GAS, hex"")) ); - // The message is passed to the correct recipient - vm.expectEmit(Predeploys.L2_TO_L1_MESSAGE_PASSER); - emit MessagePassed( - l2ToL1MessagePasser.messageNonce(), - address(feeVault), - recipient, - amount, - 400_000, - hex"", - Hashing.hashWithdrawal( - Types.WithdrawalTransaction({ - nonce: l2ToL1MessagePasser.messageNonce(), - sender: address(feeVault), - target: recipient, - value: amount, - gasLimit: 400_000, - data: hex"" - }) - ) + uint256 nonce = l2ToL1MessagePasser.messageNonce(); + bytes32 withdrawalHash = Hashing.hashWithdrawal( + Types.WithdrawalTransaction({ + nonce: nonce, + sender: address(feeVault), + target: recipient, + value: amount, + gasLimit: WITHDRAWAL_MIN_GAS, + data: hex"" + }) ); + vm.expectEmit(Predeploys.L2_TO_L1_MESSAGE_PASSER); + emit MessagePassed(nonce, address(feeVault), recipient, amount, WITHDRAWAL_MIN_GAS, hex"", withdrawalHash); + feeVault.withdraw(); - // The withdrawal was successful assertEq(feeVault.totalProcessed(), amount); assertEq(address(feeVault).balance, 0); assertEq(Predeploys.L2_TO_L1_MESSAGE_PASSER.balance, amount); @@ -150,23 +132,16 @@ abstract contract FeeVault_Uncategorized_Test is CommonTest { function test_withdraw_toL2_succeeds() public { _setupL2Withdrawal(); - uint256 amount = feeVault.minWithdrawalAmount() + 1; + uint256 amount = minWithdrawalAmount + 1; vm.deal(address(feeVault), amount); - // No ether has been withdrawn yet assertEq(feeVault.totalProcessed(), 0); + _expectWithdrawalEvents(amount, recipient, Types.WithdrawalNetwork.L2); - vm.expectEmit(address(address(feeVault))); - emit Withdrawal(address(feeVault).balance, feeVault.RECIPIENT(), address(this)); - vm.expectEmit(address(address(feeVault))); - emit Withdrawal(address(feeVault).balance, feeVault.RECIPIENT(), address(this), Types.WithdrawalNetwork.L2); - - // The entire feeVault's balance is withdrawn - vm.expectCall(recipient, address(feeVault).balance, bytes("")); + vm.expectCall(recipient, amount, bytes("")); uint256 withdrawnAmount = feeVault.withdraw(); - // The withdrawal was successful assertEq(withdrawnAmount, amount); assertEq(feeVault.totalProcessed(), amount); assertEq(address(feeVault).balance, 0); @@ -178,17 +153,15 @@ abstract contract FeeVault_Uncategorized_Test is CommonTest { function test_withdraw_toL2recipientReverts_fails() external { _setupL2Withdrawal(); - uint256 amount = feeVault.minWithdrawalAmount(); + uint256 amount = minWithdrawalAmount; vm.deal(address(feeVault), amount); - // No ether has been withdrawn yet assertEq(feeVault.totalProcessed(), 0); // Ensure the RECIPIENT reverts - vm.etch(feeVault.RECIPIENT(), type(Reverter).runtimeCode); + vm.etch(recipient, type(Reverter).runtimeCode); - // The entire feeVault's balance is withdrawn - vm.expectCall(recipient, address(feeVault).balance, bytes("")); + vm.expectCall(recipient, amount, bytes("")); vm.expectRevert("FeeVault: failed to send ETH to L2 fee recipient"); feeVault.withdraw(); assertEq(feeVault.totalProcessed(), 0); @@ -196,77 +169,61 @@ abstract contract FeeVault_Uncategorized_Test is CommonTest { /// @notice Tests that the owner can successfully set minimum withdrawal amount with fuzz testing. function testFuzz_setMinWithdrawalAmount_succeeds(uint256 _newMinWithdrawalAmount) external { - address owner = IProxyAdmin(Predeploys.PROXY_ADMIN).owner(); + vm.prank(proxyAdminOwner); + feeVault.setMinWithdrawalAmount(_newMinWithdrawalAmount); - vm.prank(owner); - IFeeVault(payable(address(feeVault))).setMinWithdrawalAmount(_newMinWithdrawalAmount); - - // Verify the value was updated assertEq(feeVault.minWithdrawalAmount(), _newMinWithdrawalAmount); } /// @notice Tests that non-owner cannot set minimum withdrawal amount with fuzz testing. function testFuzz_setMinWithdrawalAmount_onlyOwner_reverts(address _caller, uint256 _newAmount) external { - address owner = IProxyAdmin(Predeploys.PROXY_ADMIN).owner(); - vm.assume(_caller != owner); + vm.assume(_caller != proxyAdminOwner); uint256 initialAmount = feeVault.minWithdrawalAmount(); vm.prank(_caller); vm.expectRevert(IFeeVault.FeeVault_OnlyProxyAdminOwner.selector); - IFeeVault(payable(address(feeVault))).setMinWithdrawalAmount(_newAmount); + feeVault.setMinWithdrawalAmount(_newAmount); - // Verify the value and boolean flag were NOT changed assertEq(feeVault.minWithdrawalAmount(), initialAmount); } /// @notice Tests that the owner can successfully set recipient with fuzz testing. function testFuzz_setRecipient_succeeds(address _newRecipient) external { - address owner = IProxyAdmin(Predeploys.PROXY_ADMIN).owner(); - - vm.prank(owner); - IFeeVault(payable(address(feeVault))).setRecipient(_newRecipient); + vm.prank(proxyAdminOwner); + feeVault.setRecipient(_newRecipient); - // Verify the value was updated assertEq(feeVault.recipient(), _newRecipient); } /// @notice Tests that non-owner cannot set recipient with fuzz testing. function testFuzz_setRecipient_onlyOwner_reverts(address _caller, address _newRecipient) external { - address owner = IProxyAdmin(Predeploys.PROXY_ADMIN).owner(); - vm.assume(_caller != owner); + vm.assume(_caller != proxyAdminOwner); address initialRecipient = feeVault.recipient(); vm.prank(_caller); vm.expectRevert(IFeeVault.FeeVault_OnlyProxyAdminOwner.selector); - IFeeVault(payable(address(feeVault))).setRecipient(_newRecipient); + feeVault.setRecipient(_newRecipient); - // Verify the value and boolean flag were NOT changed assertEq(feeVault.recipient(), initialRecipient); } /// @notice Tests that the owner can successfully set withdrawal network with fuzz testing. function testFuzz_setWithdrawalNetwork_succeeds(uint8 _networkValue) external { - // Bound to valid enum values (0 = L1, 1 = L2) _networkValue = uint8(bound(_networkValue, 0, 1)); Types.WithdrawalNetwork newNetwork = Types.WithdrawalNetwork(_networkValue); - address owner = IProxyAdmin(Predeploys.PROXY_ADMIN).owner(); - - vm.prank(owner); - IFeeVault(payable(address(feeVault))).setWithdrawalNetwork(newNetwork); + vm.prank(proxyAdminOwner); + feeVault.setWithdrawalNetwork(newNetwork); - // Verify the value was updated assertEq(uint8(feeVault.withdrawalNetwork()), uint8(newNetwork)); } /// @notice Tests that non-owner cannot set withdrawal network with fuzz testing. function testFuzz_setWithdrawalNetwork_onlyOwner_reverts(address _caller, uint8 _networkValue) external { - address owner = IProxyAdmin(Predeploys.PROXY_ADMIN).owner(); - vm.assume(_caller != owner); + vm.assume(_caller != proxyAdminOwner); - // Bound to valid enum values _networkValue = uint8(bound(_networkValue, 0, 1)); Types.WithdrawalNetwork newNetwork = Types.WithdrawalNetwork(_networkValue); @@ -274,9 +231,8 @@ abstract contract FeeVault_Uncategorized_Test is CommonTest { vm.prank(_caller); vm.expectRevert(IFeeVault.FeeVault_OnlyProxyAdminOwner.selector); - IFeeVault(payable(address(feeVault))).setWithdrawalNetwork(newNetwork); + feeVault.setWithdrawalNetwork(newNetwork); - // Verify the value and boolean flag were NOT changed assertEq(uint8(feeVault.withdrawalNetwork()), uint8(initialNetwork)); } } From d7e6d204e8482e7b1f69b109d2b1395afeb6e877 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 11:46:57 -0400 Subject: [PATCH 081/135] Refactor GasPriceOracle tests: remove redundant comments, introduce internal helper functions for setting L1 block values, and enhance clarity and maintainability of test setup. --- test/L2/GasPriceOracle.t.sol | 176 +++++++++-------------------------- 1 file changed, 42 insertions(+), 134 deletions(-) diff --git a/test/L2/GasPriceOracle.t.sol b/test/L2/GasPriceOracle.t.sol index 428ac245..fac45944 100644 --- a/test/L2/GasPriceOracle.t.sol +++ b/test/L2/GasPriceOracle.t.sol @@ -12,7 +12,6 @@ import { stdError } from "lib/forge-std/src/Test.sol"; contract GasPriceOracle_Test is CommonTest { address depositor; - // The initial L1 context values uint64 constant number = 10; uint64 constant timestamp = 11; uint256 constant baseFee = 2 * (10 ** 6); @@ -31,15 +30,43 @@ contract GasPriceOracle_Test is CommonTest { uint64 constant MAX_UINT64 = type(uint64).max; uint32 constant MAX_UINT32 = type(uint32).max; - /// @dev Sets up the test suite. function setUp() public virtual override { super.setUp(); depositor = l1Block.DEPOSITOR_ACCOUNT(); } + + function _setEcotoneL1BlockValues() internal { + bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesEcotone( + baseFeeScalar, blobBaseFeeScalar, sequenceNumber, timestamp, number, baseFee, blobBaseFee, hash, batcherHash + ); + + vm.prank(depositor); + (bool success,) = address(l1Block).call(calldataPacked); + require(success, "GasPriceOracle_Test: L1Block setup failed"); + } + + function _setIsthmusL1BlockValues(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) internal { + bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesIsthmus( + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number, + baseFee, + blobBaseFee, + hash, + batcherHash, + _operatorFeeScalar, + _operatorFeeConstant + ); + + vm.prank(depositor); + (bool success,) = address(l1Block).call(calldataPacked); + require(success, "GasPriceOracle_Test: L1Block setup failed"); + } } contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { - /// @dev Sets up the test suite. function setUp() public virtual override { // The gasPriceOracle tests rely on an L2 genesis that is not past Ecotone. l2Fork = Fork.DELTA; @@ -59,42 +86,35 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { }); } - /// @dev Tests that `l1BaseFee` is set correctly. function test_l1BaseFee_succeeds() external view { assertEq(gasPriceOracle.l1BaseFee(), baseFee); } - /// @dev Tests that `gasPrice` is set correctly. function test_gasPrice_succeeds() external { vm.fee(100); uint256 gasPrice = gasPriceOracle.gasPrice(); assertEq(gasPrice, 100); } - /// @dev Tests that `baseFee` is set correctly. function test_baseFee_succeeds() external { vm.fee(64); uint256 gasPrice = gasPriceOracle.baseFee(); assertEq(gasPrice, 64); } - /// @dev Tests that `scalar` is set correctly. function test_scalar_succeeds() external view { assertEq(gasPriceOracle.scalar(), l1FeeScalar); } - /// @dev Tests that `overhead` is set correctly. function test_overhead_succeeds() external view { assertEq(gasPriceOracle.overhead(), l1FeeOverhead); } - /// @dev Tests that `decimals` is set correctly. function test_decimals_succeeds() external view { assertEq(gasPriceOracle.decimals(), 6); assertEq(gasPriceOracle.DECIMALS(), 6); } - /// @dev Tests that `setGasPrice` reverts since it was removed in bedrock. function test_setGasPrice_doesNotExist_reverts() external { // nosemgrep: sol-style-use-abi-encodecall (bool success, bytes memory returndata) = @@ -104,7 +124,6 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { assertEq(returndata, hex""); } - /// @dev Tests that `setL1BaseFee` reverts since it was removed in bedrock. function test_setL1BaseFee_doesNotExist_reverts() external { // nosemgrep: sol-style-use-abi-encodecall (bool success, bytes memory returndata) = @@ -114,21 +133,18 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { assertEq(returndata, hex""); } - /// @dev Tests that Fjord cannot be activated without activating Ecotone function test_setFjord_withoutEcotone_reverts() external { vm.prank(depositor); vm.expectRevert("GasPriceOracle: Fjord can only be activated after Ecotone"); gasPriceOracle.setFjord(); } - /// @dev Tests that Jovian activation requires Isthmus to be active first. function test_setJovian_requiresIsthmus_reverts() external { vm.prank(depositor); vm.expectRevert("GasPriceOracle: Jovian can only be activated after Isthmus"); gasPriceOracle.setJovian(); } - /// @dev Tests that `getL1Fee` returns the expected value when both fjord and ecotone are not active function test_getL1Fee_whenFjordAndEcotoneNotActive_succeeds() external { vm.store(address(gasPriceOracle), bytes32(uint256(0)), bytes32(0)); bytes memory data = hex"1111"; @@ -139,7 +155,6 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { // l1FeeScalar(i.e. 10)) / 1e6 } - /// @dev Tests that `getL1GasUsed` returns the expected value when both fjord and ecotone are not active function test_getL1GasUsed_whenFjordAndEcotoneNotActive_succeeds() external { vm.store(address(gasPriceOracle), bytes32(uint256(0)), bytes32(0)); bytes memory data = hex"1111"; @@ -150,81 +165,62 @@ contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { } contract GasPriceOracleEcotone_Test is GasPriceOracle_Test { - /// @dev Sets up the test suite. function setUp() public virtual override { l2Fork = Fork.ECOTONE; super.setUp(); assertEq(gasPriceOracle.isEcotone(), true); - bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesEcotone( - baseFeeScalar, blobBaseFeeScalar, sequenceNumber, timestamp, number, baseFee, blobBaseFee, hash, batcherHash - ); - - // Execute the function call - vm.prank(depositor); - (bool success,) = address(l1Block).call(calldataPacked); - require(success, "GasPriceOracleEcotone_Test: Function call failed"); + _setEcotoneL1BlockValues(); } - /// @dev Tests that `setEcotone` is only callable by the depositor. function test_setEcotone_wrongCaller_reverts() external { vm.expectRevert("GasPriceOracle: only the depositor account can set isEcotone flag"); gasPriceOracle.setEcotone(); } - /// @dev Tests that `gasPrice` is set correctly. function test_gasPrice_succeeds() external { vm.fee(100); uint256 gasPrice = gasPriceOracle.gasPrice(); assertEq(gasPrice, 100); } - /// @dev Tests that `baseFee` is set correctly. function test_baseFee_succeeds() external { vm.fee(64); uint256 gasPrice = gasPriceOracle.baseFee(); assertEq(gasPrice, 64); } - /// @dev Tests that `overhead` reverts since it was removed in ecotone. function test_overhead_legacyFunction_reverts() external { vm.expectRevert("GasPriceOracle: overhead() is deprecated"); gasPriceOracle.overhead(); } - /// @dev Tests that `scalar` reverts since it was removed in ecotone. function test_scalar_legacyFunction_reverts() external { vm.expectRevert("GasPriceOracle: scalar() is deprecated"); gasPriceOracle.scalar(); } - /// @dev Tests that `l1BaseFee` is set correctly. function test_l1BaseFee_succeeds() external view { assertEq(gasPriceOracle.l1BaseFee(), baseFee); } - /// @dev Tests that `blobBaseFee` is set correctly. function test_blobBaseFee_succeeds() external view { assertEq(gasPriceOracle.blobBaseFee(), blobBaseFee); } - /// @dev Tests that `baseFeeScalar` is set correctly. function test_baseFeeScalar_succeeds() external view { assertEq(gasPriceOracle.baseFeeScalar(), baseFeeScalar); } - /// @dev Tests that `blobBaseFeeScalar` is set correctly. function test_blobBaseFeeScalar_succeeds() external view { assertEq(gasPriceOracle.blobBaseFeeScalar(), blobBaseFeeScalar); } - /// @dev Tests that `decimals` is set correctly. function test_decimals_succeeds() external view { assertEq(gasPriceOracle.decimals(), 6); assertEq(gasPriceOracle.DECIMALS(), 6); } - /// @dev Tests that `getL1GasUsed` and `getL1Fee` return expected values function test_getL1Fee_succeeds() external view { bytes memory data = hex"0000010203"; // 2 zero bytes, 3 non-zero bytes // (2*4) + (3*16) + (68*16) == 1144 @@ -235,7 +231,6 @@ contract GasPriceOracleEcotone_Test is GasPriceOracle_Test { assertEq(price, 48977); } - /// @dev Tests that `setFjord` is only callable by the depositor. function test_setFjord_wrongCaller_reverts() external { vm.expectRevert("GasPriceOracle: only the depositor account can set isFjord flag"); gasPriceOracle.setFjord(); @@ -243,81 +238,62 @@ contract GasPriceOracleEcotone_Test is GasPriceOracle_Test { } contract GasPriceOracleFjordActive_Test is GasPriceOracle_Test { - /// @dev Sets up the test suite. function setUp() public virtual override { l2Fork = Fork.FJORD; super.setUp(); - bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesEcotone( - baseFeeScalar, blobBaseFeeScalar, sequenceNumber, timestamp, number, baseFee, blobBaseFee, hash, batcherHash - ); - - vm.prank(depositor); - (bool success,) = address(l1Block).call(calldataPacked); - require(success, "GasPriceOracleFjordActive_Test: Function call failed"); + _setEcotoneL1BlockValues(); } - /// @dev Tests that `setFjord` cannot be called when Fjord is already activate function test_setFjord_whenFjordActive_reverts() external { vm.expectRevert("GasPriceOracle: Fjord already active"); vm.prank(depositor); gasPriceOracle.setFjord(); } - /// @dev Tests that `gasPrice` is set correctly. function test_gasPrice_succeeds() external { vm.fee(100); uint256 gasPrice = gasPriceOracle.gasPrice(); assertEq(gasPrice, 100); } - /// @dev Tests that `baseFee` is set correctly. function test_baseFee_succeeds() external { vm.fee(64); uint256 gasPrice = gasPriceOracle.baseFee(); assertEq(gasPrice, 64); } - /// @dev Tests that `overhead` reverts since it was removed in ecotone. function test_overhead_legacyFunction_reverts() external { vm.expectRevert("GasPriceOracle: overhead() is deprecated"); gasPriceOracle.overhead(); } - /// @dev Tests that `scalar` reverts since it was removed in ecotone. function test_scalar_legacyFunction_reverts() external { vm.expectRevert("GasPriceOracle: scalar() is deprecated"); gasPriceOracle.scalar(); } - /// @dev Tests that `l1BaseFee` is set correctly. function test_l1BaseFee_succeeds() external view { assertEq(gasPriceOracle.l1BaseFee(), baseFee); } - /// @dev Tests that `blobBaseFee` is set correctly. function test_blobBaseFee_succeeds() external view { assertEq(gasPriceOracle.blobBaseFee(), blobBaseFee); } - /// @dev Tests that `baseFeeScalar` is set correctly. function test_baseFeeScalar_succeeds() external view { assertEq(gasPriceOracle.baseFeeScalar(), baseFeeScalar); } - /// @dev Tests that `blobBaseFeeScalar` is set correctly. function test_blobBaseFeeScalar_succeeds() external view { assertEq(gasPriceOracle.blobBaseFeeScalar(), blobBaseFeeScalar); } - /// @dev Tests that `decimals` is set correctly. function test_decimals_succeeds() external view { assertEq(gasPriceOracle.decimals(), 6); assertEq(gasPriceOracle.DECIMALS(), 6); } - /// @dev Tests that `getL1GasUsed`, `getL1Fee` and `getL1FeeUpperBound` return expected values - /// for the minimum bound of the linear regression function test_getL1FeeMinimumBound_succeeds() external view { bytes memory data = hex"0000010203"; // fastlzSize: 74, inc signature uint256 gas = gasPriceOracle.getL1GasUsed(data); @@ -337,8 +313,6 @@ contract GasPriceOracleFjordActive_Test is GasPriceOracle_Test { assertEq(upperBound, 68500); } - /// @dev Tests that `getL1GasUsed`, `getL1Fee` and `getL1FeeUpperBound` return expected values - /// for a specific test transaction function test_getL1FeeRegression_succeeds() external view { // fastlzSize: 235, inc signature bytes memory data = hex"1d2c3ec4f5a9b3f3cd2c024e455c1143a74bbd637c324adcbd4f74e346786ac44e23e78f47d932abedd8d1" @@ -361,7 +335,6 @@ contract GasPriceOracleFjordActive_Test is GasPriceOracle_Test { assertEq(upperBound, 111214); } - /// @dev Tests that `operatorFee` is 0 is Isthmus is not activated. function test_getOperatorFee_succeeds() external view { assertEq(gasPriceOracle.isIsthmus(), false); assertEq(gasPriceOracle.getOperatorFee(10), 0); @@ -369,46 +342,25 @@ contract GasPriceOracleFjordActive_Test is GasPriceOracle_Test { } contract GasPriceOracleIsthmus_Test is GasPriceOracle_Test { - /// @dev Sets up the test suite. function setUp() public virtual override { l2Fork = Fork.ISTHMUS; super.setUp(); - bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesIsthmus( - baseFeeScalar, - blobBaseFeeScalar, - sequenceNumber, - timestamp, - number, - baseFee, - blobBaseFee, - hash, - batcherHash, - operatorFeeScalar, - operatorFeeConstant - ); - - vm.prank(depositor); - (bool success,) = address(l1Block).call(calldataPacked); - require(success, "GasPriceOracleIsthmus_Test: Function call failed"); + _setIsthmusL1BlockValues(operatorFeeScalar, operatorFeeConstant); } - /// @dev Tests that `operatorFee` is set correctly using the Isthmus formula (divide by 1e6). function test_getOperatorFee_succeeds() external view { assertEq(gasPriceOracle.getOperatorFee(10), 10 * operatorFeeScalar / 1e6 + operatorFeeConstant); } - /// @dev Tests that `setIsthmus` is only callable by the depositor. function test_setIsthmus_wrongCaller_reverts() external { vm.expectRevert("GasPriceOracle: only the depositor account can set isIsthmus flag"); gasPriceOracle.setIsthmus(); } - /// @dev Tests that Jovian cannot be activated yet (since it's not activated by default). function test_setJovian_notActivated_succeeds() external { assertEq(gasPriceOracle.isJovian(), false); - // Activate Jovian vm.prank(depositor); gasPriceOracle.setJovian(); @@ -417,32 +369,14 @@ contract GasPriceOracleIsthmus_Test is GasPriceOracle_Test { } contract GasPriceOracleJovian_Test is GasPriceOracle_Test { - /// @dev Sets up the test suite with Isthmus parameters configured. function setUp() public virtual override { l2Fork = Fork.ISTHMUS; super.setUp(); - // Configure Isthmus state on the L1 block. - bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesIsthmus( - baseFeeScalar, - blobBaseFeeScalar, - sequenceNumber, - timestamp, - number, - baseFee, - blobBaseFee, - hash, - batcherHash, - operatorFeeScalar, - operatorFeeConstant - ); - - vm.prank(depositor); - (bool success,) = address(l1Block).call(calldataPacked); - require(success, "GasPriceOracleJovian_Test: L1Block setup failed"); + _setIsthmusL1BlockValues(operatorFeeScalar, operatorFeeConstant); assertEq(gasPriceOracle.isIsthmus(), true, "Isthmus should be active before enabling Jovian"); - assertEq(gasPriceOracle.isJovian(), false, "Jovian starts active"); + assertEq(gasPriceOracle.isJovian(), false, "Jovian starts inactive"); } function _activateJovian() internal { @@ -451,40 +385,16 @@ contract GasPriceOracleJovian_Test is GasPriceOracle_Test { assertEq(gasPriceOracle.isJovian(), true, "Jovian activation failed"); } - function _setOperatorFeeParams(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) internal { - vm.prank(depositor); - (bool success,) = address(l1Block) - .call( - Encoding.encodeSetL1BlockValuesIsthmus( - baseFeeScalar, - blobBaseFeeScalar, - sequenceNumber, - timestamp, - number, - baseFee, - blobBaseFee, - hash, - batcherHash, - _operatorFeeScalar, - _operatorFeeConstant - ) - ); - require(success, "GasPriceOracleJovian_Test: L1Block setup failed"); - } - - /// @dev Tests that `operatorFee` is set correctly using the new Jovian formula (multiply by 100). function test_getOperatorFee_succeeds() external { _activateJovian(); assertEq(gasPriceOracle.getOperatorFee(10), 10 * operatorFeeScalar * 100 + operatorFeeConstant); } - /// @dev Tests that `setJovian` is only callable by the depositor. function test_setJovian_wrongCaller_reverts() external { vm.expectRevert("GasPriceOracle: only the depositor account can set isJovian flag"); gasPriceOracle.setJovian(); } - /// @dev Tests that `setJovian` cannot be activated twice. function test_setJovian_alreadyActive_reverts() external { _activateJovian(); vm.prank(depositor); @@ -492,10 +402,9 @@ contract GasPriceOracleJovian_Test is GasPriceOracle_Test { gasPriceOracle.setJovian(); } - /// @dev Tests the transition from Isthmus formula to Jovian formula. function test_formulaTransition_edgeCases_works() external { // Check Isthmus formula with a low gasUsed value (divide by 1e6) - _setOperatorFeeParams(operatorFeeScalar, operatorFeeConstant); + _setIsthmusL1BlockValues(operatorFeeScalar, operatorFeeConstant); uint256 isthmusFee = gasPriceOracle.getOperatorFee(10); assertEq( isthmusFee, @@ -506,7 +415,7 @@ contract GasPriceOracleJovian_Test is GasPriceOracle_Test { // Use maximum values permitted by data types for scalars. // Use maximum value for gasUsed according to client implementations. // Assert that the fee is as expected (no overflow). - _setOperatorFeeParams(MAX_UINT32, MAX_UINT64); + _setIsthmusL1BlockValues(MAX_UINT32, MAX_UINT64); isthmusFee = gasPriceOracle.getOperatorFee(MAX_UINT64); assertEq( isthmusFee, @@ -516,19 +425,18 @@ contract GasPriceOracleJovian_Test is GasPriceOracle_Test { // Show that the math saturates if the maximum // value for gasUsed (according to data type) is used. - _setOperatorFeeParams(1e6, 1); + _setIsthmusL1BlockValues(1e6, 1); uint256 saturatedIsthmusFee = gasPriceOracle.getOperatorFee(MAX_UINT256); assertEq( saturatedIsthmusFee, - 115792089237316195423570985008687907853269984665640564039457584007913130, + MAX_UINT256 / 1e6 + 1, "Incorrect value for fee under Isthmus (saturating arithmetic triggered)" ); - // Activate Jovian _activateJovian(); // Check Jovian formula with a low gasUsed value (multiply by 100) - _setOperatorFeeParams(operatorFeeScalar, operatorFeeConstant); + _setIsthmusL1BlockValues(operatorFeeScalar, operatorFeeConstant); uint256 jovianFee = gasPriceOracle.getOperatorFee(10); assertEq( jovianFee, @@ -539,7 +447,7 @@ contract GasPriceOracleJovian_Test is GasPriceOracle_Test { // Use maximum values permitted by data types for scalars. // Use maximum value for gasUsed according to client implementations. // Assert that the fee is as expected (no overflow). - _setOperatorFeeParams(MAX_UINT32, MAX_UINT64); + _setIsthmusL1BlockValues(MAX_UINT32, MAX_UINT64); jovianFee = gasPriceOracle.getOperatorFee(MAX_UINT64); assertEq( jovianFee, @@ -549,7 +457,7 @@ contract GasPriceOracleJovian_Test is GasPriceOracle_Test { // Show that a revert is possible if the maximum // value for gasUsed (according to data type) is used. - _setOperatorFeeParams(1, 1); + _setIsthmusL1BlockValues(1, 1); vm.expectRevert(stdError.arithmeticError); gasPriceOracle.getOperatorFee(MAX_UINT256); From bab353ed9d5a3360e428f0f79468a8f09b0485c3 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 11:51:26 -0400 Subject: [PATCH 082/135] Refactor L1Block tests: introduce internal helper functions for asserting depositor calls and handling legacy high bits, enhance clarity with constants for improved maintainability of test setup. --- test/L2/L1Block.t.sol | 152 +++++++++++++++--------------------------- 1 file changed, 55 insertions(+), 97 deletions(-) diff --git a/test/L2/L1Block.t.sol b/test/L2/L1Block.t.sol index d58f17b8..bddc94cd 100644 --- a/test/L2/L1Block.t.sol +++ b/test/L2/L1Block.t.sol @@ -3,16 +3,17 @@ pragma solidity 0.8.15; // Testing import { CommonTest } from "test/setup/CommonTest.sol"; -import { stdStorage, StdStorage } from "lib/forge-std/src/Test.sol"; // Libraries import { Encoding } from "src/libraries/Encoding.sol"; -import { Constants } from "src/libraries/Constants.sol"; -import { Features } from "src/libraries/Features.sol"; /// @title L1Block_ TestInit /// @notice Reusable test initialization for `L1Block` tests. abstract contract L1Block_TestInit is CommonTest { + bytes4 internal constant NOT_DEPOSITOR_SELECTOR = 0x3cc50b45; + bytes32 internal constant LEGACY_HIGH_BITS_MASK = + hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"; + address depositor; /// @notice Sets up the test suite. @@ -23,14 +24,23 @@ abstract contract L1Block_TestInit is CommonTest { /// @notice Asserts that legacy high 128-bit ranges in key storage slots remain zeroed. function assertEmptyLegacySlotRanges() internal view { - // 128 high bits mask for 32-byte word - bytes32 mask128 = hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"; - // Check scalars and sequenceNumber slot (slot 3) bytes32 scalarsSlot = vm.load(address(l1Block), bytes32(uint256(3))); - assertEq(0, scalarsSlot & mask128); - // Check number and timestamp slot (slot 0) + assertEq(0, scalarsSlot & LEGACY_HIGH_BITS_MASK); + bytes32 numberTimestampSlot = vm.load(address(l1Block), bytes32(uint256(0))); - assertEq(0, numberTimestampSlot & mask128); + assertEq(0, numberTimestampSlot & LEGACY_HIGH_BITS_MASK); + } + + function assertDepositorCallSucceeds(bytes memory callData) internal { + vm.prank(depositor); + (bool success,) = address(l1Block).call(callData); + assertTrue(success, "function call failed"); + } + + function assertNotDepositorReverts(bytes memory callData) internal { + (bool success, bytes memory data) = address(l1Block).call(callData); + assertFalse(success, "function call should have failed"); + assertEq(data, abi.encodeWithSelector(NOT_DEPOSITOR_SELECTOR)); } } @@ -40,7 +50,7 @@ contract L1Block_Version_Test is L1Block_TestInit { /// @notice Tests that the version function returns a valid string. We avoid testing the /// specific value of the string as it changes frequently. function test_version_succeeds() external view { - assert(bytes(l1Block.version()).length > 0); + assertGt(bytes(l1Block.version()).length, 0); } } @@ -106,6 +116,20 @@ contract L1Block_SetL1BlockValues_Test is L1Block_TestInit { /// @title L1Block_SetL1BlockValuesEcotone_Test /// @notice Tests the `setL1BlockValuesEcotone` function of the `L1Block` contract. contract L1Block_SetL1BlockValuesEcotone_Test is L1Block_TestInit { + function encodeMaxSetL1BlockValuesEcotone() internal pure returns (bytes memory) { + return Encoding.encodeSetL1BlockValuesEcotone( + type(uint32).max, + type(uint32).max, + type(uint64).max, + type(uint64).max, + type(uint64).max, + type(uint256).max, + type(uint256).max, + bytes32(type(uint256).max), + bytes32(type(uint256).max) + ); + } + /// @notice Tests that setL1BlockValuesEcotone updates the values appropriately. function testFuzz_setL1BlockValuesEcotone_succeeds( uint32 baseFeeScalar, @@ -124,9 +148,7 @@ contract L1Block_SetL1BlockValuesEcotone_Test is L1Block_TestInit { baseFeeScalar, blobBaseFeeScalar, sequenceNumber, timestamp, number, baseFee, blobBaseFee, hash, batcherHash ); - vm.prank(depositor); - (bool success,) = address(l1Block).call(functionCallDataPacked); - assertTrue(success, "Function call failed"); + assertDepositorCallSucceeds(functionCallDataPacked); assertEq(l1Block.baseFeeScalar(), baseFeeScalar); assertEq(l1Block.blobBaseFeeScalar(), blobBaseFeeScalar); @@ -143,26 +165,20 @@ contract L1Block_SetL1BlockValuesEcotone_Test is L1Block_TestInit { /// @notice Tests that `setL1BlockValuesEcotone` succeeds with max values function test_setL1BlockValuesEcotone_isDepositorMax_succeeds() external { - bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesEcotone( - type(uint32).max, - type(uint32).max, - type(uint64).max, - type(uint64).max, - type(uint64).max, - type(uint256).max, - type(uint256).max, - bytes32(type(uint256).max), - bytes32(type(uint256).max) - ); - - vm.prank(depositor); - (bool success,) = address(l1Block).call(functionCallDataPacked); - assertTrue(success, "function call failed"); + assertDepositorCallSucceeds(encodeMaxSetL1BlockValuesEcotone()); } /// @notice Tests that `setL1BlockValuesEcotone` reverts if sender address is not the depositor function test_setL1BlockValuesEcotone_notDepositor_reverts() external { - bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesEcotone( + assertNotDepositorReverts(encodeMaxSetL1BlockValuesEcotone()); + } +} + +/// @title L1Block_SetL1BlockValuesIsthmus_Test +/// @notice Tests the `setL1BlockValuesIsthmus` function of the `L1Block` contract. +contract L1Block_SetL1BlockValuesIsthmus_Test is L1Block_TestInit { + function encodeMaxSetL1BlockValuesIsthmus() internal pure returns (bytes memory) { + return Encoding.encodeSetL1BlockValuesIsthmus( type(uint32).max, type(uint32).max, type(uint64).max, @@ -171,20 +187,12 @@ contract L1Block_SetL1BlockValuesEcotone_Test is L1Block_TestInit { type(uint256).max, type(uint256).max, bytes32(type(uint256).max), - bytes32(type(uint256).max) + bytes32(type(uint256).max), + type(uint32).max, + type(uint64).max ); - - (bool success, bytes memory data) = address(l1Block).call(functionCallDataPacked); - assertTrue(!success, "function call should have failed"); - // make sure return value is the expected function selector for "NotDepositor()" - bytes memory expReturn = hex"3cc50b45"; - assertEq(data, expReturn); } -} -/// @title L1Block_SetL1BlockValuesIsthmus_Test -/// @notice Tests the `setL1BlockValuesIsthmus` function of the `L1Block` contract. -contract L1Block_SetL1BlockValuesIsthmus_Test is L1Block_TestInit { /// @notice Tests that setL1BlockValuesIsthmus updates the values appropriately. function testFuzz_setL1BlockValuesIsthmus_succeeds( uint32 baseFeeScalar, @@ -215,9 +223,7 @@ contract L1Block_SetL1BlockValuesIsthmus_Test is L1Block_TestInit { operatorFeeConstant ); - vm.prank(depositor); - (bool success,) = address(l1Block).call(functionCallDataPacked); - assertTrue(success, "Function call failed"); + assertDepositorCallSucceeds(functionCallDataPacked); assertEq(l1Block.baseFeeScalar(), baseFeeScalar); assertEq(l1Block.blobBaseFeeScalar(), blobBaseFeeScalar); @@ -236,46 +242,12 @@ contract L1Block_SetL1BlockValuesIsthmus_Test is L1Block_TestInit { /// @notice Tests that `setL1BlockValuesIsthmus` succeeds with max values function test_setL1BlockValuesIsthmus_isDepositorMax_succeeds() external { - bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesIsthmus( - type(uint32).max, - type(uint32).max, - type(uint64).max, - type(uint64).max, - type(uint64).max, - type(uint256).max, - type(uint256).max, - bytes32(type(uint256).max), - bytes32(type(uint256).max), - type(uint32).max, - type(uint64).max - ); - - vm.prank(depositor); - (bool success,) = address(l1Block).call(functionCallDataPacked); - assertTrue(success, "function call failed"); + assertDepositorCallSucceeds(encodeMaxSetL1BlockValuesIsthmus()); } /// @notice Tests that `setL1BlockValuesIsthmus` reverts if sender address is not the depositor function test_setL1BlockValuesIsthmus_notDepositor_reverts() external { - bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesIsthmus( - type(uint32).max, - type(uint32).max, - type(uint64).max, - type(uint64).max, - type(uint64).max, - type(uint256).max, - type(uint256).max, - bytes32(type(uint256).max), - bytes32(type(uint256).max), - type(uint32).max, - type(uint64).max - ); - - (bool success, bytes memory data) = address(l1Block).call(functionCallDataPacked); - assertTrue(!success, "function call should have failed"); - // make sure return value is the expected function selector for "NotDepositor()" - bytes memory expReturn = hex"3cc50b45"; - assertEq(data, expReturn); + assertNotDepositorReverts(encodeMaxSetL1BlockValuesIsthmus()); } } @@ -332,9 +304,7 @@ contract L1Block_SetL1BlockValuesJovian_Test is L1Block_TestInit { params.daFootprintGasScalar ); - vm.prank(depositor); - (bool success,) = address(l1Block).call(functionCallDataPacked); - assertTrue(success, "Function call failed"); + assertDepositorCallSucceeds(functionCallDataPacked); assertEq(l1Block.baseFeeScalar(), params.baseFeeScalar); assertEq(l1Block.blobBaseFeeScalar(), params.blobBaseFeeScalar); @@ -354,11 +324,7 @@ contract L1Block_SetL1BlockValuesJovian_Test is L1Block_TestInit { /// @notice Tests that `setL1BlockValuesJovian` succeeds with max values function test_setL1BlockValuesJovian_isDepositorMax_succeeds() external { - bytes memory functionCallDataPacked = encodeMaxSetL1BlockValuesJovian(); - - vm.prank(depositor); - (bool success,) = address(l1Block).call(functionCallDataPacked); - assertTrue(success, "function call failed"); + assertDepositorCallSucceeds(encodeMaxSetL1BlockValuesJovian()); } /// @notice Prevents the L1 attributes system transaction from approaching its gas limit. @@ -367,20 +333,12 @@ contract L1Block_SetL1BlockValuesJovian_Test is L1Block_TestInit { bytes memory functionCallDataPacked = encodeMaxSetL1BlockValuesJovian(); - vm.prank(depositor); - (bool success,) = address(l1Block).call(functionCallDataPacked); - assertTrue(success, "function call failed"); + assertDepositorCallSucceeds(functionCallDataPacked); assertLt(vm.lastCallGas().gasTotalUsed, 200_000); } /// @notice Tests that `setL1BlockValuesJovian` reverts if sender address is not the depositor function test_setL1BlockValuesJovian_notDepositor_reverts() external { - bytes memory functionCallDataPacked = encodeMaxSetL1BlockValuesJovian(); - - (bool success, bytes memory data) = address(l1Block).call(functionCallDataPacked); - assertTrue(!success, "function call should have failed"); - // make sure return value is the expected function selector for "NotDepositor()" - bytes memory expReturn = hex"3cc50b45"; - assertEq(data, expReturn); + assertNotDepositorReverts(encodeMaxSetL1BlockValuesJovian()); } } From cba7abd057d0c4b564e0daa33486b2e952775cbe Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 11:54:33 -0400 Subject: [PATCH 083/135] Refactor L1FeeVault tests: remove redundant comments, streamline test setup by eliminating hardcoded feeVaultName assignment, and enhance clarity with improved variable assignments for withdrawal network configuration. --- test/L2/L1FeeVault.t.sol | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/L2/L1FeeVault.t.sol b/test/L2/L1FeeVault.t.sol index 3e920254..d12077ba 100644 --- a/test/L2/L1FeeVault.t.sol +++ b/test/L2/L1FeeVault.t.sol @@ -1,35 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Interfaces import { IFeeVault } from "interfaces/L2/IFeeVault.sol"; - -// Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { FeeVault_Uncategorized_Test } from "test/L2/FeeVault.t.sol"; import { Types } from "src/libraries/Types.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; -/// @title L1FeeVault_Version_Test -/// @notice Tests the `version` function of the `L1FeeVault` contract. contract L1FeeVault_Version_Test is CommonTest { - /// @notice Tests that the version returns a valid semver string. function test_version_succeeds() external view { SemverComp.parse(l1FeeVault.version()); } } -/// @title L1FeeVault_Uncategorized_Test -/// @notice Test contract for the L1FeeVault contract's functionality contract L1FeeVault_Uncategorized_Test is FeeVault_Uncategorized_Test { - /// @dev Sets up the test suite. function setUp() public virtual override { super.setUp(); recipient = deploy.cfg().l1FeeVaultRecipient(); - feeVaultName = "L1FeeVault"; minWithdrawalAmount = deploy.cfg().l1FeeVaultMinimumWithdrawalAmount(); feeVault = IFeeVault(payable(Predeploys.L1_FEE_VAULT)); - withdrawalNetwork = Types.WithdrawalNetwork(uint8(deploy.cfg().l1FeeVaultWithdrawalNetwork())); + withdrawalNetwork = Types.WithdrawalNetwork(deploy.cfg().l1FeeVaultWithdrawalNetwork()); } } From 3447e28875a93072e6d3147a4d527482c1ea6937 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:02:36 -0400 Subject: [PATCH 084/135] Refactor L2CrossDomainMessenger tests: replace hardcoded values with constants, introduce internal helper functions for nonce and alias handling, and enhance clarity in message encoding and assertions for improved maintainability. --- test/L2/L2CrossDomainMessenger.t.sol | 200 ++++++++++----------------- 1 file changed, 75 insertions(+), 125 deletions(-) diff --git a/test/L2/L2CrossDomainMessenger.t.sol b/test/L2/L2CrossDomainMessenger.t.sol index 73a03480..51625e76 100644 --- a/test/L2/L2CrossDomainMessenger.t.sol +++ b/test/L2/L2CrossDomainMessenger.t.sol @@ -12,7 +12,6 @@ import { Hashing } from "src/libraries/Hashing.sol"; import { Encoding } from "src/libraries/Encoding.sol"; import { Types } from "src/libraries/Types.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; -import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; // Interfaces import { IL2CrossDomainMessenger } from "interfaces/L2/IL2CrossDomainMessenger.sol"; @@ -21,8 +20,7 @@ import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; /// @title L2CrossDomainMessenger_TestInit /// @notice Reusable test initialization for `L2CrossDomainMessenger` tests. abstract contract L2CrossDomainMessenger_TestInit is CommonTest { - /// @notice Receiver address for testing - address recipient = address(0xabbaacdc); + address internal constant recipient = address(0xabbaacdc); } /// @title L2CrossDomainMessenger_Constructor_Test @@ -73,38 +71,42 @@ contract L2CrossDomainMessenger_SendMessage_Test is L2CrossDomainMessenger_TestI /// @notice Tests that `sendMessage` executes successfully with the original test case. function test_sendMessage_succeeds() external { + uint32 minGasLimit = 100; + bytes memory message = hex"ff"; + uint256 nonce = l2CrossDomainMessenger.messageNonce(); + uint256 withdrawalNonce = l2ToL1MessagePasser.messageNonce(); + uint64 baseGas = l2CrossDomainMessenger.baseGas(message, minGasLimit); bytes memory xDomainCallData = - Encoding.encodeCrossDomainMessage(l2CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff"); + Encoding.encodeCrossDomainMessage(nonce, alice, recipient, 0, minGasLimit, message); vm.expectCall( address(l2ToL1MessagePasser), abi.encodeCall( - IL2ToL1MessagePasser.initiateWithdrawal, - (address(l1CrossDomainMessenger), l2CrossDomainMessenger.baseGas(hex"ff", 100), xDomainCallData) + IL2ToL1MessagePasser.initiateWithdrawal, (address(l1CrossDomainMessenger), baseGas, xDomainCallData) ) ); vm.expectEmit(true, true, true, true); emit MessagePassed( - l2ToL1MessagePasser.messageNonce(), + withdrawalNonce, address(l2CrossDomainMessenger), address(l1CrossDomainMessenger), 0, - l2CrossDomainMessenger.baseGas(hex"ff", 100), + baseGas, xDomainCallData, Hashing.hashWithdrawal( Types.WithdrawalTransaction({ - nonce: l2ToL1MessagePasser.messageNonce(), + nonce: withdrawalNonce, sender: address(l2CrossDomainMessenger), target: address(l1CrossDomainMessenger), value: 0, - gasLimit: l2CrossDomainMessenger.baseGas(hex"ff", 100), + gasLimit: baseGas, data: xDomainCallData }) ) ); vm.prank(alice); - l2CrossDomainMessenger.sendMessage(recipient, hex"ff", uint32(100)); + l2CrossDomainMessenger.sendMessage(recipient, message, minGasLimit); } /// @notice Tests that `sendMessage` can be called twice and that the nonce increments correctly. @@ -120,6 +122,25 @@ contract L2CrossDomainMessenger_SendMessage_Test is L2CrossDomainMessenger_TestI /// @notice General tests that are not testing any function directly of the /// `L2CrossDomainMessenger` contract. contract L2CrossDomainMessenger_Uncategorized_Test is L2CrossDomainMessenger_TestInit { + uint256 internal constant l2SenderSlotIndex = 50; + + function _versionedNonce(uint16 _version) internal pure returns (uint256) { + return Encoding.encodeVersionedNonce({ _nonce: 0, _version: _version }); + } + + function _l1MessengerAlias() internal view returns (address) { + return AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + } + + function _setPortalL2Sender(address _sender) internal { + vm.store(address(optimismPortal2), bytes32(l2SenderSlotIndex), bytes32(abi.encode(_sender))); + } + + function _assertMessageStatus(bytes32 _hash, bool _successful, bool _failed) internal view { + assertEq(l2CrossDomainMessenger.successfulMessages(_hash), _successful); + assertEq(l2CrossDomainMessenger.failedMessages(_hash), _failed); + } + /// @notice Tests that `messageNonce` can be decoded correctly. function test_messageVersion_succeeds() external view { (, uint16 version) = Encoding.decodeVersionedNonce(l2CrossDomainMessenger.messageNonce()); @@ -136,28 +157,20 @@ contract L2CrossDomainMessenger_Uncategorized_Test is L2CrossDomainMessenger_Tes function test_relayMessage_v2_reverts() external { address target = address(0xabcd); address sender = address(l1CrossDomainMessenger); - address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + address caller = _l1MessengerAlias(); - // Expect a revert. vm.expectRevert("CrossDomainMessenger: only version 0 or 1 messages are supported at this time"); - // Try to relay a v2 message. vm.prank(caller); - l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce(0, 2), // nonce - sender, - target, - 0, // value - 0, - hex"1111" - ); + l2CrossDomainMessenger.relayMessage(_versionedNonce(2), sender, target, 0, 0, hex"1111"); } /// @notice Tests that `relayMessage` executes successfully. function test_relayMessage_succeeds() external { address target = address(0xabcd); address sender = address(l1CrossDomainMessenger); - address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + address caller = _l1MessengerAlias(); + uint256 nonce = _versionedNonce(1); vm.expectCall(target, hex"1111"); @@ -165,106 +178,67 @@ contract L2CrossDomainMessenger_Uncategorized_Test is L2CrossDomainMessenger_Tes vm.expectEmit(true, true, true, true); - bytes32 hash = - Hashing.hashCrossDomainMessage(Encoding.encodeVersionedNonce(0, 1), sender, target, 0, 0, hex"1111"); + bytes32 hash = Hashing.hashCrossDomainMessage(nonce, sender, target, 0, 0, hex"1111"); emit RelayedMessage(hash); - l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce(0, 1), // nonce - sender, - target, - 0, // value - 0, - hex"1111" - ); + l2CrossDomainMessenger.relayMessage(nonce, sender, target, 0, 0, hex"1111"); - // the message hash is in the successfulMessages mapping - assert(l2CrossDomainMessenger.successfulMessages(hash)); - // it is not in the received messages mapping - assertEq(l2CrossDomainMessenger.failedMessages(hash), false); + _assertMessageStatus(hash, true, false); } /// @notice Tests that `relayMessage` reverts if the value sent does not match the amount function test_relayMessage_fromOtherMessengerValueMismatch_reverts() external { - // set the target to be alice address target = alice; address sender = address(l1CrossDomainMessenger); - address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + address caller = _l1MessengerAlias(); bytes memory message = hex"1111"; - // cannot send a message where the amount inputted does not match the msg.value vm.deal(caller, 10 ether); vm.prank(caller); vm.expectRevert(stdError.assertionError); - l2CrossDomainMessenger.relayMessage{ value: 10 ether }( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 9 ether, 0, message - ); + l2CrossDomainMessenger.relayMessage{ value: 10 ether }(_versionedNonce(1), sender, target, 9 ether, 0, message); } /// @notice Tests that `relayMessage` reverts if a failed message is attempted to be replayed /// and the caller is the other messenger function test_relayMessage_fromOtherMessengerFailedMessageReplay_reverts() external { - // set the target to be alice address target = alice; address sender = address(l1CrossDomainMessenger); - address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + address caller = _l1MessengerAlias(); + uint256 nonce = _versionedNonce(1); bytes memory message = hex"1111"; - // make a failed message vm.etch(target, hex"fe"); vm.prank(caller); - l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message - ); + l2CrossDomainMessenger.relayMessage(nonce, sender, target, 0, 0, message); - // cannot replay messages when the caller is the other messenger vm.prank(caller); vm.expectRevert(stdError.assertionError); - l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message - ); + l2CrossDomainMessenger.relayMessage(nonce, sender, target, 0, 0, message); } /// @notice Tests that `relayMessage` reverts if attempting to relay a message sent to self function test_relayMessage_toSelf_reverts() external { address sender = address(l1CrossDomainMessenger); - address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + address caller = _l1MessengerAlias(); bytes memory message = hex"1111"; - vm.store(address(optimismPortal2), bytes32(0), bytes32(abi.encode(sender))); - vm.prank(caller); vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address"); - l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), - sender, - address(l2CrossDomainMessenger), - 0, - 0, - message - ); + l2CrossDomainMessenger.relayMessage(_versionedNonce(1), sender, address(l2CrossDomainMessenger), 0, 0, message); } /// @notice Tests that `relayMessage` reverts if attempting to relay a message sent to the /// `l2ToL1MessagePasser` address function test_relayMessage_toL2ToL1MessagePasser_reverts() external { address sender = address(l1CrossDomainMessenger); - address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + address caller = _l1MessengerAlias(); bytes memory message = hex"1111"; - vm.store(address(optimismPortal2), bytes32(0), bytes32(abi.encode(sender))); - vm.prank(caller); vm.expectRevert("CrossDomainMessenger: cannot send message to blocked system address"); - l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), - sender, - address(l2ToL1MessagePasser), - 0, - 0, - message - ); + l2CrossDomainMessenger.relayMessage(_versionedNonce(1), sender, address(l2ToL1MessagePasser), 0, 0, message); } /// @notice Tests that `relayMessage` reverts if the message called by non-`optimismPortal2` @@ -274,13 +248,9 @@ contract L2CrossDomainMessenger_Uncategorized_Test is L2CrossDomainMessenger_Tes address sender = address(l1CrossDomainMessenger); bytes memory message = hex"1111"; - vm.store(address(optimismPortal2), bytes32(0), bytes32(abi.encode(sender))); - vm.prank(bob); vm.expectRevert("CrossDomainMessenger: message cannot be replayed"); - l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, message - ); + l2CrossDomainMessenger.relayMessage(_versionedNonce(1), sender, target, 0, 0, message); } /// @notice Tests that `relayMessage` on L2 will always succeed for any potential message, @@ -327,7 +297,7 @@ contract L2CrossDomainMessenger_Uncategorized_Test is L2CrossDomainMessenger_Tes // Encode the message. bytes memory encoded = Encoding.encodeCrossDomainMessage( - Encoding.encodeVersionedNonce(0, 1), // nonce + _versionedNonce(1), alice, // Sender doesn't matter target, 0, // Value doesn't matter @@ -335,40 +305,36 @@ contract L2CrossDomainMessenger_Uncategorized_Test is L2CrossDomainMessenger_Tes _message ); - // Count the number of non-zero bytes in the message. - uint256 zeroBytesInCalldata = 0; + // Count calldata bytes so the EIP-7623 floor can be checked against actual encoded data. uint256 nonzeroBytesInCalldata = 0; for (uint256 i = 0; i < encoded.length; i++) { if (encoded[i] != bytes1(0)) { nonzeroBytesInCalldata++; - } else { - zeroBytesInCalldata++; } } - // Base gas must always be sufficient to cover the floor cost from EIP-7623. - assertGt(baseGas, 21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10)); + uint256 zeroBytesInCalldata = encoded.length - nonzeroBytesInCalldata; + uint256 calldataTokens = zeroBytesInCalldata + nonzeroBytesInCalldata * 4; + uint256 floorCost = l2CrossDomainMessenger.TX_BASE_GAS() + calldataTokens + * (l2CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD() / 4); - // In the L2 => L1 direction we actually get all of the base gas supplied, nothing is - // deducted. This is an advantage over the L1 => L2 direction because it means the base - // gas goes a lot further. - uint256 gasSupplied = baseGas; + // Base gas must always be sufficient to cover the floor cost from EIP-7623. + assertGt(baseGas, floorCost); - // Store the value of l2Sender in the OptimismPortal2 contract. - StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender"); - vm.store(address(optimismPortal2), bytes32(slot.slot), bytes32(abi.encode(address(l2CrossDomainMessenger)))); + _setPortalL2Sender(address(l2CrossDomainMessenger)); - // We'll trigger the L1CrossDomainMessenger as if we're the OptimismPortal2 vm.prank(address(optimismPortal2)); - // Trigger the L1CrossDomainMessenger. - // Should NOT fail. - (bool success,) = address(l1CrossDomainMessenger).call{ gas: gasSupplied }(encoded); + // In the L2 => L1 direction we actually get all of the base gas supplied, nothing is + // deducted. This is an advantage over the L1 => L2 direction because it means the base + // gas goes a lot further. + (bool success,) = address(l1CrossDomainMessenger).call{ gas: baseGas }(encoded); assertTrue(success, "L1CrossDomainMessenger call should not fail"); // Message should either be in the failed or successful messages mapping. - bool inFailedMessages = l1CrossDomainMessenger.failedMessages(keccak256(encoded)); - bool inSuccessfulMessages = l1CrossDomainMessenger.successfulMessages(keccak256(encoded)); + bytes32 encodedHash = keccak256(encoded); + bool inFailedMessages = l1CrossDomainMessenger.failedMessages(encodedHash); + bool inSuccessfulMessages = l1CrossDomainMessenger.successfulMessages(encodedHash); assertTrue( inFailedMessages || inSuccessfulMessages, "message should be in either failed or successful messages" ); @@ -380,9 +346,9 @@ contract L2CrossDomainMessenger_Uncategorized_Test is L2CrossDomainMessenger_Tes vm.expectRevert("CrossDomainMessenger: xDomainMessageSender is not set"); l2CrossDomainMessenger.xDomainMessageSender(); - address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + address caller = _l1MessengerAlias(); vm.prank(caller); - l2CrossDomainMessenger.relayMessage(Encoding.encodeVersionedNonce(0, 1), address(0), address(0), 0, 0, hex""); + l2CrossDomainMessenger.relayMessage(_versionedNonce(1), address(0), address(0), 0, 0, hex""); vm.expectRevert("CrossDomainMessenger: xDomainMessageSender is not set"); l2CrossDomainMessenger.xDomainMessageSender(); @@ -393,28 +359,20 @@ contract L2CrossDomainMessenger_Uncategorized_Test is L2CrossDomainMessenger_Tes function test_relayMessage_retry_succeeds() external { address target = address(0xabcd); address sender = address(l1CrossDomainMessenger); - address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + address caller = _l1MessengerAlias(); + uint256 nonce = _versionedNonce(1); uint256 value = 100; - bytes32 hash = - Hashing.hashCrossDomainMessage(Encoding.encodeVersionedNonce(0, 1), sender, target, value, 0, hex"1111"); + bytes32 hash = Hashing.hashCrossDomainMessage(nonce, sender, target, value, 0, hex"1111"); vm.etch(target, address(new Reverter()).code); vm.deal(address(caller), value); vm.prank(caller); - l2CrossDomainMessenger.relayMessage{ value: value }( - Encoding.encodeVersionedNonce(0, 1), // nonce - sender, - target, - value, - 0, - hex"1111" - ); + l2CrossDomainMessenger.relayMessage{ value: value }(nonce, sender, target, value, 0, hex"1111"); assertEq(address(l2CrossDomainMessenger).balance, value); assertEq(address(target).balance, 0); - assertEq(l2CrossDomainMessenger.successfulMessages(hash), false); - assertEq(l2CrossDomainMessenger.failedMessages(hash), true); + _assertMessageStatus(hash, false, true); vm.expectEmit(true, true, true, true); @@ -422,18 +380,10 @@ contract L2CrossDomainMessenger_Uncategorized_Test is L2CrossDomainMessenger_Tes vm.etch(target, address(0).code); vm.prank(address(sender)); - l2CrossDomainMessenger.relayMessage( - Encoding.encodeVersionedNonce(0, 1), // nonce - sender, - target, - value, - 0, - hex"1111" - ); + l2CrossDomainMessenger.relayMessage(nonce, sender, target, value, 0, hex"1111"); assertEq(address(l2CrossDomainMessenger).balance, 0); assertEq(address(target).balance, value); - assertEq(l2CrossDomainMessenger.successfulMessages(hash), true); - assertEq(l2CrossDomainMessenger.failedMessages(hash), true); + _assertMessageStatus(hash, true, true); } } From 747e80d042c14594e3e55b005da898939baadf07 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:09:28 -0400 Subject: [PATCH 085/135] Refactor L2ERC721Bridge tests: remove unused test contracts, replace hardcoded values with constants, and introduce internal helper functions for improved clarity and maintainability in test setup and assertions. --- test/L2/L2ERC721Bridge.t.sol | 276 ++++++++++++++--------------------- 1 file changed, 112 insertions(+), 164 deletions(-) diff --git a/test/L2/L2ERC721Bridge.t.sol b/test/L2/L2ERC721Bridge.t.sol index 4be2803a..07f15b0c 100644 --- a/test/L2/L2ERC721Bridge.t.sol +++ b/test/L2/L2ERC721Bridge.t.sol @@ -5,22 +5,11 @@ pragma solidity 0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; // Contracts -import { ERC721 } from "lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; import { OptimismMintableERC721 } from "src/L2/OptimismMintableERC721.sol"; // Interfaces import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; -import { IL2ERC721Bridge } from "interfaces/L2/IL2ERC721Bridge.sol"; - -/// @title TestERC721 -/// @notice A test ERC721 token used for `L2ERC721Bridge` tests. -contract L2ERC721Bridge_TestERC721_Harness is ERC721 { - constructor() ERC721("Test", "TST") { } - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} +import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; /// @title TestMintableERC721 /// @notice A test OptimismMintableERC721 token used for `L2ERC721Bridge` tests. @@ -53,9 +42,7 @@ contract L2ERC721Bridge_NonCompliantERC721_Harness { return address(0x01); } - function burn(address, uint256) external { - // Do nothing. - } + function burn(address, uint256) external { } function supportsInterface(bytes4) external pure returns (bool) { return false; @@ -65,9 +52,10 @@ contract L2ERC721Bridge_NonCompliantERC721_Harness { /// @title L2ERC721Bridge_TestInit /// @notice Reusable test initialization for `L2ERC721Bridge` tests. abstract contract L2ERC721Bridge_TestInit is CommonTest { - L2ERC721Bridge_TestMintableERC721_Harness internal localToken; - L2ERC721Bridge_TestERC721_Harness internal remoteToken; uint256 internal constant tokenId = 1; + uint32 internal constant DEFAULT_MIN_GAS_LIMIT = 1234; + bytes internal constant EXTRA_DATA = hex"5678"; + address internal constant remoteToken = address(0x01); event ERC721BridgeInitiated( address indexed localToken, @@ -86,21 +74,67 @@ abstract contract L2ERC721Bridge_TestInit is CommonTest { uint256 tokenId, bytes extraData ); +} + +abstract contract L2ERC721Bridge_Bridge_TestInit is L2ERC721Bridge_TestInit { + L2ERC721Bridge_TestMintableERC721_Harness internal localToken; /// @notice Sets up the test suite. - function setUp() public override { + function setUp() public virtual override { super.setUp(); - remoteToken = new L2ERC721Bridge_TestERC721_Harness(); - localToken = new L2ERC721Bridge_TestMintableERC721_Harness(address(l2ERC721Bridge), address(remoteToken)); + localToken = new L2ERC721Bridge_TestMintableERC721_Harness(address(l2ERC721Bridge), remoteToken); - // Mint alice a token. localToken.mint(alice, tokenId); - // Approve the bridge to transfer the token. vm.prank(alice); localToken.approve(address(l2ERC721Bridge), tokenId); } + + function _expectBridgeMessage(address _to) internal { + bytes memory message = abi.encodeCall( + IL1ERC721Bridge.finalizeBridgeERC721, (remoteToken, address(localToken), alice, _to, tokenId, EXTRA_DATA) + ); + + vm.expectCall( + address(l2ERC721Bridge.messenger()), + abi.encodeCall( + ICrossDomainMessenger.sendMessage, + (address(l2ERC721Bridge.otherBridge()), message, DEFAULT_MIN_GAS_LIMIT) + ) + ); + } + + function _expectBridgeInitiated(address _to) internal { + vm.expectEmit(address(l2ERC721Bridge)); + emit ERC721BridgeInitiated(address(localToken), remoteToken, alice, _to, tokenId, EXTRA_DATA); + } + + function _mockXDomainMessageSender(address _sender) internal { + vm.mockCall( + address(l2ERC721Bridge.messenger()), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), + abi.encode(_sender) + ); + } + + function _mockOtherBridge() internal { + _mockXDomainMessageSender(address(l2ERC721Bridge.otherBridge())); + } + + function _bridgeToken() internal { + vm.prank(alice, alice); + l2ERC721Bridge.bridgeERC721(address(localToken), remoteToken, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); + } + + function _assertTokenBurned() internal { + vm.expectRevert("ERC721: invalid token ID"); + localToken.ownerOf(tokenId); + } + + function _assertTokenOwnedBy(address _owner) internal view { + assertEq(localToken.ownerOf(tokenId), _owner); + } } /// @title L2ERC721Bridge_Test_Constructor @@ -117,263 +151,177 @@ contract L2ERC721Bridge_Constructor_Test is L2ERC721Bridge_TestInit { /// @title L2ERC721Bridge_FinalizeBridgeERC721_Test /// @notice Tests the `finalizeBridgeERC721` function of the `L2ERC721Bridge` contract. -contract L2ERC721Bridge_FinalizeBridgeERC721_Test is L2ERC721Bridge_TestInit { +contract L2ERC721Bridge_FinalizeBridgeERC721_Test is L2ERC721Bridge_Bridge_TestInit { /// @notice Tests that `finalizeBridgeERC721` correctly finalizes a bridged token. function test_finalizeBridgeERC721_succeeds() external { - // Bridge the token. - vm.prank(alice, alice); - l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + _bridgeToken(); - // Expect an event to be emitted. - vm.expectEmit(true, true, true, true); - emit ERC721BridgeFinalized(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + vm.expectEmit(address(l2ERC721Bridge)); + emit ERC721BridgeFinalized(address(localToken), remoteToken, alice, alice, tokenId, EXTRA_DATA); - // Finalize a withdrawal. - vm.mockCall( - address(l2CrossDomainMessenger), - abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(l1ERC721Bridge) - ); - vm.prank(address(l2CrossDomainMessenger)); - l2ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + _mockOtherBridge(); + vm.prank(address(l2ERC721Bridge.messenger())); + l2ERC721Bridge.finalizeBridgeERC721(address(localToken), remoteToken, alice, alice, tokenId, EXTRA_DATA); - // Token is not locked in the bridge. - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenOwnedBy(alice); } /// @notice Tests that `finalizeBridgeERC721` reverts if the token is not compliant with the /// `IOptimismMintableERC721` interface. function test_finalizeBridgeERC721_interfaceNotCompliant_reverts() external { - // Create a non-compliant token L2ERC721Bridge_NonCompliantERC721_Harness nonCompliantToken = new L2ERC721Bridge_NonCompliantERC721_Harness(alice); - // Bridge the non-compliant token. vm.prank(alice, alice); - l2ERC721Bridge.bridgeERC721(address(nonCompliantToken), address(0x01), tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721(address(nonCompliantToken), remoteToken, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); - // Attempt to finalize the withdrawal. Should revert because the token does not claim to be - // compliant with the `IOptimismMintableERC721` interface. - vm.mockCall( - address(l2CrossDomainMessenger), - abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(l1ERC721Bridge) - ); - vm.prank(address(l2CrossDomainMessenger)); + _mockOtherBridge(); + vm.prank(address(l2ERC721Bridge.messenger())); vm.expectRevert("L2ERC721Bridge: local token interface is not compliant"); - l2ERC721Bridge.finalizeBridgeERC721( - address(address(nonCompliantToken)), address(address(0x01)), alice, alice, tokenId, hex"5678" - ); + l2ERC721Bridge.finalizeBridgeERC721(address(nonCompliantToken), remoteToken, alice, alice, tokenId, EXTRA_DATA); } /// @notice Tests that `finalizeBridgeERC721` reverts when not called by the remote bridge. function test_finalizeBridgeERC721_notViaLocalMessenger_reverts() external { - // Finalize a withdrawal. vm.prank(alice); vm.expectRevert("ERC721Bridge: function can only be called from the other bridge"); - l2ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + l2ERC721Bridge.finalizeBridgeERC721(address(localToken), remoteToken, alice, alice, tokenId, EXTRA_DATA); } /// @notice Tests that `finalizeBridgeERC721` reverts when not called by the remote bridge. function test_finalizeBridgeERC721_notFromRemoteMessenger_reverts() external { - // Finalize a withdrawal. - vm.mockCall( - address(l2CrossDomainMessenger), - abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(alice) - ); - vm.prank(address(l2CrossDomainMessenger)); + _mockXDomainMessageSender(alice); + vm.prank(address(l2ERC721Bridge.messenger())); vm.expectRevert("ERC721Bridge: function can only be called from the other bridge"); - l2ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + l2ERC721Bridge.finalizeBridgeERC721(address(localToken), remoteToken, alice, alice, tokenId, EXTRA_DATA); } /// @notice Tests that `finalizeBridgeERC721` reverts when the local token is the address of /// the bridge itself. function test_finalizeBridgeERC721_selfToken_reverts() external { - // Finalize a withdrawal. - vm.mockCall( - address(l2CrossDomainMessenger), - abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1ERC721Bridge)) - ); - vm.prank(address(l2CrossDomainMessenger)); + _mockOtherBridge(); + vm.prank(address(l2ERC721Bridge.messenger())); vm.expectRevert("L2ERC721Bridge: local token cannot be self"); - l2ERC721Bridge.finalizeBridgeERC721( - address(l2ERC721Bridge), address(remoteToken), alice, alice, tokenId, hex"5678" - ); + l2ERC721Bridge.finalizeBridgeERC721(address(l2ERC721Bridge), remoteToken, alice, alice, tokenId, EXTRA_DATA); } /// @notice Tests that `finalizeBridgeERC721` reverts when already finalized. function test_finalizeBridgeERC721_alreadyExists_reverts() external { - // Finalize a withdrawal. - vm.mockCall( - address(l2CrossDomainMessenger), - abi.encodeCall(l2CrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l1ERC721Bridge)) - ); - vm.prank(address(l2CrossDomainMessenger)); + _mockOtherBridge(); + vm.prank(address(l2ERC721Bridge.messenger())); vm.expectRevert("ERC721: token already minted"); - l2ERC721Bridge.finalizeBridgeERC721(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + l2ERC721Bridge.finalizeBridgeERC721(address(localToken), remoteToken, alice, alice, tokenId, EXTRA_DATA); } } -/// @title L2ERC721Bridge_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the `L2ERC721Bridge` -/// contract or are testing multiple functions at once. -contract L2ERC721Bridge_Uncategorized_Test is L2ERC721Bridge_TestInit { +/// @title L2ERC721Bridge_Paused_Test +/// @notice Tests the `paused` function of the `L2ERC721Bridge` contract. +contract L2ERC721Bridge_Paused_Test is L2ERC721Bridge_TestInit { /// @notice Ensures that the L2ERC721Bridge is always not paused. The pausability happens on L1 /// and not L2. function test_paused_succeeds() external view { assertFalse(l2ERC721Bridge.paused()); } +} +/// @title L2ERC721Bridge_BridgeERC721_Test +/// @notice Tests the `bridgeERC721` and `bridgeERC721To` functions of the `L2ERC721Bridge` contract. +contract L2ERC721Bridge_BridgeERC721_Test is L2ERC721Bridge_Bridge_TestInit { /// @notice Tests that `bridgeERC721` correctly bridges a token and burns it on the origin /// chain. function test_bridgeERC721_succeeds() public { - // Expect a call to the messenger. - vm.expectCall( - address(l2CrossDomainMessenger), - abi.encodeCall( - l2CrossDomainMessenger.sendMessage, - ( - address(l1ERC721Bridge), - abi.encodeCall( - IL2ERC721Bridge.finalizeBridgeERC721, - (address(remoteToken), address(localToken), alice, alice, tokenId, hex"5678") - ), - 1234 - ) - ) - ); - - // Expect an event to be emitted. - vm.expectEmit(true, true, true, true); - emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + _expectBridgeMessage(alice); + _expectBridgeInitiated(alice); - // Bridge the token. - vm.prank(alice, alice); - l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + _bridgeToken(); - // Token is burned. - vm.expectRevert("ERC721: invalid token ID"); - localToken.ownerOf(tokenId); + _assertTokenBurned(); } /// @notice Tests that `bridgeERC721` reverts if the owner is not an EOA. function test_bridgeERC721_fromContract_reverts() external { - // Bridge the token. vm.etch(alice, hex"01"); vm.prank(alice); vm.expectRevert("ERC721Bridge: account is not externally owned"); - l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721(address(localToken), remoteToken, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); - // Token is not locked in the bridge. - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenOwnedBy(alice); } /// @notice Tests that `bridgeERC721` reverts if the local token is the zero address. function test_bridgeERC721_localTokenZeroAddress_reverts() external { - // Bridge the token. vm.prank(alice, alice); vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - l2ERC721Bridge.bridgeERC721(address(0), address(remoteToken), tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721(address(0), remoteToken, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); - // Token is not locked in the bridge. - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenOwnedBy(alice); } /// @notice Tests that `bridgeERC721` reverts if the remote token is the zero address. function test_bridgeERC721_remoteTokenZeroAddress_reverts() external { - // Bridge the token. vm.prank(alice, alice); vm.expectRevert("L2ERC721Bridge: remote token cannot be address(0)"); - l2ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); - // Token is not locked in the bridge. - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenOwnedBy(alice); } /// @notice Tests that `bridgeERC721` reverts if the caller is not the token owner. function test_bridgeERC721_wrongOwner_reverts() external { - // Bridge the token. vm.prank(bob, bob); vm.expectRevert("L2ERC721Bridge: Withdrawal is not being initiated by NFT owner"); - l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721(address(localToken), remoteToken, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); - // Token is not locked in the bridge. - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenOwnedBy(alice); } /// @notice Tests that `bridgeERC721To` correctly bridges a token and burns it on the origin /// chain. function test_bridgeERC721To_succeeds() external { - // Expect a call to the messenger. - vm.expectCall( - address(l2CrossDomainMessenger), - abi.encodeCall( - l2CrossDomainMessenger.sendMessage, - ( - address(l1ERC721Bridge), - abi.encodeCall( - IL1ERC721Bridge.finalizeBridgeERC721, - (address(remoteToken), address(localToken), alice, bob, tokenId, hex"5678") - ), - 1234 - ) - ) - ); - - // Expect an event to be emitted. - vm.expectEmit(true, true, true, true); - emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, bob, tokenId, hex"5678"); + _expectBridgeMessage(bob); + _expectBridgeInitiated(bob); - // Bridge the token. vm.prank(alice); - l2ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), bob, tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721To(address(localToken), remoteToken, bob, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); - // Token is burned. - vm.expectRevert("ERC721: invalid token ID"); - localToken.ownerOf(tokenId); + _assertTokenBurned(); } /// @notice Tests that `bridgeERC721To` reverts if the local token is the zero address. function test_bridgeERC721To_localTokenZeroAddress_reverts() external { - // Bridge the token. vm.prank(alice); vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - l2ERC721Bridge.bridgeERC721To(address(0), address(l1ERC721Bridge), bob, tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721To( + address(0), address(l1ERC721Bridge), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA + ); - // Token is not locked in the bridge. - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenOwnedBy(alice); } /// @notice Tests that `bridgeERC721To` reverts if the remote token is the zero address. function test_bridgeERC721To_remoteTokenZeroAddress_reverts() external { - // Bridge the token. vm.prank(alice); vm.expectRevert("L2ERC721Bridge: remote token cannot be address(0)"); - l2ERC721Bridge.bridgeERC721To(address(localToken), address(0), bob, tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721To(address(localToken), address(0), bob, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); - // Token is not locked in the bridge. - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenOwnedBy(alice); } /// @notice Tests that `bridgeERC721To` reverts if the caller is not the token owner. function test_bridgeERC721To_wrongOwner_reverts() external { - // Bridge the token. vm.prank(bob); vm.expectRevert("L2ERC721Bridge: Withdrawal is not being initiated by NFT owner"); - l2ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), bob, tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721To(address(localToken), remoteToken, bob, tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA); - // Token is not locked in the bridge. - assertEq(localToken.ownerOf(tokenId), alice); + _assertTokenOwnedBy(alice); } /// @notice Tests that `bridgeERC721To` reverts if the to address is the zero address. function test_bridgeERC721To_toZeroAddress_reverts() external { - // Bridge the token. vm.prank(bob); vm.expectRevert("ERC721Bridge: nft recipient cannot be address(0)"); - l2ERC721Bridge.bridgeERC721To(address(localToken), address(remoteToken), address(0), tokenId, 1234, hex"5678"); + l2ERC721Bridge.bridgeERC721To( + address(localToken), remoteToken, address(0), tokenId, DEFAULT_MIN_GAS_LIMIT, EXTRA_DATA + ); } } From 441e82e22a5e0f4c9ebf704f5c73b52bc4cc3baf Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:17:27 -0400 Subject: [PATCH 086/135] Refactor L2StandardBridge tests: simplify function signatures by removing legacy parameters, enhance clarity with internal helper functions for mocking cross-domain messages, and streamline test setup for improved maintainability. --- test/L2/L2StandardBridge.t.sol | 114 ++++++++++----------------------- 1 file changed, 33 insertions(+), 81 deletions(-) diff --git a/test/L2/L2StandardBridge.t.sol b/test/L2/L2StandardBridge.t.sol index 03332e40..07244695 100644 --- a/test/L2/L2StandardBridge.t.sol +++ b/test/L2/L2StandardBridge.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.15; // Testing -import { stdStorage, StdStorage } from "lib/forge-std/src/Test.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; @@ -26,8 +25,7 @@ import { IL2StandardBridge } from "interfaces/L2/IL2StandardBridge.sol"; /// @notice Reusable test initialization for `L2StandardBridge` tests. abstract contract L2StandardBridge_TestInit is CommonTest { /// @notice Sets up expected calls and emits for a successful ERC20 withdrawal. - function _preBridgeERC20(bool _isLegacy, address _l2Token) internal { - // Alice has 100 L2Token + function _preBridgeERC20(address _l2Token) internal { deal(_l2Token, alice, 100, true); assertEq(ERC20(_l2Token).balanceOf(alice), 100); uint256 nonce = l2CrossDomainMessenger.messageNonce(); @@ -49,17 +47,6 @@ abstract contract L2StandardBridge_TestInit is CommonTest { }) ); - if (_isLegacy) { - vm.expectCall( - address(l2StandardBridge), abi.encodeCall(l2StandardBridge.withdraw, (_l2Token, 100, 1000, hex"")) - ); - } else { - vm.expectCall( - address(l2StandardBridge), - abi.encodeCall(l2StandardBridge.bridgeERC20, (_l2Token, address(L1Token), 100, 1000, hex"")) - ); - } - vm.expectCall( address(l2CrossDomainMessenger), abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l1StandardBridge), message, 1000)) @@ -72,7 +59,6 @@ abstract contract L2StandardBridge_TestInit is CommonTest { ) ); - // The l2StandardBridge should burn the tokens vm.expectCall(_l2Token, abi.encodeCall(OptimismMintableERC20.burn, (alice, 100))); vm.expectEmit(true, true, true, true); @@ -107,9 +93,9 @@ abstract contract L2StandardBridge_TestInit is CommonTest { /// recipient. /// @dev `withdrawTo` and `bridgeERC20To` should behave the same when transferring ERC20 tokens /// so they should share the same setup and expectEmit calls - function _preBridgeERC20To(bool _isLegacy, address _l2Token) internal { + function _preBridgeERC20To(address _l2Token) internal { deal(_l2Token, alice, 100, true); - assertEq(L2Token.balanceOf(alice), 100); + assertEq(ERC20(_l2Token).balanceOf(alice), 100); uint256 nonce = l2CrossDomainMessenger.messageNonce(); bytes memory message = abi.encodeCall(IStandardBridge.finalizeBridgeERC20, (address(L1Token), _l2Token, alice, bob, 100, hex"")); @@ -154,18 +140,6 @@ abstract contract L2StandardBridge_TestInit is CommonTest { vm.expectEmit(address(l2CrossDomainMessenger)); emit SentMessageExtension1(address(l2StandardBridge), 0); - if (_isLegacy) { - vm.expectCall( - address(l2StandardBridge), - abi.encodeCall(l2StandardBridge.withdrawTo, (_l2Token, bob, 100, 1000, hex"")) - ); - } else { - vm.expectCall( - address(l2StandardBridge), - abi.encodeCall(l2StandardBridge.bridgeERC20To, (_l2Token, address(L1Token), bob, 100, 1000, hex"")) - ); - } - vm.expectCall( address(l2CrossDomainMessenger), abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l1StandardBridge), message, 1000)) @@ -178,13 +152,28 @@ abstract contract L2StandardBridge_TestInit is CommonTest { ) ); - // The l2StandardBridge should burn the tokens - vm.expectCall(address(L2Token), abi.encodeCall(OptimismMintableERC20.burn, (alice, 100))); + vm.expectCall(_l2Token, abi.encodeCall(OptimismMintableERC20.burn, (alice, 100))); vm.prank(alice, alice); } - using stdStorage for StdStorage; + function _mockXDomainMessageSender(address _sender) internal { + vm.mockCall( + address(l2StandardBridge.messenger()), + abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), + abi.encode(_sender) + ); + } + + function _mockOtherBridge() internal { + _mockXDomainMessageSender(address(l2StandardBridge.OTHER_BRIDGE())); + } + + function _setupFinalizeBridgeETH() internal returns (address messenger) { + messenger = address(l2StandardBridge.messenger()); + _mockOtherBridge(); + vm.deal(messenger, 100); + } } /// @title L2StandardBridge_Version_Test @@ -348,7 +337,7 @@ contract L2StandardBridge_Withdraw_Test is L2StandardBridge_TestInit { /// @notice Tests that `withdraw` burns the tokens, emits `WithdrawalInitiated`, and initiates /// a withdrawal with `Withdrawer.initiateWithdrawal`. function test_withdraw_withdrawingERC20_succeeds() external { - _preBridgeERC20({ _isLegacy: true, _l2Token: address(L2Token) }); + _preBridgeERC20(address(L2Token)); l2StandardBridge.withdraw(address(L2Token), 100, 1000, hex""); assertEq(L2Token.balanceOf(alice), 0); @@ -378,7 +367,7 @@ contract L2StandardBridge_WithdrawTo_Test is L2StandardBridge_TestInit { /// @notice Tests that `withdrawTo` burns the tokens, emits `WithdrawalInitiated`, and /// initiates a withdrawal with `Withdrawer.initiateWithdrawal`. function test_withdrawTo_withdrawingERC20_succeeds() external { - _preBridgeERC20To({ _isLegacy: true, _l2Token: address(L2Token) }); + _preBridgeERC20To(address(L2Token)); l2StandardBridge.withdrawTo(address(L2Token), bob, 100, 1000, hex""); assertEq(L2Token.balanceOf(alice), 0); @@ -398,7 +387,7 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { /// @notice Tests that `bridgeERC20` burns the tokens, emits `WithdrawalInitiated`, and /// initiates a withdrawal with `Withdrawer.initiateWithdrawal`. function test_bridgeERC20_succeeds() external { - _preBridgeERC20({ _isLegacy: false, _l2Token: address(L2Token) }); + _preBridgeERC20(address(L2Token)); l2StandardBridge.bridgeERC20(address(L2Token), address(L1Token), 100, 1000, hex""); assertEq(L2Token.balanceOf(alice), 0); @@ -413,35 +402,25 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { /// @notice Tests that `bridgeERC20To` burns the tokens, emits `WithdrawalInitiated`, and /// initiates a withdrawal with `Withdrawer.initiateWithdrawal`. function test_bridgeERC20To_succeeds() external { - _preBridgeERC20To({ _isLegacy: false, _l2Token: address(L2Token) }); + _preBridgeERC20To(address(L2Token)); l2StandardBridge.bridgeERC20To(address(L2Token), address(L1Token), bob, 100, 1000, hex""); assertEq(L2Token.balanceOf(alice), 0); } /// @notice Tests that `finalizeBridgeETH` reverts if the recipient is the other bridge. function test_finalizeBridgeETH_sendToSelf_reverts() external { - vm.mockCall( - address(l2StandardBridge.messenger()), - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) - ); - vm.deal(address(l2CrossDomainMessenger), 100); - vm.prank(address(l2CrossDomainMessenger)); + address messenger = _setupFinalizeBridgeETH(); + vm.prank(messenger); vm.expectRevert("StandardBridge: cannot send to self"); l2StandardBridge.finalizeBridgeETH{ value: 100 }(alice, address(l2StandardBridge), 100, hex""); } /// @notice Tests that `finalizeBridgeETH` reverts if the recipient is the messenger. function test_finalizeBridgeETH_sendToMessenger_reverts() external { - vm.mockCall( - address(l2StandardBridge.messenger()), - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) - ); - vm.deal(address(l2CrossDomainMessenger), 100); - vm.prank(address(l2CrossDomainMessenger)); + address messenger = _setupFinalizeBridgeETH(); + vm.prank(messenger); vm.expectRevert("StandardBridge: cannot send to messenger"); - l2StandardBridge.finalizeBridgeETH{ value: 100 }(alice, address(l2CrossDomainMessenger), 100, hex""); + l2StandardBridge.finalizeBridgeETH{ value: 100 }(alice, messenger, 100, hex""); } /// @notice Tests that bridging ETH succeeds. @@ -451,10 +430,6 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { bytes memory message = abi.encodeCall(IStandardBridge.finalizeBridgeETH, (alice, alice, _value, _extraData)); - vm.expectCall( - address(l2StandardBridge), _value, abi.encodeCall(l2StandardBridge.bridgeETH, (_minGasLimit, _extraData)) - ); - vm.expectCall( address(l2CrossDomainMessenger), _value, @@ -483,16 +458,8 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); uint256 nonce = l2CrossDomainMessenger.messageNonce(); - vm.expectCall( - address(l2StandardBridge), - _value, - abi.encodeCall(l1StandardBridge.bridgeETHTo, (bob, _minGasLimit, _extraData)) - ); - bytes memory message = abi.encodeCall(IStandardBridge.finalizeBridgeETH, (alice, bob, _value, _extraData)); - // the L2 bridge should call - // L2CrossDomainMessenger.sendMessage vm.expectCall( address(l2CrossDomainMessenger), abi.encodeCall(ICrossDomainMessenger.sendMessage, (address(l1StandardBridge), message, _minGasLimit)) @@ -509,7 +476,6 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { vm.expectEmit(address(l2CrossDomainMessenger)); emit SentMessageExtension1(address(l2StandardBridge), _value); - // deposit eth to bob vm.deal(alice, _value); vm.prank(alice, alice); @@ -518,13 +484,7 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { /// @notice Tests that `finalizeBridgeETH` succeeds. function test_finalizeBridgeETH_succeeds() external { - address messenger = address(l2StandardBridge.messenger()); - vm.mockCall( - messenger, - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) - ); - vm.deal(messenger, 100); + address messenger = _setupFinalizeBridgeETH(); vm.prank(messenger); vm.expectEmit(true, true, true, true); @@ -541,11 +501,7 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { address messenger = address(l2StandardBridge.messenger()); address localToken = address(L2Token); address remoteToken = address(L1Token); - vm.mockCall( - messenger, - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) - ); + _mockOtherBridge(); deal(localToken, messenger, 100, true); vm.prank(messenger); @@ -562,11 +518,7 @@ contract L2StandardBridge_Uncategorized_Test is L2StandardBridge_TestInit { address messenger = address(l2StandardBridge.messenger()); address localToken = address(L2Token); address remoteToken = address(BadL1Token); - vm.mockCall( - messenger, - abi.encodeCall(ICrossDomainMessenger.xDomainMessageSender, ()), - abi.encode(address(l2StandardBridge.OTHER_BRIDGE())) - ); + _mockOtherBridge(); deal(localToken, messenger, 100, true); vm.prank(messenger); From 534b910dc2cb77e93bd1e4d541151ecf7bce2cb9 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:22:41 -0400 Subject: [PATCH 087/135] Refactor L2ToL1MessagePasser tests: introduce an abstract test initialization contract to streamline test setup, enhance clarity with internal helper functions for message passing, and improve maintainability by reducing code duplication across test contracts. --- test/L2/L2ToL1MessagePasser.t.sol | 146 ++++++++++++++---------------- 1 file changed, 68 insertions(+), 78 deletions(-) diff --git a/test/L2/L2ToL1MessagePasser.t.sol b/test/L2/L2ToL1MessagePasser.t.sol index 5768b36f..ab55fe71 100644 --- a/test/L2/L2ToL1MessagePasser.t.sol +++ b/test/L2/L2ToL1MessagePasser.t.sol @@ -8,11 +8,56 @@ import { CommonTest } from "test/setup/CommonTest.sol"; import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { Features } from "src/libraries/Features.sol"; +import { Encoding } from "src/libraries/Encoding.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; +/// @title L2ToL1MessagePasser_TestInit +/// @notice Reusable test initialization for `L2ToL1MessagePasser` tests. +abstract contract L2ToL1MessagePasser_TestInit is CommonTest { + uint256 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; + + function _hashWithdrawal( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + pure + returns (bytes32) + { + return Hashing.hashWithdrawal( + Types.WithdrawalTransaction({ + nonce: _nonce, sender: _sender, target: _target, value: _value, gasLimit: _gasLimit, data: _data + }) + ); + } + + function _expectMessagePassed( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + internal + returns (bytes32) + { + bytes32 withdrawalHash = _hashWithdrawal(_nonce, _sender, _target, _value, _gasLimit, _data); + + vm.expectEmit(address(l2ToL1MessagePasser)); + emit MessagePassed(_nonce, _sender, _target, _value, _gasLimit, _data, withdrawalHash); + + return withdrawalHash; + } +} + /// @title L2ToL1MessagePasser_Version_Test /// @notice Tests the `version` function of the `L2ToL1MessagePasser` contract. -contract L2ToL1MessagePasser_Version_Test is CommonTest { +contract L2ToL1MessagePasser_Version_Test is L2ToL1MessagePasser_TestInit { /// @notice Tests that the version follows valid semver format. function test_version_validFormat_succeeds() external view { SemverComp.parse(l2ToL1MessagePasser.version()); @@ -21,47 +66,32 @@ contract L2ToL1MessagePasser_Version_Test is CommonTest { /// @title L2ToL1MessagePasser_Receive_Test /// @notice Tests the `receive` function of the `L2ToL1MessagePasser` contract. -contract L2ToL1MessagePasser_Receive_Test is CommonTest { +contract L2ToL1MessagePasser_Receive_Test is L2ToL1MessagePasser_TestInit { /// @notice Tests that receive() initiates withdrawal with default gas limit. function testFuzz_receive_initiatesWithdrawal_succeeds(uint256 _value) external { skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); uint256 nonce = l2ToL1MessagePasser.messageNonce(); - - bytes32 withdrawalHash = Hashing.hashWithdrawal( - Types.WithdrawalTransaction({ - nonce: nonce, - sender: address(this), - target: address(this), - value: _value, - gasLimit: 100_000, // RECEIVE_DEFAULT_GAS_LIMIT - data: bytes("") - }) - ); - - vm.expectEmit(address(l2ToL1MessagePasser)); - emit MessagePassed(nonce, address(this), address(this), _value, 100_000, bytes(""), withdrawalHash); + bytes memory data = bytes(""); + bytes32 withdrawalHash = + _expectMessagePassed(nonce, address(this), address(this), _value, RECEIVE_DEFAULT_GAS_LIMIT, data); vm.deal(address(this), _value); - (bool success,) = address(l2ToL1MessagePasser).call{ value: _value }(""); + (bool success,) = address(l2ToL1MessagePasser).call{ value: _value }(data); assertTrue(success); - assertEq(l2ToL1MessagePasser.sentMessages(withdrawalHash), true); + assertTrue(l2ToL1MessagePasser.sentMessages(withdrawalHash)); assertEq(l2ToL1MessagePasser.messageNonce(), nonce + 1); } } /// @title L2ToL1MessagePasser_Burn_Test /// @notice Tests the `burn` function of the `L2ToL1MessagePasser` contract. -contract L2ToL1MessagePasser_Burn_Test is CommonTest { +contract L2ToL1MessagePasser_Burn_Test is L2ToL1MessagePasser_TestInit { /// @notice Tests that `burn` succeeds and destroys the ETH held in the contract. - function testFuzz_burn_succeeds(uint256 _value, address _target, uint256 _gasLimit, bytes memory _data) external { + function testFuzz_burn_succeeds(uint256 _value) external { skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); - vm.deal(address(this), _value); - - l2ToL1MessagePasser.initiateWithdrawal{ value: _value }({ - _target: _target, _gasLimit: _gasLimit, _data: _data - }); + vm.deal(address(l2ToL1MessagePasser), _value); assertEq(address(l2ToL1MessagePasser).balance, _value); @@ -69,14 +99,13 @@ contract L2ToL1MessagePasser_Burn_Test is CommonTest { emit WithdrawerBalanceBurnt(_value); l2ToL1MessagePasser.burn(); - // The Withdrawer should have no balance assertEq(address(l2ToL1MessagePasser).balance, 0); } } /// @title L2ToL1MessagePasser_InitiateWithdrawal_Test /// @notice Tests the `initiateWithdrawal` function of the `L2ToL1MessagePasser` contract. -contract L2ToL1MessagePasser_InitiateWithdrawal_Test is CommonTest { +contract L2ToL1MessagePasser_InitiateWithdrawal_Test is L2ToL1MessagePasser_TestInit { /// @notice Tests that `initiateWithdrawal` succeeds and correctly sets the state of the /// message passer for the withdrawal hash. function testFuzz_initiateWithdrawal_succeeds( @@ -93,25 +122,14 @@ contract L2ToL1MessagePasser_InitiateWithdrawal_Test is CommonTest { } uint256 nonce = l2ToL1MessagePasser.messageNonce(); - bytes32 withdrawalHash = Hashing.hashWithdrawal( - Types.WithdrawalTransaction({ - nonce: nonce, sender: _sender, target: _target, value: _value, gasLimit: _gasLimit, data: _data - }) - ); - - vm.expectEmit(address(l2ToL1MessagePasser)); - emit MessagePassed(nonce, _sender, _target, _value, _gasLimit, _data, withdrawalHash); + bytes32 withdrawalHash = _expectMessagePassed(nonce, _sender, _target, _value, _gasLimit, _data); vm.deal(_sender, _value); vm.prank(_sender); l2ToL1MessagePasser.initiateWithdrawal{ value: _value }(_target, _gasLimit, _data); - assertEq(l2ToL1MessagePasser.sentMessages(withdrawalHash), true); + assertTrue(l2ToL1MessagePasser.sentMessages(withdrawalHash)); assertEq(l2ToL1MessagePasser.messageNonce(), nonce + 1); - - bytes32 slot = keccak256(bytes.concat(withdrawalHash, bytes32(0))); - - assertEq(vm.load(address(l2ToL1MessagePasser), slot), bytes32(uint256(1))); } /// @notice Tests that `initiateWithdrawal` succeeds when called by a contract. @@ -124,26 +142,13 @@ contract L2ToL1MessagePasser_InitiateWithdrawal_Test is CommonTest { external { skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); - bytes32 withdrawalHash = Hashing.hashWithdrawal( - Types.WithdrawalTransaction({ - nonce: l2ToL1MessagePasser.messageNonce(), - sender: address(this), - target: _target, - value: _value, - gasLimit: _gasLimit, - data: _data - }) - ); - - vm.expectEmit(address(l2ToL1MessagePasser)); - emit MessagePassed( - l2ToL1MessagePasser.messageNonce(), address(this), _target, _value, _gasLimit, _data, withdrawalHash - ); + uint256 nonce = l2ToL1MessagePasser.messageNonce(); + bytes32 withdrawalHash = _expectMessagePassed(nonce, address(this), _target, _value, _gasLimit, _data); vm.deal(address(this), _value); l2ToL1MessagePasser.initiateWithdrawal{ value: _value }(_target, _gasLimit, _data); - assertEq(l2ToL1MessagePasser.sentMessages(withdrawalHash), true); + assertTrue(l2ToL1MessagePasser.sentMessages(withdrawalHash)); } /// @notice Tests that `initiateWithdrawal` succeeds when called by an EOA. @@ -157,40 +162,25 @@ contract L2ToL1MessagePasser_InitiateWithdrawal_Test is CommonTest { { skipIfSysFeatureEnabled(Features.CUSTOM_GAS_TOKEN); uint256 nonce = l2ToL1MessagePasser.messageNonce(); - - // Verify caller is an EOA (alice has no code) - assertEq(alice.code.length, 0); - - // EOA emulation - vm.prank(alice, alice); vm.deal(alice, _value); - bytes32 withdrawalHash = - Hashing.hashWithdrawal(Types.WithdrawalTransaction(nonce, alice, _target, _value, _gasLimit, _data)); - - vm.expectEmit(address(l2ToL1MessagePasser)); - emit MessagePassed(nonce, alice, _target, _value, _gasLimit, _data, withdrawalHash); + bytes32 withdrawalHash = _expectMessagePassed(nonce, alice, _target, _value, _gasLimit, _data); + vm.prank(alice, alice); l2ToL1MessagePasser.initiateWithdrawal{ value: _value }({ _target: _target, _gasLimit: _gasLimit, _data: _data }); - // the sent messages mapping is filled - assertEq(l2ToL1MessagePasser.sentMessages(withdrawalHash), true); - // the nonce increments - assertEq(nonce + 1, l2ToL1MessagePasser.messageNonce()); + assertTrue(l2ToL1MessagePasser.sentMessages(withdrawalHash)); + assertEq(l2ToL1MessagePasser.messageNonce(), nonce + 1); } } /// @title L2ToL1MessagePasser_MessageNonce_Test /// @notice Tests the `messageNonce` function of the `L2ToL1MessagePasser` contract. -contract L2ToL1MessagePasser_MessageNonce_Test is CommonTest { +contract L2ToL1MessagePasser_MessageNonce_Test is L2ToL1MessagePasser_TestInit { /// @notice Tests that messageNonce encodes version in upper bytes. function test_messageNonce_encodesVersion_succeeds() external view { - uint256 nonce = l2ToL1MessagePasser.messageNonce(); - - // MESSAGE_VERSION is 1, should be in upper 2 bytes - // Version is stored in bits 240-255 (upper 2 bytes of uint256) - uint256 version = nonce >> 240; - assertEq(version, 1); + (, uint16 version) = Encoding.decodeVersionedNonce(l2ToL1MessagePasser.messageNonce()); + assertEq(version, l2ToL1MessagePasser.MESSAGE_VERSION()); } } From 2a219943ab5972a043192a10af1ea4209e805eed Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:26:06 -0400 Subject: [PATCH 088/135] Refactor OperatorFeeVault tests: streamline test setup by removing hardcoded feeVaultName assignment, enhance clarity by simplifying the setUp function, and improve maintainability with consistent variable assignments for recipient and withdrawal network configuration. --- test/L2/OperatorFeeVault.t.sol | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/L2/OperatorFeeVault.t.sol b/test/L2/OperatorFeeVault.t.sol index 9f09f567..c672a951 100644 --- a/test/L2/OperatorFeeVault.t.sol +++ b/test/L2/OperatorFeeVault.t.sol @@ -1,34 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Interfaces import { IFeeVault } from "interfaces/L2/IFeeVault.sol"; - -// Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { FeeVault_Uncategorized_Test } from "test/L2/FeeVault.t.sol"; import { Types } from "src/libraries/Types.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; -/// @title OperatorFeeVault_Uncategorized_Test -/// @notice Test contract for the OperatorFeeVault contract's functionality contract OperatorFeeVault_Uncategorized_Test is FeeVault_Uncategorized_Test { - /// @dev Sets up the test suite. - function setUp() public virtual override { + function setUp() public override { super.setUp(); recipient = deploy.cfg().operatorFeeVaultRecipient(); - feeVaultName = "OperatorFeeVault"; minWithdrawalAmount = deploy.cfg().operatorFeeVaultMinimumWithdrawalAmount(); feeVault = IFeeVault(payable(Predeploys.OPERATOR_FEE_VAULT)); withdrawalNetwork = Types.WithdrawalNetwork(uint8(deploy.cfg().operatorFeeVaultWithdrawalNetwork())); } } -/// @title OperatorFeeVault_Version_Test -/// @notice Tests the `version` function of the `OperatorFeeVault` contract. contract OperatorFeeVault_Version_Test is CommonTest { - /// @notice Tests that version returns a valid semver string. function test_version_validFormat_succeeds() external view { SemverComp.parse(operatorFeeVault.version()); } From 2955f11a4819b9de02bb0de7a2fa66aabddf1ee0 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:33:48 -0400 Subject: [PATCH 089/135] Refactor OptimismMintableERC721 tests: replace hardcoded values with constants for remoteChainId and tokenId, streamline test setup with internal helper functions for minting and burning, and enhance clarity in event expectations and assertions for improved maintainability. --- test/L2/OptimismMintableERC721.t.sol | 108 +++++++++++---------------- 1 file changed, 45 insertions(+), 63 deletions(-) diff --git a/test/L2/OptimismMintableERC721.t.sol b/test/L2/OptimismMintableERC721.t.sol index cc5ffe53..0783be4a 100644 --- a/test/L2/OptimismMintableERC721.t.sol +++ b/test/L2/OptimismMintableERC721.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { ERC721, IERC721 } from "lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; +import { IERC721 } from "lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol"; import { IERC721Enumerable } from "lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import { IERC165 } from "lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol"; import { Strings } from "lib/openzeppelin-contracts/contracts/utils/Strings.sol"; @@ -11,7 +11,10 @@ import { OptimismMintableERC721, IOptimismMintableERC721 } from "src/L2/Optimism /// @title OptimismMintableERC721_TestInit /// @notice Reusable test initialization for `OptimismMintableERC721` tests. abstract contract OptimismMintableERC721_TestInit is CommonTest { - ERC721 internal L1NFT; + uint256 internal constant remoteChainId = 1; + uint256 internal constant tokenId = 1; + + address internal L1NFT; OptimismMintableERC721 internal L2NFT; event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); @@ -23,14 +26,17 @@ abstract contract OptimismMintableERC721_TestInit is CommonTest { function setUp() public override { super.setUp(); - // Set up the token pair. - L1NFT = new ERC721("L1NFT", "L1T"); - L2NFT = new OptimismMintableERC721(address(l2ERC721Bridge), 1, address(L1NFT), "L2NFT", "L2T"); + L1NFT = makeAddr("L1NFT"); + L2NFT = new OptimismMintableERC721(address(l2ERC721Bridge), remoteChainId, L1NFT, "L2NFT", "L2T"); - // Label the addresses for nice traces. - vm.label(address(L1NFT), "L1ERC721Token"); + vm.label(L1NFT, "L1ERC721Token"); vm.label(address(L2NFT), "L2ERC721Token"); } + + function _bridgeMint(address _to) internal { + vm.prank(address(l2ERC721Bridge)); + L2NFT.safeMint(_to, tokenId); + } } /// @title OptimismMintableERC721_Constructor_Test @@ -40,30 +46,30 @@ contract OptimismMintableERC721_Constructor_Test is OptimismMintableERC721_TestI function test_constructor_succeeds() external view { assertEq(L2NFT.name(), "L2NFT"); assertEq(L2NFT.symbol(), "L2T"); - assertEq(L2NFT.remoteToken(), address(L1NFT)); + assertEq(L2NFT.remoteToken(), L1NFT); assertEq(L2NFT.bridge(), address(l2ERC721Bridge)); - assertEq(L2NFT.remoteChainId(), 1); - assertEq(L2NFT.REMOTE_TOKEN(), address(L1NFT)); + assertEq(L2NFT.remoteChainId(), remoteChainId); + assertEq(L2NFT.REMOTE_TOKEN(), L1NFT); assertEq(L2NFT.BRIDGE(), address(l2ERC721Bridge)); - assertEq(L2NFT.REMOTE_CHAIN_ID(), 1); + assertEq(L2NFT.REMOTE_CHAIN_ID(), remoteChainId); } /// @notice Tests that the constructor reverts when the bridge address is zero. function test_constructor_bridgeAsAddress0_reverts() external { vm.expectRevert("OptimismMintableERC721: bridge cannot be address(0)"); - L2NFT = new OptimismMintableERC721(address(0), 1, address(L1NFT), "L2NFT", "L2T"); + L2NFT = new OptimismMintableERC721(address(0), remoteChainId, L1NFT, "L2NFT", "L2T"); } /// @notice Tests that the constructor reverts when the remote chain ID is zero. function test_constructor_remoteChainId0_reverts() external { vm.expectRevert("OptimismMintableERC721: remote chain id cannot be zero"); - L2NFT = new OptimismMintableERC721(address(l2ERC721Bridge), 0, address(L1NFT), "L2NFT", "L2T"); + L2NFT = new OptimismMintableERC721(address(l2ERC721Bridge), 0, L1NFT, "L2NFT", "L2T"); } /// @notice Tests that the constructor reverts when the remote token address is zero. function test_constructor_remoteTokenAsAddress0_reverts() external { vm.expectRevert("OptimismMintableERC721: remote token cannot be address(0)"); - L2NFT = new OptimismMintableERC721(address(l2ERC721Bridge), 1, address(0), "L2NFT", "L2T"); + L2NFT = new OptimismMintableERC721(address(l2ERC721Bridge), remoteChainId, address(0), "L2NFT", "L2T"); } } @@ -73,28 +79,22 @@ contract OptimismMintableERC721_SafeMint_Test is OptimismMintableERC721_TestInit /// @notice Tests that the `safeMint` function successfully mints a token when called by the /// bridge. function test_safeMint_succeeds() external { - // Expect a transfer event. - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), alice, 1); + vm.expectEmit(address(L2NFT)); + emit Transfer(address(0), alice, tokenId); - // Expect a mint event. - vm.expectEmit(true, true, true, true); - emit Mint(alice, 1); + vm.expectEmit(address(L2NFT)); + emit Mint(alice, tokenId); - // Mint the token. - vm.prank(address(l2ERC721Bridge)); - L2NFT.safeMint(alice, 1); + _bridgeMint(alice); - // Token should be owned by alice. - assertEq(L2NFT.ownerOf(1), alice); + assertEq(L2NFT.ownerOf(tokenId), alice); } /// @notice Tests that the `safeMint` function reverts when called by an address other than the bridge. function test_safeMint_notBridge_reverts() external { - // Try to mint the token. vm.expectRevert("OptimismMintableERC721: only bridge can call this function"); vm.prank(address(alice)); - L2NFT.safeMint(alice, 1); + L2NFT.safeMint(alice, tokenId); } } @@ -104,38 +104,29 @@ contract OptimismMintableERC721_Burn_Test is OptimismMintableERC721_TestInit { /// @notice Tests that the `burn` function successfully burns a token when called by the /// bridge. function test_burn_succeeds() external { - // Mint the token first. - vm.prank(address(l2ERC721Bridge)); - L2NFT.safeMint(alice, 1); + _bridgeMint(alice); - // Expect a transfer event. - vm.expectEmit(true, true, true, true); - emit Transfer(alice, address(0), 1); + vm.expectEmit(address(L2NFT)); + emit Transfer(alice, address(0), tokenId); - // Expect a burn event. - vm.expectEmit(true, true, true, true); - emit Burn(alice, 1); + vm.expectEmit(address(L2NFT)); + emit Burn(alice, tokenId); - // Burn the token. vm.prank(address(l2ERC721Bridge)); - L2NFT.burn(alice, 1); + L2NFT.burn(alice, tokenId); - // Token should be owned by address(0). vm.expectRevert("ERC721: invalid token ID"); - L2NFT.ownerOf(1); + L2NFT.ownerOf(tokenId); } /// @notice Tests that the `burn` function reverts when called by an address other than the /// bridge. function test_burn_notBridge_reverts() external { - // Mint the token first. - vm.prank(address(l2ERC721Bridge)); - L2NFT.safeMint(alice, 1); + _bridgeMint(alice); - // Try to burn the token. vm.expectRevert("OptimismMintableERC721: only bridge can call this function"); vm.prank(address(alice)); - L2NFT.burn(alice, 1); + L2NFT.burn(alice, tokenId); } } @@ -145,13 +136,9 @@ contract OptimismMintableERC721_SupportsInterface_Test is OptimismMintableERC721 /// @notice Tests that the `supportsInterface` function returns true for /// IOptimismMintableERC721, IERC721Enumerable, IERC721 and IERC165 interfaces. function test_supportsInterface_succeeds() external view { - // Checks if the contract supports the IOptimismMintableERC721 interface. assertTrue(L2NFT.supportsInterface(type(IOptimismMintableERC721).interfaceId)); - // Checks if the contract supports the IERC721Enumerable interface. assertTrue(L2NFT.supportsInterface(type(IERC721Enumerable).interfaceId)); - // Checks if the contract supports the IERC721 interface. assertTrue(L2NFT.supportsInterface(type(IERC721).interfaceId)); - // Checks if the contract supports the IERC165 interface. assertTrue(L2NFT.supportsInterface(type(IERC165).interfaceId)); } } @@ -162,22 +149,17 @@ contract OptimismMintableERC721_SupportsInterface_Test is OptimismMintableERC721 contract OptimismMintableERC721_Uncategorized_Test is OptimismMintableERC721_TestInit { /// @notice Tests that the `tokenURI` function returns the correct URI for a minted token. function test_tokenURI_succeeds() external { - // Mint the token first. - vm.prank(address(l2ERC721Bridge)); - L2NFT.safeMint(alice, 1); + _bridgeMint(alice); - // Token URI should be correct. assertEq( - L2NFT.tokenURI(1), - string( - abi.encodePacked( - "ethereum:", - Strings.toHexString(uint160(address(L1NFT)), 20), - "@", - Strings.toString(1), - "/tokenURI?uint256=", - Strings.toString(1) - ) + L2NFT.tokenURI(tokenId), + string.concat( + "ethereum:", + Strings.toHexString(uint160(L1NFT), 20), + "@", + Strings.toString(remoteChainId), + "/tokenURI?uint256=", + Strings.toString(tokenId) ) ); } From ae218df317ac9746473a01bc4f050151021df3ab Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:38:12 -0400 Subject: [PATCH 090/135] Refactor OptimismMintableERC721Factory tests: replace hardcoded values with constants for remote token address, token name, and token symbol, enhance clarity in function signatures, and streamline test assertions for improved maintainability. --- test/L2/OptimismMintableERC721Factory.t.sol | 35 +++++++++------------ 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/test/L2/OptimismMintableERC721Factory.t.sol b/test/L2/OptimismMintableERC721Factory.t.sol index 8d45a34c..b11cb483 100644 --- a/test/L2/OptimismMintableERC721Factory.t.sol +++ b/test/L2/OptimismMintableERC721Factory.t.sol @@ -7,9 +7,14 @@ import { OptimismMintableERC721 } from "src/L2/OptimismMintableERC721.sol"; /// @title OptimismMintableERC721Factory_TestInit /// @notice Reusable test initialization for `OptimismMintableERC721Factory` tests. abstract contract OptimismMintableERC721Factory_TestInit is CommonTest { + address internal constant REMOTE_TOKEN = address(1234); + string internal constant TOKEN_NAME = "L2Token"; + string internal constant TOKEN_SYMBOL = "L2T"; + event OptimismMintableERC721Created(address indexed localToken, address indexed remoteToken, address deployer); - function calculateTokenAddress( + /// @notice Precalculates the address of the token contract. + function _calculateTokenAddress( address _remote, string memory _name, string memory _symbol @@ -47,29 +52,22 @@ contract OptimismMintableERC721Factory_Constructor_Test is OptimismMintableERC72 contract OptimismMintableERC721Factory_CreateOptimismMintableERC721_Test is OptimismMintableERC721Factory_TestInit { /// @notice Tests that the `createOptimismMintableERC721` function succeeds. function test_createOptimismMintableERC721_succeeds() external { - address remote = address(1234); - address local = calculateTokenAddress(address(1234), "L2Token", "L2T"); + address local = _calculateTokenAddress(REMOTE_TOKEN, TOKEN_NAME, TOKEN_SYMBOL); - // Expect a token creation event. vm.expectEmit(address(l2OptimismMintableERC721Factory)); - emit OptimismMintableERC721Created(local, remote, alice); + emit OptimismMintableERC721Created(local, REMOTE_TOKEN, alice); - // Create the token. vm.prank(alice); OptimismMintableERC721 created = OptimismMintableERC721( - l2OptimismMintableERC721Factory.createOptimismMintableERC721(remote, "L2Token", "L2T") + l2OptimismMintableERC721Factory.createOptimismMintableERC721(REMOTE_TOKEN, TOKEN_NAME, TOKEN_SYMBOL) ); - // Token address should be correct. assertEq(address(created), local); - - // Should be marked as created by the factory. assertTrue(l2OptimismMintableERC721Factory.isOptimismMintableERC721(address(created))); - // Token should've been constructed correctly. - assertEq(created.name(), "L2Token"); - assertEq(created.symbol(), "L2T"); - assertEq(created.REMOTE_TOKEN(), remote); + assertEq(created.name(), TOKEN_NAME); + assertEq(created.symbol(), TOKEN_SYMBOL); + assertEq(created.REMOTE_TOKEN(), REMOTE_TOKEN); assertEq(created.BRIDGE(), address(l2ERC721Bridge)); assertEq(created.REMOTE_CHAIN_ID(), deploy.cfg().l1ChainId()); } @@ -77,22 +75,19 @@ contract OptimismMintableERC721Factory_CreateOptimismMintableERC721_Test is Opti /// @notice Tests that the `createOptimismMintableERC721` function reverts if the same token is /// created twice. function test_createOptimismMintableERC721_sameTwice_reverts() external { - address remote = address(1234); - vm.prank(alice); - l2OptimismMintableERC721Factory.createOptimismMintableERC721(remote, "L2Token", "L2T"); + l2OptimismMintableERC721Factory.createOptimismMintableERC721(REMOTE_TOKEN, TOKEN_NAME, TOKEN_SYMBOL); vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args vm.prank(alice); - l2OptimismMintableERC721Factory.createOptimismMintableERC721(remote, "L2Token", "L2T"); + l2OptimismMintableERC721Factory.createOptimismMintableERC721(REMOTE_TOKEN, TOKEN_NAME, TOKEN_SYMBOL); } /// @notice Tests that the `createOptimismMintableERC721` function reverts if the remote token /// address is zero. function test_createOptimismMintableERC721_zeroRemoteToken_reverts() external { - // Try to create a token with a zero remote token address. vm.expectRevert("OptimismMintableERC721Factory: L1 token address cannot be address(0)"); - l2OptimismMintableERC721Factory.createOptimismMintableERC721(address(0), "L2Token", "L2T"); + l2OptimismMintableERC721Factory.createOptimismMintableERC721(address(0), TOKEN_NAME, TOKEN_SYMBOL); } } From cf3fe6b2f100082406fd8e476b3a05be8e12912a Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:42:58 -0400 Subject: [PATCH 091/135] Refactor SequencerFeeVault tests: streamline test setup by removing hardcoded feeVaultName assignment, enhance clarity with updated function names, and improve maintainability with consistent variable assignments for recipient and withdrawal network configuration. --- test/L2/SequencerFeeVault.t.sol | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test/L2/SequencerFeeVault.t.sol b/test/L2/SequencerFeeVault.t.sol index 5ef59377..307b59ed 100644 --- a/test/L2/SequencerFeeVault.t.sol +++ b/test/L2/SequencerFeeVault.t.sol @@ -1,29 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Interfaces import { IFeeVault } from "interfaces/L2/IFeeVault.sol"; -import { ISequencerFeeVault } from "interfaces/L2/ISequencerFeeVault.sol"; -// Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { FeeVault_Uncategorized_Test } from "test/L2/FeeVault.t.sol"; import { Types } from "src/libraries/Types.sol"; -/// @title SequencerFeeVault_Uncategorized_Test -/// @notice Test contract for the SequencerFeeVault contract's functionality contract SequencerFeeVault_Uncategorized_Test is FeeVault_Uncategorized_Test { - /// @dev Sets up the test suite. function setUp() public virtual override { super.setUp(); recipient = deploy.cfg().sequencerFeeVaultRecipient(); - feeVaultName = "SequencerFeeVault"; minWithdrawalAmount = deploy.cfg().sequencerFeeVaultMinimumWithdrawalAmount(); feeVault = IFeeVault(payable(Predeploys.SEQUENCER_FEE_WALLET)); - withdrawalNetwork = Types.WithdrawalNetwork(uint8(deploy.cfg().sequencerFeeVaultWithdrawalNetwork())); + withdrawalNetwork = Types.WithdrawalNetwork(deploy.cfg().sequencerFeeVaultWithdrawalNetwork()); } - function test_constructor_l1FeeWallet_succeeds() external view { - assertEq(ISequencerFeeVault(payable(address(feeVault))).l1FeeWallet(), recipient); + function test_l1FeeWallet_matchesRecipient_succeeds() external view { + assertEq(sequencerFeeVault.l1FeeWallet(), recipient); } } From c76af2ab5f80371d1cb951eb9400b27cdc3bba99 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:46:33 -0400 Subject: [PATCH 092/135] Refactor WETH tests: update function names for clarity, streamline assertions by directly comparing expected and actual values, and enhance test structure with consistent naming conventions for improved maintainability. --- test/L2/WETH.t.sol | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/test/L2/WETH.t.sol b/test/L2/WETH.t.sol index 12581b33..fb86c95e 100644 --- a/test/L2/WETH.t.sol +++ b/test/L2/WETH.t.sol @@ -1,54 +1,40 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing utilities import { CommonTest } from "test/setup/CommonTest.sol"; - -// Libraries import { SemverComp } from "src/libraries/SemverComp.sol"; - -// Interfaces import { ISemver } from "interfaces/universal/ISemver.sol"; -/// @title WETH_Version_Test -/// @notice Tests the `version` function of the `WETH` contract. contract WETH_Version_Test is CommonTest { - /// @notice Tests that the version returns a valid semver string. - function test_version_succeeds() external view { + function test_version_validFormat_succeeds() external view { SemverComp.parse(ISemver(address(weth)).version()); } } -/// @title WETH_Name_Test -/// @notice Tests the `name` function of the `WETH` contract. contract WETH_Name_Test is CommonTest { - /// @notice Tests that the `name` function returns the correct value. function testFuzz_name_succeeds(string memory _gasPayingTokenName) external { + vm.assume(bytes(_gasPayingTokenName).length <= 128); vm.mockCall(address(l1Block), abi.encodeCall(l1Block.gasPayingTokenName, ()), abi.encode(_gasPayingTokenName)); - assertEq(string.concat("Wrapped ", _gasPayingTokenName), weth.name()); + assertEq(weth.name(), string.concat("Wrapped ", _gasPayingTokenName)); } - /// @notice Tests that the `name` function returns 'Wrapped Ether' by default. - function test_name_ether_succeeds() external view { - assertEq(string.concat("Wrapped ", l1Block.gasPayingTokenName()), weth.name()); + function test_name_defaultGasPayingToken_succeeds() external view { + assertEq(weth.name(), string.concat("Wrapped ", l1Block.gasPayingTokenName())); } } -/// @title WETH_Symbol_Test -/// @notice Tests the `symbol` function of the `WETH` contract. contract WETH_Symbol_Test is CommonTest { - /// @notice Tests that the `symbol` function returns the correct value. function testFuzz_symbol_succeeds(string memory _gasPayingTokenSymbol) external { + vm.assume(bytes(_gasPayingTokenSymbol).length <= 128); vm.mockCall( address(l1Block), abi.encodeCall(l1Block.gasPayingTokenSymbol, ()), abi.encode(_gasPayingTokenSymbol) ); - assertEq(string.concat("W", _gasPayingTokenSymbol), weth.symbol()); + assertEq(weth.symbol(), string.concat("W", _gasPayingTokenSymbol)); } - /// @notice Tests that the `symbol` function returns 'WETH' by default. - function test_symbol_ether_succeeds() external view { - assertEq(string.concat("W", l1Block.gasPayingTokenSymbol()), weth.symbol()); + function test_symbol_defaultGasPayingToken_succeeds() external view { + assertEq(weth.symbol(), string.concat("W", l1Block.gasPayingTokenSymbol())); } } From f8d5b26b7bbdc41bbf9518a317b61b0ca86456fe Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:52:50 -0400 Subject: [PATCH 093/135] Refactor L1ChugSplashProxy tests: simplify test setup by introducing an abstract initialization contract, replace hardcoded bytecode with constants for clarity, and enhance maintainability through consistent use of internal helper functions across test contracts. --- test/legacy/L1ChugSplashProxy.t.sol | 81 +++++++++++++++-------------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/test/legacy/L1ChugSplashProxy.t.sol b/test/legacy/L1ChugSplashProxy.t.sol index bc5c6231..e7662cb6 100644 --- a/test/legacy/L1ChugSplashProxy.t.sol +++ b/test/legacy/L1ChugSplashProxy.t.sol @@ -16,11 +16,7 @@ import { LibString } from "lib/solady/src/utils/LibString.sol"; import { IL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; contract L1ChugSplashProxy_Owner_Harness { - bool public isUpgrading; - - function setIsUpgrading(bool _isUpgrading) public { - isUpgrading = _isUpgrading; - } + bool public isUpgrading = true; } contract L1ChugSplashProxy_Implementation_Harness { @@ -48,20 +44,37 @@ contract L1ChugSplashProxy_Implementation_Harness { /// @title L1ChugSplashProxy_TestInit /// @notice Reusable test initialization for `L1ChugSplashProxy` tests. abstract contract L1ChugSplashProxy_TestInit is Test { + bytes internal constant RETURN_42_BYTECODE = hex"604260005260206000f3"; + IL1ChugSplashProxy proxy; - address impl; address owner = makeAddr("owner"); address alice = makeAddr("alice"); - function setUp() public { - proxy = IL1ChugSplashProxy( + function setUp() public virtual { + proxy = _deployProxy(); + vm.prank(owner); + assertEq(proxy.getOwner(), owner); + } + + function _deployProxy() internal returns (IL1ChugSplashProxy) { + return IL1ChugSplashProxy( DeployUtils.create1({ _name: "L1ChugSplashProxy", _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ChugSplashProxy.__constructor__, (owner))) }) ); - vm.prank(owner); - assertEq(proxy.getOwner(), owner); + } + + function _proxyAsImplementation() internal view returns (L1ChugSplashProxy_Implementation_Harness) { + return L1ChugSplashProxy_Implementation_Harness(address(proxy)); + } +} + +abstract contract L1ChugSplashProxy_WithImplementation_TestInit is L1ChugSplashProxy_TestInit { + address impl; + + function setUp() public virtual override { + super.setUp(); vm.prank(owner); proxy.setCode(type(L1ChugSplashProxy_Implementation_Harness).runtimeCode); @@ -73,11 +86,11 @@ abstract contract L1ChugSplashProxy_TestInit is Test { /// @title L1ChugSplashProxy_SetCode_Test /// @notice Tests the `setCode` function of the `L1ChugSplashProxy` contract. -contract L1ChugSplashProxy_SetCode_Test is L1ChugSplashProxy_TestInit { +contract L1ChugSplashProxy_SetCode_Test is L1ChugSplashProxy_WithImplementation_TestInit { /// @notice Tests that the owner can deploy a new implementation with a given runtime code. function test_setCode_whenOwner_succeeds() public { vm.prank(owner); - proxy.setCode(hex"604260005260206000f3"); + proxy.setCode(RETURN_42_BYTECODE); vm.prank(owner); assertNotEq(proxy.getImplementation(), impl); @@ -85,7 +98,7 @@ contract L1ChugSplashProxy_SetCode_Test is L1ChugSplashProxy_TestInit { /// @notice Tests that when not the owner, `setCode` delegatecalls the implementation. function test_setCode_whenNotOwner_works() public view { - uint256 ret = L1ChugSplashProxy_Implementation_Harness(address(proxy)).setCode(hex"604260005260206000f3"); + uint256 ret = _proxyAsImplementation().setCode(RETURN_42_BYTECODE); assertEq(ret, 1); } @@ -95,7 +108,6 @@ contract L1ChugSplashProxy_SetCode_Test is L1ChugSplashProxy_TestInit { vm.prank(owner); proxy.setCode(type(L1ChugSplashProxy_Implementation_Harness).runtimeCode); - // does not deploy new implementation vm.prank(owner); assertEq(proxy.getImplementation(), impl); } @@ -135,7 +147,7 @@ contract L1ChugSplashProxy_SetCode_Test is L1ChugSplashProxy_TestInit { /// @title L1ChugSplashProxy_SetStorage_Test /// @notice Tests the `setStorage` function of the `L1ChugSplashProxy` contract. -contract L1ChugSplashProxy_SetStorage_Test is L1ChugSplashProxy_TestInit { +contract L1ChugSplashProxy_SetStorage_Test is L1ChugSplashProxy_WithImplementation_TestInit { /// @notice Tests that the owner can set storage of the proxy function test_setStorage_whenOwner_works() public { vm.prank(owner); @@ -145,8 +157,7 @@ contract L1ChugSplashProxy_SetStorage_Test is L1ChugSplashProxy_TestInit { /// @notice Tests that when not the owner, `setStorage` delegatecalls the implementation function test_setStorage_whenNotOwner_works() public view { - uint256 ret = - L1ChugSplashProxy_Implementation_Harness(address(proxy)).setStorage(bytes32(0), bytes32(uint256(42))); + uint256 ret = _proxyAsImplementation().setStorage(bytes32(0), bytes32(uint256(42))); assertEq(ret, 2); assertEq(vm.load(address(proxy), bytes32(0)), bytes32(uint256(0))); } @@ -154,7 +165,7 @@ contract L1ChugSplashProxy_SetStorage_Test is L1ChugSplashProxy_TestInit { /// @title L1ChugSplashProxy_SetOwner_Test /// @notice Tests the `setOwner` function of the `L1ChugSplashProxy` contract. -contract L1ChugSplashProxy_SetOwner_Test is L1ChugSplashProxy_TestInit { +contract L1ChugSplashProxy_SetOwner_Test is L1ChugSplashProxy_WithImplementation_TestInit { /// @notice Tests that the owner can set the owner of the proxy function test_setOwner_whenOwner_works() public { vm.prank(owner); @@ -166,7 +177,7 @@ contract L1ChugSplashProxy_SetOwner_Test is L1ChugSplashProxy_TestInit { /// @notice Tests that when not the owner, `setOwner` delegatecalls the implementation function test_setOwner_whenNotOwner_works() public { - uint256 ret = L1ChugSplashProxy_Implementation_Harness(address(proxy)).setOwner(alice); + uint256 ret = _proxyAsImplementation().setOwner(alice); assertEq(ret, 3); vm.prank(owner); @@ -176,7 +187,7 @@ contract L1ChugSplashProxy_SetOwner_Test is L1ChugSplashProxy_TestInit { /// @title L1ChugSplashProxy_GetOwner_Test /// @notice Tests the `getOwner` function of the `L1ChugSplashProxy` contract. -contract L1ChugSplashProxy_GetOwner_Test is L1ChugSplashProxy_TestInit { +contract L1ChugSplashProxy_GetOwner_Test is L1ChugSplashProxy_WithImplementation_TestInit { /// @notice Tests that the owner can get the owner of the proxy function test_getOwner_whenOwner_works() public { vm.prank(owner); @@ -185,14 +196,14 @@ contract L1ChugSplashProxy_GetOwner_Test is L1ChugSplashProxy_TestInit { /// @notice Tests that when not the owner, `getOwner` delegatecalls the implementation function test_getOwner_whenNotOwner_works() public view { - uint256 ret = L1ChugSplashProxy_Implementation_Harness(address(proxy)).getOwner(); + uint256 ret = _proxyAsImplementation().getOwner(); assertEq(ret, 4); } } /// @title L1ChugSplashProxy_GetImplementation_Test /// @notice Tests the `getImplementation` function of the `L1ChugSplashProxy` contract. -contract L1ChugSplashProxy_GetImplementation_Test is L1ChugSplashProxy_TestInit { +contract L1ChugSplashProxy_GetImplementation_Test is L1ChugSplashProxy_WithImplementation_TestInit { /// @notice Tests that the owner can get the implementation of the proxy function test_getImplementation_whenOwner_works() public { vm.prank(owner); @@ -201,29 +212,25 @@ contract L1ChugSplashProxy_GetImplementation_Test is L1ChugSplashProxy_TestInit /// @notice Tests that when not the owner, `getImplementation` delegatecalls the implementation function test_getImplementation_whenNotOwner_works() public view { - uint256 ret = L1ChugSplashProxy_Implementation_Harness(address(proxy)).getImplementation(); + uint256 ret = _proxyAsImplementation().getImplementation(); assertEq(ret, 5); } } -/// @title L1ChugSplashProxy_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the `L1ChugSplashProxy` -/// contract or are testing multiple functions at once. -contract L1ChugSplashProxy_Uncategorized_Test is L1ChugSplashProxy_TestInit { +/// @title L1ChugSplashProxy_NoImplementation_Test +/// @notice Tests calls against a proxy before its implementation has been set. +contract L1ChugSplashProxy_NoImplementation_Test is L1ChugSplashProxy_TestInit { /// @notice Tests that when the caller is not the owner and the implementation is not set, all /// calls reverts. function test_calls_whenNotOwnerNoImplementation_reverts() public { - proxy = IL1ChugSplashProxy( - DeployUtils.create1({ - _name: "L1ChugSplashProxy", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ChugSplashProxy.__constructor__, (owner))) - }) - ); - vm.expectRevert(bytes("L1ChugSplashProxy: implementation is not set yet")); - L1ChugSplashProxy_Implementation_Harness(address(proxy)).setCode(hex"604260005260206000f3"); + _proxyAsImplementation().setCode(RETURN_42_BYTECODE); } +} +/// @title L1ChugSplashProxy_Upgrading_Test +/// @notice Tests calls against a proxy while its owner reports an active upgrade. +contract L1ChugSplashProxy_Upgrading_Test is L1ChugSplashProxy_WithImplementation_TestInit { /// @notice Tests that when the caller is not the owner but the owner has marked `isUpgrading` /// as true, the call reverts. function test_calls_whenUpgrading_reverts() public { @@ -231,9 +238,7 @@ contract L1ChugSplashProxy_Uncategorized_Test is L1ChugSplashProxy_TestInit { vm.prank(owner); proxy.setOwner(address(ownerContract)); - ownerContract.setIsUpgrading(true); - vm.expectRevert(bytes("L1ChugSplashProxy: system is currently being upgraded")); - L1ChugSplashProxy_Implementation_Harness(address(proxy)).setCode(hex"604260005260206000f3"); + _proxyAsImplementation().setCode(RETURN_42_BYTECODE); } } From 740972814533fa0cde1ea3f773cd10dc0754884c Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:56:52 -0400 Subject: [PATCH 094/135] Refactor ResolvedDelegateProxy tests: streamline test setup by introducing an abstract base contract, replace hardcoded implementation names with constants for clarity, and enhance maintainability through consistent use of internal helper functions across test cases. --- test/legacy/ResolvedDelegateProxy.t.sol | 93 +++++++++++-------------- 1 file changed, 40 insertions(+), 53 deletions(-) diff --git a/test/legacy/ResolvedDelegateProxy.t.sol b/test/legacy/ResolvedDelegateProxy.t.sol index 27997f09..247395f2 100644 --- a/test/legacy/ResolvedDelegateProxy.t.sol +++ b/test/legacy/ResolvedDelegateProxy.t.sol @@ -1,13 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing utilities import { Test } from "lib/forge-std/src/Test.sol"; - -// Target contract dependencies import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; - -// Target contract import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; @@ -21,76 +16,68 @@ contract ResolvedDelegateProxy_SimpleImplementation_Harness { } } -/// @title ResolvedDelegateProxy_TestInit -/// @notice Reusable test initialization for `ResolvedDelegateProxy` tests. -abstract contract ResolvedDelegateProxy_TestInit is Test { - IAddressManager internal addressManager; - ResolvedDelegateProxy_SimpleImplementation_Harness internal impl; - ResolvedDelegateProxy_SimpleImplementation_Harness internal proxy; +abstract contract ResolvedDelegateProxy_TestBase is Test { + string internal constant IMPLEMENTATION_NAME = "SimpleImplementation"; - /// @notice Sets up the test suite. - function setUp() public { - // Set up the address manager. - addressManager = IAddressManager( + function _deployAddressManager() internal returns (IAddressManager) { + return IAddressManager( DeployUtils.create1({ _name: "AddressManager", _args: DeployUtils.encodeConstructor(abi.encodeCall(IAddressManager.__constructor__, ())) }) ); - impl = new ResolvedDelegateProxy_SimpleImplementation_Harness(); - addressManager.setAddress("SimpleImplementation", address(impl)); + } - // Set up the proxy. - proxy = ResolvedDelegateProxy_SimpleImplementation_Harness( - address( - DeployUtils.create1({ - _name: "ResolvedDelegateProxy", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IResolvedDelegateProxy.__constructor__, (addressManager, "SimpleImplementation")) - ) - }) - ) + function _deployProxy(IAddressManager _addressManager) internal returns (IResolvedDelegateProxy) { + return IResolvedDelegateProxy( + DeployUtils.create1({ + _name: "ResolvedDelegateProxy", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IResolvedDelegateProxy.__constructor__, (_addressManager, IMPLEMENTATION_NAME)) + ) + }) ); } + + function _proxyAsImplementation(IResolvedDelegateProxy _proxy) + internal + pure + returns (ResolvedDelegateProxy_SimpleImplementation_Harness) + { + return ResolvedDelegateProxy_SimpleImplementation_Harness(address(_proxy)); + } } -/// @title ResolvedDelegateProxy_Fallback_Test -/// @notice Tests the `fallback` function of the `ResolvedDelegateProxy` contract. -contract ResolvedDelegateProxy_Fallback_Test is ResolvedDelegateProxy_TestInit { - /// @notice Tests that the proxy properly bubbles up returndata when the delegatecall succeeds. +contract ResolvedDelegateProxy_Fallback_Test is ResolvedDelegateProxy_TestBase { + IAddressManager internal addressManager; + ResolvedDelegateProxy_SimpleImplementation_Harness internal impl; + IResolvedDelegateProxy internal proxy; + + function setUp() public { + addressManager = _deployAddressManager(); + impl = new ResolvedDelegateProxy_SimpleImplementation_Harness(); + addressManager.setAddress(IMPLEMENTATION_NAME, address(impl)); + + proxy = _deployProxy(addressManager); + } + function testFuzz_fallback_delegateCallFoo_succeeds(uint256 x) public { vm.expectCall(address(impl), abi.encodeCall(impl.foo, (x))); - assertEq(proxy.foo(x), x); + assertEq(_proxyAsImplementation(proxy).foo(x), x); } - /// @notice Tests that the proxy properly bubbles up returndata when the delegatecall reverts. function test_fallback_delegateCallBar_reverts() public { vm.expectRevert("SimpleImplementation: revert"); vm.expectCall(address(impl), abi.encodeCall(impl.bar, ())); - proxy.bar(); + _proxyAsImplementation(proxy).bar(); } +} - /// @notice Tests that the proxy fallback reverts as expected if the implementation within the - /// address manager is not set. +contract ResolvedDelegateProxy_FallbackNoImplementation_Test is ResolvedDelegateProxy_TestBase { function test_fallback_addressManagerNotSet_reverts() public { - IAddressManager am = IAddressManager( - DeployUtils.create1({ - _name: "AddressManager", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IAddressManager.__constructor__, ())) - }) - ); - ResolvedDelegateProxy_SimpleImplementation_Harness p = ResolvedDelegateProxy_SimpleImplementation_Harness( - address( - DeployUtils.create1({ - _name: "ResolvedDelegateProxy", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IResolvedDelegateProxy.__constructor__, (am, "SimpleImplementation")) - ) - }) - ) - ); + IResolvedDelegateProxy proxy = _deployProxy(_deployAddressManager()); vm.expectRevert("ResolvedDelegateProxy: target address must be initialized"); - p.foo(0); + _proxyAsImplementation(proxy).foo(0); } } From e025f27fd3ae005d3a93d226d2acf31ac15309ba Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 12:59:02 -0400 Subject: [PATCH 095/135] Refactor LibGameId tests: enhance clarity by adding GameType import, streamline assertions to use raw values for comparison, and improve maintainability in test structure. --- test/libraries/bridge/LibGameId.t.sol | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/test/libraries/bridge/LibGameId.t.sol b/test/libraries/bridge/LibGameId.t.sol index 02f78005..0dad1483 100644 --- a/test/libraries/bridge/LibGameId.t.sol +++ b/test/libraries/bridge/LibGameId.t.sol @@ -3,14 +3,9 @@ pragma solidity ^0.8.15; import { Test } from "lib/forge-std/src/Test.sol"; -import { Timestamp, GameId, LibGameId } from "src/libraries/bridge/LibUDT.sol"; -import "src/libraries/bridge/Types.sol"; +import { Timestamp, GameId, GameType, LibGameId } from "src/libraries/bridge/LibUDT.sol"; -/// @title LibGameId_Pack_Test -/// @notice Tests the `pack` and `unpack` functions of the `LibGameId` library. contract LibGameId_Pack_Test is Test { - /// @notice Tests that a round trip of packing and unpacking a `GameId` maintains the same - /// values. function testFuzz_pack_roundTrip_succeeds( GameType _gameType, Timestamp _timestamp, @@ -22,8 +17,8 @@ contract LibGameId_Pack_Test is Test { GameId gameId = LibGameId.pack(_gameType, _timestamp, _gameProxy); (GameType gameType_, Timestamp timestamp_, address gameProxy_) = LibGameId.unpack(gameId); - assertEq(GameType.unwrap(gameType_), GameType.unwrap(_gameType)); - assertEq(Timestamp.unwrap(timestamp_), Timestamp.unwrap(_timestamp)); + assertEq(gameType_.raw(), _gameType.raw()); + assertEq(timestamp_.raw(), _timestamp.raw()); assertEq(gameProxy_, _gameProxy); } } From 3b6e793772dcfc82182fd979c2330afe42a1503f Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 13:05:14 -0400 Subject: [PATCH 096/135] Refactor RLPReader tests: introduce an abstract base contract for reusable test helpers, streamline assertions with a new internal function for repeated byte comparisons, and enhance clarity in test case descriptions for improved maintainability. --- test/libraries/rlp/RLPReader.t.sol | 36 ++++++++++++++++-------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/test/libraries/rlp/RLPReader.t.sol b/test/libraries/rlp/RLPReader.t.sol index c0068c47..047a55f1 100644 --- a/test/libraries/rlp/RLPReader.t.sol +++ b/test/libraries/rlp/RLPReader.t.sol @@ -8,8 +8,6 @@ import "src/libraries/rlp/RLPErrors.sol"; /// @title RLPReader_readBytes_Test /// @notice Tests the `readBytes` function of the `RLPReader` library. -/// @dev Here we allow internal reverts as readRawBytes uses memory allocations and can only be -/// tested internally. contract RLPReader_readBytes_Test is Test { /// @notice Tests that the `readBytes` function returns the correct bytes when given a null /// byte. @@ -66,9 +64,19 @@ contract RLPReader_readBytes_Test is Test { } } +/// @title RLPReader_TestInit +/// @notice Reusable helpers for `RLPReader` tests. +abstract contract RLPReader_TestInit is Test { + function assertRepeatedRawBytes(RLPReader.RLPItem[] memory _list, bytes memory _expected) internal pure { + for (uint256 i = 0; i < _list.length; i++) { + assertEq(RLPReader.readRawBytes(_list[i]), _expected); + } + } +} + /// @title RLPReader_readList_Test /// @notice Tests the `readList` function of the `RLPReader` library. -contract RLPReader_readList_Test is Test { +contract RLPReader_readList_Test is RLPReader_TestInit { /// @notice Tests that the `readList` function returns an empty array when given an empty list. function test_readList_empty_succeeds() external pure { RLPReader.RLPItem[] memory list = RLPReader.readList(hex"c0"); @@ -114,10 +122,7 @@ contract RLPReader_readList_Test is Test { ); assertEq(list.length, 4); - assertEq(RLPReader.readRawBytes(list[0]), hex"cf84617364668471776572847a786376"); - assertEq(RLPReader.readRawBytes(list[1]), hex"cf84617364668471776572847a786376"); - assertEq(RLPReader.readRawBytes(list[2]), hex"cf84617364668471776572847a786376"); - assertEq(RLPReader.readRawBytes(list[3]), hex"cf84617364668471776572847a786376"); + assertRepeatedRawBytes(list, hex"cf84617364668471776572847a786376"); } /// @notice Tests that the `readList` function correctly parses a very long list with 32 nested @@ -127,10 +132,7 @@ contract RLPReader_readList_Test is Test { hex"f90200cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376cf84617364668471776572847a786376" ); assertEq(list.length, 32); - - for (uint256 i = 0; i < 32; i++) { - assertEq(RLPReader.readRawBytes(list[i]), hex"cf84617364668471776572847a786376"); - } + assertRepeatedRawBytes(list, hex"cf84617364668471776572847a786376"); } /// @notice Tests that the `readList` function reverts when given a list longer than 32 @@ -196,18 +198,18 @@ contract RLPReader_readList_Test is Test { RLPReader.readList(hex"efdebdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); } - /// @notice Tests that the `readList` function reverts when given data that causes int32 - /// overflow. + /// @notice Tests that the `readList` function reverts when the declared string length exceeds + /// the available payload. /// forge-config: default.allow_internal_expect_revert = true - function test_readList_int32Overflow_reverts() external { + function test_readList_declaredStringLengthExceedsPayload_reverts() external { vm.expectRevert(ContentLengthMismatch.selector); RLPReader.readList(hex"bf0f000000000000021111"); } - /// @notice Tests that the `readList` function reverts when given data that causes int32 - /// overflow with a different prefix. + /// @notice Tests that the `readList` function reverts when the declared list length exceeds + /// the available payload. /// forge-config: default.allow_internal_expect_revert = true - function test_readList_int32Overflow2_reverts() external { + function test_readList_declaredListLengthExceedsPayload_reverts() external { vm.expectRevert(ContentLengthMismatch.selector); RLPReader.readList(hex"ff0f000000000000021111"); } From a11d38648be9dc315a8c0de6223a47ecc2f5d85e Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 13:09:42 -0400 Subject: [PATCH 097/135] Refactor RLPWriter tests: introduce an abstract base contract for reusable test helpers, streamline list encoding with a new internal function for creating encoded string lists, and enhance clarity in test cases by reducing redundancy in list initialization. --- test/libraries/rlp/RLPWriter.t.sol | 51 ++++++++++++++++-------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/test/libraries/rlp/RLPWriter.t.sol b/test/libraries/rlp/RLPWriter.t.sol index 83705b12..61572179 100644 --- a/test/libraries/rlp/RLPWriter.t.sol +++ b/test/libraries/rlp/RLPWriter.t.sol @@ -125,10 +125,22 @@ contract RLPWriter_writeUint_Test is Test { assertGt(encoded.length, 0); } } + +/// @title RLPWriter_TestInit +/// @notice Reusable helpers for `RLPWriter` tests. +abstract contract RLPWriter_TestInit is Test { + function encodedThreeStringList() internal pure returns (bytes[] memory list_) { + list_ = new bytes[](3); + list_[0] = RLPWriter.writeString("asdf"); + list_[1] = RLPWriter.writeString("qwer"); + list_[2] = RLPWriter.writeString("zxcv"); + } +} + /// @title RLPWriter_writeList_Test /// @notice Tests the `writeList` function of the `RLPWriter` library. -contract RLPWriter_writeList_Test is Test { +contract RLPWriter_writeList_Test is RLPWriter_TestInit { /// @notice Tests that the `writeList` function returns the correct RLP encoding when given an /// empty list. function test_writeList_empty_succeeds() external pure { @@ -186,16 +198,11 @@ contract RLPWriter_writeList_Test is Test { /// long list containing nested lists. function test_writeList_longlist1_succeeds() external pure { bytes[] memory list = new bytes[](4); - bytes[] memory list2 = new bytes[](3); - - list2[0] = RLPWriter.writeString("asdf"); - list2[1] = RLPWriter.writeString("qwer"); - list2[2] = RLPWriter.writeString("zxcv"); + bytes memory nestedList = RLPWriter.writeList(encodedThreeStringList()); - list[0] = RLPWriter.writeList(list2); - list[1] = RLPWriter.writeList(list2); - list[2] = RLPWriter.writeList(list2); - list[3] = RLPWriter.writeList(list2); + for (uint256 i = 0; i < list.length; i++) { + list[i] = nestedList; + } assertEq( RLPWriter.writeList(list), @@ -207,14 +214,10 @@ contract RLPWriter_writeList_Test is Test { /// very long list with 32 nested lists. function test_writeList_longlist2_succeeds() external pure { bytes[] memory list = new bytes[](32); - bytes[] memory list2 = new bytes[](3); - - list2[0] = RLPWriter.writeString("asdf"); - list2[1] = RLPWriter.writeString("qwer"); - list2[2] = RLPWriter.writeString("zxcv"); + bytes memory nestedList = RLPWriter.writeList(encodedThreeStringList()); for (uint256 i = 0; i < 32; i++) { - list[i] = RLPWriter.writeList(list2); + list[i] = nestedList; } assertEq( @@ -229,12 +232,13 @@ contract RLPWriter_writeList_Test is Test { // [ [ [], [] ], [] ] bytes[] memory list = new bytes[](2); bytes[] memory list2 = new bytes[](2); + bytes memory emptyList = RLPWriter.writeList(new bytes[](0)); - list2[0] = RLPWriter.writeList(new bytes[](0)); - list2[1] = RLPWriter.writeList(new bytes[](0)); + list2[0] = emptyList; + list2[1] = emptyList; list[0] = RLPWriter.writeList(list2); - list[1] = RLPWriter.writeList(new bytes[](0)); + list[1] = emptyList; assertEq(RLPWriter.writeList(list), hex"c4c2c0c0c0"); } @@ -243,16 +247,17 @@ contract RLPWriter_writeList_Test is Test { /// complex nested list structure. function test_writeList_listoflists2_succeeds() external pure { // [ [], [[]], [ [], [[]] ] ] + bytes memory emptyList = RLPWriter.writeList(new bytes[](0)); bytes[] memory list = new bytes[](3); - list[0] = RLPWriter.writeList(new bytes[](0)); + list[0] = emptyList; bytes[] memory list2 = new bytes[](1); - list2[0] = RLPWriter.writeList(new bytes[](0)); + list2[0] = emptyList; list[1] = RLPWriter.writeList(list2); bytes[] memory list3 = new bytes[](2); - list3[0] = RLPWriter.writeList(new bytes[](0)); + list3[0] = emptyList; list3[1] = RLPWriter.writeList(list2); list[2] = RLPWriter.writeList(list3); @@ -316,7 +321,7 @@ contract RLPWriter_writeAddress_Test is Test { function testFuzz_writeAddress_anyAddress_succeeds(address _addr) external pure { bytes memory encoded = RLPWriter.writeAddress(_addr); assertEq(encoded.length, 21); - assertEq(uint8(encoded[0]), 0x94); + assertEq(encoded[0], bytes1(0x94)); } } From c0747206a48afb7788df09756d8d24ca083a26f1 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 13:14:39 -0400 Subject: [PATCH 098/135] Refactor MerkleTrie tests: update import statements for error handling, streamline proof node handling in test cases, and enhance clarity by removing redundant wrapper instantiation for improved maintainability. --- test/libraries/trie/MerkleTrie.t.sol | 37 ++++++---------------------- 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/test/libraries/trie/MerkleTrie.t.sol b/test/libraries/trie/MerkleTrie.t.sol index 71041488..a860b9c6 100644 --- a/test/libraries/trie/MerkleTrie.t.sol +++ b/test/libraries/trie/MerkleTrie.t.sol @@ -5,7 +5,7 @@ import { Test } from "lib/forge-std/src/Test.sol"; import { MerkleTrie } from "src/libraries/trie/MerkleTrie.sol"; import { RLPReader } from "src/libraries/rlp/RLPReader.sol"; import { FFIInterface } from "test/setup/FFIInterface.sol"; -import "src/libraries/rlp/RLPErrors.sol"; +import { InvalidDataRemainder, UnexpectedString } from "src/libraries/rlp/RLPErrors.sol"; contract MerkleTrie_Harness { function exposed_get(bytes memory _key, bytes[] memory _proof, bytes32 _root) public pure returns (bytes memory) { @@ -417,31 +417,18 @@ contract MerkleTrie_Get_Test is MerkleTrie_TestInit { /// @notice Tests that `get` reverts if a proof node has an unknown prefix function test_get_unknownNodePrefix_reverts(uint8 prefix) external { - // bound it to only have prefixes where the first nibble is >= 4 prefix = uint8(bound(prefix, 0x40, 0xff)); - // if the first nibble of the prefix is odd, make it even by adding 16 if (((prefix / 16) % 2) == 1) { unchecked { prefix += 16; } - // bound it again in case it overflowed prefix = uint8(bound(prefix, 0x40, 0xff)); } - MerkleTrie_Harness wrapper = new MerkleTrie_Harness(); - bytes memory key = abi.encodePacked( keccak256(abi.encodePacked(bytes32(0xa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c))) ); bytes[] memory proof = new bytes[](5); - proof[0] = - hex"f90211a085ed702d58e6a962ad0e785e5c9036e06d878fd065eb9669122447f6aee7957da05badb8cfd5a7493d928614730af6e14eabe2c93fbac93c853dde3270c446309da01de85a57c524ac56a5bd4bed0b0aa7d963e364ad930ea964d0a42631a77ded4da0fe3143892366faeb9fae1117b888263afe0f74e6c73555fee53a604bf7188431a0af2c79f0dddd15d6f62e3fa60d515c44d58444ad3915c7ca4bddb31c8f148d0ca08f37a2f9093a4aee39519f3a06fe4674cc670fbbbd7a5f4eb096310b7bc1fdc9a086bd12d2031d9714130c687e6822250fa24b3147824780bea96cf8a7406c8966a03e42538ba2da8adaa0eca4550ef62de4dabde8ca06b71ac1b276ff31b67a7655a04a439f7eb6a62c77ec921139925af3359f61d16e083076e0e425583289107d7da0c453a51991b5a4c6174ddff77c0b7d9cc86f05ffda6ff523e2365f19797c7a00a06f43b7b9a118264ab4b6c24d7d3de77f39071a298426bfc27180adfca57d590da0032e0db4dcf122d4bdb1d4ec3c5df5fabd3127bcefe412cb046b7f0d80d11c9fa0560c2b8c9466b8cb5ffd600f24ea0ed9838bfdab7870d505d4387c2468d3c498a0597996e939ff8c29c9e59dc47c111e760450a9c4fe2b065825762da2a1f32495a0e3411c9af104364230c49de5a4d0802c84df18beee9778673364e1747a875622a02a6928825356d8280f361a02285af30e73889f4e55ecb63ed85c8581e07061d680"; - proof[1] = - hex"f90211a0db246208c4cef45e9aeb9f1df1baa8572675bc453f7da538165a2bb9e6a4c416a0d26d82a9ddff901d2a1f9e18506120dec0e5b3c95549c5bff0efc355061ea73fa04f1cedbb5c7df7ee5cc3210980baf5537affb29c661c6a4eeb193bc42e7fbc74a0acea389e0cf577d0e13483d98b15c82618ac18b7cc4a479981e3e672ddd16867a0ef59a06aeea1eb5ba1313bbe1fa74ff264d84d7319ab6178213734b5b5efa9c1a08f85dc6001713d77aa4e12982dfdb28fd1c7dcc290d46f2749e8a7d67ba2a694a0f6698ff794881edc50340b75311de64ce3da5f97debcfdfd4d20de57ef3ba7eba0680071ce05e9c7915f731bac8b9673332d1d77ea1f7dadab36d9b233eea32ba4a035ad3686f436232360c2aa364c9f2aa2081318b9fb33cd1050d69ee46f791d62a03b495b3d65d9ae39680a0f835c1d1378d218f7b1fb88d2b2c6ac6ef916f09172a0a808d1e8c632d9a6cfeb3c2c123a58b5b3e1998d4bd02c5fb0f7c5d4ba1338e6a0369376e9152831135ff3a902c9740cf22951d67edd51bf0541565e379d7efc25a0cc26d7fa1c326bc14950e92d9de78e4ed8372ff9727dec34602f24057b3a9b30a0278081783022e748dc70874a72377038935c00c1f0a24bbb8cd0fc208d8b68f4a06c4e83593571b94d08cb78ece0de4920b02a650a47a16583f94c8fe35f724707a0cd7eb9d730e5138fd943200b577e7bbb827d010a50d19af2f4b19ef19658156d80"; - proof[2] = - hex"f90211a0065f58fbe63e8e3387e91047d5b7a4532e7d9de0abc47f04791aef27a140fdb5a0858beea29778551c01b0d3e542d707675856da9c3f1d065c845e55c24d77be89a0e90a410489eff6f4f8d70b0cce1fb1339117ec0f6f1db195a6cc410509a2ebaea078ba7fe504e8d01d57f6bee52c4938d779108e500b5923272441ed2964b8c45da0f0430ed9fa807e5fb6ace75f8766ea60009d8539e00006e359f5f7bc38a76596a0a98a7938db99a2d80abea6349de42bf2576c9e51cc715c85fbacab365ec16f5ba026fadc7d124a456c62ada928eaede3e80611e3e6f99041f7393f062e9e788c8ca0ca48cad1e00d22d6146173341a6060378e738be727a7265a923cf6bfd1f8b610a0f8a4aae21a78ac28e2a61f50396f9a80f6c8232fe4afa203160361c0962242baa09a1029479959fb29b4da7239393fd6ca20bc376d860324f429a51b0e0565a158a0eefb84d3943d680e176258dffe0104ac48c171a8574a811535256a2d8ba531dea062a3d709a2f70ba1970842c4f20a602291273d1f6022e7a14cde2afdcd51e795a0397e6b9b87012cd79cbd0bb7daa4cc43830a673d80b65fb88c0449140175d89ca0f8a4c73c0078cbd32961227910e3f9315bc587716062e39f66be19747ccf9b67a0ea4bdd1b187fdba273a8625f88f284994d19c38ec58651839852665717d953d9a0319ebf356f45da83c7f106f1fd3decbf15f651fad3389a0d279602cdea8ee11480"; - proof[3] = - hex"f8f1a069a092c7a950214e7e45b99012dc8ad112eab0fc94ae5ca9efbd6949068384f280a0b25c46db67ef7cf0c47bb400c31c85a26c5a204431527c964c8ecaf3d63e52cc80a01911a2a74db0d8d182447176e23f25556d1a1eaa0afad96453f2d64876ad88e480808080a04a0ca9e3bed1bc3e3c819384d19b6d5e523164a6520c4eb42e828a63ef730ae38080a03b598ed1b9269d4b05e2e75cfb54298d25437669870c919a59a147d2d256fdba80a0db2d655057c83107a73d086cfdd8fcc74739bb48c652eb0ce597178ecf96b39aa05c66ac392a761341b9c22b773ea19af311f34ef537640b9bb96842ec6ace913280"; proof[4] = bytes.concat( hex"f69f", @@ -453,7 +440,7 @@ contract MerkleTrie_Get_Test is MerkleTrie_TestInit { (proof[0], proof[1], proof[2], proof[3], root) = rehashOtherElements(proof[4]); vm.expectRevert("MerkleTrie: received a node with an unknown prefix"); - wrapper.exposed_get(key, proof, root); + harness.exposed_get(key, proof, root); } /// @notice Tests that `get` reverts if a proof node is unparsable i.e list length is not 2 or 17 @@ -463,8 +450,6 @@ contract MerkleTrie_Get_Test is MerkleTrie_TestInit { listLen++; } - MerkleTrie_Harness wrapper = new MerkleTrie_Harness(); - bytes memory key = abi.encodePacked( keccak256(abi.encodePacked(bytes32(0xa15bc60c955c405d20d9149c709e2460f1c2d9a497496a7f46004d1772c3054c))) ); @@ -478,44 +463,36 @@ contract MerkleTrie_Get_Test is MerkleTrie_TestInit { proof[3] = hex"f8f1a069a092c7a950214e7e45b99012dc8ad112eab0fc94ae5ca9efbd6949068384f280a0b25c46db67ef7cf0c47bb400c31c85a26c5a204431527c964c8ecaf3d63e52cc80a01911a2a74db0d8d182447176e23f25556d1a1eaa0afad96453f2d64876ad88e480808080a04a0ca9e3bed1bc3e3c819384d19b6d5e523164a6520c4eb42e828a63ef730ae38080a03b598ed1b9269d4b05e2e75cfb54298d25437669870c919a59a147d2d256fdba80a0db2d655057c83107a73d086cfdd8fcc74739bb48c652eb0ce597178ecf96b39aa05c66ac392a761341b9c22b773ea19af311f34ef537640b9bb96842ec6ace913280"; proof[4] = - hex"f69f204dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f195942536c09e5f5691498805884fa37811be3b2bddb4"; // Correct - // leaf node + hex"f69f204dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f195942536c09e5f5691498805884fa37811be3b2bddb4"; bytes32 root = keccak256(proof[0]); - // Should not revert - wrapper.exposed_get(key, proof, root); + harness.exposed_get(key, proof, root); if (listLen > 3) { - // Node with list > 3 proof[4] = hex"f8379f204dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f195942536c09e5f5691498805884fa37811be3b2bddb480"; for (uint256 i; i < listLen - 3; i++) { proof[4] = bytes.concat(proof[4], hex"80"); } proof[4][1] = bytes1(uint8(proof[4][1]) + (listLen - 3)); - // rehash all proof elements and insert it into the proof element above it (proof[0], proof[1], proof[2], proof[3], root) = rehashOtherElements(proof[4]); vm.expectRevert("MerkleTrie: received an unparseable node"); - wrapper.exposed_get(key, proof, root); + harness.exposed_get(key, proof, root); } else if (listLen == 1) { - // Node with list of 1 proof[4] = hex"e09f204dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f1"; - // rehash all proof elements and insert it into the proof element above it (proof[0], proof[1], proof[2], proof[3], root) = rehashOtherElements(proof[4]); vm.expectRevert("MerkleTrie: received an unparseable node"); - wrapper.exposed_get(key, proof, root); + harness.exposed_get(key, proof, root); } else if (listLen == 3) { - // Node with list of 3 proof[4] = hex"f79f204dcf44e265ba93879b2da89e1b16ab48fc5eb8e31bc16b0612d6da8463f195942536c09e5f5691498805884fa37811be3b2bddb480"; - // rehash all proof elements and insert it into the proof element above it (proof[0], proof[1], proof[2], proof[3], root) = rehashOtherElements(proof[4]); vm.expectRevert("MerkleTrie: received an unparseable node"); - wrapper.exposed_get(key, proof, root); + harness.exposed_get(key, proof, root); } } } From f84d48b9f6e0372ed41dd6c0c03396112fcf911b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 13:20:58 -0400 Subject: [PATCH 099/135] Refactor Bytes tests: streamline memory pointer management with new internal functions, enhance equality checks for dynamic byte arrays, and improve clarity in test cases by removing redundant code and simplifying assertions. --- test/libraries/Bytes.t.sol | 147 +++++++++---------------------------- 1 file changed, 36 insertions(+), 111 deletions(-) diff --git a/test/libraries/Bytes.t.sol b/test/libraries/Bytes.t.sol index a3308772..4f214fd5 100644 --- a/test/libraries/Bytes.t.sol +++ b/test/libraries/Bytes.t.sol @@ -1,10 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing utilities import { Test } from "lib/forge-std/src/Test.sol"; - -// Target contract import { Bytes } from "src/libraries/Bytes.sol"; contract Bytes_Harness { @@ -16,34 +13,38 @@ contract Bytes_Harness { /// @title Bytes_TestInit /// @notice Reusable test initialization for `Bytes` tests. abstract contract Bytes_TestInit is Test { - Bytes_Harness harness; - - /// @notice Manually checks equality of two dynamic `bytes` arrays in memory. - /// @param _a The first `bytes` array to compare. - /// @param _b The second `bytes` array to compare. - /// @return True if the two `bytes` arrays are equal in memory. function manualEq(bytes memory _a, bytes memory _b) internal pure returns (bool) { - bool _eq; + return _a.length == _b.length && keccak256(_a) == keccak256(_b); + } + + function freeMemoryPtr() internal pure returns (uint64 ptr_) { assembly { - _eq := and( - // Check if the contents of the two bytes arrays are equal in memory. - eq(keccak256(add(0x20, _a), mload(_a)), keccak256(add(0x20, _b), mload(_b))), - // Check if the length of the two bytes arrays are equal in memory. - // This is redundant given the above check, but included for completeness. - eq(mload(_a), mload(_b)) - ) + ptr_ := mload(0x40) } - return _eq; } - function setUp() public { - harness = new Bytes_Harness(); + function nextSliceMemoryPtr(uint64 _ptr, uint256 _length) internal pure returns (uint64) { + if (_length == 0) { + return _ptr + 0x20; + } + + return uint64((_ptr + 0x20 + _length + 0x1f) & ~uint256(0x1f)); + } + + function nextBytesMemoryPtr(uint64 _ptr, uint256 _length) internal pure returns (uint64) { + return uint64(_ptr + 0x20 + ((_length + 0x1f) & ~uint256(0x1f))); } } /// @title Bytes_Slice_Test /// @notice Tests the `slice` function of the `Bytes` library. contract Bytes_Slice_Test is Bytes_TestInit { + Bytes_Harness harness; + + function setUp() public { + harness = new Bytes_Harness(); + } + /// @notice Tests that the `slice` function works as expected when starting from index 0. function test_slice_fromZeroIdx_works() public pure { bytes memory input = hex"11223344556677889900"; @@ -107,54 +108,17 @@ contract Bytes_Slice_Test is Bytes_TestInit { function testFuzz_slice_memorySafety_succeeds(bytes memory _input, uint256 _start, uint256 _length) public { vm.assume(_input.length > 0); - // The start should never be more than the length of the input bytes array - 1 _start = bound(_start, 0, _input.length - 1); - - // The length should never be more than the length of the input bytes array - the starting - // slice index. _length = bound(_length, 0, _input.length - _start); - // Grab the free memory pointer before the slice operation - uint64 initPtr; - assembly { - initPtr := mload(0x40) - } - uint64 expectedPtr = uint64((initPtr + 0x20 + _length + 0x1f) & ~uint256(0x1f)); + uint64 initPtr = freeMemoryPtr(); + uint64 expectedPtr = nextSliceMemoryPtr(initPtr, _length); - // Ensure that all memory outside of the expected range is safe. vm.expectSafeMemory(initPtr, expectedPtr); - - // Slice the input bytes array from `_start` to `_start + _length` bytes memory slice = Bytes.slice(_input, _start, _length); - vm.stopExpectSafeMemory(); - // Grab the free memory pointer after the slice operation - uint64 finalPtr; - assembly { - finalPtr := mload(0x40) - } - - // The free memory pointer should have been updated properly - if (_length == 0) { - // If the slice length is zero, only 32 bytes of memory should have been allocated. - assertEq(finalPtr, initPtr + 0x20); - } else { - // If the slice length is greater than zero, the memory allocated should be the length - // of the slice rounded up to the next 32 byte word + 32 bytes for the length of the - // byte array. - // - // Note that we use a slightly less efficient, but equivalent method of rounding up - // `_length` to the next multiple of 32 than is used in the `slice` function. This is - // to diff test the method used in `slice`. - uint64 _expectedPtr = uint64(((initPtr + 0x20 + _length + 0x1F) >> 5) << 5); - assertEq(finalPtr, _expectedPtr); - - // Sanity check for equivalence of the rounding methods. - assertEq(_expectedPtr, expectedPtr); - } - - // The slice length should be equal to `_length` + assertEq(freeMemoryPtr(), expectedPtr); assertEq(slice.length, _length); } @@ -181,26 +145,25 @@ contract Bytes_Slice_Test is Bytes_TestInit { /// @notice Tests that, when given a length `n` that is greater than `type(uint256).max - 31`, /// the `slice` function reverts. - function testFuzz_slice_lengthOverflows_reverts(bytes memory _input, uint256 _start, uint256 _length) public { + function testFuzz_slice_lengthOverflows_reverts(uint256 _length) public { // Ensure that the `_length` will overflow if a number >= 31 is added to it. _length = uint256(bound(_length, type(uint256).max - 30, type(uint256).max)); vm.expectRevert("slice_overflow"); - harness.exposed_slice(_input, _start, _length); + harness.exposed_slice(hex"", 0, _length); } /// @notice Tests that, when given a start index `n` that is greater than /// `type(uint256).max - n`, the `slice` function reverts. /// The calls to `bound` are to reduce the number of times that `assume` is triggered. function testFuzz_slice_rangeOverflows_reverts(bytes memory _input, uint256 _start, uint256 _length) public { + vm.assume(_input.length > 1); + // Ensure that `_length` is a realistic length of a slice. This is to make sure we revert // on the correct require statement. - _length = bound(_length, 0, _input.length == 0 ? 0 : _input.length - 1); - vm.assume(_length < _input.length); + _length = bound(_length, 1, _input.length - 1); - // Ensure that `_start` will overflow if `_length` is added to it. - _start = bound(_start, type(uint256).max - _length, type(uint256).max); - vm.assume(_start > type(uint256).max - _length); + _start = bound(_start, type(uint256).max - _length + 1, type(uint256).max); vm.expectRevert("slice_overflow"); harness.exposed_slice(_input, _start, _length); @@ -240,60 +203,22 @@ contract Bytes_ToNibbles_Test is Bytes_TestInit { /// @notice Tests that, given an input of 0 bytes, the `toNibbles` function returns a zero /// length array. function test_toNibbles_zeroLengthInput_works() public pure { - bytes memory input = hex""; - bytes memory expected = hex""; - bytes memory actual = Bytes.toNibbles(input); - - assertEq(input.length, 0); - assertEq(expected.length, 0); - assertEq(actual.length, 0); - assertEq(actual, expected); + assertEq(Bytes.toNibbles(hex""), hex""); } /// @notice Tests that the `toNibbles` function correctly updates the free memory pointer /// depending on the length of the resulting array. function testFuzz_toNibbles_memorySafety_succeeds(bytes memory _input) public { - // Grab the free memory pointer before the `toNibbles` operation - uint64 initPtr; - assembly { - initPtr := mload(0x40) - } - uint64 expectedPtr = uint64(initPtr + 0x20 + ((_input.length * 2 + 0x1F) & ~uint256(0x1F))); + uint256 nibblesLength = _input.length * 2; + uint64 initPtr = freeMemoryPtr(); + uint64 expectedPtr = nextBytesMemoryPtr(initPtr, nibblesLength); - // Ensure that all memory outside of the expected range is safe. vm.expectSafeMemory(initPtr, expectedPtr); - - // Pull out each individual nibble from the input bytes array bytes memory nibbles = Bytes.toNibbles(_input); - vm.stopExpectSafeMemory(); - // Grab the free memory pointer after the `toNibbles` operation - uint64 finalPtr; - assembly { - finalPtr := mload(0x40) - } - - // The free memory pointer should have been updated properly - if (_input.length == 0) { - // If the input length is zero, only 32 bytes of memory should have been allocated. - assertEq(finalPtr, initPtr + 0x20); - } else { - // If the input length is greater than zero, the memory allocated should be the length - // of the input * 2 + 32 bytes for the length field. - // - // Note that we use a slightly less efficient, but equivalent method of rounding up - // `_length` to the next multiple of 32 than is used in the `toNibbles` function. This - // is to diff test the method used in `toNibbles`. - uint64 _expectedPtr = uint64(initPtr + 0x20 + (((_input.length * 2 + 0x1F) >> 5) << 5)); - assertEq(finalPtr, _expectedPtr); - - // Sanity check for equivalence of the rounding methods. - assertEq(_expectedPtr, expectedPtr); - } - - // The nibbles length should be equal to `_length * 2` - assertEq(nibbles.length, _input.length << 1); + assertEq(freeMemoryPtr(), expectedPtr); + assertEq(nibbles.length, nibblesLength); } } From 8401d7c94c86bcd06b3fa958fc08d7c3b22880af Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 13:52:30 -0400 Subject: [PATCH 100/135] Refactor Constants tests: enhance clarity by removing redundant comments, streamline assertions for constant values, and improve maintainability in test structure. --- test/libraries/Constants.t.sol | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/libraries/Constants.t.sol b/test/libraries/Constants.t.sol index 7fc24797..3a2541ac 100644 --- a/test/libraries/Constants.t.sol +++ b/test/libraries/Constants.t.sol @@ -5,47 +5,37 @@ import { Test } from "lib/forge-std/src/Test.sol"; import { Constants } from "src/libraries/Constants.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; -/// @title Constants_Test -/// @notice Tests the constant values defined in the `Constants` library. contract Constants_Test is Test { - /// @notice Verify ESTIMATION_ADDRESS constant value. function test_estimationAddress_succeeds() external pure { assertEq(Constants.ESTIMATION_ADDRESS, address(1)); } - /// @notice Verify DEFAULT_L2_SENDER constant value. function test_defaultL2Sender_succeeds() external pure { assertEq(Constants.DEFAULT_L2_SENDER, 0x000000000000000000000000000000000000dEaD); } - /// @notice Verify EIP1967 proxy implementation storage slot. function test_proxyImplementationAddress_succeeds() external pure { assertEq( bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1), Constants.PROXY_IMPLEMENTATION_ADDRESS ); } - /// @notice Verify EIP1967 proxy admin storage slot. function test_proxyOwnerAddress_succeeds() external pure { assertEq(bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1), Constants.PROXY_OWNER_ADDRESS); } - /// @notice Verify GUARD_STORAGE_SLOT constant value. function test_guardStorageSlot_succeeds() external pure { assertEq(keccak256("guard_manager.guard.address"), Constants.GUARD_STORAGE_SLOT); } - /// @notice Verify ETHER constant value. function test_ether_succeeds() external pure { assertEq(Constants.ETHER, 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); } - /// @notice Verify DEPOSITOR_ACCOUNT constant value. function test_depositorAccount_succeeds() external pure { assertEq(Constants.DEPOSITOR_ACCOUNT, 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001); } - /// @notice Verify DEFAULT_RESOURCE_CONFIG returns expected values. function test_defaultResourceConfig_succeeds() external pure { IResourceMetering.ResourceConfig memory config = Constants.DEFAULT_RESOURCE_CONFIG(); assertEq(config.maxResourceLimit, 20_000_000); From e7b000b3f552e345496b3754249a20f0d7210037 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 13:59:32 -0400 Subject: [PATCH 101/135] Refactor Encoding tests: introduce internal helper functions for generating output roots and super root proofs, streamline assertions for encoding comparisons, and enhance clarity in test cases by improving naming conventions and reducing redundancy. --- test/libraries/Encoding.t.sol | 203 ++++++++++++---------------------- 1 file changed, 69 insertions(+), 134 deletions(-) diff --git a/test/libraries/Encoding.t.sol b/test/libraries/Encoding.t.sol index b55164c1..972dbfb5 100644 --- a/test/libraries/Encoding.t.sol +++ b/test/libraries/Encoding.t.sol @@ -30,13 +30,45 @@ contract Encoding_Harness { } /// @title Encoding_TestInit -/// @notice Reusable test initialization for `Encoding` tests. +/// @notice Reusable helpers for `Encoding` tests. abstract contract Encoding_TestInit is CommonTest { - Encoding_Harness encoding; + bytes1 internal constant SUPER_ROOT_VERSION = 0x01; + uint256 internal constant MAX_OUTPUT_ROOTS = 50; - function setUp() public override { - super.setUp(); - encoding = new Encoding_Harness(); + function _outputRoot(uint256 _chainId, bytes32 _root) internal pure returns (Types.OutputRootWithChainId memory) { + return Types.OutputRootWithChainId({ chainId: _chainId, root: _root }); + } + + function _superRootProof( + uint64 _timestamp, + uint256 _length, + uint256 _seed + ) + internal + pure + returns (Types.SuperRootProof memory proof) + { + _length = bound(_length, 1, MAX_OUTPUT_ROOTS); + + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](_length); + for (uint256 i = 0; i < _length; i++) { + outputRoots[i] = _outputRoot( + uint256(keccak256(abi.encode(_seed, uint8(0), i))), keccak256(abi.encode(_seed, uint8(1), i)) + ); + } + + proof = Types.SuperRootProof({ version: SUPER_ROOT_VERSION, timestamp: _timestamp, outputRoots: outputRoots }); + } + + function _expectedSuperRootProofEncoding(Types.SuperRootProof memory _proof) + internal + pure + returns (bytes memory expected) + { + expected = bytes.concat(_proof.version, bytes8(_proof.timestamp)); + for (uint256 i = 0; i < _proof.outputRoots.length; i++) { + expected = bytes.concat(expected, bytes32(_proof.outputRoots[i].chainId), _proof.outputRoots[i].root); + } } } @@ -56,14 +88,14 @@ contract Encoding_EncodeDepositTransaction_Test is Encoding_TestInit { ) external { - Types.UserDepositTransaction memory t = Types.UserDepositTransaction( + Types.UserDepositTransaction memory depositTx = Types.UserDepositTransaction( _from, _to, isCreate, _value, _mint, _gas, _data, bytes32(uint256(0)), _logIndex ); - bytes memory txn = Encoding.encodeDepositTransaction(t); - bytes memory _txn = ffi.encodeDepositTransaction(t); + bytes memory actual = Encoding.encodeDepositTransaction(depositTx); + bytes memory expected = ffi.encodeDepositTransaction(depositTx); - assertEq(txn, _txn); + assertEq(actual, expected); } } @@ -82,24 +114,23 @@ contract Encoding_EncodeCrossDomainMessage_Test is Encoding_TestInit { ) external { - uint8 version = _version % 2; + uint8 version = uint8(bound(uint256(_version), 0, 1)); uint256 nonce = Encoding.encodeVersionedNonce(_nonce, version); - bytes memory encoding = Encoding.encodeCrossDomainMessage(nonce, _sender, _target, _value, _gasLimit, _data); - - bytes memory _encoding = ffi.encodeCrossDomainMessage(nonce, _sender, _target, _value, _gasLimit, _data); + bytes memory actual = Encoding.encodeCrossDomainMessage(nonce, _sender, _target, _value, _gasLimit, _data); + bytes memory expected = ffi.encodeCrossDomainMessage(nonce, _sender, _target, _value, _gasLimit, _data); - assertEq(encoding, _encoding); + assertEq(actual, expected); } /// @notice Tests that encodeCrossDomainMessage reverts if version is greater than 1. - function testFuzz_encodeCrossDomainMessage_versionGreaterThanOne_reverts(uint256 nonce) external { - // nonce >> 240 must be greater than 1 - uint256 minInvalidNonce = (uint256(type(uint240).max) + 1) * 2; - nonce = bound(nonce, minInvalidNonce, type(uint256).max); + function testFuzz_encodeCrossDomainMessage_versionGreaterThanOne_reverts(uint240 _nonce, uint16 _version) external { + uint16 invalidVersion = uint16(bound(uint256(_version), 2, type(uint16).max)); + uint256 nonce = Encoding.encodeVersionedNonce(_nonce, invalidVersion); + Encoding_Harness harness = new Encoding_Harness(); vm.expectRevert(bytes("Encoding: unknown cross domain message version")); - encoding.encodeCrossDomainMessage(nonce, address(this), address(this), 1, 100, hex""); + harness.encodeCrossDomainMessage(nonce, address(this), address(this), 1, 100, hex""); } } @@ -111,99 +142,33 @@ contract Encoding_EncodeSuperRootProof_Test is Encoding_TestInit { /// @param _length The number of output roots in the super root proof /// @param _seed The seed used to generate the output roots function testFuzz_encodeSuperRootProof_succeeds(uint64 _timestamp, uint256 _length, uint256 _seed) external pure { - // Ensure at least 1 element and cap at a reasonable maximum to avoid gas issues - _length = uint256(bound(_length, 1, 50)); - - // Create output roots array - Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](_length); - - // Generate deterministic chain IDs and roots based on the seed - for (uint256 i = 0; i < _length; i++) { - // Use different derivations of the seed for each value - uint256 chainId = uint256(keccak256(abi.encode(_seed, "chainId", i))); - bytes32 root = keccak256(abi.encode(_seed, "root", i)); - - outputRoots[i] = Types.OutputRootWithChainId({ chainId: chainId, root: root }); - } + Types.SuperRootProof memory proof = _superRootProof(_timestamp, _length, _seed); - // Create the super root proof - Types.SuperRootProof memory proof = - Types.SuperRootProof({ version: 0x01, timestamp: _timestamp, outputRoots: outputRoots }); - - // Encode the proof - bytes memory encoded = Encoding.encodeSuperRootProof(proof); - - // Verify encoding structure - assertEq(encoded[0], bytes1(0x01), "Version byte should be 0x01"); - - // Verify timestamp (bytes 1-8) - bytes8 encodedTimestamp; - for (uint256 i = 0; i < 8; i++) { - encodedTimestamp |= bytes8(encoded[i + 1]) >> (i * 8); - } - assertEq(uint64(encodedTimestamp), _timestamp, "Timestamp should match"); - - // Verify each chain ID and root is encoded correctly - uint256 offset = 9; // 1 byte version + 8 bytes timestamp - for (uint256 i = 0; i < _length; i++) { - // Extract chain ID (32 bytes) - uint256 encodedChainId; - assembly { - // Load 32 bytes from encoded at position offset - encodedChainId := mload(add(add(encoded, 32), offset)) - } - assertEq(encodedChainId, outputRoots[i].chainId, "Chain ID should match"); - offset += 32; - - // Extract root (32 bytes) - bytes32 encodedRoot; - assembly { - // Load 32 bytes from encoded at position offset - encodedRoot := mload(add(add(encoded, 32), offset)) - } - assertEq(encodedRoot, outputRoots[i].root, "Root should match"); - offset += 32; - } - - // Verify total length - assertEq(encoded.length, 9 + (_length * 64), "Encoded length should match expected"); + assertEq(Encoding.encodeSuperRootProof(proof), _expectedSuperRootProofEncoding(proof)); } /// @notice Tests encoding with a single output root function test_encodeSuperRootProof_singleOutputRoot_succeeds() external pure { - // Create a single output root Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](1); - outputRoots[0] = Types.OutputRootWithChainId({ chainId: 10, root: bytes32(uint256(0xdeadbeef)) }); + outputRoots[0] = _outputRoot(10, bytes32(uint256(0xdeadbeef))); - // Create the super root proof Types.SuperRootProof memory proof = - Types.SuperRootProof({ version: 0x01, timestamp: 1234567890, outputRoots: outputRoots }); + Types.SuperRootProof({ version: SUPER_ROOT_VERSION, timestamp: 1234567890, outputRoots: outputRoots }); - // Encode the proof - bytes memory encoded = Encoding.encodeSuperRootProof(proof); - - // Expected: 1 byte version + 8 bytes timestamp + (32 bytes chainId + 32 bytes root) - assertEq(encoded.length, 1 + 8 + 64, "Encoded length should be 73 bytes"); - assertEq(encoded[0], bytes1(0x01), "First byte should be version 0x01"); + assertEq(Encoding.encodeSuperRootProof(proof), _expectedSuperRootProofEncoding(proof)); } /// @notice Tests encoding with multiple output roots function test_encodeSuperRootProof_multipleOutputRoots_succeeds() external pure { - // Create multiple output roots Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](3); - outputRoots[0] = Types.OutputRootWithChainId({ chainId: 10, root: bytes32(uint256(0xdeadbeef)) }); - outputRoots[1] = Types.OutputRootWithChainId({ chainId: 20, root: bytes32(uint256(0xbeefcafe)) }); - outputRoots[2] = Types.OutputRootWithChainId({ chainId: 30, root: bytes32(uint256(0xcafebabe)) }); + outputRoots[0] = _outputRoot(10, bytes32(uint256(0xdeadbeef))); + outputRoots[1] = _outputRoot(20, bytes32(uint256(0xbeefcafe))); + outputRoots[2] = _outputRoot(30, bytes32(uint256(0xcafebabe))); - // Create the super root proof Types.SuperRootProof memory proof = - Types.SuperRootProof({ version: 0x01, timestamp: 1234567890, outputRoots: outputRoots }); - - // Encode the proof - bytes memory encoded = Encoding.encodeSuperRootProof(proof); + Types.SuperRootProof({ version: SUPER_ROOT_VERSION, timestamp: 1234567890, outputRoots: outputRoots }); - // Expected: 1 byte version + 8 bytes timestamp + 3 * (32 bytes chainId + 32 bytes root) - assertEq(encoded.length, 1 + 8 + (3 * 64), "Encoded length should be 201 bytes"); + assertEq(Encoding.encodeSuperRootProof(proof), _expectedSuperRootProofEncoding(proof)); } /// @notice Tests that the Solidity impl of encodeSuperRootProof matches the FFI impl @@ -211,70 +176,40 @@ contract Encoding_EncodeSuperRootProof_Test is Encoding_TestInit { /// @param _length The number of output roots in the super root proof /// @param _seed The seed used to generate the output roots function testDiff_encodeSuperRootProof_succeeds(uint64 _timestamp, uint256 _length, uint256 _seed) external { - // Ensure at least 1 element and cap at a reasonable maximum to avoid gas issues - _length = uint256(bound(_length, 1, 50)); + Types.SuperRootProof memory proof = _superRootProof(_timestamp, _length, _seed); - // Create output roots array - Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](_length); - - // Generate deterministic chain IDs and roots based on the seed - for (uint256 i = 0; i < _length; i++) { - // Use different derivations of the seed for each value - uint256 chainId = uint256(keccak256(abi.encode(_seed, "chainId", i))); - bytes32 root = keccak256(abi.encode(_seed, "root", i)); - - outputRoots[i] = Types.OutputRootWithChainId({ chainId: chainId, root: root }); - } - - // Create the super root proof - Types.SuperRootProof memory proof = - Types.SuperRootProof({ version: 0x01, timestamp: _timestamp, outputRoots: outputRoots }); - - // Encode using the Solidity implementation - bytes memory encoding1 = Encoding.encodeSuperRootProof(proof); - - // Encode using the FFI implementation - bytes memory encoding2 = ffi.encodeSuperRootProof(proof); - - // Compare the results - assertEq(encoding1, encoding2, "Solidity and FFI implementations should match"); + assertEq(Encoding.encodeSuperRootProof(proof), ffi.encodeSuperRootProof(proof)); } /// @notice Tests that encoding fails when version is not 0x01 /// @param _version The version to use for the super root proof /// @param _timestamp The timestamp of the super root proof function testFuzz_encodeSuperRootProof_invalidVersion_reverts(bytes1 _version, uint64 _timestamp) external { - // Ensure version is not 0x01 - if (_version == 0x01) { + if (_version == SUPER_ROOT_VERSION) { _version = 0x02; } - // Create a minimal valid output roots array Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](1); - outputRoots[0] = Types.OutputRootWithChainId({ chainId: 1, root: bytes32(uint256(1)) }); + outputRoots[0] = _outputRoot(1, bytes32(uint256(1))); - // Create the super root proof with invalid version Types.SuperRootProof memory proof = Types.SuperRootProof({ version: _version, timestamp: _timestamp, outputRoots: outputRoots }); + Encoding_Harness harness = new Encoding_Harness(); - // Expect revert when encoding vm.expectRevert(Encoding.Encoding_InvalidSuperRootVersion.selector); - encoding.encodeSuperRootProof(proof); + harness.encodeSuperRootProof(proof); } /// @notice Tests that encoding fails when output roots array is empty /// @param _timestamp The timestamp of the super root proof function testFuzz_encodeSuperRootProof_emptyOutputRoots_reverts(uint64 _timestamp) external { - // Create an empty output roots array Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](0); - - // Create the super root proof with empty output roots Types.SuperRootProof memory proof = - Types.SuperRootProof({ version: 0x01, timestamp: _timestamp, outputRoots: outputRoots }); + Types.SuperRootProof({ version: SUPER_ROOT_VERSION, timestamp: _timestamp, outputRoots: outputRoots }); + Encoding_Harness harness = new Encoding_Harness(); - // Expect revert when encoding vm.expectRevert(Encoding.Encoding_EmptySuperRoot.selector); - encoding.encodeSuperRootProof(proof); + harness.encodeSuperRootProof(proof); } } From 613877199e6b74fc1e674429c69dfebb13af1079 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:03:42 -0400 Subject: [PATCH 102/135] Refactor EOA tests: replace abstract contract with direct test contract, streamline private key handling with a new internal function, and enhance clarity in assertions for EOA detection scenarios. --- test/libraries/EOA.t.sol | 67 +++++++++++++--------------------------- 1 file changed, 21 insertions(+), 46 deletions(-) diff --git a/test/libraries/EOA.t.sol b/test/libraries/EOA.t.sol index 7f7f082b..97153565 100644 --- a/test/libraries/EOA.t.sol +++ b/test/libraries/EOA.t.sol @@ -17,99 +17,74 @@ contract EOA_Harness { } } -/// @title EOA_TestInit -/// @notice Reusable test initialization for `EOA` tests. -abstract contract EOA_TestInit is Test { +/// @title EOA_isSenderEOA_Test +/// @notice Tests the `isSenderEOA` function of the `EOA` library. +contract EOA_isSenderEOA_Test is Test { EOA_Harness harness; /// @notice Sets up the test. function setUp() public { harness = new EOA_Harness(); } -} -/// @title EOA_isSenderEOA_Test -/// @notice Tests the `isSenderEOA` function of the `EOA` library. -contract EOA_isSenderEOA_Test is EOA_TestInit { /// @notice Tests that a standard EOA is detected as an EOA. /// @param _privateKey The private key of the sender. function testFuzz_isSenderEOA_isStandardEOA_succeeds(uint256 _privateKey) external { - // Make sure that the private key is in the range of a valid secp256k1 private key. - _privateKey = boundPrivateKey(_privateKey); - - // Make sure that the sender is a standard EOA with no code. - address sender = vm.addr(_privateKey); + address sender = _senderFromPrivateKey(_privateKey); vm.assume(sender.code.length == 0); - // Should be considered an EOA vm.prank(sender, sender); - assertEq(harness.isSenderEOA(), true); + assertTrue(harness.isSenderEOA()); } /// @notice Tests that a 7702 EOA is detected as an EOA. /// @param _privateKey The private key of the sender. /// @param _7702Target The target of the 7702 EOA. function testFuzz_isSenderEOA_is7702EOA_succeeds(uint256 _privateKey, address _7702Target) external { - // Make sure that the private key is in the range of a valid secp256k1 private key. - _privateKey = boundPrivateKey(_privateKey); - // Delegating to address(0) revokes the delegation per EIP-7702, so exclude it. vm.assume(_7702Target != address(0)); - // Make sure that the sender is a 7702 EOA. - address sender = vm.addr(_privateKey); + address sender = _senderFromPrivateKey(_privateKey); vm.etch(sender, abi.encodePacked(hex"EF0100", _7702Target)); - // Should be considered a 7702 EOA. vm.prank(sender, sender); - assertEq(harness.isSenderEOA(), true); + assertTrue(harness.isSenderEOA()); // Should still be considered an EOA even if origin is different. vm.prank(sender, address(0x0420)); - assertEq(harness.isSenderEOA(), true); + assertTrue(harness.isSenderEOA()); } /// @notice Tests that a contract is not detected as an EOA. /// @param _privateKey The private key of the sender. /// @param _code The code of the sender. function testFuzz_isSenderEOA_isContract_succeeds(uint256 _privateKey, bytes memory _code) external { - // Make sure that the private key is in the range of a valid secp256k1 private key. - _privateKey = boundPrivateKey(_privateKey); - - // If code is empty or starts with EF, change it. + // Avoid empty code and the 0xEF prefix space, which includes 7702 delegation code. if (_code.length == 0 || _code[0] == 0xEF) { _code = bytes.concat(hex"FFFFFF", _code); } - // Make sure that the sender is a contract. - address sender = vm.addr(_privateKey); + address sender = _senderFromPrivateKey(_privateKey); vm.etch(sender, _code); - // Should not be considered an EOA. vm.prank(sender); - assertEq(harness.isSenderEOA(), false); + assertFalse(harness.isSenderEOA()); } /// @notice Tests that a contract with 23 bytes of code is not detected as an EOA. /// @param _privateKey The private key of the sender. function testFuzz_isSenderEOA_isContract23BytesNot7702_succeeds(uint256 _privateKey) external { - // Make sure that the private key is in the range of a valid secp256k1 private key. - _privateKey = boundPrivateKey(_privateKey); + address sender = _senderFromPrivateKey(_privateKey); + vm.etch(sender, abi.encodePacked(hex"FE", bytes22(0))); - // Generate a random 23 byte code. - bytes memory code = vm.randomBytes(23); - - // If the code happens to be EOF code, change it! - if (code[0] == 0xEF) { - code[0] = 0xFE; // Anything but EF! - } - - // Make sure that the sender is a contract. - address sender = vm.addr(_privateKey); - vm.etch(sender, code); - - // Should not be considered an EOA. vm.prank(sender); - assertEq(harness.isSenderEOA(), false); + assertFalse(harness.isSenderEOA()); + } + + /// @notice Returns the sender for a valid secp256k1 private key. + /// @param _privateKey The private key of the sender. + /// @return sender_ The sender address. + function _senderFromPrivateKey(uint256 _privateKey) internal pure returns (address sender_) { + sender_ = vm.addr(boundPrivateKey(_privateKey)); } } From 3c1f7fbf609fe6c43a6ecadbaf07e689212f6d7a Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:09:04 -0400 Subject: [PATCH 103/135] Refactor Hashing tests: introduce an abstract base contract for shared test initialization, streamline super root proof generation with internal helper functions, and enhance clarity in assertions for hash comparisons. --- test/libraries/Hashing.t.sol | 118 ++++++++++++++++------------------- 1 file changed, 54 insertions(+), 64 deletions(-) diff --git a/test/libraries/Hashing.t.sol b/test/libraries/Hashing.t.sol index dfb61e7a..f02a8da9 100644 --- a/test/libraries/Hashing.t.sol +++ b/test/libraries/Hashing.t.sol @@ -32,6 +32,36 @@ contract Hashing_Harness { } } +abstract contract Hashing_TestInit is CommonTest { + bytes1 internal constant SUPER_ROOT_VERSION = 0x01; + uint256 internal constant MAX_OUTPUT_ROOTS = 50; + + function _outputRoot(uint256 _chainId, bytes32 _root) internal pure returns (Types.OutputRootWithChainId memory) { + return Types.OutputRootWithChainId({ chainId: _chainId, root: _root }); + } + + function _superRootProof( + uint64 _timestamp, + uint256 _length, + uint256 _seed + ) + internal + pure + returns (Types.SuperRootProof memory proof) + { + _length = bound(_length, 1, MAX_OUTPUT_ROOTS); + + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](_length); + for (uint256 i = 0; i < _length; i++) { + outputRoots[i] = _outputRoot( + uint256(keccak256(abi.encode(_seed, uint8(0), i))), keccak256(abi.encode(_seed, uint8(1), i)) + ); + } + + proof = Types.SuperRootProof({ version: SUPER_ROOT_VERSION, timestamp: _timestamp, outputRoots: outputRoots }); + } +} + /// @title Hashing_hashDepositTransaction_Test /// @notice Tests the `hashDepositTransaction` function of the `Hashing` library. contract Hashing_hashDepositTransaction_Test is CommonTest { @@ -57,7 +87,7 @@ contract Hashing_hashDepositTransaction_Test is CommonTest { _mint, _gas, _data, - bytes32(uint256(0)), + bytes32(0), _logIndex ) ), @@ -90,15 +120,7 @@ contract Hashing_hashDepositSource_Test is CommonTest { /// @title Hashing_hashCrossDomainMessage_Test /// @notice Tests the `hashCrossDomainMessage` function of the `Hashing` library. contract Hashing_hashCrossDomainMessage_Test is CommonTest { - Hashing_Harness internal harness; - - /// @notice Sets up the test. - function setUp() public override { - super.setUp(); - harness = new Hashing_Harness(); - } /// @notice Tests that hashCrossDomainMessage returns the correct hash in a simple case. - function testDiff_hashCrossDomainMessage_succeeds( uint240 _nonce, uint16 _version, @@ -110,7 +132,6 @@ contract Hashing_hashCrossDomainMessage_Test is CommonTest { ) external { - // Ensure the version is valid. uint16 version = uint16(bound(uint256(_version), 0, 1)); uint256 nonce = Encoding.encodeVersionedNonce(_nonce, version); @@ -123,28 +144,13 @@ contract Hashing_hashCrossDomainMessage_Test is CommonTest { /// @notice Tests that hashCrossDomainMessage reverts with unknown version. /// @param _nonce Message nonce base value. /// @param _version Invalid version number (will be bounded to 2+). - /// @param _sender Address of the sender of the message. - /// @param _target Address of the target of the message. - /// @param _value ETH value to send to the target. - /// @param _gasLimit Gas limit to use for the message. - /// @param _data Data to send with the message. - function testFuzz_hashCrossDomainMessage_unknownVersion_reverts( - uint240 _nonce, - uint16 _version, - address _sender, - address _target, - uint256 _value, - uint256 _gasLimit, - bytes memory _data - ) - external - { - // Use version 2 or higher (invalid versions) + function testFuzz_hashCrossDomainMessage_unknownVersion_reverts(uint240 _nonce, uint16 _version) external { uint16 invalidVersion = uint16(bound(uint256(_version), 2, type(uint16).max)); uint256 nonce = Encoding.encodeVersionedNonce(_nonce, invalidVersion); + Hashing_Harness harness = new Hashing_Harness(); vm.expectRevert(bytes("Hashing: unknown cross domain message version")); - harness.hashCrossDomainMessage(nonce, _sender, _target, _value, _gasLimit, _data); + harness.hashCrossDomainMessage(nonce, address(this), address(this), 1, 100, hex""); } } @@ -225,49 +231,33 @@ contract Hashing_hashL2toL2CrossDomainMessage_Test is CommonTest { /// @title Hashing_hashSuperRootProof_Test /// @notice Tests the `hashSuperRootProof` function of the `Hashing` library. -contract Hashing_hashSuperRootProof_Test is CommonTest { - Hashing_Harness internal harness; - - /// @notice Sets up the test. - function setUp() public override { - super.setUp(); - harness = new Hashing_Harness(); - } - +contract Hashing_hashSuperRootProof_Test is Hashing_TestInit { /// @notice Tests that the Solidity impl of hashSuperRootProof matches the FFI impl - /// @param _proof The super root proof to test. - function testDiff_hashSuperRootProof_succeeds(Types.SuperRootProof memory _proof) external { - // Make sure the proof has the right version. - _proof.version = 0x01; - - // Make sure the proof has at least one output root. - if (_proof.outputRoots.length == 0) { - _proof.outputRoots = new Types.OutputRootWithChainId[](1); - _proof.outputRoots[0] = Types.OutputRootWithChainId({ - chainId: vm.randomUint(0, type(uint64).max), root: bytes32(vm.randomUint()) - }); - } + /// @param _timestamp The timestamp of the super root proof. + /// @param _length The number of output roots in the super root proof. + /// @param _seed The seed used to generate the output roots. + function testDiff_hashSuperRootProof_succeeds(uint64 _timestamp, uint256 _length, uint256 _seed) external { + Types.SuperRootProof memory proof = _superRootProof(_timestamp, _length, _seed); - // Encode using the Solidity implementation - bytes32 hash1 = harness.hashSuperRootProof(_proof); - - // Encode using the FFI implementation - bytes32 hash2 = ffi.hashSuperRootProof(_proof); - - // Compare the results - assertEq(hash1, hash2, "Solidity and FFI implementations should match"); + assertEq(Hashing.hashSuperRootProof(proof), ffi.hashSuperRootProof(proof), "hash mismatch"); } /// @notice Tests that hashSuperRootProof reverts when the version is incorrect. - /// @param _proof The super root proof to test. - function testFuzz_hashSuperRootProof_wrongVersion_reverts(Types.SuperRootProof memory _proof) external { - // 0x01 is the correct version, so we need any other version. - if (_proof.version == 0x01) { - _proof.version = 0x00; + /// @param _version The version to use for the super root proof. + /// @param _timestamp The timestamp of the super root proof. + function testFuzz_hashSuperRootProof_wrongVersion_reverts(bytes1 _version, uint64 _timestamp) external { + if (_version == SUPER_ROOT_VERSION) { + _version = 0x00; } - // Should always revert when the version is incorrect. + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](1); + outputRoots[0] = _outputRoot(1, bytes32(uint256(1))); + + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: _version, timestamp: _timestamp, outputRoots: outputRoots }); + Hashing_Harness harness = new Hashing_Harness(); + vm.expectRevert(Encoding.Encoding_InvalidSuperRootVersion.selector); - harness.hashSuperRootProof(_proof); + harness.hashSuperRootProof(proof); } } From d94831c68108c0f970e5b09bb2ba3b0fcd620c6b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:12:25 -0400 Subject: [PATCH 104/135] Refactor Predeploys tests: remove unnecessary omission check, streamline predeploy validation logic, and enhance clarity in assertions for predeploy contract code verification. --- test/libraries/Predeploys.t.sol | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/test/libraries/Predeploys.t.sol b/test/libraries/Predeploys.t.sol index 1a9e7254..dfa5083f 100644 --- a/test/libraries/Predeploys.t.sol +++ b/test/libraries/Predeploys.t.sol @@ -22,11 +22,6 @@ abstract contract Predeploys_TestInit is CommonTest { return _addr == Predeploys.L1_BLOCK_ATTRIBUTES || _addr == Predeploys.L2_STANDARD_BRIDGE; } - /// @notice Returns true if the account is not meant to be in the L2 genesis anymore. - function _isOmitted(address _addr) internal pure returns (bool) { - return _addr == Predeploys.L1_MESSAGE_SENDER; - } - /// @notice Returns true if the predeploy is initializable and uses OpenZeppelin v4 storage pattern. /// These contracts have _initialized in the regular storage layout. function _isInitializableV4(address _addr) internal pure returns (bool) { @@ -48,26 +43,20 @@ abstract contract Predeploys_TestInit is CommonTest { /// @notice Internal test function for predeploys validation across different forks. function _test_predeploys() internal view { - uint256 count = 2048; uint160 prefix = uint160(0x420) << 148; bytes memory proxyCode = vm.getDeployedCode("src/universal/Proxy.sol:Proxy"); - for (uint256 i = 0; i < count; i++) { + for (uint256 i = 0; i < Predeploys.PREDEPLOY_COUNT; i++) { address addr = address(prefix | uint160(i)); - address implAddr = Predeploys.predeployToCodeNamespace(addr); - if (_isOmitted(addr)) { - assertEq(implAddr.code.length, 0, "must have no code"); + if (addr == Predeploys.L1_MESSAGE_SENDER) { + assertEq(Predeploys.predeployToCodeNamespace(addr).code.length, 0, "must have no code"); continue; } bool isPredeploy = Predeploys.isSupportedPredeploy(addr); - - bytes memory code = addr.code; - if (isPredeploy) assertTrue(code.length > 0); - - bool proxied = Predeploys.notProxied(addr) == false; + bool proxied = !Predeploys.notProxied(addr); if (!isPredeploy) { // All of the predeploys, even if inactive, have their admin set to the proxy admin @@ -75,13 +64,17 @@ abstract contract Predeploys_TestInit is CommonTest { continue; } + address implAddr = Predeploys.predeployToCodeNamespace(addr); + bytes memory code = addr.code; + assertTrue(code.length > 0); + string memory cname = Predeploys.getName(addr); assertNotEq(cname, "", "must have a name"); bytes memory supposedCode = vm.getDeployedCode(string.concat(cname, ".sol:", cname)); assertNotEq(supposedCode.length, 0, "must have supposed code"); - if (proxied == false) { + if (!proxied) { // can't check bytecode if it's modified with immutables in genesis. if (!_usesImmutables(addr)) { assertEq(code, supposedCode, "non-proxy contract should be deployed in-place"); From e8dbf859f59e857ed3da0587d206fbf960746674 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:20:46 -0400 Subject: [PATCH 105/135] Refactor Preinstalls tests: introduce internal helper functions for preinstall assertions, streamline code verification logic, and enhance clarity in test cases by utilizing constants for chain ID and domain separator offsets. --- test/libraries/Preinstalls.t.sol | 67 +++++++++++++++++++------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/test/libraries/Preinstalls.t.sol b/test/libraries/Preinstalls.t.sol index 2a2592db..e5940b34 100644 --- a/test/libraries/Preinstalls.t.sol +++ b/test/libraries/Preinstalls.t.sol @@ -9,39 +9,54 @@ import { IEIP712 } from "interfaces/universal/IEIP712.sol"; /// @title Preinstalls_TestInit /// @notice Reusable test initialization for `Preinstalls` tests. abstract contract Preinstalls_TestInit is CommonTest { + uint256 internal constant PERMIT2_CHAIN_ID_OFFSET = 6945; + uint256 internal constant PERMIT2_DOMAIN_SEPARATOR_OFFSET = 6983; + uint256 internal constant PERMIT2_DEPLOYMENT_CHAIN_ID = 901; + function assertPreinstall(address _addr, bytes memory _code) internal view { + assertPreinstall(_addr, _code, block.chainid); + } + + function assertPreinstall(address _addr, bytes memory _code, uint256 _chainId) internal view { + bytes memory deployedCode = _addr.code; + assertNotEq(_code.length, 0, "must have code"); - assertNotEq(_addr.code.length, 0, "deployed preinstall account must have code"); - assertEq(_addr.code, _code, "equal code must be deployed"); - assertEq(Preinstalls.getDeployedCode(_addr, block.chainid), _code, "deployed-code getter must match"); + assertNotEq(deployedCode.length, 0, "deployed preinstall account must have code"); + assertEq(deployedCode, _code, "equal code must be deployed"); + assertEq(Preinstalls.getDeployedCode(_addr, _chainId), _code, "deployed-code getter must match"); assertNotEq(Preinstalls.getName(_addr), "", "must have a name"); if (_addr != Preinstalls.DeterministicDeploymentProxy) { assertEq(vm.getNonce(_addr), 1, "preinstall account must have 1 nonce"); } } + + function assertPermit2Code( + uint256 _chainId, + bytes32 _expectedDomainSeparator + ) + internal + pure + returns (bytes memory code_) + { + code_ = Preinstalls.getPermit2Code(_chainId); + assertNotEq(code_.length, 0, "must have code"); + assertEq(uint256(bytes32(Bytes.slice(code_, PERMIT2_CHAIN_ID_OFFSET, 32))), _chainId, "expecting chain ID"); + assertEq( + bytes32(Bytes.slice(code_, PERMIT2_DOMAIN_SEPARATOR_OFFSET, 32)), + _expectedDomainSeparator, + "expecting domain separator" + ); + } } /// @title Preinstalls_GetPermit2Code_Test /// @notice Tests the `getPermit2Code` function of the `Preinstalls` library. contract Preinstalls_GetPermit2Code_Test is Preinstalls_TestInit { function test_getPermit2Code_templating_works() external pure { - bytes memory customCode = Preinstalls.getPermit2Code(1234); - assertNotEq(customCode.length, 0, "must have code"); - assertEq(uint256(bytes32(Bytes.slice(customCode, 6945, 32))), uint256(1234), "expecting custom chain ID"); - assertEq( - bytes32(Bytes.slice(customCode, 6983, 32)), - bytes32(0x6cda538cafce36292a6ef27740629597f85f6716f5694d26d5c59fc1d07cfd95), - "expecting custom domain separator" - ); + assertPermit2Code(1234, bytes32(0x6cda538cafce36292a6ef27740629597f85f6716f5694d26d5c59fc1d07cfd95)); - bytes memory defaultCode = Preinstalls.getPermit2Code(1); - assertNotEq(defaultCode.length, 0, "must have code"); - assertEq(uint256(bytes32(Bytes.slice(defaultCode, 6945, 32))), uint256(1), "expecting default chain ID"); - assertEq( - bytes32(Bytes.slice(defaultCode, 6983, 32)), - bytes32(0x866a5aba21966af95d6c7ab78eb2b2fc913915c28be3b9aa07cc04ff903e3f28), - "expecting default domain separator" - ); + bytes memory defaultCode = + assertPermit2Code(1, bytes32(0x866a5aba21966af95d6c7ab78eb2b2fc913915c28be3b9aa07cc04ff903e3f28)); assertEq(defaultCode, Preinstalls.Permit2TemplateCode, "template is using chain ID 1"); } } @@ -57,10 +72,9 @@ contract Preinstalls_Uncategorized_Test is Preinstalls_TestInit { keccak256(abi.encodePacked("EIP712Domain(string name,uint256 chainId,address verifyingContract)")); bytes32 nameHash = keccak256(abi.encodePacked("Permit2")); uint256 chainId = block.chainid; - bytes memory encoded = abi.encode(typeHash, nameHash, chainId, Preinstalls.Permit2); - bytes32 expectedDomainSeparator = keccak256(encoded); + bytes32 expectedDomainSeparator = keccak256(abi.encode(typeHash, nameHash, chainId, Preinstalls.Permit2)); assertEq(domainSeparator, expectedDomainSeparator, "Domain separator mismatch"); - assertEq(chainId, uint256(901)); // uses devnet config + assertEq(chainId, PERMIT2_DEPLOYMENT_CHAIN_ID); // uses devnet config assertEq(domainSeparator, bytes32(0x48deb34b39fb4b41f5c195008940d5ef510cdd7853eba5807b2fa08dfd586475)); // Warning the Permit2 domain separator as cached in the DeployPermit2.sol bytecode is // incorrect. @@ -98,11 +112,10 @@ contract Preinstalls_Uncategorized_Test is Preinstalls_TestInit { assertPreinstall(Preinstalls.MultiSend_v130, Preinstalls.MultiSend_v130Code); } - function test_preinstall_permit2_succeeds() external { - uint256 pre = block.chainid; - vm.chainId(901); // TODO legacy deployment does not use same chainID as tests run with - assertPreinstall(Preinstalls.Permit2, Preinstalls.getPermit2Code(block.chainid)); - vm.chainId(pre); + function test_preinstall_permit2_succeeds() external view { + assertPreinstall( + Preinstalls.Permit2, Preinstalls.getPermit2Code(PERMIT2_DEPLOYMENT_CHAIN_ID), PERMIT2_DEPLOYMENT_CHAIN_ID + ); } function test_preinstall_senderCreatorv060_succeeds() external view { From 817e3a13f0026804843c92b9fc94d5044a1b1042 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:28:35 -0400 Subject: [PATCH 106/135] Refactor SafeCall tests: introduce a CallBalances struct for improved balance management, streamline balance assertions with a new internal function, and enhance clarity in test cases by reducing redundancy in balance checks. --- test/libraries/SafeCall.t.sol | 195 ++++++++++++++++------------------ 1 file changed, 90 insertions(+), 105 deletions(-) diff --git a/test/libraries/SafeCall.t.sol b/test/libraries/SafeCall.t.sol index c76bfdd6..f94d7ac0 100644 --- a/test/libraries/SafeCall.t.sol +++ b/test/libraries/SafeCall.t.sol @@ -32,16 +32,26 @@ contract SimpleSafeCaller { /// @title SafeCall_TestInit /// @notice Reusable test initialization for `SafeCall` tests. abstract contract SafeCall_TestInit is Test { - /// @notice Helper function to deduplicate code. Makes all assumptions required for these - /// tests. + struct CallBalances { + uint256 from; + uint256 to; + } + + /// @notice Makes all assumptions required for these tests. function assumeNot(address _addr) internal { - vm.deal(_addr, 0); vm.assume(_addr != address(this)); assumeAddressIsNot(_addr, StdCheatsSafe.AddressType.ForgeAddress, StdCheatsSafe.AddressType.Precompile); + vm.deal(_addr, 0); } - /// @notice Internal helper function for `send` tests - function sendTest(address _from, address _to, uint64 _gas, uint256 _value) internal { + function prepareCall( + address _from, + address _to, + uint256 _value + ) + internal + returns (CallBalances memory balancesBefore) + { assumeNot(_from); assumeNot(_to); @@ -49,7 +59,29 @@ abstract contract SafeCall_TestInit is Test { vm.deal(_from, _value); assertEq(_from.balance, _value, "from balance not dealt"); - uint256[2] memory balancesBefore = [_from.balance, _to.balance]; + balancesBefore = CallBalances({ from: _from.balance, to: _to.balance }); + } + + function assertCallBalances( + address _from, + address _to, + uint256 _value, + CallBalances memory _balancesBefore + ) + internal + view + { + if (_from == _to) { + assertEq(_from.balance, _balancesBefore.from, "self-transfer did not preserve balance"); + } else { + assertEq(_from.balance, _balancesBefore.from - _value, "from balance not drained"); + assertEq(_to.balance, _balancesBefore.to + _value, "to balance received"); + } + } + + /// @notice Internal helper function for `send` tests + function sendTest(address _from, address _to, uint64 _gas, uint256 _value) internal { + CallBalances memory balancesBefore = prepareCall(_from, _to, _value); vm.expectCall(_to, _value, bytes("")); vm.prank(_from); @@ -61,12 +93,7 @@ abstract contract SafeCall_TestInit is Test { } assertTrue(success, "send not successful"); - if (_from == _to) { - assertEq(_from.balance, balancesBefore[0], "Self-send did not change balance"); - } else { - assertEq(_from.balance, balancesBefore[0] - _value, "from balance not drained"); - assertEq(_to.balance, balancesBefore[1] + _value, "to balance received"); - } + assertCallBalances(_from, _to, _value, balancesBefore); } } @@ -90,26 +117,14 @@ contract SafeCall_Send_Test is SafeCall_TestInit { contract SafeCall_Call_Test is SafeCall_TestInit { /// @notice Tests that `call` succeeds. function testFuzz_call_succeeds(address from, address to, uint256 gas, uint64 value, bytes memory data) external { - assumeNot(from); - assumeNot(to); - - assertEq(from.balance, 0, "from balance is 0"); - vm.deal(from, value); - assertEq(from.balance, value, "from balance not dealt"); - - uint256[2] memory balancesBefore = [from.balance, to.balance]; + CallBalances memory balancesBefore = prepareCall(from, to, value); vm.expectCall(to, value, data); vm.prank(from); bool success = SafeCall.call(to, gas, value, data); assertTrue(success, "call not successful"); - if (from == to) { - assertEq(from.balance, balancesBefore[0], "Self-send did not change balance"); - } else { - assertEq(from.balance, balancesBefore[0] - value, "from balance not drained"); - assertEq(to.balance, balancesBefore[1] + value, "to balance received"); - } + assertCallBalances(from, to, value, balancesBefore); } } @@ -126,110 +141,80 @@ contract SafeCall_CallWithMinGas_Test is SafeCall_TestInit { ) external { - assumeNot(from); - assumeNot(to); - - assertEq(from.balance, 0, "from balance is 0"); - vm.deal(from, value); - assertEq(from.balance, value, "from balance not dealt"); + CallBalances memory balancesBefore = prepareCall(from, to, value); // Bound minGas to [0, l1_block_gas_limit] minGas = uint64(bound(minGas, 0, 30_000_000)); - uint256[2] memory balancesBefore = [from.balance, to.balance]; - vm.expectCallMinGas(to, value, minGas, data); vm.prank(from); bool success = SafeCall.callWithMinGas(to, minGas, value, data); assertTrue(success, "call not successful"); - if (from == to) { - assertEq(from.balance, balancesBefore[0], "Self-send did not change balance"); - } else { - assertEq(from.balance, balancesBefore[0] - value, "from balance not drained"); - assertEq(to.balance, balancesBefore[1] + value, "to balance received"); - } + assertCallBalances(from, to, value, balancesBefore); } /// @notice Tests that `callWithMinGas` succeeds for the lower gas bounds. function test_callWithMinGas_noLeakageLow_succeeds() external { SimpleSafeCaller caller = new SimpleSafeCaller(); - for (uint64 i = 40_000; i < 100_000; i++) { - uint256 snapshot = vm.snapshot(); - - // The values below are best gotten by setting the value to a high number and running - // the test with a verbosity of `-vvv` then setting the value to the value (gas arg) of - // the failed assertion. A faster way to do this for forge coverage cases, is to - // comment out the optimizer and optimizer runs in the foundry.toml file and then run - // forge test. This is faster because forge test only compiles modified contracts - // unlike forge coverage. - uint256 expected; - - // Because forge coverage always runs with the optimizer disabled, if forge coverage is - // run before testing this with forge test or forge snapshot, forge clean should be run - // first so that it recompiles the contracts using the foundry.toml optimizer settings. - if (vm.isContext(VmSafe.ForgeContext.Coverage) || LibString.eq(Config.foundryProfile(), "lite")) { - // 66_290 is the exact amount of gas required to make the safe call - // successfully with the optimizer disabled (ran via forge coverage) - expected = 66_290; - } else if (vm.isContext(VmSafe.ForgeContext.Test) || vm.isContext(VmSafe.ForgeContext.Snapshot)) { - // 65_922 is the exact amount of gas required to make the safe call - // successfully with the foundry.toml optimizer settings. - expected = 65_922; - } else { - revert("SafeCall_Test: unknown context"); - } - - if (i < expected) { - assertFalse(caller.makeSafeCall(i, 25_000)); - } else { - vm.expectCallMinGas(address(caller), 0, 25_000, abi.encodeCall(caller.setA, (1))); - assertTrue(caller.makeSafeCall(i, 25_000)); - } - - assertTrue(vm.revertTo(snapshot)); - } + checkNoGasLeakage({ + _caller: caller, + _startGas: 40_000, + _endGas: 100_000, + _minGas: 25_000, + _expected: expectedSafeCallGas({ _coverageOrLiteGas: 66_290, _testGas: 65_922 }), + _setACall: abi.encodeCall(caller.setA, (1)) + }); } /// @notice Tests that `callWithMinGas` succeeds on the upper gas bounds. function test_callWithMinGas_noLeakageHigh_succeeds() external { SimpleSafeCaller caller = new SimpleSafeCaller(); - for (uint64 i = 15_200_000; i < 15_300_000; i++) { - uint256 snapshot = vm.snapshot(); - - // The values below are best gotten by setting the value to a high number and running - // the test with a verbosity of `-vvv` then setting the value to the value (gas arg) of - // the failed assertion. A faster way to do this for forge coverage cases, is to - // comment out the optimizer and optimizer runs in the foundry.toml file and then run - // forge test. This is faster because forge test only compiles modified contracts - // unlike forge coverage. - uint256 expected; - - // Because forge coverage always runs with the optimizer disabled, if forge coverage is - // run before testing this with forge test or forge snapshot, forge clean should be run - // first so that it recompiles the contracts using the foundry.toml optimizer settings. - if (vm.isContext(VmSafe.ForgeContext.Coverage) || LibString.eq(Config.foundryProfile(), "lite")) { - // 15_278_989 is the exact amount of gas required to make the safe call - // successfully with the optimizer disabled (ran via forge coverage) - expected = 15_278_989; - } else if (vm.isContext(VmSafe.ForgeContext.Test) || vm.isContext(VmSafe.ForgeContext.Snapshot)) { - // 15_278_621 is the exact amount of gas required to make the safe call - // successfully with the foundry.toml optimizer settings. - expected = 15_278_621; - } else { - revert("SafeCall_Test: unknown context"); - } + checkNoGasLeakage({ + _caller: caller, + _startGas: 15_200_000, + _endGas: 15_300_000, + _minGas: 15_000_000, + _expected: expectedSafeCallGas({ _coverageOrLiteGas: 15_278_989, _testGas: 15_278_621 }), + _setACall: abi.encodeCall(caller.setA, (1)) + }); + } + + /// @notice Returns the gas threshold calibrated for the current forge context. + /// @dev Thresholds are calibrated from the failing gas arg when running these tests with `-vvv`. + function expectedSafeCallGas(uint256 _coverageOrLiteGas, uint256 _testGas) internal view returns (uint256) { + if (vm.isContext(VmSafe.ForgeContext.Coverage) || LibString.eq(Config.foundryProfile(), "lite")) { + return _coverageOrLiteGas; + } else if (vm.isContext(VmSafe.ForgeContext.Test) || vm.isContext(VmSafe.ForgeContext.Snapshot)) { + return _testGas; + } else { + revert("SafeCall_Test: unknown context"); + } + } + + function checkNoGasLeakage( + SimpleSafeCaller _caller, + uint64 _startGas, + uint64 _endGas, + uint64 _minGas, + uint256 _expected, + bytes memory _setACall + ) + internal + { + for (uint64 i = _startGas; i < _endGas; i++) { + uint256 snapshot = vm.snapshotState(); - if (i < expected) { - assertFalse(caller.makeSafeCall(i, 15_000_000)); + if (i < _expected) { + assertFalse(_caller.makeSafeCall(i, _minGas)); } else { - vm.expectCallMinGas(address(caller), 0, 15_000_000, abi.encodeCall(caller.setA, (1))); - assertTrue(caller.makeSafeCall(i, 15_000_000)); + vm.expectCallMinGas(address(_caller), 0, _minGas, _setACall); + assertTrue(_caller.makeSafeCall(i, _minGas)); } - assertTrue(vm.revertTo(snapshot)); + assertTrue(vm.revertToState(snapshot)); } } } From ea6f68a077c85abfd1d7b4d791a335a3c40c661e Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:33:32 -0400 Subject: [PATCH 107/135] Refactor SemverComp tests: streamline error handling by introducing internal helper functions for assertion checks, enhance clarity in test cases by reducing redundancy, and improve maintainability in the test structure. --- test/libraries/SemverComp.t.sol | 89 +++++++-------------------------- 1 file changed, 19 insertions(+), 70 deletions(-) diff --git a/test/libraries/SemverComp.t.sol b/test/libraries/SemverComp.t.sol index 70ab7db6..79d91466 100644 --- a/test/libraries/SemverComp.t.sol +++ b/test/libraries/SemverComp.t.sol @@ -1,131 +1,88 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Forge import { Test } from "lib/forge-std/src/Test.sol"; -// Libraries import { JSONParserLib } from "lib/solady/src/utils/JSONParserLib.sol"; import { SemverComp } from "src/libraries/SemverComp.sol"; -/// @title SemverComp_Harness -/// @notice Exposes internal functions of `SemverComp` for testing. contract SemverComp_Harness { - /// @notice Parses a semver string into a Semver struct. This is a wrapper around - /// `SemverComp.parse` that returns the major, minor, and patch components as - /// separate values. - /// @param _semver The semver string to parse. - /// @return major_ The major version. - /// @return minor_ The minor version. - /// @return patch_ The patch version. function parse(string memory _semver) external pure returns (uint256 major_, uint256 minor_, uint256 patch_) { SemverComp.Semver memory v = SemverComp.parse(_semver); return (v.major, v.minor, v.patch); } } -/// @title SemverComp_TestInit -/// @notice Reusable test initialization for `SemverComp` tests. abstract contract SemverComp_TestInit is Test { SemverComp_Harness internal harness; - /// @notice Sets up the test environment. function setUp() public { harness = new SemverComp_Harness(); } - /// @notice Asserts that the parsed semver components match the expected values. - /// @param _semver The semver string to parse. - /// @param _major The expected major version. - /// @param _minor The expected minor version. - /// @param _patch The expected patch version. function assertParsedEq(string memory _semver, uint256 _major, uint256 _minor, uint256 _patch) internal view { (uint256 major, uint256 minor, uint256 patch) = harness.parse(_semver); assertEq(major, _major, "major mismatch"); assertEq(minor, _minor, "minor mismatch"); assertEq(patch, _patch, "patch mismatch"); } + + function assertParseReverts(string memory _semver, bytes4 _selector) internal { + vm.expectRevert(_selector); + harness.parse(_semver); + } } -/// @title SemverComp_parse_Test -/// @notice Tests the `parse` function behavior. contract SemverComp_parse_Test is SemverComp_TestInit { - /// @notice Parses the minimal version. function test_parse_basicZero_succeeds() external view { assertParsedEq("0.0.0", 0, 0, 0); } - /// @notice Parses a standard version. function test_parse_basic123_succeeds() external view { assertParsedEq("1.2.3", 1, 2, 3); } - /// @notice Ignores prerelease identifiers. function test_parse_withPrerelease_succeeds() external view { assertParsedEq("1.2.3-alpha", 1, 2, 3); assertParsedEq("1.2.3-alpha.1", 1, 2, 3); assertParsedEq("10.20.30-rc.1", 10, 20, 30); } - /// @notice Ignores build metadata. function test_parse_withBuildMetadataOnly_succeeds() external view { assertParsedEq("1.2.3+build.5", 1, 2, 3); assertParsedEq("1.2.3+20240101", 1, 2, 3); } - /// @notice Ignores prerelease and build metadata together. function test_parse_withPrereleaseAndBuild_succeeds() external view { assertParsedEq("1.2.3-rc.1+build.5", 1, 2, 3); assertParsedEq("2.0.0-beta+exp.sha.5114f85", 2, 0, 0); } - /// @notice Reverts when fewer than 3 dot-separated core parts are present. function test_parse_lessThanThreeParts_reverts() external { - vm.expectRevert(SemverComp.SemverComp_InvalidSemverParts.selector); - harness.parse("1.2"); - - vm.expectRevert(SemverComp.SemverComp_InvalidSemverParts.selector); - harness.parse("1"); - - vm.expectRevert(SemverComp.SemverComp_InvalidSemverParts.selector); - harness.parse(""); + assertParseReverts("1.2", SemverComp.SemverComp_InvalidSemverParts.selector); + assertParseReverts("1", SemverComp.SemverComp_InvalidSemverParts.selector); + assertParseReverts("", SemverComp.SemverComp_InvalidSemverParts.selector); } - /// @notice Current behavior: extra dot-components beyond the core 3 are ignored. function test_parse_extraDotComponents_succeeds() external view { assertParsedEq("1.2.3.4", 1, 2, 3); assertParsedEq("1.2.3.4.5", 1, 2, 3); } - /// @notice Reverts on non-numeric core parts. function test_parse_nonNumeric_reverts() external { - vm.expectRevert(JSONParserLib.ParsingFailed.selector); - harness.parse("a.b.c"); - - vm.expectRevert(JSONParserLib.ParsingFailed.selector); - harness.parse("1.b.3"); - - vm.expectRevert(JSONParserLib.ParsingFailed.selector); - harness.parse("1.2.c"); + assertParseReverts("a.b.c", JSONParserLib.ParsingFailed.selector); + assertParseReverts("1.b.3", JSONParserLib.ParsingFailed.selector); + assertParseReverts("1.2.c", JSONParserLib.ParsingFailed.selector); } - /// @notice Reverts on certain commonly malformed inputs. function test_parse_malformedInputs_reverts() external { - // Leading/trailing whitespace - vm.expectRevert(JSONParserLib.ParsingFailed.selector); - harness.parse(" 1.2.3"); - vm.expectRevert(JSONParserLib.ParsingFailed.selector); - harness.parse("1.2.3 "); - - // "v" prefix - vm.expectRevert(JSONParserLib.ParsingFailed.selector); - harness.parse("v1.2.3"); + assertParseReverts(" 1.2.3", JSONParserLib.ParsingFailed.selector); + assertParseReverts("1.2.3 ", JSONParserLib.ParsingFailed.selector); + assertParseReverts("v1.2.3", JSONParserLib.ParsingFailed.selector); } } -/// @title SemverComp_Eq_Test -/// @notice Tests the `eq` function behavior. -contract SemverComp_Eq_Test is SemverComp_TestInit { +contract SemverComp_Eq_Test is Test { function test_eq_succeeds() external pure { assertTrue(SemverComp.eq("1.2.3", "1.2.3")); @@ -135,9 +92,7 @@ contract SemverComp_Eq_Test is SemverComp_TestInit { } } -/// @title SemverComp_Lt_Test -/// @notice Tests the `lt` function behavior. -contract SemverComp_Lt_Test is SemverComp_TestInit { +contract SemverComp_Lt_Test is Test { function test_lt_succeeds() external pure { assertTrue(SemverComp.lt("1.2.3", "1.2.4")); assertTrue(SemverComp.lt("1.2.3", "1.3.0")); @@ -149,9 +104,7 @@ contract SemverComp_Lt_Test is SemverComp_TestInit { } } -/// @title SemverComp_Lte_Test -/// @notice Tests the `lte` function behavior. -contract SemverComp_Lte_Test is SemverComp_TestInit { +contract SemverComp_Lte_Test is Test { function test_lte_succeeds() external pure { assertTrue(SemverComp.lte("1.2.3", "1.2.3")); assertTrue(SemverComp.lte("1.2.3", "1.2.4")); @@ -163,9 +116,7 @@ contract SemverComp_Lte_Test is SemverComp_TestInit { } } -/// @title SemverComp_Gt_Test -/// @notice Tests the `gt` function behavior. -contract SemverComp_Gt_Test is SemverComp_TestInit { +contract SemverComp_Gt_Test is Test { function test_gt_succeeds() external pure { assertTrue(SemverComp.gt("1.2.4", "1.2.3")); assertTrue(SemverComp.gt("1.3.0", "1.2.3")); @@ -177,9 +128,7 @@ contract SemverComp_Gt_Test is SemverComp_TestInit { } } -/// @title SemverComp_Gte_Test -/// @notice Tests the `gte` function behavior. -contract SemverComp_Gte_Test is SemverComp_TestInit { +contract SemverComp_Gte_Test is Test { function test_gte_succeeds() external pure { assertTrue(SemverComp.gte("1.2.3", "1.2.3")); assertTrue(SemverComp.gte("1.2.4", "1.2.3")); From 4d869b1dc0a26cb2543edfbe9a0e2b4a4f8524c9 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:38:09 -0400 Subject: [PATCH 108/135] Refactor DelegateCaller and GasBurner contracts: optimize gas handling in delegate calls by using calldata, streamline gas burning logic with a constant overhead, and enhance clarity by removing redundant internal functions. --- test/mocks/Callers.sol | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/test/mocks/Callers.sol b/test/mocks/Callers.sol index 965d1eaa..0d1dbed6 100644 --- a/test/mocks/Callers.sol +++ b/test/mocks/Callers.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -// Libraries import { Burn } from "src/libraries/Burn.sol"; contract CallRecorder { @@ -35,21 +34,17 @@ contract Reverter { /// @dev Can be etched in to any address to test making a delegatecall from that address. contract DelegateCaller { - function dcForward(address _target, bytes memory _data) external { + function dcForward(address _target, bytes calldata _data) external { assembly { - // Perform the delegatecall, make sure to pass all available gas. - let success := delegatecall(gas(), _target, add(_data, 0x20), mload(_data), 0x0, 0x0) + calldatacopy(0x0, _data.offset, _data.length) + let success := delegatecall(gas(), _target, 0x0, _data.length, 0x0, 0x0) - // Copy returndata into memory at 0x0....returndatasize. Note that this *will* - // overwrite the calldata that we just copied into memory but that doesn't really - // matter because we'll be returning in a second anyway. - returndatacopy(0x0, 0x0, returndatasize()) + let size := returndatasize() + returndatacopy(0x0, 0x0, size) - // Success == 0 means a revert. We'll revert too and pass the data up. - if iszero(success) { revert(0x0, returndatasize()) } + if iszero(success) { revert(0x0, size) } - // Otherwise we'll just return and pass the data up. - return(0x0, returndatasize()) + return(0x0, size) } } } @@ -57,28 +52,18 @@ contract DelegateCaller { /// @title GasBurner /// @notice Contract that burns a specified amount of gas on receive or fallback. contract GasBurner { - /// @notice The amount of gas to burn on receive or fallback. - uint256 immutable GAS_TO_BURN; + uint256 internal constant GAS_BURN_OVERHEAD = 500; + uint256 internal immutable GAS_TO_BURN; - /// @notice Constructor. - /// @param _gas The amount of gas to burn on receive or fallback. constructor(uint256 _gas) { - // 500 gas buffer for Solidity overhead. - GAS_TO_BURN = _gas - 500; + GAS_TO_BURN = _gas - GAS_BURN_OVERHEAD; } - /// @notice Receive function that burns the specified amount of gas. receive() external payable { - _burn(); + Burn.gas(GAS_TO_BURN); } - /// @notice Fallback function that burns the specified amount of gas. fallback() external payable { - _burn(); - } - - /// @notice Internal function that burns the specified amount of gas. - function _burn() internal view { Burn.gas(GAS_TO_BURN); } } From 76b7728ab9e18a0512d7091829f20c589721dbf8 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:46:29 -0400 Subject: [PATCH 109/135] Refactor EIP1967Helper library: replace hardcoded storage slot constants with references to Constants, streamline admin and implementation management functions for improved clarity and maintainability. --- test/mocks/EIP1967Helper.sol | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/test/mocks/EIP1967Helper.sol b/test/mocks/EIP1967Helper.sol index 4720b7e9..22badcc8 100644 --- a/test/mocks/EIP1967Helper.sol +++ b/test/mocks/EIP1967Helper.sol @@ -2,34 +2,26 @@ pragma solidity ^0.8.0; import { Vm } from "lib/forge-std/src/Vm.sol"; +import { Constants } from "src/libraries/Constants.sol"; /// @title EIP1967Helper /// @dev Testing library to help with reading EIP 1967 variables from state library EIP1967Helper { - /// @notice The storage slot that holds the address of a proxy implementation. - /// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)` - bytes32 internal constant PROXY_IMPLEMENTATION_SLOT = - 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - - /// @notice The storage slot that holds the address of the owner. - /// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)` - bytes32 internal constant PROXY_ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; - Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); function getAdmin(address _proxy) internal view returns (address) { - return address(uint160(uint256(vm.load(address(_proxy), PROXY_ADMIN_SLOT)))); + return address(uint160(uint256(vm.load(_proxy, Constants.PROXY_OWNER_ADDRESS)))); } - function setAdmin(address _addr, address _admin) internal { - vm.store(_addr, PROXY_ADMIN_SLOT, bytes32(uint256(uint160(_admin)))); + function setAdmin(address _proxy, address _admin) internal { + vm.store(_proxy, Constants.PROXY_OWNER_ADDRESS, bytes32(uint256(uint160(_admin)))); } function getImplementation(address _proxy) internal view returns (address) { - return address(uint160(uint256(vm.load(address(_proxy), PROXY_IMPLEMENTATION_SLOT)))); + return address(uint160(uint256(vm.load(_proxy, Constants.PROXY_IMPLEMENTATION_ADDRESS)))); } - function setImplementation(address _addr, address _impl) internal { - vm.store(_addr, PROXY_IMPLEMENTATION_SLOT, bytes32(uint256(uint160(_impl)))); + function setImplementation(address _proxy, address _impl) internal { + vm.store(_proxy, Constants.PROXY_IMPLEMENTATION_ADDRESS, bytes32(uint256(uint160(_impl)))); } } From 1571466daae74e491d3f319006aeab6e406faa2d Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:50:39 -0400 Subject: [PATCH 110/135] Update DevTEEProverRegistry contract: clarify documentation for the development registry, emphasizing that it allows signer registration without Nitro attestation verification, and streamline comments for improved readability. --- test/mocks/MockDevTEEProverRegistry.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/mocks/MockDevTEEProverRegistry.sol b/test/mocks/MockDevTEEProverRegistry.sol index 523a9615..dfe9546f 100644 --- a/test/mocks/MockDevTEEProverRegistry.sol +++ b/test/mocks/MockDevTEEProverRegistry.sol @@ -8,9 +8,8 @@ import { EnumerableSetLib } from "lib/solady-v0.0.245/src/utils/EnumerableSetLib import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; /// @title DevTEEProverRegistry -/// @notice Development version of TEEProverRegistry with bypassed attestation for testing. -/// @dev This contract adds addDevSigner() which bypasses AWS Nitro attestation verification. -/// DO NOT deploy this contract to production networks. +/// @notice Test/development registry that can register signers without Nitro attestation verification. +/// @dev DO NOT deploy this contract to production networks. contract DevTEEProverRegistry is TEEProverRegistry { using EnumerableSetLib for EnumerableSetLib.AddressSet; @@ -21,10 +20,8 @@ contract DevTEEProverRegistry is TEEProverRegistry { TEEProverRegistry(nitroVerifier, factory) { } - /// @notice Registers a signer for testing (bypasses attestation verification). + /// @notice Registers a signer and image hash without attestation verification. /// @dev Only callable by owner. For development/testing use only. - /// The imageHash parameter is stored so isValidSigner() can validate against - /// the current AggregateVerifier's TEE_IMAGE_HASH. /// @param signer The address of the signer to register. /// @param imageHash The TEE image hash to associate with this signer. function addDevSigner(address signer, bytes32 imageHash) external onlyOwner { From 69c23ae5952c6d5348542ce99d9789183443a7d1 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 14:57:51 -0400 Subject: [PATCH 111/135] Refactor NextImpl contract: update import path for Initializable, enhance variable visibility by making them private, and clarify comments regarding storage slot usage for improved understanding of upgrade behavior. --- test/mocks/NextImpl.sol | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/mocks/NextImpl.sol b/test/mocks/NextImpl.sol index 9840e710..a52d14e3 100644 --- a/test/mocks/NextImpl.sol +++ b/test/mocks/NextImpl.sol @@ -1,21 +1,20 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -import { Initializable } from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; +import { Initializable } from "lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol"; /// @title NextImpl -/// @dev Used for testing a future upgrade beyond the current implementations. -// We include some variables so that we can sanity check accessing storage values after an upgrade. +/// @dev Mock future implementation used to verify storage access after an upgrade. contract NextImpl is Initializable { - // Initializable occupies the zero-th slot. - bytes32 slot1; - bytes32[19] __gap; - bytes32 slot21; + // Initializable stores its flags in slot 0, so these fields place slot21 at storage slot 21. + bytes32 private slot1; + uint256[19] private __gap; + bytes32 private slot21; + bytes32 public constant slot21Init = bytes32(hex"1337"); function initialize(uint8 _init) public reinitializer(_init) { - // Slot21 is unused by an of our upgradeable contracts. - // This is used to verify that we can access this value after an upgrade. + // Slot 21 is unused by any current upgrade target and proves the new implementation can write to it. slot21 = slot21Init; } } From e006329b82428625128423ab56afbf4b52e8cacd Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 15:05:46 -0400 Subject: [PATCH 112/135] Refactor L2Genesis tests: streamline test functions by renaming for clarity, consolidate fee vault assertions into a helper function, and enhance readability by removing redundant comments. --- test/scripts/L2Genesis.t.sol | 142 +++++++++++++++++------------------ 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/test/scripts/L2Genesis.t.sol b/test/scripts/L2Genesis.t.sol index 8d7c9807..c0bd8093 100644 --- a/test/scripts/L2Genesis.t.sol +++ b/test/scripts/L2Genesis.t.sol @@ -6,10 +6,6 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { L2Genesis } from "scripts/L2Genesis.s.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { LATEST_FORK } from "scripts/libraries/Config.sol"; -import { ISequencerFeeVault } from "interfaces/L2/ISequencerFeeVault.sol"; -import { IBaseFeeVault } from "interfaces/L2/IBaseFeeVault.sol"; -import { IL1FeeVault } from "interfaces/L2/IL1FeeVault.sol"; -import { IOperatorFeeVault } from "interfaces/L2/IOperatorFeeVault.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IOptimismMintableERC721Factory } from "interfaces/L2/IOptimismMintableERC721Factory.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; @@ -28,12 +24,9 @@ abstract contract L2Genesis_TestInit is Test { genesis = new L2Genesis(); } - function testProxyAdmin() internal view { - // Verify owner in the proxy + function _assertProxyAdmin() internal view { assertEq(input.opChainProxyAdminOwner, IProxyAdmin(Predeploys.PROXY_ADMIN).owner()); - // Verify owner in the implementation to catch storage shifting issues - // The implementation is stored in the code namespace address proxyAdminImpl = Predeploys.predeployToCodeNamespace(Predeploys.PROXY_ADMIN); assertEq( input.opChainProxyAdminOwner, @@ -42,71 +35,77 @@ abstract contract L2Genesis_TestInit is Test { ); } - function testPredeploys() internal view { + function _assertPredeploys() internal view { uint160 prefix = uint160(0x420) << 148; for (uint256 i = 0; i < Predeploys.PREDEPLOY_COUNT; i++) { address addr = address(prefix | uint160(i)); - // If it's not proxied, skip next checks. if (Predeploys.notProxied(addr)) { continue; } - // All predeploys should have code assertGt(addr.code.length, 0); - // All proxied predeploys should have the 1967 admin slot set to the ProxyAdmin assertEq(Predeploys.PROXY_ADMIN, EIP1967Helper.getAdmin(addr)); - // If it's not a supported predeploy, skip next checks. if (!Predeploys.isSupportedPredeploy(addr)) { continue; } - // All proxied predeploys should have the 1967 admin slot set to the ProxyAdmin - // predeploy address impl = Predeploys.predeployToCodeNamespace(addr); + assertEq(impl, EIP1967Helper.getImplementation(addr)); assertGt(impl.code.length, 0); } assertGt(Predeploys.WETH.code.length, 0); } - function testVaultsWithoutRevenueShare() internal view { - IBaseFeeVault baseFeeVault = IBaseFeeVault(payable(Predeploys.BASE_FEE_VAULT)); - IL1FeeVault l1FeeVault = IL1FeeVault(payable(Predeploys.L1_FEE_VAULT)); - ISequencerFeeVault sequencerFeeVault = ISequencerFeeVault(payable(Predeploys.SEQUENCER_FEE_WALLET)); - IOperatorFeeVault operatorFeeVault = IOperatorFeeVault(payable(Predeploys.OPERATOR_FEE_VAULT)); - - assertEq(baseFeeVault.RECIPIENT(), input.baseFeeVaultRecipient); - assertEq(baseFeeVault.recipient(), input.baseFeeVaultRecipient); - assertEq(baseFeeVault.MIN_WITHDRAWAL_AMOUNT(), input.baseFeeVaultMinimumWithdrawalAmount); - assertEq(baseFeeVault.minWithdrawalAmount(), input.baseFeeVaultMinimumWithdrawalAmount); - assertEq(uint8(baseFeeVault.WITHDRAWAL_NETWORK()), uint8(input.baseFeeVaultWithdrawalNetwork)); - assertEq(uint8(baseFeeVault.withdrawalNetwork()), uint8(input.baseFeeVaultWithdrawalNetwork)); - - assertEq(l1FeeVault.RECIPIENT(), input.l1FeeVaultRecipient); - assertEq(l1FeeVault.recipient(), input.l1FeeVaultRecipient); - assertEq(l1FeeVault.MIN_WITHDRAWAL_AMOUNT(), input.l1FeeVaultMinimumWithdrawalAmount); - assertEq(l1FeeVault.minWithdrawalAmount(), input.l1FeeVaultMinimumWithdrawalAmount); - assertEq(uint8(l1FeeVault.WITHDRAWAL_NETWORK()), uint8(input.l1FeeVaultWithdrawalNetwork)); - assertEq(uint8(l1FeeVault.withdrawalNetwork()), uint8(input.l1FeeVaultWithdrawalNetwork)); - - assertEq(sequencerFeeVault.RECIPIENT(), input.sequencerFeeVaultRecipient); - assertEq(sequencerFeeVault.recipient(), input.sequencerFeeVaultRecipient); - assertEq(sequencerFeeVault.MIN_WITHDRAWAL_AMOUNT(), input.sequencerFeeVaultMinimumWithdrawalAmount); - assertEq(sequencerFeeVault.minWithdrawalAmount(), input.sequencerFeeVaultMinimumWithdrawalAmount); - assertEq(uint8(sequencerFeeVault.WITHDRAWAL_NETWORK()), uint8(input.sequencerFeeVaultWithdrawalNetwork)); - assertEq(uint8(sequencerFeeVault.withdrawalNetwork()), uint8(input.sequencerFeeVaultWithdrawalNetwork)); - - assertEq(operatorFeeVault.RECIPIENT(), input.operatorFeeVaultRecipient); - assertEq(operatorFeeVault.recipient(), input.operatorFeeVaultRecipient); - assertEq(operatorFeeVault.MIN_WITHDRAWAL_AMOUNT(), input.operatorFeeVaultMinimumWithdrawalAmount); - assertEq(operatorFeeVault.minWithdrawalAmount(), input.operatorFeeVaultMinimumWithdrawalAmount); - assertEq(uint8(operatorFeeVault.WITHDRAWAL_NETWORK()), uint8(input.operatorFeeVaultWithdrawalNetwork)); - assertEq(uint8(operatorFeeVault.withdrawalNetwork()), uint8(input.operatorFeeVaultWithdrawalNetwork)); + function _assertFeeVaultsWithoutRevenueShare() internal view { + _assertFeeVault( + Predeploys.BASE_FEE_VAULT, + input.baseFeeVaultRecipient, + input.baseFeeVaultMinimumWithdrawalAmount, + input.baseFeeVaultWithdrawalNetwork + ); + _assertFeeVault( + Predeploys.L1_FEE_VAULT, + input.l1FeeVaultRecipient, + input.l1FeeVaultMinimumWithdrawalAmount, + input.l1FeeVaultWithdrawalNetwork + ); + _assertFeeVault( + Predeploys.SEQUENCER_FEE_WALLET, + input.sequencerFeeVaultRecipient, + input.sequencerFeeVaultMinimumWithdrawalAmount, + input.sequencerFeeVaultWithdrawalNetwork + ); + _assertFeeVault( + Predeploys.OPERATOR_FEE_VAULT, + input.operatorFeeVaultRecipient, + input.operatorFeeVaultMinimumWithdrawalAmount, + input.operatorFeeVaultWithdrawalNetwork + ); } - function testFactories() internal view { + function _assertFeeVault( + address _vault, + address _recipient, + uint256 _minWithdrawalAmount, + uint256 _withdrawalNetwork + ) + internal + view + { + IFeeVault vault = IFeeVault(payable(_vault)); + + assertEq(vault.RECIPIENT(), _recipient); + assertEq(vault.recipient(), _recipient); + assertEq(vault.MIN_WITHDRAWAL_AMOUNT(), _minWithdrawalAmount); + assertEq(vault.minWithdrawalAmount(), _minWithdrawalAmount); + assertEq(uint256(vault.WITHDRAWAL_NETWORK()), _withdrawalNetwork); + assertEq(uint256(vault.withdrawalNetwork()), _withdrawalNetwork); + } + + function _assertFactories() internal view { IOptimismMintableERC20Factory erc20Factory = IOptimismMintableERC20Factory(payable(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY)); IOptimismMintableERC721Factory erc721Factory = @@ -117,12 +116,12 @@ abstract contract L2Genesis_TestInit is Test { assertEq(erc721Factory.remoteChainID(), input.l1ChainID); } - function testForks() internal view { - // The fork should be set to Isthmus at least. Check by validating the GasPriceOracle + function _assertForks() internal view { IGasPriceOracle gasPriceOracle = IGasPriceOracle(payable(Predeploys.GAS_PRICE_ORACLE)); - assertEq(gasPriceOracle.isEcotone(), true); - assertEq(gasPriceOracle.isFjord(), true); - assertEq(gasPriceOracle.isIsthmus(), true); + assertTrue(gasPriceOracle.isEcotone()); + assertTrue(gasPriceOracle.isFjord()); + assertTrue(gasPriceOracle.isIsthmus()); + assertTrue(gasPriceOracle.isJovian()); } } @@ -131,26 +130,26 @@ abstract contract L2Genesis_TestInit is Test { contract L2Genesis_Run_Test is L2Genesis_TestInit { function setUp() public override { super.setUp(); - // Set up default input configuration + input = L2Genesis.Input({ l1ChainID: 1, l2ChainID: 2, - l1CrossDomainMessengerProxy: payable(address(0x0000000000000000000000000000000000000001)), - l1StandardBridgeProxy: payable(address(0x0000000000000000000000000000000000000002)), - l1ERC721BridgeProxy: payable(address(0x0000000000000000000000000000000000000003)), - opChainProxyAdminOwner: address(0x0000000000000000000000000000000000000004), - sequencerFeeVaultRecipient: address(0x0000000000000000000000000000000000000005), + l1CrossDomainMessengerProxy: payable(makeAddr("L1CrossDomainMessengerProxy")), + l1StandardBridgeProxy: payable(makeAddr("L1StandardBridgeProxy")), + l1ERC721BridgeProxy: payable(makeAddr("L1ERC721BridgeProxy")), + opChainProxyAdminOwner: makeAddr("ProxyAdminOwner"), + sequencerFeeVaultRecipient: makeAddr("SequencerFeeVaultRecipient"), sequencerFeeVaultMinimumWithdrawalAmount: 1, - sequencerFeeVaultWithdrawalNetwork: 1, - baseFeeVaultRecipient: address(0x0000000000000000000000000000000000000006), + sequencerFeeVaultWithdrawalNetwork: uint256(Types.WithdrawalNetwork.L2), + baseFeeVaultRecipient: makeAddr("BaseFeeVaultRecipient"), baseFeeVaultMinimumWithdrawalAmount: 1, - baseFeeVaultWithdrawalNetwork: 1, - l1FeeVaultRecipient: address(0x0000000000000000000000000000000000000007), + baseFeeVaultWithdrawalNetwork: uint256(Types.WithdrawalNetwork.L2), + l1FeeVaultRecipient: makeAddr("L1FeeVaultRecipient"), l1FeeVaultMinimumWithdrawalAmount: 1, - l1FeeVaultWithdrawalNetwork: 1, - operatorFeeVaultRecipient: address(0x0000000000000000000000000000000000000008), + l1FeeVaultWithdrawalNetwork: uint256(Types.WithdrawalNetwork.L2), + operatorFeeVaultRecipient: makeAddr("OperatorFeeVaultRecipient"), operatorFeeVaultMinimumWithdrawalAmount: 1, - operatorFeeVaultWithdrawalNetwork: 1, + operatorFeeVaultWithdrawalNetwork: uint256(Types.WithdrawalNetwork.L2), fork: uint256(LATEST_FORK), fundDevAccounts: true }); @@ -159,9 +158,10 @@ contract L2Genesis_Run_Test is L2Genesis_TestInit { function test_run_succeeds() external { genesis.run(input); - testProxyAdmin(); - testPredeploys(); - testFactories(); - testForks(); + _assertProxyAdmin(); + _assertPredeploys(); + _assertFeeVaultsWithoutRevenueShare(); + _assertFactories(); + _assertForks(); } } From a0c1a1b038c379b0c966299944c4a5e4812b1fb0 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 15:11:21 -0400 Subject: [PATCH 113/135] Refactor CommonTest contract: update console import, replace hardcoded balances with a constant, and improve function visibility for better maintainability and clarity in test setup. --- test/setup/CommonTest.sol | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/test/setup/CommonTest.sol b/test/setup/CommonTest.sol index 63b78c5a..3ee0a0fe 100644 --- a/test/setup/CommonTest.sol +++ b/test/setup/CommonTest.sol @@ -12,18 +12,18 @@ import { FFIInterface } from "test/setup/FFIInterface.sol"; // Contracts import { ERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; -import { console } from "lib/forge-std/src/console.sol"; +import { console2 as console } from "lib/forge-std/src/console2.sol"; // Interfaces import { IOptimismMintableERC20Full } from "interfaces/universal/IOptimismMintableERC20Full.sol"; /// @title CommonTest -/// @dev An extension to `Test` that sets up the optimism smart contracts. +/// @dev An extension to `Test` that sets up the Optimism smart contracts. abstract contract CommonTest is Test, Setup, Events { address alice; address bob; - bytes32 constant nonZeroHash = keccak256(abi.encode("NON_ZERO")); + uint256 constant DEFAULT_TEST_BALANCE = 10_000 ether; FFIInterface constant ffi = FFIInterface(address(uint160(uint256(keccak256(abi.encode("optimism.ffi")))))); @@ -52,8 +52,8 @@ abstract contract CommonTest is Test, Setup, Events { alice = makeAddr("alice"); bob = makeAddr("bob"); - vm.deal(alice, 10000 ether); - vm.deal(bob, 10000 ether); + vm.deal(alice, DEFAULT_TEST_BALANCE); + vm.deal(bob, DEFAULT_TEST_BALANCE); // Override the config after the deploy script initialized the config if (useUpgradedFork) { @@ -76,23 +76,18 @@ abstract contract CommonTest is Test, Setup, Events { excludeContract(address(deploy)); excludeContract(address(deploy.cfg())); - // Deploy L1 Setup.L1(); - // Deploy L2 Setup.L2(); - // Call bridge initializer setup function bridgeInitializerSetUp(); } - function bridgeInitializerSetUp() public { + function bridgeInitializerSetUp() internal { L1Token = new ERC20("Native L1 Token", "L1T"); if (isForkTest()) { - console.log("CommonTest: fork test detected, skipping L2 setup"); L2Token = IOptimismMintableERC20Full(makeAddr("L2Token")); } else { - // Deploy the L2 ERC20 now L2Token = IOptimismMintableERC20Full( l2OptimismMintableERC20Factory.createStandardL2Token( address(L1Token), @@ -104,23 +99,18 @@ abstract contract CommonTest is Test, Setup, Events { NativeL2Token = new ERC20("Native L2 Token", "L2T"); + string memory remoteL1TokenName = string(abi.encodePacked("L1-", NativeL2Token.name())); + string memory remoteL1TokenSymbol = string(abi.encodePacked("L1-", NativeL2Token.symbol())); + RemoteL1Token = IOptimismMintableERC20Full( l1OptimismMintableERC20Factory.createStandardL2Token( - address(NativeL2Token), - string(abi.encodePacked("L1-", NativeL2Token.name())), - string(abi.encodePacked("L1-", NativeL2Token.symbol())) + address(NativeL2Token), remoteL1TokenName, remoteL1TokenSymbol ) ); BadL1Token = ERC20( - l1OptimismMintableERC20Factory.createStandardL2Token( - address(1), - string(abi.encodePacked("L1-", NativeL2Token.name())), - string(abi.encodePacked("L1-", NativeL2Token.symbol())) - ) + l1OptimismMintableERC20Factory.createStandardL2Token(address(1), remoteL1TokenName, remoteL1TokenSymbol) ); - - console.log("CommonTest: SetUp complete!"); } /// @dev Helper function that wraps `TransactionDeposited` event. @@ -151,9 +141,9 @@ abstract contract CommonTest is Test, Setup, Events { } /// @dev Disables upgrade mode for testing. By default the fork testing env will be upgraded to the latest - /// implementation. This can be used to disable the upgrade which, is useful for tests targeting the upgrade + /// implementation. This can be used to disable the upgrade, which is useful for tests targeting the upgrade /// process itself. - function disableUpgradedFork() public { + function disableUpgradedFork() internal { _checkNotDeployed("non-upgraded fork"); useUpgradedFork = false; From cf2539f689c131093992f0396d719ef25027ed65 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 15:18:51 -0400 Subject: [PATCH 114/135] Refactor Events contract: remove unused events to streamline the contract, enhancing clarity and maintainability in the event definitions. --- test/setup/Events.sol | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/test/setup/Events.sol b/test/setup/Events.sol index d3119ed8..4ed90fc2 100644 --- a/test/setup/Events.sol +++ b/test/setup/Events.sol @@ -1,28 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -// Libraries import { Types } from "src/libraries/Types.sol"; -import { Timestamp } from "src/libraries/bridge/LibUDT.sol"; -import "src/libraries/bridge/Types.sol"; - -// Interfaces -import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; /// @title Events /// @dev Contains various events that are tested against. This contract needs to /// exist until we either modularize the implementations or use a newer version of /// solc that allows for referencing events from other contracts. abstract contract Events { - /// @dev OpenZeppelin Ownable.sol transferOwnership event event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); - event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); - event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt); event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit); event SentMessageExtension1(address indexed sender, uint256 value); @@ -38,22 +29,6 @@ abstract contract Events { event WithdrawerBalanceBurnt(uint256 indexed amount); event RelayedMessage(bytes32 indexed msgHash); event FailedRelayedMessage(bytes32 indexed msgHash); - event TransactionDeposited( - address indexed from, - address indexed to, - uint256 mint, - uint256 value, - uint64 gasLimit, - bool isCreation, - bytes data - ); - event WhatHappened(bool success, bytes returndata); - - event OutputProposed( - bytes32 indexed outputRoot, uint256 indexed l2OutputIndex, uint256 indexed l2BlockNumber, uint256 l1Timestamp - ); - - event OutputsDeleted(uint256 indexed prevNextOutputIndex, uint256 indexed newNextOutputIndex); event Withdrawal(uint256 value, address to, address from); event Withdrawal(uint256 value, address to, address from, Types.WithdrawalNetwork withdrawalNetwork); @@ -78,10 +53,6 @@ abstract contract Events { address indexed l1Token, address indexed l2Token, address indexed from, address to, uint256 amount, bytes data ); - event DepositFailed( - address indexed l1Token, address indexed l2Token, address indexed from, address to, uint256 amount, bytes data - ); - event ETHBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes data); event ETHBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes data); @@ -109,12 +80,4 @@ abstract contract Events { event Unpaused(address identifier); event PauseExtended(address identifier); - - event BalanceChanged(address account, uint256 balance); - - event ETHMigrated(address indexed lockbox, uint256 ethBalance); - - event PortalMigrated( - address oldLockbox, address newLockbox, address oldAnchorStateRegistry, address newAnchorStateRegistry - ); } From f4aaa3787ebc83f0afc2f50a668f055fa15a5cda Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 15:23:53 -0400 Subject: [PATCH 115/135] Refactor FeatureFlags contract: remove unused feature bitmap management functions, update SystemConfig interaction, and clarify contract documentation for improved maintainability and clarity in feature checks. --- test/setup/FeatureFlags.sol | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/test/setup/FeatureFlags.sol b/test/setup/FeatureFlags.sol index f41543d5..a28cdeb3 100644 --- a/test/setup/FeatureFlags.sol +++ b/test/setup/FeatureFlags.sol @@ -6,39 +6,16 @@ import { Vm } from "lib/forge-std/src/Vm.sol"; // Interfaces import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -/// @notice FeatureFlags manages the feature bitmap by either direct user input or via environment -/// variables. +/// @notice Provides helpers for checking SystemConfig-gated test behavior. abstract contract FeatureFlags { - /// @notice The address of the foundry Vm contract. Vm private constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - /// @notice The development feature bitmap. - bytes32 internal devFeatureBitmap; + ISystemConfig private sysCfg; - /// @notice The address of the SystemConfig contract. - ISystemConfig internal sysCfg; - - /// @notice Sets the address of the SystemConfig contract. - /// @param _sysCfg The address of the SystemConfig contract. - function setSystemConfig(ISystemConfig _sysCfg) public { + function setSystemConfig(ISystemConfig _sysCfg) internal { sysCfg = _sysCfg; } - /// @notice Enables a feature. - /// @param _feature The feature to set. - function setDevFeatureEnabled(bytes32 _feature) public { - devFeatureBitmap |= _feature; - } - - /// @notice Disables a feature. - /// @param _feature The feature to set. - function setDevFeatureDisabled(bytes32 _feature) public { - devFeatureBitmap &= ~_feature; - } - - /// @notice Checks if a system feature is enabled. - /// @param _feature The feature to check. - /// @return True if the feature is enabled, false otherwise. function isSysFeatureEnabled(bytes32 _feature) public view returns (bool) { return sysCfg.isFeatureEnabled(_feature); } From 616dc0035fd715ae233a3b69951a340e5c38faad Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 15:29:33 -0400 Subject: [PATCH 116/135] Refactor FFIInterface contract: streamline command construction by introducing helper functions for command generation and argument setting, enhancing code clarity and maintainability in transaction input handling. --- test/setup/FFIInterface.sol | 182 +++++++++++++++--------------------- 1 file changed, 73 insertions(+), 109 deletions(-) diff --git a/test/setup/FFIInterface.sol b/test/setup/FFIInterface.sol index ca2fd4d8..313796b5 100644 --- a/test/setup/FFIInterface.sol +++ b/test/setup/FFIInterface.sol @@ -11,32 +11,17 @@ import { Strings } from "lib/openzeppelin-contracts/contracts/utils/Strings.sol" /// are multiple artifacts for different compiler versions. contract FFIInterface { Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + string internal constant GO_FFI = "scripts/go-ffi/go-ffi"; + string internal constant DIFF_MODE = "diff"; function getProveWithdrawalTransactionInputs(Types.WithdrawalTransaction memory _tx) external returns (bytes32, bytes32, bytes32, bytes32, bytes[] memory) { - string[] memory cmds = new string[](9); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "getProveWithdrawalTransactionInputs"; - cmds[3] = vm.toString(_tx.nonce); - cmds[4] = vm.toString(_tx.sender); - cmds[5] = vm.toString(_tx.target); - cmds[6] = vm.toString(_tx.value); - cmds[7] = vm.toString(_tx.gasLimit); - cmds[8] = vm.toString(_tx.data); - - bytes memory result = vm.ffi(cmds); - ( - bytes32 stateRoot, - bytes32 storageRoot, - bytes32 outputRoot, - bytes32 withdrawalHash, - bytes[] memory withdrawalProof - ) = abi.decode(result, (bytes32, bytes32, bytes32, bytes32, bytes[])); - - return (stateRoot, storageRoot, outputRoot, withdrawalHash, withdrawalProof); + string[] memory cmds = _newDiffCommand("getProveWithdrawalTransactionInputs", 6); + _setCrossDomainArgs(cmds, _tx.nonce, _tx.sender, _tx.target, _tx.value, _tx.gasLimit, _tx.data); + + return abi.decode(vm.ffi(cmds), (bytes32, bytes32, bytes32, bytes32, bytes[])); } function hashCrossDomainMessage( @@ -50,19 +35,10 @@ contract FFIInterface { external returns (bytes32) { - string[] memory cmds = new string[](9); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "hashCrossDomainMessage"; - cmds[3] = vm.toString(_nonce); - cmds[4] = vm.toString(_sender); - cmds[5] = vm.toString(_target); - cmds[6] = vm.toString(_value); - cmds[7] = vm.toString(_gasLimit); - cmds[8] = vm.toString(_data); + string[] memory cmds = _newDiffCommand("hashCrossDomainMessage", 6); + _setCrossDomainArgs(cmds, _nonce, _sender, _target, _value, _gasLimit, _data); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (bytes32)); + return abi.decode(vm.ffi(cmds), (bytes32)); } function hashWithdrawal( @@ -76,19 +52,10 @@ contract FFIInterface { external returns (bytes32) { - string[] memory cmds = new string[](9); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "hashWithdrawal"; - cmds[3] = vm.toString(_nonce); - cmds[4] = vm.toString(_sender); - cmds[5] = vm.toString(_target); - cmds[6] = vm.toString(_value); - cmds[7] = vm.toString(_gasLimit); - cmds[8] = vm.toString(_data); + string[] memory cmds = _newDiffCommand("hashWithdrawal", 6); + _setCrossDomainArgs(cmds, _nonce, _sender, _target, _value, _gasLimit, _data); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (bytes32)); + return abi.decode(vm.ffi(cmds), (bytes32)); } function hashOutputRootProof( @@ -100,17 +67,13 @@ contract FFIInterface { external returns (bytes32) { - string[] memory cmds = new string[](7); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "hashOutputRootProof"; + string[] memory cmds = _newDiffCommand("hashOutputRootProof", 4); cmds[3] = Strings.toHexString(uint256(_version)); cmds[4] = Strings.toHexString(uint256(_stateRoot)); cmds[5] = Strings.toHexString(uint256(_messagePasserStorageRoot)); cmds[6] = Strings.toHexString(uint256(_latestBlockhash)); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (bytes32)); + return abi.decode(vm.ffi(cmds), (bytes32)); } function hashDepositTransaction( @@ -125,10 +88,7 @@ contract FFIInterface { external returns (bytes32) { - string[] memory cmds = new string[](11); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "hashDepositTransaction"; + string[] memory cmds = _newDiffCommand("hashDepositTransaction", 8); cmds[3] = "0x0000000000000000000000000000000000000000000000000000000000000000"; cmds[4] = vm.toString(_logIndex); cmds[5] = vm.toString(_from); @@ -138,15 +98,11 @@ contract FFIInterface { cmds[9] = vm.toString(_gas); cmds[10] = vm.toString(_data); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (bytes32)); + return abi.decode(vm.ffi(cmds), (bytes32)); } function encodeDepositTransaction(Types.UserDepositTransaction calldata txn) external returns (bytes memory) { - string[] memory cmds = new string[](12); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "encodeDepositTransaction"; + string[] memory cmds = _newDiffCommand("encodeDepositTransaction", 9); cmds[3] = vm.toString(txn.from); cmds[4] = vm.toString(txn.to); cmds[5] = vm.toString(txn.value); @@ -157,8 +113,7 @@ contract FFIInterface { cmds[10] = vm.toString(txn.l1BlockHash); cmds[11] = vm.toString(txn.logIndex); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (bytes)); + return abi.decode(vm.ffi(cmds), (bytes)); } function encodeCrossDomainMessage( @@ -172,84 +127,93 @@ contract FFIInterface { external returns (bytes memory) { - string[] memory cmds = new string[](9); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "encodeCrossDomainMessage"; - cmds[3] = vm.toString(_nonce); - cmds[4] = vm.toString(_sender); - cmds[5] = vm.toString(_target); - cmds[6] = vm.toString(_value); - cmds[7] = vm.toString(_gasLimit); - cmds[8] = vm.toString(_data); + string[] memory cmds = _newDiffCommand("encodeCrossDomainMessage", 6); + _setCrossDomainArgs(cmds, _nonce, _sender, _target, _value, _gasLimit, _data); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (bytes)); + return abi.decode(vm.ffi(cmds), (bytes)); } function encodeSuperRootProof(Types.SuperRootProof calldata proof) external returns (bytes memory) { - string[] memory cmds = new string[](4); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "encodeSuperRootProof"; + string[] memory cmds = _newDiffCommand("encodeSuperRootProof", 1); cmds[3] = vm.toString(abi.encode(proof)); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (bytes)); + return abi.decode(vm.ffi(cmds), (bytes)); } function hashSuperRootProof(Types.SuperRootProof calldata proof) external returns (bytes32) { - string[] memory cmds = new string[](4); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "hashSuperRootProof"; + string[] memory cmds = _newDiffCommand("hashSuperRootProof", 1); cmds[3] = vm.toString(abi.encode(proof)); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (bytes32)); + return abi.decode(vm.ffi(cmds), (bytes32)); } function decodeVersionedNonce(uint256 nonce) external returns (uint256, uint256) { - string[] memory cmds = new string[](4); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "decodeVersionedNonce"; + string[] memory cmds = _newDiffCommand("decodeVersionedNonce", 1); cmds[3] = vm.toString(nonce); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (uint256, uint256)); + return abi.decode(vm.ffi(cmds), (uint256, uint256)); } function getMerkleTrieFuzzCase(string memory variant) external returns (bytes32, bytes memory, bytes memory, bytes[] memory) { - string[] memory cmds = new string[](3); - cmds[0] = "./scripts/go-ffi/go-ffi"; - cmds[1] = "trie"; - cmds[2] = variant; + string[] memory cmds = _newCommand("trie", variant, 0); return abi.decode(vm.ffi(cmds), (bytes32, bytes, bytes, bytes[])); } function encodeScalarEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external returns (bytes32) { - string[] memory cmds = new string[](5); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "encodeScalarEcotone"; + string[] memory cmds = _newDiffCommand("encodeScalarEcotone", 2); cmds[3] = vm.toString(_basefeeScalar); cmds[4] = vm.toString(_blobbasefeeScalar); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (bytes32)); + + return abi.decode(vm.ffi(cmds), (bytes32)); } function decodeScalarEcotone(bytes32 _scalar) external returns (uint32, uint32) { - string[] memory cmds = new string[](4); - cmds[0] = "scripts/go-ffi/go-ffi"; - cmds[1] = "diff"; - cmds[2] = "decodeScalarEcotone"; + string[] memory cmds = _newDiffCommand("decodeScalarEcotone", 1); cmds[3] = vm.toString(_scalar); - bytes memory result = vm.ffi(cmds); - return abi.decode(result, (uint32, uint32)); + + return abi.decode(vm.ffi(cmds), (uint32, uint32)); + } + + function _newDiffCommand(string memory _variant, uint256 _argCount) private pure returns (string[] memory cmds) { + return _newCommand(DIFF_MODE, _variant, _argCount); + } + + function _newCommand( + string memory _mode, + string memory _variant, + uint256 _argCount + ) + private + pure + returns (string[] memory cmds) + { + cmds = new string[](3 + _argCount); + cmds[0] = GO_FFI; + cmds[1] = _mode; + cmds[2] = _variant; + } + + function _setCrossDomainArgs( + string[] memory _cmds, + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _gasLimit, + bytes memory _data + ) + private + pure + { + _cmds[3] = vm.toString(_nonce); + _cmds[4] = vm.toString(_sender); + _cmds[5] = vm.toString(_target); + _cmds[6] = vm.toString(_value); + _cmds[7] = vm.toString(_gasLimit); + _cmds[8] = vm.toString(_data); } } From ab276b57a7e303001dd62b47c8e586480368c81f Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 15:50:06 -0400 Subject: [PATCH 117/135] Refactor ForkLive contract: remove unused imports and StdAssertions dependency, streamline upgrade logic by consolidating function parameters, and enhance clarity in contract documentation for improved maintainability. --- test/setup/ForkLive.s.sol | 66 +++++++++++++-------------------------- 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/test/setup/ForkLive.s.sol b/test/setup/ForkLive.s.sol index 281a82cb..2b1b0cea 100644 --- a/test/setup/ForkLive.s.sol +++ b/test/setup/ForkLive.s.sol @@ -3,9 +3,6 @@ pragma solidity ^0.8.0; import { console2 as console } from "lib/forge-std/src/console2.sol"; import { Script } from "lib/forge-std/src/Script.sol"; -import { StdAssertions } from "lib/forge-std/src/StdAssertions.sol"; - -import { FeatureFlags } from "test/setup/FeatureFlags.sol"; // Scripts import { Artifacts } from "scripts/Artifacts.s.sol"; @@ -15,21 +12,16 @@ import { Config } from "scripts/libraries/Config.sol"; // Libraries import { GameTypes } from "src/libraries/bridge/Types.sol"; -import { Claim } from "src/libraries/bridge/LibUDT.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { Types } from "scripts/libraries/Types.sol"; // Interfaces import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "interfaces/L1/proofs/IDisputeGameFactory.sol"; -import { IDisputeGame } from "interfaces/L1/proofs/IDisputeGame.sol"; -import { IAggregateVerifier } from "interfaces/L1/proofs/IAggregateVerifier.sol"; import { IAggregateVerifier } from "interfaces/L1/proofs/IAggregateVerifier.sol"; -import { IDelayedWETH } from "interfaces/L1/proofs/IDelayedWETH.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol"; import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; @@ -43,13 +35,16 @@ import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; /// `forkSystemAddresses`. /// This contract must not have constructor logic because it is set into state using `etch`. -contract ForkLive is Script, StdAssertions, FeatureFlags { +contract ForkLive is Script { DeployConfig internal constant cfg = DeployConfig(address(uint160(uint256(keccak256(abi.encode("optimism.deployconfig")))))); Artifacts internal constant artifacts = Artifacts(address(uint160(uint256(keccak256(abi.encode("optimism.artifacts")))))); + SystemDeploy internal constant deploy = + SystemDeploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy")))))); + bool public useOpsRepo; struct SystemAddresses { @@ -98,22 +93,15 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { useOpsRepo = bytes(superchainOpsAllocsPath).length > 0; if (useOpsRepo) { console.log("ForkLive: loading state from %s", superchainOpsAllocsPath); - // Set the resultant state from the superchain ops repo upgrades. - // The allocs are generated when simulating an upgrade task that runs vm.dumpState. - // These allocs represent the state of the EVM after the upgrade has been simulated. + // Load the simulated post-upgrade state before deriving implementation addresses. vm.loadAllocs(superchainOpsAllocsPath); - // Next, fetch the addresses from the configured fork entrypoints. This function uses a local EVM - // to retrieve implementation addresses by reading from the live proxy addresses. - // Setting the allocs first ensures the correct implementation addresses are retrieved. - _readForkAddresses(); - } else { - // Read the live system and save the addresses to the Artifacts contract. - _readForkAddresses(); - // Now deploy the updated implementations of the contracts. + } + + _readForkAddresses(); + if (!useOpsRepo) { _deployNewImplementations(); } - // Now upgrade the contracts (if the config is set to do so) if (useOpsRepo) { console.log("ForkLive: using ops repo to upgrade"); } else if (cfg.useUpgradedFork()) { @@ -189,18 +177,16 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { /// @notice Calls to the SystemDeploy.s.sol contract etched by Setup.sol to a deterministic address, sets up the /// environment, and deploys new implementations. function _deployNewImplementations() internal { - SystemDeploy deploy = SystemDeploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy")))))); deploy.deployImplementations(); } /// @notice Performs a script-level upgrade without a manager delegatecall. /// @param _upgrader The address of the OP Chain ProxyAdmin owner to use for the chain upgrade. - function _doUpgrade(address _upgrader) internal { + /// @param _systemConfigProxy The OP Chain SystemConfig proxy to upgrade. + function _doUpgrade(address _upgrader, ISystemConfig _systemConfigProxy) internal { SystemDeploy systemDeploy = new SystemDeploy(); Types.Implementations memory implementations = _latestImplementations(); - ISystemConfig systemConfigProxy = ISystemConfig(artifacts.mustGetAddress("SystemConfigProxy")); - ISuperchainConfig superchainConfig = ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")); IProxyAdmin superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); address superchainPAO = superchainProxyAdmin.owner(); @@ -224,7 +210,7 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { saveArtifacts: false, superchainConfigProxy: ISuperchainConfig(address(0)), implementations: implementations, - systemConfigProxy: systemConfigProxy + systemConfigProxy: _systemConfigProxy }) ); } @@ -237,43 +223,33 @@ contract ForkLive is Script, StdAssertions, FeatureFlags { address upgrader = proxyAdmin.owner(); vm.label(upgrader, "ProxyAdmin Owner"); - // Run past upgrades depending on network. - if (block.chainid == 1) { - // Mainnet - // This is empty because the block number in the justfile is after the most recent upgrade so there are no - // past upgrades to run. - } else { + if (block.chainid != 1) { revert UnsupportedChainId(); } - // Current upgrade. - _doUpgrade(upgrader); + _doUpgrade(upgrader, systemConfig); console.log("ForkLive: Saving newly deployed contracts"); // A new ASR and new dispute games were deployed, so we need to update them IDisputeGameFactory disputeGameFactory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); - IDisputeGame av = disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER); - artifacts.save("AggregateVerifier", address(av)); - - IAnchorStateRegistry newAnchorStateRegistry = av.anchorStateRegistry(); - artifacts.save("AnchorStateRegistryProxy", address(newAnchorStateRegistry)); + IAggregateVerifier aggregateVerifier = + IAggregateVerifier(address(disputeGameFactory.gameImpls(GameTypes.AGGREGATE_VERIFIER))); + artifacts.save("AggregateVerifier", address(aggregateVerifier)); - // Get the lockbox address from the portal, and save it IOptimismPortal2 portal = IOptimismPortal2(artifacts.mustGetAddress("OptimismPortalProxy")); address lockboxAddress = address(portal.ethLockbox()); artifacts.save("ETHLockboxProxy", lockboxAddress); - // Get the new DelayedWETH address and save it (might be a new proxy). - IDelayedWETH newDelayedWeth = IAggregateVerifier(address(av)).DELAYED_WETH(); - artifacts.save("DelayedWETHProxy", address(newDelayedWeth)); - artifacts.save("DelayedWETHImpl", EIP1967Helper.getImplementation(address(newDelayedWeth))); + GameAddresses memory gameAddresses = _aggregateVerifierAddresses(aggregateVerifier); + artifacts.save("AnchorStateRegistryProxy", gameAddresses.anchorStateRegistry); + artifacts.save("DelayedWETHProxy", gameAddresses.weth); + artifacts.save("DelayedWETHImpl", EIP1967Helper.getImplementation(gameAddresses.weth)); } /// @notice Returns the latest implementation set saved by deployImplementations. function _latestImplementations() internal view returns (Types.Implementations memory) { - SystemDeploy deploy = SystemDeploy(address(uint160(uint256(keccak256(abi.encode("optimism.deploy")))))); return deploy.getImplementations(); } From 0c4e298a1821c13f2acf3db94223406f0c77d3b9 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 15:58:59 -0400 Subject: [PATCH 118/135] Refactor Setup contract: add DeployConfig import, streamline L2 genesis setup by utilizing DeployUtils for etching, and enhance clarity in contract documentation regarding test setups and configurations. --- test/setup/Setup.sol | 67 ++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/test/setup/Setup.sol b/test/setup/Setup.sol index dd70a798..a415ccfd 100644 --- a/test/setup/Setup.sol +++ b/test/setup/Setup.sol @@ -8,6 +8,7 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import { FeatureFlags } from "test/setup/FeatureFlags.sol"; // Scripts +import { DeployConfig } from "scripts/deploy/DeployConfig.s.sol"; import { SystemDeploy } from "scripts/deploy/SystemDeploy.s.sol"; import { ForkLive } from "test/setup/ForkLive.s.sol"; import { LATEST_FORK } from "scripts/libraries/Config.sol"; @@ -48,11 +49,10 @@ import { IGasPriceOracle } from "interfaces/L2/IGasPriceOracle.sol"; import { IL1Block } from "interfaces/L2/IL1Block.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IWETH98 } from "interfaces/universal/IWETH98.sol"; -import { IVerifier } from "interfaces/L1/proofs/IVerifier.sol"; import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; /// @title Setup -/// @dev This contact is responsible for setting up the contracts in state. It currently +/// @dev This contract is responsible for setting up the contracts in state. It currently /// sets the L2 contracts directly at the predeploy addresses instead of setting them /// up behind proxies. In the future we will migrate to importing the genesis JSON /// file that is created to set up the L2 contracts instead of setting them up manually. @@ -123,7 +123,6 @@ abstract contract Setup is FeatureFlags { IGasPriceOracle gasPriceOracle = IGasPriceOracle(Predeploys.GAS_PRICE_ORACLE); IL1Block l1Block = IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES); IWETH98 weth = IWETH98(payable(Predeploys.WETH)); - IVerifier aggregateVerifier; TEEProverRegistry teeProverRegistry; /// @notice Indicates whether a test is running against a forked production network. @@ -131,13 +130,7 @@ abstract contract Setup is FeatureFlags { return Config.forkTest(); } - /// @notice Indicates whether a test is running against a forked network that is OP. - function isOpFork() public view returns (bool) { - string memory opChain = Config.forkOpChain(); - return keccak256(bytes(opChain)) == keccak256(bytes("op")); - } - - /// @dev Deploys either the SystemDeploy.s.sol or Fork.s.sol contract, by fetching the bytecode dynamically using + /// @dev Deploys the SystemDeploy and ForkLive setup contracts by fetching the bytecode dynamically using /// `vm.getDeployedCode()` and etching it into the state. /// This enables us to avoid including the bytecode of those contracts in the bytecode of this contract. /// If the bytecode of those contracts was included in this contract, then it will double @@ -147,7 +140,8 @@ abstract contract Setup is FeatureFlags { function setUp() public virtual { console.log("Setup: L1 setup start!"); - if (isForkTest()) { + bool forkTest = isForkTest(); + if (forkTest) { vm.createSelectFork(Config.forkRpcUrl(), Config.forkBlockNumber()); console.log("Setup: fork selected!"); require( @@ -164,15 +158,13 @@ abstract contract Setup is FeatureFlags { console.log("Setup: L1 setup done!"); - if (isForkTest()) { - // Return early if this is a fork test as we don't need to setup L2 + if (forkTest) { console.log("Setup: fork test detected, skipping L2 genesis generation"); return; } console.log("Setup: L2 setup start!"); - vm.etch(address(l2Genesis), vm.getDeployedCode("L2Genesis.s.sol:L2Genesis")); - vm.allowCheatcodes(address(l2Genesis)); + DeployUtils.etchLabelAndAllowCheatcodes({ _etchTo: address(l2Genesis), _cname: "L2Genesis" }); console.log("Setup: L2 setup done!"); } @@ -213,13 +205,10 @@ abstract contract Setup is FeatureFlags { /// @dev Sets up the L1 contracts. function L1() public { console.log("Setup: creating L1 deployments"); - // Set the deterministic deployer in state to ensure that it is there - vm.etch( - 0x4e59b44847b379578588920cA78FbF26c0B4956C, - hex"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" - ); + vm.etch(Preinstalls.DeterministicDeploymentProxy, Preinstalls.DeterministicDeploymentProxyCode); - if (isForkTest()) { + bool forkTest = isForkTest(); + if (forkTest) { forkLive.run(); } else { deploy.run(); @@ -231,7 +220,7 @@ abstract contract Setup is FeatureFlags { // Only skip ETHLockbox assignment if we're in a fork test with non-upgraded fork // TODO(#14691): Remove this check once Upgrade 15 is deployed on Mainnet. - if (!isForkTest() || deploy.cfg().useUpgradedFork()) { + if (!forkTest || deploy.cfg().useUpgradedFork()) { // Here we use getAddress instead of mustGetAddress because some chains might not have // the ETHLockbox proxy. Chains that don't have the ETHLockbox proxy will just return // address(0) and cause a revert if we use mustGetAddress. @@ -256,7 +245,6 @@ abstract contract Setup is FeatureFlags { proxyAdminOwner = proxyAdmin.owner(); superchainProxyAdmin = IProxyAdmin(EIP1967Helper.getAdmin(address(superchainConfig))); superchainProxyAdminOwner = superchainProxyAdmin.owner(); - aggregateVerifier = IVerifier(artifacts.getAddress("AggregateVerifier")); teeProverRegistry = TEEProverRegistry(artifacts.getAddress("TEEProverRegistry")); console.log("Setup: registered L1 deployments"); @@ -274,28 +262,29 @@ abstract contract Setup is FeatureFlags { } console.log("Setup: creating L2 genesis with fork %s", l2Fork.toString()); + DeployConfig cfg = deploy.cfg(); l2Genesis.run( L2Genesis.Input({ - l1ChainID: deploy.cfg().l1ChainId(), - l2ChainID: deploy.cfg().l2ChainId(), + l1ChainID: cfg.l1ChainId(), + l2ChainID: cfg.l2ChainId(), l1CrossDomainMessengerProxy: payable(address(l1CrossDomainMessenger)), l1StandardBridgeProxy: payable(address(l1StandardBridge)), l1ERC721BridgeProxy: payable(address(l1ERC721Bridge)), - opChainProxyAdminOwner: deploy.cfg().proxyAdminOwner(), - sequencerFeeVaultRecipient: deploy.cfg().sequencerFeeVaultRecipient(), - sequencerFeeVaultMinimumWithdrawalAmount: deploy.cfg().sequencerFeeVaultMinimumWithdrawalAmount(), - sequencerFeeVaultWithdrawalNetwork: deploy.cfg().sequencerFeeVaultWithdrawalNetwork(), - baseFeeVaultRecipient: deploy.cfg().baseFeeVaultRecipient(), - baseFeeVaultMinimumWithdrawalAmount: deploy.cfg().baseFeeVaultMinimumWithdrawalAmount(), - baseFeeVaultWithdrawalNetwork: deploy.cfg().baseFeeVaultWithdrawalNetwork(), - l1FeeVaultRecipient: deploy.cfg().l1FeeVaultRecipient(), - l1FeeVaultMinimumWithdrawalAmount: deploy.cfg().l1FeeVaultMinimumWithdrawalAmount(), - l1FeeVaultWithdrawalNetwork: deploy.cfg().l1FeeVaultWithdrawalNetwork(), - operatorFeeVaultRecipient: deploy.cfg().operatorFeeVaultRecipient(), - operatorFeeVaultMinimumWithdrawalAmount: deploy.cfg().operatorFeeVaultMinimumWithdrawalAmount(), - operatorFeeVaultWithdrawalNetwork: deploy.cfg().operatorFeeVaultWithdrawalNetwork(), + opChainProxyAdminOwner: cfg.proxyAdminOwner(), + sequencerFeeVaultRecipient: cfg.sequencerFeeVaultRecipient(), + sequencerFeeVaultMinimumWithdrawalAmount: cfg.sequencerFeeVaultMinimumWithdrawalAmount(), + sequencerFeeVaultWithdrawalNetwork: cfg.sequencerFeeVaultWithdrawalNetwork(), + baseFeeVaultRecipient: cfg.baseFeeVaultRecipient(), + baseFeeVaultMinimumWithdrawalAmount: cfg.baseFeeVaultMinimumWithdrawalAmount(), + baseFeeVaultWithdrawalNetwork: cfg.baseFeeVaultWithdrawalNetwork(), + l1FeeVaultRecipient: cfg.l1FeeVaultRecipient(), + l1FeeVaultMinimumWithdrawalAmount: cfg.l1FeeVaultMinimumWithdrawalAmount(), + l1FeeVaultWithdrawalNetwork: cfg.l1FeeVaultWithdrawalNetwork(), + operatorFeeVaultRecipient: cfg.operatorFeeVaultRecipient(), + operatorFeeVaultMinimumWithdrawalAmount: cfg.operatorFeeVaultMinimumWithdrawalAmount(), + operatorFeeVaultWithdrawalNetwork: cfg.operatorFeeVaultWithdrawalNetwork(), fork: uint256(l2Fork), - fundDevAccounts: deploy.cfg().fundDevAccounts() + fundDevAccounts: cfg.fundDevAccounts() }) ); From de512bd945c8d4db963a6c173dd65751288c0491 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 16:02:06 -0400 Subject: [PATCH 119/135] Refactor Counter contract: initialize count variable without a default value, and enhance input validation in incrementPayable function by requiring a positive value for transactions. --- test/universal/Counter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/universal/Counter.sol b/test/universal/Counter.sol index 65bb4e18..5517cf5c 100644 --- a/test/universal/Counter.sol +++ b/test/universal/Counter.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.25; contract Counter { address internal immutable OWNER; - uint256 public count = 0; + uint256 public count; constructor(address owner) { OWNER = owner; @@ -16,7 +16,7 @@ contract Counter { } function incrementPayable() external payable { - require(msg.value != 0, "value must be greater than 0"); + require(msg.value > 0, "value must be greater than 0"); count += 1; } } From a3d2de03506ca83b457280cefeb8d2d190580e0e Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 16:07:56 -0400 Subject: [PATCH 120/135] Refactor CrossDomainMessenger tests: enhance message relay functionality by introducing immutable variables for improved gas efficiency, streamline nonce handling with a dedicated function, and improve clarity in assertions for message status checks. --- test/universal/CrossDomainMessenger.t.sol | 166 ++++++++++------------ 1 file changed, 73 insertions(+), 93 deletions(-) diff --git a/test/universal/CrossDomainMessenger.t.sol b/test/universal/CrossDomainMessenger.t.sol index 5efd4bad..bcd09724 100644 --- a/test/universal/CrossDomainMessenger.t.sol +++ b/test/universal/CrossDomainMessenger.t.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.15; // Testing utilities import { Test } from "lib/forge-std/src/Test.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; +import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; // Libraries import { Math } from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; @@ -17,9 +18,9 @@ import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.s /// @notice A mock external contract called via the SafeCall inside the CrossDomainMessenger's /// `relayMessage` function. contract CrossDomainMessenger_ExternalRelay_Harness is Test { - address internal op; + address internal immutable op; address internal fuzzedSender; - IL1CrossDomainMessenger internal l1CrossDomainMessenger; + IL1CrossDomainMessenger internal immutable l1CrossDomainMessenger; event FailedRelayedMessage(bytes32 indexed msgHash); @@ -28,19 +29,12 @@ contract CrossDomainMessenger_ExternalRelay_Harness is Test { op = _op; } - /// @notice Internal helper function to relay a message and perform assertions. - function _internalRelay(address _innerSender) internal { + function _internalRelay(address _innerSender, bytes memory _callMessage) internal { address initialSender = l1CrossDomainMessenger.xDomainMessageSender(); - - bytes memory callMessage = getCallData(); + uint256 nonce = Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }); bytes32 hash = Hashing.hashCrossDomainMessage({ - _nonce: Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), - _sender: _innerSender, - _target: address(this), - _value: 0, - _gasLimit: 0, - _data: callMessage + _nonce: nonce, _sender: _innerSender, _target: address(this), _value: 0, _gasLimit: 0, _data: _callMessage }); vm.expectEmit(true, true, true, true); @@ -48,12 +42,12 @@ contract CrossDomainMessenger_ExternalRelay_Harness is Test { vm.prank(address(op)); l1CrossDomainMessenger.relayMessage({ - _nonce: Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), + _nonce: nonce, _sender: _innerSender, _target: address(this), _value: 0, _minGasLimit: 0, - _message: callMessage + _message: _callMessage }); assertTrue(l1CrossDomainMessenger.failedMessages(hash)); @@ -63,21 +57,21 @@ contract CrossDomainMessenger_ExternalRelay_Harness is Test { /// @notice externalCallWithMinGas is called by the CrossDomainMessenger. function externalCallWithMinGas() external payable { + bytes memory callMessage = getCallData(); + for (uint256 i = 0; i < 10; i++) { address _innerSender; unchecked { _innerSender = address(uint160(uint256(uint160(fuzzedSender)) + i)); } - _internalRelay(_innerSender); + _internalRelay(_innerSender, callMessage); } } - /// @notice Helper function to get the callData for an `externalCallWithMinGas function getCallData() public pure returns (bytes memory) { return abi.encodeCall(CrossDomainMessenger_ExternalRelay_Harness.externalCallWithMinGas, ()); } - /// @notice Helper function to set the fuzzed sender function setFuzzedSender(address _fuzzedSender) public { fuzzedSender = _fuzzedSender; } @@ -86,15 +80,26 @@ contract CrossDomainMessenger_ExternalRelay_Harness is Test { /// @title CrossDomainMessenger_TestInit /// @notice Reusable test initialization for `CrossDomainMessenger` tests. abstract contract CrossDomainMessenger_TestInit is CommonTest { - // Storage slot of the l2Sender - uint256 constant senderSlotIndex = 50; - CrossDomainMessenger_ExternalRelay_Harness public er; function setUp() public override { super.setUp(); er = new CrossDomainMessenger_ExternalRelay_Harness(l1CrossDomainMessenger, address(optimismPortal2)); } + + function _versionedNonce(uint16 _version) internal pure returns (uint256) { + return Encoding.encodeVersionedNonce({ _nonce: 0, _version: _version }); + } + + function _setPortalL2Sender(address _sender) internal { + StorageSlot memory senderSlot = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender"); + vm.store(address(optimismPortal2), bytes32(senderSlot.slot), bytes32(abi.encode(_sender))); + } + + function _assertMessageStatus(bytes32 _hash, bool _successful, bool _failed) internal view { + assertEq(l1CrossDomainMessenger.successfulMessages(_hash), _successful); + assertEq(l1CrossDomainMessenger.failedMessages(_hash), _failed); + } } /// @title CrossDomainMessenger_RelayMessage_Test @@ -118,32 +123,20 @@ contract CrossDomainMessenger_RelayMessage_Test is CrossDomainMessenger_TestInit vm.expectCall(target, callMessage); uint64 gasLimit = uint64(bound(_gasLimit, 0, 30_000_000)); + uint256 nonce = _versionedNonce(1); bytes32 hash = Hashing.hashCrossDomainMessage({ - _nonce: Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), - _sender: sender, - _target: target, - _value: 0, - _gasLimit: gasLimit, - _data: callMessage + _nonce: nonce, _sender: sender, _target: target, _value: 0, _gasLimit: gasLimit, _data: callMessage }); - // Set the value of `op.l2Sender()` to be the L2 Cross Domain Messenger. - vm.store(address(optimismPortal2), bytes32(senderSlotIndex), bytes32(abi.encode(sender))); + _setPortalL2Sender(sender); vm.prank(address(optimismPortal2)); l1CrossDomainMessenger.relayMessage({ - _nonce: Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), - _sender: sender, - _target: target, - _value: 0, - _minGasLimit: gasLimit, - _message: callMessage + _nonce: nonce, _sender: sender, _target: target, _value: 0, _minGasLimit: gasLimit, _message: callMessage }); - assertTrue(l1CrossDomainMessenger.successfulMessages(hash)); - assertEq(l1CrossDomainMessenger.failedMessages(hash), false); + _assertMessageStatus(hash, true, false); - // Ensures that the `xDomainMsgSender` is set back to `Predeploys.L2_CROSS_DOMAIN_MESSENGER` vm.expectRevert("CrossDomainMessenger: xDomainMessageSender is not set"); l1CrossDomainMessenger.xDomainMessageSender(); } @@ -153,6 +146,40 @@ contract CrossDomainMessenger_RelayMessage_Test is CrossDomainMessenger_TestInit /// L2 CrossDomainMessenger contracts. For simplicity, we use the L1 Messenger as the test /// contract. contract CrossDomainMessenger_BaseGas_Test is CommonTest { + function _totalMessageSize(uint256 _messageLength) internal view returns (uint64) { + return uint64(_messageLength + l1CrossDomainMessenger.ENCODING_OVERHEAD()); + } + + function _executionGas(uint32 _minGasLimit) internal view returns (uint64) { + return uint64( + l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD() + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + + l1CrossDomainMessenger.RELAY_RESERVED_GAS() + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER() + + ((_minGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR()) + / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()) + ); + } + + function _floorGas(uint256 _messageLength) internal view returns (uint64) { + return l1CrossDomainMessenger.TX_BASE_GAS() + + (_totalMessageSize(_messageLength) * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD()); + } + + function _executionGasWithOverhead(uint256 _messageLength, uint32 _minGasLimit) internal view returns (uint64) { + return l1CrossDomainMessenger.TX_BASE_GAS() + _executionGas(_minGasLimit) + + (_totalMessageSize(_messageLength) * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD()); + } + + function _baseGasMinimum(uint256 _messageLength, uint32 _minGasLimit) internal view returns (uint64) { + uint64 totalMessageSize = _totalMessageSize(_messageLength); + return l1CrossDomainMessenger.TX_BASE_GAS() + + uint64( + Math.max( + _executionGas(_minGasLimit) + (totalMessageSize * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD()), + totalMessageSize * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD() + ) + ); + } + /// @notice Ensure that `baseGas` passes for the max value of `_minGasLimit`, this is about /// 4 Billion. function test_baseGas_succeeds() external view { @@ -179,50 +206,26 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest { /// @notice Test that `baseGas` returns at least the floor cost for calldata function test_baseGas_floor_succeeds() external view { - // Create a message large enough that the floor cost would be higher than the execution gas bytes memory largeMessage = new bytes(100_000); uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0); - // Calculate the expected floor cost - uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS() - + (uint64(largeMessage.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) - * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD()); - - // Verify that the result is at least the floor cost - assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost"); + assertGe(baseGasResult, _floorGas(largeMessage.length), "baseGas should return at least the floor cost"); } /// @notice Test that `baseGas` returns the execution gas when it's higher than the floor cost function test_baseGas_executionGas_succeeds() external view { - // Create a small message where execution gas would be higher than floor cost bytes memory smallMessage = new bytes(10); uint32 highGasLimit = 1_000_000; uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit); + uint64 expectedExecutionGasWithOverhead = _executionGasWithOverhead(smallMessage.length, highGasLimit); - // Calculate the expected floor cost - uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS() - + (uint64(smallMessage.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) - * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD()); - - // Calculate the expected execution gas (simplified version of what's in the contract) - uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD() - + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS() - + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER() - + ((highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR()) - / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()); - - uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas - + (uint64(smallMessage.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) - * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD()); - - // Verify that the result is the execution gas (which should be higher than floor cost) - assertTrue( - baseGasResult >= expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas" - ); - assertTrue( - expectedExecutionGasWithOverhead > floorCost, "Execution gas should be higher than floor cost for this test" + assertGe(baseGasResult, expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas"); + assertGt( + expectedExecutionGasWithOverhead, + _floorGas(smallMessage.length), + "Execution gas should be higher than floor cost for this test" ); } @@ -232,31 +235,8 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest { /// @param _minGasLimit The minimum gas limit to test function testFuzz_baseGas_maxLogic_succeeds(bytes calldata _message, uint32 _minGasLimit) external view { uint64 baseGasResult = l1CrossDomainMessenger.baseGas(_message, _minGasLimit); + uint64 expectedMinimum = _baseGasMinimum(_message.length, _minGasLimit); - // Calculate the expected execution gas - uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD() - + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS() - + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER() - + ((_minGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR()) - / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR()); - - uint64 executionGasWithOverhead = executionGas - + (uint64(_message.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) - * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD()); - - // The result should be at least the maximum of the two calculations - uint64 expectedMinimum = uint64( - Math.max( - executionGasWithOverhead, - uint64(_message.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) - * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD() - ) - ); - expectedMinimum += l1CrossDomainMessenger.TX_BASE_GAS(); - - assertTrue( - baseGasResult >= expectedMinimum, - "baseGas should return at least the maximum of execution gas and floor cost" - ); + assertGe(baseGasResult, expectedMinimum, "baseGas should return at least the max gas path"); } } From 474f4ae70034c9827b283bcd5c7e08d67d47523c Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 16:14:48 -0400 Subject: [PATCH 121/135] Refactor ExtendedPause tests: streamline pause and unpause logic by introducing helper functions for clarity, enhance assertion checks for system pause state, and improve function visibility for better maintainability. --- test/universal/ExtendedPause.t.sol | 37 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/test/universal/ExtendedPause.t.sol b/test/universal/ExtendedPause.t.sol index 16dc061a..19e2b171 100644 --- a/test/universal/ExtendedPause.t.sol +++ b/test/universal/ExtendedPause.t.sol @@ -9,34 +9,35 @@ import { CommonTest } from "test/setup/CommonTest.sol"; /// that the behavior is consistent. contract ExtendedPause_Test is CommonTest { /// @notice Tests that other contracts are paused when the superchain config is paused - function test_pause_fullSystem_succeeds() public { - assertFalse(superchainConfig.paused(address(0))); + function test_pause_fullSystem_succeeds() external { + _assertFullSystemPaused(false); - vm.prank(superchainConfig.guardian()); - superchainConfig.pause(address(0)); + _pauseSuperchain(); - // validate the paused state - assertTrue(superchainConfig.paused(address(0))); - assertTrue(optimismPortal2.paused()); - assertTrue(l1CrossDomainMessenger.paused()); - assertTrue(l1StandardBridge.paused()); - assertTrue(l1ERC721Bridge.paused()); + _assertFullSystemPaused(true); } /// @notice Tests that other contracts are unpaused when the superchain config is paused and /// then unpaused. function test_unpause_fullSystem_succeeds() external { - // first use the test above to pause the system - test_pause_fullSystem_succeeds(); + _pauseSuperchain(); vm.prank(superchainConfig.guardian()); superchainConfig.unpause(address(0)); - // validate the unpaused state - assertFalse(superchainConfig.paused(address(0))); - assertFalse(optimismPortal2.paused()); - assertFalse(l1CrossDomainMessenger.paused()); - assertFalse(l1StandardBridge.paused()); - assertFalse(l1ERC721Bridge.paused()); + _assertFullSystemPaused(false); + } + + function _pauseSuperchain() internal { + vm.prank(superchainConfig.guardian()); + superchainConfig.pause(address(0)); + } + + function _assertFullSystemPaused(bool _paused) internal view { + assertEq(superchainConfig.paused(address(0)), _paused); + assertEq(optimismPortal2.paused(), _paused); + assertEq(l1CrossDomainMessenger.paused(), _paused); + assertEq(l1StandardBridge.paused(), _paused); + assertEq(l1ERC721Bridge.paused(), _paused); } } From 7246c2af7d14d446ccf4ab831abddedd6de29903 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 16:22:07 -0400 Subject: [PATCH 122/135] Refactor MultisigScript tests: simplify counter initialization, correct balance assertion message, and enhance signature handling with dedicated helper functions for improved clarity and maintainability. --- test/universal/MultisigScript.t.sol | 167 +++++++++++----------------- 1 file changed, 63 insertions(+), 104 deletions(-) diff --git a/test/universal/MultisigScript.t.sol b/test/universal/MultisigScript.t.sol index d9d89f3e..82baf76b 100644 --- a/test/universal/MultisigScript.t.sol +++ b/test/universal/MultisigScript.t.sol @@ -20,7 +20,7 @@ contract MultisigScriptTest is Test, MultisigScript { Vm.Wallet internal wallet3 = vm.createWallet("3"); address internal safe = address(1001); - Counter internal counter = new Counter(address(safe)); + Counter internal counter = new Counter(safe); /// @dev Controls whether to use hash-based or EIP-712 JSON output. True by default. bool internal _useDataHashes = true; @@ -58,7 +58,7 @@ contract MultisigScriptTest is Test, MultisigScript { assertEq(counterValue, 6, "Counter value is not 6"); uint256 counterBalance = address(counter).balance; - assertEq(counterBalance, 3 ether, "Counter balance is not 1 ether"); + assertEq(counterBalance, 3 ether, "Counter balance is not 3 ether"); } /// @inheritdoc MultisigScript @@ -94,12 +94,7 @@ contract MultisigScriptTest is Test, MultisigScript { Call[] memory calls = new Call[](4); - calls[0] = Call({ - operation: Enum.Operation.Call, - target: address(counter), - data: abi.encodeCall(Counter.increment, ()), - value: 0 - }); + calls[0] = counterIncrementCall; // Use multicall to test the delegatecall use case calls[1] = Call({ @@ -109,12 +104,7 @@ contract MultisigScriptTest is Test, MultisigScript { value: 0 }); - calls[2] = Call({ - operation: Enum.Operation.Call, - target: address(counter), - data: abi.encodeCall(Counter.incrementPayable, ()), - value: 1 ether - }); + calls[2] = counterIncrementCallPayable; calls[3] = Call({ operation: Enum.Operation.DelegateCall, @@ -128,12 +118,10 @@ contract MultisigScriptTest is Test, MultisigScript { /// @inheritdoc MultisigScript function _ownerSafe() internal view override returns (address) { - return address(safe); + return safe; } /// @inheritdoc MultisigScript - /// - /// @dev Returns `_useDataHashes` which is true by default (hash-based signing). function _printDataHashes() internal view override returns (bool) { return _useDataHashes; } @@ -145,6 +133,16 @@ contract MultisigScriptTest is Test, MultisigScript { return _encodeTransactionData(_ownerSafe(), _buildAggregatedScriptCall({ scriptCalls: _buildCalls() })); } + function _signDigest(Vm.Wallet memory wallet, bytes32 digest) internal returns (bytes memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet, digest); + return abi.encodePacked(r, s, v); + } + + function _lastLoggedBytes() internal returns (bytes memory) { + Vm.Log[] memory logs = vm.getRecordedLogs(); + return abi.decode(logs[logs.length - 1].data, (bytes)); + } + /// @notice Tests that sign() emits the correct data to sign function test_sign() external { vm.recordLogs(); @@ -152,43 +150,33 @@ contract MultisigScriptTest is Test, MultisigScript { vm.prank(wallet1.addr); this.sign(new address[](0)); - Vm.Log[] memory logs = vm.getRecordedLogs(); - bytes memory logged = abi.decode(logs[logs.length - 1].data, (bytes)); + bytes memory logged = _lastLoggedBytes(); bytes memory expected = _expectedTxDataForCurrentBuildCalls(); - assertEq(keccak256(logged), keccak256(expected)); + assertEq(logged, expected); } /// @notice Tests that verify() accepts valid 2-of-2 signatures function test_verify_valid_signatures() external { - // Two-of-two signatures over the encoded transaction data should verify bytes32 digest = keccak256(_expectedTxDataForCurrentBuildCalls()); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(wallet1, digest); - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(wallet2, digest); - bytes memory signatures = abi.encodePacked(r1, s1, v1, r2, s2, v2); + bytes memory signatures = abi.encodePacked(_signDigest(wallet1, digest), _signDigest(wallet2, digest)); verify(new address[](0), signatures); } /// @notice Tests that verify() reverts when given an invalid signature function test_verify_reverts_with_invalid_signature() external { - // One valid, one invalid should revert bytes32 digest = keccak256(_expectedTxDataForCurrentBuildCalls()); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(wallet1, digest); - bytes memory signatures = abi.encodePacked(r1, s1, v1, bytes32(0), bytes32(0), uint8(27)); - bytes memory callData = abi.encodeCall(this.verify, (new address[](0), signatures)); - (bool success, bytes memory ret) = address(this).call(callData); - assertFalse(success); - assertTrue(ret.length > 0); + bytes memory signatures = abi.encodePacked(_signDigest(wallet1, digest), bytes32(0), bytes32(0), uint8(27)); + + vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args + this.verify(new address[](0), signatures); } /// @notice Tests that simulate() executes the transaction without broadcasting function test_simulate_only() external { bytes32 digest = keccak256(_expectedTxDataForCurrentBuildCalls()); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(wallet1, digest); - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(wallet2, digest); - bytes memory signatures = abi.encodePacked(r1, s1, v1, r2, s2, v2); + bytes memory signatures = abi.encodePacked(_signDigest(wallet1, digest), _signDigest(wallet2, digest)); - // Simulate should execute successfully and satisfy _postCheck simulate(signatures); } @@ -196,13 +184,9 @@ contract MultisigScriptTest is Test, MultisigScript { /// /// @dev Safe is 2/3, but we provide all 3 signatures function test_run_with_more_signatures_than_threshold() external { - // Sign with all 3 owners (threshold is 2, but we provide 3) bytes32 digest = keccak256(_expectedTxDataForCurrentBuildCalls()); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(wallet1, digest); - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(wallet2, digest); - (uint8 v3, bytes32 r3, bytes32 s3) = vm.sign(wallet3, digest); - - bytes memory signatures = abi.encodePacked(r1, s1, v1, r2, s2, v2, r3, s3, v3); + bytes memory signatures = + abi.encodePacked(_signDigest(wallet1, digest), _signDigest(wallet2, digest), _signDigest(wallet3, digest)); run(signatures); } @@ -217,10 +201,8 @@ contract MultisigScriptTest is Test, MultisigScript { vm.prank(wallet1.addr); this.sign(new address[](0)); - Vm.Log[] memory logs = vm.getRecordedLogs(); - bytes memory logged = abi.decode(logs[logs.length - 1].data, (bytes)); + bytes memory logged = _lastLoggedBytes(); - // Verify the logged data contains EIP-712 JSON structure markers string memory loggedStr = string(logged); assertTrue(LibString.contains(loggedStr, "EIP712Domain"), "EIP-712 output should contain EIP712Domain"); assertTrue(LibString.contains(loggedStr, "SafeTx"), "EIP-712 output should contain SafeTx type"); @@ -280,12 +262,24 @@ abstract contract MultisigScriptNestedBase is Test, MultisigScript { } function _assertSignOutput(address[] memory safes, bytes memory dataToSign) internal { - bytes memory txData = abi.encodeWithSelector(this.sign.selector, safes); - (bool success,) = address(this).call(txData); - assertTrue(success); + this.sign(safes); + assertEq(_lastLoggedBytes(), dataToSign); + } + + function _signData(Vm.Wallet memory wallet, bytes memory dataToSign) internal returns (bytes memory) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet, keccak256(dataToSign)); + return abi.encodePacked(r, s, v); + } + + function _approveFrom(address signerSafe, Vm.Wallet memory wallet) internal { + (address[] memory safes, bytes memory dataToSign) = _getSignerData(signerSafe); + approve(safes, _signData(wallet, dataToSign)); + } + + function _lastLoggedBytes() internal returns (bytes memory) { Vm.Log[] memory logs = vm.getRecordedLogs(); - assertEq(keccak256(logs[logs.length - 1].data), keccak256(abi.encode(dataToSign))); + return abi.decode(logs[logs.length - 1].data, (bytes)); } function _signerSafes(address signerSafe) internal view virtual returns (address[] memory safes); @@ -299,7 +293,7 @@ contract MultisigScriptNestedTest is MultisigScriptNestedBase { vm.etch(safe2, safeCode); vm.etch(safe3, safeCode); - nestedCounter = new Counter(address(safe3)); + nestedCounter = new Counter(safe3); _setupLeafSafes(); @@ -339,55 +333,37 @@ contract MultisigScriptNestedTest is MultisigScriptNestedBase { /// @notice Tests that approve() succeeds with valid signature from safe1 function test_approve_safe1() external { - (address[] memory safes, bytes memory dataToSign) = _getSignerData(safe1); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(nestedWallet1, keccak256(dataToSign)); - approve(safes, abi.encodePacked(r, s, v)); + _approveFrom(safe1, nestedWallet1); } /// @notice Tests that approve() succeeds with valid signature from safe2 function test_approve_safe2() external { - (address[] memory safes, bytes memory dataToSign) = _getSignerData(safe2); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(nestedWallet2, keccak256(dataToSign)); - approve(safes, abi.encodePacked(r, s, v)); + _approveFrom(safe2, nestedWallet2); } /// @notice Tests that approve() fails when signature doesn't match the safe function test_approve_notOwner() external { (, bytes memory dataToSign) = _getSignerData(safe1); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(nestedWallet1, keccak256(dataToSign)); - (address[] memory safes2,) = _getSignerData(safe2); + bytes memory signature = _signData(nestedWallet1, dataToSign); - bytes memory data = abi.encodeCall(this.approve, (safes2, abi.encodePacked(r, s, v))); - (bool success, bytes memory result) = address(this).call(data); - assertFalse(success); - assertEq(result, abi.encodeWithSignature("Error(string)", "not enough signatures")); + vm.expectRevert("not enough signatures"); + this.approve(_signerSafes(safe2), signature); } /// @notice Tests the full flow: approve from both safes, then run function test_run() external { - (address[] memory safes1, bytes memory dataToSign1) = _getSignerData(safe1); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(nestedWallet1, keccak256(dataToSign1)); - - (address[] memory safes2, bytes memory dataToSign2) = _getSignerData(safe2); - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(nestedWallet2, keccak256(dataToSign2)); - - approve(safes1, abi.encodePacked(r1, s1, v1)); - approve(safes2, abi.encodePacked(r2, s2, v2)); + _approveFrom(safe1, nestedWallet1); + _approveFrom(safe2, nestedWallet2); run(""); } /// @notice Tests that run() fails when not all nested safes have approved function test_run_notApproved() external { - (address[] memory safes1, bytes memory dataToSign) = _getSignerData(safe1); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(nestedWallet1, keccak256(dataToSign)); + _approveFrom(safe1, nestedWallet1); - approve(safes1, abi.encodePacked(r1, s1, v1)); - - bytes memory data = abi.encodeCall(this.run, ("")); - (bool success, bytes memory result) = address(this).call(data); - assertFalse(success); - assertEq(result, abi.encodeWithSignature("Error(string)", "not enough signatures")); + vm.expectRevert("not enough signatures"); + this.run(""); } } @@ -400,7 +376,7 @@ contract MultisigScriptDoubleNestedTest is MultisigScriptNestedBase { vm.etch(safe3, safeCode); vm.etch(safe4, safeCode); - nestedCounter = new Counter(address(safe4)); + nestedCounter = new Counter(safe4); _setupLeafSafes(); @@ -432,43 +408,26 @@ contract MultisigScriptDoubleNestedTest is MultisigScriptNestedBase { _assertSignOutput(safes, dataToSign); } - /// @notice Tests the approval flow through all nested levels - function test_runInit_double_nested() external { - (address[] memory safes1, bytes memory dataToSign1) = _getSignerData(safe1); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(nestedWallet1, keccak256(dataToSign1)); - - (address[] memory safes2, bytes memory dataToSign2) = _getSignerData(safe2); - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(nestedWallet2, keccak256(dataToSign2)); - - approve(safes1, abi.encodePacked(r1, s1, v1)); - approve(safes2, abi.encodePacked(r2, s2, v2)); + /// @notice Tests the intermediate approval flow through nested safes + function test_approve_double_nested() external { + _approveFrom(safe1, nestedWallet1); + _approveFrom(safe2, nestedWallet2); approve(_toArray(safe3), ""); } /// @notice Tests that intermediate approve fails when not all leaf safes have approved function test_runInit_double_nested_notApproved() external { - (address[] memory safes1, bytes memory dataToSign) = _getSignerData(safe1); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(nestedWallet1, keccak256(dataToSign)); - - approve(safes1, abi.encodePacked(r1, s1, v1)); + _approveFrom(safe1, nestedWallet1); - bytes memory data = abi.encodeCall(this.approve, (_toArray(safe3), "")); - (bool success, bytes memory result) = address(this).call(data); - assertFalse(success); - assertEq(result, abi.encodeWithSignature("Error(string)", "not enough signatures")); + vm.expectRevert("not enough signatures"); + this.approve(_toArray(safe3), ""); } /// @notice Tests the full flow: approve from all nested safes, then run function test_run_double_nested() external { - (address[] memory safes1, bytes memory dataToSign1) = _getSignerData(safe1); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(nestedWallet1, keccak256(dataToSign1)); - - (address[] memory safes2, bytes memory dataToSign2) = _getSignerData(safe2); - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(nestedWallet2, keccak256(dataToSign2)); - - approve(safes1, abi.encodePacked(r1, s1, v1)); - approve(safes2, abi.encodePacked(r2, s2, v2)); + _approveFrom(safe1, nestedWallet1); + _approveFrom(safe2, nestedWallet2); approve(_toArray(safe3), ""); run(""); From 1b547033a3874d05a04ac7ca57010c9e770ace24 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 16:31:27 -0400 Subject: [PATCH 123/135] Refactor MultisigScriptDeposit tests: streamline deposit transaction assertions by introducing helper functions for L2 call verification, enhance clarity in test logic, and improve consistency in handling single and multiple L2 calls. --- test/universal/MultisigScriptDeposit.t.sol | 169 +++++++++------------ 1 file changed, 69 insertions(+), 100 deletions(-) diff --git a/test/universal/MultisigScriptDeposit.t.sol b/test/universal/MultisigScriptDeposit.t.sol index 1874925e..0329a21c 100644 --- a/test/universal/MultisigScriptDeposit.t.sol +++ b/test/universal/MultisigScriptDeposit.t.sol @@ -4,9 +4,10 @@ pragma solidity 0.8.25; import { ICBMulticall, Call3Value } from "interfaces/universal/ICBMulticall.sol"; import { Test } from "lib/forge-std/src/Test.sol"; import { Vm } from "lib/forge-std/src/Vm.sol"; +import { Bytes } from "src/libraries/Bytes.sol"; import { Preinstalls } from "src/libraries/Preinstalls.sol"; -import { MultisigScriptDeposit } from "scripts/universal/MultisigScriptDeposit.sol"; +import { IOptimismPortal2, MultisigScriptDeposit } from "scripts/universal/MultisigScriptDeposit.sol"; import { Simulation } from "scripts/universal/Simulation.sol"; import { IGnosisSafe } from "scripts/universal/IGnosisSafe.sol"; @@ -55,21 +56,15 @@ contract MultisigScriptDepositTest is Test, MultisigScriptDeposit { MockOptimismPortal internal portal; Counter internal l2Counter; - // Test configuration - address internal testL2Target; uint64 internal testGasLimit = 200_000; function() internal view returns (Call3Value[] memory) buildL2CallsInternal; function setUp() public { - // Deploy mock portal portal = new MockOptimismPortal(); - // Deploy a counter to use as L2 target (for calldata encoding) l2Counter = new Counter(address(this)); - testL2Target = address(l2Counter); - // Setup Safe vm.etch(safe, Preinstalls.getDeployedCode(Preinstalls.Safe_v130, block.chainid)); vm.etch(Preinstalls.MultiCall3, Preinstalls.getDeployedCode(Preinstalls.MultiCall3, block.chainid)); vm.deal(safe, 100 ether); @@ -114,24 +109,8 @@ contract MultisigScriptDepositTest is Test, MultisigScriptDeposit { Call[] memory calls = _buildCalls(); - // Should produce exactly one L1 call to the portal - assertEq(calls.length, 1, "Should have one L1 call"); - assertEq(calls[0].target, address(portal), "Target should be portal"); - assertEq(calls[0].value, 0, "Value should be 0"); - - // Decode the depositTransaction call - (address to, uint256 value, uint64 gasLimit, bool isCreation, bytes memory data) = - _decodeDepositTransaction(calls[0].data); - - assertEq(to, CB_MULTICALL, "L2 target should be CB_MULTICALL"); - assertEq(value, 0, "Bridged value should be 0"); - assertEq(gasLimit, testGasLimit, "Gas limit should match"); - assertFalse(isCreation, "Should not be creation"); - assertTrue(data.length > 0, "Data should not be empty"); - - // Verify the L2 data is an aggregate3Value call - bytes4 selector = bytes4(data); - assertEq(selector, ICBMulticall.aggregate3Value.selector, "Should be aggregate3Value call"); + bytes memory l2Data = _assertSinglePortalDeposit(calls, 0); + assertEq(bytes4(l2Data), ICBMulticall.aggregate3Value.selector, "Should be aggregate3Value call"); } /// @notice Test that multiple L2 calls are batched correctly @@ -140,28 +119,13 @@ contract MultisigScriptDepositTest is Test, MultisigScriptDeposit { Call[] memory calls = _buildCalls(); - // Should still produce exactly one L1 call - assertEq(calls.length, 1, "Should have one L1 call"); - - // Decode and verify - (address to, uint256 value, uint64 gasLimit, bool isCreation, bytes memory data) = - _decodeDepositTransaction(calls[0].data); - - assertEq(to, CB_MULTICALL, "L2 target should be CB_MULTICALL"); - assertEq(value, 0, "Bridged value should be 0"); - assertEq(gasLimit, testGasLimit, "Gas limit should match"); - assertFalse(isCreation, "Should not be creation"); - - // Decode the aggregate3Value call to verify multiple L2 calls are included - Call3Value[] memory l2Calls = abi.decode(_stripSelector(data), (Call3Value[])); + Call3Value[] memory l2Calls = _decodeAggregate3Value(_assertSinglePortalDeposit(calls, 0)); assertEq(l2Calls.length, 3, "Should have 3 L2 calls"); - // Verify call parameters are preserved through the wrapping - assertEq(l2Calls[0].target, testL2Target, "First call target should be preserved"); - assertEq(l2Calls[1].target, testL2Target, "Second call target should be preserved"); - assertEq(l2Calls[2].target, testL2Target, "Third call target should be preserved"); + assertEq(l2Calls[0].target, address(l2Counter), "First call target should be preserved"); + assertEq(l2Calls[1].target, address(l2Counter), "Second call target should be preserved"); + assertEq(l2Calls[2].target, address(l2Counter), "Third call target should be preserved"); - // Verify allowFailure flags are preserved (first two are false, third is true) assertFalse(l2Calls[0].allowFailure, "First call allowFailure should be false"); assertFalse(l2Calls[1].allowFailure, "Second call allowFailure should be false"); assertTrue(l2Calls[2].allowFailure, "Third call allowFailure should be true (preserved)"); @@ -173,12 +137,7 @@ contract MultisigScriptDepositTest is Test, MultisigScriptDeposit { Call[] memory calls = _buildCalls(); - // Value should be sum of all L2 call values (1 + 2 + 0.5 = 3.5 ether) - assertEq(calls[0].value, 3.5 ether, "L1 call value should be sum of L2 values"); - - // Decode and verify bridged value - (, uint256 value,,,) = _decodeDepositTransaction(calls[0].data); - assertEq(value, 3.5 ether, "Bridged value should be 3.5 ether"); + _assertSinglePortalDeposit(calls, 3.5 ether); } /// @notice Test that single L2 call still goes through multicall (consistent behavior) @@ -186,11 +145,9 @@ contract MultisigScriptDepositTest is Test, MultisigScriptDeposit { buildL2CallsInternal = _buildSingleL2CallNoValue; Call[] memory calls = _buildCalls(); - (,,,, bytes memory data) = _decodeDepositTransaction(calls[0].data); + bytes memory l2Data = _assertSinglePortalDeposit(calls, 0); - // Even single calls should be wrapped in aggregate3Value for consistency - bytes4 selector = bytes4(data); - assertEq(selector, ICBMulticall.aggregate3Value.selector, "Single call should still use aggregate3Value"); + assertEq(bytes4(l2Data), ICBMulticall.aggregate3Value.selector, "Single call should still use aggregate3Value"); } /// @notice Test the full sign flow with deposit transaction @@ -198,32 +155,16 @@ contract MultisigScriptDepositTest is Test, MultisigScriptDeposit { buildL2CallsInternal = _buildSingleL2CallNoValue; vm.recordLogs(); - bytes memory txData = abi.encodeWithSelector(this.sign.selector, new address[](0)); - vm.prank(wallet1.addr); - (bool success,) = address(this).call(txData); - assertTrue(success, "Sign should succeed"); + _signFrom(wallet1.addr); - // Verify DataToSign event was emitted - Vm.Log[] memory logs = vm.getRecordedLogs(); - bool foundDataToSign = false; - for (uint256 i; i < logs.length; i++) { - if (logs[i].topics[0] == keccak256("DataToSign(bytes)")) { - foundDataToSign = true; - break; - } - } - assertTrue(foundDataToSign, "DataToSign event should be emitted"); + assertEq(_lastLoggedBytes(), _expectedTxDataForCurrentBuildCalls()); } /// @notice Test sign flow with ETH value function test_sign_depositTransaction_withValue() external { buildL2CallsInternal = _buildL2CallsWithValue; - vm.recordLogs(); - bytes memory txData = abi.encodeWithSelector(this.sign.selector, new address[](0)); - vm.prank(wallet1.addr); - (bool success,) = address(this).call(txData); - assertTrue(success, "Sign with value should succeed"); + _signFrom(wallet1.addr); } /// @notice Test default _optimismPortal() returns correct address for mainnet @@ -260,70 +201,98 @@ contract MultisigScriptDepositTest is Test, MultisigScriptDeposit { function _buildSingleL2CallNoValue() internal view returns (Call3Value[] memory) { Call3Value[] memory calls = new Call3Value[](1); calls[0] = Call3Value({ - target: testL2Target, allowFailure: false, callData: abi.encodeCall(Counter.increment, ()), value: 0 + target: address(l2Counter), allowFailure: false, callData: abi.encodeCall(Counter.increment, ()), value: 0 }); return calls; } function _buildMultipleL2CallsNoValue() internal view returns (Call3Value[] memory) { + bytes memory incrementCall = abi.encodeCall(Counter.increment, ()); Call3Value[] memory calls = new Call3Value[](3); - calls[0] = Call3Value({ - target: testL2Target, allowFailure: false, callData: abi.encodeCall(Counter.increment, ()), value: 0 - }); - calls[1] = Call3Value({ - target: testL2Target, allowFailure: false, callData: abi.encodeCall(Counter.increment, ()), value: 0 - }); + calls[0] = Call3Value({ target: address(l2Counter), allowFailure: false, callData: incrementCall, value: 0 }); + calls[1] = Call3Value({ target: address(l2Counter), allowFailure: false, callData: incrementCall, value: 0 }); calls[2] = Call3Value({ - target: testL2Target, + target: address(l2Counter), allowFailure: true, // Test allowFailure flag preservation - callData: abi.encodeCall(Counter.increment, ()), + callData: incrementCall, value: 0 }); return calls; } function _buildL2CallsWithValue() internal view returns (Call3Value[] memory) { + bytes memory incrementPayableCall = abi.encodeCall(Counter.incrementPayable, ()); Call3Value[] memory calls = new Call3Value[](3); calls[0] = Call3Value({ - target: testL2Target, - allowFailure: false, - callData: abi.encodeCall(Counter.incrementPayable, ()), - value: 1 ether + target: address(l2Counter), allowFailure: false, callData: incrementPayableCall, value: 1 ether }); calls[1] = Call3Value({ - target: testL2Target, - allowFailure: false, - callData: abi.encodeCall(Counter.incrementPayable, ()), - value: 2 ether + target: address(l2Counter), allowFailure: false, callData: incrementPayableCall, value: 2 ether }); calls[2] = Call3Value({ - target: testL2Target, - allowFailure: false, - callData: abi.encodeCall(Counter.incrementPayable, ()), - value: 0.5 ether + target: address(l2Counter), allowFailure: false, callData: incrementPayableCall, value: 0.5 ether }); return calls; } + function _assertSinglePortalDeposit( + Call[] memory calls, + uint256 expectedValue + ) + internal + view + returns (bytes memory l2Data) + { + assertEq(calls.length, 1, "Should have one L1 call"); + assertEq(calls[0].target, address(portal), "Target should be portal"); + assertEq(calls[0].value, expectedValue, "L1 call value should match bridged value"); + + (address to, uint256 value, uint64 gasLimit, bool isCreation, bytes memory data) = + _decodeDepositTransaction(calls[0].data); + + assertEq(to, CB_MULTICALL, "L2 target should be CB_MULTICALL"); + assertEq(value, expectedValue, "Bridged value should match"); + assertEq(gasLimit, testGasLimit, "Gas limit should match"); + assertFalse(isCreation, "Should not be creation"); + + return data; + } + + function _expectedTxDataForCurrentBuildCalls() internal view returns (bytes memory) { + return _encodeTransactionData(_ownerSafe(), _buildAggregatedScriptCall({ scriptCalls: _buildCalls() })); + } + + function _lastLoggedBytes() internal returns (bytes memory) { + Vm.Log[] memory logs = vm.getRecordedLogs(); + return abi.decode(logs[logs.length - 1].data, (bytes)); + } + + function _signFrom(address signer) internal { + vm.prank(signer); + this.sign(new address[](0)); + } + /// @notice Decode depositTransaction calldata function _decodeDepositTransaction(bytes memory callData) internal pure returns (address to, uint256 value, uint64 gasLimit, bool isCreation, bytes memory data) { - // Skip the 4-byte selector bytes memory params = _stripSelector(callData); + require(bytes4(callData) == IOptimismPortal2.depositTransaction.selector, "Unexpected deposit selector"); (to, value, gasLimit, isCreation, data) = abi.decode(params, (address, uint256, uint64, bool, bytes)); } + function _decodeAggregate3Value(bytes memory data) internal pure returns (Call3Value[] memory) { + bytes memory params = _stripSelector(data); + require(bytes4(data) == ICBMulticall.aggregate3Value.selector, "Unexpected aggregate selector"); + return abi.decode(params, (Call3Value[])); + } + /// @notice Strip the 4-byte function selector from calldata function _stripSelector(bytes memory data) internal pure returns (bytes memory) { require(data.length >= 4, "Data too short"); - bytes memory result = new bytes(data.length - 4); - for (uint256 i = 4; i < data.length; i++) { - result[i - 4] = data[i]; - } - return result; + return Bytes.slice(data, 4); } } From e79d6dd2465852fd0d3cec516202b34bff8613f8 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 16:35:39 -0400 Subject: [PATCH 124/135] Refactor OptimismMintableERC20 tests: introduce a helper function for minting to streamline test logic, enhance clarity in assertions, and remove redundant code for improved maintainability. --- test/universal/OptimismMintableERC20.t.sol | 77 +++++----------------- 1 file changed, 15 insertions(+), 62 deletions(-) diff --git a/test/universal/OptimismMintableERC20.t.sol b/test/universal/OptimismMintableERC20.t.sol index b3be8191..4b0475b9 100644 --- a/test/universal/OptimismMintableERC20.t.sol +++ b/test/universal/OptimismMintableERC20.t.sol @@ -11,16 +11,19 @@ import { IERC165 } from "lib/openzeppelin-contracts/contracts/utils/introspectio abstract contract OptimismMintableERC20_TestInit is CommonTest { event Mint(address indexed account, uint256 amount); event Burn(address indexed account, uint256 amount); + + function _bridgeMint(address _to, uint256 _amount) internal { + vm.prank(address(l2StandardBridge)); + L2Token.mint(_to, _amount); + } } /// @title OptimismMintableERC20_Permit2_Test /// @notice Tests the `permit2` function of the `OptimismMintableERC20` contract. contract OptimismMintableERC20_Permit2_Test is OptimismMintableERC20_TestInit { function test_permit2_transferFrom_succeeds() external { - vm.prank(address(l2StandardBridge)); - L2Token.mint(alice, 100); + _bridgeMint(alice, 100); - assertEq(L2Token.balanceOf(bob), 0); vm.prank(L2Token.PERMIT2()); L2Token.transferFrom(alice, bob, 100); assertEq(L2Token.balanceOf(bob), 100); @@ -42,14 +45,12 @@ contract OptimismMintableERC20_Mint_Test is OptimismMintableERC20_TestInit { vm.expectEmit(true, true, true, true); emit Mint(alice, 100); - vm.prank(address(l2StandardBridge)); - L2Token.mint(alice, 100); + _bridgeMint(alice, 100); assertEq(L2Token.balanceOf(alice), 100); } function test_mint_notBridge_reverts() external { - // NOT the bridge vm.expectRevert("OptimismMintableERC20: only bridge can mint and burn"); vm.prank(address(alice)); L2Token.mint(alice, 100); @@ -60,8 +61,7 @@ contract OptimismMintableERC20_Mint_Test is OptimismMintableERC20_TestInit { /// @notice Tests the `burn` function of the `OptimismMintableERC20` contract. contract OptimismMintableERC20_Burn_Test is OptimismMintableERC20_TestInit { function test_burn_succeeds() external { - vm.prank(address(l2StandardBridge)); - L2Token.mint(alice, 100); + _bridgeMint(alice, 100); vm.expectEmit(true, true, true, true); emit Burn(alice, 100); @@ -73,7 +73,6 @@ contract OptimismMintableERC20_Burn_Test is OptimismMintableERC20_TestInit { } function test_burn_notBridge_reverts() external { - // NOT the bridge vm.expectRevert("OptimismMintableERC20: only bridge can mint and burn"); vm.prank(address(alice)); L2Token.burn(alice, 100); @@ -84,65 +83,19 @@ contract OptimismMintableERC20_Burn_Test is OptimismMintableERC20_TestInit { /// @notice Tests the `supportsInterface` function of the `OptimismMintableERC20` contract. contract OptimismMintableERC20_SupportsInterface_Test is OptimismMintableERC20_TestInit { function test_erc165_supportsInterface_succeeds() external view { - // The assertEq calls in this test are comparing the manual calculation of the iface, with - // what is returned by the solidity's type().interfaceId, just to be safe. - bytes4 iface1 = bytes4(keccak256("supportsInterface(bytes4)")); - assertEq(iface1, type(IERC165).interfaceId); - assert(L2Token.supportsInterface(iface1)); - - bytes4 iface2 = L2Token.l1Token.selector ^ L2Token.mint.selector ^ L2Token.burn.selector; - assertEq(iface2, type(ILegacyMintableERC20).interfaceId); - assert(L2Token.supportsInterface(iface2)); - - bytes4 iface3 = - L2Token.remoteToken.selector ^ L2Token.bridge.selector ^ L2Token.mint.selector ^ L2Token.burn.selector; - assertEq(iface3, type(IOptimismMintableERC20).interfaceId); - assert(L2Token.supportsInterface(iface3)); - } -} - -/// @title OptimismMintableERC20_L1Token_Test -/// @notice Tests the `l1Token` function of the `OptimismMintableERC20` contract. -contract OptimismMintableERC20_L1Token_Test is OptimismMintableERC20_TestInit { - function test_l1Token_succeeds() external view { - assertEq(L2Token.l1Token(), address(L1Token)); - } -} - -/// @title OptimismMintableERC20_L2Bridge_Test -/// @notice Tests the `l2Bridge` function of the `OptimismMintableERC20` contract. -contract OptimismMintableERC20_L2Bridge_Test is OptimismMintableERC20_TestInit { - function test_l2Bridge_succeeds() external view { - assertEq(L2Token.l2Bridge(), address(l2StandardBridge)); - } -} - -/// @title OptimismMintableERC20_RemoteToken_Test -/// @notice Tests the `remoteToken` function of the `OptimismMintableERC20` contract. -contract OptimismMintableERC20_RemoteToken_Test is OptimismMintableERC20_TestInit { - function test_remoteToken_succeeds() external view { - assertEq(L2Token.remoteToken(), address(L1Token)); - } -} - -/// @title OptimismMintableERC20_Bridge_Test -/// @notice Tests the `bridge` function of the `OptimismMintableERC20` contract. -contract OptimismMintableERC20_Bridge_Test is OptimismMintableERC20_TestInit { - function test_bridge_succeeds() external view { - assertEq(L2Token.bridge(), address(l2StandardBridge)); + assertTrue(L2Token.supportsInterface(type(IERC165).interfaceId)); + assertTrue(L2Token.supportsInterface(type(ILegacyMintableERC20).interfaceId)); + assertTrue(L2Token.supportsInterface(type(IOptimismMintableERC20).interfaceId)); } } -/// @title OptimismMintableERC20_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the `OptimismMintableERC20` -/// contract. -contract OptimismMintableERC20_Uncategorized_Test is OptimismMintableERC20_TestInit { - function test_legacy_succeeds() external view { - // Getters for the remote token +/// @title OptimismMintableERC20_Getters_Test +/// @notice Tests getter and legacy getter functions of the `OptimismMintableERC20` contract. +contract OptimismMintableERC20_Getters_Test is OptimismMintableERC20_TestInit { + function test_getters_succeeds() external view { assertEq(L2Token.REMOTE_TOKEN(), address(L1Token)); assertEq(L2Token.remoteToken(), address(L1Token)); assertEq(L2Token.l1Token(), address(L1Token)); - // Getters for the bridge assertEq(L2Token.BRIDGE(), address(l2StandardBridge)); assertEq(L2Token.bridge(), address(l2StandardBridge)); assertEq(L2Token.l2Bridge(), address(l2StandardBridge)); From f002cea3e0db7d6fc55eb80e0969531d253fd84f Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 16:41:17 -0400 Subject: [PATCH 125/135] Enhance OptimismMintableERC20Factory tests: introduce a constant for remote token address revert message, rename test functions for clarity, and streamline assertions for improved readability and maintainability. --- .../OptimismMintableERC20Factory.t.sol | 56 +++++++------------ 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/test/universal/OptimismMintableERC20Factory.t.sol b/test/universal/OptimismMintableERC20Factory.t.sol index 0dfb5b96..62adc83f 100644 --- a/test/universal/OptimismMintableERC20Factory.t.sol +++ b/test/universal/OptimismMintableERC20Factory.t.sol @@ -20,6 +20,9 @@ abstract contract OptimismMintableERC20Factory_TestInit is CommonTest { event StandardL2TokenCreated(address indexed remoteToken, address indexed localToken); event OptimismMintableERC20Created(address indexed localToken, address indexed remoteToken, address deployer); + string internal constant REMOTE_TOKEN_ZERO_REVERT = + "OptimismMintableERC20Factory: must provide remote token address"; + /// @notice Precalculates the address of the token contract. function _calculateTokenAddress( address _remote, @@ -75,11 +78,8 @@ contract OptimismMintableERC20Factory_CreateStandardL2Token_Test is OptimismMint ) external { - // Assume vm.assume(_remoteToken != address(0)); - // Arrange - // Defaults to 18 decimals address local = _calculateTokenAddress(_remoteToken, _name, _symbol, 18); vm.expectEmit(address(l2OptimismMintableERC20Factory)); @@ -88,19 +88,17 @@ contract OptimismMintableERC20Factory_CreateStandardL2Token_Test is OptimismMint vm.expectEmit(address(l2OptimismMintableERC20Factory)); emit OptimismMintableERC20Created(local, _remoteToken, _caller); - // Act vm.prank(_caller); address addr = l2OptimismMintableERC20Factory.createStandardL2Token(_remoteToken, _name, _symbol); - // Assert - assertTrue(addr == local); - assertTrue(OptimismMintableERC20(local).decimals() == 18); + assertEq(addr, local); + assertEq(OptimismMintableERC20(local).decimals(), 18); assertEq(l2OptimismMintableERC20Factory.deployments(local), _remoteToken); } /// @notice Test that calling `createOptimismMintableERC20WithDecimals` with valid parameters /// succeeds. - function test_createStandardL2TokenWithDecimals_succeeds( + function test_createOptimismMintableERC20WithDecimals_succeeds( address _caller, address _remoteToken, string memory _name, @@ -109,10 +107,8 @@ contract OptimismMintableERC20Factory_CreateStandardL2Token_Test is OptimismMint ) external { - // Assume vm.assume(_remoteToken != address(0)); - // Arrange address local = _calculateTokenAddress(_remoteToken, _name, _symbol, _decimals); vm.expectEmit(address(l2OptimismMintableERC20Factory)); @@ -121,15 +117,13 @@ contract OptimismMintableERC20Factory_CreateStandardL2Token_Test is OptimismMint vm.expectEmit(address(l2OptimismMintableERC20Factory)); emit OptimismMintableERC20Created(local, _remoteToken, _caller); - // Act vm.prank(_caller); address addr = l2OptimismMintableERC20Factory.createOptimismMintableERC20WithDecimals( _remoteToken, _name, _symbol, _decimals ); - // Assert - assertTrue(addr == local); - assertTrue(OptimismMintableERC20(local).decimals() == _decimals); + assertEq(addr, local); + assertEq(OptimismMintableERC20(local).decimals(), _decimals); assertEq(l2OptimismMintableERC20Factory.deployments(local), _remoteToken); } @@ -142,23 +136,20 @@ contract OptimismMintableERC20Factory_CreateStandardL2Token_Test is OptimismMint ) external { - // Assume vm.assume(_remoteToken != address(0)); vm.prank(_caller); l2OptimismMintableERC20Factory.createStandardL2Token(_remoteToken, _name, _symbol); - // Arrange - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args + vm.expectRevert(bytes("")); - // Act vm.prank(_caller); l2OptimismMintableERC20Factory.createStandardL2Token(_remoteToken, _name, _symbol); } - /// @notice Test that calling `createStandardL2TokenWithDecimals` with the same parameters + /// @notice Test that calling `createOptimismMintableERC20WithDecimals` with the same parameters /// twice reverts. - function test_createStandardL2TokenWithDecimals_sameTwice_reverts( + function test_createOptimismMintableERC20WithDecimals_sameTwice_reverts( address _caller, address _remoteToken, string memory _name, @@ -167,16 +158,13 @@ contract OptimismMintableERC20Factory_CreateStandardL2Token_Test is OptimismMint ) external { - // Assume vm.assume(_remoteToken != address(0)); vm.prank(_caller); l2OptimismMintableERC20Factory.createOptimismMintableERC20WithDecimals(_remoteToken, _name, _symbol, _decimals); - // Arrange - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args + vm.expectRevert(bytes("")); - // Act vm.prank(_caller); l2OptimismMintableERC20Factory.createOptimismMintableERC20WithDecimals(_remoteToken, _name, _symbol, _decimals); } @@ -189,18 +177,15 @@ contract OptimismMintableERC20Factory_CreateStandardL2Token_Test is OptimismMint ) external { - // Arrange - address remote = address(0); - vm.expectRevert("OptimismMintableERC20Factory: must provide remote token address"); + vm.expectRevert(bytes(REMOTE_TOKEN_ZERO_REVERT)); - // Act vm.prank(_caller); - l2OptimismMintableERC20Factory.createStandardL2Token(remote, _name, _symbol); + l2OptimismMintableERC20Factory.createStandardL2Token(address(0), _name, _symbol); } - /// @notice Test that calling `createStandardL2TokenWithDecimals` with a zero remote token + /// @notice Test that calling `createOptimismMintableERC20WithDecimals` with a zero remote token /// address reverts. - function test_createStandardL2TokenWithDecimals_remoteIsZero_reverts( + function test_createOptimismMintableERC20WithDecimals_remoteIsZero_reverts( address _caller, string memory _name, string memory _symbol, @@ -208,13 +193,10 @@ contract OptimismMintableERC20Factory_CreateStandardL2Token_Test is OptimismMint ) external { - // Arrange - address remote = address(0); - vm.expectRevert("OptimismMintableERC20Factory: must provide remote token address"); + vm.expectRevert(bytes(REMOTE_TOKEN_ZERO_REVERT)); - // Act vm.prank(_caller); - l2OptimismMintableERC20Factory.createOptimismMintableERC20WithDecimals(remote, _name, _symbol, _decimals); + l2OptimismMintableERC20Factory.createOptimismMintableERC20WithDecimals(address(0), _name, _symbol, _decimals); } } @@ -236,7 +218,7 @@ contract OptimismMintableERC20Factory_Uncategorized_Test is OptimismMintableERC2 proxy.upgradeToAndCall(address(nextImpl), abi.encodeCall(NextImpl.initialize, (2))); assertEq(proxy.implementation(), address(nextImpl)); - // Verify that the NextImpl contract initialized its values according as expected + // Verify that the NextImpl contract initialized its values as expected. bytes32 slot21After = vm.load(address(l1OptimismMintableERC20Factory), bytes32(uint256(21))); bytes32 slot21Expected = NextImpl(address(l1OptimismMintableERC20Factory)).slot21Init(); assertEq(slot21Expected, slot21After); From 1ca5be2adf4ad147856015023e7a3cea98efa21c Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 16:50:18 -0400 Subject: [PATCH 126/135] Refactor Proxy tests: streamline upgrade logic by introducing helper functions for implementation management, enhance clarity in assertions, and improve maintainability by removing redundant code and constants. --- test/universal/Proxy.t.sol | 246 ++++++++++++++----------------------- 1 file changed, 91 insertions(+), 155 deletions(-) diff --git a/test/universal/Proxy.t.sol b/test/universal/Proxy.t.sol index 9af41bed..89114cee 100644 --- a/test/universal/Proxy.t.sol +++ b/test/universal/Proxy.t.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.15; import { Test } from "lib/forge-std/src/Test.sol"; -import { Bytes32AddressLib } from "lib/solmate/src/utils/Bytes32AddressLib.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; +import { Proxy } from "src/universal/Proxy.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; contract Proxy_SimpleStorage_Harness { mapping(uint256 => uint256) internal store; @@ -30,27 +30,35 @@ abstract contract Proxy_TestInit is Test { event Upgraded(address indexed implementation); event AdminChanged(address previousAdmin, address newAdmin); - address alice = address(64); - - bytes32 internal constant IMPLEMENTATION_KEY = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); - - bytes32 internal constant OWNER_KEY = bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1); + address internal constant ADMIN = address(64); IProxy proxy; Proxy_SimpleStorage_Harness simpleStorage; function setUp() external { - // Deploy a proxy and simple storage contract as the implementation - proxy = IProxy( - DeployUtils.create1({ - _name: "src/universal/Proxy.sol:Proxy", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxy.__constructor__, (alice))) - }) - ); + proxy = IProxy(payable(address(new Proxy(ADMIN)))); simpleStorage = new Proxy_SimpleStorage_Harness(); - vm.prank(alice); - proxy.upgradeTo(address(simpleStorage)); + _upgradeTo(address(simpleStorage)); + } + + function _proxyStorage() internal view returns (Proxy_SimpleStorage_Harness) { + return Proxy_SimpleStorage_Harness(address(proxy)); + } + + function _upgradeTo(address _newImplementation) internal { + vm.prank(ADMIN); + proxy.upgradeTo(_newImplementation); + } + + function _getImplementation() internal returns (address) { + vm.prank(ADMIN); + return proxy.implementation(); + } + + function _adminAs(address _caller) internal returns (address) { + vm.prank(_caller); + return proxy.admin(); } } @@ -58,52 +66,31 @@ abstract contract Proxy_TestInit is Test { /// @notice Tests the `upgradeTo` function of the `Proxy` contract. contract Proxy_UpgradeTo_Test is Proxy_TestInit { function test_upgradeTo_notAdmin_succeeds() external { - // The implementation does not have a `upgradeTo` method, calling `upgradeTo` not as the - // owner should revert. - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - proxy.upgradeTo(address(64)); + address newImplementation = address(128); + + vm.expectRevert(bytes("")); + proxy.upgradeTo(newImplementation); - // Call `upgradeTo` as the owner, it should succeed and emit the `Upgraded` event. vm.expectEmit(true, true, true, true); - emit Upgraded(address(64)); - vm.prank(alice); - proxy.upgradeTo(address(64)); + emit Upgraded(newImplementation); + _upgradeTo(newImplementation); - // Get the implementation as the owner - vm.prank(alice); - address impl = proxy.implementation(); - assertEq(impl, address(64)); + assertEq(_getImplementation(), newImplementation); } function test_upgradeTo_clashingFunctionSignatures_succeeds() external { - // Clasher has a clashing function with the proxy. Proxy_Clasher_Harness clasher = new Proxy_Clasher_Harness(); - // Set the clasher as the implementation. - vm.prank(alice); - proxy.upgradeTo(address(clasher)); - - { - // Assert that the implementation was set properly. - vm.prank(alice); - address impl = proxy.implementation(); - assertEq(impl, address(clasher)); - } + _upgradeTo(address(clasher)); + assertEq(_getImplementation(), address(clasher)); // Call the clashing function on the proxy not as the owner so that the call passes // through. The implementation will revert so we can be sure that the call passed through. vm.expectRevert(bytes("Clasher: upgradeTo")); proxy.upgradeTo(address(0)); - { - // Now call the clashing function as the owner and be sure that it doesn't pass through - // to the implementation. - vm.prank(alice); - proxy.upgradeTo(address(0)); - vm.prank(alice); - address impl = proxy.implementation(); - assertEq(impl, address(0)); - } + _upgradeTo(address(0)); + assertEq(_getImplementation(), address(0)); } } @@ -111,67 +98,47 @@ contract Proxy_UpgradeTo_Test is Proxy_TestInit { /// @notice Tests the `upgradeToAndCall` function of the `Proxy` contract. contract Proxy_UpgradeToAndCall_Test is Proxy_TestInit { function test_upgradeToAndCall_succeeds() external { - { - // There should be nothing in the current proxy storage - uint256 expect = Proxy_SimpleStorage_Harness(address(proxy)).get(1); - assertEq(expect, 0); - } + assertEq(_proxyStorage().get(1), 0); - // Deploy a new SimpleStorage - simpleStorage = new Proxy_SimpleStorage_Harness(); + Proxy_SimpleStorage_Harness newSimpleStorage = new Proxy_SimpleStorage_Harness(); - // Set the new SimpleStorage as the implementation and call. vm.expectEmit(true, true, true, true); - emit Upgraded(address(simpleStorage)); - vm.prank(alice); - proxy.upgradeToAndCall(address(simpleStorage), abi.encodeCall(Proxy_SimpleStorage_Harness.set, (1, 1))); + emit Upgraded(address(newSimpleStorage)); + vm.prank(ADMIN); + proxy.upgradeToAndCall(address(newSimpleStorage), abi.encodeCall(Proxy_SimpleStorage_Harness.set, (1, 1))); - // The call should have impacted the state - uint256 result = Proxy_SimpleStorage_Harness(address(proxy)).get(1); - assertEq(result, 1); + assertEq(_proxyStorage().get(1), 1); } function test_upgradeToAndCall_functionDoesNotExist_reverts() external { - // Get the current implementation address - vm.prank(alice); - address impl = proxy.implementation(); - assertEq(impl, address(simpleStorage)); + address initialImplementation = _getImplementation(); + assertEq(initialImplementation, address(simpleStorage)); - // Deploy a new SimpleStorage - simpleStorage = new Proxy_SimpleStorage_Harness(); + Proxy_SimpleStorage_Harness newSimpleStorage = new Proxy_SimpleStorage_Harness(); // Set the new SimpleStorage as the implementation and call. This reverts because the // calldata doesn't match a function on the implementation. vm.expectRevert("Proxy: delegatecall to new implementation contract failed"); - vm.prank(alice); - proxy.upgradeToAndCall(address(simpleStorage), hex""); - - // The implementation address should have not updated because the call to - // `upgradeToAndCall` reverted. - vm.prank(alice); - address postImpl = proxy.implementation(); - assertEq(impl, postImpl); - - // The attempt to `upgradeToAndCall` should revert when it is not called by the owner. - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - proxy.upgradeToAndCall(address(simpleStorage), abi.encodeCall(simpleStorage.set, (1, 1))); + vm.prank(ADMIN); + proxy.upgradeToAndCall(address(newSimpleStorage), hex""); + + assertEq(_getImplementation(), initialImplementation); + + vm.expectRevert(bytes("")); + proxy.upgradeToAndCall(address(newSimpleStorage), abi.encodeCall(Proxy_SimpleStorage_Harness.set, (1, 1))); } function test_upgradeToAndCall_isPayable_succeeds() external { - // Give alice some funds - vm.deal(alice, 1 ether); + uint256 value = 1 ether; - // Set the implementation and call and send value. - vm.prank(alice); - proxy.upgradeToAndCall{ value: 1 ether }(address(simpleStorage), abi.encodeCall(simpleStorage.set, (1, 1))); - - // The implementation address should be correct - vm.prank(alice); - address impl = proxy.implementation(); - assertEq(impl, address(simpleStorage)); + vm.deal(ADMIN, value); + vm.prank(ADMIN); + proxy.upgradeToAndCall{ value: value }( + address(simpleStorage), abi.encodeCall(Proxy_SimpleStorage_Harness.set, (1, 1)) + ); - // The proxy should have a balance - assertEq(address(proxy).balance, 1 ether); + assertEq(_getImplementation(), address(simpleStorage)); + assertEq(address(proxy).balance, value); } } @@ -179,16 +146,13 @@ contract Proxy_UpgradeToAndCall_Test is Proxy_TestInit { /// @notice Tests the `changeAdmin` function of the `Proxy` contract. contract Proxy_ChangeAdmin_Test is Proxy_TestInit { function test_changeAdmin_ownerKey_succeeds() external { - // The hardcoded owner key should be correct - vm.prank(alice); - proxy.changeAdmin(address(6)); + address newAdmin = address(6); - bytes32 key = vm.load(address(proxy), OWNER_KEY); - assertEq(address(6), Bytes32AddressLib.fromLast20Bytes(key)); + vm.prank(ADMIN); + proxy.changeAdmin(newAdmin); - vm.prank(address(6)); - address owner = proxy.admin(); - assertEq(owner, address(6)); + assertEq(EIP1967Helper.getAdmin(address(proxy)), newAdmin); + assertEq(_adminAs(newAdmin), newAdmin); } } @@ -196,26 +160,20 @@ contract Proxy_ChangeAdmin_Test is Proxy_TestInit { /// @notice Tests the `admin` function of the `Proxy` contract. contract Proxy_Admin_Test is Proxy_TestInit { function test_admin_notAdmin_succeeds() external { - // Calling `changeAdmin` not as the owner should revert as the implementation does not have - // a `changeAdmin` method. - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - proxy.changeAdmin(address(1)); + address newAdmin = address(1); + + vm.expectRevert(bytes("")); + proxy.changeAdmin(newAdmin); - // Call `changeAdmin` as the owner, it should succeed and emit the `AdminChanged` event. vm.expectEmit(true, true, true, true); - emit AdminChanged(alice, address(1)); - vm.prank(alice); - proxy.changeAdmin(address(1)); + emit AdminChanged(ADMIN, newAdmin); + vm.prank(ADMIN); + proxy.changeAdmin(newAdmin); - // Calling `admin` not as the owner should revert as the implementation does not have a - // `admin` method. - vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args + vm.expectRevert(bytes("")); proxy.admin(); - // Calling `admin` as the owner should work. - vm.prank(address(1)); - address owner = proxy.admin(); - assertEq(owner, address(1)); + assertEq(_adminAs(newAdmin), newAdmin); } } @@ -223,16 +181,12 @@ contract Proxy_Admin_Test is Proxy_TestInit { /// @notice Tests the `implementation` function of the `Proxy` contract. contract Proxy_Implementation_Test is Proxy_TestInit { function test_implementation_key_succeeds() external { - // The hardcoded implementation key should be correct - vm.prank(alice); - proxy.upgradeTo(address(6)); + address newImplementation = address(6); - bytes32 key = vm.load(address(proxy), IMPLEMENTATION_KEY); - assertEq(address(6), Bytes32AddressLib.fromLast20Bytes(key)); + _upgradeTo(newImplementation); - vm.prank(alice); - address impl = proxy.implementation(); - assertEq(impl, address(6)); + assertEq(EIP1967Helper.getImplementation(address(proxy)), newImplementation); + assertEq(_getImplementation(), newImplementation); } // Allow for `eth_call` to call proxy methods by setting "from" to `address(0)`. @@ -243,42 +197,24 @@ contract Proxy_Implementation_Test is Proxy_TestInit { } function test_implementation_isZeroAddress_reverts() external { - // Set `address(0)` as the implementation. - vm.prank(alice); - proxy.upgradeTo(address(0)); - - (bool success, bytes memory returndata) = address(proxy).call(hex""); - assertEq(success, false); + _upgradeTo(address(0)); - bytes memory err = abi.encodeWithSignature("Error(string)", "Proxy: implementation not initialized"); // nosemgrep: - // sol-style-use-abi-encodecall - - assertEq(returndata, err); + vm.expectRevert("Proxy: implementation not initialized"); + _proxyStorage().get(1); } } -/// @title Proxy_Uncategorized_Test -/// @notice General tests that are not testing any function directly of the `Proxy` contract. -contract Proxy_Uncategorized_Test is Proxy_TestInit { +/// @title Proxy_Delegation_Test +/// @notice Tests proxy delegation behavior. +contract Proxy_Delegation_Test is Proxy_TestInit { function test_delegatesToImpl_succeeds() external { - // Call the storage setter on the proxy - Proxy_SimpleStorage_Harness(address(proxy)).set(1, 1); - - // The key should not be set in the implementation - uint256 result = simpleStorage.get(1); - assertEq(result, 0); - { - // The key should be set in the proxy - uint256 expect = Proxy_SimpleStorage_Harness(address(proxy)).get(1); - assertEq(expect, 1); - } - - { - // The owner should be able to call through the proxy when there is not a function - // selector crash. - vm.prank(alice); - uint256 expect = Proxy_SimpleStorage_Harness(address(proxy)).get(1); - assertEq(expect, 1); - } + _proxyStorage().set(1, 1); + + assertEq(simpleStorage.get(1), 0); + assertEq(_proxyStorage().get(1), 1); + + // The owner should be able to call through the proxy when there is no selector clash. + vm.prank(ADMIN); + assertEq(_proxyStorage().get(1), 1); } } From b01854af0f87b6bf2e39b2e0e7af4228cb9d6ef2 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 16:55:30 -0400 Subject: [PATCH 127/135] Refactor ProxyAdmin tests: replace hardcoded addresses with constants for improved readability, streamline setup logic by enhancing clarity in ownership management, and consolidate assertion methods for better maintainability. --- test/universal/ProxyAdmin.t.sol | 125 ++++++++++++++------------------ 1 file changed, 53 insertions(+), 72 deletions(-) diff --git a/test/universal/ProxyAdmin.t.sol b/test/universal/ProxyAdmin.t.sol index 88fdc4ec..17aa6469 100644 --- a/test/universal/ProxyAdmin.t.sol +++ b/test/universal/ProxyAdmin.t.sol @@ -17,7 +17,8 @@ import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; /// @title ProxyAdmin_TestInit /// @notice Reusable test initialization for `ProxyAdmin` tests. abstract contract ProxyAdmin_TestInit is Test { - address alice = address(64); + address internal constant PROXY_ADMIN_OWNER = address(64); + address internal constant NEW_PROXY_ADMIN = address(128); IProxy proxy; IL1ChugSplashProxy chugsplash; @@ -30,15 +31,13 @@ abstract contract ProxyAdmin_TestInit is Test { Proxy_SimpleStorage_Harness implementation; function setUp() external { - // Deploy the proxy admin admin = IProxyAdmin( DeployUtils.create1({ _name: "ProxyAdmin", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (alice))) + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProxyAdmin.__constructor__, (PROXY_ADMIN_OWNER))) }) ); - // Deploy the standard proxy proxy = IProxy( DeployUtils.create1({ _name: "src/universal/Proxy.sol:Proxy", @@ -46,7 +45,6 @@ abstract contract ProxyAdmin_TestInit is Test { }) ); - // Deploy the legacy L1ChugSplashProxy with the admin as the owner chugsplash = IL1ChugSplashProxy( DeployUtils.create1({ _name: "L1ChugSplashProxy", @@ -56,15 +54,14 @@ abstract contract ProxyAdmin_TestInit is Test { }) ); - // Deploy the legacy AddressManager addressManager = IAddressManager( DeployUtils.create1({ _name: "AddressManager", _args: DeployUtils.encodeConstructor(abi.encodeCall(IAddressManager.__constructor__, ())) }) ); - // The proxy admin must be the new owner of the address manager addressManager.transferOwnership(address(admin)); + // Deploy a legacy ResolvedDelegateProxy with the name `a`. Whatever `a` is set to in // AddressManager will be the address that is used for the implementation. resolved = IResolvedDelegateProxy( @@ -75,15 +72,13 @@ abstract contract ProxyAdmin_TestInit is Test { ) }) ); - // Impersonate alice for setting up the admin. - vm.startPrank(alice); - // Set the address of the address manager in the admin so that it can resolve the + + vm.startPrank(PROXY_ADMIN_OWNER); + // Set the address manager so the admin can resolve the // implementation address of legacy ResolvedDelegateProxy based proxies. - admin.setAddressManager(IAddressManager(address(addressManager))); - // Set the reverse lookup of the ResolvedDelegateProxy proxy + admin.setAddressManager(addressManager); admin.setImplementationName(address(resolved), "a"); - // Set the proxy types admin.setProxyType(address(proxy), IProxyAdmin.ProxyType.ERC1967); admin.setProxyType(address(chugsplash), IProxyAdmin.ProxyType.CHUGSPLASH); admin.setProxyType(address(resolved), IProxyAdmin.ProxyType.RESOLVED); @@ -106,7 +101,7 @@ contract ProxyAdmin_SetProxyType_Test is ProxyAdmin_TestInit { /// @notice Tests the `setImplementationName` function of the `ProxyAdmin` contract. contract ProxyAdmin_SetImplementationName_Test is ProxyAdmin_TestInit { function test_setImplementationName_succeeds() external { - vm.prank(alice); + vm.prank(PROXY_ADMIN_OWNER); admin.setImplementationName(address(1), "foo"); assertEq(admin.implementationName(address(1)), "foo"); } @@ -122,7 +117,7 @@ contract ProxyAdmin_SetImplementationName_Test is ProxyAdmin_TestInit { contract ProxyAdmin_SetAddressManager_Test is ProxyAdmin_TestInit { function test_setAddressManager_notOwner_reverts() external { vm.expectRevert("Ownable: caller is not the owner"); - admin.setAddressManager(IAddressManager((address(0)))); + admin.setAddressManager(IAddressManager(address(0))); } } @@ -130,74 +125,67 @@ contract ProxyAdmin_SetAddressManager_Test is ProxyAdmin_TestInit { /// @notice Tests the `isUpgrading` function of the `ProxyAdmin` contract. contract ProxyAdmin_IsUpgrading_Test is ProxyAdmin_TestInit { function test_isUpgrading_succeeds() external { - assertEq(false, admin.isUpgrading()); + assertFalse(admin.isUpgrading()); - vm.prank(alice); + vm.prank(PROXY_ADMIN_OWNER); admin.setUpgrading(true); - assertEq(true, admin.isUpgrading()); + assertTrue(admin.isUpgrading()); } } /// @title ProxyAdmin_GetProxyImplementation_Test /// @notice Tests the `getProxyImplementation` function of the `ProxyAdmin` contract. contract ProxyAdmin_GetProxyImplementation_Test is ProxyAdmin_TestInit { - function getProxyImplementation(address payable _proxy) internal { - { - address impl = admin.getProxyImplementation(_proxy); - assertEq(impl, address(0)); - } + function _assertProxyImplementation(address payable _proxy) internal { + assertEq(admin.getProxyImplementation(_proxy), address(0)); - vm.prank(alice); + vm.prank(PROXY_ADMIN_OWNER); admin.upgrade(_proxy, address(implementation)); - { - address impl = admin.getProxyImplementation(_proxy); - assertEq(impl, address(implementation)); - } + assertEq(admin.getProxyImplementation(_proxy), address(implementation)); } function test_getProxyImplementation_erc1967_succeeds() external { - getProxyImplementation(payable(proxy)); + _assertProxyImplementation(payable(proxy)); } function test_getProxyImplementation_chugsplash_succeeds() external { - getProxyImplementation(payable(chugsplash)); + _assertProxyImplementation(payable(chugsplash)); } function test_getProxyImplementation_resolved_succeeds() external { - getProxyImplementation(payable(resolved)); + _assertProxyImplementation(payable(resolved)); } } /// @title ProxyAdmin_GetProxyAdmin_Test /// @notice Tests the `getProxyAdmin` function of the `ProxyAdmin` contract. contract ProxyAdmin_GetProxyAdmin_Test is ProxyAdmin_TestInit { - function getProxyAdmin(address payable _proxy) internal view { - address owner = admin.getProxyAdmin(_proxy); - assertEq(owner, address(admin)); + function _assertProxyAdmin(address payable _proxy) internal view { + assertEq(admin.getProxyAdmin(_proxy), address(admin)); } function test_getProxyAdmin_erc1967_succeeds() external view { - getProxyAdmin(payable(proxy)); + _assertProxyAdmin(payable(proxy)); } function test_getProxyAdmin_chugsplash_succeeds() external view { - getProxyAdmin(payable(chugsplash)); + _assertProxyAdmin(payable(chugsplash)); } function test_getProxyAdmin_resolved_succeeds() external view { - getProxyAdmin(payable(resolved)); + _assertProxyAdmin(payable(resolved)); } } /// @title ProxyAdmin_ChangeProxyAdmin_Test /// @notice Tests the `changeProxyAdmin` function of the `ProxyAdmin` contract. contract ProxyAdmin_ChangeProxyAdmin_Test is ProxyAdmin_TestInit { - function changeProxyAdmin(address payable _proxy) internal { + function _assertChangeProxyAdmin(address payable _proxy) internal { IProxyAdmin.ProxyType proxyType = admin.proxyType(address(_proxy)); - vm.prank(alice); - admin.changeProxyAdmin(_proxy, address(128)); + vm.prank(PROXY_ADMIN_OWNER); + admin.changeProxyAdmin(_proxy, NEW_PROXY_ADMIN); // The proxy is no longer the admin and can // no longer call the proxy interface except for @@ -210,86 +198,79 @@ contract ProxyAdmin_ChangeProxyAdmin_Test is ProxyAdmin_TestInit { vm.expectRevert("L1ChugSplashProxy: implementation is not set yet"); admin.getProxyAdmin(_proxy); } else if (proxyType == IProxyAdmin.ProxyType.RESOLVED) { - // Just an empty block to show that all cases are covered - } else { - vm.expectRevert("ProxyAdmin: unknown proxy type"); + assertEq(admin.getProxyAdmin(_proxy), NEW_PROXY_ADMIN); } // Call the proxy contract directly to get the admin. // Different proxy types have different interfaces. - vm.prank(address(128)); + vm.prank(NEW_PROXY_ADMIN); if (proxyType == IProxyAdmin.ProxyType.ERC1967) { - assertEq(IProxy(payable(_proxy)).admin(), address(128)); + assertEq(IProxy(payable(_proxy)).admin(), NEW_PROXY_ADMIN); } else if (proxyType == IProxyAdmin.ProxyType.CHUGSPLASH) { - assertEq(IL1ChugSplashProxy(payable(_proxy)).getOwner(), address(128)); + assertEq(IL1ChugSplashProxy(payable(_proxy)).getOwner(), NEW_PROXY_ADMIN); } else if (proxyType == IProxyAdmin.ProxyType.RESOLVED) { - assertEq(addressManager.owner(), address(128)); - } else { - assert(false); + assertEq(addressManager.owner(), NEW_PROXY_ADMIN); } } function test_changeProxyAdmin_erc1967_succeeds() external { - changeProxyAdmin(payable(proxy)); + _assertChangeProxyAdmin(payable(proxy)); } function test_changeProxyAdmin_chugsplash_succeeds() external { - changeProxyAdmin(payable(chugsplash)); + _assertChangeProxyAdmin(payable(chugsplash)); } function test_changeProxyAdmin_resolved_succeeds() external { - changeProxyAdmin(payable(resolved)); + _assertChangeProxyAdmin(payable(resolved)); } } /// @title ProxyAdmin_Upgrade_Test /// @notice Tests the `upgrade` function of the `ProxyAdmin` contract. contract ProxyAdmin_Upgrade_Test is ProxyAdmin_TestInit { - function upgrade(address payable _proxy) internal { - vm.prank(alice); + function _assertUpgrade(address payable _proxy) internal { + vm.prank(PROXY_ADMIN_OWNER); admin.upgrade(_proxy, address(implementation)); - address impl = admin.getProxyImplementation(_proxy); - assertEq(impl, address(implementation)); + assertEq(admin.getProxyImplementation(_proxy), address(implementation)); } function test_upgrade_erc1967_succeeds() external { - upgrade(payable(proxy)); + _assertUpgrade(payable(proxy)); } function test_upgrade_chugsplash_succeeds() external { - upgrade(payable(chugsplash)); + _assertUpgrade(payable(chugsplash)); } function test_upgrade_resolved_succeeds() external { - upgrade(payable(resolved)); + _assertUpgrade(payable(resolved)); } } /// @title ProxyAdmin_UpgradeAndCall_Test /// @notice Tests the `upgradeAndCall` function of the `ProxyAdmin` contract. contract ProxyAdmin_UpgradeAndCall_Test is ProxyAdmin_TestInit { - function upgradeAndCall(address payable _proxy) internal { - vm.prank(alice); + function _assertUpgradeAndCall(address payable _proxy) internal { + vm.prank(PROXY_ADMIN_OWNER); admin.upgradeAndCall(_proxy, address(implementation), abi.encodeCall(Proxy_SimpleStorage_Harness.set, (1, 1))); - address impl = admin.getProxyImplementation(_proxy); - assertEq(impl, address(implementation)); + assertEq(admin.getProxyImplementation(_proxy), address(implementation)); - uint256 got = Proxy_SimpleStorage_Harness(address(_proxy)).get(1); - assertEq(got, 1); + assertEq(Proxy_SimpleStorage_Harness(address(_proxy)).get(1), 1); } - function test_erc1967UpgradeAndCall_succeeds() external { - upgradeAndCall(payable(proxy)); + function test_upgradeAndCall_erc1967_succeeds() external { + _assertUpgradeAndCall(payable(proxy)); } - function test_chugsplashUpgradeAndCall_succeeds() external { - upgradeAndCall(payable(chugsplash)); + function test_upgradeAndCall_chugsplash_succeeds() external { + _assertUpgradeAndCall(payable(chugsplash)); } - function test_delegateResolvedUpgradeAndCall_succeeds() external { - upgradeAndCall(payable(resolved)); + function test_upgradeAndCall_resolved_succeeds() external { + _assertUpgradeAndCall(payable(resolved)); } } @@ -298,7 +279,7 @@ contract ProxyAdmin_UpgradeAndCall_Test is ProxyAdmin_TestInit { /// functions of the `ProxyAdmin` contract. contract ProxyAdmin_Uncategorized_Test is ProxyAdmin_TestInit { function test_owner_succeeds() external view { - assertEq(admin.owner(), alice); + assertEq(admin.owner(), PROXY_ADMIN_OWNER); } function test_proxyType_succeeds() external view { From 498ad5d74b813bbc300b6570925b365b125a658b Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 17:02:44 -0400 Subject: [PATCH 128/135] Refactor ProxyAdminOwnedBase tests: replace hardcoded values with helper functions for managing ResolvedDelegateProxy slots, enhance clarity in assertions, and improve maintainability by streamlining setup logic. --- test/universal/ProxyAdminOwnedBase.t.sol | 116 +++++++++-------------- 1 file changed, 43 insertions(+), 73 deletions(-) diff --git a/test/universal/ProxyAdminOwnedBase.t.sol b/test/universal/ProxyAdminOwnedBase.t.sol index 0872fe04..b8d6a466 100644 --- a/test/universal/ProxyAdminOwnedBase.t.sol +++ b/test/universal/ProxyAdminOwnedBase.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.15; // Testing import { CommonTest } from "test/setup/CommonTest.sol"; -import { Constants } from "src/libraries/Constants.sol"; +import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; // Contracts import { ProxyAdminOwnedBase } from "src/universal/ProxyAdminOwnedBase.sol"; @@ -12,10 +12,10 @@ import { ProxyAdminOwnedBase } from "src/universal/ProxyAdminOwnedBase.sol"; /// @notice Contract implementing the abstract `ProxyAdminOwnedBase` contract so we can write unit /// tests for the `ProxyAdminOwnedBase` contract. contract ProxyAdminOwnedBase_Harness is ProxyAdminOwnedBase { - /// @notice Slot 0, used to test ResolvedDelegateProxy behavior. + /// @notice Slot 0, matching the legacy ResolvedDelegateProxy storage layout. mapping(address => string) public slot0; - /// @notice Slot 1, used to test ResolvedDelegateProxy behavior. + /// @notice Slot 1, matching the legacy ResolvedDelegateProxy storage layout. mapping(address => address) public slot1; /// @notice Assert that the proxy admin owner of the current contract is the same as the proxy @@ -38,19 +38,15 @@ contract ProxyAdminOwnedBase_Harness is ProxyAdminOwnedBase { function assertOnlyProxyAdminOrProxyAdminOwner() public view { _assertOnlyProxyAdminOrProxyAdminOwner(); } - - /// @notice Set the value of slot 0 for the provided address. - function setSlot0(address _address, string memory _value) public { - slot0[_address] = _value; - } - - /// @notice Set the value of slot 1 for the provided address. - function setSlot1(address _address, address _value) public { - slot1[_address] = _value; - } } abstract contract ProxyAdminOwnedBase_TestInit is CommonTest { + /// @notice Stored name value that identifies the legacy ResolvedDelegateProxy. + bytes32 internal constant RESOLVED_DELEGATE_PROXY_NAME = bytes32("OVM_L1CrossDomainMessenger"); + + /// @notice Length of the stored ResolvedDelegateProxy name. + uint256 internal constant RESOLVED_DELEGATE_PROXY_NAME_LENGTH = 26; + /// @notice Harness for the `ProxyAdminOwnedBase` contract. ProxyAdminOwnedBase_Harness public harness; @@ -58,14 +54,39 @@ abstract contract ProxyAdminOwnedBase_TestInit is CommonTest { function setUp() public override { super.setUp(); - // Create a new harness harness = new ProxyAdminOwnedBase_Harness(); + EIP1967Helper.setAdmin(address(harness), address(proxyAdmin)); + } - // Set the owner of the harness to the ProxyAdmin contract. + /// @notice Clears the EIP-1967 admin slot and sets the legacy ResolvedDelegateProxy slots. + function setResolvedDelegateProxy(address _addressManager) internal { + EIP1967Helper.setAdmin(address(harness), address(0)); + vm.store(address(harness), resolvedDelegateProxyNameSlot(), resolvedDelegateProxyNameSlotValue()); vm.store( - address(harness), bytes32(Constants.PROXY_OWNER_ADDRESS), bytes32(uint256(uint160(address(proxyAdmin)))) + address(harness), resolvedDelegateProxyAddressManagerSlot(), bytes32(uint256(uint160(_addressManager))) ); } + + /// @notice Stores a raw value in the ResolvedDelegateProxy name slot. + function setResolvedDelegateProxyNameSlot(bytes32 _value) internal { + EIP1967Helper.setAdmin(address(harness), address(0)); + vm.store(address(harness), resolvedDelegateProxyNameSlot(), _value); + } + + /// @notice Returns the storage slot for `slot0[address(harness)]`. + function resolvedDelegateProxyNameSlot() internal view returns (bytes32) { + return keccak256(abi.encode(address(harness), uint256(0))); + } + + /// @notice Returns the storage value Solidity uses for the short string name. + function resolvedDelegateProxyNameSlotValue() internal pure returns (bytes32) { + return bytes32(uint256(RESOLVED_DELEGATE_PROXY_NAME) | uint256(RESOLVED_DELEGATE_PROXY_NAME_LENGTH * 2)); + } + + /// @notice Returns the storage slot for `slot1[address(harness)]`. + function resolvedDelegateProxyAddressManagerSlot() internal view returns (bytes32) { + return keccak256(abi.encode(address(harness), uint256(1))); + } } contract ProxyAdminOwnedBase_proxyAdminOwner_Test is ProxyAdminOwnedBase_TestInit { @@ -84,49 +105,25 @@ contract ProxyAdminOwnedBase_proxyAdmin_Test is ProxyAdminOwnedBase_TestInit { /// @notice Tests that the proxyAdmin function returns the correct proxy when the current /// contract is a full ResolvedDelegateProxy. function test_proxyAdmin_fullResolvedDelegateProxy_succeeds() public { - // Unset the standard proxy owner slot. - vm.store(address(harness), bytes32(Constants.PROXY_OWNER_ADDRESS), bytes32(0)); - - // Store the string "OVM_L1CrossDomainMessenger" in slot 0. - harness.setSlot0(address(harness), "OVM_L1CrossDomainMessenger"); - - // Store the address of the proxyAdmin in slot 1. - harness.setSlot1(address(harness), address(addressManager)); - - // Expect no revert. + setResolvedDelegateProxy(address(addressManager)); assertEq(address(harness.proxyAdmin()), address(proxyAdmin)); } /// @notice Tests that the proxyAdmin function reverts if the current contract is not a /// ResolvedDelegateProxy. - /// @param _slot0Value The value to store in slot 0. - function test_proxyAdmin_notResolvedDelegateProxy_reverts(string memory _slot0Value) public { - // Assume the slot 0 value is not "OVM_L1CrossDomainMessenger". - vm.assume(keccak256(abi.encode(_slot0Value)) != keccak256(abi.encode("OVM_L1CrossDomainMessenger"))); + /// @param _slot0Value The raw value to store in the ResolvedDelegateProxy name slot. + function test_proxyAdmin_notResolvedDelegateProxy_reverts(bytes32 _slot0Value) public { + vm.assume(_slot0Value != resolvedDelegateProxyNameSlotValue()); + setResolvedDelegateProxyNameSlot(_slot0Value); - // Unset the standard proxy owner slot. - vm.store(address(harness), bytes32(Constants.PROXY_OWNER_ADDRESS), bytes32(0)); - - // Store the slot 0 value. - harness.setSlot0(address(harness), _slot0Value); - - // Expect a revert. vm.expectRevert(ProxyAdminOwnedBase.ProxyAdminOwnedBase_NotResolvedDelegateProxy.selector); harness.proxyAdmin(); } /// @notice Tests that the proxyAdmin function reverts if the proxy admin is not found. function test_proxyAdmin_proxyAdminNotFound_reverts() public { - // Unset the standard proxy owner slot. - vm.store(address(harness), bytes32(Constants.PROXY_OWNER_ADDRESS), bytes32(0)); - - // Store the string "OVM_L1CrossDomainMessenger" in slot 0. - harness.setSlot0(address(harness), "OVM_L1CrossDomainMessenger"); + setResolvedDelegateProxy(address(0)); - // Store address(0) in slot 1. - harness.setSlot1(address(harness), address(0)); - - // Expect a revert. vm.expectRevert(ProxyAdminOwnedBase.ProxyAdminOwnedBase_ProxyAdminNotFound.selector); harness.proxyAdmin(); } @@ -136,13 +133,10 @@ contract ProxyAdminOwnedBase_assertSharedProxyAdminOwner_Test is ProxyAdminOwned /// @notice Tests that the assertSharedProxyAdminOwner function does not revert if the provided /// proxy has the same owner as the current contract. function test_assertSharedProxyAdminOwner_sameOwner_succeeds(address _proxy) public { - // Assume the provided proxy is not a forge address. assumeNotForgeAddress(_proxy); - // Mock the proxyAdminOwner function to return the same owner as the current contract. vm.mockCall(_proxy, abi.encodeCall(ProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner)); - // Expect no revert. harness.assertSharedProxyAdminOwner(_proxy); } @@ -154,17 +148,11 @@ contract ProxyAdminOwnedBase_assertSharedProxyAdminOwner_Test is ProxyAdminOwned ) public { - // Assume the provided proxy is not a forge address. assumeNotForgeAddress(_proxy); - assumeNotForgeAddress(_otherProxyOwner); - - // Assume the other proxy owner is not the same as the current owner. vm.assume(_otherProxyOwner != proxyAdminOwner); - // Mock the proxyAdminOwner function to return the other proxy owner. vm.mockCall(_proxy, abi.encodeCall(ProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(_otherProxyOwner)); - // Expect a revert. vm.expectRevert(ProxyAdminOwnedBase.ProxyAdminOwnedBase_NotSharedProxyAdminOwner.selector); harness.assertSharedProxyAdminOwner(_proxy); } @@ -174,10 +162,7 @@ contract ProxyAdminOwnedBase_assertOnlyProxyAdmin_Test is ProxyAdminOwnedBase_Te /// @notice Tests that the assertOnlyProxyAdmin function does not revert if the caller is the /// ProxyAdmin. function test_assertOnlyProxyAdmin_proxyAdmin_succeeds() public { - // Prank as the ProxyAdmin. vm.prank(address(proxyAdmin)); - - // Expect no revert. harness.assertOnlyProxyAdmin(); } @@ -185,11 +170,9 @@ contract ProxyAdminOwnedBase_assertOnlyProxyAdmin_Test is ProxyAdminOwnedBase_Te /// ProxyAdmin. /// @param _sender The address of the sender to test. function test_assertOnlyProxyAdmin_notProxyAdmin_reverts(address _sender) public { - // Prank as the not ProxyAdmin. vm.assume(_sender != address(proxyAdmin)); vm.prank(_sender); - // Expect a revert. vm.expectRevert(ProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdmin.selector); harness.assertOnlyProxyAdmin(); } @@ -199,10 +182,7 @@ contract ProxyAdminOwnedBase_assertOnlyProxyAdminOwner_Test is ProxyAdminOwnedBa /// @notice Tests that the assertOnlyProxyAdminOwner function does not revert if the caller is /// the ProxyAdmin owner. function test_assertOnlyProxyAdminOwner_proxyAdminOwner_succeeds() public { - // Prank as the ProxyAdmin owner. vm.prank(proxyAdminOwner); - - // Expect no revert. harness.assertOnlyProxyAdminOwner(); } @@ -210,11 +190,9 @@ contract ProxyAdminOwnedBase_assertOnlyProxyAdminOwner_Test is ProxyAdminOwnedBa /// ProxyAdmin owner. /// @param _sender The address of the sender to test. function test_assertOnlyProxyAdminOwner_notProxyAdminOwner_reverts(address _sender) public { - // Prank as the not ProxyAdmin owner. vm.assume(_sender != proxyAdminOwner); vm.prank(_sender); - // Expect a revert. vm.expectRevert(ProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOwner.selector); harness.assertOnlyProxyAdminOwner(); } @@ -224,20 +202,14 @@ contract ProxyAdminOwnedBase_assertOnlyProxyAdminOrProxyAdminOwner_Test is Proxy /// @notice Tests that the assertOnlyProxyAdminOrProxyAdminOwner function does not revert if /// the caller is the ProxyAdmin or the ProxyAdmin owner. function test_assertOnlyProxyAdminOrProxyAdminOwner_proxyAdmin_succeeds() public { - // Prank as the ProxyAdmin. vm.prank(address(proxyAdmin)); - - // Expect no revert. harness.assertOnlyProxyAdminOrProxyAdminOwner(); } /// @notice Tests that the assertOnlyProxyAdminOrProxyAdminOwner function does not revert if /// the caller is the ProxyAdmin owner. function test_assertOnlyProxyAdminOrProxyAdminOwner_proxyAdminOwner_succeeds() public { - // Prank as the ProxyAdmin owner. vm.prank(proxyAdminOwner); - - // Expect no revert. harness.assertOnlyProxyAdminOrProxyAdminOwner(); } @@ -245,11 +217,9 @@ contract ProxyAdminOwnedBase_assertOnlyProxyAdminOrProxyAdminOwner_Test is Proxy /// is not the ProxyAdmin or the ProxyAdmin owner. /// @param _sender The address of the sender to test. function test_assertOnlyProxyAdminOrProxyAdminOwner_notProxyAdminOrProxyAdminOwner_reverts(address _sender) public { - // Prank as the not ProxyAdmin or ProxyAdmin owner. vm.assume(_sender != address(proxyAdmin) && _sender != proxyAdminOwner); vm.prank(_sender); - // Expect a revert. vm.expectRevert(ProxyAdminOwnedBase.ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner.selector); harness.assertOnlyProxyAdminOrProxyAdminOwner(); } From 93b5b10cd2bfc64a235560eaff730f25d67d46b0 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 17:14:19 -0400 Subject: [PATCH 129/135] Refactor ReinitializableBase tests: change visibility of test functions to external, streamline constructor tests by directly asserting initialization values, and enhance clarity in test logic for improved maintainability. --- test/universal/ReinitializableBase.t.sol | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/test/universal/ReinitializableBase.t.sol b/test/universal/ReinitializableBase.t.sol index 5c7055cb..58f3445b 100644 --- a/test/universal/ReinitializableBase.t.sol +++ b/test/universal/ReinitializableBase.t.sol @@ -7,27 +7,18 @@ import { Test } from "lib/forge-std/src/Test.sol"; // Contracts import { ReinitializableBase } from "src/universal/ReinitializableBase.sol"; -/// @title ReinitializableBase_Harness -/// @notice Harness contract to allow direct instantiation and testing of `ReinitializableBase` -/// logic. contract ReinitializableBase_Harness is ReinitializableBase { constructor(uint8 _initVersion) ReinitializableBase(_initVersion) { } } -/// @title ReinitializableBase_Constructor_Test -/// @notice Tests the constructor of the `ReinitializableBase` contract. contract ReinitializableBase_Constructor_Test is Test { - /// @notice Tests that the contract creation reverts when init version is zero. - function test_constructor_zeroVersion_reverts() public { + function test_constructor_zeroVersion_reverts() external { vm.expectRevert(ReinitializableBase.ReinitializableBase_ZeroInitVersion.selector); new ReinitializableBase_Harness(0); } - /// @notice Tests that constructor succeeds with valid non-zero init versions. - /// @param _initVersion Init version to use when creating the contract. - function testFuzz_constructor_validVersion_succeeds(uint8 _initVersion) public { + function testFuzz_constructor_validVersion_succeeds(uint8 _initVersion) external { _initVersion = uint8(bound(_initVersion, 1, type(uint8).max)); - ReinitializableBase_Harness harness = new ReinitializableBase_Harness(_initVersion); - assertEq(harness.initVersion(), _initVersion); + assertEq(new ReinitializableBase_Harness(_initVersion).initVersion(), _initVersion); } } From 1b9319a2fdf6c5158f0661b5290178ecc8f86fda Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 17:18:47 -0400 Subject: [PATCH 130/135] Refactor StandardBridge tests: update import paths for legacy interfaces, replace hardcoded addresses with constants for improved readability, and streamline assertions in test cases for better maintainability. --- test/universal/StandardBridge.t.sol | 50 +++++++++-------------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/test/universal/StandardBridge.t.sol b/test/universal/StandardBridge.t.sol index 9e9ce971..dcf46190 100644 --- a/test/universal/StandardBridge.t.sol +++ b/test/universal/StandardBridge.t.sol @@ -3,15 +3,15 @@ pragma solidity 0.8.15; import { StandardBridge } from "src/universal/StandardBridge.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; -import { OptimismMintableERC20, ILegacyMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; +import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; +import { ILegacyMintableERC20 } from "interfaces/legacy/ILegacyMintableERC20.sol"; import { ERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import { IERC165 } from "lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol"; /// @title StandardBridgeTester /// @notice Simple wrapper around the StandardBridge contract that exposes /// internal functions so they can be more easily tested directly. contract StandardBridgeTester is StandardBridge { - constructor() StandardBridge() { } - function isOptimismMintableERC20(address _token) external view returns (bool) { return _isOptimismMintableERC20(_token); } @@ -36,15 +36,9 @@ contract LegacyMintable is ERC20 { function burn(address _from, uint256 _amount) external pure { } - /// @notice Implements ERC165. This implementation should not be changed as - /// it is how the actual legacy optimism mintable token does the - /// check. Allows for testing against code that is has been deployed, - /// assuming different compiler version is no problem. + /// @notice Matches the deployed legacy mintable token's ERC165 support. function supportsInterface(bytes4 _interfaceId) external pure returns (bool) { - bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165 - bytes4 secondSupportedInterface = ILegacyMintableERC20.l1Token.selector ^ ILegacyMintableERC20.mint.selector - ^ ILegacyMintableERC20.burn.selector; - return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface; + return _interfaceId == type(IERC165).interfaceId || _interfaceId == type(ILegacyMintableERC20).interfaceId; } } @@ -53,9 +47,10 @@ contract LegacyMintable is ERC20 { /// @dev This setup is primarily for tests focusing on internal stateless logic or default states /// of the `StandardBridge` contract. abstract contract StandardBridge_TestInit is CommonTest { + address internal constant OTHER_TOKEN = address(0x20); + StandardBridgeTester internal bridge; OptimismMintableERC20 internal mintable; - ERC20 internal erc20; LegacyMintable internal legacy; function setUp() public override { @@ -67,7 +62,6 @@ abstract contract StandardBridge_TestInit is CommonTest { _bridge: address(0), _remoteToken: address(0), _name: "Stonks", _symbol: "STONK", _decimals: 18 }); - erc20 = new ERC20("Altcoin", "ALT"); legacy = new LegacyMintable("Legacy", "LEG"); } } @@ -84,40 +78,28 @@ contract StandardBridge_Paused_Test is StandardBridge_TestInit { /// @title StandardBridge_IsOptimismMintableERC20_Test /// @notice Tests the `_isOptimismMintableERC20` internal function of `StandardBridge`. contract StandardBridge_IsOptimismMintableERC20_Test is StandardBridge_TestInit { - /// @notice Test coverage for identifying OptimismMintableERC20 tokens. This function should - /// return true for both modern and legacy OptimismMintableERC20 tokens and false for - /// any accounts that do not implement the interface. function test_isOptimismMintableERC20_succeeds() external view { - // Both the modern and legacy mintable tokens should return true assertTrue(bridge.isOptimismMintableERC20(address(mintable))); assertTrue(bridge.isOptimismMintableERC20(address(legacy))); - // A regular ERC20 should return false - assertFalse(bridge.isOptimismMintableERC20(address(erc20))); - // Non existent contracts should return false and not revert - assertEq(address(0x20).code.length, 0); - assertFalse(bridge.isOptimismMintableERC20(address(0x20))); + assertFalse(bridge.isOptimismMintableERC20(address(L1Token))); + + assertEq(OTHER_TOKEN.code.length, 0); + assertFalse(bridge.isOptimismMintableERC20(OTHER_TOKEN)); } } /// @title StandardBridge_IsCorrectTokenPair_Test /// @notice Tests the `_isCorrectTokenPair` internal function of `StandardBridge`. contract StandardBridge_IsCorrectTokenPair_Test is StandardBridge_TestInit { - /// @notice Test coverage of `isCorrectTokenPair` under different types of tokens. function test_isCorrectTokenPair_succeeds() external { - // Modern + known to be correct remote token assertTrue(bridge.isCorrectTokenPair(address(mintable), mintable.remoteToken())); - // Modern + known to be correct l1Token (legacy interface) assertTrue(bridge.isCorrectTokenPair(address(mintable), mintable.l1Token())); - // Modern + known to be incorrect remote token - assertTrue(mintable.remoteToken() != address(0x20)); - assertFalse(bridge.isCorrectTokenPair(address(mintable), address(0x20))); - // Legacy + known to be correct l1Token + assertFalse(bridge.isCorrectTokenPair(address(mintable), OTHER_TOKEN)); + assertTrue(bridge.isCorrectTokenPair(address(legacy), legacy.l1Token())); - // Legacy + known to be incorrect l1Token - assertTrue(legacy.l1Token() != address(0x20)); - assertFalse(bridge.isCorrectTokenPair(address(legacy), address(0x20))); - // A token that doesn't support either modern or legacy interface will revert + assertFalse(bridge.isCorrectTokenPair(address(legacy), OTHER_TOKEN)); + vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args - bridge.isCorrectTokenPair(address(erc20), address(1)); + bridge.isCorrectTokenPair(address(L1Token), address(1)); } } From 571576a5357dfe0ed288fa2ee20c60bc946723c8 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 17:24:43 -0400 Subject: [PATCH 131/135] Refactor WETH98 tests: update contract initialization to use the WETH98 implementation directly, introduce a helper function for deposit logic, and enhance test clarity by reducing redundancy in deposit calls. --- test/universal/WETH98.t.sol | 53 +++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/test/universal/WETH98.t.sol b/test/universal/WETH98.t.sol index 323a4ddb..17316686 100644 --- a/test/universal/WETH98.t.sol +++ b/test/universal/WETH98.t.sol @@ -5,8 +5,7 @@ pragma solidity 0.8.15; import { Test } from "lib/forge-std/src/Test.sol"; // Contracts -import { IWETH98 } from "interfaces/universal/IWETH98.sol"; -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; +import { WETH98 } from "src/universal/WETH98.sol"; /// @title WETH98_TestInit /// @notice Reusable test initialization for `WETH98` tests. @@ -16,19 +15,20 @@ abstract contract WETH98_TestInit is Test { event Deposit(address indexed dst, uint256 wad); event Withdrawal(address indexed src, uint256 wad); - IWETH98 public weth; + WETH98 public weth; address alice; address bob; function setUp() public { - weth = IWETH98( - DeployUtils.create1({ - _name: "WETH98", _args: DeployUtils.encodeConstructor(abi.encodeCall(IWETH98.__constructor__, ())) - }) - ); + weth = new WETH98(); alice = makeAddr("alice"); bob = makeAddr("bob"); - deal(alice, 1 ether); + } + + function _depositAlice(uint256 _amount) internal { + deal(alice, _amount); + vm.prank(alice); + weth.deposit{ value: _amount }(); } } @@ -36,6 +36,7 @@ abstract contract WETH98_TestInit is Test { /// @notice Tests the `receive` function of the `WETH98` contract. contract WETH98_Receive_Test is WETH98_TestInit { function test_receive_succeeds() public { + deal(alice, 1 ether); vm.expectEmit(address(weth)); emit Deposit(alice, 1 ether); vm.prank(alice); @@ -49,6 +50,7 @@ contract WETH98_Receive_Test is WETH98_TestInit { /// @notice Tests the `fallback` function of the `WETH98` contract. contract WETH98_Fallback_Test is WETH98_TestInit { function test_fallback_succeeds() public { + deal(alice, 1 ether); vm.expectEmit(address(weth)); emit Deposit(alice, 1 ether); vm.prank(alice); @@ -62,6 +64,7 @@ contract WETH98_Fallback_Test is WETH98_TestInit { /// @notice Tests the `deposit` function of the `WETH98` contract. contract WETH98_Deposit_Test is WETH98_TestInit { function test_deposit_succeeds() public { + deal(alice, 1 ether); vm.expectEmit(address(weth)); emit Deposit(alice, 1 ether); vm.prank(alice); @@ -74,8 +77,7 @@ contract WETH98_Deposit_Test is WETH98_TestInit { /// @notice Tests the `withdraw` function of the `WETH98` contract. contract WETH98_Withdraw_Test is WETH98_TestInit { function test_withdraw_succeeds() public { - vm.prank(alice); - weth.deposit{ value: 1 ether }(); + _depositAlice(1 ether); vm.expectEmit(address(weth)); emit Withdrawal(alice, 1 ether); vm.prank(alice); @@ -84,8 +86,7 @@ contract WETH98_Withdraw_Test is WETH98_TestInit { } function test_withdraw_partialWithdrawal_succeeds() public { - vm.prank(alice); - weth.deposit{ value: 1 ether }(); + _depositAlice(1 ether); vm.expectEmit(address(weth)); emit Withdrawal(alice, 1 ether / 2); vm.prank(alice); @@ -94,9 +95,8 @@ contract WETH98_Withdraw_Test is WETH98_TestInit { } function test_withdraw_tooLargeWithdrawal_fails() public { - vm.prank(alice); - weth.deposit{ value: 1 ether }(); - vm.expectRevert(); + _depositAlice(1 ether); + vm.expectRevert(bytes("")); vm.prank(alice); weth.withdraw(1 ether + 1); } @@ -118,8 +118,7 @@ contract WETH98_Approve_Test is WETH98_TestInit { /// @notice Tests the `transfer` function of the `WETH98` contract. contract WETH98_Transfer_Test is WETH98_TestInit { function test_transfer_succeeds() public { - vm.prank(alice); - weth.deposit{ value: 1 ether }(); + _depositAlice(1 ether); vm.expectEmit(address(weth)); emit Transfer(alice, bob, 1 ether); vm.prank(alice); @@ -129,9 +128,8 @@ contract WETH98_Transfer_Test is WETH98_TestInit { } function test_transfer_tooLarge_fails() public { - vm.prank(alice); - weth.deposit{ value: 1 ether }(); - vm.expectRevert(); + _depositAlice(1 ether); + vm.expectRevert(bytes("")); vm.prank(alice); weth.transfer(bob, 1 ether + 1); } @@ -141,8 +139,7 @@ contract WETH98_Transfer_Test is WETH98_TestInit { /// @notice Tests the `transferFrom` function of the `WETH98` contract. contract WETH98_TransferFrom_Test is WETH98_TestInit { function test_transferFrom_succeeds() public { - vm.prank(alice); - weth.deposit{ value: 1 ether }(); + _depositAlice(1 ether); vm.prank(alice); weth.approve(bob, 1 ether); vm.expectEmit(address(weth)); @@ -154,21 +151,19 @@ contract WETH98_TransferFrom_Test is WETH98_TestInit { } function test_transferFrom_tooLittleApproval_fails() public { - vm.prank(alice); - weth.deposit{ value: 1 ether }(); + _depositAlice(1 ether); vm.prank(alice); weth.approve(bob, 1 ether); - vm.expectRevert(); + vm.expectRevert(bytes("")); vm.prank(bob); weth.transferFrom(alice, bob, 1 ether + 1); } function test_transferFrom_tooLittleBalance_fails() public { - vm.prank(alice); - weth.deposit{ value: 1 ether }(); + _depositAlice(1 ether); vm.prank(alice); weth.approve(bob, 2 ether); - vm.expectRevert(); + vm.expectRevert(bytes("")); vm.prank(bob); weth.transferFrom(alice, bob, 1 ether + 1); } From 65d6e8d2a750c03d38a777bb9ce1a8f568f3c543 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 17:33:08 -0400 Subject: [PATCH 132/135] Refactor CBMulticall tests: streamline call creation with helper functions, enhance error handling in delegate calls, and improve clarity in test assertions for better maintainability. --- test/utils/CBMulticall.t.sol | 184 +++++++++++++++++------------------ 1 file changed, 91 insertions(+), 93 deletions(-) diff --git a/test/utils/CBMulticall.t.sol b/test/utils/CBMulticall.t.sol index 28136e2b..19b0078d 100644 --- a/test/utils/CBMulticall.t.sol +++ b/test/utils/CBMulticall.t.sol @@ -4,9 +4,7 @@ pragma solidity 0.8.25; import { CBMulticall, Call, Call3, Call3Value, Result } from "src/universal/CBMulticall.sol"; import { Test } from "lib/forge-std/src/Test.sol"; -/// @dev Helper contract used to invoke `aggregateDelegateCalls` via `delegatecall`. -/// This simulates the intended multisig usage pattern where the multicall -/// logic is executed in the context of another contract. +/// @dev Invokes `aggregateDelegateCalls` through `delegatecall` to simulate multisig execution. contract CBMulticallDelegateCaller { CBMulticall public mc; @@ -15,8 +13,13 @@ contract CBMulticallDelegateCaller { } function aggregateDelegateCalls(Call3[] calldata calls) external returns (Result[] memory) { - (, bytes memory data) = + (bool success, bytes memory data) = address(mc).delegatecall(abi.encodeWithSelector(CBMulticall.aggregateDelegateCalls.selector, calls)); + if (!success) { + assembly ("memory-safe") { + revert(add(data, 0x20), mload(data)) + } + } return abi.decode(data, (Result[])); } } @@ -36,20 +39,40 @@ contract MockReceiver { } contract CBMulticallTest is Test { + string internal constant MULTICALL_CALL_FAILED = "Multicall3: call failed"; + CBMulticall mc; MockReceiver target; - CBMulticallDelegateCaller delegateCaller; function setUp() public { mc = new CBMulticall(); target = new MockReceiver(); - delegateCaller = new CBMulticallDelegateCaller(mc); + } + + function _bumpCall(uint256 x) internal view returns (Call memory) { + return Call({ target: address(target), callData: abi.encodeCall(MockReceiver.bump, (x)) }); + } + + function _revertingCall() internal view returns (Call memory) { + return Call({ target: address(target), callData: abi.encodeCall(MockReceiver.willRevert, ()) }); + } + + function _bumpCall3(bool allowFailure, uint256 x) internal view returns (Call3 memory) { + return Call3({ + target: address(target), allowFailure: allowFailure, callData: abi.encodeCall(MockReceiver.bump, (x)) + }); + } + + function _revertingCall3(bool allowFailure) internal view returns (Call3 memory) { + return Call3({ + target: address(target), allowFailure: allowFailure, callData: abi.encodeCall(MockReceiver.willRevert, ()) + }); } function test_aggregate_returnsBlockNumberAndData() external { Call[] memory calls = new Call[](2); - calls[0] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 41) }); - calls[1] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 1) }); + calls[0] = _bumpCall(41); + calls[1] = _bumpCall(1); (uint256 bn, bytes[] memory rdata) = mc.aggregate(calls); assertEq(bn, block.number); @@ -59,16 +82,16 @@ contract CBMulticallTest is Test { function test_aggregate_revertsOnFailedCall() external { Call[] memory calls = new Call[](2); - calls[0] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 0) }); - calls[1] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) }); - vm.expectRevert(bytes("Multicall3: call failed")); + calls[0] = _bumpCall(0); + calls[1] = _revertingCall(); + vm.expectRevert(bytes(MULTICALL_CALL_FAILED)); mc.aggregate(calls); } function test_tryAggregate_noRequire_returnsResults() external { Call[] memory calls = new Call[](2); - calls[0] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 1) }); - calls[1] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) }); + calls[0] = _bumpCall(1); + calls[1] = _revertingCall(); Result[] memory results = mc.tryAggregate(false, calls); assertEq(results.length, 2); @@ -79,16 +102,16 @@ contract CBMulticallTest is Test { function test_tryAggregate_requireSuccess_revertsOnFailure() external { Call[] memory calls = new Call[](2); - calls[0] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 0) }); - calls[1] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) }); - vm.expectRevert(bytes("Multicall3: call failed")); + calls[0] = _bumpCall(0); + calls[1] = _revertingCall(); + vm.expectRevert(bytes(MULTICALL_CALL_FAILED)); mc.tryAggregate(true, calls); } function test_tryBlockAndAggregate_noRequire_returnsBlockInfoAndResults() external { Call[] memory calls = new Call[](2); - calls[0] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 0) }); - calls[1] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) }); + calls[0] = _bumpCall(0); + calls[1] = _revertingCall(); (uint256 bn, bytes32 bh, Result[] memory res) = mc.tryBlockAndAggregate(false, calls); assertEq(bn, block.number); @@ -100,8 +123,8 @@ contract CBMulticallTest is Test { function test_blockAndAggregate_allSuccess_returnsResults() external { Call[] memory calls = new Call[](2); - calls[0] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 1) }); - calls[1] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 2) }); + calls[0] = _bumpCall(1); + calls[1] = _bumpCall(2); (uint256 bn, bytes32 bh, Result[] memory res) = mc.blockAndAggregate(calls); assertEq(bn, block.number); assertEq(bh, blockhash(block.number)); @@ -111,126 +134,101 @@ contract CBMulticallTest is Test { function test_blockAndAggregate_revertsOnFailure() external { Call[] memory calls = new Call[](2); - calls[0] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 0) }); - calls[1] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) }); - vm.expectRevert(bytes("Multicall3: call failed")); + calls[0] = _bumpCall(0); + calls[1] = _revertingCall(); + vm.expectRevert(bytes(MULTICALL_CALL_FAILED)); mc.blockAndAggregate(calls); } function test_tryBlockAndAggregate_requireSuccess_revertsOnFailure() external { Call[] memory calls = new Call[](2); - calls[0] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.bump.selector, 0) }); - calls[1] = Call({ target: address(target), callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) }); - vm.expectRevert(bytes("Multicall3: call failed")); + calls[0] = _bumpCall(0); + calls[1] = _revertingCall(); + vm.expectRevert(bytes(MULTICALL_CALL_FAILED)); mc.tryBlockAndAggregate(true, calls); } function test_aggregate3_success() external { - Call3[] memory calls3 = new Call3[](1); - calls3[0] = Call3({ - target: address(target), - allowFailure: false, - callData: abi.encodeWithSelector(MockReceiver.bump.selector, 4) - }); - Result[] memory ret3 = mc.aggregate3(calls3); - assertTrue(ret3[0].success); - assertEq(abi.decode(ret3[0].returnData, (uint256)), 5); + Call3[] memory calls = new Call3[](1); + calls[0] = _bumpCall3(false, 4); + Result[] memory results = mc.aggregate3(calls); + assertTrue(results[0].success); + assertEq(abi.decode(results[0].returnData, (uint256)), 5); } function test_aggregate3_allowedFailure_returnsFalse() external { - Call3[] memory calls3 = new Call3[](1); - calls3[0] = Call3({ - target: address(target), - allowFailure: true, - callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) - }); - Result[] memory ret3 = mc.aggregate3(calls3); - assertFalse(ret3[0].success); + Call3[] memory calls = new Call3[](1); + calls[0] = _revertingCall3(true); + Result[] memory results = mc.aggregate3(calls); + assertFalse(results[0].success); } function test_aggregate3_revertsOnNonAllowedFailure() external { - Call3[] memory calls3 = new Call3[](1); - calls3[0] = Call3({ - target: address(target), - allowFailure: false, - callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) - }); - vm.expectRevert(bytes("Multicall3: call failed")); - mc.aggregate3(calls3); + Call3[] memory calls = new Call3[](1); + calls[0] = _revertingCall3(false); + vm.expectRevert(bytes(MULTICALL_CALL_FAILED)); + mc.aggregate3(calls); } function test_aggregateDelegateCalls_success() external { - Call3[] memory calls3 = new Call3[](1); - calls3[0] = Call3({ - target: address(target), - allowFailure: false, - callData: abi.encodeWithSelector(MockReceiver.bump.selector, 4) - }); - Result[] memory ret3 = delegateCaller.aggregateDelegateCalls(calls3); - assertTrue(ret3[0].success); - assertEq(abi.decode(ret3[0].returnData, (uint256)), 5); + CBMulticallDelegateCaller caller = new CBMulticallDelegateCaller(mc); + Call3[] memory calls = new Call3[](1); + calls[0] = _bumpCall3(false, 4); + Result[] memory results = caller.aggregateDelegateCalls(calls); + assertTrue(results[0].success); + assertEq(abi.decode(results[0].returnData, (uint256)), 5); } function test_aggregateDelegateCalls_allowedFailure_returnsFalse() external { - Call3[] memory calls3 = new Call3[](1); - calls3[0] = Call3({ - target: address(target), - allowFailure: true, - callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) - }); - Result[] memory ret3 = delegateCaller.aggregateDelegateCalls(calls3); - assertFalse(ret3[0].success); + CBMulticallDelegateCaller caller = new CBMulticallDelegateCaller(mc); + Call3[] memory calls = new Call3[](1); + calls[0] = _revertingCall3(true); + Result[] memory results = caller.aggregateDelegateCalls(calls); + assertFalse(results[0].success); } function test_aggregateDelegateCalls_revertsOnNonAllowedFailure() external { - Call3[] memory calls3 = new Call3[](1); - calls3[0] = Call3({ - target: address(target), - allowFailure: false, - callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) - }); - vm.expectRevert(); - delegateCaller.aggregateDelegateCalls(calls3); + CBMulticallDelegateCaller caller = new CBMulticallDelegateCaller(mc); + Call3[] memory calls = new Call3[](1); + calls[0] = _revertingCall3(false); + vm.expectRevert(bytes(MULTICALL_CALL_FAILED)); + caller.aggregateDelegateCalls(calls); } function test_aggregateDelegateCalls_directCall_revertsWithMustDelegateCall() external { - Call3[] memory calls3 = new Call3[](1); - calls3[0] = Call3({ - target: address(target), - allowFailure: false, - callData: abi.encodeWithSelector(MockReceiver.bump.selector, 1) - }); + Call3[] memory calls = new Call3[](1); + calls[0] = _bumpCall3(false, 1); vm.expectRevert(CBMulticall.MustDelegateCall.selector); - mc.aggregateDelegateCalls(calls3); + mc.aggregateDelegateCalls(calls); } function test_aggregate3Value_success_usesContractBalance() external { vm.deal(address(mc), 1 ether); - Call3Value[] memory callsV = new Call3Value[](1); - callsV[0] = Call3Value({ + Call3Value[] memory calls = new Call3Value[](1); + calls[0] = Call3Value({ target: address(target), allowFailure: false, value: 0.5 ether, - callData: abi.encodeWithSelector(MockReceiver.payAndEcho.selector, 7) + callData: abi.encodeCall(MockReceiver.payAndEcho, (7)) }); - Result[] memory retV = mc.aggregate3Value(callsV); - (uint256 x, uint256 v) = abi.decode(retV[0].returnData, (uint256, uint256)); + Result[] memory results = mc.aggregate3Value(calls); + (uint256 x, uint256 v) = abi.decode(results[0].returnData, (uint256, uint256)); assertEq(x, 7); assertEq(v, 0.5 ether); assertEq(address(target).balance, 0.5 ether); } function test_aggregate3Value_revertsOnNonAllowedFailure() external { - Call3Value[] memory callsV = new Call3Value[](1); - callsV[0] = Call3Value({ + Call3Value[] memory calls = new Call3Value[](1); + calls[0] = Call3Value({ target: address(target), allowFailure: false, value: 0, - callData: abi.encodeWithSelector(MockReceiver.willRevert.selector) + callData: abi.encodeCall(MockReceiver.willRevert, ()) }); - vm.expectRevert(bytes("Multicall3: call failed")); - mc.aggregate3Value(callsV); + vm.expectRevert(bytes(MULTICALL_CALL_FAILED)); + mc.aggregate3Value(calls); } function test_getBlockNumber() external view { From 0b621a524a061357aec800817de847d9cb9b9c89 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 17:35:53 -0400 Subject: [PATCH 133/135] Remove redundant test description in AddressAliasHelper tests for improved clarity and maintainability. --- test/vendor/AddressAliasHelper.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/vendor/AddressAliasHelper.t.sol b/test/vendor/AddressAliasHelper.t.sol index 76747cec..bdeaeb76 100644 --- a/test/vendor/AddressAliasHelper.t.sol +++ b/test/vendor/AddressAliasHelper.t.sol @@ -8,7 +8,6 @@ import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; /// @notice General tests that are not testing any function directly of the `AddressAliasHelper` /// contract or are testing multiple functions at once. contract AddressAliasHelper_Uncategorized_Test is Test { - /// @notice Tests that applying and then undoing an alias results in the original address. function testFuzz_applyAndUndo_succeeds(address _address) external pure { address aliased = AddressAliasHelper.applyL1ToL2Alias(_address); address unaliased = AddressAliasHelper.undoL1ToL2Alias(aliased); From 8f44af21a9c76f769563d986d1e33e6322309d39 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 17:49:10 -0400 Subject: [PATCH 134/135] Refactor Initializable tests: streamline contract initialization by consolidating calldata assignments, remove redundant comments for improved clarity, and enhance maintainability of test setup logic. --- test/vendor/Initializable.t.sol | 238 +++++++++++--------------------- 1 file changed, 79 insertions(+), 159 deletions(-) diff --git a/test/vendor/Initializable.t.sol b/test/vendor/Initializable.t.sol index e36f9fd3..9bdd9df9 100644 --- a/test/vendor/Initializable.t.sol +++ b/test/vendor/Initializable.t.sol @@ -22,9 +22,7 @@ import { TEEProverRegistry } from "src/L1/proofs/tee/TEEProverRegistry.sol"; /// @title Initializer_Test /// @dev Ensures that the `initialize()` function on contracts cannot be called more than -/// once. This contract inherits from `ERC721Bridge_Initializer` because it is the -/// deepest contract in the inheritance chain for setting up the system contracts. -/// For each L1 contract both the implementation and the proxy are tested. +/// once. For each L1 contract both the implementation and the proxy are tested. contract Initializer_Test is CommonTest { /// @notice Contains the address of an `Initializable` contract and the calldata /// used to initialize it. @@ -37,263 +35,195 @@ contract Initializer_Test is CommonTest { /// @notice Array of contracts to test. InitializeableContract[] contracts; - /// @notice Mapping of nickname to actual contract name. - /// @dev Nicknames are only used when one proxy contract has multiple potential implementations - /// as can happen when a new implementation is being developed. - mapping(string => string) nicknames; - function setUp() public override { super.setUp(); - // Initialize the `contracts` array with the addresses of the contracts to test, the - // calldata used to initialize them, and the storage slot of their `_initialized` flag. + // Initialize the `contracts` array with the addresses of the contracts to test and the + // calldata used to initialize them. // This array should contain all initializable L1 contracts. L2 contract initialization is // tested in Predeploys.t.sol. - // The 'name' field should be the name of the contract as it saved in the deployment + // The 'name' field should be the name of the contract as it is saved in the deployment // script. - // L1CrossDomainMessengerImpl + bytes memory initCalldata = abi.encodeCall(l1CrossDomainMessenger.initialize, (systemConfig, optimismPortal2)); contracts.push( InitializeableContract({ name: "L1CrossDomainMessengerImpl", target: addressManager.getAddress("OVM_L1CrossDomainMessenger"), - initCalldata: abi.encodeCall(l1CrossDomainMessenger.initialize, (systemConfig, optimismPortal2)) + initCalldata: initCalldata }) ); - // L1CrossDomainMessengerProxy contracts.push( InitializeableContract({ - name: "L1CrossDomainMessengerProxy", - target: address(l1CrossDomainMessenger), - initCalldata: abi.encodeCall(l1CrossDomainMessenger.initialize, (systemConfig, optimismPortal2)) + name: "L1CrossDomainMessengerProxy", target: address(l1CrossDomainMessenger), initCalldata: initCalldata }) ); - // DisputeGameFactoryImpl + + initCalldata = abi.encodeCall(disputeGameFactory.initialize, (address(0))); contracts.push( InitializeableContract({ name: "DisputeGameFactoryImpl", target: EIP1967Helper.getImplementation(address(disputeGameFactory)), - initCalldata: abi.encodeCall(disputeGameFactory.initialize, (address(0))) + initCalldata: initCalldata }) ); - // DisputeGameFactoryProxy contracts.push( InitializeableContract({ - name: "DisputeGameFactoryProxy", - target: address(disputeGameFactory), - initCalldata: abi.encodeCall(disputeGameFactory.initialize, (address(0))) + name: "DisputeGameFactoryProxy", target: address(disputeGameFactory), initCalldata: initCalldata }) ); - // DelayedWETHImpl + + initCalldata = abi.encodeCall(delayedWeth.initialize, (ISystemConfig(address(0)))); contracts.push( InitializeableContract({ name: "DelayedWETHImpl", target: EIP1967Helper.getImplementation(address(delayedWeth)), - initCalldata: abi.encodeCall(delayedWeth.initialize, (ISystemConfig(address(0)))) + initCalldata: initCalldata }) ); - // DelayedWETHProxy contracts.push( InitializeableContract({ - name: "DelayedWETHProxy", - target: address(delayedWeth), - initCalldata: abi.encodeCall(delayedWeth.initialize, (ISystemConfig(address(0)))) + name: "DelayedWETHProxy", target: address(delayedWeth), initCalldata: initCalldata }) ); - // OptimismPortal2Impl + initCalldata = abi.encodeCall(optimismPortal2.initialize, (systemConfig, anchorStateRegistry)); contracts.push( InitializeableContract({ name: "OptimismPortal2Impl", target: EIP1967Helper.getImplementation(address(optimismPortal2)), - initCalldata: abi.encodeCall(optimismPortal2.initialize, (systemConfig, anchorStateRegistry)) + initCalldata: initCalldata }) ); - // OptimismPortal2Proxy contracts.push( InitializeableContract({ - name: "OptimismPortal2Proxy", - target: address(optimismPortal2), - initCalldata: abi.encodeCall(optimismPortal2.initialize, (systemConfig, anchorStateRegistry)) + name: "OptimismPortal2Proxy", target: address(optimismPortal2), initCalldata: initCalldata }) ); - // SystemConfigImpl + initCalldata = abi.encodeCall( + systemConfig.initialize, + ( + address(0xdead), + 0, + 0, + bytes32(0), + 1, + address(0), + IResourceMetering.ResourceConfig({ + maxResourceLimit: 1, + elasticityMultiplier: 1, + baseFeeMaxChangeDenominator: 2, + minimumBaseFee: 0, + systemTxMaxGas: 0, + maximumBaseFee: 0 + }), + address(0), + ISystemConfig.Addresses({ + l1CrossDomainMessenger: address(0), + l1ERC721Bridge: address(0), + l1StandardBridge: address(0), + optimismPortal: address(0), + optimismMintableERC20Factory: address(0), + delayedWETH: address(0) + }), + 0, + ISuperchainConfig(address(0)) + ) + ); contracts.push( InitializeableContract({ name: "SystemConfigImpl", target: EIP1967Helper.getImplementation(address(systemConfig)), - initCalldata: abi.encodeCall( - systemConfig.initialize, - ( - address(0xdead), - 0, - 0, - bytes32(0), - 1, - address(0), - IResourceMetering.ResourceConfig({ - maxResourceLimit: 1, - elasticityMultiplier: 1, - baseFeeMaxChangeDenominator: 2, - minimumBaseFee: 0, - systemTxMaxGas: 0, - maximumBaseFee: 0 - }), - address(0), - ISystemConfig.Addresses({ - l1CrossDomainMessenger: address(0), - l1ERC721Bridge: address(0), - l1StandardBridge: address(0), - optimismPortal: address(0), - optimismMintableERC20Factory: address(0), - delayedWETH: address(0) - }), - 0, - ISuperchainConfig(address(0)) - ) - ) + initCalldata: initCalldata }) ); - // SystemConfigProxy contracts.push( InitializeableContract({ - name: "SystemConfigProxy", - target: address(systemConfig), - initCalldata: abi.encodeCall( - systemConfig.initialize, - ( - address(0xdead), - 0, - 0, - bytes32(0), - 1, - address(0), - IResourceMetering.ResourceConfig({ - maxResourceLimit: 1, - elasticityMultiplier: 1, - baseFeeMaxChangeDenominator: 2, - minimumBaseFee: 0, - systemTxMaxGas: 0, - maximumBaseFee: 0 - }), - address(0), - ISystemConfig.Addresses({ - l1CrossDomainMessenger: address(0), - l1ERC721Bridge: address(0), - l1StandardBridge: address(0), - optimismPortal: address(0), - optimismMintableERC20Factory: address(0), - delayedWETH: address(0) - }), - 0, - ISuperchainConfig(address(0)) - ) - ) + name: "SystemConfigProxy", target: address(systemConfig), initCalldata: initCalldata }) ); - // L1StandardBridgeImpl + + initCalldata = abi.encodeCall(l1StandardBridge.initialize, (l1CrossDomainMessenger, systemConfig)); contracts.push( InitializeableContract({ name: "L1StandardBridgeImpl", target: EIP1967Helper.getImplementation(address(l1StandardBridge)), - initCalldata: abi.encodeCall(l1StandardBridge.initialize, (l1CrossDomainMessenger, systemConfig)) + initCalldata: initCalldata }) ); - // L1StandardBridgeProxy contracts.push( InitializeableContract({ - name: "L1StandardBridgeProxy", - target: address(l1StandardBridge), - initCalldata: abi.encodeCall(l1StandardBridge.initialize, (l1CrossDomainMessenger, systemConfig)) + name: "L1StandardBridgeProxy", target: address(l1StandardBridge), initCalldata: initCalldata }) ); - // L1ERC721BridgeImpl + + initCalldata = abi.encodeCall(l1ERC721Bridge.initialize, (l1CrossDomainMessenger, systemConfig)); contracts.push( InitializeableContract({ name: "L1ERC721BridgeImpl", target: EIP1967Helper.getImplementation(address(l1ERC721Bridge)), - initCalldata: abi.encodeCall(l1ERC721Bridge.initialize, (l1CrossDomainMessenger, systemConfig)) + initCalldata: initCalldata }) ); - // L1ERC721BridgeProxy contracts.push( InitializeableContract({ - name: "L1ERC721BridgeProxy", - target: address(l1ERC721Bridge), - initCalldata: abi.encodeCall(l1ERC721Bridge.initialize, (l1CrossDomainMessenger, systemConfig)) + name: "L1ERC721BridgeProxy", target: address(l1ERC721Bridge), initCalldata: initCalldata }) ); - // OptimismMintableERC20FactoryImpl + + initCalldata = abi.encodeCall(l1OptimismMintableERC20Factory.initialize, (address(l1StandardBridge))); contracts.push( InitializeableContract({ name: "OptimismMintableERC20FactoryImpl", target: EIP1967Helper.getImplementation(address(l1OptimismMintableERC20Factory)), - initCalldata: abi.encodeCall(l1OptimismMintableERC20Factory.initialize, (address(l1StandardBridge))) + initCalldata: initCalldata }) ); - // OptimismMintableERC20FactoryProxy contracts.push( InitializeableContract({ name: "OptimismMintableERC20FactoryProxy", target: address(l1OptimismMintableERC20Factory), - initCalldata: abi.encodeCall(l1OptimismMintableERC20Factory.initialize, (address(l1StandardBridge))) + initCalldata: initCalldata }) ); - // AnchorStateRegistry + + initCalldata = abi.encodeCall( + anchorStateRegistry.initialize, + ( + ISystemConfig(address(0)), + IDisputeGameFactory(address(0)), + Proposal({ root: Hash.wrap(bytes32(0)), l2SequenceNumber: 0 }), + GameType.wrap(uint32(deploy.cfg().respectedGameType())) + ) + ); contracts.push( InitializeableContract({ name: "AnchorStateRegistryImpl", target: EIP1967Helper.getImplementation(address(anchorStateRegistry)), - initCalldata: abi.encodeCall( - anchorStateRegistry.initialize, - ( - ISystemConfig(address(0)), - IDisputeGameFactory(address(0)), - Proposal({ root: Hash.wrap(bytes32(0)), l2SequenceNumber: 0 }), - GameType.wrap(uint32(deploy.cfg().respectedGameType())) - ) - ) + initCalldata: initCalldata }) ); - // AnchorStateRegistryProxy contracts.push( InitializeableContract({ - name: "AnchorStateRegistryProxy", - target: address(anchorStateRegistry), - initCalldata: abi.encodeCall( - anchorStateRegistry.initialize, - ( - ISystemConfig(address(0)), - IDisputeGameFactory(address(0)), - Proposal({ root: Hash.wrap(bytes32(0)), l2SequenceNumber: 0 }), - GameType.wrap(uint32(deploy.cfg().respectedGameType())) - ) - ) + name: "AnchorStateRegistryProxy", target: address(anchorStateRegistry), initCalldata: initCalldata }) ); // ETHLockbox is only deployed when interop is enabled if (address(ethLockbox) != address(0)) { - // ETHLockboxImpl + initCalldata = abi.encodeCall(ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0))); contracts.push( InitializeableContract({ name: "ETHLockboxImpl", target: EIP1967Helper.getImplementation(address(ethLockbox)), - initCalldata: abi.encodeCall( - ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) - ) + initCalldata: initCalldata }) ); - // ETHLockboxProxy contracts.push( InitializeableContract({ - name: "ETHLockboxProxy", - target: address(ethLockbox), - initCalldata: abi.encodeCall( - ethLockbox.initialize, (ISystemConfig(address(0)), new IOptimismPortal2[](0)) - ) + name: "ETHLockboxProxy", target: address(ethLockbox), initCalldata: initCalldata }) ); } @@ -302,7 +232,6 @@ contract Initializer_Test is CommonTest { // uint8, so it cannot be tested by this framework. It is excluded below. if (address(teeProverRegistry) != address(0)) { - // TEEProverRegistryImpl contracts.push( InitializeableContract({ name: "TEEProverRegistryImpl", @@ -395,11 +324,10 @@ contract Initializer_Test is CommonTest { // Attempt to re-initialize all contracts within the `contracts` array. for (uint256 i; i < contracts.length; i++) { InitializeableContract memory _contract = contracts[i]; - string memory deploymentName = _getRealContractName(_contract.name); // Assert that the contract is already initialized. assertTrue( - ForgeArtifacts.isInitialized({ _name: _removeSuffix(deploymentName), _address: _contract.target }), + ForgeArtifacts.isInitialized({ _name: _removeSuffix(_contract.name), _address: _contract.target }), "Initializable: contract is not initialized" ); @@ -415,20 +343,12 @@ contract Initializer_Test is CommonTest { /// @return matching_ True if the contract is in the `contracts` array, false otherwise. function _hasMatchingContract(string memory _name) internal view returns (bool matching_) { for (uint256 i; i < contracts.length; i++) { - if (LibString.eq(contracts[i].name, _getRealContractName(_name))) { - // return early + if (LibString.eq(contracts[i].name, _name)) { return true; } } } - /// @dev Returns the real name of the contract, including any nicknames. - /// @param _name The name of the contract. - /// @return real_ The real name of the contract. - function _getRealContractName(string memory _name) internal view returns (string memory real_) { - real_ = bytes(nicknames[_name]).length > 0 ? nicknames[_name] : _name; - } - /// @dev Extracts the revert string from returndata encoded in the form of `Error(string)`. function _extractErrorString(bytes memory _returnData) internal pure returns (string memory error_) { // The first 4 bytes of the return data should be the selector for `Error(string)`. If not, revert. From 8a4a2b83822c224a39e072bd06db57f9b85583ee Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Sun, 17 May 2026 17:53:18 -0400 Subject: [PATCH 135/135] Refactor InitializableOZv5 tests: rename struct for clarity, consolidate initialization logic for FeeVault contracts, and enhance test assertions to ensure proper initialization behavior. --- test/vendor/InitializableOZv5.t.sol | 76 +++++++---------------------- 1 file changed, 18 insertions(+), 58 deletions(-) diff --git a/test/vendor/InitializableOZv5.t.sol b/test/vendor/InitializableOZv5.t.sol index 7a4705e2..aac051e1 100644 --- a/test/vendor/InitializableOZv5.t.sol +++ b/test/vendor/InitializableOZv5.t.sol @@ -16,95 +16,55 @@ contract InitializerOZv5_Test is Test { /// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; - /// @notice Contains the address of an `Initializable` contract and the calldata - /// used to initialize it. - struct InitializeableContract { + struct InitializableContract { address target; bytes initCalldata; } - /// @notice Contains the addresses of the contracts to test as well as the calldata - /// used to initialize them. - InitializeableContract[] contracts; + InitializableContract[] private contracts; function setUp() public { - // Initialize the `contracts` array with the addresses of the contracts to test and the - // calldata used to initialize them + bytes memory constructorArgs = DeployUtils.encodeConstructor(abi.encodeCall(IFeeVault.__constructor__, ())); + bytes memory initCalldata = abi.encodeCall(IFeeVault.initialize, (address(0), 0, Types.WithdrawalNetwork.L1)); - // BaseFeeVault contracts.push( - InitializeableContract({ - target: address( - DeployUtils.create1({ - _name: "BaseFeeVault", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IFeeVault.__constructor__, ())) - }) - ), - initCalldata: abi.encodeCall(IFeeVault.initialize, (address(0), 0, Types.WithdrawalNetwork.L1)) + InitializableContract({ + target: address(DeployUtils.create1({ _name: "BaseFeeVault", _args: constructorArgs })), + initCalldata: initCalldata }) ); - // OperatorFeeVault contracts.push( - InitializeableContract({ - target: address( - DeployUtils.create1({ - _name: "OperatorFeeVault", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IFeeVault.__constructor__, ())) - }) - ), - initCalldata: abi.encodeCall(IFeeVault.initialize, (address(0), 0, Types.WithdrawalNetwork.L1)) + InitializableContract({ + target: address(DeployUtils.create1({ _name: "OperatorFeeVault", _args: constructorArgs })), + initCalldata: initCalldata }) ); - // SequencerFeeVault contracts.push( - InitializeableContract({ - target: address( - DeployUtils.create1({ - _name: "SequencerFeeVault", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IFeeVault.__constructor__, ())) - }) - ), - initCalldata: abi.encodeCall(IFeeVault.initialize, (address(0), 0, Types.WithdrawalNetwork.L1)) + InitializableContract({ + target: address(DeployUtils.create1({ _name: "SequencerFeeVault", _args: constructorArgs })), + initCalldata: initCalldata }) ); - // L1FeeVault contracts.push( - InitializeableContract({ - target: address( - DeployUtils.create1({ - _name: "L1FeeVault", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IFeeVault.__constructor__, ())) - }) - ), - initCalldata: abi.encodeCall(IFeeVault.initialize, (address(0), 0, Types.WithdrawalNetwork.L1)) + InitializableContract({ + target: address(DeployUtils.create1({ _name: "L1FeeVault", _args: constructorArgs })), + initCalldata: initCalldata }) ); } - /// @notice Tests that: - /// 1. The `initialized` flag of each contract is properly set to `type(uint64).max`, - /// signifying that the contracts are initialized. - /// 2. The `initialize()` function of each contract cannot be called more than once. - /// 3. Returns the correct error when attempting to re-initialize a contract. + /// @notice Ensures OZ v5 initializers are disabled on deployed FeeVault contracts. function test_cannotReinitialize_succeeds() public { - // Attempt to re-initialize all contracts within the `contracts` array. for (uint256 i; i < contracts.length; i++) { - InitializeableContract memory _contract = contracts[i]; - uint256 size; - address target = _contract.target; - assembly { - size := extcodesize(target) - } + InitializableContract memory _contract = contracts[i]; - // Assert that the contract is already initialized. bytes32 slotVal = vm.load(_contract.target, INITIALIZABLE_STORAGE); uint64 initialized = uint64(uint256(slotVal)); assertEq(initialized, type(uint64).max); - // Then, attempt to re-initialize the contract. This should fail. (bool success, bytes memory returnData) = _contract.target.call(_contract.initCalldata); assertFalse(success); assertEq(bytes4(returnData), Initializable.InvalidInitialization.selector);