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/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..d5804b89 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,28 @@ +name: Lint Check + +on: [push] + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Forge Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run Forge fmt --check + run: | + forge fmt --check + id: fmt 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 0878c6b9..b77572be 100644 --- a/script/InitializeRootContracts.s.sol +++ b/script/InitializeRootContracts.s.sol @@ -27,7 +27,6 @@ contract InitializeRootContracts is Script { string[] memory checksumInputs = Utils.getChecksumInputs(childBridgeAdaptor); bytes memory checksumOutput = vm.ffi(checksumInputs); string memory childBridgeAdaptorChecksum = string(Utils.removeZeroByteValues(checksumOutput)); - address childETHToken = vm.envAddress("CHILD_ETH_ADDRESS"); /** * INITIALIZE ROOT CHAIN CONTRACTS */ @@ -39,8 +38,7 @@ contract InitializeRootContracts is Script { childERC20Bridge, childBridgeAdaptorChecksum, rootChainChildTokenTemplate, - rootIMXToken, - childETHToken + rootIMXToken ); rootBridgeAdaptor.setChildBridgeAdaptor(); diff --git a/src/child/ChildERC20Bridge.sol b/src/child/ChildERC20Bridge.sol index aefbc582..dc40ed38 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); } /** @@ -121,10 +133,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(); } @@ -148,11 +164,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(); } @@ -160,7 +181,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 4181cc25..d3f858ba 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); /// @notice Error when the given child chain bridge adaptor is invalid. diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index 1c95f010..d241cd53 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 (As a checksummed string). * @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,12 +63,12 @@ contract RootERC20Bridge is address newChildERC20Bridge, string memory newChildBridgeAdaptor, address newChildTokenTemplate, - address newRootIMXToken, - address newChildETHToken + address newRootIMXToken ) public initializer { if ( newRootBridgeAdaptor == address(0) || newChildERC20Bridge == address(0) - || newChildTokenTemplate == address(0) || newRootIMXToken == address(0) || newChildETHToken == address(0) + || newChildTokenTemplate == address(0) + || newRootIMXToken == address(0) ) { revert ZeroAddress(); } @@ -77,7 +78,9 @@ contract RootERC20Bridge is childERC20Bridge = newChildERC20Bridge; childTokenTemplate = newChildTokenTemplate; rootIMXToken = newRootIMXToken; - childETHToken = newChildETHToken; + IChildERC20 clonedETHToken = + IChildERC20(Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(NATIVE_ETH)))); + childETHToken = address(clonedETHToken); rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor); childBridgeAdaptor = newChildBridgeAdaptor; } @@ -110,7 +113,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) { @@ -149,6 +152,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(); } @@ -186,7 +194,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)) { @@ -206,8 +214,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 fdee9777..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,12 +97,127 @@ 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; string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); - // (address childToken, bytes memory predictedPayload) = - // setupDeposit(token, rootBridge, gasPrice, tokenAmount, true); (address childToken, bytes memory predictedPayload) = setupDeposit(token, rootBridge, mapTokenFee, depositFee, tokenAmount, true); 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 d12653ea..9f74de0c 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -21,9 +21,8 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid address constant CHILD_BRIDGE_ADAPTOR = address(4); string CHILD_BRIDGE_ADAPTOR_STRING = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); 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; @@ -49,8 +48,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR_STRING, address(token), - IMX_TOKEN, - CHILD_ETH_TOKEN + IMX_TOKEN ); } @@ -71,51 +69,50 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR_STRING, address(token), - IMX_TOKEN, - CHILD_ETH_TOKEN + IMX_TOKEN ); } function test_RevertIf_InitializeWithAZeroAddressRootAdapter() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(0), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(1), address(1)); + bridge.initialize(address(0), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressChildBridge() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(0), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(1), address(1)); + bridge.initialize(address(1), address(0), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(1)); } function test_RevertIf_InitializeWithEmptyChildAdapter() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(InvalidChildERC20BridgeAdaptor.selector); - bridge.initialize(address(1), address(1), "", address(1), address(1), address(1)); + bridge.initialize(address(1), address(1), "", address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressTokenTemplate() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(0), address(1), address(1)); + bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(0), address(1)); } function test_RevertIf_InitializeWithAZeroAddressIMXToken() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(0), address(1)); + bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(0), address(1)); } function test_RevertIf_InitializeWithAZeroAddressETHToken() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(1), address(0)); + bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, 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)); + bridge.initialize(address(0), address(0), "", address(0), address(0)); } /** @@ -190,6 +187,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); @@ -216,7 +218,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), @@ -227,18 +229,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); @@ -252,7 +254,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), @@ -263,15 +265,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); } @@ -279,7 +281,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); @@ -292,7 +294,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); @@ -302,7 +304,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); @@ -310,7 +312,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); @@ -319,7 +321,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 00f94590..a0a1f2e3 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -17,11 +17,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, @@ -32,6 +32,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(); @@ -48,8 +52,7 @@ contract Utils is Test { childBridge, Strings.toHexString(childBridgeAdaptor), address(token), - imxTokenAddress, - ethTokenAddress + imxTokenAddress ); axelarAdaptor.setChildBridgeAdaptor(); } @@ -90,6 +93,7 @@ contract Utils is Test { if (saveTokenMapping) { childToken = rootBridge.mapToken{value: mapTokenFee}(token); } + if (address(token) == address(0xeee)) { vm.deal(to, tokenAmount + depositFee); } else {