Skip to content

Commit

Permalink
evm: add fork upgrade test (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
a5-pickle committed Jan 3, 2024
1 parent ea14e8d commit 2e9cc44
Show file tree
Hide file tree
Showing 3 changed files with 254 additions and 1 deletion.
236 changes: 236 additions & 0 deletions evm/forge/tests/ForkSlots.t.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
}
2 changes: 1 addition & 1 deletion evm/forge/tests/gas/CircleIntegrationComparison.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions evm/forge/tests/helpers/libraries/WormholeOverride.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}
}

0 comments on commit 2e9cc44

Please sign in to comment.