Skip to content

Commit

Permalink
Add example
Browse files Browse the repository at this point in the history
  • Loading branch information
wcgcyx committed Jul 16, 2024
1 parent 9616f8e commit 4189f5e
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 1,270 deletions.
343 changes: 21 additions & 322 deletions test/invariant/InvariantBridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,352 +2,51 @@
pragma solidity 0.8.19;

import {Test} from "forge-std/Test.sol";
import {ChildERC20} from "../../src/child/ChildERC20.sol";
import {WIMX} from "../../src/child/WIMX.sol";
import {WETH} from "../../src/lib/WETH.sol";
import {IChildERC20Bridge, ChildERC20Bridge} from "../../src/child/ChildERC20Bridge.sol";
import {IRootERC20Bridge, IERC20Metadata} from "../../src/root/RootERC20Bridge.sol";
import {RootERC20BridgeFlowRate} from "../../src/root/flowrate/RootERC20BridgeFlowRate.sol";
import {MockAdaptor} from "./MockAdaptor.sol";
import {ChildHelper} from "./child/ChildHelper.sol";
import {RootHelper} from "./root/RootHelper.sol";
import {ChildERC20BridgeHandler} from "./child/ChildERC20BridgeHandler.sol";
import {RootERC20BridgeFlowRateHandler} from "./root/RootERC20BridgeFlowRateHandler.sol";
import {MockMessenger} from "./MockMessenger.sol";
import {MockHandler} from "./MockHandler.sol";
import "forge-std/console.sol";

