From b7f329a2224a87d50403a0e2a8c49a08ef08400c Mon Sep 17 00:00:00 2001 From: Craig M Date: Tue, 23 Jan 2024 18:33:45 +1300 Subject: [PATCH 01/24] WIP --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/fork/root/RootERC20BridgeFlowRate.t.sol diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol new file mode 100644 index 00000000..7f25ce42 --- /dev/null +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +import {Test, console2} from "forge-std/Test.sol"; +import {RootERC20BridgeFlowRate} from "../../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; +import {Utils} from "../../utils.t.sol"; + +contract RootERC20BridgeFlowRateForkTest is Test, Utils { + uint256 mainnetFork; + address payable rootBridgeAddress = payable(0xBa5E35E26Ae59c7aea6F029B68c6460De2d13eB6); + RootERC20BridgeFlowRate public rootBridgeFlowRate; + string MAINNET_RPC_URL = vm.envString("FORK_MAINNET_RPC_URL"); + + function setUp() public { + mainnetFork = vm.createFork(MAINNET_RPC_URL); + rootBridgeFlowRate = RootERC20BridgeFlowRate(rootBridgeAddress); + } + + function test_getWithdrawalDelay() public { + uint256 withdrawDelay = rootBridgeFlowRate.withdrawalDelay(); + console2.log("withdrawDelay"); + console2.logUint(withdrawDelay); + assertEq(withdrawDelay, uint256(86400)); + } +} From f00f34bdf4e72108b52949a8b3d3bd93a75afef3 Mon Sep 17 00:00:00 2001 From: Craig M Date: Wed, 24 Jan 2024 10:34:46 +1300 Subject: [PATCH 02/24] basic fork test working --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index 7f25ce42..e8831b13 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -13,12 +13,12 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { function setUp() public { mainnetFork = vm.createFork(MAINNET_RPC_URL); + vm.selectFork(mainnetFork); rootBridgeFlowRate = RootERC20BridgeFlowRate(rootBridgeAddress); } function test_getWithdrawalDelay() public { uint256 withdrawDelay = rootBridgeFlowRate.withdrawalDelay(); - console2.log("withdrawDelay"); console2.logUint(withdrawDelay); assertEq(withdrawDelay, uint256(86400)); } From fb5df13bb72ed3adfc3a26a8b65834c91b2507ab Mon Sep 17 00:00:00 2001 From: Craig M Date: Wed, 24 Jan 2024 17:59:05 +1300 Subject: [PATCH 03/24] first tests passes --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 134 ++++++++++++++++++- 1 file changed, 129 insertions(+), 5 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index e8831b13..5baec1e1 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -3,23 +3,147 @@ pragma solidity 0.8.19; import {Test, console2} from "forge-std/Test.sol"; import {RootERC20BridgeFlowRate} from "../../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; +import {IFlowRateWithdrawalQueueErrors} from "../../../src/root/flowrate/FlowRateWithdrawalQueue.sol"; + import {Utils} from "../../utils.t.sol"; contract RootERC20BridgeFlowRateForkTest is Test, Utils { uint256 mainnetFork; - address payable rootBridgeAddress = payable(0xBa5E35E26Ae59c7aea6F029B68c6460De2d13eB6); RootERC20BridgeFlowRate public rootBridgeFlowRate; string MAINNET_RPC_URL = vm.envString("FORK_MAINNET_RPC_URL"); + address NATIVE_ETH = address(0x0000000000000000000000000000000000000Eee); + uint256 withdrawDelay; + + // move to .env + address payable rootBridgeAddress = payable(0xBa5E35E26Ae59c7aea6F029B68c6460De2d13eB6); + address rootAdapter = address(0x4f49B53928A71E553bB1B0F66a5BcB54Fd4E8932); + address receiver1 = address(0x111); + address receiver2 = address(0x222); + + + struct Bucket { + uint256 capacity; + uint256 depth; + uint256 refillTime; + uint256 refillRate; + } function setUp() public { mainnetFork = vm.createFork(MAINNET_RPC_URL); vm.selectFork(mainnetFork); rootBridgeFlowRate = RootERC20BridgeFlowRate(rootBridgeAddress); + withdrawDelay = rootBridgeFlowRate.withdrawalDelay(); + assertGt(withdrawDelay, 0); } - function test_getWithdrawalDelay() public { - uint256 withdrawDelay = rootBridgeFlowRate.withdrawalDelay(); - console2.logUint(withdrawDelay); - assertEq(withdrawDelay, uint256(86400)); + function test_flowRateETH() public { + uint256 largeThreshold = rootBridgeFlowRate.largeTransferThresholds(NATIVE_ETH); + (uint256 capacity, uint256 depth, uint256 refillTime, uint256 refillRate) = rootBridgeFlowRate.flowRateBuckets(NATIVE_ETH); + + // send 75% of the largeThreshold value + uint256 txValue = ((largeThreshold / 100) * 75); + + uint256 numTxs = (depth / txValue) + 2; + + uint256 numTxsReceiver1 = 0; + + //deal enough ETH to the bridge to cover all the txs + vm.deal(rootBridgeAddress, txValue * numTxs); + + while(depth > 0) { + //prank as axelar sending a message to the adapter + vm.startPrank(rootAdapter); + + bytes memory predictedPayload1 = + abi.encode(rootBridgeFlowRate.WITHDRAW_SIG(), NATIVE_ETH, receiver1, receiver1, txValue); + rootBridgeFlowRate.onMessageReceive(predictedPayload1); + vm.stopPrank(); + + (capacity, depth, refillTime, refillRate) = rootBridgeFlowRate.flowRateBuckets(NATIVE_ETH); + + bool queueActivated = rootBridgeFlowRate.withdrawalQueueActivated(); + + if (depth > 0) { + assertFalse(queueActivated); + } else { + assertTrue(queueActivated); + } + + numTxsReceiver1 += 1; + } + + //sanity check we dealt the enough eth + assertEq(numTxsReceiver1+1, numTxs); + + //send one more tx to receiver2 and make sure it gets queued + vm.startPrank(rootAdapter); + bytes memory predictedPayload2 = + abi.encode(rootBridgeFlowRate.WITHDRAW_SIG(), NATIVE_ETH, receiver2, receiver2, txValue); + rootBridgeFlowRate.onMessageReceive(predictedPayload2); + vm.stopPrank(); + + uint256 pendingLength1 = rootBridgeFlowRate.getPendingWithdrawalsLength(receiver1); + uint256 pendingLength2 = rootBridgeFlowRate.getPendingWithdrawalsLength(receiver2); + + //each receiver should have 1 queued tx + assertEq(pendingLength1, 1); + assertEq(pendingLength2, 1); + + uint256[] memory indices1 = new uint256[](1); + indices1[0] = 0; + + RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending1 = + rootBridgeFlowRate.getPendingWithdrawals(receiver1, indices1); + + assertEq(pending1.length, 1); + assertEq(pending1[0].withdrawer, receiver1); + assertEq(pending1[0].token, NATIVE_ETH); + assertEq(pending1[0].amount, txValue); + uint256 timestamp1 = pending1[0].timestamp; + + uint256 okTime1 = timestamp1 + withdrawDelay; + + //deal some eth to pay withdraw gas + vm.deal(address(this), 1 ether); + + //try to process withdraw 1 + vm.expectRevert( + abi.encodeWithSelector(IFlowRateWithdrawalQueueErrors.WithdrawalRequestTooEarly.selector, timestamp1, okTime1) + ); + rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver1, 0); + + uint256[] memory indices2 = new uint256[](1); + indices2[0] = 0; + + RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending2 = + rootBridgeFlowRate.getPendingWithdrawals(receiver2, indices2); + + assertEq(pending2.length, 1); + assertEq(pending2[0].withdrawer, receiver2); + assertEq(pending2[0].token, NATIVE_ETH); + assertEq(pending2[0].amount, txValue); + uint256 timestamp2 = pending2[0].timestamp; + + uint256 okTime2 = timestamp2 + withdrawDelay; + + //try to process withdraw 2 + vm.expectRevert( + abi.encodeWithSelector(IFlowRateWithdrawalQueueErrors.WithdrawalRequestTooEarly.selector, timestamp2, okTime2) + ); + rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver2, 0); + + vm.warp(okTime1+1); + + rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver1, 0); + + vm.warp(okTime2+1); + + rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver2, 0); + + console2.log('success'); + + //warp to time when withdraw can be processed + + //try to withdraw again } } From c6ab8293c55ffa22d88d5ad10fcbe42cd84defd0 Mon Sep 17 00:00:00 2001 From: Craig M Date: Tue, 30 Jan 2024 11:55:28 +1300 Subject: [PATCH 04/24] some refactoring --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 141 ++++++++----------- 1 file changed, 60 insertions(+), 81 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index 5baec1e1..d97f67c9 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -16,6 +16,7 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { // move to .env address payable rootBridgeAddress = payable(0xBa5E35E26Ae59c7aea6F029B68c6460De2d13eB6); + address IMX = address(0xF57e7e7C23978C3cAEC3C3548E3D615c346e79fF); address rootAdapter = address(0x4f49B53928A71E553bB1B0F66a5BcB54Fd4E8932); address receiver1 = address(0x111); address receiver2 = address(0x222); @@ -37,113 +38,91 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { } function test_flowRateETH() public { - uint256 largeThreshold = rootBridgeFlowRate.largeTransferThresholds(NATIVE_ETH); - (uint256 capacity, uint256 depth, uint256 refillTime, uint256 refillRate) = rootBridgeFlowRate.flowRateBuckets(NATIVE_ETH); + _flowRate(NATIVE_ETH); + } + + function _flowRate(address token) public { + uint256 largeThreshold = rootBridgeFlowRate.largeTransferThresholds(token); + + //only need depth returned + (, uint256 depth, ,) = rootBridgeFlowRate.flowRateBuckets(token); // send 75% of the largeThreshold value uint256 txValue = ((largeThreshold / 100) * 75); uint256 numTxs = (depth / txValue) + 2; - uint256 numTxsReceiver1 = 0; - - //deal enough ETH to the bridge to cover all the txs - vm.deal(rootBridgeAddress, txValue * numTxs); + if (token == NATIVE_ETH) { + //deal enough ETH to the bridge to cover all the txs + vm.deal(rootBridgeAddress, txValue * numTxs); + } else { + //@TODO ensure the bridge has enough tokens to cover all the txs + console2.log('not eth'); + } - while(depth > 0) { - //prank as axelar sending a message to the adapter - vm.startPrank(rootAdapter); + //withdraw until queue is activated + bool queueActivated = rootBridgeFlowRate.withdrawalQueueActivated(); + while(queueActivated == false) { + _sendWithdrawMessage(token, receiver1, receiver1, txValue); + queueActivated = rootBridgeFlowRate.withdrawalQueueActivated(); + } - bytes memory predictedPayload1 = - abi.encode(rootBridgeFlowRate.WITHDRAW_SIG(), NATIVE_ETH, receiver1, receiver1, txValue); - rootBridgeFlowRate.onMessageReceive(predictedPayload1); - vm.stopPrank(); + //send one more tx to receiver2 and make sure it gets queued + _sendWithdrawMessage(token, receiver2, receiver2, txValue); - (capacity, depth, refillTime, refillRate) = rootBridgeFlowRate.flowRateBuckets(NATIVE_ETH); + //attempt to withdraw for receiver 1 + uint256 okTime1 = _attemptEarlyWithdraw(token, receiver1, txValue); - bool queueActivated = rootBridgeFlowRate.withdrawalQueueActivated(); + //attempt to withdraw for receiver 2 + uint256 okTime2 = _attemptEarlyWithdraw(token, receiver2, txValue); - if (depth > 0) { - assertFalse(queueActivated); - } else { - assertTrue(queueActivated); - } + //fast forward past withdrawal delay time and withdraw for receiver 1 + vm.warp(okTime1+1); + rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver1, 0); - numTxsReceiver1 += 1; - } + //fast forward past withdrawal delay time and withdraw for receiver 2 + vm.warp(okTime2+1); + rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver2, 0); - //sanity check we dealt the enough eth - assertEq(numTxsReceiver1+1, numTxs); + } - //send one more tx to receiver2 and make sure it gets queued - vm.startPrank(rootAdapter); - bytes memory predictedPayload2 = - abi.encode(rootBridgeFlowRate.WITHDRAW_SIG(), NATIVE_ETH, receiver2, receiver2, txValue); - rootBridgeFlowRate.onMessageReceive(predictedPayload2); - vm.stopPrank(); + function _attemptEarlyWithdraw(address token, address receiver, uint256 txValue) + public returns (uint256 okTime){ - uint256 pendingLength1 = rootBridgeFlowRate.getPendingWithdrawalsLength(receiver1); - uint256 pendingLength2 = rootBridgeFlowRate.getPendingWithdrawalsLength(receiver2); + uint256 pendingLength = rootBridgeFlowRate.getPendingWithdrawalsLength(receiver); - //each receiver should have 1 queued tx - assertEq(pendingLength1, 1); - assertEq(pendingLength2, 1); + assertEq(pendingLength, 1); - uint256[] memory indices1 = new uint256[](1); - indices1[0] = 0; + uint256[] memory indices = new uint256[](1); + indices[0] = 0; - RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending1 = - rootBridgeFlowRate.getPendingWithdrawals(receiver1, indices1); + RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending = + rootBridgeFlowRate.getPendingWithdrawals(receiver, indices); - assertEq(pending1.length, 1); - assertEq(pending1[0].withdrawer, receiver1); - assertEq(pending1[0].token, NATIVE_ETH); - assertEq(pending1[0].amount, txValue); - uint256 timestamp1 = pending1[0].timestamp; + assertEq(pending.length, 1); + assertEq(pending[0].withdrawer, receiver); + assertEq(pending[0].token, token); + assertEq(pending[0].amount, txValue); + uint256 timestamp = pending[0].timestamp; - uint256 okTime1 = timestamp1 + withdrawDelay; + okTime = timestamp + withdrawDelay; //deal some eth to pay withdraw gas vm.deal(address(this), 1 ether); - //try to process withdraw 1 - vm.expectRevert( - abi.encodeWithSelector(IFlowRateWithdrawalQueueErrors.WithdrawalRequestTooEarly.selector, timestamp1, okTime1) - ); - rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver1, 0); - - uint256[] memory indices2 = new uint256[](1); - indices2[0] = 0; - - RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending2 = - rootBridgeFlowRate.getPendingWithdrawals(receiver2, indices2); - - assertEq(pending2.length, 1); - assertEq(pending2[0].withdrawer, receiver2); - assertEq(pending2[0].token, NATIVE_ETH); - assertEq(pending2[0].amount, txValue); - uint256 timestamp2 = pending2[0].timestamp; - - uint256 okTime2 = timestamp2 + withdrawDelay; - - //try to process withdraw 2 + //try to process the withdrawal vm.expectRevert( - abi.encodeWithSelector(IFlowRateWithdrawalQueueErrors.WithdrawalRequestTooEarly.selector, timestamp2, okTime2) + abi.encodeWithSelector(IFlowRateWithdrawalQueueErrors.WithdrawalRequestTooEarly.selector, timestamp, okTime) ); - rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver2, 0); - - vm.warp(okTime1+1); - - rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver1, 0); - - vm.warp(okTime2+1); - - rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver2, 0); - - console2.log('success'); - - //warp to time when withdraw can be processed + rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver, 0); + } - //try to withdraw again + function _sendWithdrawMessage(address token, address sender, address receiver, uint256 txValue) public { + //prank as axelar sending a message to the adapter + vm.startPrank(rootAdapter); + bytes memory predictedPayload = + abi.encode(rootBridgeFlowRate.WITHDRAW_SIG(), token, sender, receiver, txValue); + rootBridgeFlowRate.onMessageReceive(predictedPayload); + vm.stopPrank(); } } From 84c262761d082bbb74d4c493002bdd3aeab4dd69 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 31 Jan 2024 16:49:58 +1100 Subject: [PATCH 05/24] Implement flowrate fork test --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 266 +++++++++++++------ 1 file changed, 179 insertions(+), 87 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index d97f67c9..01cba67f 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -4,125 +4,217 @@ pragma solidity 0.8.19; import {Test, console2} from "forge-std/Test.sol"; import {RootERC20BridgeFlowRate} from "../../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; import {IFlowRateWithdrawalQueueErrors} from "../../../src/root/flowrate/FlowRateWithdrawalQueue.sol"; +import {console} from "forge-std/console.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {Utils} from "../../utils.t.sol"; contract RootERC20BridgeFlowRateForkTest is Test, Utils { - uint256 mainnetFork; - RootERC20BridgeFlowRate public rootBridgeFlowRate; - string MAINNET_RPC_URL = vm.envString("FORK_MAINNET_RPC_URL"); - address NATIVE_ETH = address(0x0000000000000000000000000000000000000Eee); - uint256 withdrawDelay; - - // move to .env - address payable rootBridgeAddress = payable(0xBa5E35E26Ae59c7aea6F029B68c6460De2d13eB6); - address IMX = address(0xF57e7e7C23978C3cAEC3C3548E3D615c346e79fF); - address rootAdapter = address(0x4f49B53928A71E553bB1B0F66a5BcB54Fd4E8932); - address receiver1 = address(0x111); - address receiver2 = address(0x222); - - - struct Bucket { - uint256 capacity; - uint256 depth; - uint256 refillTime; - uint256 refillRate; + address public constant NATIVE_ETH = address(0xeee); + + string[] private deployments = vm.envString("DEPLOYMENTS", ","); + mapping(string => string) private rpcURLForEnv; + mapping(string => uint256) private forkOfEnv; + mapping(string => RootERC20BridgeFlowRate) private bridgeForEnv; + mapping(string => address[]) private tokensForEnv; + + string private deployment; + modifier forEachDeployment() { + for (uint256 i; i < deployments.length; i++) { + deployment = deployments[i]; + vm.selectFork(forkOfEnv[deployment]); + _; + } } function setUp() public { - mainnetFork = vm.createFork(MAINNET_RPC_URL); - vm.selectFork(mainnetFork); - rootBridgeFlowRate = RootERC20BridgeFlowRate(rootBridgeAddress); - withdrawDelay = rootBridgeFlowRate.withdrawalDelay(); - assertGt(withdrawDelay, 0); + for (uint256 i; i < deployments.length; i++) { + string memory dep = deployments[i]; + rpcURLForEnv[dep] = vm.envString(string.concat(dep, "_RPC_URL")); + bridgeForEnv[dep] = RootERC20BridgeFlowRate(payable(vm.envAddress(string.concat(dep, "_BRIDGE_ADDRESS")))); + tokensForEnv[dep] = vm.envAddress(string.concat(dep, "_FLOW_RATED_TOKENS"), ","); + forkOfEnv[dep] = vm.createFork(rpcURLForEnv[dep]); + } } - function test_flowRateETH() public { - _flowRate(NATIVE_ETH); + function test_withdrawalQueueEnforcedWhenFlowRateExceeded() public forEachDeployment { + console.log("Testing deployment: ", deployment); + vm.selectFork(forkOfEnv[deployment]); + _verifyWithdrawalQueueEnforcedForAllTokens(bridgeForEnv[deployment], tokensForEnv[deployment]); } - function _flowRate(address token) public { - uint256 largeThreshold = rootBridgeFlowRate.largeTransferThresholds(token); + function test_nonFlowRatedTokenIsQueued() public forEachDeployment { + vm.selectFork(forkOfEnv[deployment]); + RootERC20BridgeFlowRate bridge = bridgeForEnv[deployment]; - //only need depth returned - (, uint256 depth, ,) = rootBridgeFlowRate.flowRateBuckets(token); + // preconditions + assertFalse(bridge.withdrawalQueueActivated()); - // send 75% of the largeThreshold value - uint256 txValue = ((largeThreshold / 100) * 75); + // deploy and map a token + ERC20 erc20 = new ERC20("Test Token", "TEST"); + bridge.mapToken{value: 100 gwei}(erc20); + _giveBridgeFunds(address(erc20), address(erc20), 1 ether); - uint256 numTxs = (depth / txValue) + 2; + // ensure withdrawals for the token, which does not have flow rate configured, is queued + _sendWithdrawalMessage(bridge, address(erc20), address(1), 1 ether); + _verifyWithdrawalWasQueued(bridge, address(erc20), address(1), 1 ether); - if (token == NATIVE_ETH) { - //deal enough ETH to the bridge to cover all the txs - vm.deal(rootBridgeAddress, txValue * numTxs); - } else { - //@TODO ensure the bridge has enough tokens to cover all the txs - console2.log('not eth'); - } + // The queue should only affect the specific token + assertFalse(bridge.withdrawalQueueActivated()); + } - //withdraw until queue is activated - bool queueActivated = rootBridgeFlowRate.withdrawalQueueActivated(); - while(queueActivated == false) { - _sendWithdrawMessage(token, receiver1, receiver1, txValue); - queueActivated = rootBridgeFlowRate.withdrawalQueueActivated(); - } + function test_withdrawalQueueDelayEnforced() public {} - //send one more tx to receiver2 and make sure it gets queued - _sendWithdrawMessage(token, receiver2, receiver2, txValue); + function test_withdrawalQueuedIfTransferIsTooLarge() public { - //attempt to withdraw for receiver 1 - uint256 okTime1 = _attemptEarlyWithdraw(token, receiver1, txValue); + } - //attempt to withdraw for receiver 2 - uint256 okTime2 = _attemptEarlyWithdraw(token, receiver2, txValue); + function test_queuedWithdrawalsCanBeFinalised() public {} + + function _verifyWithdrawalQueueEnforcedForAllTokens(RootERC20BridgeFlowRate bridge, address[] memory tokens) + private + { + // preconditions + assertFalse(bridge.withdrawalQueueActivated()); + assertGt(bridge.withdrawalDelay(), 0); + + uint256 snapshotId = vm.snapshot(); + for (uint256 i; i < tokens.length; i++) { + address token = tokens[i]; + console.log("Testing flow rate for token: ", token); + // exceed flow rate for any token + _exceedFlowRateParameters(bridge, token, address(11)); + + // Verify that any subsequent withdrawal by other users for other tokens gets queued + address otherToken = tokens[(i + 1) % tokens.length]; + _sendWithdrawalMessage(bridge, otherToken, address(12), 1 ether); + _verifyWithdrawalWasQueued(bridge, otherToken, address(12), 1 ether); + + // roll back state for subsequent test + vm.revertTo(snapshotId); + } + } - //fast forward past withdrawal delay time and withdraw for receiver 1 - vm.warp(okTime1+1); - rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver1, 0); + function _exceedFlowRateParameters(RootERC20BridgeFlowRate bridge, address token, address receiver) private { + (uint256 capacity, uint256 depth,, uint256 refillRate) = bridge.flowRateBuckets(token); + // check preconditions + assertGt(bridge.largeTransferThresholds(token), 0); + assertGt(capacity, 0); + assertGt(refillRate, 0); + assertEq(bridge.getPendingWithdrawalsLength(receiver), 0); - //fast forward past withdrawal delay time and withdraw for receiver 2 - vm.warp(okTime2+1); - rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver2, 0); + uint256 txValue = bridge.largeTransferThresholds(token) - 1; + uint256 numTxs = depth > txValue ? (depth / txValue) + 2 : 1; - } + _giveBridgeFunds(address(bridge), token, numTxs * txValue * 2); - function _attemptEarlyWithdraw(address token, address receiver, uint256 txValue) - public returns (uint256 okTime){ + // withdraw until flow rate is exceeded + for (uint256 i = 0; i < numTxs; i++) { + (, depth,,) = bridge.flowRateBuckets(token); + _sendWithdrawalMessage(bridge, token, receiver, txValue); + } - uint256 pendingLength = rootBridgeFlowRate.getPendingWithdrawalsLength(receiver); + assertTrue(bridge.withdrawalQueueActivated()); + _verifyWithdrawalWasQueued(bridge, token, receiver, txValue); + } - assertEq(pendingLength, 1); + function _giveBridgeFunds(address bridge, address token, uint256 amount) private { + if (token == NATIVE_ETH) { + deal(bridge, amount); + } else { + deal(token, bridge, amount); + } + } + function _verifyWithdrawalWasQueued( + RootERC20BridgeFlowRate bridge, + address token, + address receiver, + uint256 txValue + ) private { uint256[] memory indices = new uint256[](1); indices[0] = 0; + assertEq(bridge.getPendingWithdrawalsLength(receiver), 1, "Expected 1 pending withdrawal"); + RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending = bridge.getPendingWithdrawals(receiver, indices); + assertEq(pending[0].withdrawer, receiver, "Unexpected withdrawer"); + assertEq(pending[0].token, token, "Unexpected token"); + assertEq(pending[0].amount, txValue, "Unexpected amount"); + } - RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending = - rootBridgeFlowRate.getPendingWithdrawals(receiver, indices); - - assertEq(pending.length, 1); - assertEq(pending[0].withdrawer, receiver); - assertEq(pending[0].token, token); - assertEq(pending[0].amount, txValue); - uint256 timestamp = pending[0].timestamp; - - okTime = timestamp + withdrawDelay; - - //deal some eth to pay withdraw gas - vm.deal(address(this), 1 ether); - - //try to process the withdrawal - vm.expectRevert( - abi.encodeWithSelector(IFlowRateWithdrawalQueueErrors.WithdrawalRequestTooEarly.selector, timestamp, okTime) - ); - rootBridgeFlowRate.finaliseQueuedWithdrawal(receiver, 0); + // ensure subsequent withdrawals for any token, by any entity are queued + // _sendWithdrawMessage(NATIVE_ETH, receiver, receiver, txValue); + + /* + Check that the withdrawal queue has been activated + Check that the user has a pending withdrawal for the specified token + */ + // + // assertEq(bridge.getPendingWithdrawalsLength(receiver1), 0); + // + // //send one more tx to receiver2 and make sure it gets queued + // _sendWithdrawMessage(token, receiver2, receiver2, txValue); + // + // //attempt to withdraw for receiver 1 + // uint256 okTime1 = _attemptEarlyWithdraw(token, receiver1, txValue); + // + // //attempt to withdraw for receiver 2 + // uint256 okTime2 = _attemptEarlyWithdraw(token, receiver2, txValue); + // + // //fast forward past withdrawal delay time and withdraw for receiver 1 + // vm.warp(okTime1 + 1); + // bridge.finaliseQueuedWithdrawal(receiver1, 0); + // + // //fast forward past withdrawal delay time and withdraw for receiver 2 + // vm.warp(okTime2 + 1); + // bridge.finaliseQueuedWithdrawal(receiver2, 0); + + // function _attemptEarlyWithdraw(address token, address receiver, uint256 txValue) public returns (uint256 okTime) { + // uint withdrawDelay = bridge.withdrawalDelay(); + // uint256 pendingLength = bridge.getPendingWithdrawalsLength(receiver); + // + // assertEq(pendingLength, 1); + // + // uint256[] memory indices = new uint256[](1); + // indices[0] = 0; + // + // RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending = + // bridge.getPendingWithdrawals(receiver, indices); + // + // assertEq(pending.length, 1); + // assertEq(pending[0].withdrawer, receiver); + // assertEq(pending[0].token, token); + // assertEq(pending[0].amount, txValue); + // uint256 timestamp = pending[0].timestamp; + // + // okTime = timestamp + withdrawDelay; + // + // //deal some eth to pay withdraw gas + // vm.deal(address(this), 1 ether); + // + // //try to process the withdrawal + // vm.expectRevert( + // abi.encodeWithSelector(IFlowRateWithdrawalQueueErrors.WithdrawalRequestTooEarly.selector, timestamp, okTime) + // ); + // bridge.finaliseQueuedWithdrawal(receiver, 0); + // } + + function _sendWithdrawalMessage(RootERC20BridgeFlowRate bridge, address token, address sender, uint256 txValue) + private + { + _sendWithdrawMessage(bridge, token, sender, sender, txValue); } - function _sendWithdrawMessage(address token, address sender, address receiver, uint256 txValue) public { + function _sendWithdrawMessage( + RootERC20BridgeFlowRate bridge, + address token, + address sender, + address receiver, + uint256 txValue + ) private { //prank as axelar sending a message to the adapter - vm.startPrank(rootAdapter); - bytes memory predictedPayload = - abi.encode(rootBridgeFlowRate.WITHDRAW_SIG(), token, sender, receiver, txValue); - rootBridgeFlowRate.onMessageReceive(predictedPayload); + vm.startPrank(address(bridge.rootBridgeAdaptor())); + bytes memory predictedPayload = abi.encode(bridge.WITHDRAW_SIG(), token, sender, receiver, txValue); + bridge.onMessageReceive(predictedPayload); vm.stopPrank(); } } From f1c08fd96b291697cf35b91f369f19063a057c04 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 31 Jan 2024 22:12:54 +1100 Subject: [PATCH 06/24] Refactor test --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 251 +++++++++++-------- 1 file changed, 150 insertions(+), 101 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index 01cba67f..cc8e4a9d 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -4,13 +4,34 @@ pragma solidity 0.8.19; import {Test, console2} from "forge-std/Test.sol"; import {RootERC20BridgeFlowRate} from "../../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; import {IFlowRateWithdrawalQueueErrors} from "../../../src/root/flowrate/FlowRateWithdrawalQueue.sol"; -import {console} from "forge-std/console.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {console} from "forge-std/Console.sol"; import {Utils} from "../../utils.t.sol"; - +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @dev This test suite evaluates the flow rate functionality of already deployed RootERC20BridgeFlowRate contracts. + * The tests are executed against forked chains for each deployment (e.g., mainnet, testnet). + * This test suite's objective is not to exhaustively test the flow rate functionality, as this is adequately + * addressed in unit and integration tests. Instead, it aims to ensure that the functionality works as expected + * in each deployed environment. Conducting live E2E tests on the flow rate capability in a mainnet environment + * for each configured token would be complex, expensive, and potentially disruptive. + * Therefore, these tests provide an alternative way to verify that these capabilities function correctly in + * the deployed environment. They can help identify any issues related to deployment parameters or + * specific tokens or deployment conditions that may not have been captured in unit and integration tests. + * + * The test suite is parameterized by the following environment variables: + * - DEPLOYMENTS: comma-separated list of deployments to test (e.g., MAINNET, TESTNET) + * - _RPC_URL: RPC URL for the forked chain for the deployment (e.g., MAINNET_RPC_URL) + * - _BRIDGE_ADDRESS: address of the RootERC20BridgeFlowRate contract for the deployment (e.g., MAINNET_BRIDGE_ADDRESS) + * - _FLOW_RATED_TOKENS: comma-separated list of tokens to test for the deployment (e.g., MAINNET_FLOW_RATED_TOKENS) + * + * NOTE: Foundry's deal() function does not currently support contracts that use the proxy pattern, such as USDC. + * Hence this test is limited to ETH and ERC20 tokens that do not use the proxy pattern. + */ contract RootERC20BridgeFlowRateForkTest is Test, Utils { - address public constant NATIVE_ETH = address(0xeee); + address private constant ETH = address(0xeee); string[] private deployments = vm.envString("DEPLOYMENTS", ","); mapping(string => string) private rpcURLForEnv; @@ -19,6 +40,10 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { mapping(string => address[]) private tokensForEnv; string private deployment; + + /** + * @dev runs a given test function for each deployment in the DEPLOYMENTS environment variable (e.g. MAINNET, TESTNET) + */ modifier forEachDeployment() { for (uint256 i; i < deployments.length; i++) { deployment = deployments[i]; @@ -37,18 +62,47 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { } } + /** + * @dev Tests that that exceeding the flow rate parameters for any of the configured tokens in a given deployment, triggers the withdrawal delays. + * This test is run for each deployment environment, and against each configured token. + * The test also checks that flow rate parameters configured are valid. + */ function test_withdrawalQueueEnforcedWhenFlowRateExceeded() public forEachDeployment { - console.log("Testing deployment: ", deployment); - vm.selectFork(forkOfEnv[deployment]); - _verifyWithdrawalQueueEnforcedForAllTokens(bridgeForEnv[deployment], tokensForEnv[deployment]); + RootERC20BridgeFlowRate bridge = bridgeForEnv[deployment]; + address[] memory tokens = tokensForEnv[deployment]; + // preconditions + assertFalse(bridge.withdrawalQueueActivated()); + assertGt(bridge.withdrawalDelay(), 0); + + address receiver1 = createAddress(1); + address receiver2 = createAddress(2); + uint256 snapshotId = vm.snapshot(); + for (uint256 i; i < tokens.length; i++) { + address token = tokens[i]; + // exceed flow rate for any token + _exceedFlowRateParameters(bridge, token, receiver1); + + // Verify that any subsequent withdrawal by other users for other tokens gets queued + address otherToken = tokens[(i + 1) % tokens.length]; + _sendWithdrawalMessage(bridge, otherToken, receiver2, 1 ether); + _verifyWithdrawalWasQueued(bridge, otherToken, receiver2, 1 ether); + _verifyBalance(otherToken, receiver2, 0); + + // roll back state for subsequent test + vm.revertTo(snapshotId); + } } - function test_nonFlowRatedTokenIsQueued() public forEachDeployment { - vm.selectFork(forkOfEnv[deployment]); + /** + * @dev Tests that withdrawal of non-flow rated tokens are queued. + * This test is run for each deployment environment. + */ + function test_nonFlowRatedTokenWithdrawalsAreQueued() public forEachDeployment { RootERC20BridgeFlowRate bridge = bridgeForEnv[deployment]; + address receiver = createAddress(1); // preconditions - assertFalse(bridge.withdrawalQueueActivated()); + assertFalse(bridge.withdrawalQueueActivated(), "Precondition: Withdrawal queue should not activate"); // deploy and map a token ERC20 erc20 = new ERC20("Test Token", "TEST"); @@ -56,57 +110,104 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { _giveBridgeFunds(address(erc20), address(erc20), 1 ether); // ensure withdrawals for the token, which does not have flow rate configured, is queued - _sendWithdrawalMessage(bridge, address(erc20), address(1), 1 ether); - _verifyWithdrawalWasQueued(bridge, address(erc20), address(1), 1 ether); + _sendWithdrawalMessage(bridge, address(erc20), receiver, 1 ether); + _verifyWithdrawalWasQueued(bridge, address(erc20), receiver, 1 ether); // The queue should only affect the specific token assertFalse(bridge.withdrawalQueueActivated()); } - function test_withdrawalQueueDelayEnforced() public {} + /** + * @dev Tests that for queued withdrawal the mandatory delay is enforced. Also ensures that the withdrawal delay parameters for a deployment are valid. + */ + function test_withdrawalQueueDelayEnforced() public forEachDeployment { + uint256 snapshotId = vm.snapshot(); + address[] memory tokens = tokensForEnv[deployment]; + for (uint256 i; i < tokens.length; i++) { + address token = tokens[i]; + RootERC20BridgeFlowRate bridge = bridgeForEnv[deployment]; + + assertTrue( + bridge.withdrawalDelay() > 0 days && bridge.withdrawalDelay() <= 3 days, + "Precondition: Withdrawal delay appears either too low or too high" + ); - function test_withdrawalQueuedIfTransferIsTooLarge() public { + address receiver = address(12234); + uint256 amount = bridge.largeTransferThresholds(token) + 1; - } + _sendWithdrawalMessage(bridge, token, receiver, amount); + _verifyWithdrawalWasQueued(bridge, token, receiver, amount); - function test_queuedWithdrawalsCanBeFinalised() public {} + // check that early withdrawal attempt fails + vm.expectRevert(); + bridge.finaliseQueuedWithdrawal(receiver, 0); - function _verifyWithdrawalQueueEnforcedForAllTokens(RootERC20BridgeFlowRate bridge, address[] memory tokens) - private - { - // preconditions - assertFalse(bridge.withdrawalQueueActivated()); - assertGt(bridge.withdrawalDelay(), 0); + // check that timely withdrawal succeeds + vm.warp(block.timestamp + bridge.withdrawalDelay()); + bridge.finaliseQueuedWithdrawal(receiver, 0); + _verifyBalance(token, receiver, amount); + + // roll back state for subsequent test + vm.revertTo(snapshotId); + } + } + + function test_withdrawalIsQueuedIfSizeThresholdForTokenExceeded() public forEachDeployment { + address[] memory tokens = tokensForEnv[deployment]; + RootERC20BridgeFlowRate bridge = bridgeForEnv[deployment]; + address receiver = createAddress(1); uint256 snapshotId = vm.snapshot(); for (uint256 i; i < tokens.length; i++) { address token = tokens[i]; - console.log("Testing flow rate for token: ", token); - // exceed flow rate for any token - _exceedFlowRateParameters(bridge, token, address(11)); + uint256 largeAmount = bridge.largeTransferThresholds(token) + 1; - // Verify that any subsequent withdrawal by other users for other tokens gets queued - address otherToken = tokens[(i + 1) % tokens.length]; - _sendWithdrawalMessage(bridge, otherToken, address(12), 1 ether); - _verifyWithdrawalWasQueued(bridge, otherToken, address(12), 1 ether); + // preconditions + _checkIsValidLargeThreshold(token, largeAmount); + + _sendWithdrawalMessage(bridge, token, receiver, largeAmount); + _verifyWithdrawalWasQueued(bridge, token, receiver, largeAmount); + + // The queue should only affect the specific token + assertFalse(bridge.withdrawalQueueActivated()); // roll back state for subsequent test vm.revertTo(snapshotId); } } + // check that the large transfer threshold for the token is at least greater than 1 whole unit of the token + function _checkIsValidLargeThreshold(address token, uint256 amount) private { + if (token == ETH) { + assertGe(amount, 1 ether); + } else { + assertGe(amount, 1 ^ IERC20Metadata(token).decimals()); + } + } + function _exceedFlowRateParameters(RootERC20BridgeFlowRate bridge, address token, address receiver) private { (uint256 capacity, uint256 depth,, uint256 refillRate) = bridge.flowRateBuckets(token); - // check preconditions - assertGt(bridge.largeTransferThresholds(token), 0); - assertGt(capacity, 0); + + uint256 oneUnit = token == ETH ? 1 ether : 1 ^ IERC20Metadata(token).decimals(); + // Check if the thresholds are within reasonable range + assertGt( + bridge.largeTransferThresholds(token), + oneUnit, + "Precondition: Large transfer threshold should be greater than 1 unit of token" + ); + assertLt( + bridge.largeTransferThresholds(token), + capacity, + "Precondition: Large transfer threshold should be less than capacity" + ); + assertGt(capacity, oneUnit); assertGt(refillRate, 0); assertEq(bridge.getPendingWithdrawalsLength(receiver), 0); uint256 txValue = bridge.largeTransferThresholds(token) - 1; uint256 numTxs = depth > txValue ? (depth / txValue) + 2 : 1; - _giveBridgeFunds(address(bridge), token, numTxs * txValue * 2); + _giveBridgeFunds(address(bridge), token, numTxs * txValue); // withdraw until flow rate is exceeded for (uint256 i = 0; i < numTxs; i++) { @@ -116,10 +217,11 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { assertTrue(bridge.withdrawalQueueActivated()); _verifyWithdrawalWasQueued(bridge, token, receiver, txValue); + _verifyBalance(token, receiver, (numTxs - 1) * txValue); } function _giveBridgeFunds(address bridge, address token, uint256 amount) private { - if (token == NATIVE_ETH) { + if (token == ETH) { deal(bridge, amount); } else { deal(token, bridge, amount); @@ -141,80 +243,27 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { assertEq(pending[0].amount, txValue, "Unexpected amount"); } - // ensure subsequent withdrawals for any token, by any entity are queued - // _sendWithdrawMessage(NATIVE_ETH, receiver, receiver, txValue); - - /* - Check that the withdrawal queue has been activated - Check that the user has a pending withdrawal for the specified token - */ - // - // assertEq(bridge.getPendingWithdrawalsLength(receiver1), 0); - // - // //send one more tx to receiver2 and make sure it gets queued - // _sendWithdrawMessage(token, receiver2, receiver2, txValue); - // - // //attempt to withdraw for receiver 1 - // uint256 okTime1 = _attemptEarlyWithdraw(token, receiver1, txValue); - // - // //attempt to withdraw for receiver 2 - // uint256 okTime2 = _attemptEarlyWithdraw(token, receiver2, txValue); - // - // //fast forward past withdrawal delay time and withdraw for receiver 1 - // vm.warp(okTime1 + 1); - // bridge.finaliseQueuedWithdrawal(receiver1, 0); - // - // //fast forward past withdrawal delay time and withdraw for receiver 2 - // vm.warp(okTime2 + 1); - // bridge.finaliseQueuedWithdrawal(receiver2, 0); - - // function _attemptEarlyWithdraw(address token, address receiver, uint256 txValue) public returns (uint256 okTime) { - // uint withdrawDelay = bridge.withdrawalDelay(); - // uint256 pendingLength = bridge.getPendingWithdrawalsLength(receiver); - // - // assertEq(pendingLength, 1); - // - // uint256[] memory indices = new uint256[](1); - // indices[0] = 0; - // - // RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending = - // bridge.getPendingWithdrawals(receiver, indices); - // - // assertEq(pending.length, 1); - // assertEq(pending[0].withdrawer, receiver); - // assertEq(pending[0].token, token); - // assertEq(pending[0].amount, txValue); - // uint256 timestamp = pending[0].timestamp; - // - // okTime = timestamp + withdrawDelay; - // - // //deal some eth to pay withdraw gas - // vm.deal(address(this), 1 ether); - // - // //try to process the withdrawal - // vm.expectRevert( - // abi.encodeWithSelector(IFlowRateWithdrawalQueueErrors.WithdrawalRequestTooEarly.selector, timestamp, okTime) - // ); - // bridge.finaliseQueuedWithdrawal(receiver, 0); - // } + function _verifyBalance(address token, address receiver, uint256 expectedAmount) private { + if (token == ETH) { + assertEq(receiver.balance, expectedAmount); + } else { + assertEq(ERC20(token).balanceOf(receiver), expectedAmount); + } + } function _sendWithdrawalMessage(RootERC20BridgeFlowRate bridge, address token, address sender, uint256 txValue) - private + private { - _sendWithdrawMessage(bridge, token, sender, sender, txValue); - } - - function _sendWithdrawMessage( - RootERC20BridgeFlowRate bridge, - address token, - address sender, - address receiver, - uint256 txValue - ) private { //prank as axelar sending a message to the adapter vm.startPrank(address(bridge.rootBridgeAdaptor())); - bytes memory predictedPayload = abi.encode(bridge.WITHDRAW_SIG(), token, sender, receiver, txValue); + bytes memory predictedPayload = abi.encode(bridge.WITHDRAW_SIG(), token, sender, sender, txValue); bridge.onMessageReceive(predictedPayload); vm.stopPrank(); } + + function createAddress(uint256 index) private view returns (address) { + return address( + uint160(uint256(keccak256(abi.encodePacked("root-bridge-fork-test", index, blockhash(block.number))))) + ); + } } From e8998cdec3f8570b397fed250738aabea1fa422c Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Thu, 1 Feb 2024 13:31:57 +1100 Subject: [PATCH 07/24] Refactor and improve comments --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 133 +++++++++++-------- 1 file changed, 75 insertions(+), 58 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index cc8e4a9d..934383a2 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -29,36 +29,44 @@ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IER * * NOTE: Foundry's deal() function does not currently support contracts that use the proxy pattern, such as USDC. * Hence this test is limited to ETH and ERC20 tokens that do not use the proxy pattern. + * Details: https://github.com/foundry-rs/forge-std/issues/318#issuecomment-1452463876 */ contract RootERC20BridgeFlowRateForkTest is Test, Utils { address private constant ETH = address(0xeee); string[] private deployments = vm.envString("DEPLOYMENTS", ","); + + // rpc endpoints to the root chain for each environment mapping(string => string) private rpcURLForEnv; - mapping(string => uint256) private forkOfEnv; - mapping(string => RootERC20BridgeFlowRate) private bridgeForEnv; + // the fork id for each environment + mapping(string => uint256) private forkIdForEnv; + // the list of tokens for which flow rate parameters have been configured, in each environment mapping(string => address[]) private tokensForEnv; + // the root bridge address in each environment + mapping(string => RootERC20BridgeFlowRate) private bridgeInEnv; + // the deployment environment currently being tested string private deployment; /** - * @dev runs a given test function for each deployment in the DEPLOYMENTS environment variable (e.g. MAINNET, TESTNET) + * @dev Runs a test function against each deployment listed in the DEPLOYMENTS environment variable (e.g. MAINNET, TESTNET) */ modifier forEachDeployment() { for (uint256 i; i < deployments.length; i++) { deployment = deployments[i]; - vm.selectFork(forkOfEnv[deployment]); + vm.selectFork(forkIdForEnv[deployment]); _; } } function setUp() public { + // extract the rpc endpoint, bridge address, and tokens for each deployment for (uint256 i; i < deployments.length; i++) { string memory dep = deployments[i]; rpcURLForEnv[dep] = vm.envString(string.concat(dep, "_RPC_URL")); - bridgeForEnv[dep] = RootERC20BridgeFlowRate(payable(vm.envAddress(string.concat(dep, "_BRIDGE_ADDRESS")))); + bridgeInEnv[dep] = RootERC20BridgeFlowRate(payable(vm.envAddress(string.concat(dep, "_BRIDGE_ADDRESS")))); tokensForEnv[dep] = vm.envAddress(string.concat(dep, "_FLOW_RATED_TOKENS"), ","); - forkOfEnv[dep] = vm.createFork(rpcURLForEnv[dep]); + forkIdForEnv[dep] = vm.createFork(rpcURLForEnv[dep]); } } @@ -68,25 +76,25 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { * The test also checks that flow rate parameters configured are valid. */ function test_withdrawalQueueEnforcedWhenFlowRateExceeded() public forEachDeployment { - RootERC20BridgeFlowRate bridge = bridgeForEnv[deployment]; + RootERC20BridgeFlowRate bridge = bridgeInEnv[deployment]; address[] memory tokens = tokensForEnv[deployment]; - // preconditions - assertFalse(bridge.withdrawalQueueActivated()); - assertGt(bridge.withdrawalDelay(), 0); + // precondition: sanity check on the the current state and parameters of the bridge + assertFalse(bridge.withdrawalQueueActivated(), "Precondition: Withdrawal queue should not already be active"); + assertGt(bridge.withdrawalDelay(), 0, "Precondition: Withdrawal delay should be greater than 0"); - address receiver1 = createAddress(1); - address receiver2 = createAddress(2); + address withdrawer1 = createAddress(1); + address withdrawer2 = createAddress(2); uint256 snapshotId = vm.snapshot(); for (uint256 i; i < tokens.length; i++) { address token = tokens[i]; - // exceed flow rate for any token - _exceedFlowRateParameters(bridge, token, receiver1); + // exceed flow rate for token + _exceedFlowRateParameters(bridge, token, withdrawer1); // Verify that any subsequent withdrawal by other users for other tokens gets queued address otherToken = tokens[(i + 1) % tokens.length]; - _sendWithdrawalMessage(bridge, otherToken, receiver2, 1 ether); - _verifyWithdrawalWasQueued(bridge, otherToken, receiver2, 1 ether); - _verifyBalance(otherToken, receiver2, 0); + _sendWithdrawalMessage(bridge, otherToken, withdrawer2, 1 ether); + _verifyWithdrawalWasQueued(bridge, otherToken, withdrawer2, 1 ether); + _verifyBalance(otherToken, withdrawer2, 0); // roll back state for subsequent test vm.revertTo(snapshotId); @@ -94,68 +102,74 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { } /** - * @dev Tests that withdrawal of non-flow rated tokens are queued. - * This test is run for each deployment environment. + * @dev Tests that withdrawal of non-flow rated tokens get queued. + * This test is run for each deployment environment. */ function test_nonFlowRatedTokenWithdrawalsAreQueued() public forEachDeployment { - RootERC20BridgeFlowRate bridge = bridgeForEnv[deployment]; - address receiver = createAddress(1); + RootERC20BridgeFlowRate bridge = bridgeInEnv[deployment]; + address withdrawer = createAddress(1); // preconditions assertFalse(bridge.withdrawalQueueActivated(), "Precondition: Withdrawal queue should not activate"); // deploy and map a token - ERC20 erc20 = new ERC20("Test Token", "TEST"); - bridge.mapToken{value: 100 gwei}(erc20); - _giveBridgeFunds(address(erc20), address(erc20), 1 ether); + ERC20 nonFlowRatedToken = new ERC20("Test Token", "TEST"); + bridge.mapToken{value: 100 gwei}(nonFlowRatedToken); + _giveBridgeFunds(address(bridge), address(nonFlowRatedToken), 1 ether); // ensure withdrawals for the token, which does not have flow rate configured, is queued - _sendWithdrawalMessage(bridge, address(erc20), receiver, 1 ether); - _verifyWithdrawalWasQueued(bridge, address(erc20), receiver, 1 ether); + _sendWithdrawalMessage(bridge, address(nonFlowRatedToken), withdrawer, 1 ether); + _verifyWithdrawalWasQueued(bridge, address(nonFlowRatedToken), withdrawer, 1 ether); // The queue should only affect the specific token assertFalse(bridge.withdrawalQueueActivated()); } /** - * @dev Tests that for queued withdrawal the mandatory delay is enforced. Also ensures that the withdrawal delay parameters for a deployment are valid. + * @dev Tests that for queued withdrawal the mandatory delay is enforced. + * Also ensures that the withdrawal delay parameters for a deployment are valid. */ function test_withdrawalQueueDelayEnforced() public forEachDeployment { - uint256 snapshotId = vm.snapshot(); + RootERC20BridgeFlowRate bridge = bridgeInEnv[deployment]; + // preconditions: sanity check on the current state and parameters of the bridge + assertGt(bridge.withdrawalDelay(), 0 days, "Precondition: Withdrawal delay should be greater than 0"); + address[] memory tokens = tokensForEnv[deployment]; + uint256 snapshotId = vm.snapshot(); for (uint256 i; i < tokens.length; i++) { address token = tokens[i]; - RootERC20BridgeFlowRate bridge = bridgeForEnv[deployment]; - - assertTrue( - bridge.withdrawalDelay() > 0 days && bridge.withdrawalDelay() <= 3 days, - "Precondition: Withdrawal delay appears either too low or too high" - ); - - address receiver = address(12234); uint256 amount = bridge.largeTransferThresholds(token) + 1; + _giveBridgeFunds(address(bridge), token, amount); + + address withdrawer = createAddress(1); + _sendWithdrawalMessage(bridge, token, withdrawer, amount); + _verifyWithdrawalWasQueued(bridge, token, withdrawer, amount); - _sendWithdrawalMessage(bridge, token, receiver, amount); - _verifyWithdrawalWasQueued(bridge, token, receiver, amount); + console.log("Bridge balance: ", address(bridge).balance); + console.log("Test contract balance: ", address(this).balance); // check that early withdrawal attempt fails vm.expectRevert(); - bridge.finaliseQueuedWithdrawal(receiver, 0); + bridge.finaliseQueuedWithdrawal(withdrawer, 0); // check that timely withdrawal succeeds vm.warp(block.timestamp + bridge.withdrawalDelay()); - bridge.finaliseQueuedWithdrawal(receiver, 0); - _verifyBalance(token, receiver, amount); + bridge.finaliseQueuedWithdrawal(withdrawer, 0); + _verifyBalance(token, withdrawer, amount); // roll back state for subsequent test vm.revertTo(snapshotId); } } + /** + * @dev Tests that withdrawals that exceed the size threshold for a given token get queued. + * Also ensures that the threshold parameters for a token are valid. + */ function test_withdrawalIsQueuedIfSizeThresholdForTokenExceeded() public forEachDeployment { address[] memory tokens = tokensForEnv[deployment]; - RootERC20BridgeFlowRate bridge = bridgeForEnv[deployment]; - address receiver = createAddress(1); + RootERC20BridgeFlowRate bridge = bridgeInEnv[deployment]; + address withdrawer = createAddress(1); uint256 snapshotId = vm.snapshot(); for (uint256 i; i < tokens.length; i++) { @@ -165,8 +179,8 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { // preconditions _checkIsValidLargeThreshold(token, largeAmount); - _sendWithdrawalMessage(bridge, token, receiver, largeAmount); - _verifyWithdrawalWasQueued(bridge, token, receiver, largeAmount); + _sendWithdrawalMessage(bridge, token, withdrawer, largeAmount); + _verifyWithdrawalWasQueued(bridge, token, withdrawer, largeAmount); // The queue should only affect the specific token assertFalse(bridge.withdrawalQueueActivated()); @@ -176,7 +190,7 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { } } - // check that the large transfer threshold for the token is at least greater than 1 whole unit of the token + /// @dev check that the large transfer threshold for the token is at least greater than 1 whole unit of the token function _checkIsValidLargeThreshold(address token, uint256 amount) private { if (token == ETH) { assertGe(amount, 1 ether); @@ -185,7 +199,8 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { } } - function _exceedFlowRateParameters(RootERC20BridgeFlowRate bridge, address token, address receiver) private { + /// @dev sends a number of withdrawal messages to the bridge that exceeds the flow rate parameters for a given token + function _exceedFlowRateParameters(RootERC20BridgeFlowRate bridge, address token, address withdrawer) private { (uint256 capacity, uint256 depth,, uint256 refillRate) = bridge.flowRateBuckets(token); uint256 oneUnit = token == ETH ? 1 ether : 1 ^ IERC20Metadata(token).decimals(); @@ -202,7 +217,7 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { ); assertGt(capacity, oneUnit); assertGt(refillRate, 0); - assertEq(bridge.getPendingWithdrawalsLength(receiver), 0); + assertEq(bridge.getPendingWithdrawalsLength(withdrawer), 0); uint256 txValue = bridge.largeTransferThresholds(token) - 1; uint256 numTxs = depth > txValue ? (depth / txValue) + 2 : 1; @@ -212,14 +227,15 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { // withdraw until flow rate is exceeded for (uint256 i = 0; i < numTxs; i++) { (, depth,,) = bridge.flowRateBuckets(token); - _sendWithdrawalMessage(bridge, token, receiver, txValue); + _sendWithdrawalMessage(bridge, token, withdrawer, txValue); } assertTrue(bridge.withdrawalQueueActivated()); - _verifyWithdrawalWasQueued(bridge, token, receiver, txValue); - _verifyBalance(token, receiver, (numTxs - 1) * txValue); + _verifyWithdrawalWasQueued(bridge, token, withdrawer, txValue); + _verifyBalance(token, withdrawer, (numTxs - 1) * txValue); } + /// @dev sends an amount of a given token to the bridge. function _giveBridgeFunds(address bridge, address token, uint256 amount) private { if (token == ETH) { deal(bridge, amount); @@ -228,26 +244,27 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { } } + /// @dev checks that a withdrawal was queued for a given token and user function _verifyWithdrawalWasQueued( RootERC20BridgeFlowRate bridge, address token, - address receiver, + address withdrawer, uint256 txValue ) private { uint256[] memory indices = new uint256[](1); indices[0] = 0; - assertEq(bridge.getPendingWithdrawalsLength(receiver), 1, "Expected 1 pending withdrawal"); - RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending = bridge.getPendingWithdrawals(receiver, indices); - assertEq(pending[0].withdrawer, receiver, "Unexpected withdrawer"); + assertEq(bridge.getPendingWithdrawalsLength(withdrawer), 1, "Expected 1 pending withdrawal"); + RootERC20BridgeFlowRate.PendingWithdrawal[] memory pending = bridge.getPendingWithdrawals(withdrawer, indices); + assertEq(pending[0].withdrawer, withdrawer, "Unexpected withdrawer"); assertEq(pending[0].token, token, "Unexpected token"); assertEq(pending[0].amount, txValue, "Unexpected amount"); } - function _verifyBalance(address token, address receiver, uint256 expectedAmount) private { + function _verifyBalance(address token, address withdrawer, uint256 expectedAmount) private { if (token == ETH) { - assertEq(receiver.balance, expectedAmount); + assertEq(withdrawer.balance, expectedAmount); } else { - assertEq(ERC20(token).balanceOf(receiver), expectedAmount); + assertEq(ERC20(token).balanceOf(withdrawer), expectedAmount); } } From 2d07c828797e64b007080e328be14be78c94241e Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Thu, 1 Feb 2024 13:40:26 +1100 Subject: [PATCH 08/24] Fix formatting --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index 934383a2..6c11d7ca 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -35,7 +35,7 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { address private constant ETH = address(0xeee); string[] private deployments = vm.envString("DEPLOYMENTS", ","); - + // rpc endpoints to the root chain for each environment mapping(string => string) private rpcURLForEnv; // the fork id for each environment From 16b67292751389134001a896c4a534b18f68a354 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Thu, 1 Feb 2024 14:10:31 +1100 Subject: [PATCH 09/24] Skip fork tests in CI --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 921bdba7..09d7d256 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,5 +30,5 @@ jobs: - name: Run Forge tests run: | - forge test -vvv + forge test --no-match-path "test/fork/**" -vvv id: test From 6e18d514f45da7165cca78b84f544ecf852d8bfa Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Thu, 1 Feb 2024 14:25:21 +1100 Subject: [PATCH 10/24] Remove unused imports --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index 6c11d7ca..f6280334 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -import {Test, console2} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {RootERC20BridgeFlowRate} from "../../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; import {IFlowRateWithdrawalQueueErrors} from "../../../src/root/flowrate/FlowRateWithdrawalQueue.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {console} from "forge-std/Console.sol"; import {Utils} from "../../utils.t.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; @@ -145,9 +144,6 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { _sendWithdrawalMessage(bridge, token, withdrawer, amount); _verifyWithdrawalWasQueued(bridge, token, withdrawer, amount); - console.log("Bridge balance: ", address(bridge).balance); - console.log("Test contract balance: ", address(this).balance); - // check that early withdrawal attempt fails vm.expectRevert(); bridge.finaliseQueuedWithdrawal(withdrawer, 0); From 4bfdc8d7722dbf1c97530a021d848247c61d8519 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Thu, 1 Feb 2024 14:36:12 +1100 Subject: [PATCH 11/24] Make error verification more precise --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index f6280334..7cd14fbd 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -142,10 +142,17 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { address withdrawer = createAddress(1); _sendWithdrawalMessage(bridge, token, withdrawer, amount); - _verifyWithdrawalWasQueued(bridge, token, withdrawer, amount); + RootERC20BridgeFlowRate.PendingWithdrawal memory pending = + _verifyWithdrawalWasQueued(bridge, token, withdrawer, amount); // check that early withdrawal attempt fails - vm.expectRevert(); + vm.expectRevert( + abi.encodeWithSelector( + IFlowRateWithdrawalQueueErrors.WithdrawalRequestTooEarly.selector, + block.timestamp, + pending.timestamp + bridge.withdrawalDelay() + ) + ); bridge.finaliseQueuedWithdrawal(withdrawer, 0); // check that timely withdrawal succeeds @@ -246,7 +253,7 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { address token, address withdrawer, uint256 txValue - ) private { + ) private returns (RootERC20BridgeFlowRate.PendingWithdrawal memory) { uint256[] memory indices = new uint256[](1); indices[0] = 0; assertEq(bridge.getPendingWithdrawalsLength(withdrawer), 1, "Expected 1 pending withdrawal"); @@ -254,6 +261,7 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { assertEq(pending[0].withdrawer, withdrawer, "Unexpected withdrawer"); assertEq(pending[0].token, token, "Unexpected token"); assertEq(pending[0].amount, txValue, "Unexpected amount"); + return pending[0]; } function _verifyBalance(address token, address withdrawer, uint256 expectedAmount) private { From 13efe03cb5c671eb709a316aac94d5489ec50537 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Thu, 1 Feb 2024 14:42:15 +1100 Subject: [PATCH 12/24] Minor edits to comments --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index 7cd14fbd..cfe14e99 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -10,9 +10,9 @@ import {Utils} from "../../utils.t.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; /** - * @dev This test suite evaluates the flow rate functionality of already deployed RootERC20BridgeFlowRate contracts. + * @dev This test suite tests the flow rate control functionality of already deployed RootERC20BridgeFlowRate contracts. * The tests are executed against forked chains for each deployment (e.g., mainnet, testnet). - * This test suite's objective is not to exhaustively test the flow rate functionality, as this is adequately + * The objective of this test suite is not to exhaustively test the flow rate functionality, as this is adequately * addressed in unit and integration tests. Instead, it aims to ensure that the functionality works as expected * in each deployed environment. Conducting live E2E tests on the flow rate capability in a mainnet environment * for each configured token would be complex, expensive, and potentially disruptive. From b381ee9183fa2800db122fc5fd21d620fda40596 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Thu, 1 Feb 2024 15:28:10 +1100 Subject: [PATCH 13/24] Fix incorrect USDC address on Testnet --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 64274934..8dff631a 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ ABIs for contracts can be obtained from the blockchain explorer links for each c |-------------|-----------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| | Wrapped ETH | [`0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2`](https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2) | [`0x7b79995e5f793a07bc00c21412e50ecae098e7f9`](https://sepolia.etherscan.io/address/0x7b79995e5f793a07bc00c21412e50ecae098e7f9) | | IMX | [`0xf57e7e7c23978c3caec3c3548e3d615c346e79ff`](https://etherscan.io/token/0xf57e7e7c23978c3caec3c3548e3d615c346e79ff) | [`0xe2629e08f4125d14e446660028bd98ee60ee69f2`](https://sepolia.etherscan.io/address/0xe2629e08f4125d14e446660028bd98ee60ee69f2) | -| USDC | [`0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`](https://etherscan.io/token/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) | [`0xe2629e08f4125d14e446660028bd98ee60ee69f2`](https://sepolia.etherscan.io/address/0x40b87d235A5B010a20A241F15797C9debf1ecd01) | +| USDC | [`0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`](https://etherscan.io/token/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) | [`0x40b87d235A5B010a20A241F15797C9debf1ecd01`](https://sepolia.etherscan.io/address/0x40b87d235A5B010a20A241F15797C9debf1ecd01) | ### Child Chain #### Core Contracts From 7234cdd1bd720cfa2b5a4bf7fbe12cc4b91b7b84 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Fri, 2 Feb 2024 11:25:12 +1100 Subject: [PATCH 14/24] Relax flow rate parameter range check --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index cfe14e99..1bd4430e 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -206,19 +206,18 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { function _exceedFlowRateParameters(RootERC20BridgeFlowRate bridge, address token, address withdrawer) private { (uint256 capacity, uint256 depth,, uint256 refillRate) = bridge.flowRateBuckets(token); - uint256 oneUnit = token == ETH ? 1 ether : 1 ^ IERC20Metadata(token).decimals(); // Check if the thresholds are within reasonable range assertGt( bridge.largeTransferThresholds(token), - oneUnit, - "Precondition: Large transfer threshold should be greater than 1 unit of token" + 0, + "Precondition: Large transfer threshold should be greater than zero" ); assertLt( bridge.largeTransferThresholds(token), capacity, "Precondition: Large transfer threshold should be less than capacity" ); - assertGt(capacity, oneUnit); + assertGt(capacity, 0); assertGt(refillRate, 0); assertEq(bridge.getPendingWithdrawalsLength(withdrawer), 0); From 00ffbb78e5f2b4e1d7fe8a66068b782afc19bad0 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 10:49:35 +1100 Subject: [PATCH 15/24] Refactor test --- test/fork/root/RootERC20BridgeFlowRate.t.sol | 25 ++++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/test/fork/root/RootERC20BridgeFlowRate.t.sol b/test/fork/root/RootERC20BridgeFlowRate.t.sol index 1bd4430e..ca5b409e 100644 --- a/test/fork/root/RootERC20BridgeFlowRate.t.sol +++ b/test/fork/root/RootERC20BridgeFlowRate.t.sol @@ -5,7 +5,6 @@ import {Test} from "forge-std/Test.sol"; import {RootERC20BridgeFlowRate} from "../../../src/root/flowrate/RootERC20BridgeFlowRate.sol"; import {IFlowRateWithdrawalQueueErrors} from "../../../src/root/flowrate/FlowRateWithdrawalQueue.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - import {Utils} from "../../utils.t.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; @@ -221,20 +220,20 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { assertGt(refillRate, 0); assertEq(bridge.getPendingWithdrawalsLength(withdrawer), 0); - uint256 txValue = bridge.largeTransferThresholds(token) - 1; - uint256 numTxs = depth > txValue ? (depth / txValue) + 2 : 1; - - _giveBridgeFunds(address(bridge), token, numTxs * txValue); - - // withdraw until flow rate is exceeded - for (uint256 i = 0; i < numTxs; i++) { + uint256 largeTransferThreshold = bridge.largeTransferThresholds(token); + uint256 totalWithdrawals; + uint256 amount; + while (depth > 0) { + amount = depth > largeTransferThreshold ? largeTransferThreshold - 1 : depth + 1; + _giveBridgeFunds(address(bridge), token, amount); + _sendWithdrawalMessage(bridge, token, withdrawer, amount); (, depth,,) = bridge.flowRateBuckets(token); - _sendWithdrawalMessage(bridge, token, withdrawer, txValue); + totalWithdrawals += amount; } assertTrue(bridge.withdrawalQueueActivated()); - _verifyWithdrawalWasQueued(bridge, token, withdrawer, txValue); - _verifyBalance(token, withdrawer, (numTxs - 1) * txValue); + _verifyWithdrawalWasQueued(bridge, token, withdrawer, amount); + _verifyBalance(token, withdrawer, totalWithdrawals - amount); } /// @dev sends an amount of a given token to the bridge. @@ -265,9 +264,9 @@ contract RootERC20BridgeFlowRateForkTest is Test, Utils { function _verifyBalance(address token, address withdrawer, uint256 expectedAmount) private { if (token == ETH) { - assertEq(withdrawer.balance, expectedAmount); + assertEq(withdrawer.balance, expectedAmount, "Balance does not match expected"); } else { - assertEq(ERC20(token).balanceOf(withdrawer), expectedAmount); + assertEq(ERC20(token).balanceOf(withdrawer), expectedAmount, "Balance does not match expected"); } } From 6ec59a1e1d5ab48de3f17e7aaa076d60738e04be Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 10:49:50 +1100 Subject: [PATCH 16/24] Add Fork tests to CI --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09d7d256..9d072230 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,12 @@ jobs: forge build --sizes id: build - - name: Run Forge tests + - name: Run unit and integration tests run: | forge test --no-match-path "test/fork/**" -vvv id: test + + - name: Run Fork Tests + run: | + forge test --match-path "test/fork/**" -vvv + id: test From d58f54f41c0a0341d3fc0f3f7934d90e1b9cd8be Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 10:58:11 +1100 Subject: [PATCH 17/24] Fix ID conflict in CI job --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9d072230..161f7202 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,9 +31,9 @@ jobs: - name: Run unit and integration tests run: | forge test --no-match-path "test/fork/**" -vvv - id: test + id: unit_integration_test - name: Run Fork Tests run: | forge test --match-path "test/fork/**" -vvv - id: test + id: fork_test From a919ae4853fccb7d806e3581b6876ff5e5911b77 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 11:06:06 +1100 Subject: [PATCH 18/24] Increase CI logging for fork tests --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 161f7202..15e33296 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,5 +35,5 @@ jobs: - name: Run Fork Tests run: | - forge test --match-path "test/fork/**" -vvv + forge test --match-path "test/fork/**" -vvvvv id: fork_test From e6363d92b2f9d0ef3df28cf55af8aeac96cc793d Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 11:25:33 +1100 Subject: [PATCH 19/24] Add Fork test env variables --- .github/workflows/test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15e33296..ede69bd0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,9 +7,14 @@ env: jobs: check: + env: + DEPLOYMENTS: MAINNET,TESTNET + MAINNET_BRIDGE_ADDRESS: 0xBa5E35E26Ae59c7aea6F029B68c6460De2d13eB6 + MAINNET_FLOW_RATED_TOKENS: 0x0000000000000000000000000000000000000Eee,0xF57e7e7C23978C3cAEC3C3548E3D615c346e79fF,0xdAC17F958D2ee523a2206206994597C13D831ec7,0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599,0xccC8cb5229B0ac8069C51fd58367Fd1e622aFD97,0x9AB7bb7FdC60f4357ECFef43986818A2A3569c62 + TESTNET_BRIDGE_ADDRESS: 0x0D3C59c779Fd552C27b23F723E80246c840100F5 + TESTNET_FLOW_RATED_TOKENS: 0x0000000000000000000000000000000000000Eee,0xe2629e08f4125d14e446660028bd98ee60ee69f2 strategy: fail-fast: true - name: Foundry project runs-on: ubuntu-latest steps: From e30f2abc4dae344590067a9cdd151d926f8a3d0e Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 11:36:23 +1100 Subject: [PATCH 20/24] Update test.yml --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ede69bd0..7fe5285c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,11 +8,11 @@ env: jobs: check: env: - DEPLOYMENTS: MAINNET,TESTNET - MAINNET_BRIDGE_ADDRESS: 0xBa5E35E26Ae59c7aea6F029B68c6460De2d13eB6 - MAINNET_FLOW_RATED_TOKENS: 0x0000000000000000000000000000000000000Eee,0xF57e7e7C23978C3cAEC3C3548E3D615c346e79fF,0xdAC17F958D2ee523a2206206994597C13D831ec7,0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599,0xccC8cb5229B0ac8069C51fd58367Fd1e622aFD97,0x9AB7bb7FdC60f4357ECFef43986818A2A3569c62 - TESTNET_BRIDGE_ADDRESS: 0x0D3C59c779Fd552C27b23F723E80246c840100F5 - TESTNET_FLOW_RATED_TOKENS: 0x0000000000000000000000000000000000000Eee,0xe2629e08f4125d14e446660028bd98ee60ee69f2 + DEPLOYMENTS: "MAINNET,TESTNET" + MAINNET_BRIDGE_ADDRESS: "0xBa5E35E26Ae59c7aea6F029B68c6460De2d13eB6" + MAINNET_FLOW_RATED_TOKENS: "0x0000000000000000000000000000000000000Eee,0xF57e7e7C23978C3cAEC3C3548E3D615c346e79fF,0xdAC17F958D2ee523a2206206994597C13D831ec7,0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599,0xccC8cb5229B0ac8069C51fd58367Fd1e622aFD97,0x9AB7bb7FdC60f4357ECFef43986818A2A3569c62" + TESTNET_BRIDGE_ADDRESS: "0x0D3C59c779Fd552C27b23F723E80246c840100F5" + TESTNET_FLOW_RATED_TOKENS: "0x0000000000000000000000000000000000000Eee,0xe2629e08f4125d14e446660028bd98ee60ee69f2" strategy: fail-fast: true name: Foundry project From 062cffbc4cb65e4e5816d774c57cb2cc97d4549c Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 11:40:37 +1100 Subject: [PATCH 21/24] Update test.yml --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7fe5285c..33264fb9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,8 @@ jobs: MAINNET_FLOW_RATED_TOKENS: "0x0000000000000000000000000000000000000Eee,0xF57e7e7C23978C3cAEC3C3548E3D615c346e79fF,0xdAC17F958D2ee523a2206206994597C13D831ec7,0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599,0xccC8cb5229B0ac8069C51fd58367Fd1e622aFD97,0x9AB7bb7FdC60f4357ECFef43986818A2A3569c62" TESTNET_BRIDGE_ADDRESS: "0x0D3C59c779Fd552C27b23F723E80246c840100F5" TESTNET_FLOW_RATED_TOKENS: "0x0000000000000000000000000000000000000Eee,0xe2629e08f4125d14e446660028bd98ee60ee69f2" + MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} + TESTNET_RPC_URL: ${{ secrets.TESTNET_RPC_URL }} strategy: fail-fast: true name: Foundry project From bd0b8e6b68e52472fed6936aceab57adb62e77f5 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 11:58:48 +1100 Subject: [PATCH 22/24] Update job name for clarity --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 33264fb9..740d9140 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: TESTNET_RPC_URL: ${{ secrets.TESTNET_RPC_URL }} strategy: fail-fast: true - name: Foundry project + name: Build and Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From a6bc3024869b27278884edd8facdc2a028ac15cd Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 12:13:29 +1100 Subject: [PATCH 23/24] Use configured repository variables in CI --- .github/workflows/test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 740d9140..eba27536 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,11 +8,11 @@ env: jobs: check: env: - DEPLOYMENTS: "MAINNET,TESTNET" - MAINNET_BRIDGE_ADDRESS: "0xBa5E35E26Ae59c7aea6F029B68c6460De2d13eB6" - MAINNET_FLOW_RATED_TOKENS: "0x0000000000000000000000000000000000000Eee,0xF57e7e7C23978C3cAEC3C3548E3D615c346e79fF,0xdAC17F958D2ee523a2206206994597C13D831ec7,0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599,0xccC8cb5229B0ac8069C51fd58367Fd1e622aFD97,0x9AB7bb7FdC60f4357ECFef43986818A2A3569c62" - TESTNET_BRIDGE_ADDRESS: "0x0D3C59c779Fd552C27b23F723E80246c840100F5" - TESTNET_FLOW_RATED_TOKENS: "0x0000000000000000000000000000000000000Eee,0xe2629e08f4125d14e446660028bd98ee60ee69f2" + DEPLOYMENTS: ${{ vars.DEPLOYMENTS }} + MAINNET_BRIDGE_ADDRESS: ${{ vars.MAINNET_BRIDGE_ADDRESS }} + MAINNET_FLOW_RATED_TOKENS: ${{ vars.MAINNET_FLOW_RATED_TOKENS }} + TESTNET_BRIDGE_ADDRESS: ${{ vars.TESTNET_BRIDGE_ADDRESS }} + TESTNET_FLOW_RATED_TOKENS: ${{ vars.TESTNET_FLOW_RATED_TOKENS }} MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} TESTNET_RPC_URL: ${{ secrets.TESTNET_RPC_URL }} strategy: From 4e5d1f1f7bdd7b097fed5c48260f94f246bf3c7f Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 7 Feb 2024 12:29:28 +1100 Subject: [PATCH 24/24] Update README test instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8dff631a..dc582c08 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ $ forge build ### Test ```shell -$ forge test +$ forge test --no-match-path "test/fork/**" ``` ## Contract Deployment