wormhole-circle-integration/evm/forge-test/CircleIntegration.t.sol

814 lines
32 KiB
Solidity

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {BytesLib} from "wormhole/libraries/external/BytesLib.sol";
import {IWormhole} from "wormhole/interfaces/IWormhole.sol";
import {ICircleIntegration} from "../src/interfaces/ICircleIntegration.sol";
import {IUSDC} from "../src/interfaces/circle/IUSDC.sol";
import {CircleIntegrationStructs} from "../src/circle_integration/CircleIntegrationStructs.sol";
import {CircleIntegrationSetup} from "../src/circle_integration/CircleIntegrationSetup.sol";
import {CircleIntegrationImplementation} from "../src/circle_integration/CircleIntegrationImplementation.sol";
import {CircleIntegrationProxy} from "../src/circle_integration/CircleIntegrationProxy.sol";
import {WormholeSimulator} from "wormhole-forge-sdk/WormholeSimulator.sol";
contract CircleIntegrationTest is Test {
using BytesLib for bytes;
bytes32 constant GOVERNANCE_MODULE = 0x000000000000000000000000000000436972636c65496e746567726174696f6e;
uint8 constant GOVERNANCE_UPDATE_WORMHOLE_FINALITY = 1;
uint8 constant GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN = 2;
uint8 constant GOVERNANCE_UPGRADE_CONTRACT = 3;
// USDC
IUSDC usdc;
// dependencies
WormholeSimulator wormholeSimulator;
IWormhole wormhole;
ICircleIntegration circleIntegration;
// foreign
bytes32 foreignUsdc;
function maxUSDCAmountToMint() public view returns (uint256) {
return type(uint256).max - usdc.totalSupply();
}
function mintUSDC(uint256 amount) public {
require(amount <= maxUSDCAmountToMint(), "total supply overflow");
usdc.mint(address(this), amount);
}
function setupWormhole() public {
// Set up this chain's Wormhole
wormholeSimulator = new WormholeSimulator(
vm.envAddress("TESTING_WORMHOLE_ADDRESS"), uint256(vm.envBytes32("TESTING_DEVNET_GUARDIAN")));
wormhole = wormholeSimulator.wormhole();
}
function setupUSDC() public {
usdc = IUSDC(vm.envAddress("TESTING_USDC_TOKEN_ADDRESS"));
(, bytes memory queriedDecimals) = address(usdc).staticcall(abi.encodeWithSignature("decimals()"));
uint8 decimals = abi.decode(queriedDecimals, (uint8));
require(decimals == 6, "wrong USDC");
// spoof .configureMinter() call with the master minter account
// allow this test contract to mint USDC
vm.prank(usdc.masterMinter());
usdc.configureMinter(address(this), type(uint256).max);
uint256 amount = 42069;
mintUSDC(amount);
require(usdc.balanceOf(address(this)) == amount);
}
function setupCircleIntegration() public {
// deploy Setup
CircleIntegrationSetup setup = new CircleIntegrationSetup();
// deploy Implementation
CircleIntegrationImplementation implementation = new CircleIntegrationImplementation();
// deploy Proxy
CircleIntegrationProxy proxy = new CircleIntegrationProxy(
address(setup),
abi.encodeWithSelector(
bytes4(keccak256("setup(address,address,uint8,address,uint16,bytes32)")),
address(implementation),
address(wormhole),
uint8(1), // finality
vm.envAddress("TESTING_CIRCLE_BRIDGE_ADDRESS"), // circleBridge
uint16(1),
bytes32(0x0000000000000000000000000000000000000000000000000000000000000004)
)
);
circleIntegration = ICircleIntegration(address(proxy));
}
function setUp() public {
// set up circle contracts (transferring ownership to address(this), etc)
setupUSDC();
// set up wormhole simulator
setupWormhole();
// now our contract
setupCircleIntegration();
foreignUsdc = bytes32(uint256(uint160(vm.envAddress("TESTING_FOREIGN_USDC_TOKEN_ADDRESS"))));
}
function registerContract(uint16 foreignChain, bytes32 foreignEmitter, uint32 domain) public {
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
circleIntegration.chainId(),
abi.encodePacked(foreignChain, foreignEmitter, domain)
);
// Register emitter and domain.
circleIntegration.registerEmitterAndDomain(encodedMessage);
}
function prepareCircleIntegrationTest(uint256 amount) public {
// Set up USDC token for test
if (amount > 0) {
// First mint USDC.
mintUSDC(amount);
// Next set allowance.
usdc.approve(address(circleIntegration), amount);
}
}
function testEncodeDepositWithPayload(
bytes32 token,
uint256 amount,
uint32 sourceDomain,
uint32 targetDomain,
uint64 nonce,
bytes32 fromAddress,
bytes32 mintRecipient,
bytes memory payload
) public view {
vm.assume(token != bytes32(0));
vm.assume(amount > 0);
vm.assume(targetDomain != sourceDomain);
vm.assume(nonce > 0);
vm.assume(fromAddress != bytes32(0));
vm.assume(mintRecipient != bytes32(0));
vm.assume(payload.length > 0);
ICircleIntegration.DepositWithPayload memory deposit;
deposit.token = token;
deposit.amount = amount;
deposit.sourceDomain = sourceDomain;
deposit.targetDomain = targetDomain;
deposit.nonce = nonce;
deposit.fromAddress = fromAddress;
deposit.mintRecipient = mintRecipient;
deposit.payload = payload;
bytes memory serialized = circleIntegration.encodeDepositWithPayload(deposit);
// payload ID
require(serialized.toUint8(0) == 1, "invalid payload");
// token
for (uint256 i = 0; i < 32;) {
require(deposit.token[i] == serialized[i + 1], "invalid token serialization");
unchecked {
i += 1;
}
}
// amount
for (uint256 i = 0; i < 32;) {
require(bytes32(deposit.amount)[i] == serialized[i + 33], "invalid amount serialization");
unchecked {
i += 1;
}
}
// sourceDomain 65
for (uint256 i = 0; i < 4;) {
require(bytes4(deposit.sourceDomain)[i] == serialized[i + 65], "invalid sourceDomain serialization");
unchecked {
i += 1;
}
}
// targetDomain 69 (hehe)
for (uint256 i = 0; i < 4;) {
require(bytes4(deposit.targetDomain)[i] == serialized[i + 69], "invalid targetDomain serialization");
unchecked {
i += 1;
}
}
// nonce
for (uint256 i = 0; i < 8;) {
require(bytes8(deposit.nonce)[i] == serialized[i + 73], "invalid nonce serialization");
unchecked {
i += 1;
}
}
// fromAddress
for (uint256 i = 0; i < 8;) {
require(deposit.fromAddress[i] == serialized[i + 81], "invalid fromAddress serialization");
unchecked {
i += 1;
}
}
// mintRecipient
for (uint256 i = 0; i < 8;) {
require(deposit.mintRecipient[i] == serialized[i + 113], "invalid mintRecipient serialization");
unchecked {
i += 1;
}
}
// payload length
uint256 payloadLen = deposit.payload.length;
for (uint256 i = 0; i < 2;) {
require(bytes32(payloadLen)[i + 30] == serialized[i + 145], "invalid payload length serialization");
unchecked {
i += 1;
}
}
// payload
for (uint256 i = 0; i < payloadLen;) {
require(deposit.payload[i] == serialized[i + 147], "invalid payload serialization");
unchecked {
i += 1;
}
}
}
function testDecodeDepositWithPayload(
bytes32 token,
uint256 amount,
uint32 sourceDomain,
uint32 targetDomain,
uint64 nonce,
bytes32 fromAddress,
bytes32 mintRecipient,
bytes memory payload
) public view {
vm.assume(token != bytes32(0));
vm.assume(amount > 0);
vm.assume(targetDomain != sourceDomain);
vm.assume(nonce > 0);
vm.assume(fromAddress != bytes32(0));
vm.assume(mintRecipient != bytes32(0));
vm.assume(payload.length > 0);
ICircleIntegration.DepositWithPayload memory expected;
expected.token = token;
expected.amount = amount;
expected.sourceDomain = 0;
expected.targetDomain = 1;
expected.nonce = nonce;
expected.fromAddress = fromAddress;
expected.mintRecipient = mintRecipient;
expected.payload = payload;
bytes memory serialized = circleIntegration.encodeDepositWithPayload(expected);
ICircleIntegration.DepositWithPayload memory deposit = circleIntegration.decodeDepositWithPayload(serialized);
require(deposit.token == expected.token, "token != expected");
require(deposit.amount == expected.amount, "amount != expected");
require(deposit.sourceDomain == expected.sourceDomain, "sourceDomain != expected");
require(deposit.targetDomain == expected.targetDomain, "targetDomain != expected");
require(deposit.nonce == expected.nonce, "nonce != expected");
require(deposit.fromAddress == expected.fromAddress, "fromAddress != expected");
require(deposit.mintRecipient == expected.mintRecipient, "mintRecipient != expected");
for (uint256 i = 0; i < deposit.payload.length;) {
require(deposit.payload[i] == expected.payload[i], "payload != expected");
unchecked {
i += 1;
}
}
}
function testCannotConsumeGovernanceMessageInvalidGovernanceChainId(uint16 governanceChainId, uint8 action)
public
{
vm.assume(governanceChainId != wormholeSimulator.governanceChainId());
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
governanceChainId,
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
action,
circleIntegration.chainId(),
abi.encodePacked("Mission accomplished.")
);
// You shall not pass!
vm.expectRevert("invalid governance chain");
circleIntegration.verifyGovernanceMessage(encodedMessage, action);
}
function testCannotConsumeGovernanceMessageInvalidGovernanceContract(bytes32 governanceContract, uint8 action)
public
{
vm.assume(governanceContract != wormholeSimulator.governanceContract());
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
governanceContract,
GOVERNANCE_MODULE,
action,
circleIntegration.chainId(),
abi.encodePacked("Mission accomplished.")
);
// You shall not pass!
vm.expectRevert("invalid governance contract");
circleIntegration.verifyGovernanceMessage(encodedMessage, action);
}
function testCannotConsumeGovernanceMessageInvalidModule(bytes32 governanceModule, uint8 action) public {
vm.assume(governanceModule != GOVERNANCE_MODULE);
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
governanceModule,
action,
circleIntegration.chainId(),
abi.encodePacked("Mission accomplished.")
);
// You shall not pass!
vm.expectRevert("invalid governance module");
circleIntegration.verifyGovernanceMessage(encodedMessage, action);
}
function testCannotConsumeGovernanceMessageInvalidAction(uint8 action, uint8 wrongAction) public {
vm.assume(action != wrongAction);
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
action,
circleIntegration.chainId(),
abi.encodePacked("Mission accomplished.")
);
// You shall not pass!
vm.expectRevert("invalid governance action");
circleIntegration.verifyGovernanceMessage(encodedMessage, wrongAction);
}
function testCannotUpdateWormholeFinalityInvalidLength(uint8 finality) public {
vm.assume(finality > 0 && finality != circleIntegration.wormholeFinality());
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_UPDATE_WORMHOLE_FINALITY,
circleIntegration.chainId(),
abi.encodePacked(finality, "But wait! There's more.")
);
// You shall not pass!
vm.expectRevert("invalid governance payload length");
circleIntegration.updateWormholeFinality(encodedMessage);
}
function testCannotUpdateWormholeFinalityInvalidTargetChain(uint16 targetChainId, uint8 finality) public {
vm.assume(targetChainId != circleIntegration.chainId());
vm.assume(finality > 0 && finality != circleIntegration.wormholeFinality());
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_UPDATE_WORMHOLE_FINALITY,
targetChainId,
abi.encodePacked(finality)
);
// You shall not pass!
vm.expectRevert("invalid target chain");
circleIntegration.updateWormholeFinality(encodedMessage);
}
function testUpdateWormholeFinality(uint8 finality) public {
vm.assume(finality > 0 && finality != circleIntegration.wormholeFinality());
assertEq(circleIntegration.wormholeFinality(), 1, "starting finality incorrect");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_UPDATE_WORMHOLE_FINALITY,
circleIntegration.chainId(),
abi.encodePacked(finality)
);
// Update with governance message
circleIntegration.updateWormholeFinality(encodedMessage);
assertEq(circleIntegration.wormholeFinality(), finality, "new finality incorrect");
}
function testCannotRegisterEmitterAndDomainInvalidLength(uint16 foreignChain, bytes32 foreignEmitter, uint32 domain)
public
{
vm.assume(foreignChain > 0);
vm.assume(foreignChain != circleIntegration.chainId());
vm.assume(foreignEmitter != bytes32(0));
// For the purposes of this test, we will assume the domain set is > 0
vm.assume(domain > 0);
vm.assume(domain != circleIntegration.localDomain());
// No emitters should be registered for this chain.
assertEq(circleIntegration.getRegisteredEmitter(foreignChain), bytes32(0), "already registered");
assertEq(circleIntegration.getDomainFromChainId(foreignChain), 0, "domain already registered");
assertEq(circleIntegration.getChainIdFromDomain(domain), 0, "chain already registered");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
circleIntegration.chainId(),
abi.encodePacked(foreignChain, foreignEmitter, domain, "But wait! There's more.")
);
// You shall not pass!
vm.expectRevert("invalid governance payload length");
circleIntegration.registerEmitterAndDomain(encodedMessage);
}
function testCannotRegisterEmitterAndDomainInvalidTargetChain(
uint16 targetChain,
uint16 foreignChain,
bytes32 foreignEmitter,
uint32 domain
) public {
vm.assume(
targetChain != circleIntegration.chainId() &&
targetChain != 0
);
vm.assume(foreignChain > 0);
vm.assume(foreignChain != circleIntegration.chainId());
vm.assume(foreignEmitter != bytes32(0));
// For the purposes of this test, we will assume the domain set is > 0
vm.assume(domain > 0);
vm.assume(domain != circleIntegration.localDomain());
// No emitters should be registered for this chain.
assertEq(circleIntegration.getRegisteredEmitter(foreignChain), bytes32(0), "already registered");
assertEq(circleIntegration.getDomainFromChainId(foreignChain), 0, "domain already registered");
assertEq(circleIntegration.getChainIdFromDomain(domain), 0, "chain already registered");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
targetChain,
abi.encodePacked(foreignChain, foreignEmitter, domain)
);
// You shall not pass!
vm.expectRevert("invalid target chain");
circleIntegration.registerEmitterAndDomain(encodedMessage);
}
function testCannotRegisterEmitterAndDomainInvalidForeignChain(bytes32 foreignEmitter, uint32 domain) public {
vm.assume(foreignEmitter != bytes32(0));
// For the purposes of this test, we will assume the domain set is > 0
vm.assume(domain > 0);
vm.assume(domain != circleIntegration.localDomain());
// No emitters should be registered for this chain.
// emitterChain cannot be zero
{
uint16 foreignChain = 0;
assertEq(circleIntegration.getRegisteredEmitter(foreignChain), bytes32(0), "already registered");
assertEq(circleIntegration.getDomainFromChainId(foreignChain), 0, "domain already registered");
assertEq(circleIntegration.getChainIdFromDomain(domain), 0, "chain already registered");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
circleIntegration.chainId(),
abi.encodePacked(foreignChain, foreignEmitter, domain)
);
// You shall not pass!
vm.expectRevert("invalid chain");
circleIntegration.registerEmitterAndDomain(encodedMessage);
}
// emitterChain cannot be this chain's
{
uint16 foreignChain = circleIntegration.chainId();
assertEq(circleIntegration.getRegisteredEmitter(foreignChain), bytes32(0), "already registered");
assertEq(circleIntegration.getDomainFromChainId(foreignChain), 0, "domain already registered");
assertEq(circleIntegration.getChainIdFromDomain(domain), 0, "chain already registered");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
circleIntegration.chainId(),
abi.encodePacked(foreignChain, foreignEmitter, domain)
);
// You shall not pass!
vm.expectRevert("invalid chain");
circleIntegration.registerEmitterAndDomain(encodedMessage);
}
}
function testCannotRegisterEmitterAndDomainInvalidEmitterAddress(uint16 foreignChain, uint32 domain) public {
vm.assume(foreignChain > 0);
vm.assume(foreignChain != circleIntegration.chainId());
// For the purposes of this test, we will assume the domain set is > 0
vm.assume(domain > 0);
vm.assume(domain != circleIntegration.localDomain());
// No emitters should be registered for this chain.
assertEq(circleIntegration.getRegisteredEmitter(foreignChain), bytes32(0), "already registered");
assertEq(circleIntegration.getDomainFromChainId(foreignChain), 0, "domain already registered");
assertEq(circleIntegration.getChainIdFromDomain(domain), 0, "chain already registered");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
circleIntegration.chainId(),
abi.encodePacked(
foreignChain,
bytes32(0), // emitterAddress
domain
)
);
// You shall not pass!
vm.expectRevert("emitter cannot be zero address");
circleIntegration.registerEmitterAndDomain(encodedMessage);
}
function testCannotRegisterEmitterAndDomainInvalidDomain(uint16 foreignChain, bytes32 foreignEmitter) public {
vm.assume(foreignChain > 0);
vm.assume(foreignChain != circleIntegration.chainId());
vm.assume(foreignEmitter != bytes32(0));
// No emitters should be registered for this chain.
assertEq(circleIntegration.getRegisteredEmitter(foreignChain), bytes32(0), "already registered");
assertEq(circleIntegration.getDomainFromChainId(foreignChain), 0, "domain already registered");
{
uint32 domain = circleIntegration.localDomain();
assertEq(circleIntegration.getChainIdFromDomain(domain), 0, "chain already registered");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
circleIntegration.chainId(),
abi.encodePacked(foreignChain, foreignEmitter, domain)
);
// You shall not pass!
vm.expectRevert("domain == localDomain()");
circleIntegration.registerEmitterAndDomain(encodedMessage);
}
}
function testRegisterEmitterAndDomainNoTarget() public {
uint16 foreignChain = 42069;
bytes32 foreignEmitter = bytes32(uint256(uint160(vm.envAddress("TESTING_FOREIGN_USDC_TOKEN_ADDRESS"))));
uint32 domain = 69420;
// No emitters should be registered for this chain.
assertEq(circleIntegration.getRegisteredEmitter(foreignChain), bytes32(0), "already registered");
assertEq(circleIntegration.getDomainFromChainId(foreignChain), 0, "domain already registered");
assertEq(circleIntegration.getChainIdFromDomain(domain), 0, "chain already registered");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
0, // NOTE: set the target chain to zero
abi.encodePacked(foreignChain, foreignEmitter, domain)
);
// Register emitter and domain.
circleIntegration.registerEmitterAndDomain(encodedMessage);
require(circleIntegration.getRegisteredEmitter(foreignChain) == foreignEmitter, "wrong foreignEmitter");
require(circleIntegration.getDomainFromChainId(foreignChain) == domain, "wrong domain");
require(circleIntegration.getChainIdFromDomain(domain) == foreignChain, "wrong chain");
}
function testRegisterEmitterAndDomain(uint16 foreignChain, bytes32 foreignEmitter, uint32 domain) public {
vm.assume(foreignChain > 0);
vm.assume(foreignChain != circleIntegration.chainId());
vm.assume(foreignEmitter != bytes32(0));
// For the purposes of this test, we will assume the domain set is > 0
vm.assume(domain > 0);
vm.assume(domain != circleIntegration.localDomain());
// No emitters should be registered for this chain.
assertEq(circleIntegration.getRegisteredEmitter(foreignChain), bytes32(0), "already registered");
assertEq(circleIntegration.getDomainFromChainId(foreignChain), 0, "domain already registered");
assertEq(circleIntegration.getChainIdFromDomain(domain), 0, "chain already registered");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
circleIntegration.chainId(),
abi.encodePacked(foreignChain, foreignEmitter, domain)
);
// Register emitter and domain.
circleIntegration.registerEmitterAndDomain(encodedMessage);
require(circleIntegration.getRegisteredEmitter(foreignChain) == foreignEmitter, "wrong foreignEmitter");
require(circleIntegration.getDomainFromChainId(foreignChain) == domain, "wrong domain");
require(circleIntegration.getChainIdFromDomain(domain) == foreignChain, "wrong chain");
// we cannot register for this chain again
{
bytes memory anotherMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_REGISTER_EMITTER_AND_DOMAIN,
circleIntegration.chainId(),
abi.encodePacked(foreignChain, foreignEmitter, domain)
);
// You shall not pass!
vm.expectRevert("chain already registered");
circleIntegration.registerEmitterAndDomain(anotherMessage);
}
}
function testCannotUpgradeContractInvalidImplementation(bytes12 garbage, address newImplementation) public {
vm.assume(garbage != bytes12(0));
vm.assume(newImplementation != address(0) && !circleIntegration.isInitialized(newImplementation));
// First attempt to submit garbage implementation
{
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_UPGRADE_CONTRACT,
circleIntegration.chainId(),
abi.encodePacked(garbage, newImplementation)
);
// You shall not pass!
vm.expectRevert("invalid address");
circleIntegration.upgradeContract(encodedMessage);
}
// Now use legitimate-looking ERC20 address
{
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_UPGRADE_CONTRACT,
circleIntegration.chainId(),
abi.encodePacked(bytes12(0), newImplementation)
);
// You shall not pass!
vm.expectRevert("invalid implementation");
circleIntegration.upgradeContract(encodedMessage);
}
// Now use one of Wormhole's implementations
{
address wormholeImplementation = 0x46DB25598441915D59df8955DD2E4256bC3c6e95;
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_UPGRADE_CONTRACT,
circleIntegration.chainId(),
abi.encodePacked(bytes12(0), wormholeImplementation)
);
// You shall not pass!
vm.expectRevert("invalid implementation");
circleIntegration.upgradeContract(encodedMessage);
}
}
function testUpgradeContract() public {
// Deploy new implementation.
CircleIntegrationImplementation implementation = new CircleIntegrationImplementation();
// Should not be initialized yet.
require(!circleIntegration.isInitialized(address(implementation)), "already initialized");
bytes memory encodedMessage = wormholeSimulator.makeSignedGovernanceObservation(
wormholeSimulator.governanceChainId(),
wormholeSimulator.governanceContract(),
GOVERNANCE_MODULE,
GOVERNANCE_UPGRADE_CONTRACT,
circleIntegration.chainId(),
abi.encodePacked(bytes12(0), address(implementation))
);
// Upgrade contract.
circleIntegration.upgradeContract(encodedMessage);
// Should not be initialized yet.
require(circleIntegration.isInitialized(address(implementation)), "implementation not initialized");
}
function testCannotTransferTokensWithPayloadInvalidToken(
address token,
uint256 amount,
uint16 targetChain,
bytes32 mintRecipient
) public {
vm.assume(token != address(usdc));
vm.assume(amount > 0 && amount <= maxUSDCAmountToMint());
vm.assume(targetChain > 0 && targetChain != circleIntegration.chainId());
vm.assume(mintRecipient != bytes32(0));
prepareCircleIntegrationTest(amount);
// You shall not pass!
vm.expectRevert("token not accepted");
circleIntegration.transferTokensWithPayload(
ICircleIntegration.TransferParameters({
token: token,
amount: amount,
targetChain: targetChain,
mintRecipient: mintRecipient
}),
0, // batchId
abi.encodePacked("All your base are belong to us") // payload
);
}
function testCannotTransferTokensWithPayloadZeroAmount(uint16 targetChain, bytes32 mintRecipient) public {
vm.assume(targetChain > 0 && targetChain != circleIntegration.chainId());
vm.assume(mintRecipient != bytes32(0));
uint256 amount = 0;
prepareCircleIntegrationTest(amount);
// You shall not pass!
vm.expectRevert("amount must be > 0");
circleIntegration.transferTokensWithPayload(
ICircleIntegration.TransferParameters({
token: address(usdc),
amount: amount,
targetChain: targetChain,
mintRecipient: mintRecipient
}),
0, // batchId
abi.encodePacked("All your base are belong to us") // payload
);
}
function testCannotTransferTokensWithPayloadInvalidMintRecipient(uint256 amount, uint16 targetChain) public {
vm.assume(amount > 0 && amount <= maxUSDCAmountToMint());
vm.assume(targetChain > 0 && targetChain != circleIntegration.chainId());
prepareCircleIntegrationTest(amount);
// You shall not pass!
vm.expectRevert("invalid mint recipient");
circleIntegration.transferTokensWithPayload(
ICircleIntegration.TransferParameters({
token: address(usdc),
amount: amount,
targetChain: targetChain,
mintRecipient: bytes32(0)
}),
0, // batchId
abi.encodePacked("All your base are belong to us") // payload
);
}
}