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); + } }