diff --git a/evm/forge/tests/ForkSlots.t.sol b/evm/forge/tests/ForkSlots.t.sol new file mode 100644 index 0000000..1edf23f --- /dev/null +++ b/evm/forge/tests/ForkSlots.t.sol @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.22; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {IWormhole} from "src/interfaces/IWormhole.sol"; +import {ICircleIntegration} from "src/interfaces/ICircleIntegration.sol"; + +import {Utils} from "src/libraries/Utils.sol"; + +import {Implementation} from "src/contracts/CircleIntegration/Implementation.sol"; + +import { + CircleIntegrationOverride, + CraftedCctpMessageParams, + CraftedVaaParams +} from "test-helpers/libraries/CircleIntegrationOverride.sol"; +import {SlotCheck} from "test-helpers/libraries/SlotCheck.sol"; +import {WormholeOverride} from "test-helpers/libraries/WormholeOverride.sol"; + +contract ForkSlots is Test { + using CircleIntegrationOverride for *; + using WormholeOverride for *; + using Utils for address; + using SlotCheck for *; + + bytes32 constant GOVERNANCE_MODULE = + 0x000000000000000000000000000000436972636c65496e746567726174696f6e; + + address constant FORKED_CIRCLE_INTEGRATION_ADDRESS = 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c; + + ICircleIntegration forked; + address forkedAddress; + + IWormhole wormhole; + + // Expected at slot 0x0. + + uint16 expectedChainId; + uint8 expectedWormholeFinality; + uint32 expectedLocalDomain; + address expectedWormholeAddress; + uint16 expectedGovernanceChainId; + + // Expected at slot 0x1. + bytes32 expectedGovernanceContract; + + // Expected at slot 0x2. + address expectedCircleBridgeAddress; + + // Expected at slot 0x3. + address expectedCircleTransmitterAddress; + + // Expected at slot 0x4. + address expectedCircleTokenMinterAddress; + + // Expected at slot 0xa. + uint256 expectedEvmChain; + + function setUp() public { + forked = ICircleIntegration(FORKED_CIRCLE_INTEGRATION_ADDRESS); + forked.setUpOverride(uint256(vm.envBytes32("TESTING_DEVNET_GUARDIAN"))); + forkedAddress = address(forked); + + wormhole = forked.wormhole(); + + // Set expected values. + expectedChainId = forked.chainId(); + expectedWormholeFinality = forked.wormholeFinality(); + expectedLocalDomain = forked.localDomain(); + expectedWormholeAddress = address(forked.wormhole()); + expectedGovernanceChainId = forked.governanceChainId(); + expectedGovernanceContract = forked.governanceContract(); + expectedCircleBridgeAddress = address(forked.circleBridge()); + expectedCircleTransmitterAddress = address(forked.circleTransmitter()); + expectedCircleTokenMinterAddress = address(forked.circleTokenMinter()); + expectedEvmChain = forked.evmChain(); + } + + function test_UpgradeForkAndCheckSlots() public { + // Deploy new implementation. + Implementation implementation = new Implementation( + address(wormhole), + vm.envAddress("TESTING_CIRCLE_BRIDGE_ADDRESS") // tokenMessenger + ); + + // Should not be initialized yet. + bool isInitialized = forked.isInitialized(address(implementation)); + assertFalse(isInitialized, "already initialized"); + + (IWormhole.VM memory vaa, bytes memory encodedVaa) = wormhole.craftGovernanceVaa( + GOVERNANCE_MODULE, + 3, // action + forked.chainId(), + 69, // sequence + abi.encodePacked(address(implementation).toUniversalAddress()) + ); + + // This VAA should not have been consumed yet. + bool isVaaConsumed = forked.isMessageConsumed(vaa.hash); + assertFalse(isVaaConsumed, "VAA already consumed"); + + // Before upgrading, fetch some expected values. + uint16 expectedRegisteredChainId = 2; + bytes32 expectedEmitter = forked.getRegisteredEmitter(expectedRegisteredChainId); + uint32 expectedCctpDomain = forked.getDomainFromChainId(expectedRegisteredChainId); + + // Check slots before upgrade. + { + bytes32 slotZeroData = vm.load(address(forked), bytes32(0)); + + // If the data is already zeroed, check the remaining zeroed slots. Otherwise check that + // the slots are the expected values from the existing getters. + if (slotZeroData != 0) { + // Now check slots that will be zeroed. + uint256 bitOffset; + + // First 2 bytes is chain ID. + assertEq( + uint16(uint256(slotZeroData >> bitOffset)), + expectedChainId, + "slot 0x0 not equal to expected before upgrade" + ); + bitOffset += 16; + + // Next byte is wormhole finality. + assertEq( + uint8(uint256(slotZeroData >> bitOffset)), + expectedWormholeFinality, + "slot 0x0 not equal to expected before upgrade" + ); + bitOffset += 8; + + // Next 4 bytes is local domain. + assertEq( + uint32(uint256(slotZeroData >> bitOffset)), + expectedLocalDomain, + "slot 0x0 not equal to expected before upgrade" + ); + bitOffset += 32; + + // Next 20 bytes is wormhole address. + assertEq( + address(uint160(uint256(slotZeroData >> bitOffset))), + expectedWormholeAddress, + "slot 0x0 not equal to expected before upgrade" + ); + bitOffset += 160; + + // Next 2 bytes is governance chain ID. + assertEq( + uint16(uint256(slotZeroData >> bitOffset)), + expectedGovernanceChainId, + "slot 0x0 not equal to expected before upgrade" + ); + bitOffset += 16; + + // Remaining bytes are zero. + assertEq( + uint256(slotZeroData >> bitOffset), + 0, + "slot 0x0 not equal to expected before upgrade" + ); + } + if (slotZeroData == 0) { + assertTrue( + forkedAddress.slotValueZero(0x1), + "slot 0x1 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueZero(0x2), + "slot 0x2 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueZero(0x3), + "slot 0x3 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueZero(0x4), + "slot 0x4 not equal to expected before upgrade" + ); + } else { + assertTrue( + forkedAddress.slotValueEquals(0x1, expectedGovernanceContract), + "slot 0x1 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals(0x2, expectedCircleBridgeAddress), + "slot 0x2 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals(0x3, expectedCircleTransmitterAddress), + "slot 0x3 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals(0x4, expectedCircleTokenMinterAddress), + "slot 0x4 not equal to expected before upgrade" + ); + } + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(address(implementation), uint256(0x5))), isInitialized + ), + "mapped slot 0x5 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(expectedRegisteredChainId, uint256(0x6))), expectedEmitter + ), + "mapped slot 0x6 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(expectedRegisteredChainId, uint256(0x7))), + expectedCctpDomain + ), + "mapped slot 0x7 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(expectedCctpDomain, uint256(0x8))), + expectedRegisteredChainId + ), + "mapped slot 0x8 not equal to expected before upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(vaa.hash, uint256(0x9))), isVaaConsumed + ), + "mapped slot 0x9 not equal to expected before upgrade" + ); + if (slotZeroData == 0) { + assertTrue( + forkedAddress.slotValueZero(0xa), + "slot 0xa not equal to expected before upgrade" + ); + } else { + assertTrue( + forkedAddress.slotValueEquals(0xa, expectedEvmChain), + "slot 0xa not equal to expected before upgrade" + ); + } + } + + // Upgrade contract. + forked.upgradeContract(encodedVaa); + + // Now initialized. + isInitialized = forked.isInitialized(address(implementation)); + assertTrue(isInitialized, "implementation not initialized"); + + // VAA now consumed. + isVaaConsumed = forked.isMessageConsumed(vaa.hash); + assertTrue(isVaaConsumed, "VAA not consumed"); + + // Now check all slots that were checked before. + { + assertTrue( + forkedAddress.slotValueZero(bytes32(0)), + "slot 0x0 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueZero(0x1), "slot 0x1 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueZero(0x2), "slot 0x2 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueZero(0x3), "slot 0x3 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueZero(0x4), "slot 0x4 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(address(implementation), uint256(0x5))), isInitialized + ), + "mapped slot 0x5 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(expectedRegisteredChainId, uint256(0x6))), expectedEmitter + ), + "mapped slot 0x6 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(expectedRegisteredChainId, uint256(0x7))), + expectedCctpDomain + ), + "mapped slot 0x7 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(expectedCctpDomain, uint256(0x8))), + expectedRegisteredChainId + ), + "mapped slot 0x8 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueEquals( + keccak256(abi.encode(vaa.hash, uint256(0x9))), isVaaConsumed + ), + "mapped slot 0x9 not equal to expected after upgrade" + ); + assertTrue( + forkedAddress.slotValueZero(0xa), "slot 0xa not equal to expected after upgrade" + ); + } + + // Make sure getters still retrieve expected values. + assertEq(forked.chainId(), expectedChainId, "chainId not equal to expected after upgrade"); + assertEq( + forked.wormholeFinality(), + expectedWormholeFinality, + "wormholeFinality not equal to expected after upgrade" + ); + assertEq( + forked.localDomain(), + expectedLocalDomain, + "localDomain not equal to expected after upgrade" + ); + assertEq( + address(forked.wormhole()), + expectedWormholeAddress, + "wormholeAddress not equal to expected after upgrade" + ); + assertEq( + forked.governanceChainId(), + expectedGovernanceChainId, + "governanceChainId not equal to expected after upgrade" + ); + assertEq( + forked.governanceContract(), + expectedGovernanceContract, + "governanceContract not equal to expected after upgrade" + ); + assertEq( + address(forked.circleBridge()), + expectedCircleBridgeAddress, + "circleBridgeAddress not equal to expected after upgrade" + ); + assertEq( + address(forked.circleTransmitter()), + expectedCircleTransmitterAddress, + "circleTransmitterAddress not equal to expected after upgrade" + ); + assertEq( + address(forked.circleTokenMinter()), + expectedCircleTokenMinterAddress, + "circleTokenMinterAddress not equal to expected after upgrade" + ); + assertEq( + forked.evmChain(), expectedEvmChain, "evmChain not equal to expected after upgrade" + ); + } +} diff --git a/evm/forge/tests/gas/CircleIntegrationComparison.t.sol b/evm/forge/tests/gas/CircleIntegrationComparison.t.sol index 82b74f3..2598d36 100644 --- a/evm/forge/tests/gas/CircleIntegrationComparison.t.sol +++ b/evm/forge/tests/gas/CircleIntegrationComparison.t.sol @@ -38,7 +38,7 @@ contract CircleIntegrationComparison is Test { bytes32 immutable FOREIGN_USDC_ADDRESS = bytes32(uint256(uint160(vm.envAddress("TESTING_FOREIGN_USDC_TOKEN_ADDRESS")))); - address immutable FORKED_CIRCLE_INTEGRATION_ADDRESS = 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c; + address constant FORKED_CIRCLE_INTEGRATION_ADDRESS = 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c; // dependencies IWormhole wormhole; diff --git a/evm/forge/tests/helpers/libraries/SlotCheck.sol b/evm/forge/tests/helpers/libraries/SlotCheck.sol new file mode 100644 index 0000000..6697061 --- /dev/null +++ b/evm/forge/tests/helpers/libraries/SlotCheck.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.22; + +import {Vm} from "forge-std/Vm.sol"; + +library SlotCheck { + address constant VM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); + Vm constant vm = Vm(VM_ADDRESS); + + function slotValueEquals(address contractAddr, uint256 slot, bool expected) + internal + view + returns (bool agrees) + { + agrees = slotValueEquals(contractAddr, bytes32(slot), expected); + } + + function slotValueEquals(address contractAddr, uint256 slot, uint256 expected) + internal + view + returns (bool agrees) + { + agrees = slotValueEquals(contractAddr, bytes32(slot), expected); + } + + function slotValueEquals(address contractAddr, uint256 slot, address expected) + internal + view + returns (bool agrees) + { + agrees = slotValueEquals(contractAddr, bytes32(slot), expected); + } + + function slotValueEquals(address contractAddr, uint256 slot, bytes32 expected) + internal + view + returns (bool agrees) + { + agrees = slotValueEquals(contractAddr, bytes32(slot), expected); + } + + function slotValueEquals(address contractAddr, bytes32 slot, bool expected) + internal + view + returns (bool agrees) + { + agrees = slotValueEquals(contractAddr, slot, uint256(expected ? 1 : 0)); + } + + function slotValueEquals(address contractAddr, bytes32 slot, uint256 expected) + internal + view + returns (bool agrees) + { + agrees = slotValueEquals(contractAddr, slot, bytes32(expected)); + } + + function slotValueEquals(address contractAddr, bytes32 slot, address expected) + internal + view + returns (bool agrees) + { + agrees = slotValueEquals(contractAddr, slot, bytes32(uint256(uint160(expected)))); + } + + function slotValueEquals(address contractAddr, bytes32 slot, bytes32 expected) + internal + view + returns (bool agrees) + { + agrees = vm.load(contractAddr, slot) == expected; + } + + function slotValueZero(address contractAddr, uint256 slot) + internal + view + returns (bool agrees) + { + agrees = slotValueZero(contractAddr, bytes32(slot)); + } + + function slotValueZero(address contractAddr, bytes32 slot) + internal + view + returns (bool agrees) + { + agrees = slotValueEquals(contractAddr, slot, bytes32(0)); + } +} diff --git a/evm/forge/tests/helpers/libraries/WormholeOverride.sol b/evm/forge/tests/helpers/libraries/WormholeOverride.sol index db09557..6151035 100644 --- a/evm/forge/tests/helpers/libraries/WormholeOverride.sol +++ b/evm/forge/tests/helpers/libraries/WormholeOverride.sol @@ -168,4 +168,21 @@ library WormholeOverride { encodedBody ); } + + function craftGovernanceVaa( + IWormhole wormhole, + bytes32 module, + uint8 action, + uint16 targetChain, + uint64 sequence, + bytes memory decree + ) internal view returns (IWormhole.VM memory vaa, bytes memory encoded) { + (vaa, encoded) = craftVaa( + wormhole, + GOVERNANCE_CHAIN_ID, + GOVERNANCE_CONTRACT, + sequence, + abi.encodePacked(module, action, targetChain, decree) + ); + } }