diff --git a/src/child/ChildAxelarBridgeAdaptor.sol b/src/child/ChildAxelarBridgeAdaptor.sol index ebef9f72..9888d4f2 100644 --- a/src/child/ChildAxelarBridgeAdaptor.sol +++ b/src/child/ChildAxelarBridgeAdaptor.sol @@ -49,27 +49,43 @@ contract ChildAxelarBridgeAdaptor is IAxelarGasService public gasService; - constructor(address _gateway) AxelarExecutable(_gateway) {} + /// @notice Address of the authorized initializer. + address public immutable initializerAddress; + + /** + * @notice Constructs the ChildAxelarBridgeAdaptor contract. + * @param _gateway The address of the Axelar gateway contract. + * @param _initializerAddress The address of the authorized initializer. + */ + constructor(address _gateway, address _initializerAddress) AxelarExecutable(_gateway) { + if (_initializerAddress == address(0)) { + revert ZeroAddress(); + } + initializerAddress = _initializerAddress; + } /** * @notice Initialization function for ChildAxelarBridgeAdaptor. - * @param _roles Struct containing addresses of roles. + * @param _adaptorRoles Struct containing addresses of roles. * @param _childBridge Address of child bridge contract. * @param _rootChainId Axelar's string ID for the root chain. * @param _rootBridgeAdaptor Address of the bridge adaptor on the root chain. * @param _gasService Address of Axelar Gas Service contract. */ function initialize( - InitializationRoles memory _roles, + InitializationRoles memory _adaptorRoles, address _childBridge, string memory _rootChainId, string memory _rootBridgeAdaptor, address _gasService ) external initializer { + if (msg.sender != initializerAddress) { + revert UnauthorizedInitializer(); + } if ( - _childBridge == address(0) || _gasService == address(0) || _roles.defaultAdmin == address(0) - || _roles.bridgeManager == address(0) || _roles.gasServiceManager == address(0) - || _roles.targetManager == address(0) + _childBridge == address(0) || _gasService == address(0) || _adaptorRoles.defaultAdmin == address(0) + || _adaptorRoles.bridgeManager == address(0) || _adaptorRoles.gasServiceManager == address(0) + || _adaptorRoles.targetManager == address(0) ) { revert ZeroAddress(); } @@ -83,10 +99,10 @@ contract ChildAxelarBridgeAdaptor is } __AccessControl_init(); - _grantRole(DEFAULT_ADMIN_ROLE, _roles.defaultAdmin); - _grantRole(BRIDGE_MANAGER_ROLE, _roles.bridgeManager); - _grantRole(GAS_SERVICE_MANAGER_ROLE, _roles.gasServiceManager); - _grantRole(TARGET_MANAGER_ROLE, _roles.targetManager); + _grantRole(DEFAULT_ADMIN_ROLE, _adaptorRoles.defaultAdmin); + _grantRole(BRIDGE_MANAGER_ROLE, _adaptorRoles.bridgeManager); + _grantRole(GAS_SERVICE_MANAGER_ROLE, _adaptorRoles.gasServiceManager); + _grantRole(TARGET_MANAGER_ROLE, _adaptorRoles.targetManager); childBridge = IChildERC20Bridge(_childBridge); rootChainId = _rootChainId; @@ -197,6 +213,19 @@ contract ChildAxelarBridgeAdaptor is childBridge.onMessageReceive(_payload); } + /** + * @inheritdoc AxelarExecutable + * @dev This function is called by the parent `AxelarExecutable` contract's `executeWithToken()` function. + * However, this function is not required for the bridge, and thus reverts with an `UnsupportedOperation` error. + */ + function _executeWithToken(string calldata, string calldata, bytes calldata, string calldata, uint256) + internal + pure + override + { + revert UnsupportedOperation(); + } + // slither-disable-next-line unused-state,naming-convention uint256[50] private __gapChildAxelarBridgeAdaptor; } diff --git a/src/child/ChildERC20Bridge.sol b/src/child/ChildERC20Bridge.sol index 06e3051b..7c01ad49 100644 --- a/src/child/ChildERC20Bridge.sol +++ b/src/child/ChildERC20Bridge.sol @@ -35,10 +35,16 @@ import {BridgeRoles} from "../common/BridgeRoles.sol"; * - An account with an UNPAUSER_ROLE can unpause the contract. * - An account with an ADAPTOR_MANAGER_ROLE can update the root bridge adaptor address. * - An account with a DEFAULT_ADMIN_ROLE can grant and revoke roles. - * @dev Note: + * + * @dev Caution: + * - When withdrawing ETH (L2 -> L1), it's crucial to make sure that the receiving address on the root chain, + * if it's a contract, has a receive or fallback function that allows it to accept native ETH on the root chain. + * If this isn't the case, the transaction on the root chain could revert, potentially locking the user's funds indefinitely. * - There is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care. - * - This is an upgradeable contract that should be operated behind OpenZeppelin's TransparentUpgradeableProxy. * - The initialize function is susceptible to front running, so precautions should be taken to account for this scenario. + * + * @dev Note: + * - This is an upgradeable contract that should be operated behind OpenZeppelin's TransparentUpgradeableProxy. */ contract ChildERC20Bridge is BridgeRoles, @@ -70,6 +76,8 @@ contract ChildERC20Bridge is address public childETHToken; /// @dev The address of the wrapped IMX token on L2. address public wIMXToken; + /// @dev Address of the authorized initializer. + address public immutable initializerAddress; /** * @notice Modifier to ensure that the caller is the registered child bridge adaptor. @@ -81,6 +89,17 @@ contract ChildERC20Bridge is _; } + /** + * @notice Constructs the ChildERC20Bridge contract. + * @param _initializerAddress The address of the authorized initializer. + */ + constructor(address _initializerAddress) { + if (_initializerAddress == address(0)) { + revert ZeroAddress(); + } + initializerAddress = _initializerAddress; + } + /** * @notice Initialization function for ChildERC20Bridge. * @param newRoles Struct containing addresses of roles. @@ -97,6 +116,9 @@ contract ChildERC20Bridge is address newRootIMXToken, address newWIMXToken ) public initializer { + if (msg.sender != initializerAddress) { + revert UnauthorizedInitializer(); + } if ( newBridgeAdaptor == address(0) || newChildTokenTemplate == address(0) || newRootIMXToken == address(0) || newRoles.defaultAdmin == address(0) || newRoles.pauser == address(0) || newRoles.unpauser == address(0) @@ -238,6 +260,10 @@ contract ChildERC20Bridge is /** * @inheritdoc IChildERC20Bridge + * @dev Caution: + * When withdrawing ETH, it's crucial to make sure that the receiving address (`msg.sender`) on the root chain, + * if it's a contract, has a receive or fallback function that allows it to accept native ETH. + * If this isn't the case, the transaction on the root chain could revert, potentially locking the user's funds indefinitely. */ function withdrawETH(uint256 amount) external payable { _withdraw(childETHToken, msg.sender, amount); @@ -245,6 +271,10 @@ contract ChildERC20Bridge is /** * @inheritdoc IChildERC20Bridge + * @dev Caution: + * When withdrawing ETH, it's crucial to make sure that the receiving address (`receiver`) on the root chain, + * if it's a contract, has a receive or fallback function that allows it to accept native ETH. + * If this isn't the case, the transaction on the root chain could revert, potentially locking the user's funds indefinitely. */ function withdrawETHTo(address receiver, uint256 amount) external payable { _withdraw(childETHToken, receiver, amount); @@ -314,9 +344,12 @@ contract ChildERC20Bridge is * @notice Private function to handle withdrawal of L1 native ETH. */ function _withdrawETH(uint256 amount) private returns (address) { - if (!IChildERC20(childETHToken).burn(msg.sender, amount)) { + try IChildERC20(childETHToken).burn(msg.sender, amount) returns (bool success) { + if (!success) revert BurnFailed(); + } catch { revert BurnFailed(); } + return NATIVE_ETH; } @@ -330,7 +363,9 @@ contract ChildERC20Bridge is IWIMX wIMX = IWIMX(wIMXToken); // Transfer to contract - if (!wIMX.transferFrom(msg.sender, address(this), amount)) { + try wIMX.transferFrom(msg.sender, address(this), amount) returns (bool success) { + if (!success) revert TransferWIMXFailed(); + } catch { revert TransferWIMXFailed(); } @@ -372,7 +407,9 @@ contract ChildERC20Bridge is } // Burn tokens - if (!IChildERC20(childToken).burn(msg.sender, amount)) { + try IChildERC20(childToken).burn(msg.sender, amount) returns (bool success) { + if (!success) revert BurnFailed(); + } catch { revert BurnFailed(); } @@ -459,10 +496,10 @@ contract ChildERC20Bridge is revert ZeroAddress(); } - transferTokensAndEmitEvent(rootToken, rootTokenToChildToken[rootToken], sender, receiver, amount); + _transferTokensAndEmitEvent(rootToken, rootTokenToChildToken[rootToken], sender, receiver, amount); } - function transferTokensAndEmitEvent( + function _transferTokensAndEmitEvent( address rootToken, address childToken, address sender, @@ -486,7 +523,9 @@ contract ChildERC20Bridge is revert EmptyTokenContract(); } - if (!IChildERC20(childToken).mint(receiver, amount)) { + try IChildERC20(childToken).mint(receiver, amount) returns (bool success) { + if (!success) revert MintFailed(); + } catch { revert MintFailed(); } diff --git a/src/common/AdaptorRoles.sol b/src/common/AdaptorRoles.sol index 18784f45..519fbf2e 100644 --- a/src/common/AdaptorRoles.sol +++ b/src/common/AdaptorRoles.sol @@ -24,14 +24,14 @@ abstract contract AdaptorRoles is AccessControlUpgradeable { /** * @notice Function to grant bridge manager role to an address */ - function grantBridgeManager(address account) external onlyRole(DEFAULT_ADMIN_ROLE) { + function grantBridgeManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) { grantRole(BRIDGE_MANAGER_ROLE, account); } /** * @notice Function to grant gas service manager role to an address */ - function grantGasServiceManager(address account) external onlyRole(DEFAULT_ADMIN_ROLE) { + function grantGasServiceManagerRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) { grantRole(GAS_SERVICE_MANAGER_ROLE, account); } diff --git a/src/interfaces/child/IChildAxelarBridgeAdaptor.sol b/src/interfaces/child/IChildAxelarBridgeAdaptor.sol index ce565ab0..290c1419 100644 --- a/src/interfaces/child/IChildAxelarBridgeAdaptor.sol +++ b/src/interfaces/child/IChildAxelarBridgeAdaptor.sol @@ -71,6 +71,10 @@ interface IChildAxelarBridgeAdaptorErrors { error InvalidSourceChain(); /// @notice Error when the source chain's message sender is not a recognised address. error InvalidSourceAddress(); + /// @notice Error when the an unauthorized initializer tries to initialize the contract. + error UnauthorizedInitializer(); + /// @notice Error when a function that isn't supported by the adaptor is called. + error UnsupportedOperation(); } /** diff --git a/src/interfaces/child/IChildERC20Bridge.sol b/src/interfaces/child/IChildERC20Bridge.sol index 6c3ba9bb..db898eaa 100644 --- a/src/interfaces/child/IChildERC20Bridge.sol +++ b/src/interfaces/child/IChildERC20Bridge.sol @@ -196,4 +196,6 @@ interface IChildERC20BridgeErrors { error NonWrappedNativeTransfer(); /// @notice Error when the bridge doesn't have enough native IMX to support the deposit. error InsufficientIMX(); + /// @notice Error when the an unauthorized initializer tries to initialize the contract. + error UnauthorizedInitializer(); } diff --git a/src/interfaces/root/IRootAxelarBridgeAdaptor.sol b/src/interfaces/root/IRootAxelarBridgeAdaptor.sol index e789a555..1288008f 100644 --- a/src/interfaces/root/IRootAxelarBridgeAdaptor.sol +++ b/src/interfaces/root/IRootAxelarBridgeAdaptor.sol @@ -75,6 +75,10 @@ interface IRootAxelarBridgeAdaptorErrors { error InvalidSourceAddress(); /// @notice Error when a message received has invalid source chain. error InvalidSourceChain(); + /// @notice Error when the an unauthorized initializer tries to initialize the contract. + error UnauthorizedInitializer(); + /// @notice Error when a function that isn't supported by the adaptor is called. + error UnsupportedOperation(); } /** diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index cd0ea05d..eac5809b 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -50,14 +50,21 @@ interface IRootERC20Bridge { function onMessageReceive(bytes calldata data) external; /** - * @notice Initiate sending a mapToken message to the child chain. - * This is done when a token hasn't been mapped before. - * @dev Populates a root token => child token mapping on parent chain before - * sending a message telling child chain to do the same. + * @notice Initiates sending a mapToken message to the child chain, if the token hasn't been mapped before. + * This operation requires the `rootToken` to have the following public getter functions: `name()`, `symbol()`, and `decimals()`. + * These functions are optional in the ERC20 standard. If the token does not provide these functions, + * the mapping operation will fail and return a `TokenNotSupported` error. + * + * @dev The function: + * - fails with a `AlreadyMapped` error if the token has already been mapped. + * - populates a root token => child token mapping on the root chain before + * sending a message telling the child chain to do the same. + * - is `payable` because the message passing protocol requires a fee to be paid. + * * @dev The address of the child chain token is deterministic using CREATE2. + * * @param rootToken The address of the token on the root chain. * @return childToken The address of the token to be deployed on the child chain. - * @dev The function is `payable` because the message passing protocol requires a fee to be paid. */ function mapToken(IERC20Metadata rootToken) external payable returns (address); @@ -189,4 +196,8 @@ interface IRootERC20BridgeErrors { error ImxDepositLimitTooLow(); /// @notice Error when native transfer is sent to contract from non wrapped-token address. error NonWrappedNativeTransfer(); + /// @notice Error when the an unauthorized initializer tries to initialize the contract. + error UnauthorizedInitializer(); + /// @notice Error when attempt to map a ERC20 token that doesn't support name(), symbol() or decimals(). + error TokenNotSupported(); } diff --git a/src/lib/EIP712MetaTransaction.sol b/src/lib/EIP712MetaTransaction.sol index 309f9b41..b42f8ced 100644 --- a/src/lib/EIP712MetaTransaction.sol +++ b/src/lib/EIP712MetaTransaction.sol @@ -8,6 +8,7 @@ contract EIP712MetaTransaction is EIP712Upgradeable { bytes32 private constant META_TRANSACTION_TYPEHASH = keccak256(bytes("MetaTransaction(uint256 nonce,address from,bytes functionSignature)")); + /// @dev Event emitted when a meta transaction is successfully executed. event MetaTransactionExecuted(address userAddress, address relayerAddress, bytes functionSignature); mapping(address => uint256) private nonces; @@ -23,6 +24,16 @@ contract EIP712MetaTransaction is EIP712Upgradeable { bytes functionSignature; } + /** + * @notice Executes a meta transaction on behalf of the user. + * @dev This function allows a user to sign a transaction off-chain and have it executed by another entity. + * @param userAddress The address of the user initiating the transaction. + * @param functionSignature The signature of the function to be executed. + * @param sigR Part of the signature data. + * @param sigS Part of the signature data. + * @param sigV Recovery byte of the signature. + * @return returnData The bytes returned from the executed function. + */ function executeMetaTransaction( address userAddress, bytes calldata functionSignature, @@ -53,12 +64,18 @@ contract EIP712MetaTransaction is EIP712Upgradeable { } /** - * @dev Invalidates next "offset" number of nonces for the calling address + * @notice Invalidates next "offset" number of nonces for the calling address + * @param offset The number of nonces, from the current nonce, to invalidate. */ function invalidateNext(uint256 offset) external { nonces[msg.sender] += offset; } + /** + * @notice Retrieves the current nonce for a user. + * @param user The address of the user. + * @return nonce The current nonce of the user. + */ function getNonce(address user) external view returns (uint256 nonce) { nonce = nonces[user]; } @@ -79,11 +96,21 @@ contract EIP712MetaTransaction is EIP712Upgradeable { return sender; } + /** + * @notice Verifies the signature of a meta transaction. + * @param user The address of the user. + * @param metaTx The meta transaction struct. + * @param sigR Part of the signature data. + * @param sigS Part of the signature data. + * @param sigV Recovery byte of the signature. + * @return True if the signature is valid, false otherwise. + */ function _verify(address user, MetaTransaction memory metaTx, bytes32 sigR, bytes32 sigS, uint8 sigV) private view returns (bool) { + // The inclusion of a user specific nonce removes signature malleability concerns address signer = ecrecover(_hashTypedDataV4(_hashMetaTransaction(metaTx)), sigV, sigR, sigS); require(signer != address(0), "Invalid signature"); return signer == user; @@ -95,12 +122,16 @@ contract EIP712MetaTransaction is EIP712Upgradeable { ); } + /** + * @dev Extract the first four bytes from `inBytes` + */ function _convertBytesToBytes4(bytes memory inBytes) private pure returns (bytes4 outBytes4) { if (inBytes.length == 0) { return 0x0; } // slither-disable-next-line assembly assembly { + // extract the first 4 bytes from inBytes // solhint-disable no-inline-assembly outBytes4 := mload(add(inBytes, 32)) } diff --git a/src/lib/WETH.sol b/src/lib/WETH.sol index 4bdf6c15..88c5d5f9 100644 --- a/src/lib/WETH.sol +++ b/src/lib/WETH.sol @@ -39,7 +39,7 @@ contract WETH is IWETH { require(balanceOf[msg.sender] >= wad, "Wrapped ETH: Insufficient balance"); balanceOf[msg.sender] -= wad; - Address.sendValue(payable(msg.sender), wad); + payable(msg.sender).transfer(wad); emit Withdrawal(msg.sender, wad); } diff --git a/src/root/RootAxelarBridgeAdaptor.sol b/src/root/RootAxelarBridgeAdaptor.sol index 22266d82..df31dea1 100644 --- a/src/root/RootAxelarBridgeAdaptor.sol +++ b/src/root/RootAxelarBridgeAdaptor.sol @@ -50,27 +50,43 @@ contract RootAxelarBridgeAdaptor is /// @notice Address of the Axelar Gas Service contract. IAxelarGasService public gasService; - constructor(address _gateway) AxelarExecutable(_gateway) {} + /// @notice Address of the authorized initializer. + address public immutable initializerAddress; + + /** + * @notice Constructs the RootAxelarBridgeAdaptor contract. + * @param _gateway The address of the Axelar gateway contract. + * @param _initializerAddress The address of the authorized initializer. + */ + constructor(address _gateway, address _initializerAddress) AxelarExecutable(_gateway) { + if (_initializerAddress == address(0)) { + revert ZeroAddresses(); + } + initializerAddress = _initializerAddress; + } /** * @notice Initialization function for RootAxelarBridgeAdaptor. - * @param _roles Struct containing addresses of roles. + * @param _adaptorRoles Struct containing addresses of roles. * @param _rootBridge Address of root bridge contract. * @param _childChainId Axelar's ID for the child chain. * @param _childBridgeAdaptor Address of the bridge adaptor on the child chain. * @param _gasService Address of Axelar Gas Service contract. */ function initialize( - InitializationRoles memory _roles, + InitializationRoles memory _adaptorRoles, address _rootBridge, string memory _childChainId, string memory _childBridgeAdaptor, address _gasService ) public initializer { + if (msg.sender != initializerAddress) { + revert UnauthorizedInitializer(); + } if ( - _rootBridge == address(0) || _gasService == address(0) || _roles.defaultAdmin == address(0) - || _roles.bridgeManager == address(0) || _roles.gasServiceManager == address(0) - || _roles.targetManager == address(0) + _rootBridge == address(0) || _gasService == address(0) || _adaptorRoles.defaultAdmin == address(0) + || _adaptorRoles.bridgeManager == address(0) || _adaptorRoles.gasServiceManager == address(0) + || _adaptorRoles.targetManager == address(0) ) { revert ZeroAddresses(); } @@ -85,10 +101,10 @@ contract RootAxelarBridgeAdaptor is __AccessControl_init(); - _grantRole(DEFAULT_ADMIN_ROLE, _roles.defaultAdmin); - _grantRole(BRIDGE_MANAGER_ROLE, _roles.bridgeManager); - _grantRole(GAS_SERVICE_MANAGER_ROLE, _roles.gasServiceManager); - _grantRole(TARGET_MANAGER_ROLE, _roles.targetManager); + _grantRole(DEFAULT_ADMIN_ROLE, _adaptorRoles.defaultAdmin); + _grantRole(BRIDGE_MANAGER_ROLE, _adaptorRoles.bridgeManager); + _grantRole(GAS_SERVICE_MANAGER_ROLE, _adaptorRoles.gasServiceManager); + _grantRole(TARGET_MANAGER_ROLE, _adaptorRoles.targetManager); rootBridge = IRootERC20Bridge(_rootBridge); childChainId = _childChainId; @@ -198,6 +214,19 @@ contract RootAxelarBridgeAdaptor is rootBridge.onMessageReceive(_payload); } + /** + * @inheritdoc AxelarExecutable + * @dev This function is called by the parent `AxelarExecutable` contract's `executeWithToken()` function. + * However, this function is not required for the bridge, and thus reverts with an `UnsupportedOperation` error. + */ + function _executeWithToken(string calldata, string calldata, bytes calldata, string calldata, uint256) + internal + pure + override + { + revert UnsupportedOperation(); + } + // slither-disable-next-line unused-state,naming-convention uint256[50] private __gapRootAxelarBridgeAdaptor; } diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index 1f6cb5dd..44b1f394 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -16,6 +16,7 @@ import { import {IRootBridgeAdaptor} from "../interfaces/root/IRootBridgeAdaptor.sol"; import {IWETH} from "../interfaces/root/IWETH.sol"; import {BridgeRoles} from "../common/BridgeRoles.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; /** * @title Root ERC20 Bridge @@ -39,10 +40,16 @@ import {BridgeRoles} from "../common/BridgeRoles.sol"; * - An account with a VARIABLE_MANAGER_ROLE can update the cumulative IMX deposit limit. * - An account with an ADAPTOR_MANAGER_ROLE can update the root bridge adaptor address. * - An account with a DEFAULT_ADMIN_ROLE can grant and revoke roles. - * @dev Note: + * + * @dev Caution: + * - When depositing IMX (L1 -> L2) it's crucial to make sure that the receiving address on the child chain, + * if it's a contract, has a receive or fallback function that allows it to accept native IMX on the child chain. + * If this isn't the case, the transaction on the child chain could revert, potentially locking the user's funds indefinitely. * - There is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care. - * - This is an upgradeable contract that should be operated behind OpenZeppelin's TransparentUpgradeableProxy. * - The initialize function is susceptible to front running, so precautions should be taken to account for this scenario. + * + * @dev Note: + * - This is an upgradeable contract that should be operated behind OpenZeppelin's TransparentUpgradeableProxy. */ contract RootERC20Bridge is BridgeRoles, @@ -82,6 +89,8 @@ contract RootERC20Bridge is /// @dev The maximum cumulative amount of IMX that can be deposited into the bridge. /// @dev A limit of zero indicates unlimited. uint256 public imxCumulativeDepositLimit; + /// @dev Address of the authorized initializer. + address public immutable initializerAddress; /** * @notice Modifier to ensure that the caller is the registered root bridge adaptor. @@ -93,6 +102,17 @@ contract RootERC20Bridge is _; } + /** + * @notice Constructs the RootERC20Bridge contract. + * @param _initializerAddress The address of the authorized initializer. + */ + constructor(address _initializerAddress) { + if (_initializerAddress == address(0)) { + revert ZeroAddress(); + } + initializerAddress = _initializerAddress; + } + /** * @notice Initialization function for RootERC20Bridge. * @param newRoles Struct containing addresses of roles. @@ -143,6 +163,9 @@ contract RootERC20Bridge is address newRootWETHToken, uint256 newImxCumulativeDepositLimit ) internal { + if (msg.sender != initializerAddress) { + revert UnauthorizedInitializer(); + } if ( newRootBridgeAdaptor == address(0) || newChildERC20Bridge == address(0) || newChildTokenTemplate == address(0) || newRootIMXToken == address(0) || newRootWETHToken == address(0) @@ -230,7 +253,7 @@ contract RootERC20Bridge is * The unwrapping is done through the WETH contract's `withdraw()` function, which sends the native ETH to this bridge contract. * The only reason this `receive()` function is needed is for this process, hence the validation ensures that the sender is the WETH contract. */ - receive() external payable whenNotPaused { + receive() external payable { // Revert if sender is not the WETH token address if (msg.sender != rootWETHToken) { revert NonWrappedNativeTransfer(); @@ -281,7 +304,11 @@ contract RootERC20Bridge is /** * @inheritdoc IRootERC20Bridge - * @dev Note that there is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care. + * @dev Caution: + * - When depositing IMX, it's crucial to make sure that the receiving address (`msg.sender`) on the child chain, + * if it's a contract, has a receive or fallback function that allows it to accept native IMX. + * If this isn't the case, the transaction on the child chain could revert, potentially locking the user's funds indefinitely. + * - Note that there is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care. */ function deposit(IERC20Metadata rootToken, uint256 amount) external payable override { _depositToken(rootToken, msg.sender, amount); @@ -289,7 +316,11 @@ contract RootERC20Bridge is /** * @inheritdoc IRootERC20Bridge - * @dev Note that there is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care. + * @dev Caution: + * - When depositing IMX, it's crucial to make sure that the receiving address (`receiver`) on the child chain, + * if it's a contract, has a receive or fallback function that allows it to accept native IMX. + * If this isn't the case, the transaction on the child chain could revert, potentially locking the user's funds indefinitely. + * - Note that there is undefined behaviour for bridging non-standard ERC20 tokens (e.g. rebasing tokens). Please approach such cases with great care. */ function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external payable override { _depositToken(rootToken, receiver, amount); @@ -373,8 +404,9 @@ contract RootERC20Bridge is rootTokenToChildToken[address(rootToken)] = childToken; - bytes memory payload = - abi.encode(MAP_TOKEN_SIG, rootToken, rootToken.name(), rootToken.symbol(), rootToken.decimals()); + (string memory tokenName, string memory tokenSymbol, uint8 tokenDecimals) = _getTokenDetails(rootToken); + + bytes memory payload = abi.encode(MAP_TOKEN_SIG, rootToken, tokenName, tokenSymbol, tokenDecimals); rootBridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender); emit L1TokenMapped(address(rootToken), childToken); @@ -484,6 +516,31 @@ contract RootERC20Bridge is } } + function _getTokenDetails(IERC20Metadata token) private view returns (string memory, string memory, uint8) { + string memory tokenName; + try token.name() returns (string memory name) { + tokenName = name; + } catch { + revert TokenNotSupported(); + } + + string memory tokenSymbol; + try token.symbol() returns (string memory symbol) { + tokenSymbol = symbol; + } catch { + revert TokenNotSupported(); + } + + uint8 tokenDecimals; + try token.decimals() returns (uint8 decimals) { + tokenDecimals = decimals; + } catch { + revert TokenNotSupported(); + } + + return (tokenName, tokenSymbol, tokenDecimals); + } + modifier wontIMXOverflow(address rootToken, uint256 amount) { // Assert whether the deposit is root IMX address imxToken = rootIMXToken; diff --git a/src/root/flowrate/RootERC20BridgeFlowRate.sol b/src/root/flowrate/RootERC20BridgeFlowRate.sol index dd7bf0e0..1f05a872 100644 --- a/src/root/flowrate/RootERC20BridgeFlowRate.sol +++ b/src/root/flowrate/RootERC20BridgeFlowRate.sol @@ -80,6 +80,8 @@ contract RootERC20BridgeFlowRate is // Map ERC 20 token address to threshold mapping(address => uint256) public largeTransferThresholds; + constructor(address _initializerAddress) RootERC20Bridge(_initializerAddress) {} + function initialize( InitializationRoles memory newRoles, address newRootBridgeAdaptor, diff --git a/test/integration/child/ChildAxelarBridge.t.sol b/test/integration/child/ChildAxelarBridge.t.sol index c142e21b..2b5e538c 100644 --- a/test/integration/child/ChildAxelarBridge.t.sol +++ b/test/integration/child/ChildAxelarBridge.t.sol @@ -39,10 +39,10 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil childERC20 = new ChildERC20(); childERC20.initialize(address(123), "Test", "TST", 18); - childERC20Bridge = new ChildERC20Bridge(); + childERC20Bridge = new ChildERC20Bridge(address(this)); mockChildAxelarGateway = new MockChildAxelarGateway(); mockChildAxelarGasService = new MockChildAxelarGasService(); - childAxelarBridgeAdaptor = new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway)); + childAxelarBridgeAdaptor = new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway), address(this)); IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ defaultAdmin: address(this), diff --git a/test/mocks/child/MockChildAxelarGateway.sol b/test/mocks/child/MockChildAxelarGateway.sol index 15408557..7ec086b7 100644 --- a/test/mocks/child/MockChildAxelarGateway.sol +++ b/test/mocks/child/MockChildAxelarGateway.sol @@ -6,5 +6,13 @@ contract MockChildAxelarGateway { return true; } + function validateContractCallAndMint(bytes32, string calldata, string calldata, bytes32, string calldata, uint256) + external + pure + returns (bool) + { + return true; + } + function callContract(string memory childChain, string memory childBridgeAdaptor, bytes memory payload) external {} } diff --git a/test/mocks/root/MockAxelarGateway.sol b/test/mocks/root/MockAxelarGateway.sol index 2d3328d5..6c42e433 100644 --- a/test/mocks/root/MockAxelarGateway.sol +++ b/test/mocks/root/MockAxelarGateway.sol @@ -8,4 +8,12 @@ contract MockAxelarGateway { function validateContractCall(bytes32, string calldata, string calldata, bytes32) external pure returns (bool) { return true; } + + function validateContractCallAndMint(bytes32, string calldata, string calldata, bytes32, string calldata, uint256) + external + pure + returns (bool) + { + return true; + } } diff --git a/test/unit/child/ChildAxelarBridgeAdaptor.t.sol b/test/unit/child/ChildAxelarBridgeAdaptor.t.sol index 94e505e8..87010030 100644 --- a/test/unit/child/ChildAxelarBridgeAdaptor.t.sol +++ b/test/unit/child/ChildAxelarBridgeAdaptor.t.sol @@ -43,7 +43,7 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro mockChildERC20Bridge = new MockChildERC20Bridge(); mockChildAxelarGateway = new MockChildAxelarGateway(); mockChildAxelarGasService = new MockChildAxelarGasService(); - axelarAdaptor = new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway)); + axelarAdaptor = new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway), address(this)); axelarAdaptor.initialize( roles, address(mockChildERC20Bridge), @@ -76,8 +76,26 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro ); } + function test_RevertIf_ZeroInitializerIsGiven() public { + vm.expectRevert(ZeroAddress.selector); + new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(0)); + } + + function test_RevertIf_InitializeWithUnauthorizedInitializer() public { + ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(this)); + vm.prank(address(0x1234)); + vm.expectRevert(UnauthorizedInitializer.selector); + newAdaptor.initialize( + roles, + address(mockChildERC20Bridge), + ROOT_CHAIN_NAME, + ROOT_BRIDGE_ADAPTOR, + address(mockChildAxelarGasService) + ); + } + function test_RevertIf_InitializeWithZeroAdmin() public { - ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS); + ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(this)); vm.expectRevert(ZeroAddress.selector); roles.defaultAdmin = address(0); newAdaptor.initialize( @@ -90,7 +108,7 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro } function test_RevertIf_InitializeWithZeroBridgeManager() public { - ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS); + ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(this)); vm.expectRevert(ZeroAddress.selector); roles.bridgeManager = address(0); newAdaptor.initialize( @@ -103,7 +121,7 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro } function test_RevertIf_InitializeWithZeroGasServiceManager() public { - ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS); + ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(this)); vm.expectRevert(ZeroAddress.selector); roles.gasServiceManager = address(0); newAdaptor.initialize( @@ -116,7 +134,7 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro } function test_RevertIf_InitializeWithZeroTargetManager() public { - ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS); + ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(this)); vm.expectRevert(ZeroAddress.selector); roles.targetManager = address(0); newAdaptor.initialize( @@ -129,7 +147,7 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro } function test_RevertIf_InitializeGivenZeroAddress() public { - ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS); + ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(this)); vm.expectRevert(ZeroAddress.selector); newAdaptor.initialize( roles, address(0), ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, address(mockChildAxelarGasService) @@ -137,7 +155,7 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro } function test_RevertIf_InitializeGivenEmptyRootChain() public { - ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS); + ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(this)); vm.expectRevert(InvalidRootChain.selector); newAdaptor.initialize( roles, address(mockChildERC20Bridge), "", ROOT_BRIDGE_ADAPTOR, address(mockChildAxelarGasService) @@ -145,7 +163,7 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro } function test_RevertIf_InitializeGivenAnEmptyBridgeAdaptorString() public { - ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS); + ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(this)); vm.expectRevert(InvalidRootBridgeAdaptor.selector); newAdaptor.initialize( roles, address(mockChildERC20Bridge), ROOT_CHAIN_NAME, "", address(mockChildAxelarGasService) @@ -153,7 +171,7 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro } function test_RevertIf_InitializeGivenZeroGasService() public { - ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS); + ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS, address(this)); vm.expectRevert(ZeroAddress.selector); newAdaptor.initialize(roles, address(mockChildERC20Bridge), ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, address(0)); } @@ -489,4 +507,19 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro axelarAdaptor.updateGasService(address(0)); vm.stopPrank(); } + + /** + * UNSUPPORTED OPERATION + */ + + /// Check that executeWithToken function in AxelarExecutable cannot be called + function test_RevertIf_executeWithTokenCalled() public { + bytes32 commandId = bytes32("testCommandId"); + bytes memory payload = abi.encodePacked("payload"); + string memory tokenSymbol = "TST"; + uint256 amount = 100; + + vm.expectRevert(UnsupportedOperation.selector); + axelarAdaptor.executeWithToken(commandId, ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, payload, tokenSymbol, amount); + } } diff --git a/test/unit/child/ChildERC20.t.sol b/test/unit/child/ChildERC20.t.sol new file mode 100644 index 00000000..17996e10 --- /dev/null +++ b/test/unit/child/ChildERC20.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.19; + +import {Test} from "forge-std/Test.sol"; +import {ChildERC20} from "../../../src/child/ChildERC20.sol"; + +contract ChildERC20Test is Test { + string constant DEFAULT_CHILDERC20_NAME = "Child ERC20"; + string constant DEFAULT_CHILDERC20_SYMBOL = "CERC"; + uint8 constant DEFAULT_CHILDERC20_DECIMALS = 18; + address constant DEFAULT_CHILDERC20_ADDRESS = address(111); + + ChildERC20 public childToken; + + function setUp() public { + childToken = new ChildERC20(); + childToken.initialize( + DEFAULT_CHILDERC20_ADDRESS, DEFAULT_CHILDERC20_NAME, DEFAULT_CHILDERC20_SYMBOL, DEFAULT_CHILDERC20_DECIMALS + ); + } + + function test_InitialState() public { + assertEq(childToken.name(), DEFAULT_CHILDERC20_NAME, "Incorrect token name"); + assertEq(childToken.symbol(), DEFAULT_CHILDERC20_SYMBOL, "Incorrect token symbol"); + assertEq(childToken.decimals(), DEFAULT_CHILDERC20_DECIMALS, "Incorrect token decimals"); + assertEq(childToken.totalSupply(), 0, "Incorrect token supply"); + } + + function test_FailInitialisationBadAddress() public { + ChildERC20 failedToken = new ChildERC20(); + vm.expectRevert("ChildERC20: BAD_INITIALIZATION"); + failedToken.initialize( + address(0), DEFAULT_CHILDERC20_NAME, DEFAULT_CHILDERC20_SYMBOL, DEFAULT_CHILDERC20_DECIMALS + ); + } + + function test_FailInitialisationBadName() public { + ChildERC20 failedToken = new ChildERC20(); + vm.expectRevert("ChildERC20: BAD_INITIALIZATION"); + failedToken.initialize(DEFAULT_CHILDERC20_ADDRESS, "", DEFAULT_CHILDERC20_SYMBOL, DEFAULT_CHILDERC20_DECIMALS); + } + + function test_FailInitialisationBadSymbol() public { + ChildERC20 failedToken = new ChildERC20(); + vm.expectRevert("ChildERC20: BAD_INITIALIZATION"); + failedToken.initialize(DEFAULT_CHILDERC20_ADDRESS, DEFAULT_CHILDERC20_NAME, "", DEFAULT_CHILDERC20_DECIMALS); + } + + function test_RevertIf_InitializeTwice() public { + vm.expectRevert("Initializable: contract is already initialized"); + childToken.initialize( + DEFAULT_CHILDERC20_ADDRESS, DEFAULT_CHILDERC20_NAME, DEFAULT_CHILDERC20_SYMBOL, DEFAULT_CHILDERC20_DECIMALS + ); + } + + function test_RevertIf_MintTokensByNotDeployer() public { + address notDeployer = address(333); + address receiver = address(222); + vm.prank(notDeployer); + vm.expectRevert("ChildERC20: Only bridge can call"); + childToken.mint(receiver, 100); + } + + function test_MintSuccess() public { + uint256 mintAmount = 1000000; + address receiver = address(222); + + uint256 receiverPreBal = childToken.balanceOf(receiver); + + childToken.mint(receiver, mintAmount); + + uint256 receiverPostBal = childToken.balanceOf(receiver); + + assertEq(childToken.totalSupply(), mintAmount, "Incorrect token supply"); + assertEq(receiverPostBal, receiverPreBal + mintAmount, "Incorrect token balance"); + } + + function test_RevertIf_BurnTokensByNotDeployer() public { + address notDeployer = address(333); + address receiver = address(222); + vm.prank(notDeployer); + vm.expectRevert("ChildERC20: Only bridge can call"); + childToken.burn(receiver, 100); + } + + function test_BurnSuccess() public { + uint256 mintAmount = 1000000; + uint256 burnAmount = 1000; + address receiver = address(222); + + childToken.mint(receiver, mintAmount); + + uint256 receiverPreBurnBal = childToken.balanceOf(receiver); + + childToken.burn(receiver, burnAmount); + + uint256 receiverPostBurnBal = childToken.balanceOf(receiver); + + assertEq(childToken.totalSupply(), mintAmount - burnAmount, "Incorrect token supply"); + assertEq(receiverPostBurnBal, receiverPreBurnBal - burnAmount, "Incorrect token balance"); + } +} diff --git a/test/unit/child/ChildERC20Bridge.t.sol b/test/unit/child/ChildERC20Bridge.t.sol index 85386706..e93f334e 100644 --- a/test/unit/child/ChildERC20Bridge.t.sol +++ b/test/unit/child/ChildERC20Bridge.t.sol @@ -59,7 +59,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B childTokenTemplate = new ChildERC20(); childTokenTemplate.initialize(address(123), "Test", "TST", 18); - childBridge = new ChildERC20Bridge(); + childBridge = new ChildERC20Bridge(address(this)); childBridge.initialize(roles, address(this), address(childTokenTemplate), ROOT_IMX_TOKEN, CHILD_WIMX_TOKEN); } @@ -164,60 +164,72 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B assert(childBridge.rootTokenToChildToken(NATIVE_ETH) != address(0)); } + function test_RevertIf_ZeroInitializerIsGiven() public { + vm.expectRevert(ZeroAddress.selector); + new ChildERC20Bridge(address(0)); + } + + function test_RevertIf_InitializeWithUnauthorizedInitializer() public { + ChildERC20Bridge bridge = new ChildERC20Bridge(address(this)); + vm.prank(address(0x1234)); + vm.expectRevert(UnauthorizedInitializer.selector); + bridge.initialize(roles, address(1), address(1), address(1), address(1)); + } + function test_RevertIfInitializeTwice() public { vm.expectRevert("Initializable: contract is already initialized"); childBridge.initialize(roles, address(this), address(childTokenTemplate), ROOT_IMX_TOKEN, CHILD_WIMX_TOKEN); } function test_RevertIf_InitializeWithAZeroAddressDefaultAdmin() public { - ChildERC20Bridge bridge = new ChildERC20Bridge(); + ChildERC20Bridge bridge = new ChildERC20Bridge(address(this)); roles.defaultAdmin = address(0); vm.expectRevert(ZeroAddress.selector); bridge.initialize(roles, address(1), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressPauser() public { - ChildERC20Bridge bridge = new ChildERC20Bridge(); + ChildERC20Bridge bridge = new ChildERC20Bridge(address(this)); roles.pauser = address(0); vm.expectRevert(ZeroAddress.selector); bridge.initialize(roles, address(1), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressUnpauser() public { - ChildERC20Bridge bridge = new ChildERC20Bridge(); + ChildERC20Bridge bridge = new ChildERC20Bridge(address(this)); roles.unpauser = address(0); vm.expectRevert(ZeroAddress.selector); bridge.initialize(roles, address(1), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressAdapter() public { - ChildERC20Bridge bridge = new ChildERC20Bridge(); + ChildERC20Bridge bridge = new ChildERC20Bridge(address(this)); roles.adaptorManager = address(0); vm.expectRevert(ZeroAddress.selector); bridge.initialize(roles, address(0), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressTreasuryManager() public { - ChildERC20Bridge bridge = new ChildERC20Bridge(); + ChildERC20Bridge bridge = new ChildERC20Bridge(address(this)); roles.treasuryManager = address(0); vm.expectRevert(ZeroAddress.selector); bridge.initialize(roles, address(1), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressChildTemplate() public { - ChildERC20Bridge bridge = new ChildERC20Bridge(); + ChildERC20Bridge bridge = new ChildERC20Bridge(address(this)); vm.expectRevert(ZeroAddress.selector); bridge.initialize(roles, address(1), address(0), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressIMXToken() public { - ChildERC20Bridge bridge = new ChildERC20Bridge(); + ChildERC20Bridge bridge = new ChildERC20Bridge(address(this)); vm.expectRevert(ZeroAddress.selector); bridge.initialize(roles, address(1), address(1), address(0), address(1)); } function test_RevertIf_InitializeWithAZeroAddressAll() public { - ChildERC20Bridge bridge = new ChildERC20Bridge(); + ChildERC20Bridge bridge = new ChildERC20Bridge(address(this)); vm.expectRevert(ZeroAddress.selector); roles.defaultAdmin = address(0); roles.pauser = address(0); @@ -628,7 +640,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B changePrank(attacker); // Execute withdraw - vm.expectRevert("ReentrancyGuard: reentrant call"); + vm.expectRevert(BurnFailed.selector); childBridge.withdraw{value: 1 ether}(ChildERC20(address(attackToken)), 100); } } diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdraw.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdraw.t.sol index 2af7e3fa..dd3dea5e 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdraw.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdraw.t.sol @@ -42,7 +42,7 @@ contract ChildERC20BridgeWithdrawUnitTest is Test, IChildERC20BridgeEvents, IChi initialDepositor: address(this), treasuryManager: address(this) }); - childBridge = new ChildERC20Bridge(); + childBridge = new ChildERC20Bridge(address(this)); childBridge.initialize( roles, address(mockAdaptor), address(childTokenTemplate), ROOT_IMX_TOKEN, WIMX_TOKEN_ADDRESS ); diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawETH.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawETH.t.sol index 92680ec5..b564870f 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawETH.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawETH.t.sol @@ -28,7 +28,7 @@ contract ChildERC20BridgeWithdrawETHUnitTest is Test, IChildERC20BridgeEvents, I mockAdaptor = new MockAdaptor(); - childBridge = new ChildERC20Bridge(); + childBridge = new ChildERC20Bridge(address(this)); IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: pauser, @@ -74,7 +74,7 @@ contract ChildERC20BridgeWithdrawETHUnitTest is Test, IChildERC20BridgeEvents, I uint256 withdrawAmount = 101 ether; uint256 withdrawFee = 300; - vm.expectRevert(bytes("ERC20: burn amount exceeds balance")); + vm.expectRevert(BurnFailed.selector); childBridge.withdrawETH{value: withdrawFee}(withdrawAmount); } diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawETHTo.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawETHTo.t.sol index 7c474695..21708c0f 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawETHTo.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawETHTo.t.sol @@ -37,7 +37,7 @@ contract ChildERC20BridgeWithdrawETHToUnitTest is Test, IChildERC20BridgeEvents, mockAdaptor = new MockAdaptor(); - childBridge = new ChildERC20Bridge(); + childBridge = new ChildERC20Bridge(address(this)); IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: pauser, @@ -94,7 +94,7 @@ contract ChildERC20BridgeWithdrawETHToUnitTest is Test, IChildERC20BridgeEvents, uint256 withdrawAmount = 101 ether; uint256 withdrawFee = 300; - vm.expectRevert(bytes("ERC20: burn amount exceeds balance")); + vm.expectRevert(BurnFailed.selector); childBridge.withdrawETHTo{value: withdrawFee}(address(this), withdrawAmount); } diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMX.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMX.t.sol index 914665a9..c48c08d8 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMX.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMX.t.sol @@ -27,7 +27,7 @@ contract ChildERC20BridgeWithdrawIMXUnitTest is Test, IChildERC20BridgeEvents, I mockAdaptor = new MockAdaptor(); - childBridge = new ChildERC20Bridge(); + childBridge = new ChildERC20Bridge(address(this)); IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: pauser, diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMXTo.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMXTo.t.sol index 218a35b7..b2fa6b72 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMXTo.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawIMXTo.t.sol @@ -31,7 +31,7 @@ contract ChildERC20BridgeWithdrawIMXToUnitTest is Test, IChildERC20BridgeEvents, mockAdaptor = new MockAdaptor(); - childBridge = new ChildERC20Bridge(); + childBridge = new ChildERC20Bridge(address(this)); IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: pauser, diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawTo.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawTo.t.sol index cecc7bb0..88cc2b63 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawTo.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawTo.t.sol @@ -42,7 +42,7 @@ contract ChildERC20BridgeWithdrawToUnitTest is Test, IChildERC20BridgeEvents, IC initialDepositor: address(this), treasuryManager: address(this) }); - childBridge = new ChildERC20Bridge(); + childBridge = new ChildERC20Bridge(address(this)); childBridge.initialize( roles, address(mockAdaptor), address(childTokenTemplate), ROOT_IMX_TOKEN, WIMX_TOKEN_ADDRESS ); diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMX.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMX.t.sol index a14fb6a1..0d1b314c 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMX.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMX.t.sol @@ -31,7 +31,7 @@ contract ChildERC20BridgeWithdrawWIMXUnitTest is Test, IChildERC20BridgeEvents, wIMXToken = new WIMX(); Address.sendValue(payable(wIMXToken), 100 ether); - childBridge = new ChildERC20Bridge(); + childBridge = new ChildERC20Bridge(address(this)); IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: pauser, @@ -74,7 +74,7 @@ contract ChildERC20BridgeWithdrawWIMXUnitTest is Test, IChildERC20BridgeEvents, uint256 withdrawFee = 300; wIMXToken.approve(address(childBridge), withdrawAmount); - vm.expectRevert(bytes("Wrapped IMX: Insufficient balance")); + vm.expectRevert(TransferWIMXFailed.selector); childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); } @@ -83,7 +83,7 @@ contract ChildERC20BridgeWithdrawWIMXUnitTest is Test, IChildERC20BridgeEvents, uint256 withdrawFee = 300; wIMXToken.approve(address(childBridge), withdrawAmount - 1); - vm.expectRevert(bytes("Wrapped IMX: Insufficient allowance")); + vm.expectRevert(TransferWIMXFailed.selector); childBridge.withdrawWIMX{value: withdrawFee}(withdrawAmount); } diff --git a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMXTo.t.sol b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMXTo.t.sol index 0ffe48c7..83a6da3f 100644 --- a/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMXTo.t.sol +++ b/test/unit/child/withdrawals/ChildERC20BridgeWithdrawWIMXTo.t.sol @@ -33,7 +33,7 @@ contract ChildERC20BridgeWithdrawWIMXToUnitTest is Test, IChildERC20BridgeEvents wIMXToken = new WIMX(); Address.sendValue(payable(wIMXToken), 100 ether); - childBridge = new ChildERC20Bridge(); + childBridge = new ChildERC20Bridge(address(this)); IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: pauser, @@ -83,7 +83,7 @@ contract ChildERC20BridgeWithdrawWIMXToUnitTest is Test, IChildERC20BridgeEvents uint256 withdrawFee = 300; wIMXToken.approve(address(childBridge), withdrawAmount); - vm.expectRevert(bytes("Wrapped IMX: Insufficient balance")); + vm.expectRevert(TransferWIMXFailed.selector); childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), withdrawAmount); } @@ -92,7 +92,7 @@ contract ChildERC20BridgeWithdrawWIMXToUnitTest is Test, IChildERC20BridgeEvents uint256 withdrawFee = 300; wIMXToken.approve(address(childBridge), withdrawAmount - 1); - vm.expectRevert(bytes("Wrapped IMX: Insufficient allowance")); + vm.expectRevert(TransferWIMXFailed.selector); childBridge.withdrawWIMXTo{value: withdrawFee}(address(this), withdrawAmount); } diff --git a/test/unit/common/AdaptorRoles.t.sol b/test/unit/common/AdaptorRoles.t.sol index 30760944..4ee88508 100644 --- a/test/unit/common/AdaptorRoles.t.sol +++ b/test/unit/common/AdaptorRoles.t.sol @@ -21,8 +21,8 @@ contract Setup is Test { contract AdaptorRoles is Setup { function grantRoles() internal { vm.startPrank(admin); - mockAdaptorRoles.grantBridgeManager(bridgeManager); - mockAdaptorRoles.grantGasServiceManager(gasServiceManager); + mockAdaptorRoles.grantBridgeManagerRole(bridgeManager); + mockAdaptorRoles.grantGasServiceManagerRole(gasServiceManager); mockAdaptorRoles.grantTargetManagerRole(targetManager); vm.stopPrank(); } diff --git a/test/unit/root/RootAxelarBridgeAdaptor.t.sol b/test/unit/root/RootAxelarBridgeAdaptor.t.sol index 1ac939ef..a9c3451c 100644 --- a/test/unit/root/RootAxelarBridgeAdaptor.t.sol +++ b/test/unit/root/RootAxelarBridgeAdaptor.t.sol @@ -44,7 +44,7 @@ contract RootAxelarBridgeAdaptorTest is Test, IRootAxelarBridgeAdaptorEvents, IR token = new ERC20PresetMinterPauser("Test", "TST"); mockAxelarGateway = new MockAxelarGateway(); axelarGasService = new MockAxelarGasService(); - axelarAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway)); + axelarAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); stubRootBridge = new StubRootBridge(); axelarAdaptor.initialize( roles, address(stubRootBridge), CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, address(axelarGasService) @@ -76,8 +76,22 @@ contract RootAxelarBridgeAdaptorTest is Test, IRootAxelarBridgeAdaptorEvents, IR ); } + function test_RevertIf_ZeroInitializerIsGiven() public { + vm.expectRevert(ZeroAddresses.selector); + new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(0)); + } + + function test_RevertIf_InitializeWithUnauthorizedInitializer() public { + RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); + vm.prank(address(0x1234)); + vm.expectRevert(UnauthorizedInitializer.selector); + newAdaptor.initialize( + roles, address(this), CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, address(axelarGasService) + ); + } + function test_RevertIf_InitializeWithZeroAdmin() public { - RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway)); + RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); vm.expectRevert(ZeroAddresses.selector); roles.defaultAdmin = address(0); newAdaptor.initialize( @@ -86,7 +100,7 @@ contract RootAxelarBridgeAdaptorTest is Test, IRootAxelarBridgeAdaptorEvents, IR } function test_RevertIf_InitializeWithZeroBridgeManager() public { - RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway)); + RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); vm.expectRevert(ZeroAddresses.selector); roles.bridgeManager = address(0); newAdaptor.initialize( @@ -95,7 +109,7 @@ contract RootAxelarBridgeAdaptorTest is Test, IRootAxelarBridgeAdaptorEvents, IR } function test_RevertIf_InitializeWithZeroGasServiceManager() public { - RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway)); + RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); vm.expectRevert(ZeroAddresses.selector); roles.gasServiceManager = address(0); newAdaptor.initialize( @@ -104,7 +118,7 @@ contract RootAxelarBridgeAdaptorTest is Test, IRootAxelarBridgeAdaptorEvents, IR } function test_RevertIf_InitializeWithZeroTargetManager() public { - RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway)); + RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); vm.expectRevert(ZeroAddresses.selector); roles.targetManager = address(0); newAdaptor.initialize( @@ -113,7 +127,7 @@ contract RootAxelarBridgeAdaptorTest is Test, IRootAxelarBridgeAdaptorEvents, IR } function test_RevertIf_InitializeGivenZeroAddress() public { - RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway)); + RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); vm.expectRevert(ZeroAddresses.selector); newAdaptor.initialize( roles, address(0), CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, address(axelarGasService) @@ -121,13 +135,13 @@ contract RootAxelarBridgeAdaptorTest is Test, IRootAxelarBridgeAdaptorEvents, IR } function test_RevertIf_InitializeGivenEmptyChildChainName() public { - RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway)); + RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); vm.expectRevert(InvalidChildChain.selector); newAdaptor.initialize(roles, address(this), "", CHILD_BRIDGE_ADAPTOR_STRING, address(axelarGasService)); } function test_RevertIf_InitializeGivenEmptyChildAdapter() public { - RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway)); + RootAxelarBridgeAdaptor newAdaptor = new RootAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); vm.expectRevert(InvalidChildBridgeAdaptor.selector); newAdaptor.initialize(roles, address(this), CHILD_CHAIN_NAME, "", address(axelarGasService)); } @@ -450,4 +464,21 @@ contract RootAxelarBridgeAdaptorTest is Test, IRootAxelarBridgeAdaptorEvents, IR vm.expectRevert(ZeroAddresses.selector); axelarAdaptor.updateGasService(address(0)); } + + /** + * UNSUPPORTED OPERATION + */ + + /// Check that executeWithToken function in AxelarExecutable cannot be called + function test_RevertIf_executeWithTokenCalled() public { + bytes32 commandId = bytes32("testCommandId"); + bytes memory payload = abi.encodePacked("payload"); + string memory tokenSymbol = "TST"; + uint256 amount = 100; + + vm.expectRevert(UnsupportedOperation.selector); + axelarAdaptor.executeWithToken( + commandId, CHILD_CHAIN_NAME, CHILD_BRIDGE_ADAPTOR_STRING, payload, tokenSymbol, amount + ); + } } diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 586cef74..51ab4364 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -55,7 +55,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH); - rootBridge = new RootERC20Bridge(); + rootBridge = new RootERC20Bridge(address(this)); mockAxelarGateway = new MockAxelarGateway(); axelarGasService = new MockAxelarGasService(); @@ -97,40 +97,30 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid assert(rootBridge.rootTokenToChildToken(NATIVE_ETH) != address(0)); } - function test_NativeTransferFromWETH() public { - address caller = address(0x123a); - payable(caller).transfer(2 ether); - // forge inspect src/root/RootERC20Bridge.sol:RootERC20Bridge storageLayout | grep -B3 -A5 -i "rootWETHToken" - uint256 wETHStorageSlot = 307; - vm.store(address(rootBridge), bytes32(wETHStorageSlot), bytes32(uint256(uint160(caller)))); - - vm.startPrank(caller); - uint256 bal = address(rootBridge).balance; - (bool ok,) = address(rootBridge).call{value: 1 ether}(""); - assert(ok); - uint256 postBal = address(rootBridge).balance; - - assertEq(bal + 1 ether, postBal, "balance not increased"); - } - function test_RevertI_fNativeTransferIsFromNonWETH() public { vm.expectRevert(NonWrappedNativeTransfer.selector); (bool ok,) = address(rootBridge).call{value: 1 ether}(""); assert(ok); } - function test_RevertIf_NativeTransferWhenPaused() public { - pause(IPausable(address(rootBridge))); - vm.expectRevert("Pausable: paused"); - (bool ok,) = address(rootBridge).call{value: 1 ether}(""); - assert(ok); + function test_RevertIf_ZeroInitializerIsGiven() public { + vm.expectRevert(ZeroAddress.selector); + new RootERC20Bridge(address(0)); } - function test_NativeTransferResumesFunctionalityAfterUnpausing() public { - test_RevertIf_NativeTransferWhenPaused(); - unpause(IPausable(address(rootBridge))); - // Expect success case to pass - test_NativeTransferFromWETH(); + function test_RevertIf_InitializeWithUnauthorizedInitializer() public { + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); + IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ + defaultAdmin: address(this), + pauser: address(this), + unpauser: address(this), + variableManager: address(this), + adaptorManager: address(this) + }); + + vm.prank(address(0x1234)); + vm.expectRevert(UnauthorizedInitializer.selector); + bridge.initialize(roles, address(1), address(1), address(1), address(1), address(1), UNLIMITED_IMX_DEPOSITS); } function test_RevertIf_InitializeTwice() public { @@ -155,7 +145,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_InitializeWithAZeroAddressDefaultAdmin() public { - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(0), pauser: address(this), @@ -169,7 +159,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_InitializeWithAZeroAddressPauser() public { - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(0), @@ -183,7 +173,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_InitializeWithAZeroAddressUnpauser() public { - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(this), @@ -196,7 +186,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_InitializeWithAZeroAddressVariableManager() public { - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(this), @@ -209,7 +199,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_InitializeWithAZeroAddressAdaptorManager() public { - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(this), @@ -222,7 +212,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_InitializeWithAZeroAddressRootAdapter() public { - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(this), @@ -235,7 +225,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_InitializeWithAZeroAddressChildBridge() public { - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(this), @@ -248,7 +238,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_InitializeWithAZeroAddressTokenTemplate() public { - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(this), @@ -261,7 +251,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid } function test_RevertIf_InitializeWithAZeroAddressIMXToken() public { - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(this), @@ -281,7 +271,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid variableManager: address(this), adaptorManager: address(this) }); - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); vm.expectRevert(ZeroAddress.selector); bridge.initialize(roles, address(1), address(1), address(1), address(1), address(0), UNLIMITED_IMX_DEPOSITS); } @@ -294,7 +284,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid variableManager: address(0), adaptorManager: address(0) }); - RootERC20Bridge bridge = new RootERC20Bridge(); + RootERC20Bridge bridge = new RootERC20Bridge(address(this)); vm.expectRevert(ZeroAddress.selector); bridge.initialize(roles, address(0), address(0), address(0), address(0), address(0), UNLIMITED_IMX_DEPOSITS); } @@ -441,6 +431,32 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.mapToken{value: 300}(IERC20Metadata(NATIVE_ETH)); } + function test_SucceedIf_mapTokenWithSupportedMethods() public { + rootBridge.mapToken{value: 300}(token); + } + + function test_RevertIf_mapTokenWithoutName() public { + vm.mockCallRevert(address(token), abi.encodeWithSelector(IERC20Metadata.name.selector), "Unsupported operation"); + vm.expectRevert(TokenNotSupported.selector); + rootBridge.mapToken{value: 300}(token); + } + + function test_RevertIf_mapTokenWithoutSymbol() public { + vm.mockCallRevert( + address(token), abi.encodeWithSelector(IERC20Metadata.symbol.selector), "Unsupported operation" + ); + vm.expectRevert(TokenNotSupported.selector); + rootBridge.mapToken{value: 300}(token); + } + + function test_RevertIf_mapTokenWithoutDecimals() public { + vm.mockCallRevert( + address(token), abi.encodeWithSelector(IERC20Metadata.decimals.selector), "Unsupported operation" + ); + vm.expectRevert(TokenNotSupported.selector); + rootBridge.mapToken{value: 300}(token); + } + function test_updateRootBridgeAdaptor_UpdatesRootBridgeAdaptor() public { address newAdaptorAddress = address(0x11111); diff --git a/test/unit/root/flowrate/RootERC20BridgeFlowRate.t.sol b/test/unit/root/flowrate/RootERC20BridgeFlowRate.t.sol index 9f76f984..1f435afb 100644 --- a/test/unit/root/flowrate/RootERC20BridgeFlowRate.t.sol +++ b/test/unit/root/flowrate/RootERC20BridgeFlowRate.t.sol @@ -136,7 +136,7 @@ contract RootERC20BridgeFlowRateUnitTest is bob = makeAddr("bob"); charlie = makeAddr("charlie"); - rootBridgeFlowRate = new RootERC20BridgeFlowRate(); + rootBridgeFlowRate = new RootERC20BridgeFlowRate(address(this)); mockAxelarGateway = new MockAxelarGateway(); axelarGasService = new MockAxelarGasService(); @@ -257,7 +257,7 @@ contract RootERC20BridgeFlowRateUnitTest is } function test_RevertIf_InitializeWithAZeroAddressRateAdmin() public { - RootERC20BridgeFlowRate newRootBridgeFlowRate = new RootERC20BridgeFlowRate(); + RootERC20BridgeFlowRate newRootBridgeFlowRate = new RootERC20BridgeFlowRate(address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(this), diff --git a/test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol b/test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol index 274b7871..7a8ae5b8 100644 --- a/test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol +++ b/test/unit/root/withdrawals/RootERC20BridgeWithdraw.t.sol @@ -45,7 +45,7 @@ contract RootERC20BridgeWithdrawUnitTest is Test, IRootERC20BridgeEvents, IRootE deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH); - rootBridge = new RootERC20Bridge(); + rootBridge = new RootERC20Bridge(address(this)); mockAxelarGateway = new MockAxelarGateway(); axelarGasService = new MockAxelarGasService(); diff --git a/test/utils.t.sol b/test/utils.t.sol index 77c117db..e557600e 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -66,8 +66,8 @@ contract Utils is Test { mockAxelarGateway = new MockAxelarGateway(); childTokenTemplate = new ChildERC20(); childTokenTemplate.initialize(address(1), "Test", "TST", 18); - childBridge = new ChildERC20Bridge(); - childBridgeAdaptor = new ChildAxelarBridgeAdaptor(address(mockAxelarGateway)); + childBridge = new ChildERC20Bridge(address(this)); + childBridgeAdaptor = new ChildAxelarBridgeAdaptor(address(mockAxelarGateway), address(this)); IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ defaultAdmin: address(this), pauser: address(this), @@ -124,11 +124,12 @@ contract Utils is Test { integrationTest.imxToken = ERC20PresetMinterPauser(imxTokenAddress); integrationTest.imxToken.mint(address(this), 1000000 ether); - integrationTest.rootBridgeFlowRate = new RootERC20BridgeFlowRate(); + integrationTest.rootBridgeFlowRate = new RootERC20BridgeFlowRate(address(this)); integrationTest.mockAxelarGateway = new MockAxelarGateway(); integrationTest.axelarGasService = new MockAxelarGasService(); - integrationTest.axelarAdaptor = new RootAxelarBridgeAdaptor(address(integrationTest.mockAxelarGateway)); + integrationTest.axelarAdaptor = + new RootAxelarBridgeAdaptor(address(integrationTest.mockAxelarGateway), address(this)); IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: address(this),