Skip to content

Commit

Permalink
Implement flowrate fork test
Browse files Browse the repository at this point in the history
  • Loading branch information
ermyas committed Jan 31, 2024
1 parent c6ab829 commit 84c2627
Showing 1 changed file with 179 additions and 87 deletions.
266 changes: 179 additions & 87 deletions test/fork/root/RootERC20BridgeFlowRate.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

0 comments on commit 84c2627

Please sign in to comment.