Skip to content

Commit 027c17d

Browse files
authored
test: add fork tests for upgradable contracts (#594)
1 parent e0b95e5 commit 027c17d

File tree

15 files changed

+17839
-114
lines changed

15 files changed

+17839
-114
lines changed

pkg/baseforktest.sol/baseforktest.go

Lines changed: 3901 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/erc20custodyfork.t.sol/erc20custodyforktest.go

Lines changed: 3923 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/gatewayevmfork.t.sol/gatewayevmforktest.go

Lines changed: 3923 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/gatewayzevmfork.t.sol/gatewayzevmforktest.go

Lines changed: 3923 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/upgrade/SimulateERC20CustodyUpgrade.s.sol

Lines changed: 0 additions & 39 deletions
This file was deleted.

scripts/upgrade/SimulateGatewayEVMUpgrade.s.sol

Lines changed: 0 additions & 42 deletions
This file was deleted.

scripts/upgrade/SimulateGatewayZEVMUpgrade.s.sol

Lines changed: 0 additions & 33 deletions
This file was deleted.

test/fork/BaseForkTest.sol

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
import "forge-std/Test.sol";
5+
import "forge-std/Vm.sol";
6+
7+
abstract contract BaseForkTest is Test {
8+
// RPCs of all supported chains.
9+
string public ETHEREUM_RPC_URL = vm.envString("ETHEREUM_RPC_URL");
10+
string public BSC_RPC_URL = vm.envString("BSC_RPC_URL");
11+
string public POLYGON_RPC_URL = vm.envString("POLYGON_RPC_URL");
12+
string public BASE_RPC_URL = vm.envString("BASE_RPC_URL");
13+
string public ARBITRUM_RPC_URL = vm.envString("ARBITRUM_RPC_URL");
14+
string public AVALANCHE_RPC_URL = vm.envString("AVALANCHE_RPC_URL");
15+
string public ZETACHAIN_RPC_URL = vm.envString("ZETACHAIN_RPC_URL");
16+
17+
// The fork identifiers.
18+
uint256 ethereumForkId;
19+
uint256 bscForkId;
20+
uint256 polygonForkId;
21+
uint256 baseForkId;
22+
uint256 arbitrumForkId;
23+
uint256 avalancheForkId;
24+
uint256 zetachainForkId;
25+
26+
// Mainnet admins.
27+
address constant ETHEREUM_ADMIN = 0xaeB6dDB7708467814D557e340283248be8E43124;
28+
address constant BSC_ADMIN = 0xaf28a257D292e7f0E531073f70a175b57E0261a8;
29+
address constant POLYGON_ADMIN = 0x7828F92E7d79E141189f24C98aceF71Bc07bad3f;
30+
address constant BASE_ADMIN = 0xF43CF8b3F3D22d4cC33f964c59076eaB2F8A108E;
31+
address constant ARBITRUM_ADMIN = 0xdE3fb63723f0EEed8967ff9124e1c3bA89871b03;
32+
address constant AVALANCHE_ADMIN = 0xdE3fb63723f0EEed8967ff9124e1c3bA89871b03;
33+
address constant ZETACHAIN_ADMIN = 0x01d8207AfF7a7d029114Ee35afc72d0e133B7a0A;
34+
35+
struct ChainConfig {
36+
uint256 forkId;
37+
address contractAddress;
38+
address admin;
39+
string rpcUrl;
40+
string name;
41+
}
42+
43+
ChainConfig[] public chains;
44+
45+
function setUp() public virtual {
46+
ethereumForkId = vm.createFork(ETHEREUM_RPC_URL);
47+
bscForkId = vm.createFork(BSC_RPC_URL);
48+
polygonForkId = vm.createFork(POLYGON_RPC_URL);
49+
baseForkId = vm.createFork(BASE_RPC_URL);
50+
arbitrumForkId = vm.createFork(ARBITRUM_RPC_URL);
51+
avalancheForkId = vm.createFork(AVALANCHE_RPC_URL);
52+
zetachainForkId = vm.createFork(ZETACHAIN_RPC_URL);
53+
54+
_setupChains();
55+
}
56+
57+
function _setupChains() internal virtual;
58+
59+
function testForkIdDiffer() public view {
60+
assert(ethereumForkId != bscForkId);
61+
assert(bscForkId != polygonForkId);
62+
assert(polygonForkId != baseForkId);
63+
assert(baseForkId != arbitrumForkId);
64+
assert(arbitrumForkId != avalancheForkId);
65+
assert(avalancheForkId != zetachainForkId);
66+
}
67+
68+
function testCanSwitchForks() public {
69+
for (uint256 i = 0; i < chains.length; i++) {
70+
vm.selectFork(chains[i].forkId);
71+
assertEq(vm.activeFork(), chains[i].forkId);
72+
}
73+
}
74+
75+
function testUpgradeGatewayOnAllChains() public {
76+
for (uint256 i = 0; i < chains.length; i++) {
77+
console.log("\n=== Testing upgrade on", chains[i].name, "===");
78+
_testUpgradeContract(chains[i]);
79+
}
80+
}
81+
82+
function _testUpgradeContract(ChainConfig memory config) internal virtual;
83+
84+
function _getImplementation(address proxy) internal view returns (address) {
85+
bytes32 slot = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
86+
return address(uint160(uint256(vm.load(proxy, slot))));
87+
}
88+
}

test/fork/ERC20CustodyFork.t.sol

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
import { ERC20Custody } from "../../contracts/evm/ERC20Custody.sol";
5+
import "./BaseForkTest.sol";
6+
7+
contract ERC20CustodyForkTest is BaseForkTest {
8+
// ERC20Custody proxy addresses.
9+
address constant ETHEREUM_ERC20CUSTODY_PROXY = 0x0Bad40D9e9C369f2223c835E108f43a45fd223B5;
10+
address constant BSC_ERC20CUSTODY_PROXY = 0x0Bad40D9e9C369f2223c835E108f43a45fd223B5;
11+
address constant POLYGON_ERC20CUSTODY_PROXY = 0x0Bad40D9e9C369f2223c835E108f43a45fd223B5;
12+
address constant BASE_ERC20CUSTODY_PROXY = 0x0Bad40D9e9C369f2223c835E108f43a45fd223B5;
13+
address constant ARBITRUM_ERC20CUSTODY_PROXY = 0xECe33274237E6422f2668eD7dEE5901b16336aA0;
14+
address constant AVALANCHE_ERC20CUSTODY_PROXY = 0xECe33274237E6422f2668eD7dEE5901b16336aA0;
15+
16+
function _setupChains() internal override {
17+
chains.push(
18+
ChainConfig(ethereumForkId, ETHEREUM_ERC20CUSTODY_PROXY, ETHEREUM_ADMIN, ETHEREUM_RPC_URL, "Ethereum")
19+
);
20+
chains.push(ChainConfig(bscForkId, BSC_ERC20CUSTODY_PROXY, BSC_ADMIN, BSC_RPC_URL, "BSC"));
21+
chains.push(ChainConfig(polygonForkId, POLYGON_ERC20CUSTODY_PROXY, POLYGON_ADMIN, POLYGON_RPC_URL, "Polygon"));
22+
chains.push(ChainConfig(baseForkId, BASE_ERC20CUSTODY_PROXY, BASE_ADMIN, BASE_RPC_URL, "Base"));
23+
chains.push(
24+
ChainConfig(arbitrumForkId, ARBITRUM_ERC20CUSTODY_PROXY, ARBITRUM_ADMIN, ARBITRUM_RPC_URL, "Arbitrum")
25+
);
26+
chains.push(
27+
ChainConfig(avalancheForkId, AVALANCHE_ERC20CUSTODY_PROXY, AVALANCHE_ADMIN, AVALANCHE_RPC_URL, "Avalanche")
28+
);
29+
}
30+
31+
function _testUpgradeContract(ChainConfig memory config) internal override {
32+
vm.selectFork(config.forkId);
33+
34+
// Get the current proxy contract.
35+
ERC20Custody custody = ERC20Custody(config.contractAddress);
36+
37+
// Storage state before the upgrade.
38+
address gatewayBefore = address(custody.gateway());
39+
address tssAddressBefore = custody.tssAddress();
40+
bool supportsLegacyBefore = custody.supportsLegacy();
41+
42+
// Deploy new implementation.
43+
vm.startPrank(config.admin);
44+
ERC20Custody localNewImplementation = new ERC20Custody();
45+
46+
// Upgrade the proxy.
47+
custody.upgradeToAndCall(address(localNewImplementation), "");
48+
49+
// Verify upgrade.
50+
address onChainNewImplementation = _getImplementation(address(custody));
51+
assertEq(onChainNewImplementation, address(localNewImplementation), "Upgrade failed.");
52+
console.log("Successfully upgraded to:", onChainNewImplementation);
53+
54+
// Verify state preservation.
55+
assertEq(address(custody.gateway()), gatewayBefore, "Gateway address changed.");
56+
assertEq(custody.tssAddress(), tssAddressBefore, "TSS address changed.");
57+
assertEq(custody.supportsLegacy(), supportsLegacyBefore, "Supports legacy changed.");
58+
console.log("State preserved after upgrade");
59+
60+
// Check admin still has DEFAULT_ADMIN_ROLE after upgrade.
61+
bytes32 adminRole = custody.DEFAULT_ADMIN_ROLE();
62+
bool adminHasRoleAfter = custody.hasRole(adminRole, config.admin);
63+
assertTrue(adminHasRoleAfter, "Admin lost DEFAULT_ADMIN_ROLE after upgrade.");
64+
65+
vm.stopPrank();
66+
}
67+
}

test/fork/GatewayEVMFork.t.sol

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
import { GatewayEVM } from "../../contracts/evm/GatewayEVM.sol";
5+
import "./BaseForkTest.sol";
6+
7+
contract GatewayEVMForkTest is BaseForkTest {
8+
// GatewayEVM proxy addresses.
9+
address constant ETHEREUM_GATEWAY_PROXY = 0x48B9AACC350b20147001f88821d31731Ba4C30ed;
10+
address constant BSC_GATEWAY_PROXY = 0x48B9AACC350b20147001f88821d31731Ba4C30ed;
11+
address constant POLYGON_GATEWAY_PROXY = 0x48B9AACC350b20147001f88821d31731Ba4C30ed;
12+
address constant BASE_GATEWAY_PROXY = 0x48B9AACC350b20147001f88821d31731Ba4C30ed;
13+
address constant ARBITRUM_GATEWAY_PROXY = 0x1C53e188Bc2E471f9D4A4762CFf843d32C2C8549;
14+
address constant AVALANCHE_GATEWAY_PROXY = 0x1C53e188Bc2E471f9D4A4762CFf843d32C2C8549;
15+
16+
function _setupChains() internal override {
17+
chains.push(ChainConfig(ethereumForkId, ETHEREUM_GATEWAY_PROXY, ETHEREUM_ADMIN, ETHEREUM_RPC_URL, "Ethereum"));
18+
chains.push(ChainConfig(bscForkId, BSC_GATEWAY_PROXY, BSC_ADMIN, BSC_RPC_URL, "BSC"));
19+
chains.push(ChainConfig(polygonForkId, POLYGON_GATEWAY_PROXY, POLYGON_ADMIN, POLYGON_RPC_URL, "Polygon"));
20+
chains.push(ChainConfig(baseForkId, BASE_GATEWAY_PROXY, BASE_ADMIN, BASE_RPC_URL, "Base"));
21+
chains.push(ChainConfig(arbitrumForkId, ARBITRUM_GATEWAY_PROXY, ARBITRUM_ADMIN, ARBITRUM_RPC_URL, "Arbitrum"));
22+
chains.push(
23+
ChainConfig(avalancheForkId, AVALANCHE_GATEWAY_PROXY, AVALANCHE_ADMIN, AVALANCHE_RPC_URL, "Avalanche")
24+
);
25+
}
26+
27+
function _testUpgradeContract(ChainConfig memory config) internal override {
28+
vm.selectFork(config.forkId);
29+
30+
// Get the current proxy contract.
31+
GatewayEVM gateway = GatewayEVM(config.contractAddress);
32+
33+
// Storage state before the upgrade.
34+
address custodyBefore = gateway.custody();
35+
address tssAddressBefore = gateway.tssAddress();
36+
address zetaConnectorBefore = gateway.zetaConnector();
37+
address zetaTokenBefore = gateway.zetaToken();
38+
39+
// Deploy new implementation.
40+
vm.startPrank(config.admin);
41+
GatewayEVM localNewImplementation = new GatewayEVM();
42+
43+
// Upgrade the proxy.
44+
gateway.upgradeToAndCall(address(localNewImplementation), "");
45+
46+
// Verify upgrade.
47+
address onChainNewImplementation = _getImplementation(address(gateway));
48+
assertEq(onChainNewImplementation, address(localNewImplementation), "Upgrade failed.");
49+
console.log("Successfully upgraded to:", onChainNewImplementation);
50+
51+
// Verify state preservation.
52+
assertEq(gateway.custody(), custodyBefore, "Custody address changed.");
53+
assertEq(gateway.tssAddress(), tssAddressBefore, "TSS address changed.");
54+
assertEq(gateway.zetaConnector(), zetaConnectorBefore, "Connector address changed.");
55+
assertEq(gateway.zetaToken(), zetaTokenBefore, "Zeta token address changed.");
56+
console.log("State preserved after upgrade");
57+
58+
// Check admin still has DEFAULT_ADMIN_ROLE after upgrade.
59+
bytes32 adminRole = gateway.DEFAULT_ADMIN_ROLE();
60+
bool adminHasRoleAfter = gateway.hasRole(adminRole, config.admin);
61+
assertTrue(adminHasRoleAfter, "Admin lost DEFAULT_ADMIN_ROLE after upgrade.");
62+
63+
vm.stopPrank();
64+
}
65+
}

0 commit comments

Comments
 (0)