diff --git a/.env.sample b/.env.sample index f1854b16..957e611b 100644 --- a/.env.sample +++ b/.env.sample @@ -10,5 +10,4 @@ ROOT_GAS_SERVICE_ADDRESS= CHILD_GAS_SERVICE_ADDRESS= ROOT_CHAIN_NAME= CHILD_CHAIN_NAME= -ROOT_IMX_ADDRESS= -CHILD_ETH_ADDRESS= \ No newline at end of file +ROOT_IMX_ADDRESS= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5680b282..dc2e9557 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,6 @@ docs/ .vscode/ # Contract addresses -addresses.json \ No newline at end of file +addresses.json + +/node_modules \ No newline at end of file diff --git a/script/DeployChildContracts.s.sol b/script/DeployChildContracts.s.sol index 9bf20df8..45a0cae0 100644 --- a/script/DeployChildContracts.s.sol +++ b/script/DeployChildContracts.s.sol @@ -14,7 +14,7 @@ contract DeployChildContracts is Script { function run() public { uint256 deployerPrivateKey = vm.envUint("CHILD_PRIVATE_KEY"); address childGateway = vm.envAddress("CHILD_GATEWAY_ADDRESS"); - address childGasService = vm.envAddress("CHILD_GAS_SERVICE_ADDRESS"); // Not yet used. + //address childGasService = vm.envAddress("CHILD_GAS_SERVICE_ADDRESS"); // Not yet used. string memory childRpcUrl = vm.envString("CHILD_RPC_URL"); vm.createSelectFork(childRpcUrl); diff --git a/script/InitializeRootContracts.s.sol b/script/InitializeRootContracts.s.sol index 515f122d..ad935886 100644 --- a/script/InitializeRootContracts.s.sol +++ b/script/InitializeRootContracts.s.sol @@ -22,7 +22,6 @@ contract InitializeRootContracts is Script { string memory rootRpcUrl = vm.envString("ROOT_RPC_URL"); uint256 rootPrivateKey = vm.envUint("ROOT_PRIVATE_KEY"); address rootIMXToken = vm.envAddress("ROOT_IMX_ADDRESS"); - address childETHToken = vm.envAddress("CHILD_ETH_ADDRESS"); /** * INITIALIZE ROOT CHAIN CONTRACTS @@ -31,12 +30,7 @@ contract InitializeRootContracts is Script { vm.startBroadcast(rootPrivateKey); rootERC20Bridge.initialize( - address(rootBridgeAdaptor), - childERC20Bridge, - childBridgeAdaptor, - rootChainChildTokenTemplate, - rootIMXToken, - childETHToken + address(rootBridgeAdaptor), childERC20Bridge, childBridgeAdaptor, rootChainChildTokenTemplate, rootIMXToken ); rootBridgeAdaptor.setChildBridgeAdaptor(); diff --git a/src/child/ChildERC20Bridge.sol b/src/child/ChildERC20Bridge.sol index 2b8352f4..41b096e8 100644 --- a/src/child/ChildERC20Bridge.sol +++ b/src/child/ChildERC20Bridge.sol @@ -33,18 +33,25 @@ contract ChildERC20Bridge is { using SafeERC20 for IERC20Metadata; + /// @dev leave this as the first param for the integration tests + mapping(address => address) public rootTokenToChildToken; + + bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); + bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); + address public constant NATIVE_ETH = address(0xeee); + IChildERC20BridgeAdaptor public bridgeAdaptor; + /// @dev The address that will be sending messages to, and receiving messages from, the child chain. string public rootERC20BridgeAdaptor; /// @dev The address of the token template that will be cloned to create tokens. address public childTokenTemplate; /// @dev The name of the chain that this bridge is connected to. string public rootChain; - mapping(address => address) public rootTokenToChildToken; - - bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); - bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); - address public imxToken; + /// @dev The address of the IMX ERC20 token on L1. + address public rootIMXToken; + /// @dev The address of the ETH ERC20 token on L2. + address public childETHToken; /** * @notice Initilization function for RootERC20Bridge. @@ -52,7 +59,7 @@ contract ChildERC20Bridge is * @param newRootERC20BridgeAdaptor Stringified address of root ERC20 bridge adaptor to communicate with. * @param newChildTokenTemplate Address of child token template to clone. * @param newRootChain A stringified representation of the chain that this bridge is connected to. Used for validation. - * @param newIMXToken Address of ECR20 IMX on the root chain. + * @param newRootIMXToken Address of ECR20 IMX on the root chain. * @dev Can only be called once. */ function initialize( @@ -60,9 +67,9 @@ contract ChildERC20Bridge is string memory newRootERC20BridgeAdaptor, address newChildTokenTemplate, string memory newRootChain, - address newIMXToken + address newRootIMXToken ) public initializer { - if (newBridgeAdaptor == address(0) || newChildTokenTemplate == address(0) || newIMXToken == address(0)) { + if (newBridgeAdaptor == address(0) || newChildTokenTemplate == address(0) || newRootIMXToken == address(0)) { revert ZeroAddress(); } @@ -78,7 +85,12 @@ contract ChildERC20Bridge is childTokenTemplate = newChildTokenTemplate; bridgeAdaptor = IChildERC20BridgeAdaptor(newBridgeAdaptor); rootChain = newRootChain; - imxToken = newIMXToken; + rootIMXToken = newRootIMXToken; + + IChildERC20 clonedETHToken = + IChildERC20(Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(NATIVE_ETH)))); + clonedETHToken.initialize(NATIVE_ETH, "Ethereum", "ETH", 18); + childETHToken = address(clonedETHToken); } /** @@ -120,10 +132,14 @@ contract ChildERC20Bridge is revert ZeroAddress(); } - if (address(rootToken) == imxToken) { + if (address(rootToken) == rootIMXToken) { revert CantMapIMX(); } + if (address(rootToken) == NATIVE_ETH) { + revert CantMapETH(); + } + if (rootTokenToChildToken[rootToken] != address(0)) { revert AlreadyMapped(); } @@ -147,11 +163,16 @@ contract ChildERC20Bridge is address childToken; - if (address(rootToken) != imxToken) { - childToken = rootTokenToChildToken[address(rootToken)]; - if (childToken == address(0)) { - revert NotMapped(); + if (address(rootToken) != rootIMXToken) { + if (address(rootToken) == NATIVE_ETH) { + childToken = childETHToken; + } else { + childToken = rootTokenToChildToken[address(rootToken)]; + if (childToken == address(0)) { + revert NotMapped(); + } } + if (address(childToken).code.length == 0) { revert EmptyTokenContract(); } @@ -159,7 +180,12 @@ contract ChildERC20Bridge is if (!IChildERC20(childToken).mint(receiver, amount)) { revert MintFailed(); } - emit ERC20Deposit(address(rootToken), childToken, sender, receiver, amount); + + if (address(rootToken) == NATIVE_ETH) { + emit NativeEthDeposit(address(rootToken), childToken, sender, receiver, amount); + } else { + emit ERC20Deposit(address(rootToken), childToken, sender, receiver, amount); + } } else { Address.sendValue(payable(receiver), amount); emit IMXDeposit(address(rootToken), sender, receiver, amount); diff --git a/src/interfaces/child/IChildERC20Bridge.sol b/src/interfaces/child/IChildERC20Bridge.sol index da1e9de6..90bffc19 100644 --- a/src/interfaces/child/IChildERC20Bridge.sol +++ b/src/interfaces/child/IChildERC20Bridge.sol @@ -33,7 +33,7 @@ interface IChildERC20BridgeEvents { uint256 amount ); event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount); - event NativeDeposit( + event NativeEthDeposit( address indexed rootToken, address indexed childToken, address depositor, @@ -58,6 +58,8 @@ interface IChildERC20BridgeErrors { error NotMapped(); /// @notice Error when attempting to map IMX. error CantMapIMX(); + /// @notice Error when attempting to map ETH. + error CantMapETH(); /// @notice Error when a token is already mapped. error AlreadyMapped(); /// @notice Error when a message is given to the bridge from an address not the designated bridge adaptor. diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index 1dbe07ad..6e09c715 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -46,7 +46,7 @@ interface IRootERC20BridgeEvents { uint256 amount ); event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount); - event NativeDeposit( + event NativeEthDeposit( address indexed rootToken, address indexed childToken, address depositor, @@ -68,6 +68,8 @@ interface IRootERC20BridgeErrors { error NotMapped(); /// @notice Error when attempting to map IMX. error CantMapIMX(); + /// @notice Error when attempting to map ETH. + error CantMapETH(); /// @notice Error when token balance invariant check fails. error BalanceInvariantCheckFailed(uint256 actualBalance, uint256 expectedBalance); } diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index e2d0bd5a..8a7486a1 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -11,6 +11,7 @@ import {IAxelarGateway} from "@axelar-cgp-solidity/contracts/interfaces/IAxelarG import {IRootERC20Bridge, IERC20Metadata} from "../interfaces/root/IRootERC20Bridge.sol"; import {IRootERC20BridgeEvents, IRootERC20BridgeErrors} from "../interfaces/root/IRootERC20Bridge.sol"; import {IRootERC20BridgeAdaptor} from "../interfaces/root/IRootERC20BridgeAdaptor.sol"; +import {IChildERC20} from "../interfaces/child/IChildERC20.sol"; /** * @notice RootERC20Bridge is a bridge that allows ERC20 tokens to be transferred from the root chain to the child chain. @@ -29,19 +30,20 @@ contract RootERC20Bridge is { using SafeERC20 for IERC20Metadata; + /// @dev leave this as the first param for the integration tests + mapping(address => address) public rootTokenToChildToken; + bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT"); - address public constant NATIVE_TOKEN = address(0xeee); + address public constant NATIVE_ETH = address(0xeee); IRootERC20BridgeAdaptor public rootBridgeAdaptor; /// @dev Used to verify source address in messages sent from child chain. - /// @dev Stringified version of address. string public childBridgeAdaptor; /// @dev The address that will be minting tokens on the child chain. address public childERC20Bridge; /// @dev The address of the token template that will be cloned to create tokens on the child chain. address public childTokenTemplate; - mapping(address => address) public rootTokenToChildToken; /// @dev The address of the IMX ERC20 token on L1. address public rootIMXToken; /// @dev The address of the ETH ERC20 token on L2. @@ -54,7 +56,6 @@ contract RootERC20Bridge is * @param newChildBridgeAdaptor Address of child bridge adaptor to communicate with. * @param newChildTokenTemplate Address of child token template to clone. * @param newRootIMXToken Address of ERC20 IMX on the root chain. - * @param newChildETHToken Address of ERC20 ETH on the child chain. * @dev Can only be called once. */ function initialize( @@ -62,20 +63,22 @@ contract RootERC20Bridge is address newChildERC20Bridge, address newChildBridgeAdaptor, address newChildTokenTemplate, - address newRootIMXToken, - address newChildETHToken + address newRootIMXToken ) public initializer { if ( newRootBridgeAdaptor == address(0) || newChildERC20Bridge == address(0) || newChildTokenTemplate == address(0) || newChildBridgeAdaptor == address(0) - || newRootIMXToken == address(0) || newChildETHToken == address(0) + || newRootIMXToken == address(0) ) { revert ZeroAddress(); } childERC20Bridge = newChildERC20Bridge; childTokenTemplate = newChildTokenTemplate; rootIMXToken = newRootIMXToken; - childETHToken = newChildETHToken; + + childETHToken = Clones.predictDeterministicAddress( + childTokenTemplate, keccak256(abi.encodePacked(NATIVE_ETH)), childERC20Bridge + ); rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor); childBridgeAdaptor = Strings.toHexString(newChildBridgeAdaptor); } @@ -108,7 +111,7 @@ contract RootERC20Bridge is uint256 expectedBalance = address(this).balance - (msg.value - amount); - _deposit(IERC20Metadata(NATIVE_TOKEN), receiver, amount); + _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount); // invariant check to ensure that the root native balance has increased by the amount deposited if (address(this).balance != expectedBalance) { @@ -147,6 +150,11 @@ contract RootERC20Bridge is if (address(rootToken) == rootIMXToken) { revert CantMapIMX(); } + + if (address(rootToken) == NATIVE_ETH) { + revert CantMapETH(); + } + if (rootTokenToChildToken[address(rootToken)] != address(0)) { revert AlreadyMapped(); } @@ -184,7 +192,7 @@ contract RootERC20Bridge is // TODO We can call _mapToken here, but ordering in the GMP is not guaranteed. // Therefore, we need to decide how to handle this and it may be a UI decision to wait until map token message is executed on child chain. // Discuss this, and add this decision to the design doc. - if (address(rootToken) != NATIVE_TOKEN) { + if (address(rootToken) != NATIVE_ETH) { if (address(rootToken) != rootIMXToken) { childToken = rootTokenToChildToken[address(rootToken)]; if (childToken == address(0)) { @@ -204,8 +212,8 @@ contract RootERC20Bridge is rootBridgeAdaptor.sendMessage{value: feeAmount}(payload, msg.sender); - if (address(rootToken) == NATIVE_TOKEN) { - emit NativeDeposit(address(rootToken), childETHToken, msg.sender, receiver, amount); + if (address(rootToken) == NATIVE_ETH) { + emit NativeEthDeposit(address(rootToken), childETHToken, msg.sender, receiver, amount); } else if (address(rootToken) == rootIMXToken) { emit IMXDeposit(address(rootToken), msg.sender, receiver, amount); } else { diff --git a/test/integration/child/ChildAxelarBridge.t.sol b/test/integration/child/ChildAxelarBridge.t.sol index 54d530a9..1bdbd36b 100644 --- a/test/integration/child/ChildAxelarBridge.t.sol +++ b/test/integration/child/ChildAxelarBridge.t.sol @@ -18,7 +18,8 @@ import {Utils} from "../../utils.t.sol"; contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChildERC20BridgeErrors, Utils { string public ROOT_ADAPTOR_ADDRESS = Strings.toHexString(address(1)); string public ROOT_CHAIN_NAME = "ROOT_CHAIN"; - address constant IMX_TOKEN = address(9); + address constant IMX_TOKEN_ADDRESS = address(0xccc); + address constant NATIVE_ETH = address(0xeee); ChildERC20Bridge public childERC20Bridge; ChildERC20 public childERC20; @@ -35,7 +36,11 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway), address(childERC20Bridge)); childERC20Bridge.initialize( - address(childAxelarBridgeAdaptor), ROOT_ADAPTOR_ADDRESS, address(childERC20), ROOT_CHAIN_NAME, IMX_TOKEN + address(childAxelarBridgeAdaptor), + ROOT_ADAPTOR_ADDRESS, + address(childERC20), + ROOT_CHAIN_NAME, + IMX_TOKEN_ADDRESS ); } @@ -143,6 +148,47 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil ); } + function test_deposit_EmitsNativeDeposit() public { + address sender = address(0xff); + address receiver = address(0xee); + uint256 amount = 100; + + address predictedChildETHToken = Clones.predictDeterministicAddress( + address(childERC20), keccak256(abi.encodePacked(NATIVE_ETH)), address(childERC20Bridge) + ); + + bytes32 commandId = bytes32("testCommandId"); + + vm.expectEmit(address(childERC20Bridge)); + emit NativeEthDeposit(NATIVE_ETH, predictedChildETHToken, sender, receiver, amount); + + childAxelarBridgeAdaptor.execute( + commandId, + ROOT_CHAIN_NAME, + ROOT_ADAPTOR_ADDRESS, + abi.encode(childERC20Bridge.DEPOSIT_SIG(), NATIVE_ETH, sender, receiver, amount) + ); + } + + function test_deposit_EmitsIMXDeposit() public { + address sender = address(0xff); + address receiver = address(0xee); + uint256 amount = 100; + bytes32 commandId = bytes32("testCommandId"); + + vm.deal(address(childERC20Bridge), 1 ether); + + vm.expectEmit(address(childERC20Bridge)); + emit IMXDeposit(IMX_TOKEN_ADDRESS, sender, receiver, amount); + + childAxelarBridgeAdaptor.execute( + commandId, + ROOT_CHAIN_NAME, + ROOT_ADAPTOR_ADDRESS, + abi.encode(childERC20Bridge.DEPOSIT_SIG(), IMX_TOKEN_ADDRESS, sender, receiver, amount) + ); + } + function test_deposit_TransfersTokenToReceiver() public { address rootTokenAddress = address(456); address sender = address(0xff); @@ -246,8 +292,8 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil bytes32 depositSig = childERC20Bridge.DEPOSIT_SIG(); address rootAddress = address(0x123); { - // Slot is 6 because of the Ownable, Initializable contracts coming first. - uint256 rootTokenToChildTokenMappingSlot = 6; + // Slot is 2 because of the Ownable, Initializable contracts coming first. + uint256 rootTokenToChildTokenMappingSlot = 2; address childAddress = address(444444); bytes32 slot = getMappingStorageSlotFor(rootAddress, rootTokenToChildTokenMappingSlot); bytes32 data = bytes32(uint256(uint160(childAddress))); diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index 8b0c38af..bab1f5c9 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -17,19 +17,21 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx string constant CHILD_CHAIN_NAME = "test"; bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); address constant IMX_TOKEN_ADDRESS = address(0xccc); - address constant CHILD_ETH_TOKEN = address(0xddd); + address constant NATIVE_ETH = address(0xeee); + uint256 constant mapTokenFee = 300; uint256 constant depositFee = 200; ERC20PresetMinterPauser public token; + ERC20PresetMinterPauser public imxToken; RootERC20Bridge public rootBridge; RootAxelarBridgeAdaptor public axelarAdaptor; MockAxelarGateway public mockAxelarGateway; MockAxelarGasService public axelarGasService; function setUp() public { - (token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) = - integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS, CHILD_ETH_TOKEN); + (imxToken, token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) = + integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS); } /** @@ -95,6 +97,123 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx assertEq(rootBridge.rootTokenToChildToken(address(token)), childToken, "childToken not set"); } + // TODO split into multiple tests + function test_depositETH() public { + uint256 tokenAmount = 300; + string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); + + (, bytes memory predictedPayload) = + setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, tokenAmount, false); + + console2.logBytes(predictedPayload); + + vm.expectEmit(address(axelarAdaptor)); + emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); + vm.expectEmit(address(rootBridge)); + emit NativeEthDeposit( + address(NATIVE_ETH), rootBridge.childETHToken(), address(this), address(this), tokenAmount + ); + + vm.expectCall( + address(axelarAdaptor), + depositFee, + abi.encodeWithSelector(axelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + + vm.expectCall( + address(axelarGasService), + depositFee, + abi.encodeWithSelector( + axelarGasService.payNativeGasForContractCall.selector, + address(axelarAdaptor), + CHILD_CHAIN_NAME, + childBridgeAdaptorString, + predictedPayload, + address(this) + ) + ); + + vm.expectCall( + address(mockAxelarGateway), + 0, + abi.encodeWithSelector( + mockAxelarGateway.callContract.selector, CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload + ) + ); + + uint256 bridgePreBal = address(rootBridge).balance; + + uint256 thisNativePreBal = address(this).balance; + uint256 gasServiceNativePreBal = address(axelarGasService).balance; + + rootBridge.depositETH{value: tokenAmount + depositFee}(tokenAmount); + + // Check that tokens are transferred + assertEq(bridgePreBal + tokenAmount, address(rootBridge).balance, "ETH not transferred to bridge"); + // Check that native asset transferred to gas service + assertEq(thisNativePreBal - (depositFee + tokenAmount), address(this).balance, "ETH not paid from user"); + assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); + } + + // TODO split into multiple tests + function test_depositIMXToken() public { + uint256 tokenAmount = 300; + string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); + + (, bytes memory predictedPayload) = setupDeposit( + ERC20PresetMinterPauser(IMX_TOKEN_ADDRESS), rootBridge, mapTokenFee, depositFee, tokenAmount, false + ); + + vm.expectEmit(address(axelarAdaptor)); + emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); + vm.expectEmit(address(rootBridge)); + emit IMXDeposit(address(IMX_TOKEN_ADDRESS), address(this), address(this), tokenAmount); + + vm.expectCall( + address(axelarAdaptor), + depositFee, + abi.encodeWithSelector(axelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + + vm.expectCall( + address(axelarGasService), + depositFee, + abi.encodeWithSelector( + axelarGasService.payNativeGasForContractCall.selector, + address(axelarAdaptor), + CHILD_CHAIN_NAME, + childBridgeAdaptorString, + predictedPayload, + address(this) + ) + ); + + vm.expectCall( + address(mockAxelarGateway), + 0, + abi.encodeWithSelector( + mockAxelarGateway.callContract.selector, CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload + ) + ); + + uint256 thisPreBal = imxToken.balanceOf(address(this)); + uint256 bridgePreBal = imxToken.balanceOf(address(rootBridge)); + + uint256 thisNativePreBal = address(this).balance; + uint256 gasServiceNativePreBal = address(axelarGasService).balance; + + rootBridge.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN_ADDRESS), tokenAmount); + + // Check that tokens are transferred + assertEq(thisPreBal - tokenAmount, imxToken.balanceOf(address(this)), "Tokens not transferred from user"); + assertEq( + bridgePreBal + tokenAmount, imxToken.balanceOf(address(rootBridge)), "Tokens not transferred to bridge" + ); + // Check that native asset transferred to gas service + assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH not paid from user"); + assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); + } + // TODO split into multiple tests function test_depositToken() public { uint256 tokenAmount = 300; diff --git a/test/unit/child/ChildERC20Bridge.t.sol b/test/unit/child/ChildERC20Bridge.t.sol index 2838caac..f8fb5505 100644 --- a/test/unit/child/ChildERC20Bridge.t.sol +++ b/test/unit/child/ChildERC20Bridge.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.21; import {Test, console2} from "forge-std/Test.sol"; +import {ERC20PresetMinterPauser} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import { @@ -10,6 +11,7 @@ import { IERC20Metadata, IChildERC20BridgeErrors } from "../../../src/child/ChildERC20Bridge.sol"; +import {IChildERC20} from "../../../src/interfaces/child/IChildERC20.sol"; import {ChildERC20} from "../../../src/child/ChildERC20.sol"; import {MockAdaptor} from "../../../src/test/root/MockAdaptor.sol"; import {Utils} from "../../utils.t.sol"; @@ -18,10 +20,11 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B address constant ROOT_BRIDGE = address(3); string public ROOT_BRIDGE_ADAPTOR = Strings.toHexString(address(4)); string constant ROOT_CHAIN_NAME = "test"; - - address constant IMX_TOKEN = address(99); + address constant ROOT_IMX_TOKEN = address(0xccc); + address constant NATIVE_ETH = address(0xeee); ChildERC20 public childTokenTemplate; ChildERC20 public rootToken; + address public childETHToken; ChildERC20Bridge public childBridge; function setUp() public { @@ -32,9 +35,8 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B childTokenTemplate.initialize(address(123), "Test", "TST", 18); childBridge = new ChildERC20Bridge(); - childBridge.initialize( - address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, IMX_TOKEN + address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN ); } @@ -43,12 +45,15 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B assertEq(childBridge.rootERC20BridgeAdaptor(), ROOT_BRIDGE_ADAPTOR, "rootERC20BridgeAdaptor not set"); assertEq(childBridge.childTokenTemplate(), address(childTokenTemplate), "childTokenTemplate not set"); assertEq(childBridge.rootChain(), ROOT_CHAIN_NAME, "rootChain not set"); + assertEq(childBridge.rootIMXToken(), ROOT_IMX_TOKEN, "rootIMXToken not set"); + assertNotEq(childBridge.childETHToken(), address(0), "childETHToken not set"); + assertNotEq(address(childBridge.childETHToken()).code.length, 0, "childETHToken contract empty"); } function test_RevertIfInitializeTwice() public { vm.expectRevert("Initializable: contract is already initialized"); childBridge.initialize( - address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, IMX_TOKEN + address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN ); } @@ -79,13 +84,13 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B function test_RevertIf_InitializeWithAnEmptyBridgeAdaptorString() public { ChildERC20Bridge bridge = new ChildERC20Bridge(); vm.expectRevert(InvalidRootERC20BridgeAdaptor.selector); - bridge.initialize(address(this), "", address(childTokenTemplate), ROOT_CHAIN_NAME, IMX_TOKEN); + bridge.initialize(address(this), "", address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN); } function test_RevertIf_InitializeWithAnEmptyChainNameString() public { ChildERC20Bridge bridge = new ChildERC20Bridge(); vm.expectRevert(InvalidRootChain.selector); - bridge.initialize(address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), "", IMX_TOKEN); + bridge.initialize(address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), "", ROOT_IMX_TOKEN); } function test_onMessageReceive_EmitsTokenMappedEvent() public { @@ -186,11 +191,17 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B } function test_RevertIf_mapTokenCalledWithIMXAddress() public { - bytes memory data = abi.encode(childBridge.MAP_TOKEN_SIG(), IMX_TOKEN, "ImmutableX", "IMX", 18); + bytes memory data = abi.encode(childBridge.MAP_TOKEN_SIG(), ROOT_IMX_TOKEN, "ImmutableX", "IMX", 18); vm.expectRevert(CantMapIMX.selector); childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, data); } + function test_RevertIf_mapTokenCalledWithETHAddress() public { + bytes memory data = abi.encode(childBridge.MAP_TOKEN_SIG(), NATIVE_ETH, "Ethereum", "ETH", 18); + vm.expectRevert(CantMapETH.selector); + childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, data); + } + function test_RevertIf_onMessageReceiveCalledTwice() public { bytes memory data = abi.encode( childBridge.MAP_TOKEN_SIG(), address(rootToken), rootToken.name(), rootToken.symbol(), rootToken.decimals() @@ -219,6 +230,63 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B childBridge.updateBridgeAdaptor(address(0)); } + //Deposit ETH + + function test_onMessageReceive_DepositETH_EmitsETHDepositEvent() public { + address sender = address(100); + address receiver = address(200); + uint256 amount = 1000; + + bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), address(NATIVE_ETH), sender, receiver, amount); + + address predictedChildETHToken = Clones.predictDeterministicAddress( + address(childTokenTemplate), keccak256(abi.encodePacked(NATIVE_ETH)), address(childBridge) + ); + + vm.expectEmit(address(childBridge)); + emit NativeEthDeposit(address(NATIVE_ETH), predictedChildETHToken, sender, receiver, amount); + childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData); + } + + function test_onMessageReceive_DepositETH_TransfersTokensToReceiver() public { + address sender = address(100); + address receiver = address(200); + uint256 amount = 1000; + + bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), address(NATIVE_ETH), sender, receiver, amount); + + address predictedChildETHToken = Clones.predictDeterministicAddress( + address(childTokenTemplate), keccak256(abi.encodePacked(NATIVE_ETH)), address(childBridge) + ); + + uint256 receiverPreBal = ChildERC20(predictedChildETHToken).balanceOf(receiver); + + childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData); + + assertEq( + ChildERC20(predictedChildETHToken).balanceOf(receiver), + receiverPreBal + amount, + "receiver balance not increased" + ); + } + + function test_onMessageReceive_DepositETH_IncreasesTotalSupply() public { + address sender = address(100); + address receiver = address(200); + uint256 amount = 1000; + + bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), address(NATIVE_ETH), sender, receiver, amount); + + address predictedChildETHToken = Clones.predictDeterministicAddress( + address(childTokenTemplate), keccak256(abi.encodePacked(NATIVE_ETH)), address(childBridge) + ); + uint256 totalSupplyPre = ChildERC20(predictedChildETHToken).totalSupply(); + + childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData); + + assertEq(ChildERC20(predictedChildETHToken).totalSupply(), totalSupplyPre + amount, "totalSupply not increased"); + } + //Deposit function test_onMessageReceive_DepositIMX_EmitsIMXDepositEvent() public { @@ -229,10 +297,10 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B address receiver = address(200); uint256 amount = 1 ether; - bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), IMX_TOKEN, sender, receiver, amount); + bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), ROOT_IMX_TOKEN, sender, receiver, amount); vm.expectEmit(address(childBridge)); - emit IMXDeposit(IMX_TOKEN, sender, receiver, amount); + emit IMXDeposit(ROOT_IMX_TOKEN, sender, receiver, amount); childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData); } @@ -244,7 +312,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B address receiver = address(200); uint256 amount = 1 ether; - bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), IMX_TOKEN, sender, receiver, amount); + bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), ROOT_IMX_TOKEN, sender, receiver, amount); childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData); @@ -260,7 +328,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B address receiver = address(200); uint256 amount = 10 ether; - bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), IMX_TOKEN, sender, receiver, amount); + bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), ROOT_IMX_TOKEN, sender, receiver, amount); vm.expectRevert("Address: insufficient balance"); childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData); @@ -358,8 +426,8 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B address rootAddress = address(0x123); { - // Slot is 6 because of the Ownable, Initializable contracts coming first. - uint256 rootTokenToChildTokenMappingSlot = 6; + // Slot is 2 because of the Ownable, Initializable contracts coming first. + uint256 rootTokenToChildTokenMappingSlot = 2; address childAddress = address(444444); bytes32 slot = getMappingStorageSlotFor(rootAddress, rootTokenToChildTokenMappingSlot); bytes32 data = bytes32(uint256(uint160(childAddress))); diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index c6319b85..cef2ad5d 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -20,9 +20,8 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid address constant CHILD_BRIDGE = address(3); address constant CHILD_BRIDGE_ADAPTOR = address(4); string constant CHILD_CHAIN_NAME = "test"; - address constant IMX_TOKEN = address(99); - address constant CHILD_ETH_TOKEN = address(0xddd); - address constant NATIVE_TOKEN = address(0xeee); + address constant IMX_TOKEN = address(0xccc); + address constant NATIVE_ETH = address(0xeee); uint256 constant mapTokenFee = 300; uint256 constant depositFee = 200; @@ -43,9 +42,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid mockAxelarAdaptor = new MockAdaptor(); // The specific ERC20 token template does not matter for these unit tests - rootBridge.initialize( - address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, CHILD_ETH_TOKEN - ); + rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN); } /** @@ -60,51 +57,43 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIfInitializeTwice() public { vm.expectRevert("Initializable: contract is already initialized"); - rootBridge.initialize( - address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, CHILD_ETH_TOKEN - ); + rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN); } function test_RevertIf_InitializeWithAZeroAddressRootAdapter() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(0), address(1), address(1), address(1), address(1), address(1)); + bridge.initialize(address(0), address(1), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressChildBridge() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(0), address(1), address(1), address(1), address(1)); + bridge.initialize(address(1), address(0), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressChildAdapter() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(0), address(1), address(1), address(1)); + bridge.initialize(address(1), address(1), address(0), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressTokenTemplate() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(1), address(0), address(1), address(1)); + bridge.initialize(address(1), address(1), address(1), address(0), address(1)); } function test_RevertIf_InitializeWithAZeroAddressIMXToken() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(1), address(1), address(0), address(1)); - } - - function test_RevertIf_InitializeWithAZeroAddressETHToken() public { - RootERC20Bridge bridge = new RootERC20Bridge(); - vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(1), address(1), address(1), address(0)); + bridge.initialize(address(1), address(1), address(1), address(1), address(0)); } function test_RevertIf_InitializeWithAZeroAddressAll() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(0), address(0), address(0), address(0), address(0), address(0)); + bridge.initialize(address(0), address(0), address(0), address(0), address(0)); } /** @@ -179,6 +168,11 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.mapToken{value: 300}(IERC20Metadata(IMX_TOKEN)); } + function test_RevertIf_mapTokenCalledWithETHAddress() public { + vm.expectRevert(CantMapETH.selector); + rootBridge.mapToken{value: 300}(IERC20Metadata(NATIVE_ETH)); + } + function test_updateRootBridgeAdaptor() public { address newAdaptorAddress = address(0x11111); @@ -205,7 +199,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositETHCallsSendMessage() public { uint256 amount = 1000; (, bytes memory predictedPayload) = - setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectCall( address(mockAxelarAdaptor), @@ -216,18 +210,18 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.depositETH{value: amount + depositFee}(amount); } - function test_depositETHEmitsNativeDepositEvent() public { + function test_depositETHEmitsNativeEthDepositEvent() public { uint256 amount = 1000; - setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectEmit(); - emit NativeDeposit(NATIVE_TOKEN, CHILD_ETH_TOKEN, address(this), address(this), amount); + emit NativeEthDeposit(NATIVE_ETH, rootBridge.childETHToken(), address(this), address(this), amount); rootBridge.depositETH{value: amount + depositFee}(amount); } function test_RevertIf_depositETHInsufficientValue() public { uint256 amount = 1000; - setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(InsufficientValue.selector); rootBridge.depositETH{value: (amount / 2) + depositFee}(amount); @@ -241,7 +235,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 1000; address receiver = address(12345); (, bytes memory predictedPayload) = setupDepositTo( - ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, receiver, false + ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, receiver, false ); vm.expectCall( address(mockAxelarAdaptor), @@ -252,15 +246,15 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.depositToETH{value: amount + depositFee}(receiver, amount); } - function test_depositToETHEmitsNativeDepositEvent() public { + function test_depositToETHEmitsNativeEthDepositEvent() public { uint256 amount = 1000; address receiver = address(12345); setupDepositTo( - ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, receiver, false + ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, receiver, false ); vm.expectEmit(); - emit NativeDeposit(NATIVE_TOKEN, CHILD_ETH_TOKEN, address(this), receiver, amount); + emit NativeEthDeposit(NATIVE_ETH, rootBridge.childETHToken(), address(this), receiver, amount); rootBridge.depositToETH{value: amount + depositFee}(receiver, amount); } @@ -268,7 +262,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 1000; address receiver = address(12345); setupDepositTo( - ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, receiver, false + ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, receiver, false ); vm.expectRevert(InsufficientValue.selector); @@ -281,7 +275,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositETHAmountIsZero() public { uint256 amount = 0; - setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(ZeroAmount.selector); rootBridge.depositETH{value: amount + depositFee}(amount); @@ -291,7 +285,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 0; address receiver = address(12345); - setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(ZeroAmount.selector); rootBridge.depositToETH{value: amount + depositFee}(receiver, amount); @@ -299,7 +293,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositAmountIsZero() public { uint256 amount = 0; - setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(ZeroAmount.selector); rootBridge.deposit{value: depositFee}(token, amount); @@ -308,7 +302,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositToAmountIsZero() public { uint256 amount = 0; address receiver = address(12345); - setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(ZeroAmount.selector); rootBridge.depositTo{value: depositFee}(token, receiver, amount); diff --git a/test/utils.t.sol b/test/utils.t.sol index a7373846..77585442 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -16,11 +16,11 @@ contract Utils is Test { address childBridge, address childBridgeAdaptor, string memory childBridgeName, - address imxTokenAddress, - address ethTokenAddress + address imxTokenAddress ) public returns ( + ERC20PresetMinterPauser imxToken, ERC20PresetMinterPauser token, RootERC20Bridge rootBridge, RootAxelarBridgeAdaptor axelarAdaptor, @@ -31,6 +31,10 @@ contract Utils is Test { token = new ERC20PresetMinterPauser("Test", "TST"); token.mint(address(this), 1000000 ether); + deployCodeTo("ERC20PresetMinterPauser.sol", abi.encode("ImmutableX", "IMX"), imxTokenAddress); + imxToken = ERC20PresetMinterPauser(imxTokenAddress); + imxToken.mint(address(this), 1000000 ether); + rootBridge = new RootERC20Bridge(); mockAxelarGateway = new MockAxelarGateway(); axelarGasService = new MockAxelarGasService(); @@ -42,9 +46,7 @@ contract Utils is Test { address(axelarGasService) ); - rootBridge.initialize( - address(axelarAdaptor), childBridge, childBridgeAdaptor, address(token), imxTokenAddress, ethTokenAddress - ); + rootBridge.initialize(address(axelarAdaptor), childBridge, childBridgeAdaptor, address(token), imxTokenAddress); axelarAdaptor.setChildBridgeAdaptor(); } @@ -84,6 +86,7 @@ contract Utils is Test { if (saveTokenMapping) { childToken = rootBridge.mapToken{value: mapTokenFee}(token); } + if (address(token) == address(0xeee)) { vm.deal(to, tokenAmount + depositFee); } else {