synapsecns/sanguine

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

Summary

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

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

import {FastBridgeV2, FastBridgeV2Test} from "./FastBridgeV2.t.sol";

// solhint-disable func-name-mixedcase, ordering
contract FastBridgeV2ManagementTest is FastBridgeV2Test {
    uint256 public constant FEE_RATE_MAX = 1e4; // 1%
    bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");

    uint256 public constant MIN_CANCEL_DELAY = 1 hours;
    uint256 public constant DEFAULT_CANCEL_DELAY = 1 days;

    uint256 public constant MIN_DISPUTE_PENALTY_TIME = 1 minutes;
    uint256 public constant DEFAULT_DISPUTE_PENALTY_TIME = 30 minutes;

    address public admin = makeAddr("Admin");
    address public governorA = makeAddr("Governor A");

    address public proverA = makeAddr("Prover A");
    address public proverB = makeAddr("Prover B");

    event ProverAdded(address prover);
    event ProverRemoved(address prover);

    event CancelDelayUpdated(uint256 oldCancelDelay, uint256 newCancelDelay);
    event DeployBlockSet(uint256 blockNumber);
    event DisputePenaltyTimeUpdated(uint256 oldDisputePenaltyTime, uint256 newDisputePenaltyTime);
    event FeeRateUpdated(uint256 oldFeeRate, uint256 newFeeRate);
    event FeesSwept(address token, address recipient, uint256 amount);

    function deployFastBridge() public override returns (FastBridgeV2) {
        return new FastBridgeV2(admin);
    }

    function configureFastBridge() public override {
        setGovernor(admin, governor);
    }

    function mintTokens() public override {
        srcToken.mint(address(fastBridge), 100);
        deal(address(fastBridge), 200);
        cheatCollectedProtocolFees(address(srcToken), 100);
        cheatCollectedProtocolFees(ETH_ADDRESS, 200);
    }

    function addProver(address caller, address prover) public {
        vm.prank(caller);
        fastBridge.addProver(prover);
    }

    function removeProver(address caller, address prover) public {
        vm.prank(caller);
        fastBridge.removeProver(prover);
    }

    function setGovernor(address caller, address newGovernor) public {
        vm.prank(caller);
        fastBridge.grantRole(GOVERNOR_ROLE, newGovernor);
    }

    function setCancelDelay(address caller, uint256 newCancelDelay) public {
        vm.prank(caller);
        fastBridge.setCancelDelay(newCancelDelay);
    }

    function setDeployBlock(address caller, uint256 blockNumber) public {
        vm.prank(caller);
        fastBridge.setDeployBlock(blockNumber);
    }

    function setDisputePenaltyTime(address caller, uint256 newDisputePenaltyTime) public {
        vm.prank(caller);
        fastBridge.setDisputePenaltyTime(newDisputePenaltyTime);
    }

    function setProtocolFeeRate(address caller, uint256 newFeeRate) public {
        vm.prank(caller);
        fastBridge.setProtocolFeeRate(newFeeRate);
    }

    function sweepProtocolFees(address caller, address token, address recipient) public {
        vm.prank(caller);
        fastBridge.sweepProtocolFees(token, recipient);
    }

    function test_grantGovernorRole() public {
        assertFalse(fastBridge.hasRole(GOVERNOR_ROLE, governorA));
        setGovernor(admin, governorA);
        assertTrue(fastBridge.hasRole(GOVERNOR_ROLE, governorA));
    }

    function test_grantGovernorRole_revertNotAdmin(address caller) public {
        vm.assume(caller != admin);
        expectUnauthorized(caller, fastBridge.DEFAULT_ADMIN_ROLE());
        setGovernor(caller, governorA);
    }

    function test_defaultValues() public view {
        assertEq(fastBridge.cancelDelay(), DEFAULT_CANCEL_DELAY);
        assertEq(fastBridge.deployBlock(), block.number);
        assertEq(fastBridge.disputePenaltyTime(), DEFAULT_DISPUTE_PENALTY_TIME);
        assertEq(fastBridge.protocolFeeRate(), 0);
    }

    // ════════════════════════════════════════════════ ADD PROVER ═════════════════════════════════════════════════════

    function checkProverInfo(address prover, uint16 proverID, uint256 activeFromTimestamp) public view {
        (uint16 id, uint256 ts) = fastBridge.getProverInfo(prover);
        assertEq(id, proverID);
        assertEq(ts, activeFromTimestamp);
        address p;
        (p, ts) = fastBridge.getProverInfoByID(proverID);
        if (proverID != 0) {
            assertEq(p, prover);
            assertEq(ts, activeFromTimestamp);
        } else {
            assertEq(p, address(0));
            assertEq(ts, 0);
        }
    }

    function test_addProver() public {
        uint256 proverAtime = block.timestamp;
        vm.expectEmit(address(fastBridge));
        emit ProverAdded(proverA);
        addProver(admin, proverA);
        assertEq(fastBridge.getActiveProverID(proverA), 1);
        assertEq(fastBridge.getActiveProverID(proverB), 0);
        address[] memory provers = fastBridge.getProvers();
        assertEq(provers.length, 1);
        assertEq(provers[0], proverA);
        checkProverInfo(proverA, 1, proverAtime);
        checkProverInfo(proverB, 0, 0);
    }

    function test_addProver_twice() public {
        uint256 proverAtime = block.timestamp;
        test_addProver();
        skip(1 hours);
        uint256 proverBtime = block.timestamp;
        vm.expectEmit(address(fastBridge));
        emit ProverAdded(proverB);
        addProver(admin, proverB);
        assertEq(fastBridge.getActiveProverID(proverA), 1);
        assertEq(fastBridge.getActiveProverID(proverB), 2);
        address[] memory provers = fastBridge.getProvers();
        assertEq(provers.length, 2);
        assertEq(provers[0], proverA);
        assertEq(provers[1], proverB);
        checkProverInfo(proverA, 1, proverAtime);
        checkProverInfo(proverB, 2, proverBtime);
    }

    function test_addProver_twice_afterRemoval() public {
        test_removeProver_twice();
        // Add B back
        skip(1 hours);
        uint256 proverBtime = block.timestamp;
        vm.expectEmit(address(fastBridge));
        emit ProverAdded(proverB);
        addProver(admin, proverB);
        assertEq(fastBridge.getActiveProverID(proverA), 0);
        assertEq(fastBridge.getActiveProverID(proverB), 2);
        address[] memory provers = fastBridge.getProvers();
        assertEq(provers.length, 1);
        assertEq(provers[0], proverB);
        checkProverInfo(proverA, 1, 0);
        checkProverInfo(proverB, 2, proverBtime);
        // Add A back
        skip(1 hours);
        uint256 proverAtime = block.timestamp;
        vm.expectEmit(address(fastBridge));
        emit ProverAdded(proverA);
        addProver(admin, proverA);
        assertEq(fastBridge.getActiveProverID(proverA), 1);
        assertEq(fastBridge.getActiveProverID(proverB), 2);
        provers = fastBridge.getProvers();
        assertEq(provers.length, 2);
        assertEq(provers[0], proverA);
        assertEq(provers[1], proverB);
        checkProverInfo(proverA, 1, proverAtime);
        checkProverInfo(proverB, 2, proverBtime);
    }

    function test_addProver_revertNotAdmin(address caller) public {
        vm.assume(caller != admin);
        expectUnauthorized(caller, fastBridge.DEFAULT_ADMIN_ROLE());
        addProver(caller, proverA);
    }

    function test_addProver_revertAlreadyActive() public {
        test_addProver();
        vm.expectRevert(ProverAlreadyActive.selector);
        addProver(admin, proverA);
    }

    function test_addProver_revertTooManyProvers() public {
        for (uint256 i = 0; i < type(uint16).max; i++) {
            addProver(admin, address(uint160(i)));
        }
        vm.expectRevert(ProverCapacityExceeded.selector);
        addProver(admin, proverA);
    }

    // ═══════════════════════════════════════════════ REMOVE PROVER ═══════════════════════════════════════════════════

    function test_removeProver() public {
        test_addProver_twice();
        uint256 proverBtime = block.timestamp;
        vm.expectEmit(address(fastBridge));
        emit ProverRemoved(proverA);
        removeProver(admin, proverA);
        assertEq(fastBridge.getActiveProverID(proverA), 0);
        assertEq(fastBridge.getActiveProverID(proverB), 2);
        address[] memory provers = fastBridge.getProvers();
        assertEq(provers.length, 1);
        assertEq(provers[0], proverB);
        checkProverInfo(proverA, 1, 0);
        checkProverInfo(proverB, 2, proverBtime);
    }

    function test_removeProver_twice() public {
        test_removeProver();
        vm.expectEmit(address(fastBridge));
        emit ProverRemoved(proverB);
        removeProver(admin, proverB);
        assertEq(fastBridge.getActiveProverID(proverA), 0);
        assertEq(fastBridge.getActiveProverID(proverB), 0);
        address[] memory provers = fastBridge.getProvers();
        assertEq(provers.length, 0);
        checkProverInfo(proverA, 1, 0);
        checkProverInfo(proverB, 2, 0);
    }

    function test_removeProver_revertNotAdmin(address caller) public {
        vm.assume(caller != admin);
        expectUnauthorized(caller, fastBridge.DEFAULT_ADMIN_ROLE());
        removeProver(caller, proverA);
    }

    function test_removeProver_revertNeverBeenActive() public {
        vm.expectRevert(ProverNotActive.selector);
        removeProver(admin, proverA);
    }

    function test_removeProver_revertNotActive() public {
        test_removeProver();
        vm.expectRevert(ProverNotActive.selector);
        removeProver(admin, proverA);
    }

    // ═════════════════════════════════════════════ SET CANCEL DELAY ══════════════════════════════════════════════════

    function test_setCancelDelay() public {
        vm.expectEmit(address(fastBridge));
        emit CancelDelayUpdated(DEFAULT_CANCEL_DELAY, 4 days);
        setCancelDelay(governor, 4 days);
        assertEq(fastBridge.cancelDelay(), 4 days);
    }

    function test_setCancelDelay_twice() public {
        test_setCancelDelay();
        vm.expectEmit(address(fastBridge));
        emit CancelDelayUpdated(4 days, 8 days);
        setCancelDelay(governor, 8 days);
        assertEq(fastBridge.cancelDelay(), 8 days);
    }

    function test_setCancelDelay_revertBelowMin() public {
        vm.expectRevert(CancelDelayBelowMin.selector);
        setCancelDelay(governor, MIN_CANCEL_DELAY - 1);
    }

    function test_setCancelDelay_revertNotGovernor(address caller) public {
        vm.assume(caller != governor);
        expectUnauthorized(caller, fastBridge.GOVERNOR_ROLE());
        setCancelDelay(caller, 4 days);
    }

    // ═════════════════════════════════════════════ SET DEPLOY BLOCK ══════════════════════════════════════════════════

    function test_setDeployBlock() public {
        vm.expectEmit(address(fastBridge));
        emit DeployBlockSet(123_456);
        setDeployBlock(admin, 123_456);
        assertEq(fastBridge.deployBlock(), 123_456);
    }

    function test_setDeployBlock_revertNotAdmin(address caller) public {
        vm.assume(caller != admin);
        expectUnauthorized(caller, fastBridge.DEFAULT_ADMIN_ROLE());
        setDeployBlock(caller, 123_456);
    }

    // ═════════════════════════════════════════ SET DISPUTE PENALTY TIME ══════════════════════════════════════════════

    function test_setDisputePenaltyTime() public {
        vm.expectEmit(address(fastBridge));
        emit DisputePenaltyTimeUpdated(DEFAULT_DISPUTE_PENALTY_TIME, 1 days);
        setDisputePenaltyTime(governor, 1 days);
        assertEq(fastBridge.disputePenaltyTime(), 1 days);
    }

    function test_setDisputePenaltyTime_twice() public {
        test_setDisputePenaltyTime();
        vm.expectEmit(address(fastBridge));
        emit DisputePenaltyTimeUpdated(1 days, 2 days);
        setDisputePenaltyTime(governor, 2 days);
        assertEq(fastBridge.disputePenaltyTime(), 2 days);
    }

    function test_setDisputePenaltyTime_revertBelowMin() public {
        vm.expectRevert(DisputePenaltyTimeBelowMin.selector);
        setDisputePenaltyTime(governor, MIN_DISPUTE_PENALTY_TIME - 1);
    }

    function test_setDisputePenaltyTime_revertNotGovernor(address caller) public {
        vm.assume(caller != governor);
        expectUnauthorized(caller, fastBridge.GOVERNOR_ROLE());
        setDisputePenaltyTime(caller, 1 days);
    }

    // ═══════════════════════════════════════════ SET PROTOCOL FEE RATE ═══════════════════════════════════════════════

    function test_setProtocolFeeRate() public {
        vm.expectEmit(address(fastBridge));
        emit FeeRateUpdated(0, 123);
        setProtocolFeeRate(governor, 123);
        assertEq(fastBridge.protocolFeeRate(), 123);
    }

    function test_setProtocolFeeRate_twice() public {
        test_setProtocolFeeRate();
        vm.expectEmit(address(fastBridge));
        emit FeeRateUpdated(123, FEE_RATE_MAX);
        setProtocolFeeRate(governor, FEE_RATE_MAX);
        assertEq(fastBridge.protocolFeeRate(), FEE_RATE_MAX);
    }

    function test_setProtocolFeeRate_revert_tooHigh() public {
        vm.expectRevert(FeeRateAboveMax.selector);
        setProtocolFeeRate(governor, FEE_RATE_MAX + 1);
    }

    function test_setProtocolFeeRate_revert_notGovernor(address caller) public {
        vm.assume(caller != governor);
        expectUnauthorized(caller, fastBridge.GOVERNOR_ROLE());
        setProtocolFeeRate(caller, 123);
    }

    // ════════════════════════════════════════════ SWEEP PROTOCOL FEES ════════════════════════════════════════════════

    function test_sweepProtocolFees_erc20() public {
        vm.expectEmit(address(fastBridge));
        emit FeesSwept(address(srcToken), governorA, 100);
        sweepProtocolFees(governor, address(srcToken), governorA);
        assertEq(srcToken.balanceOf(address(fastBridge)), 0);
        assertEq(srcToken.balanceOf(governorA), 100);
        assertEq(fastBridge.protocolFees(address(srcToken)), 0);
    }

    function test_sweepProtocolFees_eth() public {
        vm.expectEmit(address(fastBridge));
        emit FeesSwept(ETH_ADDRESS, governorA, 200);
        sweepProtocolFees(governor, ETH_ADDRESS, governorA);
        assertEq(address(fastBridge).balance, 0);
        assertEq(governorA.balance, 200);
        assertEq(fastBridge.protocolFees(ETH_ADDRESS), 0);
    }

    function test_sweepProtocolFees_revertNotGovernor(address caller) public {
        vm.assume(caller != governor);
        expectUnauthorized(caller, fastBridge.GOVERNOR_ROLE());
        sweepProtocolFees(caller, address(srcToken), governorA);
    }

    // ═══════════════════════════════════════════ SET CHAIN GAS AMOUNT ════════════════════════════════════════════════

    function test_chainGasAmountZero() public view {
        assertEq(fastBridge.chainGasAmount(), 0);
    }

    function test_setChainGasAmount_revert() public {
        // Generic revert: this function should not be in the V2 interface
        vm.expectRevert();
        vm.prank(governor);
        IAdmin(address(fastBridge)).setChainGasAmount(123);
    }
}