contract InvariantBridge is Test {
string public constant CHAIN_URL = "http://127.0.0.1:8500";
uint256 public constant IMX_DEPOSIT_LIMIT = 10000 ether;
uint256 public constant MAX_AMOUNT = 10000;
address public constant ADMIN = address(0x111);
uint256 public constant NO_OF_USERS = 20;
uint256 public constant NO_OF_TOKENS = 10;

address[] users;
address[] rootTokens;

uint256 childId;
uint256 rootId;
uint256 resetId;
ChildERC20Bridge childBridge;
RootERC20BridgeFlowRate rootBridge;
MockAdaptor childAdaptor;
MockAdaptor rootAdaptor;
ChildHelper childHelper;
RootHelper rootHelper;
ChildERC20BridgeHandler childBridgeHandler;
RootERC20BridgeFlowRateHandler rootBridgeHandler;

uint256 mappingGas;
MockMessenger childMessenger;
MockMessenger rootMessenger;
MockHandler rootHandler;

function setUp() public {
// Create two forks, child chain and root chain
childId = vm.createFork(CHAIN_URL);
rootId = vm.createFork(CHAIN_URL);
// Forge has an issue that fails to reset state at the end of each run.
// For example, we found out that if the context stays at child chain at the end of setUp(),
// the state on child chain will not be reset or if the context stays at root chain, the state
// on the root chain will not be reset, which causes subsequent runs to fail.
// We introduced a third chain called reset chain and we make the context to stay on the reset chain
// in order to reset state on both child chain and root chain.
resetId = vm.createFork(CHAIN_URL);

// Deploy contracts on child chain.
// Deploy messenger on child chain.
vm.selectFork(childId);
vm.startPrank(ADMIN);
ChildERC20 childTokenTemplate = new ChildERC20();
childTokenTemplate.initialize(address(123), "Test", "TST", 18);
childAdaptor = new MockAdaptor();
childMessenger = new MockMessenger(rootId);
vm.stopPrank();

childBridge = new ChildERC20Bridge(address(this));
WIMX wIMX = new WIMX();

// Deploy contracts on root chain.
// Deploy messenger on root chain.
vm.selectFork(rootId);
vm.startPrank(ADMIN);
ChildERC20 rootTokenTemplate = new ChildERC20();
rootTokenTemplate.initialize(address(123), "Test", "TST", 18);
rootAdaptor = new MockAdaptor();
rootMessenger = new MockMessenger(childId);
vm.stopPrank();

rootBridge = new RootERC20BridgeFlowRate(address(this));
ChildERC20 rootIMXToken = new ChildERC20();
rootIMXToken.initialize(address(123), "Immutable X", "IMX", 18);
WETH wETH = new WETH();

// Configure contracts on child chain.
vm.selectFork(childId);
childAdaptor.initialize(rootId, address(childBridge));
IChildERC20Bridge.InitializationRoles memory childRoles = IChildERC20Bridge.InitializationRoles({
defaultAdmin: address(this),
pauser: address(this),
unpauser: address(this),
adaptorManager: address(this),
initialDepositor: address(this),
treasuryManager: address(this)
});
childBridge.initialize(
childRoles, address(childAdaptor), address(childTokenTemplate), address(rootIMXToken), address(wIMX)
);
vm.deal(address(childBridge), IMX_DEPOSIT_LIMIT);

// Configure contracts on root chain.
vm.selectFork(rootId);
rootAdaptor.initialize(childId, address(rootBridge));
IRootERC20Bridge.InitializationRoles memory rootRoles = IRootERC20Bridge.InitializationRoles({
defaultAdmin: address(this),
pauser: address(this),
unpauser: address(this),
variableManager: address(this),
adaptorManager: address(this)
});
rootBridge.initialize(
rootRoles,
address(rootAdaptor),
address(childBridge),
address(rootTokenTemplate),
address(rootIMXToken),
address(wETH),
IMX_DEPOSIT_LIMIT,
ADMIN
);

// Create users.
vm.selectFork(rootId);
for (uint256 i = 0; i < NO_OF_USERS; i++) {
address user = vm.addr(0x10000 + i);
// Mint ETH token
vm.deal(user, MAX_AMOUNT);
// Mint IMX token
rootIMXToken.mint(user, MAX_AMOUNT);
users.push(user);
}
// Create tokens.
for (uint256 i = 0; i < NO_OF_TOKENS; i++) {
vm.startPrank(address(0x234));
ChildERC20 rootToken = new ChildERC20();
rootToken.initialize(address(123), "Test", "TST", 18);
// Mint token to user
for (uint256 j = 0; j < NO_OF_USERS; j++) {
rootToken.mint(users[j], MAX_AMOUNT);
}
vm.stopPrank();
// Configure rate for half tokens
if (i % 2 == 0) {
vm.prank(ADMIN);
rootBridge.setRateControlThreshold(address(rootToken), MAX_AMOUNT, MAX_AMOUNT / 3600, MAX_AMOUNT / 2);
}
rootTokens.push(address(rootToken));
}

// Deploy helpers and handlers on all chains.
vm.selectFork(childId);
vm.startPrank(ADMIN);
childHelper = new ChildHelper(payable(childBridge));
address temp = address(new RootHelper(ADMIN, payable(rootBridge)));
childBridgeHandler = new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), temp);
new RootERC20BridgeFlowRateHandler(childId, rootId, users, rootTokens, address(childHelper), temp);
vm.stopPrank();

vm.selectFork(rootId);
vm.startPrank(ADMIN);
new ChildHelper(payable(childBridge));
rootHelper = new RootHelper(ADMIN, payable(rootBridge));
new ChildERC20BridgeHandler(childId, rootId, users, rootTokens, address(childHelper), address(rootHelper));
rootBridgeHandler = new RootERC20BridgeFlowRateHandler(
childId, rootId, users, rootTokens, address(childHelper), address(rootHelper)
);
vm.stopPrank();

// Map tokens
vm.selectFork(rootId);
for (uint256 i = 0; i < NO_OF_TOKENS; i++) {
address rootToken = rootTokens[i];
rootBridge.mapToken{value: 1}(IERC20Metadata(rootToken));
mappingGas += 1;
// Verify
address childTokenL1 = rootBridge.rootTokenToChildToken(address(rootToken));

vm.selectFork(childId);
address childTokenL2 = childBridge.rootTokenToChildToken(address(rootToken));
vm.selectFork(rootId);

assertEq(childTokenL1, childTokenL2, "Child token address mismatch between L1 and L2");
}

