synapsecns/sanguine

View on GitHub
packages/contracts-rfq/test/FastBridgeV2.t.sol

Summary

Maintainability
Test Coverage
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {BridgeTransactionV2Lib} from "../contracts/libs/BridgeTransactionV2.sol";

import {IFastBridge} from "../contracts/interfaces/IFastBridge.sol";

// solhint-disable-next-line no-unused-import
import {IFastBridgeV2} from "../contracts/interfaces/IFastBridgeV2.sol";

import {FastBridgeV2} from "../contracts/FastBridgeV2.sol";
import {IAdminV2Errors} from "../contracts/interfaces/IAdminV2Errors.sol";
import {IFastBridgeV2Errors} from "../contracts/interfaces/IFastBridgeV2Errors.sol";

import {MockERC20} from "./mocks/MockERC20.sol";

import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {Test} from "forge-std/Test.sol";
import {StdStorage, stdStorage} from "forge-std/Test.sol";

// solhint-disable no-empty-blocks, max-states-count, ordering
abstract contract FastBridgeV2Test is Test, IAdminV2Errors, IFastBridgeV2Errors {
    using stdStorage for StdStorage;

    uint32 public constant SRC_CHAIN_ID = 1337;
    uint32 public constant DST_CHAIN_ID = 7331;
    uint256 public constant DEADLINE = 1 days;
    address public constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    FastBridgeV2 public fastBridge;
    MockERC20 public srcToken;
    MockERC20 public dstToken;

    address public relayerA = makeAddr("Relayer A");
    address public relayerB = makeAddr("Relayer B");
    address public guard = makeAddr("Guard");
    address public userA = makeAddr("User A");
    address public userB = makeAddr("User B");
    address public governor = makeAddr("Governor");
    address public canceler = makeAddr("Canceler");

    IFastBridgeV2.BridgeTransactionV2 internal tokenTx;
    IFastBridgeV2.BridgeTransactionV2 internal ethTx;
    IFastBridge.BridgeParams internal tokenParams;
    IFastBridge.BridgeParams internal ethParams;

    IFastBridgeV2.BridgeParamsV2 internal tokenParamsV2;
    IFastBridgeV2.BridgeParamsV2 internal ethParamsV2;

    bytes internal mockRequestV1;
    bytes internal invalidRequestV2;
    bytes internal mockRequestV3;

    /// @notice We include an empty "test" function so that this contract does not appear in the coverage report.
    function testFastBridgeV2Test() external {}

    function createInvalidRequestV2(bytes memory requestV2) public pure returns (bytes memory result) {
        // Copy everything but the last byte
        result = new bytes(requestV2.length - 1);
        for (uint256 i = 0; i < result.length; i++) {
            result[i] = requestV2[i];
        }
    }

    function createMockRequestV3(bytes memory requestV2) public pure returns (bytes memory result) {
        result = new bytes(requestV2.length);
        // Set the version to 3
        result[0] = 0x00;
        result[1] = 0x03;
        // Copy the rest of the request
        for (uint256 i = 2; i < result.length; i++) {
            result[i] = requestV2[i];
        }
    }

    function setUp() public virtual {
        srcToken = new MockERC20("SrcToken", 6);
        dstToken = new MockERC20("DstToken", 6);
        createFixtures();
        mockRequestV1 = abi.encode(extractV1(tokenTx));
        // Invalid V2 request is formed before `createFixturesV2` to ensure it's not using zapData
        invalidRequestV2 = createInvalidRequestV2(BridgeTransactionV2Lib.encodeV2(tokenTx));
        createFixturesV2();
        // Mock V3 request is formed after `createFixturesV2` to ensure it's using zapData if needed
        mockRequestV3 = createMockRequestV3(BridgeTransactionV2Lib.encodeV2(ethTx));
        fastBridge = deployFastBridge();
        configureFastBridge();
        mintTokens();
    }

    function deployFastBridge() public virtual returns (FastBridgeV2);

    function configureFastBridge() public virtual {}

    function mintTokens() public virtual {}

    function createFixtures() public virtual {
        tokenParams = IFastBridge.BridgeParams({
            dstChainId: DST_CHAIN_ID,
            sender: userA,
            to: userB,
            originToken: address(srcToken),
            destToken: address(dstToken),
            originAmount: 1e6,
            destAmount: 0.99e6,
            sendChainGas: false,
            deadline: block.timestamp + DEADLINE
        });
        ethParams = IFastBridge.BridgeParams({
            dstChainId: DST_CHAIN_ID,
            sender: userA,
            to: userB,
            originToken: ETH_ADDRESS,
            destToken: ETH_ADDRESS,
            originAmount: 1 ether,
            destAmount: 0.99 ether,
            sendChainGas: false,
            deadline: block.timestamp + DEADLINE
        });

        setStorageBridgeTxV2(
            tokenTx,
            IFastBridge.BridgeTransaction({
                originChainId: SRC_CHAIN_ID,
                destChainId: DST_CHAIN_ID,
                originSender: userA,
                destRecipient: userB,
                originToken: address(srcToken),
                destToken: address(dstToken),
                originAmount: 1e6,
                destAmount: 0.99e6,
                // override this in tests with protocol fees
                originFeeAmount: 0,
                sendChainGas: false,
                deadline: block.timestamp + DEADLINE,
                nonce: 0
            })
        );
        setStorageBridgeTxV2(
            ethTx,
            IFastBridge.BridgeTransaction({
                originChainId: SRC_CHAIN_ID,
                destChainId: DST_CHAIN_ID,
                originSender: userA,
                destRecipient: userB,
                originToken: ETH_ADDRESS,
                destToken: ETH_ADDRESS,
                originAmount: 1 ether,
                destAmount: 0.99 ether,
                // override this in tests with protocol fees
                originFeeAmount: 0,
                sendChainGas: false,
                deadline: block.timestamp + DEADLINE,
                nonce: 1
            })
        );
    }

    function createFixturesV2() public virtual {
        // Override in tests with exclusivity params
        tokenParamsV2 = IFastBridgeV2.BridgeParamsV2({
            quoteRelayer: address(0),
            quoteExclusivitySeconds: 0,
            quoteId: bytes(""),
            zapNative: 0,
            zapData: bytes("")
        });
        ethParamsV2 = IFastBridgeV2.BridgeParamsV2({
            quoteRelayer: address(0),
            quoteExclusivitySeconds: 0,
            quoteId: bytes(""),
            zapNative: 0,
            zapData: bytes("")
        });

        tokenTx.exclusivityRelayer = address(0);
        tokenTx.exclusivityEndTime = 0;
        ethTx.exclusivityRelayer = address(0);
        ethTx.exclusivityEndTime = 0;
    }

    function setStorageBridgeTxV2(
        IFastBridgeV2.BridgeTransactionV2 storage txV2,
        IFastBridge.BridgeTransaction memory txV1
    )
        internal
    {
        txV2.originChainId = txV1.originChainId;
        txV2.destChainId = txV1.destChainId;
        txV2.originSender = txV1.originSender;
        txV2.destRecipient = txV1.destRecipient;
        txV2.originToken = txV1.originToken;
        txV2.destToken = txV1.destToken;
        txV2.originAmount = txV1.originAmount;
        txV2.destAmount = txV1.destAmount;
        txV2.originFeeAmount = txV1.originFeeAmount;
        txV2.deadline = txV1.deadline;
        txV2.nonce = txV1.nonce;
    }

    function setTokenTestZapData(bytes memory zapData) public {
        tokenParamsV2.zapData = zapData;
        tokenTx.zapData = zapData;
    }

    function setTokenTestZapNative(uint256 zapNative) public {
        tokenParamsV2.zapNative = zapNative;
        tokenTx.zapNative = zapNative;
    }

    function setTokenTestExclusivityParams(address relayer, uint256 exclusivitySeconds) public {
        tokenParamsV2.quoteRelayer = relayer;
        tokenParamsV2.quoteExclusivitySeconds = int256(exclusivitySeconds);
        tokenParamsV2.quoteId = bytes.concat("Token:", getMockQuoteId(relayer));

        tokenTx.exclusivityRelayer = relayer;
        tokenTx.exclusivityEndTime = block.timestamp + exclusivitySeconds;
    }

    function setEthTestZapData(bytes memory zapData) public {
        ethParamsV2.zapData = zapData;
        ethTx.zapData = zapData;
    }

    function setEthTestZapNative(uint256 zapNative) public {
        ethParamsV2.zapNative = zapNative;
        ethTx.zapNative = zapNative;
    }

    function setEthTestExclusivityParams(address relayer, uint256 exclusivitySeconds) public {
        ethParamsV2.quoteRelayer = relayer;
        ethParamsV2.quoteExclusivitySeconds = int256(exclusivitySeconds);
        ethParamsV2.quoteId = bytes.concat("ETH:", getMockQuoteId(relayer));

        ethTx.exclusivityRelayer = relayer;
        ethTx.exclusivityEndTime = block.timestamp + exclusivitySeconds;
    }

    function extractV1(IFastBridgeV2.BridgeTransactionV2 memory txV2)
        public
        pure
        returns (IFastBridge.BridgeTransaction memory txV1)
    {
        txV1.originChainId = txV2.originChainId;
        txV1.destChainId = txV2.destChainId;
        txV1.originSender = txV2.originSender;
        txV1.destRecipient = txV2.destRecipient;
        txV1.originToken = txV2.originToken;
        txV1.destToken = txV2.destToken;
        txV1.originAmount = txV2.originAmount;
        txV1.destAmount = txV2.destAmount;
        txV1.originFeeAmount = txV2.originFeeAmount;
        txV1.deadline = txV2.deadline;
        txV1.nonce = txV2.nonce;
    }

    function getMockQuoteId(address relayer) public view returns (bytes memory) {
        if (relayer == relayerA) {
            return bytes("created by Relayer A");
        } else if (relayer == relayerB) {
            return bytes("created by Relayer B");
        } else {
            return bytes("created by unknown relayer");
        }
    }

    function getTxId(IFastBridgeV2.BridgeTransactionV2 memory bridgeTx) public pure returns (bytes32) {
        return keccak256(BridgeTransactionV2Lib.encodeV2(bridgeTx));
    }

    function expectUnauthorized(address caller, bytes32 role) public {
        vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, caller, role));
    }

    function expectRevertInvalidEncodedTx() public {
        vm.expectRevert(BridgeTransactionV2Lib.BridgeTransactionV2__InvalidEncodedTx.selector);
    }

    function expectRevertUnsupportedVersion(uint16 version) public {
        vm.expectRevert(
            abi.encodeWithSelector(BridgeTransactionV2Lib.BridgeTransactionV2__UnsupportedVersion.selector, version)
        );
    }

    function cheatCollectedProtocolFees(address token, uint256 amount) public {
        stdstore.target(address(fastBridge)).sig("protocolFees(address)").with_key(token).checked_write(amount);
    }

    function cheatSenderNonce(address sender, uint256 nonce) public {
        stdstore.target(address(fastBridge)).sig("senderNonces(address)").with_key(sender).checked_write(nonce);
    }
}