From b0d64a1a964ec6837a2c91b58beefa1c456debdf Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 01:44:44 +1000 Subject: [PATCH 01/33] Fix crash --- test/invariant/InvariantBridge.t.sol | 184 ++++++++++++++++++ test/invariant/MockAdaptor.sol | 42 ++++ .../child/ChildERC20BridgeHandler.sol | 89 +++++++++ test/invariant/child/ChildHelper.sol | 25 +++ .../root/RootERC20BridgeFlowRateHandler.sol | 69 +++++++ test/invariant/root/RootHelper.sol | 48 +++++ 6 files changed, 457 insertions(+) create mode 100644 test/invariant/InvariantBridge.t.sol create mode 100644 test/invariant/MockAdaptor.sol create mode 100644 test/invariant/child/ChildERC20BridgeHandler.sol create mode 100644 test/invariant/child/ChildHelper.sol create mode 100644 test/invariant/root/RootERC20BridgeFlowRateHandler.sol create mode 100644 test/invariant/root/RootHelper.sol diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol new file mode 100644 index 00000000..9e4945f2 --- /dev/null +++ b/test/invariant/InvariantBridge.t.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {ChildERC20} from "../../src/child/ChildERC20.sol"; +import {WIMX} from "../../src/child/WIMX.sol"; +import {IChildERC20Bridge, ChildERC20Bridge} from "../../src/child/ChildERC20Bridge.sol"; +import {IRootERC20Bridge, IERC20Metadata} from "../../src/root/RootERC20Bridge.sol"; +import {RootERC20BridgeFlowRate} from "../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; +import {MockAdaptor} from "./MockAdaptor.sol"; +import {ChildHelper} from "./child/ChildHelper.sol"; +import {RootHelper} from "./root/RootHelper.sol"; +import {ChildERC20BridgeHandler} from "./child/ChildERC20BridgeHandler.sol"; +import {RootERC20BridgeFlowRateHandler} from "./root/RootERC20BridgeFlowRateHandler.sol"; +import "forge-std/console.sol"; + +contract InvariantBridge is Test { + string public constant CHILD_CHAIN_URL = "http://127.0.0.1:8500"; + string public constant ROOT_CHAIN_URL = "http://127.0.0.1:8501"; + uint256 public constant IMX_DEPOSIT_LIMIT = 10000 ether; + uint256 public constant MAX_AMOUNT = 10000; + address public constant ADMIN = address(0x111); + uint256 public constant NO_OF_USERS = 5; + uint256 public constant NO_OF_TOKENS = 4; + + address[] users; + address[] rootTokens; + + uint256 childId; + uint256 rootId; + ChildERC20Bridge childBridge; + RootERC20BridgeFlowRate rootBridge; + MockAdaptor childAdaptor; + MockAdaptor rootAdaptor; + ChildHelper childHelper; + RootHelper rootHelper; + ChildERC20BridgeHandler childBridgeHandler; + RootERC20BridgeFlowRateHandler rootBridgeHandler; + + function setUp() public { + childId = vm.createFork(CHILD_CHAIN_URL); + rootId = vm.createFork(ROOT_CHAIN_URL); + + // Deploy contracts on child chain. + vm.selectFork(childId); + vm.startPrank(ADMIN); + ChildERC20 childTokenTemplate = new ChildERC20(); + childTokenTemplate.initialize(address(123), "Test", "TST", 18); + childAdaptor = new MockAdaptor(); + vm.stopPrank(); + + childBridge = new ChildERC20Bridge(address(this)); + WIMX wIMX = new WIMX(); + + // Deploy contracts on root chain. + vm.selectFork(rootId); + vm.startPrank(ADMIN); + ChildERC20 rootTokenTemplate = new ChildERC20(); + rootTokenTemplate.initialize(address(123), "Test", "TST", 18); + rootAdaptor = new MockAdaptor(); + vm.stopPrank(); + + rootBridge = new RootERC20BridgeFlowRate(address(this)); + ChildERC20 rootIMXToken = new ChildERC20(); + rootIMXToken.initialize(address(123), "Immutable X", "IMX", 18); + WIMX wETH = new WIMX(); + + // Configure contracts on child chain. + vm.selectFork(childId); + childAdaptor.initialize(rootId, address(childBridge)); + IChildERC20Bridge.InitializationRoles memory childRoles = IChildERC20Bridge.InitializationRoles({ + defaultAdmin: address(this), + pauser: address(this), + unpauser: address(this), + adaptorManager: address(this), + initialDepositor: address(this), + treasuryManager: address(this) + }); + childBridge.initialize( + childRoles, address(childAdaptor), address(childTokenTemplate), address(rootIMXToken), address(wIMX) + ); + vm.deal(address(childBridge), IMX_DEPOSIT_LIMIT); + + // Configure contracts on root chain. + vm.selectFork(rootId); + rootAdaptor.initialize(childId, address(rootBridge)); + IRootERC20Bridge.InitializationRoles memory rootRoles = IRootERC20Bridge.InitializationRoles({ + defaultAdmin: address(this), + pauser: address(this), + unpauser: address(this), + variableManager: address(this), + adaptorManager: address(this) + }); + rootBridge.initialize( + rootRoles, + address(rootAdaptor), + address(childBridge), + address(rootTokenTemplate), + address(rootIMXToken), + address(wETH), + IMX_DEPOSIT_LIMIT, + ADMIN + ); + + // Create users. + vm.selectFork(rootId); + for (uint256 i = 0; i < NO_OF_USERS; i++) { + address user = vm.addr(0x10000 + i); + // Mint ETH token + vm.deal(user, MAX_AMOUNT); + // Mint IMX token + rootIMXToken.mint(user, MAX_AMOUNT); + users.push(user); + } + // Create tokens. + for (uint256 i = 0; i < NO_OF_TOKENS; i++) { + vm.prank(address(0x234)); + ChildERC20 rootToken = new ChildERC20(); + vm.prank(address(0x234)); + rootToken.initialize(address(123), "Test", "TST", 18); + // Mint token to user + for (uint256 j = 0; j < NO_OF_USERS; j++) { + vm.prank(address(0x234)); + rootToken.mint(users[j], MAX_AMOUNT); + } + // Configure rate for half tokens + if (i % 2 == 0) { + vm.prank(ADMIN); + rootBridge.setRateControlThreshold(address(rootToken), MAX_AMOUNT, MAX_AMOUNT / 3600, MAX_AMOUNT / 2); + } + rootTokens.push(address(rootToken)); + } + + // Deploy helpers and handlers on both chains. + vm.selectFork(childId); + vm.startPrank(ADMIN); + childHelper = new ChildHelper(payable(childBridge)); + address temp = address(new RootHelper(ADMIN, payable(rootBridge))); + childBridgeHandler = + new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), temp); + new RootERC20BridgeFlowRateHandler(childId, rootId, users, rootTokens, address(childHelper), temp); + vm.stopPrank(); + + vm.selectFork(rootId); + vm.startPrank(ADMIN); + new ChildHelper(payable(childBridge)); + rootHelper = new RootHelper(ADMIN, payable(rootBridge)); + new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); + rootBridgeHandler = + new RootERC20BridgeFlowRateHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); + vm.stopPrank(); + + // Map tokens + vm.selectFork(rootId); + for (uint256 i = 0; i < NO_OF_TOKENS; i++) { + address rootToken = rootTokens[i]; + rootBridge.mapToken{value: 1}(IERC20Metadata(rootToken)); + // Verify + address childTokenL1 = rootBridge.rootTokenToChildToken(address(rootToken)); + + vm.selectFork(childId); + address childTokenL2 = childBridge.rootTokenToChildToken(address(rootToken)); + vm.selectFork(rootId); + + assertEq(childTokenL1, childTokenL2, "Child token address mismatch between L1 and L2"); + } + + // Target contracts + bytes4[] memory childSelectors = new bytes4[](1); + childSelectors[0] = childBridgeHandler.withdraw.selector; + targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors})); + + bytes4[] memory rootSelectors = new bytes4[](1); + rootSelectors[0] = rootBridgeHandler.deposit.selector; + targetSelector(FuzzSelector({addr: address(rootBridgeHandler), selectors: rootSelectors})); + + targetContract(address(childBridgeHandler)); + targetContract(address(rootBridgeHandler)); + } + + /// forge-config: default.invariant.fail-on-revert = false + function invariant_A() external { + } +} \ No newline at end of file diff --git a/test/invariant/MockAdaptor.sol b/test/invariant/MockAdaptor.sol new file mode 100644 index 00000000..3666ccdd --- /dev/null +++ b/test/invariant/MockAdaptor.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {IChildBridgeAdaptor} from "../../src/interfaces/child/IChildBridgeAdaptor.sol"; +import {IRootBridgeAdaptor} from "../../src/interfaces/root/IRootBridgeAdaptor.sol"; +import "forge-std/console.sol"; + +interface MessageReceiver { + function onMessageReceive(bytes calldata data) external; +} + +contract MockAdaptor is Test, IChildBridgeAdaptor, IRootBridgeAdaptor { + uint256 otherChainId; + MessageReceiver messageReceiver; + + constructor() {} + + function initialize(uint256 _otherChainId, address _messageReceiver) public { + otherChainId = _otherChainId; + messageReceiver = MessageReceiver(_messageReceiver); + } + + function sendMessage(bytes calldata payload, address /*refundRecipient*/ ) + external + payable + override(IChildBridgeAdaptor, IRootBridgeAdaptor) + { + uint256 original = vm.activeFork(); + + // Switch to the other chain. + vm.selectFork(otherChainId); + console.log(""); // <= Bug + onMessageReceive(payload); + + vm.selectFork(original); + } + + function onMessageReceive(bytes calldata data) public { + messageReceiver.onMessageReceive(data); + } +} \ No newline at end of file diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol new file mode 100644 index 00000000..8b1ff059 --- /dev/null +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {ChildERC20} from "../../../src/child/ChildERC20.sol"; +import {ChildHelper} from "./ChildHelper.sol"; +import {RootHelper} from "../root/RootHelper.sol"; + +contract ChildERC20BridgeHandler is Test { + uint256 public constant MAX_AMOUNT = 10000; + uint256 public constant MAX_GAS = 100; + + uint256 childId; + uint256 rootId; + address[] users; + address[] rootTokens; + ChildHelper childHelper; + RootHelper rootHelper; + + constructor( + uint256 _childId, + uint256 _rootId, + address[] memory _users, + address[] memory _rootTokens, + address _childHelper, + address _rootHelper + ) { + childId = _childId; + rootId = _rootId; + users = _users; + rootTokens = _rootTokens; + childHelper = ChildHelper(_childHelper); + rootHelper = RootHelper(_rootHelper); + } + + function initialize( + uint256 _childId, + uint256 _rootId, + address[] memory _users, + address[] memory _rootTokens, + address _childHelper, + address _rootHelper + ) public { + childId = _childId; + rootId = _rootId; + users = _users; + rootTokens = _rootTokens; + childHelper = ChildHelper(_childHelper); + rootHelper = RootHelper(_rootHelper); + } + + function withdraw(uint256 userIndexSeed, uint256 tokenIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to child chain + vm.selectFork(childId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address rootToken = rootTokens[bound(tokenIndexSeed, 0, rootTokens.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get child token + address childToken = childHelper.childBridge().rootTokenToChildToken(rootToken); + + // Get current balance + uint256 currentBalance = ChildERC20(childToken).balanceOf(user); + + if (currentBalance < amount) { + // Deposit difference + vm.selectFork(rootId); + rootHelper.deposit(user, rootToken, amount - currentBalance, gasAmt); + vm.selectFork(childId); + } + + vm.selectFork(rootId); + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + + childHelper.withdraw(user, childToken, amount, gasAmt); + + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + vm.selectFork(childId); + + vm.selectFork(original); + } +} \ No newline at end of file diff --git a/test/invariant/child/ChildHelper.sol b/test/invariant/child/ChildHelper.sol new file mode 100644 index 00000000..9209b29a --- /dev/null +++ b/test/invariant/child/ChildHelper.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {ChildERC20} from "../../../src/child/ChildERC20.sol"; +import {ChildERC20Bridge} from "../../../src/child/ChildERC20Bridge.sol"; +import {IChildERC20} from "../../../src/interfaces/child/IChildERC20.sol"; + +contract ChildHelper is Test { + ChildERC20Bridge public childBridge; + + uint256 public totalGas; + + constructor(address payable _childBridge) { + childBridge = ChildERC20Bridge(_childBridge); + } + + function withdraw(address user, address childToken, uint256 amount, uint256 gasAmt) public { + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + childBridge.withdraw{value: gasAmt}(IChildERC20(childToken), amount); + } +} \ No newline at end of file diff --git a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol new file mode 100644 index 00000000..50d33d2f --- /dev/null +++ b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {ChildERC20} from "../../../src/child/ChildERC20.sol"; +import {ChildHelper} from "../child/ChildHelper.sol"; +import {RootHelper} from "./RootHelper.sol"; + +contract RootERC20BridgeFlowRateHandler is Test { + uint256 public constant MAX_AMOUNT = 10000; + uint256 public constant MAX_GAS = 100; + + uint256 childId; + uint256 rootId; + address[] users; + address[] rootTokens; + ChildHelper childHelper; + RootHelper rootHelper; + + constructor( + uint256 _childId, + uint256 _rootId, + address[] memory _users, + address[] memory _rootTokens, + address _childHelper, + address _rootHelper + ) { + childId = _childId; + rootId = _rootId; + users = _users; + rootTokens = _rootTokens; + childHelper = ChildHelper(_childHelper); + rootHelper = RootHelper(_rootHelper); + } + + function deposit(uint256 userIndexSeed, uint256 tokenIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to root chain + vm.selectFork(rootId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address rootToken = rootTokens[bound(tokenIndexSeed, 0, rootTokens.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get child token + address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); + + // Get current balance + uint256 currentBalance = ChildERC20(rootToken).balanceOf(user); + + if (currentBalance < amount) { + // Withdraw difference + uint256 previousLen = rootHelper.getQueueSize(user); + + vm.selectFork(childId); + childHelper.withdraw(user, childToken, amount - currentBalance, gasAmt); + vm.selectFork(rootId); + + rootHelper.finaliseWithdrawal(user, previousLen); + } + + rootHelper.deposit(user, rootToken, amount, gasAmt); + + vm.selectFork(original); + } +} \ No newline at end of file diff --git a/test/invariant/root/RootHelper.sol b/test/invariant/root/RootHelper.sol new file mode 100644 index 00000000..76174c6e --- /dev/null +++ b/test/invariant/root/RootHelper.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {ChildERC20} from "../../../src/child/ChildERC20.sol"; +import {RootERC20BridgeFlowRate} from "../../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; +import {IERC20Metadata} from "../../../src/root/RootERC20Bridge.sol"; + +contract RootHelper is Test { + address admin; + RootERC20BridgeFlowRate public rootBridge; + + uint256 public totalGas; + + constructor(address _admin, address payable _rootBridge) { + admin = _admin; + rootBridge = RootERC20BridgeFlowRate(_rootBridge); + } + + function deposit(address user, address rootToken, uint256 amount, uint256 gasAmt) public { + vm.prank(user); + ChildERC20(rootToken).approve(address(rootBridge), amount); + + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + rootBridge.deposit{value: gasAmt}(IERC20Metadata(rootToken), amount); + } + + function getQueueSize(address user) public view returns (uint256) { + return rootBridge.getPendingWithdrawalsLength(user); + } + + function finaliseWithdrawal(address user, uint256 previousLen) public { + // Check if this withdrawal has hit rate limit + if (rootBridge.getPendingWithdrawalsLength(user) > previousLen) { + skip(86401); + vm.prank(user); + rootBridge.finaliseQueuedWithdrawal(user, previousLen); + } + + if (rootBridge.withdrawalQueueActivated()) { + vm.prank(admin); + rootBridge.deactivateWithdrawalQueue(); + } + } +} \ No newline at end of file From 85a58df444173910451b740641a90fd40848408b Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 02:10:56 +1000 Subject: [PATCH 02/33] Fix --- test/invariant/MockAdaptor.sol | 2 +- .../child/ChildERC20BridgeHandler.sol | 11 ++++++---- .../root/RootERC20BridgeFlowRateHandler.sol | 20 ++++++++++--------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/test/invariant/MockAdaptor.sol b/test/invariant/MockAdaptor.sol index 3666ccdd..f826fc6e 100644 --- a/test/invariant/MockAdaptor.sol +++ b/test/invariant/MockAdaptor.sol @@ -30,7 +30,7 @@ contract MockAdaptor is Test, IChildBridgeAdaptor, IRootBridgeAdaptor { // Switch to the other chain. vm.selectFork(otherChainId); - console.log(""); // <= Bug + console.log(""); // <= // Due to a foundry bug, remove this logging will very likely cause foundry to crash. onMessageReceive(payload); vm.selectFork(original); diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol index 8b1ff059..ce6fa164 100644 --- a/test/invariant/child/ChildERC20BridgeHandler.sol +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -68,10 +68,13 @@ contract ChildERC20BridgeHandler is Test { uint256 currentBalance = ChildERC20(childToken).balanceOf(user); if (currentBalance < amount) { - // Deposit difference - vm.selectFork(rootId); - rootHelper.deposit(user, rootToken, amount - currentBalance, gasAmt); - vm.selectFork(childId); + // // Deposit difference + // vm.selectFork(rootId); + // rootHelper.deposit(user, rootToken, amount - currentBalance, gasAmt); + // vm.selectFork(childId); + vm.selectFork(original); + // TODO: Issue when try to deposit in withdraw flow. + return; } vm.selectFork(rootId); diff --git a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol index 50d33d2f..99e56881 100644 --- a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol +++ b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol @@ -45,21 +45,23 @@ contract RootERC20BridgeFlowRateHandler is Test { amount = bound(amount, 1, MAX_AMOUNT); gasAmt = bound(gasAmt, 1, MAX_GAS); - // Get child token - address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); - // Get current balance uint256 currentBalance = ChildERC20(rootToken).balanceOf(user); if (currentBalance < amount) { - // Withdraw difference - uint256 previousLen = rootHelper.getQueueSize(user); + // // Withdraw difference + // // Get child token + // address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); + // uint256 previousLen = rootHelper.getQueueSize(user); - vm.selectFork(childId); - childHelper.withdraw(user, childToken, amount - currentBalance, gasAmt); - vm.selectFork(rootId); + // vm.selectFork(childId); + // childHelper.withdraw(user, childToken, amount - currentBalance, gasAmt); + // vm.selectFork(rootId); - rootHelper.finaliseWithdrawal(user, previousLen); + // rootHelper.finaliseWithdrawal(user, previousLen); + vm.selectFork(original); + // TODO: Issue when try to withdraw in deposit flow. + return; } rootHelper.deposit(user, rootToken, amount, gasAmt); From f7baef00d19b8fc31604b5cac3f35e5ff2e7468f Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 09:52:40 +1000 Subject: [PATCH 03/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 78 ++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 9e4945f2..2bdcbd79 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -166,19 +166,89 @@ contract InvariantBridge is Test { } // Target contracts - bytes4[] memory childSelectors = new bytes4[](1); - childSelectors[0] = childBridgeHandler.withdraw.selector; - targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors})); + // bytes4[] memory childSelectors = new bytes4[](1); + // childSelectors[0] = childBridgeHandler.withdraw.selector; + // targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors})); bytes4[] memory rootSelectors = new bytes4[](1); rootSelectors[0] = rootBridgeHandler.deposit.selector; targetSelector(FuzzSelector({addr: address(rootBridgeHandler), selectors: rootSelectors})); - targetContract(address(childBridgeHandler)); + // targetContract(address(childBridgeHandler)); targetContract(address(rootBridgeHandler)); } + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 1 /// forge-config: default.invariant.fail-on-revert = false function invariant_A() external { + vm.selectFork(rootId); + uint256 bridgeBalance0 = ChildERC20(rootTokens[0]).balanceOf(address(rootBridge)); + uint256 bridgeBalance1 = ChildERC20(rootTokens[1]).balanceOf(address(rootBridge)); + uint256 bridgeBalance2 = ChildERC20(rootTokens[2]).balanceOf(address(rootBridge)); + uint256 bridgeBalance3 = ChildERC20(rootTokens[3]).balanceOf(address(rootBridge)); + + address childToken0 = rootBridge.rootTokenToChildToken(rootTokens[0]); + address childToken1 = rootBridge.rootTokenToChildToken(rootTokens[1]); + address childToken2 = rootBridge.rootTokenToChildToken(rootTokens[2]); + address childToken3 = rootBridge.rootTokenToChildToken(rootTokens[3]); + + vm.selectFork(childId); + uint256 totalSupply0 = ChildERC20(childToken0).totalSupply(); + uint256 totalSupply1 = ChildERC20(childToken1).totalSupply(); + uint256 totalSupply2 = ChildERC20(childToken2).totalSupply(); + uint256 totalSupply3 = ChildERC20(childToken3).totalSupply(); + + console.log(string.concat(string.concat(vm.toString(bridgeBalance0), " "), vm.toString(totalSupply0))); + console.log(string.concat(string.concat(vm.toString(bridgeBalance1), " "), vm.toString(totalSupply1))); + console.log(string.concat(string.concat(vm.toString(bridgeBalance2), " "), vm.toString(totalSupply2))); + console.log(string.concat(string.concat(vm.toString(bridgeBalance3), " "), vm.toString(totalSupply3))); + + if (bridgeBalance0 != totalSupply0) { + console.log("000"); + revert(string.concat("**0**",string.concat(string.concat(vm.toString(bridgeBalance0), " "), vm.toString(totalSupply0)))); + } + + if (bridgeBalance1 != totalSupply1) { + console.log("111"); + revert(string.concat("**1**",string.concat(string.concat(vm.toString(bridgeBalance1), " "), vm.toString(totalSupply1)))); + } + + if (bridgeBalance2 != totalSupply2) { + console.log("222"); + revert(string.concat("**2**",string.concat(string.concat(vm.toString(bridgeBalance2), " "), vm.toString(totalSupply2)))); + } + + if (bridgeBalance3 != totalSupply3) { + console.log("333"); + revert(string.concat("**3**",string.concat(string.concat(vm.toString(bridgeBalance3), " "), vm.toString(totalSupply3)))); + } + + // assertEq(bridgeBalance0, totalSupply0); + // assertEq(bridgeBalance1, totalSupply1); + // assertEq(bridgeBalance2, totalSupply2); + // assertEq(bridgeBalance3, totalSupply3); + + // for (uint256 i = 0; i < NO_OF_TOKENS; i++) { + // address rootToken = rootTokens[i]; + + // vm.selectFork(rootId); + // uint256 bridgeBalance = ChildERC20(rootToken).balanceOf(address(rootBridge)); + // address childToken = rootBridge.rootTokenToChildToken(rootToken); + + // vm.selectFork(childId); + // uint256 totalSupply = ChildERC20(childToken).totalSupply(); + + // string memory log1 = string.concat(string.concat(vm.toString(bridgeBalance), " "), vm.toString(totalSupply)); + // console.log(string.concat("!!!", log1)); + // if (bridgeBalance != totalSupply) { + // console.log("I'm here...."); + // // // string memory res = string.concat(string.concat(vm.toString(bridgeBalance), " "), vm.toString(totalSupply)); + // // console.log(); + // revert(string.concat("???", log1)); + // // vm.writeFile("./something.txt", log1); + // } + // // assertEq(bridgeBalance, totalSupply); + // } } } \ No newline at end of file From ccf95ab555ab7b4281e076a62714be21b6dba448 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 13:42:00 +1000 Subject: [PATCH 04/33] Update --- .../child/ChildERC20BridgeHandler.sol | 11 ++++------- .../root/RootERC20BridgeFlowRateHandler.sol | 19 ++++++++----------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol index ce6fa164..8b1ff059 100644 --- a/test/invariant/child/ChildERC20BridgeHandler.sol +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -68,13 +68,10 @@ contract ChildERC20BridgeHandler is Test { uint256 currentBalance = ChildERC20(childToken).balanceOf(user); if (currentBalance < amount) { - // // Deposit difference - // vm.selectFork(rootId); - // rootHelper.deposit(user, rootToken, amount - currentBalance, gasAmt); - // vm.selectFork(childId); - vm.selectFork(original); - // TODO: Issue when try to deposit in withdraw flow. - return; + // Deposit difference + vm.selectFork(rootId); + rootHelper.deposit(user, rootToken, amount - currentBalance, gasAmt); + vm.selectFork(childId); } vm.selectFork(rootId); diff --git a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol index 99e56881..876b2e9e 100644 --- a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol +++ b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol @@ -49,19 +49,16 @@ contract RootERC20BridgeFlowRateHandler is Test { uint256 currentBalance = ChildERC20(rootToken).balanceOf(user); if (currentBalance < amount) { - // // Withdraw difference - // // Get child token - // address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); - // uint256 previousLen = rootHelper.getQueueSize(user); + // Withdraw difference + // Get child token + address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); + uint256 previousLen = rootHelper.getQueueSize(user); - // vm.selectFork(childId); - // childHelper.withdraw(user, childToken, amount - currentBalance, gasAmt); - // vm.selectFork(rootId); + vm.selectFork(childId); + childHelper.withdraw(user, childToken, amount - currentBalance, gasAmt); + vm.selectFork(rootId); - // rootHelper.finaliseWithdrawal(user, previousLen); - vm.selectFork(original); - // TODO: Issue when try to withdraw in deposit flow. - return; + rootHelper.finaliseWithdrawal(user, previousLen); } rootHelper.deposit(user, rootToken, amount, gasAmt); From de5977f1cf4399db173aaf611e3fd382cecd026b Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 13:54:18 +1000 Subject: [PATCH 05/33] Update MockAdaptor.sol --- test/invariant/MockAdaptor.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/invariant/MockAdaptor.sol b/test/invariant/MockAdaptor.sol index f826fc6e..27624bc3 100644 --- a/test/invariant/MockAdaptor.sol +++ b/test/invariant/MockAdaptor.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.19; import {Test} from "forge-std/Test.sol"; import {IChildBridgeAdaptor} from "../../src/interfaces/child/IChildBridgeAdaptor.sol"; import {IRootBridgeAdaptor} from "../../src/interfaces/root/IRootBridgeAdaptor.sol"; -import "forge-std/console.sol"; interface MessageReceiver { function onMessageReceive(bytes calldata data) external; @@ -30,7 +29,6 @@ contract MockAdaptor is Test, IChildBridgeAdaptor, IRootBridgeAdaptor { // Switch to the other chain. vm.selectFork(otherChainId); - console.log(""); // <= // Due to a foundry bug, remove this logging will very likely cause foundry to crash. onMessageReceive(payload); vm.selectFork(original); From c2f8e47359b1a7610ba9e19fccca11927486c374 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 13:55:11 +1000 Subject: [PATCH 06/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 37 ++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 2bdcbd79..9b96ea5b 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -17,6 +17,7 @@ import "forge-std/console.sol"; contract InvariantBridge is Test { string public constant CHILD_CHAIN_URL = "http://127.0.0.1:8500"; string public constant ROOT_CHAIN_URL = "http://127.0.0.1:8501"; + string public constant RESET_CHAIN_URL = "http://127.0.0.1:8502"; uint256 public constant IMX_DEPOSIT_LIMIT = 10000 ether; uint256 public constant MAX_AMOUNT = 10000; address public constant ADMIN = address(0x111); @@ -28,6 +29,7 @@ contract InvariantBridge is Test { uint256 childId; uint256 rootId; + uint256 resetId; ChildERC20Bridge childBridge; RootERC20BridgeFlowRate rootBridge; MockAdaptor childAdaptor; @@ -40,6 +42,7 @@ contract InvariantBridge is Test { function setUp() public { childId = vm.createFork(CHILD_CHAIN_URL); rootId = vm.createFork(ROOT_CHAIN_URL); + resetId = vm.createFork(RESET_CHAIN_URL); // Deploy contracts on child chain. vm.selectFork(childId); @@ -65,6 +68,14 @@ contract InvariantBridge is Test { rootIMXToken.initialize(address(123), "Immutable X", "IMX", 18); WIMX wETH = new WIMX(); + // Deploy contracts on reset chain. + vm.selectFork(resetId); + vm.startPrank(ADMIN); + ChildERC20 resetTokenTemplate = new ChildERC20(); + resetTokenTemplate.initialize(address(123), "Test", "TST", 18); + new MockAdaptor(); + vm.stopPrank(); + // Configure contracts on child chain. vm.selectFork(childId); childAdaptor.initialize(rootId, address(childBridge)); @@ -131,7 +142,7 @@ contract InvariantBridge is Test { rootTokens.push(address(rootToken)); } - // Deploy helpers and handlers on both chains. + // Deploy helpers and handlers on all chains. vm.selectFork(childId); vm.startPrank(ADMIN); childHelper = new ChildHelper(payable(childBridge)); @@ -150,6 +161,14 @@ contract InvariantBridge is Test { new RootERC20BridgeFlowRateHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); vm.stopPrank(); + vm.selectFork(resetId); + vm.startPrank(ADMIN); + new ChildHelper(payable(childBridge)); + new RootHelper(ADMIN, payable(rootBridge)); + new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); + new RootERC20BridgeFlowRateHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); + vm.stopPrank(); + // Map tokens vm.selectFork(rootId); for (uint256 i = 0; i < NO_OF_TOKENS; i++) { @@ -166,21 +185,23 @@ contract InvariantBridge is Test { } // Target contracts - // bytes4[] memory childSelectors = new bytes4[](1); - // childSelectors[0] = childBridgeHandler.withdraw.selector; - // targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors})); + bytes4[] memory childSelectors = new bytes4[](1); + childSelectors[0] = childBridgeHandler.withdraw.selector; + targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors})); bytes4[] memory rootSelectors = new bytes4[](1); rootSelectors[0] = rootBridgeHandler.deposit.selector; targetSelector(FuzzSelector({addr: address(rootBridgeHandler), selectors: rootSelectors})); - // targetContract(address(childBridgeHandler)); + targetContract(address(childBridgeHandler)); targetContract(address(rootBridgeHandler)); + + vm.selectFork(resetId); } - /// forge-config: default.invariant.runs = 1 - /// forge-config: default.invariant.depth = 1 - /// forge-config: default.invariant.fail-on-revert = false + // forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true function invariant_A() external { vm.selectFork(rootId); uint256 bridgeBalance0 = ChildERC20(rootTokens[0]).balanceOf(address(rootBridge)); From 86a629f58d53079916de864330ceacf822a121ea Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 13:55:42 +1000 Subject: [PATCH 07/33] Fmt --- test/invariant/InvariantBridge.t.sol | 38 ++++++++++++++----- test/invariant/MockAdaptor.sol | 4 +- .../child/ChildERC20BridgeHandler.sol | 2 +- test/invariant/child/ChildHelper.sol | 2 +- .../root/RootERC20BridgeFlowRateHandler.sol | 4 +- test/invariant/root/RootHelper.sol | 4 +- 6 files changed, 36 insertions(+), 18 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 9b96ea5b..441c7e41 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -147,8 +147,7 @@ contract InvariantBridge is Test { vm.startPrank(ADMIN); childHelper = new ChildHelper(payable(childBridge)); address temp = address(new RootHelper(ADMIN, payable(rootBridge))); - childBridgeHandler = - new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), temp); + childBridgeHandler = new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), temp); new RootERC20BridgeFlowRateHandler(childId, rootId, users, rootTokens, address(childHelper), temp); vm.stopPrank(); @@ -157,8 +156,9 @@ contract InvariantBridge is Test { new ChildHelper(payable(childBridge)); rootHelper = new RootHelper(ADMIN, payable(rootBridge)); new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); - rootBridgeHandler = - new RootERC20BridgeFlowRateHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); + rootBridgeHandler = new RootERC20BridgeFlowRateHandler( + childId, rootId, users, rootTokens, address(childHelper), address(rootHelper) + ); vm.stopPrank(); vm.selectFork(resetId); @@ -166,7 +166,9 @@ contract InvariantBridge is Test { new ChildHelper(payable(childBridge)); new RootHelper(ADMIN, payable(rootBridge)); new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); - new RootERC20BridgeFlowRateHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); + new RootERC20BridgeFlowRateHandler( + childId, rootId, users, rootTokens, address(childHelper), address(rootHelper) + ); vm.stopPrank(); // Map tokens @@ -227,22 +229,38 @@ contract InvariantBridge is Test { if (bridgeBalance0 != totalSupply0) { console.log("000"); - revert(string.concat("**0**",string.concat(string.concat(vm.toString(bridgeBalance0), " "), vm.toString(totalSupply0)))); + revert( + string.concat( + "**0**", string.concat(string.concat(vm.toString(bridgeBalance0), " "), vm.toString(totalSupply0)) + ) + ); } if (bridgeBalance1 != totalSupply1) { console.log("111"); - revert(string.concat("**1**",string.concat(string.concat(vm.toString(bridgeBalance1), " "), vm.toString(totalSupply1)))); + revert( + string.concat( + "**1**", string.concat(string.concat(vm.toString(bridgeBalance1), " "), vm.toString(totalSupply1)) + ) + ); } if (bridgeBalance2 != totalSupply2) { console.log("222"); - revert(string.concat("**2**",string.concat(string.concat(vm.toString(bridgeBalance2), " "), vm.toString(totalSupply2)))); + revert( + string.concat( + "**2**", string.concat(string.concat(vm.toString(bridgeBalance2), " "), vm.toString(totalSupply2)) + ) + ); } if (bridgeBalance3 != totalSupply3) { console.log("333"); - revert(string.concat("**3**",string.concat(string.concat(vm.toString(bridgeBalance3), " "), vm.toString(totalSupply3)))); + revert( + string.concat( + "**3**", string.concat(string.concat(vm.toString(bridgeBalance3), " "), vm.toString(totalSupply3)) + ) + ); } // assertEq(bridgeBalance0, totalSupply0); @@ -272,4 +290,4 @@ contract InvariantBridge is Test { // // assertEq(bridgeBalance, totalSupply); // } } -} \ No newline at end of file +} diff --git a/test/invariant/MockAdaptor.sol b/test/invariant/MockAdaptor.sol index 27624bc3..0a5bfe83 100644 --- a/test/invariant/MockAdaptor.sol +++ b/test/invariant/MockAdaptor.sol @@ -30,11 +30,11 @@ contract MockAdaptor is Test, IChildBridgeAdaptor, IRootBridgeAdaptor { // Switch to the other chain. vm.selectFork(otherChainId); onMessageReceive(payload); - + vm.selectFork(original); } function onMessageReceive(bytes calldata data) public { messageReceiver.onMessageReceive(data); } -} \ No newline at end of file +} diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol index 8b1ff059..a6a3bab0 100644 --- a/test/invariant/child/ChildERC20BridgeHandler.sol +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -86,4 +86,4 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(original); } -} \ No newline at end of file +} diff --git a/test/invariant/child/ChildHelper.sol b/test/invariant/child/ChildHelper.sol index 9209b29a..41aedff9 100644 --- a/test/invariant/child/ChildHelper.sol +++ b/test/invariant/child/ChildHelper.sol @@ -22,4 +22,4 @@ contract ChildHelper is Test { vm.prank(user); childBridge.withdraw{value: gasAmt}(IChildERC20(childToken), amount); } -} \ No newline at end of file +} diff --git a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol index 876b2e9e..1c62cd42 100644 --- a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol +++ b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol @@ -53,7 +53,7 @@ contract RootERC20BridgeFlowRateHandler is Test { // Get child token address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); uint256 previousLen = rootHelper.getQueueSize(user); - + vm.selectFork(childId); childHelper.withdraw(user, childToken, amount - currentBalance, gasAmt); vm.selectFork(rootId); @@ -65,4 +65,4 @@ contract RootERC20BridgeFlowRateHandler is Test { vm.selectFork(original); } -} \ No newline at end of file +} diff --git a/test/invariant/root/RootHelper.sol b/test/invariant/root/RootHelper.sol index 76174c6e..fd81acac 100644 --- a/test/invariant/root/RootHelper.sol +++ b/test/invariant/root/RootHelper.sol @@ -39,10 +39,10 @@ contract RootHelper is Test { vm.prank(user); rootBridge.finaliseQueuedWithdrawal(user, previousLen); } - + if (rootBridge.withdrawalQueueActivated()) { vm.prank(admin); rootBridge.deactivateWithdrawalQueue(); } } -} \ No newline at end of file +} From 9178788a0fac0c24064c0181b9574455eb62c9f0 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 13:56:34 +1000 Subject: [PATCH 08/33] Create resetchain.config.ts --- scripts/localdev/resetchain.config.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 scripts/localdev/resetchain.config.ts diff --git a/scripts/localdev/resetchain.config.ts b/scripts/localdev/resetchain.config.ts new file mode 100644 index 00000000..116a3eba --- /dev/null +++ b/scripts/localdev/resetchain.config.ts @@ -0,0 +1,21 @@ +import { HardhatUserConfig } from "hardhat/config"; +import "@nomicfoundation/hardhat-toolbox"; + +const config: HardhatUserConfig = { + networks: { + hardhat: { + hardfork: "shanghai", + mining: { + auto: false, + interval: 1200 + }, + chainId: 2502, + accounts: [], + }, + localhost: { + url: "http://127.0.0.1:8502/", + } + }, + solidity: "0.8.19", +}; +export default config; \ No newline at end of file From 808187263f94d0908ee310dd997ac4030070476c Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 14:00:44 +1000 Subject: [PATCH 09/33] Update --- package.json | 1 + scripts/localdev/chains.sh | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100755 scripts/localdev/chains.sh diff --git a/package.json b/package.json index f3648521..fb877879 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "local:ci": "cd scripts/localdev; rm -rf .child.bridge.contracts.json .root.bridge.contracts.json; ./ci.sh && ./deploy.sh && AXELAR_API_URL=skip npx mocha --require mocha-suppress-logs ../e2e/e2e.ts && ./stop.sh", "local:chainonly": "cd scripts/localdev; LOCAL_CHAIN_ONLY=true ./start.sh", "local:axelaronly": "cd scripts/localdev; npx ts-node axelar_setup.ts", + "local:threechains": "cd scripts/localdev; ./chains.sh", "stop": "cd scripts/localdev; ./stop.sh" }, "author": "", diff --git a/scripts/localdev/chains.sh b/scripts/localdev/chains.sh new file mode 100755 index 00000000..31d3cb0b --- /dev/null +++ b/scripts/localdev/chains.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -ex +set -o pipefail + +# Stop previous deployment. +./stop.sh + +# Start root & child chain. +npx hardhat node --config ./rootchain.config.ts --port 8500 > /dev/null 2>&1 & +npx hardhat node --config ./childchain.config.ts --port 8501 > /dev/null 2>&1 & +npx hardhat node --config ./resetchain.config.ts --port 8502 > /dev/null 2>&1 & +sleep 10 From 087864f4a8216955002d639555f1bb36494d2c8f Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 14:06:32 +1000 Subject: [PATCH 10/33] Update test.yml --- .github/workflows/test.yml | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eba27536..dfefbb8f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,6 +24,11 @@ jobs: with: submodules: recursive + - name: Set Node.js 18.18.x + uses: actions/setup-node@v3 + with: + node-version: 18.18.x + - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 with: @@ -35,12 +40,38 @@ jobs: forge build --sizes id: build - - name: Run unit and integration tests + - name: Run install + uses: borales/actions-yarn@v4 + with: + cmd: install + + - name: Run build + uses: borales/actions-yarn@v4 + with: + cmd: build + + - name: Run Unit Tests run: | - forge test --no-match-path "test/fork/**" -vvv - id: unit_integration_test + forge test --match-path "test/unit/**" -vvv + id: unit_test + + - name: Run Integration Tests + run: | + forge test --match-path "test/integration/**" -vvv + id: integration_test + + - name: Run Fuzz Tests + run: | + forge test --match-path "test/fuzz/**" -vvv + id: fuzz_test - name: Run Fork Tests run: | forge test --match-path "test/fork/**" -vvvvv id: fork_test + + - name: Run Invariant Tests + run: | + yarn local:threechains + forge test --match-path "test/invariant/**" -vvvvv + id: invariant_test \ No newline at end of file From 8255782849ec5ff901af4015afb244967621e66e Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 14:15:44 +1000 Subject: [PATCH 11/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 94 ++++------------------------ 1 file changed, 11 insertions(+), 83 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 441c7e41..8062a06d 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -21,8 +21,8 @@ contract InvariantBridge is Test { uint256 public constant IMX_DEPOSIT_LIMIT = 10000 ether; uint256 public constant MAX_AMOUNT = 10000; address public constant ADMIN = address(0x111); - uint256 public constant NO_OF_USERS = 5; - uint256 public constant NO_OF_TOKENS = 4; + uint256 public constant NO_OF_USERS = 20; + uint256 public constant NO_OF_TOKENS = 10; address[] users; address[] rootTokens; @@ -204,90 +204,18 @@ contract InvariantBridge is Test { // forge-config: default.invariant.runs = 256 /// forge-config: default.invariant.depth = 15 /// forge-config: default.invariant.fail-on-revert = true - function invariant_A() external { - vm.selectFork(rootId); - uint256 bridgeBalance0 = ChildERC20(rootTokens[0]).balanceOf(address(rootBridge)); - uint256 bridgeBalance1 = ChildERC20(rootTokens[1]).balanceOf(address(rootBridge)); - uint256 bridgeBalance2 = ChildERC20(rootTokens[2]).balanceOf(address(rootBridge)); - uint256 bridgeBalance3 = ChildERC20(rootTokens[3]).balanceOf(address(rootBridge)); - - address childToken0 = rootBridge.rootTokenToChildToken(rootTokens[0]); - address childToken1 = rootBridge.rootTokenToChildToken(rootTokens[1]); - address childToken2 = rootBridge.rootTokenToChildToken(rootTokens[2]); - address childToken3 = rootBridge.rootTokenToChildToken(rootTokens[3]); - - vm.selectFork(childId); - uint256 totalSupply0 = ChildERC20(childToken0).totalSupply(); - uint256 totalSupply1 = ChildERC20(childToken1).totalSupply(); - uint256 totalSupply2 = ChildERC20(childToken2).totalSupply(); - uint256 totalSupply3 = ChildERC20(childToken3).totalSupply(); - - console.log(string.concat(string.concat(vm.toString(bridgeBalance0), " "), vm.toString(totalSupply0))); - console.log(string.concat(string.concat(vm.toString(bridgeBalance1), " "), vm.toString(totalSupply1))); - console.log(string.concat(string.concat(vm.toString(bridgeBalance2), " "), vm.toString(totalSupply2))); - console.log(string.concat(string.concat(vm.toString(bridgeBalance3), " "), vm.toString(totalSupply3))); - - if (bridgeBalance0 != totalSupply0) { - console.log("000"); - revert( - string.concat( - "**0**", string.concat(string.concat(vm.toString(bridgeBalance0), " "), vm.toString(totalSupply0)) - ) - ); - } + function invariant_ERC20TokenBalanced() external { + for (uint256 i = 0; i < NO_OF_TOKENS; i++) { + address rootToken = rootTokens[i]; - if (bridgeBalance1 != totalSupply1) { - console.log("111"); - revert( - string.concat( - "**1**", string.concat(string.concat(vm.toString(bridgeBalance1), " "), vm.toString(totalSupply1)) - ) - ); - } + vm.selectFork(rootId); + uint256 bridgeBalance = ChildERC20(rootToken).balanceOf(address(rootBridge)); + address childToken = rootBridge.rootTokenToChildToken(rootToken); - if (bridgeBalance2 != totalSupply2) { - console.log("222"); - revert( - string.concat( - "**2**", string.concat(string.concat(vm.toString(bridgeBalance2), " "), vm.toString(totalSupply2)) - ) - ); - } + vm.selectFork(childId); + uint256 totalSupply = ChildERC20(childToken).totalSupply(); - if (bridgeBalance3 != totalSupply3) { - console.log("333"); - revert( - string.concat( - "**3**", string.concat(string.concat(vm.toString(bridgeBalance3), " "), vm.toString(totalSupply3)) - ) - ); + assertEq(bridgeBalance, totalSupply); } - - // assertEq(bridgeBalance0, totalSupply0); - // assertEq(bridgeBalance1, totalSupply1); - // assertEq(bridgeBalance2, totalSupply2); - // assertEq(bridgeBalance3, totalSupply3); - - // for (uint256 i = 0; i < NO_OF_TOKENS; i++) { - // address rootToken = rootTokens[i]; - - // vm.selectFork(rootId); - // uint256 bridgeBalance = ChildERC20(rootToken).balanceOf(address(rootBridge)); - // address childToken = rootBridge.rootTokenToChildToken(rootToken); - - // vm.selectFork(childId); - // uint256 totalSupply = ChildERC20(childToken).totalSupply(); - - // string memory log1 = string.concat(string.concat(vm.toString(bridgeBalance), " "), vm.toString(totalSupply)); - // console.log(string.concat("!!!", log1)); - // if (bridgeBalance != totalSupply) { - // console.log("I'm here...."); - // // // string memory res = string.concat(string.concat(vm.toString(bridgeBalance), " "), vm.toString(totalSupply)); - // // console.log(); - // revert(string.concat("???", log1)); - // // vm.writeFile("./something.txt", log1); - // } - // // assertEq(bridgeBalance, totalSupply); - // } } } From da6dcf31cb7c5e54907fc50e7c9c175a4b19c5c9 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 14:17:31 +1000 Subject: [PATCH 12/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 8062a06d..c9c645df 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -201,7 +201,7 @@ contract InvariantBridge is Test { vm.selectFork(resetId); } - // forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.runs = 256 /// forge-config: default.invariant.depth = 15 /// forge-config: default.invariant.fail-on-revert = true function invariant_ERC20TokenBalanced() external { From 32e49edceb422140c159f709b8715a015d120298 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 14:20:36 +1000 Subject: [PATCH 13/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index c9c645df..650a47d4 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -215,7 +215,14 @@ contract InvariantBridge is Test { vm.selectFork(childId); uint256 totalSupply = ChildERC20(childToken).totalSupply(); + uint256 userBalanceSum = 0; + for (uint256 j = 0; j < NO_OF_USERS; j++) { + address user = users[j]; + userBalanceSum += ChildERC20(childToken).balanceOf(user); + } + assertEq(bridgeBalance, totalSupply); + assertEq(bridgeBalance, userBalanceSum); } } } From fcfc46399276c8648c8fdab7294db93f4d535bf0 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 14:33:49 +1000 Subject: [PATCH 14/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 650a47d4..8fa902c1 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -225,4 +225,25 @@ contract InvariantBridge is Test { assertEq(bridgeBalance, userBalanceSum); } } + + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_IndividualERC20Balanced() external { + for (uint256 i = 0; i < NO_OF_TOKENS; i++) { + address rootToken = rootTokens[i]; + for (uint256 j = 0; j < NO_OF_USERS; j++) { + address user = users[j]; + + vm.selectFork(rootId); + uint256 balanceL1 = ChildERC20(rootToken).balanceOf(user); + address childToken = rootBridge.rootTokenToChildToken(rootToken); + + vm.selectFork(childId); + uint256 balanceL2 = ChildERC20(childToken).balanceOf(user); + + assertEq(balanceL1 + balanceL2, MAX_AMOUNT); + } + } + } } From 1a36766f7b75cdfd9eeefe89a4038bee35e87a99 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 14:34:32 +1000 Subject: [PATCH 15/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 8fa902c1..546c6945 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -229,7 +229,7 @@ contract InvariantBridge is Test { /// forge-config: default.invariant.runs = 256 /// forge-config: default.invariant.depth = 15 /// forge-config: default.invariant.fail-on-revert = true - function invariant_IndividualERC20Balanced() external { + function invariant_IndividualERC20TokenBalanced() external { for (uint256 i = 0; i < NO_OF_TOKENS; i++) { address rootToken = rootTokens[i]; for (uint256 j = 0; j < NO_OF_USERS; j++) { From f49b8f93aba851d5b14110bc79d1552b242451c1 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 21 Feb 2024 15:41:42 +1000 Subject: [PATCH 16/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 546c6945..f9afc0cf 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -238,7 +238,7 @@ contract InvariantBridge is Test { vm.selectFork(rootId); uint256 balanceL1 = ChildERC20(rootToken).balanceOf(user); address childToken = rootBridge.rootTokenToChildToken(rootToken); - + vm.selectFork(childId); uint256 balanceL2 = ChildERC20(childToken).balanceOf(user); From 5d548198ce3c7d09fa0a28d47dc2ce70f9ed7f10 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 26 Feb 2024 23:22:20 +1000 Subject: [PATCH 17/33] Withdraw/Deposit --- test/invariant/InvariantBridge.t.sol | 27 ++------ .../child/ChildERC20BridgeHandler.sol | 67 ++++++++++++++++++- test/invariant/child/ChildHelper.sol | 8 +++ .../root/RootERC20BridgeFlowRateHandler.sol | 62 ++++++++++++++++- test/invariant/root/RootHelper.sol | 11 +++ 5 files changed, 150 insertions(+), 25 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index f9afc0cf..eee4f254 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -187,12 +187,14 @@ contract InvariantBridge is Test { } // Target contracts - bytes4[] memory childSelectors = new bytes4[](1); + bytes4[] memory childSelectors = new bytes4[](2); childSelectors[0] = childBridgeHandler.withdraw.selector; + childSelectors[1] = childBridgeHandler.withdrawTo.selector; targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors})); - bytes4[] memory rootSelectors = new bytes4[](1); + bytes4[] memory rootSelectors = new bytes4[](2); rootSelectors[0] = rootBridgeHandler.deposit.selector; + rootSelectors[1] = rootBridgeHandler.depositTo.selector; targetSelector(FuzzSelector({addr: address(rootBridgeHandler), selectors: rootSelectors})); targetContract(address(childBridgeHandler)); @@ -225,25 +227,4 @@ contract InvariantBridge is Test { assertEq(bridgeBalance, userBalanceSum); } } - - /// forge-config: default.invariant.runs = 256 - /// forge-config: default.invariant.depth = 15 - /// forge-config: default.invariant.fail-on-revert = true - function invariant_IndividualERC20TokenBalanced() external { - for (uint256 i = 0; i < NO_OF_TOKENS; i++) { - address rootToken = rootTokens[i]; - for (uint256 j = 0; j < NO_OF_USERS; j++) { - address user = users[j]; - - vm.selectFork(rootId); - uint256 balanceL1 = ChildERC20(rootToken).balanceOf(user); - address childToken = rootBridge.rootTokenToChildToken(rootToken); - - vm.selectFork(childId); - uint256 balanceL2 = ChildERC20(childToken).balanceOf(user); - - assertEq(balanceL1 + balanceL2, MAX_AMOUNT); - } - } - } } diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol index a6a3bab0..49d4a918 100644 --- a/test/invariant/child/ChildERC20BridgeHandler.sol +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -70,7 +70,7 @@ contract ChildERC20BridgeHandler is Test { if (currentBalance < amount) { // Deposit difference vm.selectFork(rootId); - rootHelper.deposit(user, rootToken, amount - currentBalance, gasAmt); + rootHelper.deposit(user, rootToken, amount - currentBalance, 1); vm.selectFork(childId); } @@ -86,4 +86,69 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(original); } + + function withdrawTo( + uint256 userIndexSeed, + uint256 recipientIndexSeed, + uint256 tokenIndexSeed, + uint256 amount, + uint256 gasAmt + ) public { + uint256 original = vm.activeFork(); + + // Switch to child chain + vm.selectFork(childId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address recipient = users[bound(recipientIndexSeed, 0, users.length - 1)]; + address rootToken = rootTokens[bound(tokenIndexSeed, 0, rootTokens.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get child token + address childToken = childHelper.childBridge().rootTokenToChildToken(rootToken); + + // Get current balance + uint256 currentBalance = ChildERC20(childToken).balanceOf(user); + + if (currentBalance < amount) { + // Deposit difference + vm.selectFork(rootId); + uint256 offset = bound(userIndexSeed, 0, users.length - 1); + uint256 diff = amount - currentBalance; + address from = findDepositFrom(offset, rootToken, diff); + rootHelper.depositTo(from, user, rootToken, diff, 1); + vm.selectFork(childId); + } + + vm.selectFork(rootId); + uint256 previousLen = rootHelper.getQueueSize(recipient); + vm.selectFork(childId); + + childHelper.withdrawTo(user, recipient, childToken, amount, gasAmt); + + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(recipient, previousLen); + vm.selectFork(childId); + + vm.selectFork(original); + } + + function findDepositFrom(uint256 offset, address rootToken, uint256 requiredAmt) + public + view + returns (address from) + { + for (uint256 i = 0; i < users.length; i++) { + uint256 index = i + offset; + if (index >= users.length) { + index -= users.length; + } + if (ChildERC20(rootToken).balanceOf(users[index]) >= requiredAmt) { + from = users[index]; + break; + } + } + } } diff --git a/test/invariant/child/ChildHelper.sol b/test/invariant/child/ChildHelper.sol index 41aedff9..bbca00fe 100644 --- a/test/invariant/child/ChildHelper.sol +++ b/test/invariant/child/ChildHelper.sol @@ -22,4 +22,12 @@ contract ChildHelper is Test { vm.prank(user); childBridge.withdraw{value: gasAmt}(IChildERC20(childToken), amount); } + + function withdrawTo(address user, address recipient, address childToken, uint256 amount, uint256 gasAmt) public { + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + childBridge.withdrawTo{value: gasAmt}(IChildERC20(childToken), recipient, amount); + } } diff --git a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol index 1c62cd42..5d77a510 100644 --- a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol +++ b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol @@ -55,7 +55,7 @@ contract RootERC20BridgeFlowRateHandler is Test { uint256 previousLen = rootHelper.getQueueSize(user); vm.selectFork(childId); - childHelper.withdraw(user, childToken, amount - currentBalance, gasAmt); + childHelper.withdraw(user, childToken, amount - currentBalance, 1); vm.selectFork(rootId); rootHelper.finaliseWithdrawal(user, previousLen); @@ -65,4 +65,64 @@ contract RootERC20BridgeFlowRateHandler is Test { vm.selectFork(original); } + + function depositTo( + uint256 userIndexSeed, + uint256 recipientIndexSeed, + uint256 tokenIndexSeed, + uint256 amount, + uint256 gasAmt + ) public { + uint256 original = vm.activeFork(); + + // Switch to root chain + vm.selectFork(rootId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address recipient = users[bound(recipientIndexSeed, 0, users.length - 1)]; + address rootToken = rootTokens[bound(tokenIndexSeed, 0, rootTokens.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = ChildERC20(rootToken).balanceOf(user); + + if (currentBalance < amount) { + // Withdraw difference + // Get child token + address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); + uint256 previousLen = rootHelper.getQueueSize(user); + + vm.selectFork(childId); + uint256 offset = bound(userIndexSeed, 0, users.length - 1); + uint256 diff = amount - currentBalance; + address from = findWithdrawFrom(offset, childToken, diff); + childHelper.withdrawTo(from, user, childToken, diff, 1); + vm.selectFork(rootId); + + rootHelper.finaliseWithdrawal(user, previousLen); + } + + rootHelper.depositTo(user, recipient, rootToken, amount, gasAmt); + + vm.selectFork(original); + } + + function findWithdrawFrom(uint256 offset, address childToken, uint256 requiredAmt) + public + view + returns (address from) + { + for (uint256 i = 0; i < users.length; i++) { + uint256 index = i + offset; + if (index >= users.length) { + index -= users.length; + } + if (ChildERC20(childToken).balanceOf(users[index]) >= requiredAmt) { + from = users[index]; + break; + } + } + } } diff --git a/test/invariant/root/RootHelper.sol b/test/invariant/root/RootHelper.sol index fd81acac..634a9e53 100644 --- a/test/invariant/root/RootHelper.sol +++ b/test/invariant/root/RootHelper.sol @@ -28,6 +28,17 @@ contract RootHelper is Test { rootBridge.deposit{value: gasAmt}(IERC20Metadata(rootToken), amount); } + function depositTo(address user, address recipient, address rootToken, uint256 amount, uint256 gasAmt) public { + vm.prank(user); + ChildERC20(rootToken).approve(address(rootBridge), amount); + + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + rootBridge.depositTo{value: gasAmt}(IERC20Metadata(rootToken), recipient, amount); + } + function getQueueSize(address user) public view returns (uint256) { return rootBridge.getPendingWithdrawalsLength(user); } From 62e5bc95ceeb1d28e9b5ba480d0d6e0c85dc0231 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Mon, 26 Feb 2024 23:24:19 +1000 Subject: [PATCH 18/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index eee4f254..db4f9da3 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -227,4 +227,12 @@ contract InvariantBridge is Test { assertEq(bridgeBalance, userBalanceSum); } } + + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_GasBalanced() external { + assertEq(address(rootAdaptor).balance, rootHelper.totalGas()); + assertEq(address(childAdaptor).balance, childHelper.totalGas()); + } } From b77a4821848935ab241013b6039c2895064ece46 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 10:09:47 +1000 Subject: [PATCH 19/33] Add helper methods --- test/invariant/child/ChildHelper.sol | 59 ++++++++++++++++++++++++ test/invariant/root/RootHelper.sol | 69 ++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/test/invariant/child/ChildHelper.sol b/test/invariant/child/ChildHelper.sol index bbca00fe..c025a8f4 100644 --- a/test/invariant/child/ChildHelper.sol +++ b/test/invariant/child/ChildHelper.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.19; import {Test} from "forge-std/Test.sol"; import {ChildERC20} from "../../../src/child/ChildERC20.sol"; +import {WIMX} from "../../../src/child/WIMX.sol"; import {ChildERC20Bridge} from "../../../src/child/ChildERC20Bridge.sol"; import {IChildERC20} from "../../../src/interfaces/child/IChildERC20.sol"; @@ -30,4 +31,62 @@ contract ChildHelper is Test { vm.prank(user); childBridge.withdrawTo{value: gasAmt}(IChildERC20(childToken), recipient, amount); } + + function withdrawIMX(address user, uint256 amount, uint256 gasAmt) public { + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + childBridge.withdrawIMX{value: gasAmt + amount}(amount); + } + + function withdrawIMXTo(address user, address recipient, uint256 amount, uint256 gasAmt) public { + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + childBridge.withdrawIMXTo{value: gasAmt + amount}(recipient, amount); + } + + function withdrawWIMX(address user, uint256 amount, uint256 gasAmt) public { + address payable wIMX = payable(childBridge.wIMXToken()); + + vm.prank(user); + WIMX(wIMX).approve(address(childBridge), amount); + + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + childBridge.withdrawWIMX{value: gasAmt}(amount); + } + + function withdrawWIMXTo(address user, address recipient, uint256 amount, uint256 gasAmt) public { + address payable wIMX = payable(childBridge.wIMXToken()); + + vm.prank(user); + WIMX(wIMX).approve(address(childBridge), amount); + + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + childBridge.withdrawWIMXTo{value: gasAmt}(recipient, amount); + } + + function withdrawETH(address user, uint256 amount, uint256 gasAmt) public { + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + childBridge.withdrawETH{value: gasAmt}(amount); + } + + function withdrawETHTo(address user, address recipient, uint256 amount, uint256 gasAmt) public { + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + childBridge.withdrawETHTo{value: gasAmt}(recipient, amount); + } } diff --git a/test/invariant/root/RootHelper.sol b/test/invariant/root/RootHelper.sol index 634a9e53..7190f065 100644 --- a/test/invariant/root/RootHelper.sol +++ b/test/invariant/root/RootHelper.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.19; import {Test} from "forge-std/Test.sol"; import {ChildERC20} from "../../../src/child/ChildERC20.sol"; +import {WIMX as WETH} from "../../../src/child/WIMX.sol"; import {RootERC20BridgeFlowRate} from "../../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; import {IERC20Metadata} from "../../../src/root/RootERC20Bridge.sol"; @@ -39,6 +40,74 @@ contract RootHelper is Test { rootBridge.depositTo{value: gasAmt}(IERC20Metadata(rootToken), recipient, amount); } + function depositIMX(address user, uint256 amount, uint256 gasAmt) public { + address IMX = rootBridge.rootIMXToken(); + + vm.prank(user); + ChildERC20(IMX).approve(address(rootBridge), amount); + + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + rootBridge.deposit{value: gasAmt}(IERC20Metadata(IMX), amount); + } + + function depositIMXTo(address user, address recipient, uint256 amount, uint256 gasAmt) public { + address IMX = rootBridge.rootIMXToken(); + + vm.prank(user); + ChildERC20(IMX).approve(address(rootBridge), amount); + + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + rootBridge.depositTo{value: gasAmt}(IERC20Metadata(IMX), recipient, amount); + } + + function depositETH(address user, uint256 amount, uint256 gasAmt) public { + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + rootBridge.depositETH{value: gasAmt + amount}(amount); + } + + function depositETHTo(address user, address recipient, uint256 amount, uint256 gasAmt) public { + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + rootBridge.depositToETH{value: gasAmt + amount}(recipient, amount); + } + + function depositWETH(address user, uint256 amount, uint256 gasAmt) public { + address payable wETH = payable(rootBridge.rootWETHToken()); + + vm.prank(user); + WETH(wETH).approve(address(rootBridge), amount); + + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + rootBridge.deposit{value: gasAmt}(IERC20Metadata(wETH), amount); + } + + function depositWETHTo(address user, address recipient, uint256 amount, uint256 gasAmt) public { + address payable wETH = payable(rootBridge.rootWETHToken()); + + vm.prank(user); + WETH(wETH).approve(address(rootBridge), amount); + + vm.deal(user, gasAmt); + totalGas += gasAmt; + + vm.prank(user); + rootBridge.depositTo{value: gasAmt}(IERC20Metadata(wETH), recipient, amount); + } + function getQueueSize(address user) public view returns (uint256) { return rootBridge.getPendingWithdrawalsLength(user); } From dde7380d130bd65e78ad5e234076a1ee7feb5d43 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 11:07:42 +1000 Subject: [PATCH 20/33] Update --- .../child/ChildERC20BridgeHandler.sol | 38 ++++++++------ .../root/RootERC20BridgeFlowRateHandler.sol | 52 ++++++++++--------- 2 files changed, 49 insertions(+), 41 deletions(-) diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol index 49d4a918..8c5c57f6 100644 --- a/test/invariant/child/ChildERC20BridgeHandler.sol +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -68,10 +68,8 @@ contract ChildERC20BridgeHandler is Test { uint256 currentBalance = ChildERC20(childToken).balanceOf(user); if (currentBalance < amount) { - // Deposit difference - vm.selectFork(rootId); - rootHelper.deposit(user, rootToken, amount - currentBalance, 1); - vm.selectFork(childId); + // Fund difference + fund(userIndexSeed, rootToken, childToken, amount - currentBalance); } vm.selectFork(rootId); @@ -113,13 +111,8 @@ contract ChildERC20BridgeHandler is Test { uint256 currentBalance = ChildERC20(childToken).balanceOf(user); if (currentBalance < amount) { - // Deposit difference - vm.selectFork(rootId); - uint256 offset = bound(userIndexSeed, 0, users.length - 1); - uint256 diff = amount - currentBalance; - address from = findDepositFrom(offset, rootToken, diff); - rootHelper.depositTo(from, user, rootToken, diff, 1); - vm.selectFork(childId); + // Fund difference + fund(userIndexSeed, rootToken, childToken, amount - currentBalance); } vm.selectFork(rootId); @@ -135,17 +128,28 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(original); } - function findDepositFrom(uint256 offset, address rootToken, uint256 requiredAmt) - public - view - returns (address from) - { + function fund(uint256 userIndexSeed, address rootToken, address childToken, uint256 diff) public { + uint256 offset = bound(userIndexSeed, 0, users.length - 1); + address user = users[offset]; + address from = findFrom(offset, childToken, diff); + if (from != address(0)) { + vm.prank(from); + ChildERC20(childToken).transfer(user, diff); + } else { + vm.selectFork(rootId); + from = findFrom(offset, rootToken, diff); + rootHelper.depositTo(from, user, rootToken, diff, 1); + vm.selectFork(childId); + } + } + + function findFrom(uint256 offset, address token, uint256 requiredAmt) public view returns (address from) { for (uint256 i = 0; i < users.length; i++) { uint256 index = i + offset; if (index >= users.length) { index -= users.length; } - if (ChildERC20(rootToken).balanceOf(users[index]) >= requiredAmt) { + if (ChildERC20(token).balanceOf(users[index]) >= requiredAmt) { from = users[index]; break; } diff --git a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol index 5d77a510..bbf162ae 100644 --- a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol +++ b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol @@ -45,20 +45,15 @@ contract RootERC20BridgeFlowRateHandler is Test { amount = bound(amount, 1, MAX_AMOUNT); gasAmt = bound(gasAmt, 1, MAX_GAS); + // Get child token + address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); + // Get current balance uint256 currentBalance = ChildERC20(rootToken).balanceOf(user); if (currentBalance < amount) { - // Withdraw difference - // Get child token - address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); - uint256 previousLen = rootHelper.getQueueSize(user); - - vm.selectFork(childId); - childHelper.withdraw(user, childToken, amount - currentBalance, 1); - vm.selectFork(rootId); - - rootHelper.finaliseWithdrawal(user, previousLen); + // Fund difference + fund(userIndexSeed, rootToken, childToken, amount - currentBalance); } rootHelper.deposit(user, rootToken, amount, gasAmt); @@ -85,31 +80,40 @@ contract RootERC20BridgeFlowRateHandler is Test { amount = bound(amount, 1, MAX_AMOUNT); gasAmt = bound(gasAmt, 1, MAX_GAS); + // Get child token + address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); + // Get current balance uint256 currentBalance = ChildERC20(rootToken).balanceOf(user); if (currentBalance < amount) { - // Withdraw difference - // Get child token - address childToken = rootHelper.rootBridge().rootTokenToChildToken(rootToken); - uint256 previousLen = rootHelper.getQueueSize(user); + // Fund difference + fund(userIndexSeed, rootToken, childToken, amount - currentBalance); + } + rootHelper.depositTo(user, recipient, rootToken, amount, gasAmt); + + vm.selectFork(original); + } + + function fund(uint256 userIndexSeed, address rootToken, address childToken, uint256 diff) public { + uint256 offset = bound(userIndexSeed, 0, users.length - 1); + address user = users[offset]; + address from = findFrom(offset, rootToken, diff); + if (from != address(0)) { + vm.prank(from); + ChildERC20(rootToken).transfer(user, diff); + } else { + uint256 previousLen = rootHelper.getQueueSize(user); vm.selectFork(childId); - uint256 offset = bound(userIndexSeed, 0, users.length - 1); - uint256 diff = amount - currentBalance; - address from = findWithdrawFrom(offset, childToken, diff); + from = findFrom(offset, childToken, diff); childHelper.withdrawTo(from, user, childToken, diff, 1); vm.selectFork(rootId); - rootHelper.finaliseWithdrawal(user, previousLen); } - - rootHelper.depositTo(user, recipient, rootToken, amount, gasAmt); - - vm.selectFork(original); } - function findWithdrawFrom(uint256 offset, address childToken, uint256 requiredAmt) + function findFrom(uint256 offset, address token, uint256 requiredAmt) public view returns (address from) @@ -119,7 +123,7 @@ contract RootERC20BridgeFlowRateHandler is Test { if (index >= users.length) { index -= users.length; } - if (ChildERC20(childToken).balanceOf(users[index]) >= requiredAmt) { + if (ChildERC20(token).balanceOf(users[index]) >= requiredAmt) { from = users[index]; break; } From a5c4fb569036e8f152c4ad6955fd9cb65b963876 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 12:02:00 +1000 Subject: [PATCH 21/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index db4f9da3..39d8d496 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -39,6 +39,8 @@ contract InvariantBridge is Test { ChildERC20BridgeHandler childBridgeHandler; RootERC20BridgeFlowRateHandler rootBridgeHandler; + uint256 mappingGas; + function setUp() public { childId = vm.createFork(CHILD_CHAIN_URL); rootId = vm.createFork(ROOT_CHAIN_URL); @@ -176,6 +178,7 @@ contract InvariantBridge is Test { for (uint256 i = 0; i < NO_OF_TOKENS; i++) { address rootToken = rootTokens[i]; rootBridge.mapToken{value: 1}(IERC20Metadata(rootToken)); + mappingGas += 1; // Verify address childTokenL1 = rootBridge.rootTokenToChildToken(address(rootToken)); @@ -226,13 +229,17 @@ contract InvariantBridge is Test { assertEq(bridgeBalance, totalSupply); assertEq(bridgeBalance, userBalanceSum); } + vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 /// forge-config: default.invariant.depth = 15 /// forge-config: default.invariant.fail-on-revert = true function invariant_GasBalanced() external { - assertEq(address(rootAdaptor).balance, rootHelper.totalGas()); + vm.selectFork(rootId); + assertEq(address(rootAdaptor).balance - mappingGas, rootHelper.totalGas()); + vm.selectFork(childId); assertEq(address(childAdaptor).balance, childHelper.totalGas()); + vm.selectFork(resetId); } } From 1784c9180e022fcc901240b91939dd2d4fe80fbc Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 12:04:52 +1000 Subject: [PATCH 22/33] Update RootERC20BridgeFlowRateHandler.sol --- test/invariant/root/RootERC20BridgeFlowRateHandler.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol index bbf162ae..60bc7f39 100644 --- a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol +++ b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol @@ -113,11 +113,7 @@ contract RootERC20BridgeFlowRateHandler is Test { } } - function findFrom(uint256 offset, address token, uint256 requiredAmt) - public - view - returns (address from) - { + function findFrom(uint256 offset, address token, uint256 requiredAmt) public view returns (address from) { for (uint256 i = 0; i < users.length; i++) { uint256 index = i + offset; if (index >= users.length) { From 5a47e0f77ebe032f5cc6b23cb8e88b4870f7636f Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 12:27:04 +1000 Subject: [PATCH 23/33] Simplify testing scenario --- test/invariant/InvariantBridge.t.sol | 21 +++++++++ .../child/ChildERC20BridgeHandler.sol | 41 +++++------------ .../root/RootERC20BridgeFlowRateHandler.sol | 44 +++++++------------ 3 files changed, 47 insertions(+), 59 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 39d8d496..3b43463d 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -232,6 +232,27 @@ contract InvariantBridge is Test { vm.selectFork(resetId); } + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_IndividualERC20TokenBalanced() external { + for (uint256 i = 0; i < NO_OF_TOKENS; i++) { + address rootToken = rootTokens[i]; + for (uint256 j = 0; j < NO_OF_USERS; j++) { + address user = users[j]; + + vm.selectFork(rootId); + uint256 balanceL1 = ChildERC20(rootToken).balanceOf(user); + address childToken = rootBridge.rootTokenToChildToken(rootToken); + + vm.selectFork(childId); + uint256 balanceL2 = ChildERC20(childToken).balanceOf(user); + + assertEq(balanceL1 + balanceL2, MAX_AMOUNT); + } + } + } + /// forge-config: default.invariant.runs = 256 /// forge-config: default.invariant.depth = 15 /// forge-config: default.invariant.fail-on-revert = true diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol index 8c5c57f6..859e99ad 100644 --- a/test/invariant/child/ChildERC20BridgeHandler.sol +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -69,7 +69,9 @@ contract ChildERC20BridgeHandler is Test { if (currentBalance < amount) { // Fund difference - fund(userIndexSeed, rootToken, childToken, amount - currentBalance); + vm.selectFork(rootId); + rootHelper.deposit(user, rootToken, amount - currentBalance, gasAmt); + vm.selectFork(childId); } vm.selectFork(rootId); @@ -112,7 +114,9 @@ contract ChildERC20BridgeHandler is Test { if (currentBalance < amount) { // Fund difference - fund(userIndexSeed, rootToken, childToken, amount - currentBalance); + vm.selectFork(rootId); + rootHelper.deposit(user, rootToken, amount - currentBalance, gasAmt); + vm.selectFork(childId); } vm.selectFork(rootId); @@ -123,36 +127,13 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(rootId); rootHelper.finaliseWithdrawal(recipient, previousLen); + // If recipient is different, transfer back + if (user != recipient) { + vm.prank(recipient); + ChildERC20(rootToken).transfer(user, amount); + } vm.selectFork(childId); vm.selectFork(original); } - - function fund(uint256 userIndexSeed, address rootToken, address childToken, uint256 diff) public { - uint256 offset = bound(userIndexSeed, 0, users.length - 1); - address user = users[offset]; - address from = findFrom(offset, childToken, diff); - if (from != address(0)) { - vm.prank(from); - ChildERC20(childToken).transfer(user, diff); - } else { - vm.selectFork(rootId); - from = findFrom(offset, rootToken, diff); - rootHelper.depositTo(from, user, rootToken, diff, 1); - vm.selectFork(childId); - } - } - - function findFrom(uint256 offset, address token, uint256 requiredAmt) public view returns (address from) { - for (uint256 i = 0; i < users.length; i++) { - uint256 index = i + offset; - if (index >= users.length) { - index -= users.length; - } - if (ChildERC20(token).balanceOf(users[index]) >= requiredAmt) { - from = users[index]; - break; - } - } - } } diff --git a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol index 60bc7f39..65e30bca 100644 --- a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol +++ b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol @@ -53,7 +53,11 @@ contract RootERC20BridgeFlowRateHandler is Test { if (currentBalance < amount) { // Fund difference - fund(userIndexSeed, rootToken, childToken, amount - currentBalance); + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + childHelper.withdraw(user, childToken, amount - currentBalance, gasAmt); + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); } rootHelper.deposit(user, rootToken, amount, gasAmt); @@ -88,41 +92,23 @@ contract RootERC20BridgeFlowRateHandler is Test { if (currentBalance < amount) { // Fund difference - fund(userIndexSeed, rootToken, childToken, amount - currentBalance); + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + childHelper.withdraw(user, childToken, amount - currentBalance, gasAmt); + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); } rootHelper.depositTo(user, recipient, rootToken, amount, gasAmt); - vm.selectFork(original); - } - - function fund(uint256 userIndexSeed, address rootToken, address childToken, uint256 diff) public { - uint256 offset = bound(userIndexSeed, 0, users.length - 1); - address user = users[offset]; - address from = findFrom(offset, rootToken, diff); - if (from != address(0)) { - vm.prank(from); - ChildERC20(rootToken).transfer(user, diff); - } else { - uint256 previousLen = rootHelper.getQueueSize(user); + // If recipient is different, transfer back + if (user != recipient) { vm.selectFork(childId); - from = findFrom(offset, childToken, diff); - childHelper.withdrawTo(from, user, childToken, diff, 1); + vm.prank(recipient); + ChildERC20(childToken).transfer(user, amount); vm.selectFork(rootId); - rootHelper.finaliseWithdrawal(user, previousLen); } - } - function findFrom(uint256 offset, address token, uint256 requiredAmt) public view returns (address from) { - for (uint256 i = 0; i < users.length; i++) { - uint256 index = i + offset; - if (index >= users.length) { - index -= users.length; - } - if (ChildERC20(token).balanceOf(users[index]) >= requiredAmt) { - from = users[index]; - break; - } - } + vm.selectFork(original); } } From 96a9009eaa2cd82abd955e1da31e83bf89e452b5 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 12:40:36 +1000 Subject: [PATCH 24/33] Update --- test/invariant/InvariantBridge.t.sol | 60 ++++++++++++------- .../child/ChildERC20BridgeHandler.sol | 34 +++++++++++ test/invariant/child/ChildHelper.sol | 16 ++--- test/invariant/root/RootHelper.sol | 16 ++--- 4 files changed, 90 insertions(+), 36 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 3b43463d..9cae968d 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -190,9 +190,10 @@ contract InvariantBridge is Test { } // Target contracts - bytes4[] memory childSelectors = new bytes4[](2); + bytes4[] memory childSelectors = new bytes4[](3); childSelectors[0] = childBridgeHandler.withdraw.selector; childSelectors[1] = childBridgeHandler.withdrawTo.selector; + childSelectors[2] = childBridgeHandler.withdrawIMX.selector; targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors})); bytes4[] memory rootSelectors = new bytes4[](2); @@ -232,25 +233,25 @@ contract InvariantBridge is Test { vm.selectFork(resetId); } - /// forge-config: default.invariant.runs = 256 - /// forge-config: default.invariant.depth = 15 - /// forge-config: default.invariant.fail-on-revert = true - function invariant_IndividualERC20TokenBalanced() external { - for (uint256 i = 0; i < NO_OF_TOKENS; i++) { - address rootToken = rootTokens[i]; - for (uint256 j = 0; j < NO_OF_USERS; j++) { - address user = users[j]; - - vm.selectFork(rootId); - uint256 balanceL1 = ChildERC20(rootToken).balanceOf(user); - address childToken = rootBridge.rootTokenToChildToken(rootToken); - - vm.selectFork(childId); - uint256 balanceL2 = ChildERC20(childToken).balanceOf(user); - - assertEq(balanceL1 + balanceL2, MAX_AMOUNT); - } - } + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_IndividualERC20TokenBalanced() external { + for (uint256 i = 0; i < NO_OF_TOKENS; i++) { + address rootToken = rootTokens[i]; + for (uint256 j = 0; j < NO_OF_USERS; j++) { + address user = users[j]; + + vm.selectFork(rootId); + uint256 balanceL1 = ChildERC20(rootToken).balanceOf(user); + address childToken = rootBridge.rootTokenToChildToken(rootToken); + + vm.selectFork(childId); + uint256 balanceL2 = ChildERC20(childToken).balanceOf(user); + + assertEq(balanceL1 + balanceL2, MAX_AMOUNT); + } + } } /// forge-config: default.invariant.runs = 256 @@ -263,4 +264,23 @@ contract InvariantBridge is Test { assertEq(address(childAdaptor).balance, childHelper.totalGas()); vm.selectFork(resetId); } + + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_IMXBalanced() external { + vm.selectFork(rootId); + uint256 bridgeBalance = ChildERC20(rootBridge.rootIMXToken()).balanceOf(address(rootBridge)); + + vm.selectFork(childId); + uint256 userBalanceSum = 0; + for (uint256 j = 0; j < NO_OF_USERS; j++) { + address user = users[j]; + userBalanceSum += user.balance; + } + + assertEq(bridgeBalance, userBalanceSum); + + vm.selectFork(resetId); + } } diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol index 859e99ad..7b5e92c6 100644 --- a/test/invariant/child/ChildERC20BridgeHandler.sol +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -136,4 +136,38 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(original); } + + function withdrawIMX(uint256 userIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to child chain + vm.selectFork(childId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = user.balance; + + if (currentBalance < amount) { + // Fund difference + vm.selectFork(rootId); + rootHelper.depositIMX(user, amount - currentBalance, gasAmt); + vm.selectFork(childId); + } + + vm.selectFork(rootId); + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + + childHelper.withdrawIMX(user, amount, gasAmt); + + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + vm.selectFork(childId); + + vm.selectFork(original); + } } diff --git a/test/invariant/child/ChildHelper.sol b/test/invariant/child/ChildHelper.sol index c025a8f4..ac1a8d3e 100644 --- a/test/invariant/child/ChildHelper.sol +++ b/test/invariant/child/ChildHelper.sol @@ -17,7 +17,7 @@ contract ChildHelper is Test { } function withdraw(address user, address childToken, uint256 amount, uint256 gasAmt) public { - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -25,7 +25,7 @@ contract ChildHelper is Test { } function withdrawTo(address user, address recipient, address childToken, uint256 amount, uint256 gasAmt) public { - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -33,7 +33,7 @@ contract ChildHelper is Test { } function withdrawIMX(address user, uint256 amount, uint256 gasAmt) public { - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -41,7 +41,7 @@ contract ChildHelper is Test { } function withdrawIMXTo(address user, address recipient, uint256 amount, uint256 gasAmt) public { - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -54,7 +54,7 @@ contract ChildHelper is Test { vm.prank(user); WIMX(wIMX).approve(address(childBridge), amount); - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -67,7 +67,7 @@ contract ChildHelper is Test { vm.prank(user); WIMX(wIMX).approve(address(childBridge), amount); - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -75,7 +75,7 @@ contract ChildHelper is Test { } function withdrawETH(address user, uint256 amount, uint256 gasAmt) public { - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -83,7 +83,7 @@ contract ChildHelper is Test { } function withdrawETHTo(address user, address recipient, uint256 amount, uint256 gasAmt) public { - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); diff --git a/test/invariant/root/RootHelper.sol b/test/invariant/root/RootHelper.sol index 7190f065..6f0521c7 100644 --- a/test/invariant/root/RootHelper.sol +++ b/test/invariant/root/RootHelper.sol @@ -22,7 +22,7 @@ contract RootHelper is Test { vm.prank(user); ChildERC20(rootToken).approve(address(rootBridge), amount); - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -33,7 +33,7 @@ contract RootHelper is Test { vm.prank(user); ChildERC20(rootToken).approve(address(rootBridge), amount); - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -46,7 +46,7 @@ contract RootHelper is Test { vm.prank(user); ChildERC20(IMX).approve(address(rootBridge), amount); - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -59,7 +59,7 @@ contract RootHelper is Test { vm.prank(user); ChildERC20(IMX).approve(address(rootBridge), amount); - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -67,7 +67,7 @@ contract RootHelper is Test { } function depositETH(address user, uint256 amount, uint256 gasAmt) public { - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -75,7 +75,7 @@ contract RootHelper is Test { } function depositETHTo(address user, address recipient, uint256 amount, uint256 gasAmt) public { - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -88,7 +88,7 @@ contract RootHelper is Test { vm.prank(user); WETH(wETH).approve(address(rootBridge), amount); - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); @@ -101,7 +101,7 @@ contract RootHelper is Test { vm.prank(user); WETH(wETH).approve(address(rootBridge), amount); - vm.deal(user, gasAmt); + vm.deal(user, gasAmt + user.balance); totalGas += gasAmt; vm.prank(user); From cc7342fce98a009c77160db948b744fd45fbd32e Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 12:52:44 +1000 Subject: [PATCH 25/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 9cae968d..7a1bfdd6 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -252,6 +252,7 @@ contract InvariantBridge is Test { assertEq(balanceL1 + balanceL2, MAX_AMOUNT); } } + vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 @@ -273,14 +274,73 @@ contract InvariantBridge is Test { uint256 bridgeBalance = ChildERC20(rootBridge.rootIMXToken()).balanceOf(address(rootBridge)); vm.selectFork(childId); + uint256 totalSupply = IMX_DEPOSIT_LIMIT - address(childBridge).balance; + uint256 userBalanceSum = 0; for (uint256 j = 0; j < NO_OF_USERS; j++) { address user = users[j]; userBalanceSum += user.balance; } + assertEq(bridgeBalance, totalSupply); assertEq(bridgeBalance, userBalanceSum); + vm.selectFork(resetId); + } + + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_IndividualIMXBalanced() external { + for (uint256 j = 0; j < NO_OF_USERS; j++) { + address user = users[j]; + + vm.selectFork(rootId); + uint256 balanceL1 = ChildERC20(rootBridge.rootIMXToken()).balanceOf(user); + + vm.selectFork(childId); + uint256 balanceL2 = user.balance; + + assertEq(balanceL1 + balanceL2, MAX_AMOUNT); + } + vm.selectFork(resetId); + } + + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_ETHBalanced() external { + vm.selectFork(rootId); + uint256 bridgeBalance = address(rootBridge).balance; + vm.selectFork(childId); + uint256 totalSupply = ChildERC20(childBridge.childETHToken()).totalSupply(); + + uint256 userBalanceSum = 0; + for (uint256 j = 0; j < NO_OF_USERS; j++) { + address user = users[j]; + userBalanceSum += ChildERC20(childBridge.childETHToken()).balanceOf(user); + } + + assertEq(bridgeBalance, totalSupply); + assertEq(bridgeBalance, userBalanceSum); + vm.selectFork(resetId); + } + + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_IndividualETHBalanced() external { + for (uint256 j = 0; j < NO_OF_USERS; j++) { + address user = users[j]; + + vm.selectFork(rootId); + uint256 balanceL1 = user.balance; + + vm.selectFork(childId); + uint256 balanceL2 = ChildERC20(childBridge.childETHToken()).balanceOf(user); + + assertEq(balanceL1 + balanceL2, MAX_AMOUNT); + } vm.selectFork(resetId); } } From 2348071d8a9364f34b0eea4ffadf2245598363f9 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 12:59:02 +1000 Subject: [PATCH 26/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 7a1bfdd6..11d1019e 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -343,4 +343,22 @@ contract InvariantBridge is Test { } vm.selectFork(resetId); } + + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_NoRemainingWETH() external { + vm.selectFork(rootId); + assertEq(rootBridge.rootWETHToken().balance, 0); + vm.selectFork(resetId); + } + + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_NoRemainingWIMX() external { + vm.selectFork(childId); + assertEq(childBridge.wIMXToken().balance, 0); + vm.selectFork(resetId); + } } From 83e4f854a4bcbee8cfea11a72bd0aff0d480d3c5 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 14:04:50 +1000 Subject: [PATCH 27/33] Add tests --- test/invariant/InvariantBridge.t.sol | 29 +-- .../child/ChildERC20BridgeHandler.sol | 201 ++++++++++++++++++ 2 files changed, 218 insertions(+), 12 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 11d1019e..9b5ab6cd 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -190,10 +190,15 @@ contract InvariantBridge is Test { } // Target contracts - bytes4[] memory childSelectors = new bytes4[](3); + bytes4[] memory childSelectors = new bytes4[](8); childSelectors[0] = childBridgeHandler.withdraw.selector; childSelectors[1] = childBridgeHandler.withdrawTo.selector; childSelectors[2] = childBridgeHandler.withdrawIMX.selector; + childSelectors[3] = childBridgeHandler.withdrawIMXTo.selector; + childSelectors[4] = childBridgeHandler.withdrawWIMX.selector; + childSelectors[5] = childBridgeHandler.withdrawWIMXTo.selector; + childSelectors[6] = childBridgeHandler.withdrawETH.selector; + childSelectors[7] = childBridgeHandler.withdrawETHTo.selector; targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors})); bytes4[] memory rootSelectors = new bytes4[](2); @@ -255,17 +260,6 @@ contract InvariantBridge is Test { vm.selectFork(resetId); } - /// forge-config: default.invariant.runs = 256 - /// forge-config: default.invariant.depth = 15 - /// forge-config: default.invariant.fail-on-revert = true - function invariant_GasBalanced() external { - vm.selectFork(rootId); - assertEq(address(rootAdaptor).balance - mappingGas, rootHelper.totalGas()); - vm.selectFork(childId); - assertEq(address(childAdaptor).balance, childHelper.totalGas()); - vm.selectFork(resetId); - } - /// forge-config: default.invariant.runs = 256 /// forge-config: default.invariant.depth = 15 /// forge-config: default.invariant.fail-on-revert = true @@ -361,4 +355,15 @@ contract InvariantBridge is Test { assertEq(childBridge.wIMXToken().balance, 0); vm.selectFork(resetId); } + + /// forge-config: default.invariant.runs = 256 + /// forge-config: default.invariant.depth = 15 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_GasBalanced() external { + vm.selectFork(rootId); + assertEq(address(rootAdaptor).balance - mappingGas, rootHelper.totalGas()); + vm.selectFork(childId); + assertEq(address(childAdaptor).balance, childHelper.totalGas()); + vm.selectFork(resetId); + } } diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol index 7b5e92c6..72980c30 100644 --- a/test/invariant/child/ChildERC20BridgeHandler.sol +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -5,6 +5,7 @@ import {Test} from "forge-std/Test.sol"; import {ChildERC20} from "../../../src/child/ChildERC20.sol"; import {ChildHelper} from "./ChildHelper.sol"; import {RootHelper} from "../root/RootHelper.sol"; +import {WIMX} from "../../../src/child/WIMX.sol"; contract ChildERC20BridgeHandler is Test { uint256 public constant MAX_AMOUNT = 10000; @@ -170,4 +171,204 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(original); } + + function withdrawIMXTo(uint256 userIndexSeed, uint256 recipientIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to child chain + vm.selectFork(childId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address recipient = users[bound(recipientIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = user.balance; + + if (currentBalance < amount) { + // Fund difference + vm.selectFork(rootId); + rootHelper.depositIMX(user, amount - currentBalance, gasAmt); + vm.selectFork(childId); + } + + vm.selectFork(rootId); + uint256 previousLen = rootHelper.getQueueSize(recipient); + vm.selectFork(childId); + + childHelper.withdrawIMXTo(user, recipient, amount, gasAmt); + + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(recipient, previousLen); + // If recipient is different, transfer back + if (user != recipient) { + address imx = rootHelper.rootBridge().rootIMXToken(); + vm.prank(recipient); + ChildERC20(imx).transfer(user, amount); + } + vm.selectFork(childId); + + vm.selectFork(original); + } + + function withdrawWIMX(uint256 userIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to child chain + vm.selectFork(childId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = user.balance; + + if (currentBalance < amount) { + // Fund difference + vm.selectFork(rootId); + rootHelper.depositIMX(user, amount - currentBalance, gasAmt); + vm.selectFork(childId); + } + + vm.selectFork(rootId); + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + + // Wrap IMX + address payable wIMX = payable(childHelper.childBridge().wIMXToken()); + vm.prank(user); + WIMX(wIMX).deposit{value: amount}(); + + childHelper.withdrawWIMX(user, amount, gasAmt); + + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + vm.selectFork(childId); + + vm.selectFork(original); + } + + function withdrawWIMXTo(uint256 userIndexSeed, uint256 recipientIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to child chain + vm.selectFork(childId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address recipient = users[bound(recipientIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = user.balance; + + if (currentBalance < amount) { + // Fund difference + vm.selectFork(rootId); + rootHelper.depositIMX(user, amount - currentBalance, gasAmt); + vm.selectFork(childId); + } + + vm.selectFork(rootId); + uint256 previousLen = rootHelper.getQueueSize(recipient); + vm.selectFork(childId); + + // Wrap IMX + address payable wIMX = payable(childHelper.childBridge().wIMXToken()); + vm.prank(user); + WIMX(wIMX).deposit{value: amount}(); + + childHelper.withdrawWIMXTo(user, recipient, amount, gasAmt); + + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(recipient, previousLen); + // If recipient is different, transfer back + if (user != recipient) { + address imx = rootHelper.rootBridge().rootIMXToken(); + vm.prank(recipient); + ChildERC20(imx).transfer(user, amount); + } + vm.selectFork(childId); + + vm.selectFork(original); + } + + function withdrawETH(uint256 userIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to child chain + vm.selectFork(childId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = ChildERC20(childHelper.childBridge().childETHToken()).balanceOf(user); + + if (currentBalance < amount) { + // Fund difference + vm.selectFork(rootId); + rootHelper.depositETH(user, amount - currentBalance, gasAmt); + vm.selectFork(childId); + } + + vm.selectFork(rootId); + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + + childHelper.withdrawETH(user, amount, gasAmt); + + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + vm.selectFork(childId); + + vm.selectFork(original); + } + + function withdrawETHTo(uint256 userIndexSeed, uint256 recipientIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to child chain + vm.selectFork(childId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address recipient = users[bound(recipientIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = ChildERC20(childHelper.childBridge().childETHToken()).balanceOf(user); + + if (currentBalance < amount) { + // Fund difference + vm.selectFork(rootId); + rootHelper.depositETH(user, amount - currentBalance, gasAmt); + vm.selectFork(childId); + } + + vm.selectFork(rootId); + uint256 previousLen = rootHelper.getQueueSize(recipient); + vm.selectFork(childId); + + childHelper.withdrawETHTo(user, recipient, amount, gasAmt); + + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(recipient, previousLen); + // If recipient is different, transfer back + if (user != recipient) { + vm.prank(recipient); + user.call{value: amount}(""); + } + vm.selectFork(childId); + + vm.selectFork(original); + } } From d30fdc7e8ef018805272159fe45a0fe0dc6118c7 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Tue, 27 Feb 2024 14:29:06 +1000 Subject: [PATCH 28/33] Add tests --- test/invariant/InvariantBridge.t.sol | 8 +- .../root/RootERC20BridgeFlowRateHandler.sol | 208 ++++++++++++++++++ 2 files changed, 215 insertions(+), 1 deletion(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 9b5ab6cd..ee52ffaa 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -201,9 +201,15 @@ contract InvariantBridge is Test { childSelectors[7] = childBridgeHandler.withdrawETHTo.selector; targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors})); - bytes4[] memory rootSelectors = new bytes4[](2); + bytes4[] memory rootSelectors = new bytes4[](8); rootSelectors[0] = rootBridgeHandler.deposit.selector; rootSelectors[1] = rootBridgeHandler.depositTo.selector; + rootSelectors[2] = rootBridgeHandler.depositIMX.selector; + rootSelectors[3] = rootBridgeHandler.depositIMXTo.selector; + rootSelectors[4] = rootBridgeHandler.depositETH.selector; + rootSelectors[5] = rootBridgeHandler.depositETHTo.selector; + rootSelectors[6] = rootBridgeHandler.depositWETH.selector; + rootSelectors[7] = rootBridgeHandler.depositWETHTo.selector; targetSelector(FuzzSelector({addr: address(rootBridgeHandler), selectors: rootSelectors})); targetContract(address(childBridgeHandler)); diff --git a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol index 65e30bca..a4334d6d 100644 --- a/test/invariant/root/RootERC20BridgeFlowRateHandler.sol +++ b/test/invariant/root/RootERC20BridgeFlowRateHandler.sol @@ -5,6 +5,7 @@ import {Test} from "forge-std/Test.sol"; import {ChildERC20} from "../../../src/child/ChildERC20.sol"; import {ChildHelper} from "../child/ChildHelper.sol"; import {RootHelper} from "./RootHelper.sol"; +import {WIMX as WETH} from "../../../src/child/WIMX.sol"; contract RootERC20BridgeFlowRateHandler is Test { uint256 public constant MAX_AMOUNT = 10000; @@ -111,4 +112,211 @@ contract RootERC20BridgeFlowRateHandler is Test { vm.selectFork(original); } + + function depositIMX(uint256 userIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to root chain + vm.selectFork(rootId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = ChildERC20(rootHelper.rootBridge().rootIMXToken()).balanceOf(user); + + if (currentBalance < amount) { + // Fund difference + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + childHelper.withdrawIMX(user, amount - currentBalance, gasAmt); + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + } + + rootHelper.depositIMX(user, amount, gasAmt); + + vm.selectFork(original); + } + + function depositIMXTo(uint256 userIndexSeed, uint256 recipientIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to root chain + vm.selectFork(rootId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address recipient = users[bound(recipientIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = ChildERC20(rootHelper.rootBridge().rootIMXToken()).balanceOf(user); + + if (currentBalance < amount) { + // Fund difference + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + childHelper.withdrawIMX(user, amount - currentBalance, gasAmt); + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + } + + rootHelper.depositIMXTo(user, recipient, amount, gasAmt); + + // If recipient is different, transfer back + if (user != recipient) { + vm.selectFork(childId); + vm.prank(recipient); + user.call{value: amount}(""); + vm.selectFork(rootId); + } + + vm.selectFork(original); + } + + function depositETH(uint256 userIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to root chain + vm.selectFork(rootId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = user.balance; + + if (currentBalance < amount) { + // Fund difference + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + childHelper.withdrawETH(user, amount - currentBalance, gasAmt); + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + } + + rootHelper.depositETH(user, amount, gasAmt); + + vm.selectFork(original); + } + + function depositETHTo(uint256 userIndexSeed, uint256 recipientIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to root chain + vm.selectFork(rootId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address recipient = users[bound(recipientIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = user.balance; + + if (currentBalance < amount) { + // Fund difference + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + childHelper.withdrawETH(user, amount - currentBalance, gasAmt); + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + } + + rootHelper.depositETHTo(user, recipient, amount, gasAmt); + + // If recipient is different, transfer back + if (user != recipient) { + vm.selectFork(childId); + address eth = childHelper.childBridge().childETHToken(); + vm.prank(recipient); + ChildERC20(eth).transfer(user, amount); + } + vm.selectFork(childId); + + vm.selectFork(original); + } + + function depositWETH(uint256 userIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to root chain + vm.selectFork(rootId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = user.balance; + + if (currentBalance < amount) { + // Fund difference + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + childHelper.withdrawETH(user, amount - currentBalance, gasAmt); + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + } + + // Wrap ETH + address payable wETH = payable(rootHelper.rootBridge().rootWETHToken()); + vm.prank(user); + WETH(wETH).deposit{value: amount}(); + + rootHelper.depositWETH(user, amount, gasAmt); + + vm.selectFork(original); + } + + function depositWETHTo(uint256 userIndexSeed, uint256 recipientIndexSeed, uint256 amount, uint256 gasAmt) public { + uint256 original = vm.activeFork(); + + // Switch to root chain + vm.selectFork(rootId); + + // Bound + address user = users[bound(userIndexSeed, 0, users.length - 1)]; + address recipient = users[bound(recipientIndexSeed, 0, users.length - 1)]; + amount = bound(amount, 1, MAX_AMOUNT); + gasAmt = bound(gasAmt, 1, MAX_GAS); + + // Get current balance + uint256 currentBalance = user.balance; + + if (currentBalance < amount) { + // Fund difference + uint256 previousLen = rootHelper.getQueueSize(user); + vm.selectFork(childId); + childHelper.withdrawETH(user, amount - currentBalance, gasAmt); + vm.selectFork(rootId); + rootHelper.finaliseWithdrawal(user, previousLen); + } + + // Wrap ETH + address payable wETH = payable(rootHelper.rootBridge().rootWETHToken()); + vm.prank(user); + WETH(wETH).deposit{value: amount}(); + + rootHelper.depositWETHTo(user, recipient, amount, gasAmt); + + // If recipient is different, transfer back + if (user != recipient) { + vm.selectFork(childId); + address eth = childHelper.childBridge().childETHToken(); + vm.prank(recipient); + ChildERC20(eth).transfer(user, amount); + vm.selectFork(rootId); + } + + vm.selectFork(original); + } } From 8a55ab8e9e17378ee04314815f94691abce8c5fa Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 28 Feb 2024 13:26:15 +1000 Subject: [PATCH 29/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index ee52ffaa..795a6258 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -17,6 +17,12 @@ import "forge-std/console.sol"; contract InvariantBridge is Test { string public constant CHILD_CHAIN_URL = "http://127.0.0.1:8500"; string public constant ROOT_CHAIN_URL = "http://127.0.0.1:8501"; + // Forge has an issue that fails to reset state at the end of each run. + // For example, we found out that if the context stays at child chain at the end of setUp(), + // the state on child chain will not be reset or if the context stays at root chain, the state + // on the root chain will not be reset, which causes subsequent runs to fail. + // We introduced a third chain called reset chain and we make the context to stay on the reset chain + // in order to reset state on both child chain and root chain. string public constant RESET_CHAIN_URL = "http://127.0.0.1:8502"; uint256 public constant IMX_DEPOSIT_LIMIT = 10000 ether; uint256 public constant MAX_AMOUNT = 10000; @@ -127,15 +133,14 @@ contract InvariantBridge is Test { } // Create tokens. for (uint256 i = 0; i < NO_OF_TOKENS; i++) { - vm.prank(address(0x234)); + vm.startPrank(address(0x234)); ChildERC20 rootToken = new ChildERC20(); - vm.prank(address(0x234)); rootToken.initialize(address(123), "Test", "TST", 18); // Mint token to user for (uint256 j = 0; j < NO_OF_USERS; j++) { - vm.prank(address(0x234)); rootToken.mint(users[j], MAX_AMOUNT); } + vm.stopPrank(); // Configure rate for half tokens if (i % 2 == 0) { vm.prank(ADMIN); @@ -250,12 +255,13 @@ contract InvariantBridge is Test { function invariant_IndividualERC20TokenBalanced() external { for (uint256 i = 0; i < NO_OF_TOKENS; i++) { address rootToken = rootTokens[i]; + vm.selectFork(rootId); + address childToken = rootBridge.rootTokenToChildToken(rootToken); for (uint256 j = 0; j < NO_OF_USERS; j++) { address user = users[j]; vm.selectFork(rootId); uint256 balanceL1 = ChildERC20(rootToken).balanceOf(user); - address childToken = rootBridge.rootTokenToChildToken(rootToken); vm.selectFork(childId); uint256 balanceL2 = ChildERC20(childToken).balanceOf(user); From ea7943f06e2b477372b6e5fa9d209103047d18b0 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Fri, 1 Mar 2024 14:49:15 +1000 Subject: [PATCH 30/33] Cleanup unnecessary code --- test/invariant/InvariantBridge.t.sol | 8 -------- test/invariant/child/ChildERC20BridgeHandler.sol | 8 -------- 2 files changed, 16 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 795a6258..6ded7493 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -246,7 +246,6 @@ contract InvariantBridge is Test { assertEq(bridgeBalance, totalSupply); assertEq(bridgeBalance, userBalanceSum); } - vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 @@ -269,7 +268,6 @@ contract InvariantBridge is Test { assertEq(balanceL1 + balanceL2, MAX_AMOUNT); } } - vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 @@ -290,7 +288,6 @@ contract InvariantBridge is Test { assertEq(bridgeBalance, totalSupply); assertEq(bridgeBalance, userBalanceSum); - vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 @@ -308,7 +305,6 @@ contract InvariantBridge is Test { assertEq(balanceL1 + balanceL2, MAX_AMOUNT); } - vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 @@ -329,7 +325,6 @@ contract InvariantBridge is Test { assertEq(bridgeBalance, totalSupply); assertEq(bridgeBalance, userBalanceSum); - vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 @@ -356,7 +351,6 @@ contract InvariantBridge is Test { function invariant_NoRemainingWETH() external { vm.selectFork(rootId); assertEq(rootBridge.rootWETHToken().balance, 0); - vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 @@ -365,7 +359,6 @@ contract InvariantBridge is Test { function invariant_NoRemainingWIMX() external { vm.selectFork(childId); assertEq(childBridge.wIMXToken().balance, 0); - vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 @@ -376,6 +369,5 @@ contract InvariantBridge is Test { assertEq(address(rootAdaptor).balance - mappingGas, rootHelper.totalGas()); vm.selectFork(childId); assertEq(address(childAdaptor).balance, childHelper.totalGas()); - vm.selectFork(resetId); } } diff --git a/test/invariant/child/ChildERC20BridgeHandler.sol b/test/invariant/child/ChildERC20BridgeHandler.sol index 72980c30..65f9d485 100644 --- a/test/invariant/child/ChildERC20BridgeHandler.sol +++ b/test/invariant/child/ChildERC20BridgeHandler.sol @@ -83,7 +83,6 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(rootId); rootHelper.finaliseWithdrawal(user, previousLen); - vm.selectFork(childId); vm.selectFork(original); } @@ -133,7 +132,6 @@ contract ChildERC20BridgeHandler is Test { vm.prank(recipient); ChildERC20(rootToken).transfer(user, amount); } - vm.selectFork(childId); vm.selectFork(original); } @@ -167,7 +165,6 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(rootId); rootHelper.finaliseWithdrawal(user, previousLen); - vm.selectFork(childId); vm.selectFork(original); } @@ -208,7 +205,6 @@ contract ChildERC20BridgeHandler is Test { vm.prank(recipient); ChildERC20(imx).transfer(user, amount); } - vm.selectFork(childId); vm.selectFork(original); } @@ -247,7 +243,6 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(rootId); rootHelper.finaliseWithdrawal(user, previousLen); - vm.selectFork(childId); vm.selectFork(original); } @@ -293,7 +288,6 @@ contract ChildERC20BridgeHandler is Test { vm.prank(recipient); ChildERC20(imx).transfer(user, amount); } - vm.selectFork(childId); vm.selectFork(original); } @@ -327,7 +321,6 @@ contract ChildERC20BridgeHandler is Test { vm.selectFork(rootId); rootHelper.finaliseWithdrawal(user, previousLen); - vm.selectFork(childId); vm.selectFork(original); } @@ -367,7 +360,6 @@ contract ChildERC20BridgeHandler is Test { vm.prank(recipient); user.call{value: amount}(""); } - vm.selectFork(childId); vm.selectFork(original); } From 23b96bf2856dd3a34f96694b71cba5e1229ad4c7 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 6 Mar 2024 13:06:40 +1000 Subject: [PATCH 31/33] Update InvariantBridge.t.sol --- test/invariant/InvariantBridge.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 6ded7493..1b31b684 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.19; import {Test} from "forge-std/Test.sol"; import {ChildERC20} from "../../src/child/ChildERC20.sol"; import {WIMX} from "../../src/child/WIMX.sol"; +import {WETH} from "../../src/lib/WETH.sol"; import {IChildERC20Bridge, ChildERC20Bridge} from "../../src/child/ChildERC20Bridge.sol"; import {IRootERC20Bridge, IERC20Metadata} from "../../src/root/RootERC20Bridge.sol"; import {RootERC20BridgeFlowRate} from "../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; @@ -74,7 +75,7 @@ contract InvariantBridge is Test { rootBridge = new RootERC20BridgeFlowRate(address(this)); ChildERC20 rootIMXToken = new ChildERC20(); rootIMXToken.initialize(address(123), "Immutable X", "IMX", 18); - WIMX wETH = new WIMX(); + WETH wETH = new WETH(); // Deploy contracts on reset chain. vm.selectFork(resetId); From 7071c46d5f8950f68cbe2bfcb6a6b1948d15a4ca Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 6 Mar 2024 14:01:28 +1000 Subject: [PATCH 32/33] Cleanup --- test/invariant/InvariantBridge.t.sol | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index 1b31b684..dbcaa09a 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -77,14 +77,6 @@ contract InvariantBridge is Test { rootIMXToken.initialize(address(123), "Immutable X", "IMX", 18); WETH wETH = new WETH(); - // Deploy contracts on reset chain. - vm.selectFork(resetId); - vm.startPrank(ADMIN); - ChildERC20 resetTokenTemplate = new ChildERC20(); - resetTokenTemplate.initialize(address(123), "Test", "TST", 18); - new MockAdaptor(); - vm.stopPrank(); - // Configure contracts on child chain. vm.selectFork(childId); childAdaptor.initialize(rootId, address(childBridge)); @@ -169,16 +161,6 @@ contract InvariantBridge is Test { ); vm.stopPrank(); - vm.selectFork(resetId); - vm.startPrank(ADMIN); - new ChildHelper(payable(childBridge)); - new RootHelper(ADMIN, payable(rootBridge)); - new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)); - new RootERC20BridgeFlowRateHandler( - childId, rootId, users, rootTokens, address(childHelper), address(rootHelper) - ); - vm.stopPrank(); - // Map tokens vm.selectFork(rootId); for (uint256 i = 0; i < NO_OF_TOKENS; i++) { @@ -343,7 +325,6 @@ contract InvariantBridge is Test { assertEq(balanceL1 + balanceL2, MAX_AMOUNT); } - vm.selectFork(resetId); } /// forge-config: default.invariant.runs = 256 From d731c783975bdbbf65a80922bfd60e8b5631dba1 Mon Sep 17 00:00:00 2001 From: wcgcyx Date: Wed, 6 Mar 2024 14:09:44 +1000 Subject: [PATCH 33/33] Cleanup --- .github/workflows/test.yml | 2 +- package.json | 2 +- scripts/localdev/chains.sh | 2 -- test/invariant/InvariantBridge.t.sol | 22 ++++++++++------------ 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dfefbb8f..c46f5c72 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,6 +72,6 @@ jobs: - name: Run Invariant Tests run: | - yarn local:threechains + yarn local:testchain forge test --match-path "test/invariant/**" -vvvvv id: invariant_test \ No newline at end of file diff --git a/package.json b/package.json index fb877879..10840708 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "local:ci": "cd scripts/localdev; rm -rf .child.bridge.contracts.json .root.bridge.contracts.json; ./ci.sh && ./deploy.sh && AXELAR_API_URL=skip npx mocha --require mocha-suppress-logs ../e2e/e2e.ts && ./stop.sh", "local:chainonly": "cd scripts/localdev; LOCAL_CHAIN_ONLY=true ./start.sh", "local:axelaronly": "cd scripts/localdev; npx ts-node axelar_setup.ts", - "local:threechains": "cd scripts/localdev; ./chains.sh", + "local:testchain": "cd scripts/localdev; ./chains.sh", "stop": "cd scripts/localdev; ./stop.sh" }, "author": "", diff --git a/scripts/localdev/chains.sh b/scripts/localdev/chains.sh index 31d3cb0b..d515fdd0 100755 --- a/scripts/localdev/chains.sh +++ b/scripts/localdev/chains.sh @@ -7,6 +7,4 @@ set -o pipefail # Start root & child chain. npx hardhat node --config ./rootchain.config.ts --port 8500 > /dev/null 2>&1 & -npx hardhat node --config ./childchain.config.ts --port 8501 > /dev/null 2>&1 & -npx hardhat node --config ./resetchain.config.ts --port 8502 > /dev/null 2>&1 & sleep 10 diff --git a/test/invariant/InvariantBridge.t.sol b/test/invariant/InvariantBridge.t.sol index dbcaa09a..12ec8695 100644 --- a/test/invariant/InvariantBridge.t.sol +++ b/test/invariant/InvariantBridge.t.sol @@ -16,15 +16,7 @@ import {RootERC20BridgeFlowRateHandler} from "./root/RootERC20BridgeFlowRateHand import "forge-std/console.sol"; contract InvariantBridge is Test { - string public constant CHILD_CHAIN_URL = "http://127.0.0.1:8500"; - string public constant ROOT_CHAIN_URL = "http://127.0.0.1:8501"; - // Forge has an issue that fails to reset state at the end of each run. - // For example, we found out that if the context stays at child chain at the end of setUp(), - // the state on child chain will not be reset or if the context stays at root chain, the state - // on the root chain will not be reset, which causes subsequent runs to fail. - // We introduced a third chain called reset chain and we make the context to stay on the reset chain - // in order to reset state on both child chain and root chain. - string public constant RESET_CHAIN_URL = "http://127.0.0.1:8502"; + string public constant CHAIN_URL = "http://127.0.0.1:8500"; uint256 public constant IMX_DEPOSIT_LIMIT = 10000 ether; uint256 public constant MAX_AMOUNT = 10000; address public constant ADMIN = address(0x111); @@ -49,9 +41,15 @@ contract InvariantBridge is Test { uint256 mappingGas; function setUp() public { - childId = vm.createFork(CHILD_CHAIN_URL); - rootId = vm.createFork(ROOT_CHAIN_URL); - resetId = vm.createFork(RESET_CHAIN_URL); + childId = vm.createFork(CHAIN_URL); + rootId = vm.createFork(CHAIN_URL); + // Forge has an issue that fails to reset state at the end of each run. + // For example, we found out that if the context stays at child chain at the end of setUp(), + // the state on child chain will not be reset or if the context stays at root chain, the state + // on the root chain will not be reset, which causes subsequent runs to fail. + // We introduced a third chain called reset chain and we make the context to stay on the reset chain + // in order to reset state on both child chain and root chain. + resetId = vm.createFork(CHAIN_URL); // Deploy contracts on child chain. vm.selectFork(childId);