// Target contracts
bytes4[] memory childSelectors = new bytes4[](8);
childSelectors[0] = childBridgeHandler.withdraw.selector;
childSelectors[1] = childBridgeHandler.withdrawTo.selector;
childSelectors[2] = childBridgeHandler.withdrawIMX.selector;
childSelectors[3] = childBridgeHandler.withdrawIMXTo.selector;
childSelectors[4] = childBridgeHandler.withdrawWIMX.selector;
childSelectors[5] = childBridgeHandler.withdrawWIMXTo.selector;
childSelectors[6] = childBridgeHandler.withdrawETH.selector;
childSelectors[7] = childBridgeHandler.withdrawETHTo.selector;
targetSelector(FuzzSelector({addr: address(childBridgeHandler), selectors: childSelectors}));

bytes4[] memory rootSelectors = new bytes4[](8);
rootSelectors[0] = rootBridgeHandler.deposit.selector;
rootSelectors[1] = rootBridgeHandler.depositTo.selector;
rootSelectors[2] = rootBridgeHandler.depositIMX.selector;
rootSelectors[3] = rootBridgeHandler.depositIMXTo.selector;
rootSelectors[4] = rootBridgeHandler.depositETH.selector;
rootSelectors[5] = rootBridgeHandler.depositETHTo.selector;
rootSelectors[6] = rootBridgeHandler.depositWETH.selector;
rootSelectors[7] = rootBridgeHandler.depositWETHTo.selector;
targetSelector(FuzzSelector({addr: address(rootBridgeHandler), selectors: rootSelectors}));

targetContract(address(childBridgeHandler));
targetContract(address(rootBridgeHandler));
// Send a message from root chain to child chain
rootMessenger.sendMessage("0x1234");

vm.selectFork(resetId);
// Set target contract
rootHandler = new MockHandler();
bytes4[] memory rootSelectors = new bytes4[](1);
rootSelectors[0] = rootHandler.handler.selector;
targetSelector(FuzzSelector({addr: address(rootHandler), selectors: rootSelectors}));
targetContract(address(rootHandler));
}

/// forge-config: default.invariant.runs = 256
/// forge-config: default.invariant.depth = 15
/// forge-config: default.invariant.runs = 1
/// forge-config: default.invariant.depth = 5
/// forge-config: default.invariant.fail-on-revert = true
function invariant_ERC20TokenBalanced() external {
for (uint256 i = 0; i < NO_OF_TOKENS; i++) {
address rootToken = rootTokens[i];

vm.selectFork(rootId);
uint256 bridgeBalance = ChildERC20(rootToken).balanceOf(address(rootBridge));
address childToken = rootBridge.rootTokenToChildToken(rootToken);

vm.selectFork(childId);
uint256 totalSupply = ChildERC20(childToken).totalSupply();

uint256 userBalanceSum = 0;
for (uint256 j = 0; j < NO_OF_USERS; j++) {
address user = users[j];
userBalanceSum += ChildERC20(childToken).balanceOf(user);
}

assertEq(bridgeBalance, totalSupply);
assertEq(bridgeBalance, userBalanceSum);
}
}

/// forge-config: default.invariant.runs = 256
/// forge-config: default.invariant.depth = 15
/// forge-config: default.invariant.fail-on-revert = true
function invariant_IndividualERC20TokenBalanced() external {
for (uint256 i = 0; i < NO_OF_TOKENS; i++) {
address rootToken = rootTokens[i];
vm.selectFork(rootId);
address childToken = rootBridge.rootTokenToChildToken(rootToken);
for (uint256 j = 0; j < NO_OF_USERS; j++) {
address user = users[j];

vm.selectFork(rootId);
uint256 balanceL1 = ChildERC20(rootToken).balanceOf(user);

vm.selectFork(childId);
uint256 balanceL2 = ChildERC20(childToken).balanceOf(user);

assertEq(balanceL1 + balanceL2, MAX_AMOUNT);
}
}
}

