diff --git a/evm/forge/tests/ForkSlots.t.sol b/evm/forge/tests/ForkSlots.t.sol new file mode 100644 index 0000000..c9b8019 --- /dev/null +++ b/evm/forge/tests/ForkSlots.t.sol @@ -0,0 +1,236 @@ +// 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 {UsdcDeal} from "test-helpers/libraries/UsdcDeal.sol"; +import {WormholeOverride} from "test-helpers/libraries/WormholeOverride.sol"; + +contract ForkSlots is Test { + using CircleIntegrationOverride for *; + using WormholeOverride for *; + using Utils for address; + + bytes32 constant GOVERNANCE_MODULE = + 0x000000000000000000000000000000436972636c65496e746567726174696f6e; + + address constant FORKED_CIRCLE_INTEGRATION_ADDRESS = 0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c; + + ICircleIntegration forked; + IWormhole wormhole; + + function setUp() public { + forked = ICircleIntegration(FORKED_CIRCLE_INTEGRATION_ADDRESS); + forked.setUpOverride(uint256(vm.envBytes32("TESTING_DEVNET_GUARDIAN"))); + + wormhole = forked.wormhole(); + } + + 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. + assertFalse(forked.isInitialized(address(implementation)), "already initialized"); + + // Also verify slot reflects same information. + { + bytes32 data = vm.load( + address(forked), keccak256(abi.encode(address(implementation), uint256(0x5))) + ); + assertEq( + uint256(data), + 0, // false + "isInitialized does not match slot value (false)" + ); + } + + // Check slots 0x0, 0x1, 0x2, 0x3, 0x4 and 0xa before upgrade. + + // 0x0: + // + // uint16 chainId; + // uint8 wormholeFinality; + // uint32 localDomain; + // address wormhole; + // uint16 governanceChainId; + { + bytes32 data = vm.load(address(forked), bytes32(0)); + + uint256 bitOffset; + + // First 2 bytes is chain ID. + assertEq(uint16(uint256(data >> bitOffset)), forked.chainId()); + bitOffset += 16; + + // Next byte is wormhole finality. + assertEq(uint8(uint256(data >> bitOffset)), forked.wormholeFinality()); + bitOffset += 8; + + // Next 4 bytes is local domain. + assertEq(uint32(uint256(data >> bitOffset)), forked.localDomain()); + bitOffset += 32; + + // Next 20 bytes is wormhole address. + assertEq(address(uint160(uint256(data >> bitOffset))), address(wormhole)); + bitOffset += 160; + + // Next 2 bytes is governance chain ID. + assertEq(uint16(uint256(data >> bitOffset)), forked.governanceChainId()); + bitOffset += 16; + + // Remaining bytes are zero. + assertEq(uint256(data >> bitOffset), 0); + } + + // 0x1: + // + // governanceContract + { + bytes32 data = vm.load(address(forked), bytes32(uint256(0x1))); + assertEq(data, forked.governanceContract()); + } + + // 0x2: + // + // circleBridgeAddress + { + bytes32 data = vm.load(address(forked), bytes32(uint256(0x2))); + assertEq(address(uint160(uint256(data))), address(forked.circleBridge())); + } + + // 0x3: + // + // circleTransmitterAddress + { + bytes32 data = vm.load(address(forked), bytes32(uint256(0x3))); + assertEq(address(uint160(uint256(data))), address(forked.circleTransmitter())); + } + + // 0x4: + // + // circleTokenMinterAddress + { + bytes32 data = vm.load(address(forked), bytes32(uint256(0x4))); + assertEq(address(uint160(uint256(data))), address(forked.circleTokenMinter())); + } + + // 0xa: + // + // evmChain + { + bytes32 data = vm.load(address(forked), bytes32(uint256(0xa))); + assertEq(uint256(data), forked.evmChain()); + } + + // Before upgrading, fetch some expected values. + uint16 registeredChainId = 2; + bytes32 expectedEmitter = forked.getRegisteredEmitter(registeredChainId); + + // Also verify slot reflects same information. + { + bytes32 data = + vm.load(address(forked), keccak256(abi.encode(registeredChainId, uint256(0x6)))); + assertEq(data, expectedEmitter); + } + + uint32 expectedCctpDomain = forked.getDomainFromChainId(registeredChainId); + + // Also verify slot reflects same information. + { + bytes32 data = + vm.load(address(forked), keccak256(abi.encode(registeredChainId, uint256(0x7)))); + assertEq(uint32(uint256(data)), expectedCctpDomain); + } + + assertEq(forked.getChainIdFromDomain(expectedCctpDomain), registeredChainId); + + // Also verify slot reflects same information. + { + bytes32 data = + vm.load(address(forked), keccak256(abi.encode(expectedCctpDomain, uint256(0x8)))); + assertEq(uint16(uint256(data)), registeredChainId); + } + + (IWormhole.VM memory vaa, bytes memory encodedVaa) = wormhole.craftGovernanceVaa( + GOVERNANCE_MODULE, + 3, // action + forked.chainId(), + 69, // sequence + abi.encodePacked(address(implementation).toUniversalAddress()) + ); + + // Show that VAA has not been consumed yet. + assertFalse(forked.isMessageConsumed(vaa.hash), "VAA already consumed"); + + // Also verify slot reflects same information. + { + bytes32 data = vm.load(address(forked), keccak256(abi.encode(vaa.hash, uint256(0x9)))); + assertEq( + uint256(data), + 0, // false + "isMessageConsumed does not match slot value (true)" + ); + } + + // Upgrade contract. + forked.upgradeContract(encodedVaa); + + // Now initialized. + assertTrue(forked.isInitialized(address(implementation)), "implementation not initialized"); + + // Also verify slot reflects same information. + { + bytes32 data = vm.load( + address(forked), keccak256(abi.encode(address(implementation), uint256(0x5))) + ); + assertEq( + uint256(data), + 1, // true + "isInitialized does not match slot value (true)" + ); + } + + // Show that VAA is now consumed. + assertTrue(forked.isMessageConsumed(vaa.hash), "VAA not consumed"); + + // Also verify slot reflects same information. + { + bytes32 data = vm.load(address(forked), keccak256(abi.encode(vaa.hash, uint256(0x9)))); + assertEq( + uint256(data), + 1, // true + "isMessageConsumed does not match slot value (true)" + ); + } + + // Slots checked above should now be zero. + { + uint256[6] memory slots = + [0, uint256(0x1), uint256(0x2), uint256(0x3), uint256(0x4), uint256(0xa)]; + for (uint256 i; i < slots.length;) { + bytes32 data = vm.load(address(forked), bytes32(slots[i])); + assertEq(uint256(data), 0); + unchecked { + ++i; + } + } + } + } +} 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/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) + ); + } }