diff --git a/.env.sample b/.env.sample index d5f783c6..e7dde74c 100644 --- a/.env.sample +++ b/.env.sample @@ -7,6 +7,8 @@ ROOT_PRIVATE_KEY= ROOT_GATEWAY_ADDRESS= ROOT_GAS_SERVICE_ADDRESS= ROOT_CHAIN_NAME= +CHILD_CHAIN_NAME= +CHILD_WIMX_ADDRESS= ROOT_IMX_ADDRESS= ROOT_WETH_ADDRESS= INITIAL_IMX_CUMULATIVE_DEPOSIT_LIMIT= # 0 for unlimited diff --git a/script/DeployChildContracts.s.sol b/script/DeployChildContracts.s.sol index 9e63d2cc..9260078a 100644 --- a/script/DeployChildContracts.s.sol +++ b/script/DeployChildContracts.s.sol @@ -22,21 +22,14 @@ contract DeployChildContracts is Script { vm.createSelectFork(childRpcUrl); vm.startBroadcast(deployerPrivateKey); + WIMX wrappedIMX = new WIMX(); + ProxyAdmin proxyAdmin = new ProxyAdmin(); ChildERC20 childTokenTemplate = new ChildERC20(); childTokenTemplate.initialize(address(123), "TEMPLATE", "TPT", 18); - IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ - defaultAdmin: address(0x1111), - pauser: address(0x2222), - unpauser: address(0x3333), - variableManager: address(0x4444), - adaptorManager: address(0x5555) - }); - ChildERC20Bridge childERC20BridgeImplementation = new ChildERC20Bridge(); - childERC20BridgeImplementation.initialize(roles, address(1), "0x123", address(1), "root", address(1)); TransparentUpgradeableProxy childERC20BridgeProxy = new TransparentUpgradeableProxy( address(childERC20BridgeImplementation), @@ -54,8 +47,6 @@ contract DeployChildContracts is Script { "" ); - WIMX wrappedIMX = new WIMX(); - vm.stopBroadcast(); console2.log("====CHILD ADDRESSES===="); diff --git a/script/InitializeChildContracts.s.sol b/script/InitializeChildContracts.s.sol index 604b52c3..b24b6de2 100644 --- a/script/InitializeChildContracts.s.sol +++ b/script/InitializeChildContracts.s.sol @@ -19,6 +19,7 @@ struct InitializeChildContractsParams { address rootERC20BridgeAdaptor; string rootChainName; address rootIMXToken; + address wIMXToken; string childRpcUrl; uint256 deployerPrivateKey; address childGasService; @@ -30,12 +31,13 @@ contract InitializeChildContracts is Script { childAdminAddress: vm.envAddress("CHILD_ADMIN_ADDRESS"), childPauserAddress: vm.envAddress("CHILD_PAUSER_ADDRESS"), childUnpauserAddress: vm.envAddress("CHILD_UNPAUSER_ADDRESS"), - childERC20Bridge: ChildERC20Bridge(vm.envAddress("CHILD_ERC20_BRIDGE")), + childERC20Bridge: ChildERC20Bridge(payable(vm.envAddress("CHILD_ERC20_BRIDGE"))), childAxelarBridgeAdaptor: ChildAxelarBridgeAdaptor(vm.envAddress("CHILD_BRIDGE_ADAPTOR")), childTokenTemplate: vm.envAddress("CHILDCHAIN_CHILD_TOKEN_TEMPLATE"), rootERC20BridgeAdaptor: vm.envAddress("ROOT_BRIDGE_ADAPTOR"), rootChainName: vm.envString("ROOT_CHAIN_NAME"), rootIMXToken: vm.envAddress("ROOT_IMX_ADDRESS"), + wIMXToken: vm.envAddress("CHILD_WIMX_ADDRESS"), childRpcUrl: vm.envString("CHILD_RPC_URL"), deployerPrivateKey: vm.envUint("CHILD_PRIVATE_KEY"), childGasService: vm.envAddress("CHILD_GAS_SERVICE_ADDRESS") @@ -65,7 +67,8 @@ contract InitializeChildContracts is Script { rootBridgeAdaptorString, params.childTokenTemplate, params.rootChainName, - params.rootIMXToken + params.rootIMXToken, + params.wIMXToken ); params.childAxelarBridgeAdaptor.initialize( diff --git a/src/child/ChildERC20Bridge.sol b/src/child/ChildERC20Bridge.sol index a6751136..944f4a68 100644 --- a/src/child/ChildERC20Bridge.sol +++ b/src/child/ChildERC20Bridge.sol @@ -14,6 +14,7 @@ import { } from "../interfaces/child/IChildERC20Bridge.sol"; import {IChildERC20BridgeAdaptor} from "../interfaces/child/IChildERC20BridgeAdaptor.sol"; import {IChildERC20} from "../interfaces/child/IChildERC20.sol"; +import {IWIMX} from "../interfaces/child/IWIMX.sol"; /** * @notice RootERC20Bridge is a bridge that allows ERC20 tokens to be transferred from the root chain to the child chain. @@ -59,7 +60,19 @@ contract ChildERC20Bridge is /// @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; + IChildERC20 public childETHToken; + /// @dev The address of the wrapped IMX token on L2. + address public wIMXToken; + + /** + * @notice Fallback function on recieving native IMX from WIMX contract. + */ + receive() external payable { + // Revert if sender is not the WIMX token address + if (msg.sender != wIMXToken) { + revert NonWrappedNativeTransfer(); + } + } /** * @notice Initialization function for ChildERC20Bridge. @@ -69,6 +82,7 @@ contract ChildERC20Bridge is * @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 newRootIMXToken Address of ECR20 IMX on the root chain. + * @param newWIMXToken Address of wrapped IMX on the child chain. * @dev Can only be called once. */ function initialize( @@ -77,12 +91,14 @@ contract ChildERC20Bridge is string memory newRootERC20BridgeAdaptor, address newChildTokenTemplate, string memory newRootChain, - address newRootIMXToken + address newRootIMXToken, + address newWIMXToken ) public initializer { if ( newBridgeAdaptor == address(0) || newChildTokenTemplate == address(0) || newRootIMXToken == address(0) || newRoles.defaultAdmin == address(0) || newRoles.pauser == address(0) || newRoles.unpauser == address(0) || newRoles.variableManager == address(0) || newRoles.adaptorManager == address(0) + || newWIMXToken == address(0) ) { revert ZeroAddress(); } @@ -108,17 +124,31 @@ contract ChildERC20Bridge is bridgeAdaptor = IChildERC20BridgeAdaptor(newBridgeAdaptor); rootChain = newRootChain; rootIMXToken = newRootIMXToken; + wIMXToken = newWIMXToken; + // NOTE: how will this behave in an updgrade scenario? + // e.g. this clone may already be deployed and we could deploy to the same address if the salt is the same. + // Clone childERC20 for native eth IChildERC20 clonedETHToken = IChildERC20(Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(NATIVE_ETH)))); + // Initialize clonedETHToken.initialize(NATIVE_ETH, "Ethereum", "ETH", 18); - childETHToken = address(clonedETHToken); + childETHToken = clonedETHToken; + } + + /** + * @inheritdoc IChildERC20Bridge + */ + function updateBridgeAdaptor(address newBridgeAdaptor) external override onlyRole(ADAPTOR_MANAGER_ROLE) { + if (newBridgeAdaptor == address(0)) { + revert ZeroAddress(); + } + emit BridgeAdaptorUpdated(address(bridgeAdaptor), newBridgeAdaptor); + bridgeAdaptor = IChildERC20BridgeAdaptor(newBridgeAdaptor); } /** * @inheritdoc IChildERC20Bridge - * @dev This is only callable by the child chain bridge adaptor. - * @dev Validates `sourceAddress` is the root chain's bridgeAdaptor. */ function onMessageReceive(string calldata messageSourceChain, string calldata sourceAddress, bytes calldata data) external @@ -148,51 +178,105 @@ contract ChildERC20Bridge is } } + /** + * @inheritdoc IChildERC20Bridge + */ function withdraw(IChildERC20 childToken, uint256 amount) external payable { - _withdraw(childToken, msg.sender, amount); + _withdraw(address(childToken), msg.sender, amount); } + /** + * @inheritdoc IChildERC20Bridge + */ function withdrawTo(IChildERC20 childToken, address receiver, uint256 amount) external payable { - _withdraw(childToken, receiver, amount); + _withdraw(address(childToken), receiver, amount); } + /** + * @inheritdoc IChildERC20Bridge + */ function withdrawIMX(uint256 amount) external payable { - _withdrawIMX(msg.sender, amount); + _withdraw(NATIVE_IMX, msg.sender, amount); } + /** + * @inheritdoc IChildERC20Bridge + */ function withdrawIMXTo(address receiver, uint256 amount) external payable { - _withdrawIMX(receiver, amount); + _withdraw(NATIVE_IMX, receiver, amount); } - function _withdrawIMX(address receiver, uint256 amount) private { - if (msg.value < amount) { - revert InsufficientValue(); - } - - uint256 expectedBalance = address(this).balance - (msg.value - amount); - - _withdraw(IChildERC20(NATIVE_IMX), receiver, amount); + /** + * @inheritdoc IChildERC20Bridge + */ + function withdrawWIMX(uint256 amount) external payable { + _withdraw(wIMXToken, msg.sender, amount); + } - if (address(this).balance != expectedBalance) { - revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance); - } + /** + * @inheritdoc IChildERC20Bridge + */ + function withdrawWIMXTo(address receiver, uint256 amount) external payable { + _withdraw(wIMXToken, receiver, amount); } - function _withdraw(IChildERC20 childToken, address receiver, uint256 amount) private { - if (address(childToken) == address(0)) { + /** + * @notice Private function to handle withdrawal process for all ERC20 and native token types. + * @param childTokenAddr The address of the child token to withdraw. + * @param receiver The address to withdraw the tokens to. + * @param amount The amount of tokens to withdraw. + * + * Requirements: + * + * - `childTokenAddr` must not be the zero address. + * - `reciever` must not be the zero address. + * - `amount` must be greater than zero. + * - `msg.value` must be greater than zero. + * - `childToken` must exist. + * - `childToken` must be mapped. + * - `childToken` must have a the bridge set. + */ + function _withdraw(address childTokenAddr, address receiver, uint256 amount) private { + if (childTokenAddr == address(0) || receiver == address(0)) { revert ZeroAddress(); } if (amount == 0) { revert ZeroAmount(); } + if (msg.value == 0) { + revert NoGas(); + } address rootToken; uint256 feeAmount = msg.value; + if (childTokenAddr == NATIVE_IMX) { + // Native IMX. + if (msg.value < amount) { + revert InsufficientValue(); + } - if (address(childToken) == NATIVE_IMX) { feeAmount = msg.value - amount; + rootToken = rootIMXToken; + } else if (childTokenAddr == wIMXToken) { + // Wrapped IMX. + // Transfer and unwrap IMX. + uint256 expectedBalance = address(this).balance + amount; + + IWIMX wIMX = IWIMX(wIMXToken); + if (!wIMX.transferFrom(msg.sender, address(this), amount)) { + revert TransferWIMXFailed(); + } + wIMX.withdraw(amount); + + if (address(this).balance != expectedBalance) { + revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance); + } + rootToken = rootIMXToken; } else { + // Other ERC20 Tokens + IChildERC20 childToken = IChildERC20(childTokenAddr); + if (address(childToken).code.length == 0) { revert EmptyTokenContract(); } @@ -217,22 +301,32 @@ contract ChildERC20Bridge is } } - // TODO Should we enforce receiver != 0? old poly contracts don't - // Encode the message payload bytes memory payload = abi.encode(WITHDRAW_SIG, rootToken, msg.sender, receiver, amount); // Send the message to the bridge adaptor and up to root chain - bridgeAdaptor.sendMessage{value: feeAmount}(payload, msg.sender); - if (address(childToken) == NATIVE_IMX) { + if (childTokenAddr == NATIVE_IMX) { emit ChildChainNativeIMXWithdraw(rootToken, msg.sender, receiver, amount); + } else if (childTokenAddr == wIMXToken) { + emit ChildChainWrappedIMXWithdraw(rootToken, msg.sender, receiver, amount); } else { - emit ChildChainERC20Withdraw(rootToken, address(childToken), msg.sender, receiver, amount); + emit ChildChainERC20Withdraw(rootToken, childTokenAddr, msg.sender, receiver, amount); } } + /** + * @notice Private function to handle mapping of root ERC20 tokens to child ERC20 tokens. + * @param data The data payload of the message. + * + * Requirements: + * + * - `rootToken` must not be the zero address. + * - `rootToken` must not be the root IMX token. + * - `rootToken` must not be native ETH. + * - `rootToken` must not already be mapped. + */ function _mapToken(bytes calldata data) private { (, address rootToken, string memory name, string memory symbol, uint8 decimals) = abi.decode(data, (bytes32, address, string, string, uint8)); @@ -253,15 +347,30 @@ contract ChildERC20Bridge is revert AlreadyMapped(); } + // Deploy child chain token IChildERC20 childToken = IChildERC20(Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(rootToken)))); - + // Map token rootTokenToChildToken[rootToken] = address(childToken); + + // Intialize token childToken.initialize(rootToken, name, symbol, decimals); emit L2TokenMapped(rootToken, address(childToken)); } + /** + * @notice Private function to handle depositing of ERC20 and native tokens to the child chain. + * @param data The data payload of the message. + * + * Requirements: + * + * - `rootToken` must not be the zero address. + * - `receiver` must not be the zero address. + * - `childToken` must be mapped. + * - `childToken` must exist. + * + */ function _deposit(bytes calldata data) private { (address rootToken, address sender, address receiver, uint256 amount) = abi.decode(data, (address, address, address, uint256)); @@ -270,14 +379,13 @@ contract ChildERC20Bridge is revert ZeroAddress(); } - address childToken; - - if (address(rootToken) != rootIMXToken) { - if (address(rootToken) == NATIVE_ETH) { + IChildERC20 childToken; + if (rootToken != rootIMXToken) { + if (rootToken == NATIVE_ETH) { childToken = childETHToken; } else { - childToken = rootTokenToChildToken[address(rootToken)]; - if (childToken == address(0)) { + childToken = IChildERC20(rootTokenToChildToken[rootToken]); + if (address(childToken) == address(0)) { revert NotMapped(); } } @@ -286,31 +394,18 @@ contract ChildERC20Bridge is revert EmptyTokenContract(); } - if (!IChildERC20(childToken).mint(receiver, amount)) { + if (!childToken.mint(receiver, amount)) { revert MintFailed(); } - if (address(rootToken) == NATIVE_ETH) { - emit NativeEthDeposit(address(rootToken), childToken, sender, receiver, amount); + if (rootToken == NATIVE_ETH) { + emit NativeEthDeposit(rootToken, address(childToken), sender, receiver, amount); } else { - emit ChildChainERC20Deposit(address(rootToken), childToken, sender, receiver, amount); + emit ChildChainERC20Deposit(rootToken, address(childToken), sender, receiver, amount); } } else { Address.sendValue(payable(receiver), amount); - emit IMXDeposit(address(rootToken), sender, receiver, amount); - } - } - - /** - * @inheritdoc IChildERC20Bridge - */ - function updateBridgeAdaptor(address newBridgeAdaptor) external override { - if (!(hasRole(ADAPTOR_MANAGER_ROLE, msg.sender))) { - revert NotVariableManager(msg.sender); + emit IMXDeposit(rootToken, sender, receiver, amount); } - if (newBridgeAdaptor == address(0)) { - revert ZeroAddress(); - } - bridgeAdaptor = IChildERC20BridgeAdaptor(newBridgeAdaptor); } } diff --git a/src/interfaces/child/IChildERC20Bridge.sol b/src/interfaces/child/IChildERC20Bridge.sol index 39869f84..0a49556b 100644 --- a/src/interfaces/child/IChildERC20Bridge.sol +++ b/src/interfaces/child/IChildERC20Bridge.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IChildERC20} from "./IChildERC20.sol"; interface IChildERC20Bridge { struct InitializationRoles { @@ -27,12 +28,53 @@ interface IChildERC20Bridge { * @param newBridgeAdaptor The new child chain bridge adaptor address. */ function updateBridgeAdaptor(address newBridgeAdaptor) external; + + /** + * @notice Withdraws `amount` of `childToken` to `msg.sender` on the rootchain. + * @param childToken The address of the child token to withdraw. + * @param amount The amount of tokens to withdraw. + */ + function withdraw(IChildERC20 childToken, uint256 amount) external payable; + + /** + * @notice Withdraws `amount` of `childToken` to `receiver` on the rootchain. + * @param childToken The address of the child token to withdraw. + * @param receiver The address to withdraw the tokens to. + * @param amount The amount of tokens to withdraw. + */ + function withdrawTo(IChildERC20 childToken, address receiver, uint256 amount) external payable; + + /** + * @notice Withdraws `amount` of IMX to `msg.sender` on the rootchain. + * @param amount The amount of IMX to withdraw. + */ + function withdrawIMX(uint256 amount) external payable; + + /** + * @notice Withdraws `amount` of IMX to `receiver` on the rootchain. + * @param receiver The address to withdraw the IMX to. + * @param amount The amount of IMX to withdraw. + */ + function withdrawIMXTo(address receiver, uint256 amount) external payable; + + /** + * @notice Withdraws `amount` of wrapped IMX to `msg.sender` on the rootchain. + * @param amount The amount of wrapped IMX to withdraw. + */ + function withdrawWIMX(uint256 amount) external payable; + + /** + * @notice Withdraws `amount` of wrapped IMX to `receiver` on the rootchain. + * @param receiver The address to withdraw the wrapped IMX to. + * @param amount The amount of wrapped IMX to withdraw. + */ + function withdrawWIMXTo(address receiver, uint256 amount) external payable; } interface IChildERC20BridgeEvents { /// @notice Emitted when a map token message is received from the root chain and executed successfully. event L2TokenMapped(address rootToken, address childToken); - + /// @notice Emitted when a child chain ERC20 token withdrawal is initiated. event ChildChainERC20Withdraw( address indexed rootToken, address indexed childToken, @@ -40,10 +82,15 @@ interface IChildERC20BridgeEvents { address indexed receiver, uint256 amount ); + /// @notice Emitted when a child chain native IMX token withdrawal is initiated. event ChildChainNativeIMXWithdraw( - address indexed rootToken, address depositor, address indexed receiver, uint256 amount + address indexed rootToken, address indexed depositor, address indexed receiver, uint256 amount ); - + /// @notice Emitted when a child chain wrapped IMX withdrawal is initiated. + event ChildChainWrappedIMXWithdraw( + address indexed rootToken, address indexed depositor, address indexed receiver, uint256 amount + ); + /// @notice Emitted when a root chain ERC20 deposit is completed on the child chain. event ChildChainERC20Deposit( address indexed rootToken, address indexed childToken, @@ -51,7 +98,9 @@ interface IChildERC20BridgeEvents { address indexed receiver, uint256 amount ); - event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount); + /// @notice Emitted when a root chain IMX deposit is completed on the child chain. + event IMXDeposit(address indexed rootToken, address indexed depositor, address indexed receiver, uint256 amount); + /// @notice Emitted when a root chain ETH deposit is completed on the child chain. event NativeEthDeposit( address indexed rootToken, address indexed childToken, @@ -59,16 +108,18 @@ interface IChildERC20BridgeEvents { address indexed receiver, uint256 amount ); + /// @notice Emitted when a new bridge adaptor is set. + event BridgeAdaptorUpdated(address oldBridgeAdaptor, address newBridgeAdaptor); } // TODO add parameters to errors if it makes sense interface IChildERC20BridgeErrors { - /// @notice Error when the caller is not the variable manager role. - error NotVariableManager(address caller); /// @notice Error when the amount requested is less than the value sent. error InsufficientValue(); /// @notice Error when the withdrawal amount is zero error ZeroAmount(); + /// @notice Error when a message is sent with no gas payment. + error NoGas(); /// @notice Error when the contract to mint had no bytecode. error EmptyTokenContract(); /// @notice Error when the mint operation failed. @@ -103,6 +154,10 @@ interface IChildERC20BridgeErrors { error IncorrectBridgeAddress(); /// @notice Error when a call to the given child token's `burn` function fails. error BurnFailed(); + /// @notice Error when a call to WIMX token's `transferFrom` fails. + error TransferWIMXFailed(); /// @notice Error when token balance invariant check fails. error BalanceInvariantCheckFailed(uint256 actualBalance, uint256 expectedBalance); + /// @notice Error when native transfer is sent to contract from non wrapped-token address. + error NonWrappedNativeTransfer(); } diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index 1c0b286d..b26cb9f4 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -91,14 +91,14 @@ interface IRootERC20BridgeEvents { } interface IRootERC20BridgeErrors { - /// @notice Error when the caller is not the variable manager role. - error NotVariableManager(address caller); /// @notice Error when the amount requested is less than the value sent. error InsufficientValue(); /// @notice Error when there is no gas payment received. error ZeroAmount(); /// @notice Error when a zero address is given when not valid. error ZeroAddress(); + /// @notice Error when a message is sent with no gas payment. + error NoGas(); /// @notice Error when the child chain name is invalid. error InvalidChildChain(); /// @notice Error when a token is already mapped. @@ -127,4 +127,6 @@ interface IRootERC20BridgeErrors { error ImxDepositLimitExceeded(); /// @notice Error when the IMX deposit limit is set below the amount of IMX already deposited error ImxDepositLimitTooLow(); + /// @notice Error when native transfer is sent to contract from non wrapped-token address. + error NonWrappedNativeTransfer(); } diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index bd23425c..4f8fa784 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -135,11 +135,7 @@ contract RootERC20Bridge is * @param newRootBridgeAdaptor Address of new root bridge adaptor. * @dev Can only be called by ADAPTOR_MANAGER_ROLE. */ - function updateRootBridgeAdaptor(address newRootBridgeAdaptor) external { - if (!hasRole(ADAPTOR_MANAGER_ROLE, msg.sender)) { - revert NotVariableManager(msg.sender); - } - + function updateRootBridgeAdaptor(address newRootBridgeAdaptor) external onlyRole(ADAPTOR_MANAGER_ROLE) { if (newRootBridgeAdaptor == address(0)) { revert ZeroAddress(); } @@ -155,11 +151,10 @@ contract RootERC20Bridge is * @dev Can only be called by VARIABLE_MANAGER_ROLE. * @dev The limit can decrease, but it can never decrease to below the contract's IMX balance. */ - function updateImxCumulativeDepositLimit(uint256 newImxCumulativeDepositLimit) external { - if (!hasRole(VARIABLE_MANAGER_ROLE, msg.sender)) { - revert NotVariableManager(msg.sender); - } - + function updateImxCumulativeDepositLimit(uint256 newImxCumulativeDepositLimit) + external + onlyRole(VARIABLE_MANAGER_ROLE) + { if ( newImxCumulativeDepositLimit != UNLIMITED_DEPOSIT && newImxCumulativeDepositLimit < IERC20Metadata(rootIMXToken).balanceOf(address(this)) @@ -173,7 +168,12 @@ contract RootERC20Bridge is /** * @dev method to receive the ETH back from the WETH contract when it is unwrapped */ - receive() external payable {} + receive() external payable { + // Revert if sender is not the WETH token address + if (msg.sender != rootWETHToken) { + revert NonWrappedNativeTransfer(); + } + } /** * @inheritdoc IRootERC20Bridge @@ -288,6 +288,9 @@ contract RootERC20Bridge is } function _mapToken(IERC20Metadata rootToken) private returns (address) { + if (msg.value == 0) { + revert NoGas(); + } if (address(rootToken) == address(0)) { revert ZeroAddress(); } @@ -330,6 +333,9 @@ contract RootERC20Bridge is if (amount == 0) { revert ZeroAmount(); } + if (msg.value == 0) { + revert NoGas(); + } if ( address(rootToken) == rootIMXToken && imxCumulativeDepositLimit != UNLIMITED_DEPOSIT && IERC20Metadata(rootIMXToken).balanceOf(address(this)) + amount > imxCumulativeDepositLimit diff --git a/test/integration/child/ChildAxelarBridge.t.sol b/test/integration/child/ChildAxelarBridge.t.sol index 32385842..4ed35a39 100644 --- a/test/integration/child/ChildAxelarBridge.t.sol +++ b/test/integration/child/ChildAxelarBridge.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -import {Test, console2} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {ChildAxelarBridgeAdaptor} from "../../../src/child/ChildAxelarBridgeAdaptor.sol"; @@ -9,7 +9,6 @@ import { ChildERC20Bridge, IChildERC20Bridge, IChildERC20BridgeEvents, - IERC20Metadata, IChildERC20BridgeErrors } from "../../../src/child/ChildERC20Bridge.sol"; import {IChildERC20, ChildERC20} from "../../../src/child/ChildERC20.sol"; @@ -21,6 +20,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil string public ROOT_ADAPTOR_ADDRESS = Strings.toHexString(address(1)); string public ROOT_CHAIN_NAME = "ROOT_CHAIN"; address constant IMX_TOKEN_ADDRESS = address(0xccc); + address constant WIMX_TOKEN_ADDRESS = address(0xabc); address constant NATIVE_ETH = address(0xeee); ChildERC20Bridge public childERC20Bridge; @@ -51,7 +51,8 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil ROOT_ADAPTOR_ADDRESS, address(childERC20), ROOT_CHAIN_NAME, - IMX_TOKEN_ADDRESS + IMX_TOKEN_ADDRESS, + WIMX_TOKEN_ADDRESS ); childAxelarBridgeAdaptor.initialize( diff --git a/test/integration/child/withdrawals/ChildAxelarBridgeWithdraw.t.sol b/test/integration/child/withdrawals/ChildAxelarBridgeWithdraw.t.sol index e06b0d4b..6faaaecd 100644 --- a/test/integration/child/withdrawals/ChildAxelarBridgeWithdraw.t.sol +++ b/test/integration/child/withdrawals/ChildAxelarBridgeWithdraw.t.sol @@ -1,10 +1,7 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -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 {Test} from "forge-std/Test.sol"; import {MockAxelarGateway} from "../../../../src/test/root/MockAxelarGateway.sol"; import {MockAxelarGasService} from "../../../../src/test/root/MockAxelarGasService.sol"; import {ChildERC20Bridge, IChildERC20BridgeEvents} from "../../../../src/child/ChildERC20Bridge.sol"; @@ -14,7 +11,6 @@ import { IChildAxelarBridgeAdaptorErrors } from "../../../../src/child/ChildAxelarBridgeAdaptor.sol"; import {Utils} from "../../../utils.t.sol"; -import {WETH} from "../../../../src/test/root/WETH.sol"; import {ChildERC20} from "../../../../src/child/ChildERC20.sol"; contract ChildERC20BridgeWithdrawIntegrationTest is diff --git a/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawIMX.t.sol b/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawIMX.t.sol index 6a595adf..45d0538b 100644 --- a/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawIMX.t.sol +++ b/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawIMX.t.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -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 {Test} from "forge-std/Test.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {MockAxelarGateway} from "../../../../src/test/root/MockAxelarGateway.sol"; import {MockAxelarGasService} from "../../../../src/test/root/MockAxelarGasService.sol"; @@ -14,7 +12,6 @@ import { IChildAxelarBridgeAdaptorErrors } from "../../../../src/child/ChildAxelarBridgeAdaptor.sol"; import {Utils} from "../../../utils.t.sol"; -import {WETH} from "../../../../src/test/root/WETH.sol"; import {ChildERC20} from "../../../../src/child/ChildERC20.sol"; contract ChildERC20BridgeWithdrawIMXIntegrationTest is diff --git a/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawToIMX.t.sol b/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawToIMX.t.sol index de6d0b71..09bcb88e 100644 --- a/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawToIMX.t.sol +++ b/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawToIMX.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -import {Test, console2} from "forge-std/Test.sol"; -import {ERC20PresetMinterPauser} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; +import {Test} from "forge-std/Test.sol"; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {MockAxelarGateway} from "../../../../src/test/root/MockAxelarGateway.sol"; @@ -14,7 +13,6 @@ import { IChildAxelarBridgeAdaptorErrors } from "../../../../src/child/ChildAxelarBridgeAdaptor.sol"; import {Utils} from "../../../utils.t.sol"; -import {WETH} from "../../../../src/test/root/WETH.sol"; import {ChildERC20} from "../../../../src/child/ChildERC20.sol"; contract ChildERC20BridgewithdrawIMXToIntegrationTest is diff --git a/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawWIMX.t.sol b/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawWIMX.t.sol new file mode 100644 index 00000000..590164f1 --- /dev/null +++ b/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawWIMX.t.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity ^0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {MockAxelarGateway} from "../../../../src/test/root/MockAxelarGateway.sol"; +import {MockAxelarGasService} from "../../../../src/test/root/MockAxelarGasService.sol"; +import {ChildERC20Bridge, IChildERC20BridgeEvents} from "../../../../src/child/ChildERC20Bridge.sol"; +import { + ChildAxelarBridgeAdaptor, + IChildAxelarBridgeAdaptorEvents, + IChildAxelarBridgeAdaptorErrors +} from "../../../../src/child/ChildAxelarBridgeAdaptor.sol"; +import {Utils} from "../../../utils.t.sol"; +import {WIMX} from "../../../../src/child/WIMX.sol"; +import {ChildERC20} from "../../../../src/child/ChildERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +contract ChildERC20BridgeWithdrawWIMXIntegrationTest is + Test, + IChildERC20BridgeEvents, + IChildAxelarBridgeAdaptorEvents, + IChildAxelarBridgeAdaptorErrors, + Utils +{ + address constant ROOT_IMX_TOKEN = address(555555); + address constant WRAPPED_IMX = address(0xabc); + ChildERC20Bridge public childBridge; + ChildAxelarBridgeAdaptor public axelarAdaptor; + MockAxelarGasService public mockAxelarGasService; + MockAxelarGateway public mockAxelarGateway; + WIMX public wIMXToken; + + function setUp() public { + (childBridge, axelarAdaptor,,,, mockAxelarGasService, mockAxelarGateway) = childIntegrationSetup(); + wIMXToken = WIMX(payable(WRAPPED_IMX)); + Address.sendValue(payable(wIMXToken), 100 ether); + } + + function test_WithdrawWIMX_CallsBridgeAdaptor() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), address(this), withdrawAmount); + vm.expectCall( + address(axelarAdaptor), + withdrawFee, + abi.encodeWithSelector(axelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + } + + function test_WithdrawWIMX_CallsAxelarGateway() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), address(this), withdrawAmount); + vm.expectCall( + address(mockAxelarGateway), + 0, + abi.encodeWithSelector( + mockAxelarGateway.callContract.selector, + childBridge.rootChain(), + childBridge.rootERC20BridgeAdaptor(), + predictedPayload + ) + ); + + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + } + + function test_WithdrawWIMX_CallsGasService() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), address(this), withdrawAmount); + + vm.expectCall( + address(mockAxelarGasService), + withdrawFee, + abi.encodeWithSelector( + mockAxelarGasService.payNativeGasForContractCall.selector, + address(axelarAdaptor), + childBridge.rootChain(), + childBridge.rootERC20BridgeAdaptor(), + predictedPayload, + address(this) + ) + ); + + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + } + + function test_WithdrawWIMX_EmitsAxelarMessageSentEvent() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), address(this), withdrawAmount); + + vm.expectEmit(address(axelarAdaptor)); + emit AxelarMessageSent(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload); + + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + } + + function test_WithdrawWIMX_ReducesBalance() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + uint256 preBal = address(this).balance; + uint256 preTokenBal = wIMXToken.balanceOf(address(this)); + uint256 preGasBal = address(mockAxelarGasService).balance; + + wIMXToken.approve(address(childBridge), withdrawAmount); + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + + uint256 postBal = address(this).balance; + uint256 postTokenBal = wIMXToken.balanceOf(address(this)); + uint256 postGasBal = address(mockAxelarGasService).balance; + + assertEq(postBal, preBal - withdrawFee, "Balance not reduced"); + assertEq(postTokenBal, preTokenBal - withdrawAmount); + assertEq(postGasBal, preGasBal + withdrawFee, "Gas service not getting paid"); + } +} diff --git a/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawWIMXTo.t.sol b/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawWIMXTo.t.sol new file mode 100644 index 00000000..92ef53e2 --- /dev/null +++ b/test/integration/child/withdrawals/ChildAxelarBridgeWithdrawWIMXTo.t.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity ^0.8.19; + +import {Test} 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 {MockAxelarGateway} from "../../../../src/test/root/MockAxelarGateway.sol"; +import {MockAxelarGasService} from "../../../../src/test/root/MockAxelarGasService.sol"; +import {ChildERC20Bridge, IChildERC20BridgeEvents} from "../../../../src/child/ChildERC20Bridge.sol"; +import { + ChildAxelarBridgeAdaptor, + IChildAxelarBridgeAdaptorEvents, + IChildAxelarBridgeAdaptorErrors +} from "../../../../src/child/ChildAxelarBridgeAdaptor.sol"; +import {Utils} from "../../../utils.t.sol"; +import {WETH} from "../../../../src/test/root/WETH.sol"; +import {WIMX} from "../../../../src/child/WIMX.sol"; +import {ChildERC20} from "../../../../src/child/ChildERC20.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +contract ChildERC20BridgeWithdrawWIMXToIntegrationTest is + Test, + IChildERC20BridgeEvents, + IChildAxelarBridgeAdaptorEvents, + IChildAxelarBridgeAdaptorErrors, + Utils +{ + address constant ROOT_IMX_TOKEN = address(555555); + address constant WRAPPED_IMX = address(0xabc); + + ChildERC20Bridge public childBridge; + ChildAxelarBridgeAdaptor public axelarAdaptor; + address public rootToken; + address public rootImxToken; + ChildERC20 public childTokenTemplate; + MockAxelarGasService public axelarGasService; + MockAxelarGateway public mockAxelarGateway; + WIMX public wIMXToken; + + function setUp() public { + (childBridge, axelarAdaptor, rootToken, rootImxToken, childTokenTemplate, axelarGasService, mockAxelarGateway) = + childIntegrationSetup(); + wIMXToken = WIMX(payable(WRAPPED_IMX)); + Address.sendValue(payable(wIMXToken), 100 ether); + } + + function test_WithdrawWIMXTo_CallsBridgeAdaptor() public { + address receiver = address(0xabcd); + uint256 withdrawFee = 1; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount); + vm.expectCall( + address(axelarAdaptor), + withdrawFee, + abi.encodeWithSelector(axelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + + childBridge.withdrawWIMXTo{value: withdrawFee}(receiver, withdrawAmount); + } + + function test_WithdrawWIMXToWithDifferentAccount_CallsBridgeAdaptor() public { + address receiver = address(0xabcd); + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount); + vm.expectCall( + address(axelarAdaptor), + withdrawFee, + abi.encodeWithSelector(axelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + + childBridge.withdrawWIMXTo{value: withdrawFee}(receiver, withdrawAmount); + } + + function test_WithdrawWIMXTo_CallsAxelarGateway() public { + address reciever = address(0xabcd); + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), reciever, withdrawAmount); + vm.expectCall( + address(mockAxelarGateway), + 0, + abi.encodeWithSelector( + mockAxelarGateway.callContract.selector, + childBridge.rootChain(), + childBridge.rootERC20BridgeAdaptor(), + predictedPayload + ) + ); + + childBridge.withdrawWIMXTo{value: withdrawFee}(reciever, withdrawAmount); + } + + function test_WithdrawWIMXToWithDifferentAccount_CallsAxelarGateway() public { + address receiver = address(0xabcd); + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount); + vm.expectCall( + address(mockAxelarGateway), + 0, + abi.encodeWithSelector( + mockAxelarGateway.callContract.selector, + childBridge.rootChain(), + childBridge.rootERC20BridgeAdaptor(), + predictedPayload + ) + ); + + childBridge.withdrawWIMXTo{value: withdrawFee}(receiver, withdrawAmount); + } + + function test_WithdrawWIMXTo_CallsGasService() public { + address receiver = address(0xabcd); + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount); + + vm.expectCall( + address(axelarGasService), + withdrawFee, + abi.encodeWithSelector( + axelarGasService.payNativeGasForContractCall.selector, + address(axelarAdaptor), + childBridge.rootChain(), + childBridge.rootERC20BridgeAdaptor(), + predictedPayload, + address(this) + ) + ); + + childBridge.withdrawWIMXTo{value: withdrawFee}(receiver, withdrawAmount); + } + + function test_WithdrawWIMXToWithDifferentAccount_CallsGasService() public { + address receiver = address(0xabcdf); + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount); + + vm.expectCall( + address(axelarGasService), + withdrawFee, + abi.encodeWithSelector( + axelarGasService.payNativeGasForContractCall.selector, + address(axelarAdaptor), + childBridge.rootChain(), + childBridge.rootERC20BridgeAdaptor(), + predictedPayload, + address(this) + ) + ); + + childBridge.withdrawWIMXTo{value: withdrawFee}(receiver, withdrawAmount); + } + + function test_WithdrawWIMXTo_EmitsAxelarMessageSentEvent() public { + address receiver = address(0xabcd); + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount); + + vm.expectEmit(address(axelarAdaptor)); + emit AxelarMessageSent(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload); + + childBridge.withdrawWIMXTo{value: withdrawFee}(receiver, withdrawAmount); + } + + function test_WithdrawWIMXToWithDifferentAccount_EmitsAxelarMessageSentEvent() public { + address receiver = address(0xabcda); + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount); + + vm.expectEmit(address(axelarAdaptor)); + emit AxelarMessageSent(childBridge.rootChain(), childBridge.rootERC20BridgeAdaptor(), predictedPayload); + + childBridge.withdrawWIMXTo{value: withdrawFee}(receiver, withdrawAmount); + } + + function test_WithdrawWIMXTo_ReducesBalance() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + uint256 preBal = address(this).balance; + uint256 preTokenBal = wIMXToken.balanceOf(address(this)); + uint256 preGasBal = address(axelarGasService).balance; + + wIMXToken.approve(address(childBridge), withdrawAmount); + childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), withdrawAmount); + + uint256 postBal = address(this).balance; + uint256 postTokenBal = wIMXToken.balanceOf(address(this)); + uint256 postGasBal = address(axelarGasService).balance; + + assertEq(postBal, preBal - withdrawFee, "Balance not reduced"); + assertEq(postTokenBal, preTokenBal - withdrawAmount); + assertEq(postGasBal, preGasBal + withdrawFee, "Gas service not getting paid"); + } +} diff --git a/test/unit/child/ChildAxelarBridgeAdaptor.t.sol b/test/unit/child/ChildAxelarBridgeAdaptor.t.sol index bb7c1065..b69850d5 100644 --- a/test/unit/child/ChildAxelarBridgeAdaptor.t.sol +++ b/test/unit/child/ChildAxelarBridgeAdaptor.t.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -import {Test, console2} from "forge-std/Test.sol"; -import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Test} from "forge-std/Test.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {ERC20PresetMinterPauser} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; import {ChildAxelarBridgeAdaptor} from "../../../src/child/ChildAxelarBridgeAdaptor.sol"; diff --git a/test/unit/child/ChildERC20Bridge.t.sol b/test/unit/child/ChildERC20Bridge.t.sol index 151cee13..e25e4082 100644 --- a/test/unit/child/ChildERC20Bridge.t.sol +++ b/test/unit/child/ChildERC20Bridge.t.sol @@ -1,18 +1,16 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -import {Test, console2} from "forge-std/Test.sol"; -import {ERC20PresetMinterPauser} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; +import {Test} from "forge-std/Test.sol"; +import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol"; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import { ChildERC20Bridge, IChildERC20Bridge, IChildERC20BridgeEvents, - IERC20Metadata, IChildERC20BridgeErrors } from "../../../src/child/ChildERC20Bridge.sol"; -import {IChildERC20} from "../../../src/interfaces/child/IChildERC20.sol"; import {ChildERC20} from "../../../src/child/ChildERC20.sol"; import {Utils} from "../../utils.t.sol"; @@ -21,6 +19,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B string public ROOT_BRIDGE_ADAPTOR = Strings.toHexString(address(4)); string constant ROOT_CHAIN_NAME = "test"; address constant ROOT_IMX_TOKEN = address(0xccc); + address constant CHILD_WIMX_TOKEN = address(0xabc); address constant NATIVE_ETH = address(0xeee); ChildERC20 public childTokenTemplate; ChildERC20 public rootToken; @@ -43,7 +42,13 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B adaptorManager: address(this) }); childBridge.initialize( - roles, address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN + roles, + address(this), + ROOT_BRIDGE_ADAPTOR, + address(childTokenTemplate), + ROOT_CHAIN_NAME, + ROOT_IMX_TOKEN, + CHILD_WIMX_TOKEN ); } @@ -53,8 +58,28 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B 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"); + assertFalse(address(childBridge.childETHToken()) == address(0), "childETHToken not set"); + assertFalse(address(childBridge.childETHToken()).code.length == 0, "childETHToken contract empty"); + } + + function test_NativeTransferFromWIMX() public { + address caller = address(0x123a); + payable(caller).transfer(2 ether); + + uint256 wIMXStorageSlot = 158; + vm.store(address(childBridge), bytes32(wIMXStorageSlot), bytes32(uint256(uint160(caller)))); + + vm.startPrank(caller); + uint256 bal = address(childBridge).balance; + payable(childBridge).transfer(1 ether); + uint256 postBal = address(childBridge).balance; + + assertEq(bal + 1 ether, postBal, "balance not increased"); + } + + function test_RevertIfNativeTransferIsFromNonWIMX() public { + vm.expectRevert(NonWrappedNativeTransfer.selector); + payable(childBridge).transfer(1 ether); } function test_RevertIfInitializeTwice() public { @@ -67,7 +92,13 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert("Initializable: contract is already initialized"); childBridge.initialize( - roles, address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN + roles, + address(this), + ROOT_BRIDGE_ADAPTOR, + address(childTokenTemplate), + ROOT_CHAIN_NAME, + ROOT_IMX_TOKEN, + CHILD_WIMX_TOKEN ); } @@ -82,7 +113,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1)); + bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressPauser() public { @@ -96,7 +127,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1)); + bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressUnpauser() public { @@ -110,7 +141,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1)); + bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressVariableManager() public { @@ -124,7 +155,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1)); + bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressAdaptorManager() public { @@ -138,7 +169,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1)); + bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressAdapter() public { @@ -152,7 +183,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(roles, address(0), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1)); + bridge.initialize(roles, address(0), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressChildTemplate() public { @@ -166,7 +197,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(0), ROOT_CHAIN_NAME, address(1)); + bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(0), ROOT_CHAIN_NAME, address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressIMXToken() public { @@ -180,7 +211,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(0)); + bridge.initialize(roles, address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(0), address(1)); } function test_RevertIf_InitializeWithAZeroAddressAll() public { @@ -194,7 +225,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(roles, address(0), ROOT_BRIDGE_ADAPTOR, address(0), ROOT_CHAIN_NAME, address(0)); + bridge.initialize(roles, address(0), ROOT_BRIDGE_ADAPTOR, address(0), ROOT_CHAIN_NAME, address(0), address(0)); } function test_RevertIf_InitializeWithAnEmptyBridgeAdaptorString() public { @@ -208,7 +239,9 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(InvalidRootERC20BridgeAdaptor.selector); - bridge.initialize(roles, address(this), "", address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN); + bridge.initialize( + roles, address(this), "", address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN, CHILD_WIMX_TOKEN + ); } function test_RevertIf_InitializeWithAnEmptyChainNameString() public { @@ -222,7 +255,9 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B }); vm.expectRevert(InvalidRootChain.selector); - bridge.initialize(roles, address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), "", ROOT_IMX_TOKEN); + bridge.initialize( + roles, address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), "", ROOT_IMX_TOKEN, CHILD_WIMX_TOKEN + ); } function test_onMessageReceive_EmitsTokenMappedEvent() public { @@ -347,13 +382,24 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B address newAdaptorAddress = address(0x11111); assertEq(address(childBridge.bridgeAdaptor()), address(this), "bridgeAdaptor not set"); + vm.expectEmit(true, true, true, true); + emit BridgeAdaptorUpdated(address(childBridge.bridgeAdaptor()), newAdaptorAddress); childBridge.updateBridgeAdaptor(newAdaptorAddress); assertEq(address(childBridge.bridgeAdaptor()), newAdaptorAddress, "bridgeAdaptor not updated"); } function test_RevertIf_updateBridgeAdaptorCalledByNotAdaptorManager() public { - vm.prank(address(0xf00f00)); - vm.expectRevert(abi.encodeWithSelector(NotVariableManager.selector, 0xf00f00)); + address caller = address(0xf00f00); + bytes32 role = childBridge.ADAPTOR_MANAGER_ROLE(); + vm.prank(caller); + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + StringsUpgradeable.toHexString(caller), + " is missing role ", + StringsUpgradeable.toHexString(uint256(role), 32) + ) + ); childBridge.updateBridgeAdaptor(address(0x11111)); } diff --git a/test/unit/child/WIMX.t.sol b/test/unit/child/WIMX.t.sol index 2223566c..bdcbbae1 100644 --- a/test/unit/child/WIMX.t.sol +++ b/test/unit/child/WIMX.t.sol @@ -189,6 +189,8 @@ contract WIMXTest is Test { "Token supply should be 0.55 after 2nd deposit" ); + vm.stopPrank(); + // Withdraw 0.5 IMX uint256 withdrawlAmt2 = 0.5 ether; vm.startPrank(user2); diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdraw.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdraw.t.sol index 0b698567..50e60253 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdraw.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdraw.t.sol @@ -1,19 +1,15 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -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 {Test} from "forge-std/Test.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import { ChildERC20Bridge, IChildERC20Bridge, IChildERC20BridgeEvents, - IERC20Metadata, IChildERC20BridgeErrors } from "../../../../src/child/ChildERC20Bridge.sol"; -import {IChildERC20} from "../../../../src/interfaces/child/IChildERC20.sol"; -import {ChildERC20} from "../../../../src/child/ChildERC20.sol"; +import {IChildERC20, ChildERC20} from "../../../../src/child/ChildERC20.sol"; import {MockAdaptor} from "../../../../src/test/root/MockAdaptor.sol"; import {Utils} from "../../../utils.t.sol"; @@ -23,6 +19,7 @@ contract ChildERC20BridgeWithdrawUnitTest is Test, IChildERC20BridgeEvents, IChi string constant ROOT_CHAIN_NAME = "test"; address constant ROOT_IMX_TOKEN = address(0xccc); address constant NATIVE_ETH = address(0xeee); + address constant WIMX_TOKEN_ADDRESS = address(0xabc); ChildERC20 public childTokenTemplate; ChildERC20 public rootToken; ChildERC20 public childToken; @@ -53,7 +50,8 @@ contract ChildERC20BridgeWithdrawUnitTest is Test, IChildERC20BridgeEvents, IChi ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, - ROOT_IMX_TOKEN + ROOT_IMX_TOKEN, + WIMX_TOKEN_ADDRESS ); bytes memory mapTokenData = @@ -68,16 +66,23 @@ contract ChildERC20BridgeWithdrawUnitTest is Test, IChildERC20BridgeEvents, IChi childToken.approve(address(childBridge), 1000000 ether); } + function test_RevertsIf_WithdrawCalledWithZeroFee() public { + uint256 withdrawAmount = 100; + + vm.expectRevert(NoGas.selector); + childBridge.withdraw(IChildERC20(address(2222222)), withdrawAmount); + } + function test_RevertsIf_WithdrawCalledWithEmptyChildToken() public { vm.expectRevert(EmptyTokenContract.selector); - childBridge.withdraw(IChildERC20(address(2222222)), 100); + childBridge.withdraw{value: 1 ether}(IChildERC20(address(2222222)), 100); } function test_RevertsIf_WithdrawCalledWithUnmappedToken() public { ChildERC20 newToken = new ChildERC20(); newToken.initialize(address(123), "Test", "TST", 18); vm.expectRevert(NotMapped.selector); - childBridge.withdraw(IChildERC20(address(newToken)), 100); + childBridge.withdraw{value: 1 ether}(IChildERC20(address(newToken)), 100); } function test_RevertsIf_WithdrawCalledWithAChildTokenWithUnsetRootToken() public { @@ -99,7 +104,7 @@ contract ChildERC20BridgeWithdrawUnitTest is Test, IChildERC20BridgeEvents, IChi vm.store(address(childBridge), slot, data); vm.expectRevert(ZeroAddressRootToken.selector); - childBridge.withdraw(IChildERC20(address(childToken)), 100); + childBridge.withdraw{value: 1 ether}(IChildERC20(address(childToken)), 100); } function test_RevertsIf_WithdrawCalledWithAChildTokenThatHasWrongBridge() public { @@ -109,7 +114,7 @@ contract ChildERC20BridgeWithdrawUnitTest is Test, IChildERC20BridgeEvents, IChi vm.store(address(childToken), bridgeSlotBytes32, bytes32(uint256(uint160(address(0x123))))); vm.expectRevert(IncorrectBridgeAddress.selector); - childBridge.withdraw(IChildERC20(address(childToken)), 100); + childBridge.withdraw{value: 1 ether}(IChildERC20(address(childToken)), 100); } function test_RevertsIf_WithdrawWhenBurnFails() public { @@ -117,7 +122,7 @@ contract ChildERC20BridgeWithdrawUnitTest is Test, IChildERC20BridgeEvents, IChi deployCodeTo("ChildERC20FailOnBurn.sol", address(childToken)); vm.expectRevert(BurnFailed.selector); - childBridge.withdraw(IChildERC20(address(childToken)), 100); + childBridge.withdraw{value: 1 ether}(IChildERC20(address(childToken)), 100); } function test_withdraw_CallsBridgeAdaptor() public { diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMX.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMX.t.sol index 5a1b96c2..3317807e 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMX.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMX.t.sol @@ -1,16 +1,14 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -import {Test, console2} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import { ChildERC20Bridge, IChildERC20Bridge, IChildERC20BridgeEvents, - 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"; @@ -20,6 +18,7 @@ contract ChildERC20BridgeWithdrawIMXUnitTest is Test, IChildERC20BridgeEvents, I string public ROOT_BRIDGE_ADAPTOR = Strings.toHexString(address(4)); string constant ROOT_CHAIN_NAME = "test"; address constant ROOT_IMX_TOKEN = address(0xccc); + address constant WIMX_TOKEN_ADDRESS = address(0xabc); ChildERC20 public childTokenTemplate; ChildERC20Bridge public childBridge; MockAdaptor public mockAdaptor; @@ -44,10 +43,18 @@ contract ChildERC20BridgeWithdrawIMXUnitTest is Test, IChildERC20BridgeEvents, I ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, - ROOT_IMX_TOKEN + ROOT_IMX_TOKEN, + WIMX_TOKEN_ADDRESS ); } + function test_RevertIf_WithdrawIMXCalledWithZeroFee() public { + uint256 withdrawAmount = 300; + + vm.expectRevert(NoGas.selector); + childBridge.withdrawIMX(withdrawAmount); + } + function test_RevertsIf_WithdrawIMXCalledWithInsufficientFund() public { uint256 withdrawAmount = 7 ether; diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawTo.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawTo.t.sol index bf73f6c3..71a10a67 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawTo.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawTo.t.sol @@ -1,19 +1,15 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -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 {Test} from "forge-std/Test.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import { ChildERC20Bridge, IChildERC20Bridge, IChildERC20BridgeEvents, - IERC20Metadata, IChildERC20BridgeErrors } from "../../../../src/child/ChildERC20Bridge.sol"; -import {IChildERC20} from "../../../../src/interfaces/child/IChildERC20.sol"; -import {ChildERC20} from "../../../../src/child/ChildERC20.sol"; +import {IChildERC20, ChildERC20} from "../../../../src/child/ChildERC20.sol"; import {MockAdaptor} from "../../../../src/test/root/MockAdaptor.sol"; import {Utils} from "../../../utils.t.sol"; @@ -22,6 +18,7 @@ contract ChildERC20BridgeWithdrawToUnitTest is Test, IChildERC20BridgeEvents, IC string public ROOT_BRIDGE_ADAPTOR = Strings.toHexString(address(4)); string constant ROOT_CHAIN_NAME = "test"; address constant ROOT_IMX_TOKEN = address(0xccc); + address constant WIMX_TOKEN_ADDRESS = address(0xabc); address constant NATIVE_ETH = address(0xeee); ChildERC20 public childTokenTemplate; ChildERC20 public rootToken; @@ -53,7 +50,8 @@ contract ChildERC20BridgeWithdrawToUnitTest is Test, IChildERC20BridgeEvents, IC ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, - ROOT_IMX_TOKEN + ROOT_IMX_TOKEN, + WIMX_TOKEN_ADDRESS ); bytes memory mapTokenData = @@ -68,16 +66,30 @@ contract ChildERC20BridgeWithdrawToUnitTest is Test, IChildERC20BridgeEvents, IC childToken.approve(address(childBridge), 1000000 ether); } + function test_RevertsIf_WithdrawToCalledWithZeroReciever() public { + uint256 withdrawAmount = 300; + + vm.expectRevert(ZeroAddress.selector); + childBridge.withdrawTo{value: 1 ether}(IChildERC20(address(2222222)), address(0), withdrawAmount); + } + + function test_RevertsIf_WithdrawToCalledWithZeroFee() public { + uint256 withdrawAmount = 300; + + vm.expectRevert(NoGas.selector); + childBridge.withdrawTo(IChildERC20(address(2222222)), address(this), withdrawAmount); + } + function test_RevertsIf_WithdrawToCalledWithEmptyChildToken() public { vm.expectRevert(EmptyTokenContract.selector); - childBridge.withdrawTo(IChildERC20(address(2222222)), address(this), 100); + childBridge.withdrawTo{value: 1 ether}(IChildERC20(address(2222222)), address(this), 100); } function test_RevertsIf_WithdrawToCalledWithUnmappedToken() public { ChildERC20 newToken = new ChildERC20(); newToken.initialize(address(123), "Test", "TST", 18); vm.expectRevert(NotMapped.selector); - childBridge.withdrawTo(IChildERC20(address(newToken)), address(this), 100); + childBridge.withdrawTo{value: 1 ether}(IChildERC20(address(newToken)), address(this), 100); } function test_RevertsIf_WithdrawToCalledWithAChildTokenWithUnsetRootToken() public { @@ -98,7 +110,7 @@ contract ChildERC20BridgeWithdrawToUnitTest is Test, IChildERC20BridgeEvents, IC vm.store(address(childBridge), slot, data); vm.expectRevert(ZeroAddressRootToken.selector); - childBridge.withdrawTo(IChildERC20(address(childToken)), address(this), 100); + childBridge.withdrawTo{value: 1 ether}(IChildERC20(address(childToken)), address(this), 100); } function test_RevertsIf_WithdrawToCalledWithAChildTokenThatHasWrongBridge() public { @@ -108,7 +120,7 @@ contract ChildERC20BridgeWithdrawToUnitTest is Test, IChildERC20BridgeEvents, IC vm.store(address(childToken), bridgeSlotBytes32, bytes32(uint256(uint160(address(0x123))))); vm.expectRevert(IncorrectBridgeAddress.selector); - childBridge.withdrawTo(IChildERC20(address(childToken)), address(this), 100); + childBridge.withdrawTo{value: 1 ether}(IChildERC20(address(childToken)), address(this), 100); } function test_RevertsIf_WithdrawToWhenBurnFails() public { @@ -116,7 +128,7 @@ contract ChildERC20BridgeWithdrawToUnitTest is Test, IChildERC20BridgeEvents, IC deployCodeTo("ChildERC20FailOnBurn.sol", address(childToken)); vm.expectRevert(BurnFailed.selector); - childBridge.withdrawTo(IChildERC20(address(childToken)), address(this), 100); + childBridge.withdrawTo{value: 1 ether}(IChildERC20(address(childToken)), address(this), 100); } function test_withdrawTo_CallsBridgeAdaptor() public { diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawToIMX.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawToIMX.t.sol index 60780a39..542e5f70 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawToIMX.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawToIMX.t.sol @@ -1,18 +1,14 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity 0.8.19; -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 {Test} from "forge-std/Test.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import { ChildERC20Bridge, IChildERC20Bridge, IChildERC20BridgeEvents, - 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"; @@ -22,6 +18,7 @@ contract ChildERC20BridgeWithdrawIMXToUnitTest is Test, IChildERC20BridgeEvents, string public ROOT_BRIDGE_ADAPTOR = Strings.toHexString(address(4)); string constant ROOT_CHAIN_NAME = "test"; address constant ROOT_IMX_TOKEN = address(0xccc); + address constant WIMX_TOKEN_ADDRESS = address(0xabc); address constant NATIVE_ETH = address(0xeee); ChildERC20 public childTokenTemplate; ChildERC20 public rootToken; @@ -50,10 +47,25 @@ contract ChildERC20BridgeWithdrawIMXToUnitTest is Test, IChildERC20BridgeEvents, ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, - ROOT_IMX_TOKEN + ROOT_IMX_TOKEN, + WIMX_TOKEN_ADDRESS ); } + function test_RevertsIf_withdrawIMXToCalledWithZeroReciever() public { + uint256 withdrawAmount = 7 ether; + + vm.expectRevert(ZeroAddress.selector); + childBridge.withdrawIMXTo{value: 1 ether}(address(0), withdrawAmount); + } + + function test_RevertsIf_withdrawIMXToCalledWithZeroFee() public { + uint256 withdrawAmount = 7 ether; + + vm.expectRevert(NoGas.selector); + childBridge.withdrawIMXTo(address(this), withdrawAmount); + } + function test_RevertsIf_withdrawIMXToCalledWithInsufficientFund() public { uint256 withdrawAmount = 7 ether; @@ -61,7 +73,7 @@ contract ChildERC20BridgeWithdrawIMXToUnitTest is Test, IChildERC20BridgeEvents, childBridge.withdrawIMXTo{value: withdrawAmount - 1}(address(this), withdrawAmount); } - function test_RevertIf_ZeroAmountIsProvided() public { + function test_RevertIf_withdrawIMXToZeroAmountIsProvided() public { uint256 withdrawFee = 300; vm.expectRevert(ZeroAmount.selector); diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMX.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMX.t.sol new file mode 100644 index 00000000..ab031e6d --- /dev/null +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMX.t.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity ^0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import { + ChildERC20Bridge, + IChildERC20Bridge, + IChildERC20BridgeEvents, + IChildERC20BridgeErrors +} from "../../../../src/child/ChildERC20Bridge.sol"; +import {ChildERC20} from "../../../../src/child/ChildERC20.sol"; +import {MockAdaptor} from "../../../../src/test/root/MockAdaptor.sol"; +import {Utils} from "../../../utils.t.sol"; +import {WIMX} from "../../../../src/child/WIMX.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +contract ChildERC20BridgeWithdrawWIMXUnitTest is Test, IChildERC20BridgeEvents, IChildERC20BridgeErrors, Utils { + string public ROOT_BRIDGE_ADAPTOR = Strings.toHexString(address(4)); + string constant ROOT_CHAIN_NAME = "test"; + address constant ROOT_IMX_TOKEN = address(0xccc); + ChildERC20 public childTokenTemplate; + ChildERC20Bridge public childBridge; + MockAdaptor public mockAdaptor; + WIMX public wIMXToken; + + function setUp() public { + childTokenTemplate = new ChildERC20(); + childTokenTemplate.initialize(address(123), "Test", "TST", 18); + + mockAdaptor = new MockAdaptor(); + + wIMXToken = new WIMX(); + Address.sendValue(payable(wIMXToken), 100 ether); + + childBridge = new ChildERC20Bridge(); + IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ + defaultAdmin: address(this), + pauser: address(this), + unpauser: address(this), + variableManager: address(this), + adaptorManager: address(this) + }); + childBridge.initialize( + roles, + address(mockAdaptor), + ROOT_BRIDGE_ADAPTOR, + address(childTokenTemplate), + ROOT_CHAIN_NAME, + ROOT_IMX_TOKEN, + address(wIMXToken) + ); + } + + function test_RevertsIf_withdrawWIMXCalledWithZeroFee() public { + uint256 withdrawAmount = 7 ether; + + vm.expectRevert(NoGas.selector); + childBridge.withdrawWIMX(withdrawAmount); + } + + function test_RevertsIf_WithdrawWIMXCalledWithInsufficientFund() public { + uint256 withdrawAmount = 101 ether; + uint256 withdrawFee = 300; + + wIMXToken.approve(address(childBridge), withdrawAmount); + vm.expectRevert(bytes("Wrapped IMX: Insufficient balance")); + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + } + + function test_RevertsIf_WithdrawWIMXCalledWithInsufficientAllowance() public { + uint256 withdrawAmount = 99 ether; + uint256 withdrawFee = 300; + + wIMXToken.approve(address(childBridge), withdrawAmount - 1); + vm.expectRevert(bytes("Wrapped IMX: Insufficient allowance")); + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + } + + function test_RevertsIf_WithdrawWIMXCalledWithZeroAmount() public { + uint256 withdrawFee = 300; + + vm.expectRevert(ZeroAmount.selector); + childBridge.withdrawWIMX{value: withdrawFee}(0); + } + + function test_WithdrawWIMX_CallsBridgeAdaptor() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), address(this), withdrawAmount); + wIMXToken.approve(address(childBridge), withdrawAmount); + + vm.expectCall( + address(mockAdaptor), + withdrawFee, + abi.encodeWithSelector(mockAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + } + + function test_WithdrawWIMX_EmitsWrappedIMXWithdrawEvent() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + vm.expectEmit(address(childBridge)); + emit ChildChainWrappedIMXWithdraw(ROOT_IMX_TOKEN, address(this), address(this), withdrawAmount); + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + } + + function test_WithdrawWIMX_ReducesBalance() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + uint256 preBal = wIMXToken.balanceOf(address(this)); + + wIMXToken.approve(address(childBridge), withdrawAmount); + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + + uint256 postBal = wIMXToken.balanceOf(address(this)); + assertEq(postBal, preBal - withdrawAmount, "Balance not reduced"); + } + + function test_WithdrawWIMX_PaysFee() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + uint256 preBal = address(this).balance; + + wIMXToken.approve(address(childBridge), withdrawAmount); + childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); + + uint256 postBal = address(this).balance; + assertEq(postBal, preBal - withdrawFee, "Fee not paid"); + } +} diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMXTo.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMXTo.t.sol new file mode 100644 index 00000000..c9650970 --- /dev/null +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMXTo.t.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity ^0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import { + ChildERC20Bridge, + IChildERC20Bridge, + IChildERC20BridgeEvents, + IChildERC20BridgeErrors +} from "../../../../src/child/ChildERC20Bridge.sol"; +import {ChildERC20} from "../../../../src/child/ChildERC20.sol"; +import {MockAdaptor} from "../../../../src/test/root/MockAdaptor.sol"; +import {Utils} from "../../../utils.t.sol"; +import {WIMX} from "../../../../src/child/WIMX.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +contract ChildERC20BridgeWithdrawWIMXToUnitTest is Test, IChildERC20BridgeEvents, IChildERC20BridgeErrors, Utils { + address constant ROOT_BRIDGE = address(3); + string public ROOT_BRIDGE_ADAPTOR = Strings.toHexString(address(4)); + string constant ROOT_CHAIN_NAME = "test"; + address constant ROOT_IMX_TOKEN = address(0xccc); + address constant WIMX_TOKEN_ADDRESS = address(0xabc); + ChildERC20 public childTokenTemplate; + ChildERC20Bridge public childBridge; + MockAdaptor public mockAdaptor; + WIMX public wIMXToken; + + function setUp() public { + childTokenTemplate = new ChildERC20(); + childTokenTemplate.initialize(address(123), "Test", "TST", 18); + + mockAdaptor = new MockAdaptor(); + + wIMXToken = new WIMX(); + Address.sendValue(payable(wIMXToken), 100 ether); + + childBridge = new ChildERC20Bridge(); + IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ + defaultAdmin: address(this), + pauser: address(this), + unpauser: address(this), + variableManager: address(this), + adaptorManager: address(this) + }); + childBridge.initialize( + roles, + address(mockAdaptor), + ROOT_BRIDGE_ADAPTOR, + address(childTokenTemplate), + ROOT_CHAIN_NAME, + ROOT_IMX_TOKEN, + address(wIMXToken) + ); + } + + function test_RevertsIf_withdrawWIMXToCalledWithZeroReciever() public { + uint256 withdrawAmount = 7 ether; + + vm.expectRevert(ZeroAddress.selector); + childBridge.withdrawWIMXTo{value: 1 ether}(address(0), withdrawAmount); + } + + function test_RevertsIf_withdrawWIMXToCalledWithZeroFee() public { + uint256 withdrawAmount = 7 ether; + + vm.expectRevert(NoGas.selector); + childBridge.withdrawWIMXTo(address(this), withdrawAmount); + } + + function test_RevertsIf_WithdrawWIMXToCalledWithInsufficientFund() public { + uint256 withdrawAmount = 101 ether; + uint256 withdrawFee = 300; + + wIMXToken.approve(address(childBridge), withdrawAmount); + vm.expectRevert(bytes("Wrapped IMX: Insufficient balance")); + childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), withdrawAmount); + } + + function test_RevertsIf_WithdrawWIMXToCalledWithInsufficientAllowance() public { + uint256 withdrawAmount = 99 ether; + uint256 withdrawFee = 300; + + wIMXToken.approve(address(childBridge), withdrawAmount - 1); + vm.expectRevert(bytes("Wrapped IMX: Insufficient allowance")); + childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), withdrawAmount); + } + + function test_RevertsIf_WithdrawWIMXToCalledWithZeroAmount() public { + uint256 withdrawFee = 300; + + vm.expectRevert(ZeroAmount.selector); + childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), 0); + } + + function test_WithdrawWIMXTo_CallsBridgeAdaptor() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), address(this), withdrawAmount); + wIMXToken.approve(address(childBridge), withdrawAmount); + + vm.expectCall( + address(mockAdaptor), + withdrawFee, + abi.encodeWithSelector(mockAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), withdrawAmount); + } + + function test_WithdrawWIMXToWithDifferentAccount_CallsBridgeAdaptor() public { + address receiver = address(0xabcd); + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + bytes memory predictedPayload = + abi.encode(WITHDRAW_SIG, ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount); + wIMXToken.approve(address(childBridge), withdrawAmount); + + vm.expectCall( + address(mockAdaptor), + withdrawFee, + abi.encodeWithSelector(mockAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + childBridge.withdrawWIMXTo{value: withdrawFee}(receiver, withdrawAmount); + } + + function test_WithdrawWIMXTo_EmitsWrappedIMXWithdrawEvent() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + vm.expectEmit(address(childBridge)); + emit ChildChainWrappedIMXWithdraw(ROOT_IMX_TOKEN, address(this), address(this), withdrawAmount); + childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), withdrawAmount); + } + + function test_WithdrawWIMXToWithDifferentAccount_EmitsWrappedIMXWithdrawEvent() public { + address receiver = address(0xabcd); + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + wIMXToken.approve(address(childBridge), withdrawAmount); + vm.expectEmit(address(childBridge)); + emit ChildChainWrappedIMXWithdraw(ROOT_IMX_TOKEN, address(this), receiver, withdrawAmount); + childBridge.withdrawWIMXTo{value: withdrawFee}(receiver, withdrawAmount); + } + + function test_WithdrawWIMXTo_ReducesBalance() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + uint256 preBal = wIMXToken.balanceOf(address(this)); + + wIMXToken.approve(address(childBridge), withdrawAmount); + childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), withdrawAmount); + + uint256 postBal = wIMXToken.balanceOf(address(this)); + assertEq(postBal, preBal - withdrawAmount, "Balance not reduced"); + } + + function test_WithdrawWIMXTo_PaysFee() public { + uint256 withdrawFee = 300; + uint256 withdrawAmount = 7 ether; + + uint256 preBal = address(this).balance; + + wIMXToken.approve(address(childBridge), withdrawAmount); + childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), withdrawAmount); + + uint256 postBal = address(this).balance; + assertEq(postBal, preBal - withdrawFee, "Fee not paid"); + } +} diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 1604a7b0..fddccb87 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.19; import {Test, console2} from "forge-std/Test.sol"; +import "@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.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"; @@ -19,6 +20,7 @@ import {Utils} from "../../utils.t.sol"; import {WETH} from "../../../src/test/root/WETH.sol"; contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20BridgeErrors, Utils { + bytes32 constant ADAPTOR_MANAGER_ROLE = keccak256("ADAPTOR_MANAGER_ROLE"); address constant CHILD_BRIDGE = address(3); address constant CHILD_BRIDGE_ADAPTOR = address(4); string CHILD_BRIDGE_ADAPTOR_STRING = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); @@ -85,6 +87,26 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid assertEq(rootBridge.rootWETHToken(), WRAPPED_ETH, "rootWETHToken not set"); } + function test_NativeTransferFromWETH() public { + address caller = address(0x123a); + payable(caller).transfer(2 ether); + + uint256 wETHStorageSlot = 158; + vm.store(address(rootBridge), bytes32(wETHStorageSlot), bytes32(uint256(uint160(caller)))); + + vm.startPrank(caller); + uint256 bal = address(rootBridge).balance; + payable(rootBridge).transfer(1 ether); + uint256 postBal = address(rootBridge).balance; + + assertEq(bal + 1 ether, postBal, "balance not increased"); + } + + function test_RevertIfNativeTransferIsFromNonWETH() public { + vm.expectRevert(NonWrappedNativeTransfer.selector); + payable(rootBridge).transfer(1); + } + function test_RevertIfInitializeTwice() public { IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), @@ -434,6 +456,11 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid * MAP TOKEN */ + function test_RevertsIf_MapTokenCalledWithZeroFee() public { + vm.expectRevert(NoGas.selector); + rootBridge.mapToken(token); + } + function test_mapToken_EmitsTokenMappedEvent() public { address childToken = Clones.predictDeterministicAddress(address(token), keccak256(abi.encodePacked(token)), CHILD_BRIDGE); @@ -525,8 +552,17 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_updateRootBridgeAdaptorCalledByNonOwner() public { - vm.prank(address(0xf00f00)); - vm.expectRevert(abi.encodeWithSelector(NotVariableManager.selector, 0xf00f00)); + address caller = address(0xf00f00); + bytes32 role = rootBridge.ADAPTOR_MANAGER_ROLE(); + vm.prank(caller); + vm.expectRevert( + abi.encodePacked( + "AccessControl: account ", + StringsUpgradeable.toHexString(caller), + " is missing role ", + StringsUpgradeable.toHexString(uint256(role), 32) + ) + ); rootBridge.updateRootBridgeAdaptor(address(0x11111)); } @@ -724,6 +760,12 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid * DEPOSIT TOKEN */ + function test_RevertsIf_DepositTokenWithZeroFee() public { + uint256 amount = 100; + vm.expectRevert(NoGas.selector); + rootBridge.deposit(IERC20Metadata(IMX_TOKEN), amount); + } + function test_RevertsIf_IMXDepositLimitExceeded() public { uint256 imxCumulativeDepositLimit = 700; diff --git a/test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol b/test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol index a237f7f9..7d3d1d7e 100644 --- a/test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol +++ b/test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol @@ -122,7 +122,7 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE function test_onMessageReceive_TransfersTokens() public { // Need to first map the token. - rootBridge.mapToken(token); + rootBridge.mapToken{value: 1 ether}(token); // And give the bridge some tokens token.transfer(address(rootBridge), 100 ether); @@ -159,7 +159,7 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE function test_onMessageReceive_TransfersTokens_DifferentReceiver() public { address receiver = address(123456); // Need to first map the token. - rootBridge.mapToken(token); + rootBridge.mapToken{value: 1 ether}(token); // And give the bridge some tokens token.transfer(address(rootBridge), 100 ether); @@ -198,7 +198,7 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE function test_onMessageReceive_EmitsRootChainERC20WithdrawEvent() public { // Need to first map the token. - rootBridge.mapToken(token); + rootBridge.mapToken{value: 1 ether}(token); // And give the bridge some tokens token.transfer(address(rootBridge), 100 ether); @@ -229,7 +229,7 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE function test_onMessageReceive_EmitsRootChainERC20WithdrawEvent_DifferentReceiver() public { address receiver = address(123456); // Need to first map the token. - rootBridge.mapToken(token); + rootBridge.mapToken{value: 1 ether}(token); // And give the bridge some tokens token.transfer(address(rootBridge), 100 ether); diff --git a/test/utils.t.sol b/test/utils.t.sol index 44c49dc1..47bc1de4 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -11,6 +11,7 @@ import {ChildERC20Bridge, IChildERC20Bridge} from "../src/child/ChildERC20Bridge import {ChildAxelarBridgeAdaptor} from "../src/child/ChildAxelarBridgeAdaptor.sol"; import {WETH} from "../src/test/root/WETH.sol"; import {IWETH} from "../src/interfaces/root/IWETH.sol"; +import {WIMX} from "../src/child/WIMX.sol"; import {IChildERC20, ChildERC20} from "../src/child/ChildERC20.sol"; import {IRootERC20Bridge} from "../src/root/RootERC20Bridge.sol"; @@ -35,6 +36,9 @@ contract Utils is Test { string memory rootAdaptor = Strings.toHexString(address(99999)); rootIMX = address(555555); rootToken = address(44444); + address childWIMX = address(0xabc); + + deployCodeTo("WIMX.sol", childWIMX); axelarGasService = new MockAxelarGasService(); mockAxelarGateway = new MockAxelarGateway(); @@ -50,7 +54,7 @@ contract Utils is Test { adaptorManager: address(this) }); childBridge.initialize( - roles, address(childBridgeAdaptor), rootAdaptor, address(childTokenTemplate), "ROOT", rootIMX + roles, address(childBridgeAdaptor), rootAdaptor, address(childTokenTemplate), "ROOT", rootIMX, childWIMX ); childBridgeAdaptor.initialize("ROOT", address(childBridge), address(axelarGasService));