/// forge-config: default.invariant.runs = 256
/// forge-config: default.invariant.depth = 15
/// forge-config: default.invariant.fail-on-revert = true
function invariant_IMXBalanced() external {
vm.selectFork(rootId);
uint256 bridgeBalance = ChildERC20(rootBridge.rootIMXToken()).balanceOf(address(rootBridge));

vm.selectFork(childId);
uint256 totalSupply = IMX_DEPOSIT_LIMIT - address(childBridge).balance;

uint256 userBalanceSum = 0;
for (uint256 j = 0; j < NO_OF_USERS; j++) {
address user = users[j];
userBalanceSum += user.balance;
}

assertEq(bridgeBalance, totalSupply);
assertEq(bridgeBalance, userBalanceSum);
}

/// forge-config: default.invariant.runs = 256
/// forge-config: default.invariant.depth = 15
/// forge-config: default.invariant.fail-on-revert = true
function invariant_IndividualIMXBalanced() external {
for (uint256 j = 0; j < NO_OF_USERS; j++) {
address user = users[j];

vm.selectFork(rootId);
uint256 balanceL1 = ChildERC20(rootBridge.rootIMXToken()).balanceOf(user);

vm.selectFork(childId);
uint256 balanceL2 = user.balance;

assertEq(balanceL1 + balanceL2, MAX_AMOUNT);
}
}

/// forge-config: default.invariant.runs = 256
/// forge-config: default.invariant.depth = 15
/// forge-config: default.invariant.fail-on-revert = true
function invariant_ETHBalanced() external {
vm.selectFork(rootId);
uint256 bridgeBalance = address(rootBridge).balance;

vm.selectFork(childId);
uint256 totalSupply = ChildERC20(childBridge.childETHToken()).totalSupply();

uint256 userBalanceSum = 0;
for (uint256 j = 0; j < NO_OF_USERS; j++) {
address user = users[j];
userBalanceSum += ChildERC20(childBridge.childETHToken()).balanceOf(user);
}

assertEq(bridgeBalance, totalSupply);
assertEq(bridgeBalance, userBalanceSum);
}

/// forge-config: default.invariant.runs = 256
/// forge-config: default.invariant.depth = 15
/// forge-config: default.invariant.fail-on-revert = true
function invariant_IndividualETHBalanced() external {
for (uint256 j = 0; j < NO_OF_USERS; j++) {
address user = users[j];

vm.selectFork(rootId);
uint256 balanceL1 = user.balance;

vm.selectFork(childId);
uint256 balanceL2 = ChildERC20(childBridge.childETHToken()).balanceOf(user);

assertEq(balanceL1 + balanceL2, MAX_AMOUNT);
}
}

/// forge-config: default.invariant.runs = 256
/// forge-config: default.invariant.depth = 15
/// forge-config: default.invariant.fail-on-revert = true
function invariant_NoRemainingWETH() external {
vm.selectFork(rootId);
assertEq(rootBridge.rootWETHToken().balance, 0);
}

/// forge-config: default.invariant.runs = 256
/// forge-config: default.invariant.depth = 15
/// forge-config: default.invariant.fail-on-revert = true
function invariant_NoRemainingWIMX() external {
vm.selectFork(childId);
assertEq(childBridge.wIMXToken().balance, 0);
}

/// forge-config: default.invariant.runs = 256
/// forge-config: default.invariant.depth = 15
/// forge-config: default.invariant.fail-on-revert = true
function invariant_GasBalanced() external {
vm.selectFork(rootId);
assertEq(address(rootAdaptor).balance - mappingGas, rootHelper.totalGas());
vm.selectFork(childId);
assertEq(address(childAdaptor).balance, childHelper.totalGas());
function invariant_test() external {
}
}
Loading

0 comments on commit 4189f5e

Please sign in to comment.