packages/contracts-rfq/test/FastBridgeV2.GasBench.Src.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {FastBridgeV2SrcBaseTest, IFastBridgeV2} from "./FastBridgeV2.Src.Base.t.sol";
// solhint-disable func-name-mixedcase, ordering
/// @notice This test is used to estimate the gas cost of FastBridgeV2 source chain functions.
/// Very little state checks are performed, make sure to do full coverage in different tests.
contract FastBridgeV2GasBenchmarkSrcTest is FastBridgeV2SrcBaseTest {
uint256 public constant BLOCK_TIME = 12 seconds;
uint256 public constant INITIAL_RELAYER_BALANCE = 100 ether;
uint256 public constant EXCLUSIVITY_PERIOD = 60 seconds;
IFastBridgeV2.BridgeTransactionV2 internal bridgedTokenTx;
IFastBridgeV2.BridgeTransactionV2 internal bridgedEthTx;
IFastBridgeV2.BridgeTransactionV2 internal provenTokenTx;
IFastBridgeV2.BridgeTransactionV2 internal provenEthTx;
uint256 public initialUserBalanceToken;
uint256 public initialUserBalanceEth;
uint256 public initialFastBridgeBalanceToken;
uint256 public initialFastBridgeBalanceEth;
function setUp() public virtual override {
super.setUp();
initExistingTxs();
initialUserBalanceToken = srcToken.balanceOf(userA);
initialUserBalanceEth = userA.balance;
initialFastBridgeBalanceToken = srcToken.balanceOf(address(fastBridge));
initialFastBridgeBalanceEth = address(fastBridge).balance;
}
function createFixtures() public virtual override {
super.createFixtures();
bridgedTokenTx = tokenTx;
provenTokenTx = tokenTx;
bridgedEthTx = ethTx;
provenEthTx = ethTx;
// See initExistingTxs for why these start from 1, not 0
bridgedTokenTx.nonce = 1;
bridgedEthTx.nonce = 2;
provenTokenTx.nonce = 3;
provenEthTx.nonce = 4;
// Next nonce for userA tx would be 5 (either token or eth)
tokenTx.nonce = 5;
ethTx.nonce = 5;
}
function mintTokens() public virtual override {
super.mintTokens();
srcToken.mint(relayerA, INITIAL_RELAYER_BALANCE);
srcToken.mint(relayerB, INITIAL_RELAYER_BALANCE);
deal(relayerA, INITIAL_RELAYER_BALANCE);
deal(relayerB, INITIAL_RELAYER_BALANCE);
}
function initExistingTxs() public {
// Set userA nonce to 1 so that the first bridge tx doesn't have inflated gas costs due to
// the storage write from the zero initial value
cheatSenderNonce(userA, 1);
bridge({caller: userA, msgValue: 0, params: tokenParams, paramsV2: tokenParamsV2});
bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams, paramsV2: ethParamsV2});
bridge({caller: userA, msgValue: 0, params: tokenParams, paramsV2: tokenParamsV2});
bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams, paramsV2: ethParamsV2});
skipBlocksExactly(1);
prove({caller: relayerA, bridgeTx: provenTokenTx, destTxHash: hex"01"});
prove({caller: relayerB, transactionId: getTxId(provenEthTx), destTxHash: hex"02", relayer: relayerA});
// Status checks
assertEq(fastBridge.bridgeStatuses(getTxId(bridgedTokenTx)), IFastBridgeV2.BridgeStatus.REQUESTED);
assertEq(fastBridge.bridgeStatuses(getTxId(bridgedEthTx)), IFastBridgeV2.BridgeStatus.REQUESTED);
assertEq(fastBridge.bridgeStatuses(getTxId(provenTokenTx)), IFastBridgeV2.BridgeStatus.RELAYER_PROVED);
assertEq(fastBridge.bridgeStatuses(getTxId(provenEthTx)), IFastBridgeV2.BridgeStatus.RELAYER_PROVED);
assertEq(fastBridge.bridgeStatuses(getTxId(tokenTx)), IFastBridgeV2.BridgeStatus.NULL);
assertEq(fastBridge.bridgeStatuses(getTxId(ethTx)), IFastBridgeV2.BridgeStatus.NULL);
}
function skipBlocksExactly(uint256 blocks) public {
vm.roll(block.number + blocks);
vm.warp(block.timestamp + blocks * BLOCK_TIME);
}
function skipTimeAtLeast(uint256 time) public {
uint256 blocksToSkip = time / BLOCK_TIME;
if (blocksToSkip * BLOCK_TIME < time) blocksToSkip++;
skipBlocksExactly(blocksToSkip);
}
// ═══════════════════════════════════════════════════ TOKEN ═══════════════════════════════════════════════════════
function checkAfterBridgeToken() public view {
assertEq(fastBridge.bridgeStatuses(getTxId(tokenTx)), IFastBridgeV2.BridgeStatus.REQUESTED);
assertEq(srcToken.balanceOf(userA), initialUserBalanceToken - tokenParams.originAmount);
assertEq(srcToken.balanceOf(address(fastBridge)), initialFastBridgeBalanceToken + tokenParams.originAmount);
}
function test_bridge_token() public {
bridge({caller: userA, msgValue: 0, params: tokenParams, paramsV2: tokenParamsV2});
checkAfterBridgeToken();
}
function test_bridge_token_withExclusivity() public {
setTokenTestExclusivityParams(relayerA, EXCLUSIVITY_PERIOD);
bridge({caller: userA, msgValue: 0, params: tokenParams, paramsV2: tokenParamsV2});
checkAfterBridgeToken();
}
function test_prove_token() public {
bytes32 txId = getTxId(bridgedTokenTx);
prove({caller: relayerA, bridgeTx: bridgedTokenTx, destTxHash: hex"03"});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_PROVED);
(uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId);
assertEq(timestamp, block.timestamp);
assertEq(relayer, relayerA);
assertEq(srcToken.balanceOf(address(fastBridge)), initialFastBridgeBalanceToken);
}
function test_proveWithAddress_token() public {
bytes32 txId = getTxId(bridgedTokenTx);
prove({caller: relayerB, transactionId: txId, destTxHash: hex"03", relayer: relayerA});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_PROVED);
(uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId);
assertEq(timestamp, block.timestamp);
assertEq(relayer, relayerA);
}
function test_claim_token() public {
bytes32 txId = getTxId(provenTokenTx);
skipTimeAtLeast({time: CLAIM_DELAY + 1});
assertTrue(fastBridge.canClaim(txId, relayerA));
claim({caller: relayerA, bridgeTx: provenTokenTx});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED);
assertEq(srcToken.balanceOf(relayerA), INITIAL_RELAYER_BALANCE + tokenTx.originAmount);
assertEq(srcToken.balanceOf(address(fastBridge)), initialFastBridgeBalanceToken - tokenTx.originAmount);
}
function test_claimWithAddress_token() public {
bytes32 txId = getTxId(provenTokenTx);
skipTimeAtLeast({time: CLAIM_DELAY + 1});
assertTrue(fastBridge.canClaim(txId, relayerA));
claim({caller: relayerA, bridgeTx: provenTokenTx, to: relayerB});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED);
assertEq(srcToken.balanceOf(relayerB), INITIAL_RELAYER_BALANCE + tokenTx.originAmount);
assertEq(srcToken.balanceOf(address(fastBridge)), initialFastBridgeBalanceToken - tokenTx.originAmount);
}
function test_dispute_token() public {
bytes32 txId = getTxId(provenTokenTx);
dispute({caller: guard, txId: txId});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED);
assertEq(srcToken.balanceOf(address(fastBridge)), initialFastBridgeBalanceToken);
}
function test_cancelPermissioned_token() public {
bytes32 txId = getTxId(bridgedTokenTx);
skipTimeAtLeast({time: DEADLINE});
cancel({caller: canceler, bridgeTx: bridgedTokenTx});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED);
assertEq(srcToken.balanceOf(userA), initialUserBalanceToken + tokenParams.originAmount);
assertEq(srcToken.balanceOf(address(fastBridge)), initialFastBridgeBalanceToken - tokenParams.originAmount);
}
function test_cancelPermissionless_token() public {
bytes32 txId = getTxId(bridgedTokenTx);
skipTimeAtLeast({time: DEADLINE + PERMISSIONLESS_CANCEL_DELAY});
cancel({caller: userB, bridgeTx: bridgedTokenTx});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED);
assertEq(srcToken.balanceOf(userA), initialUserBalanceToken + tokenParams.originAmount);
assertEq(srcToken.balanceOf(address(fastBridge)), initialFastBridgeBalanceToken - tokenParams.originAmount);
}
// ════════════════════════════════════════════════════ ETH ════════════════════════════════════════════════════════
function checkAfterBridgeEth() public view {
assertEq(fastBridge.bridgeStatuses(getTxId(ethTx)), IFastBridgeV2.BridgeStatus.REQUESTED);
assertEq(userA.balance, initialUserBalanceEth - ethParams.originAmount);
assertEq(address(fastBridge).balance, initialFastBridgeBalanceEth + ethParams.originAmount);
}
function test_bridge_eth() public {
bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams, paramsV2: ethParamsV2});
checkAfterBridgeEth();
}
function test_bridge_eth_withExclusivity() public {
setEthTestExclusivityParams(relayerA, EXCLUSIVITY_PERIOD);
bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams, paramsV2: ethParamsV2});
checkAfterBridgeEth();
}
function test_prove_eth() public {
bytes32 txId = getTxId(bridgedEthTx);
prove({caller: relayerA, bridgeTx: bridgedEthTx, destTxHash: hex"03"});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_PROVED);
(uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId);
assertEq(timestamp, block.timestamp);
assertEq(relayer, relayerA);
assertEq(address(fastBridge).balance, initialFastBridgeBalanceEth);
}
function test_proveWithAddress_eth() public {
bytes32 txId = getTxId(bridgedEthTx);
prove({caller: relayerB, transactionId: txId, destTxHash: hex"03", relayer: relayerA});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_PROVED);
(uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId);
assertEq(timestamp, block.timestamp);
assertEq(relayer, relayerA);
assertEq(address(fastBridge).balance, initialFastBridgeBalanceEth);
}
function test_claim_eth() public {
bytes32 txId = getTxId(provenEthTx);
skipTimeAtLeast({time: CLAIM_DELAY + 1});
assertTrue(fastBridge.canClaim(txId, relayerA));
claim({caller: relayerA, bridgeTx: provenEthTx});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED);
assertEq(relayerA.balance, INITIAL_RELAYER_BALANCE + ethTx.originAmount);
assertEq(address(fastBridge).balance, initialFastBridgeBalanceEth - ethTx.originAmount);
}
function test_claimWithAddress_eth() public {
bytes32 txId = getTxId(provenEthTx);
skipTimeAtLeast({time: CLAIM_DELAY + 1});
assertTrue(fastBridge.canClaim(txId, relayerA));
claim({caller: relayerA, bridgeTx: provenEthTx, to: relayerB});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED);
assertEq(relayerB.balance, INITIAL_RELAYER_BALANCE + ethTx.originAmount);
assertEq(address(fastBridge).balance, initialFastBridgeBalanceEth - ethTx.originAmount);
}
function test_dispute_eth() public {
bytes32 txId = getTxId(provenEthTx);
dispute({caller: guard, txId: txId});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED);
assertEq(address(fastBridge).balance, initialFastBridgeBalanceEth);
}
function test_cancelPermissioned_eth() public {
bytes32 txId = getTxId(bridgedEthTx);
skipTimeAtLeast({time: DEADLINE});
cancel({caller: canceler, bridgeTx: bridgedEthTx});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED);
assertEq(userA.balance, initialUserBalanceEth + ethParams.originAmount);
assertEq(address(fastBridge).balance, initialFastBridgeBalanceEth - ethParams.originAmount);
}
function test_cancelPermissionless_eth() public {
bytes32 txId = getTxId(bridgedEthTx);
skipTimeAtLeast({time: DEADLINE + PERMISSIONLESS_CANCEL_DELAY});
cancel({caller: userB, bridgeTx: bridgedEthTx});
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED);
assertEq(userA.balance, initialUserBalanceEth + ethParams.originAmount);
assertEq(address(fastBridge).balance, initialFastBridgeBalanceEth - ethParams.originAmount);
}
}