packages/contracts-rfq/test/FastBridgeV2.Management.t.sol
// 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);
}
}