From 8b8df0f836128cb8d801718cf9b0b0b959948f80 Mon Sep 17 00:00:00 2001 From: Craig M Date: Wed, 25 Oct 2023 15:19:42 +1300 Subject: [PATCH 01/15] WIP --- .env.sample | 2 +- script/InitializeRootContracts.s.sol | 4 +- src/interfaces/root/IERC20.sol | 83 +++++++++++++++++ src/interfaces/root/IRootERC20Bridge.sol | 2 + src/interfaces/root/IWETH.sol | 30 +++++++ src/root/RootERC20Bridge.sol | 42 ++++++++- src/test/root/WETH.sol | 98 +++++++++++++++++++++ test/integration/root/RootERC20Bridge.t.sol | 11 +-- test/unit/root/RootERC20Bridge.t.sol | 95 +++++++++++++------- test/utils.t.sol | 31 ++++--- 10 files changed, 342 insertions(+), 56 deletions(-) create mode 100644 src/interfaces/root/IERC20.sol create mode 100644 src/interfaces/root/IWETH.sol create mode 100644 src/test/root/WETH.sol diff --git a/.env.sample b/.env.sample index f1854b16..ba6af658 100644 --- a/.env.sample +++ b/.env.sample @@ -11,4 +11,4 @@ CHILD_GAS_SERVICE_ADDRESS= ROOT_CHAIN_NAME= CHILD_CHAIN_NAME= ROOT_IMX_ADDRESS= -CHILD_ETH_ADDRESS= \ No newline at end of file +ROOT_WETH_ADDRESS= \ No newline at end of file diff --git a/script/InitializeRootContracts.s.sol b/script/InitializeRootContracts.s.sol index 56dc7e35..5c01698a 100644 --- a/script/InitializeRootContracts.s.sol +++ b/script/InitializeRootContracts.s.sol @@ -22,7 +22,7 @@ contract InitializeRootContracts is Script { string memory rootRpcUrl = vm.envString("ROOT_RPC_URL"); uint256 rootPrivateKey = vm.envUint("ROOT_PRIVATE_KEY"); address rootIMXToken = vm.envAddress("ROOT_IMX_ADDRESS"); - address childETHToken = vm.envAddress("CHILD_ETH_ADDRESS"); + address rootWETHToken = vm.envAddress("ROOT_WETH_ADDRESS"); /** * INITIALIZE ROOT CHAIN CONTRACTS @@ -31,7 +31,7 @@ contract InitializeRootContracts is Script { vm.startBroadcast(rootPrivateKey); rootERC20Bridge.initialize( - address(rootBridgeAdaptor), childERC20Bridge, childBridgeAdaptor, rootChainChildTokenTemplate, rootIMXToken + address(rootBridgeAdaptor), childERC20Bridge, childBridgeAdaptor, rootChainChildTokenTemplate, rootIMXToken, rootWETHToken ); rootBridgeAdaptor.setChildBridgeAdaptor(); diff --git a/src/interfaces/root/IERC20.sol b/src/interfaces/root/IERC20.sol new file mode 100644 index 00000000..3d0aa3bc --- /dev/null +++ b/src/interfaces/root/IERC20.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + error InvalidAccount(); + + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index 6e09c715..df9bd051 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -70,6 +70,8 @@ interface IRootERC20BridgeErrors { error CantMapIMX(); /// @notice Error when attempting to map ETH. error CantMapETH(); + /// @notice Error when attempting to map wETH. + error CantMapWETH(); /// @notice Error when token balance invariant check fails. error BalanceInvariantCheckFailed(uint256 actualBalance, uint256 expectedBalance); } diff --git a/src/interfaces/root/IWETH.sol b/src/interfaces/root/IWETH.sol new file mode 100644 index 00000000..4cc378ab --- /dev/null +++ b/src/interfaces/root/IWETH.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity ^0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @dev Interface of Wrapped ETH. + */ +interface IWETH is IERC20 { + /** + * @dev Emitted when `value` native ETH are deposited from `account`. + */ + event Deposit(address indexed account, uint256 value); + + /** + * @dev Emitted when `value` wETH tokens are withdrawn to `account`. + */ + event Withdrawal(address indexed account, uint256 value); + + /** + * @notice Deposit native ETH in the function call and mint the equal amount of wrapped ETH to msg.sender. + */ + function deposit() external payable; + + /** + * @notice Withdraw given amount of native ETH to msg.sender and burn the equal amount of wrapped ETH. + * @param value The amount to withdraw. + */ + function withdraw(uint256 value) external; +} diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index 17aab64b..36544fc0 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -12,6 +12,8 @@ import {IRootERC20Bridge, IERC20Metadata} from "../interfaces/root/IRootERC20Bri import {IRootERC20BridgeEvents, IRootERC20BridgeErrors} from "../interfaces/root/IRootERC20Bridge.sol"; import {IRootERC20BridgeAdaptor} from "../interfaces/root/IRootERC20BridgeAdaptor.sol"; import {IChildERC20} from "../interfaces/child/IChildERC20.sol"; +import {IWETH} from "../interfaces/root/IWETH.sol"; +import {console2} from "forge-std/Test.sol"; /** * @notice RootERC20Bridge is a bridge that allows ERC20 tokens to be transferred from the root chain to the child chain. @@ -48,6 +50,8 @@ contract RootERC20Bridge is address public rootIMXToken; /// @dev The address of the ETH ERC20 token on L2. address public childETHToken; + /// @dev The address of the wETH ERC20 token on L1. + address public rootWETHToken; /** * @notice Initilization function for RootERC20Bridge. @@ -56,6 +60,7 @@ contract RootERC20Bridge is * @param newChildBridgeAdaptor Address of child bridge adaptor to communicate with. * @param newChildTokenTemplate Address of child token template to clone. * @param newRootIMXToken Address of ERC20 IMX on the root chain. + * @param newRootWETHToken Address of ERC20 IMX on the root chain. * @dev Can only be called once. */ function initialize( @@ -63,18 +68,20 @@ contract RootERC20Bridge is address newChildERC20Bridge, address newChildBridgeAdaptor, address newChildTokenTemplate, - address newRootIMXToken + address newRootIMXToken, + address newRootWETHToken ) public initializer { if ( newRootBridgeAdaptor == address(0) || newChildERC20Bridge == address(0) || newChildTokenTemplate == address(0) || newChildBridgeAdaptor == address(0) - || newRootIMXToken == address(0) + || newRootIMXToken == address(0) || newRootWETHToken == address(0) ) { revert ZeroAddress(); } childERC20Bridge = newChildERC20Bridge; childTokenTemplate = newChildTokenTemplate; rootIMXToken = newRootIMXToken; + rootWETHToken = newRootWETHToken; IChildERC20 clonedETHToken = IChildERC20(Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(NATIVE_ETH)))); childETHToken = address(clonedETHToken); @@ -118,18 +125,41 @@ contract RootERC20Bridge is } } + function _unwrapWETH(uint256 amount) private { + uint256 expectedBalance = address(this).balance + amount; + + IERC20Metadata erc20WETH = IERC20Metadata(rootWETHToken); + + erc20WETH.safeTransferFrom(msg.sender, address(this), amount); + IWETH(rootWETHToken).withdraw(amount); + + // invariant check to ensure that the root native balance has increased by the amount deposited + if (address(this).balance != expectedBalance) { + revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance); + } + } + /** * @inheritdoc IRootERC20Bridge */ function deposit(IERC20Metadata rootToken, uint256 amount) external payable override { - _depositERC20(rootToken, msg.sender, amount); + _depositWETHorERC20(rootToken, msg.sender, amount); } /** * @inheritdoc IRootERC20Bridge */ function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external payable override { - _depositERC20(rootToken, receiver, amount); + _depositWETHorERC20(rootToken, receiver, amount); + } + + function _depositWETHorERC20(IERC20Metadata rootToken, address receiver, uint256 amount) private { + if (address(rootToken) == rootWETHToken) { + _unwrapWETH(amount); + _depositETH(receiver, amount); + } else { + _depositERC20(rootToken, receiver, amount); + } } function _depositERC20(IERC20Metadata rootToken, address receiver, uint256 amount) private { @@ -154,6 +184,10 @@ contract RootERC20Bridge is revert CantMapETH(); } + if (address(rootToken) == rootWETHToken) { + revert CantMapWETH(); + } + if (rootTokenToChildToken[address(rootToken)] != address(0)) { revert AlreadyMapped(); } diff --git a/src/test/root/WETH.sol b/src/test/root/WETH.sol new file mode 100644 index 00000000..e073e12d --- /dev/null +++ b/src/test/root/WETH.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.21; + +import {IWETH} from "../../interfaces/root/IWETH.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +/** + * @notice WETH is a wrapped ETH contract that allows users to wrap their native ETH. + * @dev This contract is adapted from the official Wrapped ETH contract. + */ +contract WETH is IWETH { + string public name = "Wrapped ETH"; + string public symbol = "WETH"; + uint8 public decimals = 18; + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + /** + * @notice Fallback function on recieving native ETH. + */ + receive() external payable { + deposit(); + } + + /** + * @notice Deposit native ETH in the function call and mint the equal amount of wrapped ETH to msg.sender. + */ + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + /** + * @notice Withdraw given amount of native ETH to msg.sender and burn the equal amount of wrapped ETH. + * @param wad The amount to withdraw. + */ + function withdraw(uint256 wad) public { + require(balanceOf[msg.sender] >= wad, "Wrapped ETH: Insufficient balance"); + balanceOf[msg.sender] -= wad; + + Address.sendValue(payable(msg.sender), wad); + emit Withdrawal(msg.sender, wad); + } + + /** + * @notice Obtain the current total supply of wrapped ETH. + * @return uint The amount of supplied wrapped ETH. + */ + function totalSupply() public view returns (uint256) { + return address(this).balance; + } + + /** + * @notice Approve given spender the ability to spend a given amount of msg.sender's tokens. + * @param guy Approved spender. + * @param wad Amount of allowance. + * @return bool Returns true if function call is successful. + */ + function approve(address guy, uint256 wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + /** + * @notice Transfer given amount of tokens from msg.sender to given destination. + * @param dst Destination of this transfer. + * @param wad Amount of this transfer. + * @return bool Returns true if function call is successful. + */ + function transfer(address dst, uint256 wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + /** + * @notice Transfer given amount of tokens from given source to given destination. + * @param src Source of this transfer. + * @param dst Destination of this transfer. + * @param wad Amount of this transfer. + * @return bool Returns true if function call is successful. + */ + function transferFrom(address src, address dst, uint256 wad) public returns (bool) { + require(balanceOf[src] >= wad, "Wrapped ETH: Insufficient balance"); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= wad, "Wrapped ETH: Insufficient allowance"); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index bab1f5c9..a3741723 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -18,6 +18,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); address constant IMX_TOKEN_ADDRESS = address(0xccc); address constant NATIVE_ETH = address(0xeee); + address constant WRAPPED_ETH = address(0xeee); uint256 constant mapTokenFee = 300; uint256 constant depositFee = 200; @@ -31,7 +32,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx function setUp() public { (imxToken, token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) = - integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS); + integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS, WRAPPED_ETH); } /** @@ -103,7 +104,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); (, bytes memory predictedPayload) = - setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, tokenAmount, false); + setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, tokenAmount, false); console2.logBytes(predictedPayload); @@ -161,7 +162,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); (, bytes memory predictedPayload) = setupDeposit( - ERC20PresetMinterPauser(IMX_TOKEN_ADDRESS), rootBridge, mapTokenFee, depositFee, tokenAmount, false + IMX_TOKEN_ADDRESS, rootBridge, mapTokenFee, depositFee, tokenAmount, false ); vm.expectEmit(address(axelarAdaptor)); @@ -219,7 +220,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx uint256 tokenAmount = 300; string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); (address childToken, bytes memory predictedPayload) = - setupDeposit(token, rootBridge, mapTokenFee, depositFee, tokenAmount, true); + setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, tokenAmount, true); vm.expectEmit(address(axelarAdaptor)); emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); @@ -275,7 +276,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx address recipient = address(9876); string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); (address childToken, bytes memory predictedPayload) = - setupDepositTo(token, rootBridge, mapTokenFee, depositFee, tokenAmount, recipient, true); + setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, tokenAmount, recipient, true); vm.expectEmit(address(axelarAdaptor)); emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index cef2ad5d..bbdbef38 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -15,6 +15,7 @@ import {MockAxelarGateway} from "../../../src/test/root/MockAxelarGateway.sol"; import {MockAxelarGasService} from "../../../src/test/root/MockAxelarGasService.sol"; import {MockAdaptor} from "../../../src/test/root/MockAdaptor.sol"; import {Utils} from "../../utils.t.sol"; +import {WETH} from "../../../src/test/root/WETH.sol"; contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20BridgeErrors, Utils { address constant CHILD_BRIDGE = address(3); @@ -22,6 +23,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid string constant CHILD_CHAIN_NAME = "test"; address constant IMX_TOKEN = address(0xccc); address constant NATIVE_ETH = address(0xeee); + address constant WRAPPED_ETH = address(0xddd); uint256 constant mapTokenFee = 300; uint256 constant depositFee = 200; @@ -35,6 +37,8 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid token = new ERC20PresetMinterPauser("Test", "TST"); deployCodeTo("ERC20PresetMinterPauser.sol", abi.encode("ImmutableX", "IMX"), IMX_TOKEN); + deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH); + rootBridge = new RootERC20Bridge(); mockAxelarGateway = new MockAxelarGateway(); axelarGasService = new MockAxelarGasService(); @@ -42,7 +46,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid mockAxelarAdaptor = new MockAdaptor(); // The specific ERC20 token template does not matter for these unit tests - rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN); + rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, WRAPPED_ETH); } /** @@ -57,43 +61,49 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIfInitializeTwice() public { vm.expectRevert("Initializable: contract is already initialized"); - rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN); + rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, WRAPPED_ETH); } function test_RevertIf_InitializeWithAZeroAddressRootAdapter() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(0), address(1), address(1), address(1), address(1)); + bridge.initialize(address(0), address(1), address(1), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressChildBridge() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(0), address(1), address(1), address(1)); + bridge.initialize(address(1), address(0), address(1), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressChildAdapter() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(0), address(1), address(1)); + bridge.initialize(address(1), address(1), address(0), address(1), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressTokenTemplate() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(1), address(0), address(1)); + bridge.initialize(address(1), address(1), address(1), address(0), address(1), address(1)); } function test_RevertIf_InitializeWithAZeroAddressIMXToken() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(1), address(1), address(1), address(1), address(0)); + bridge.initialize(address(1), address(1), address(1), address(1), address(0), address(1)); + } + + function test_RevertIf_InitializeWithAZeroAddressWETHToken() public { + RootERC20Bridge bridge = new RootERC20Bridge(); + vm.expectRevert(ZeroAddress.selector); + bridge.initialize(address(1), address(1), address(1), address(1), address(1), address(0)); } function test_RevertIf_InitializeWithAZeroAddressAll() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); - bridge.initialize(address(0), address(0), address(0), address(0), address(0)); + bridge.initialize(address(0), address(0), address(0), address(0), address(0), address(0)); } /** @@ -199,7 +209,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositETHCallsSendMessage() public { uint256 amount = 1000; (, bytes memory predictedPayload) = - setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectCall( address(mockAxelarAdaptor), @@ -212,7 +222,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositETHEmitsNativeEthDepositEvent() public { uint256 amount = 1000; - setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectEmit(); emit NativeEthDeposit(NATIVE_ETH, rootBridge.childETHToken(), address(this), address(this), amount); @@ -221,7 +231,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositETHInsufficientValue() public { uint256 amount = 1000; - setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(InsufficientValue.selector); rootBridge.depositETH{value: (amount / 2) + depositFee}(amount); @@ -235,7 +245,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 1000; address receiver = address(12345); (, bytes memory predictedPayload) = setupDepositTo( - ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, receiver, false + NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false ); vm.expectCall( address(mockAxelarAdaptor), @@ -250,7 +260,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 1000; address receiver = address(12345); setupDepositTo( - ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, receiver, false + NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false ); vm.expectEmit(); @@ -262,7 +272,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 1000; address receiver = address(12345); setupDepositTo( - ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, receiver, false + NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false ); vm.expectRevert(InsufficientValue.selector); @@ -275,7 +285,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositETHAmountIsZero() public { uint256 amount = 0; - setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(ZeroAmount.selector); rootBridge.depositETH{value: amount + depositFee}(amount); @@ -285,7 +295,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 0; address receiver = address(12345); - setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(ZeroAmount.selector); rootBridge.depositToETH{value: amount + depositFee}(receiver, amount); @@ -293,7 +303,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositAmountIsZero() public { uint256 amount = 0; - setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(ZeroAmount.selector); rootBridge.deposit{value: depositFee}(token, amount); @@ -302,19 +312,36 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositToAmountIsZero() public { uint256 amount = 0; address receiver = address(12345); - setupDeposit(ERC20PresetMinterPauser(NATIVE_ETH), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectRevert(ZeroAmount.selector); rootBridge.depositTo{value: depositFee}(token, receiver, amount); } + /** + * DEPOSIT WETH + */ + + function test_depositWETHCallsSendMessage() public { + uint256 amount = 100; + (, bytes memory predictedPayload) = setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, false); + + vm.expectCall( + address(mockAxelarAdaptor), + depositFee, + abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + + rootBridge.deposit{value: depositFee}(ERC20PresetMinterPauser(WRAPPED_ETH), amount); + } + /** * DEPOSIT TOKEN */ function test_depositCallsSendMessage() public { uint256 amount = 100; - (, bytes memory predictedPayload) = setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); + (, bytes memory predictedPayload) = setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, amount, true); vm.expectCall( address(mockAxelarAdaptor), @@ -327,7 +354,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositEmitsERC20DepositEvent() public { uint256 amount = 100; - (address childToken,) = setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); + (address childToken,) = setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, amount, true); vm.expectEmit(); emit ERC20Deposit(address(token), childToken, address(this), address(this), amount); @@ -337,7 +364,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositIMXEmitsIMXDepositEvent() public { uint256 amount = 100; - setupDeposit(ERC20PresetMinterPauser(IMX_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false); + setupDeposit(IMX_TOKEN, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectEmit(); emit IMXDeposit(IMX_TOKEN, address(this), address(this), amount); @@ -347,7 +374,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositTransfersTokens() public { uint256 amount = 100; - setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); + setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, amount, true); uint256 thisPreBal = token.balanceOf(address(this)); uint256 bridgePreBal = token.balanceOf(address(rootBridge)); @@ -361,7 +388,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositTransfersNativeAsset() public { uint256 amount = 100; - setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); + setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, amount, true); uint256 thisNativePreBal = address(this).balance; uint256 adaptorNativePreBal = address(mockAxelarAdaptor).balance; @@ -376,7 +403,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositCalledWithZeroAddress() public { uint256 amount = 100; - setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); + setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, amount, true); // Will fail when it tries to call balanceOf vm.expectRevert(); @@ -385,7 +412,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositCalledWithUnmappedToken() public { uint256 amount = 100; - setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); + setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, amount, true); ERC20PresetMinterPauser newToken = new ERC20PresetMinterPauser("Test", "TST"); @@ -396,7 +423,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid // We want to ensure that messages don't get sent when they are not supposed to function test_RevertIf_depositCalledWhenTokenApprovalNotProvided() public { uint256 amount = 100; - setupDeposit(token, rootBridge, mapTokenFee, depositFee, amount, true); + setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, amount, true); vm.expectRevert(); rootBridge.deposit{value: depositFee}(token, amount * 2); @@ -411,7 +438,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid address receiver = address(12345); (, bytes memory predictedPayload) = - setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); + setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, amount, receiver, true); vm.expectCall( address(mockAxelarAdaptor), @@ -426,7 +453,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 100; address receiver = address(12345); - (address childToken,) = setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); + (address childToken,) = setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, amount, receiver, true); vm.expectEmit(); emit ERC20Deposit(address(token), childToken, address(this), receiver, amount); @@ -438,7 +465,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid address receiver = address(12345); - setupDepositTo(ERC20PresetMinterPauser(IMX_TOKEN), rootBridge, mapTokenFee, depositFee, amount, receiver, false); + setupDepositTo(IMX_TOKEN, rootBridge, mapTokenFee, depositFee, amount, receiver, false); vm.expectEmit(); emit IMXDeposit(IMX_TOKEN, address(this), receiver, amount); @@ -449,7 +476,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); + setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, amount, receiver, true); uint256 thisPreBal = token.balanceOf(address(this)); uint256 bridgePreBal = token.balanceOf(address(rootBridge)); @@ -465,7 +492,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); + setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, amount, receiver, true); uint256 thisNativePreBal = address(this).balance; uint256 adaptorNativePreBal = address(mockAxelarAdaptor).balance; @@ -482,7 +509,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositToCalledWhenTokenApprovalNotProvided() public { uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); + setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, amount, receiver, true); vm.expectRevert(); rootBridge.depositTo{value: depositFee}(token, receiver, amount * 2); @@ -492,7 +519,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); + setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, amount, receiver, true); // Will fail when it tries to call balanceOf vm.expectRevert(); @@ -503,7 +530,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 100; address receiver = address(12345); - setupDepositTo(token, rootBridge, mapTokenFee, depositFee, amount, receiver, true); + setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, amount, receiver, true); ERC20PresetMinterPauser newToken = new ERC20PresetMinterPauser("Test", "TST"); diff --git a/test/utils.t.sol b/test/utils.t.sol index 77585442..94182e7e 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -7,6 +7,8 @@ import {MockAxelarGateway} from "../src/test/root/MockAxelarGateway.sol"; import {MockAxelarGasService} from "../src/test/root/MockAxelarGasService.sol"; import {RootERC20Bridge, IERC20Metadata} from "../src/root/RootERC20Bridge.sol"; import {ChildERC20Bridge} from "../src/child/ChildERC20Bridge.sol"; +import {WETH} from "../src/test/root/WETH.sol"; +import {IWETH} from "../src/interfaces/root/IWETH.sol"; import {IChildERC20, ChildERC20} from "../src/child/ChildERC20.sol"; import {RootAxelarBridgeAdaptor} from "../src/root/RootAxelarBridgeAdaptor.sol"; @@ -16,7 +18,8 @@ contract Utils is Test { address childBridge, address childBridgeAdaptor, string memory childBridgeName, - address imxTokenAddress + address imxTokenAddress, + address wethTokenAddress ) public returns ( @@ -35,6 +38,10 @@ contract Utils is Test { imxToken = ERC20PresetMinterPauser(imxTokenAddress); imxToken.mint(address(this), 1000000 ether); + // deployCodeTo("WETH9.sol", abi.encode("Wrapped ETH", "WETH"), wethTokenAddress); + // imxToken = ERC20PresetMinterPauser(imxTokenAddress); + // imxToken.mint(address(this), 1000000 ether); + rootBridge = new RootERC20Bridge(); mockAxelarGateway = new MockAxelarGateway(); axelarGasService = new MockAxelarGasService(); @@ -46,12 +53,12 @@ contract Utils is Test { address(axelarGasService) ); - rootBridge.initialize(address(axelarAdaptor), childBridge, childBridgeAdaptor, address(token), imxTokenAddress); + rootBridge.initialize(address(axelarAdaptor), childBridge, childBridgeAdaptor, address(token), imxTokenAddress, wethTokenAddress); axelarAdaptor.setChildBridgeAdaptor(); } function setupDeposit( - ERC20PresetMinterPauser token, + address token, RootERC20Bridge rootBridge, uint256 mapTokenFee, uint256 depositFee, @@ -62,7 +69,7 @@ contract Utils is Test { } function setupDepositTo( - ERC20PresetMinterPauser token, + address token, RootERC20Bridge rootBridge, uint256 mapTokenFee, uint256 depositFee, @@ -74,7 +81,7 @@ contract Utils is Test { } function _setupDeposit( - ERC20PresetMinterPauser token, + address token, RootERC20Bridge rootBridge, uint256 mapTokenFee, uint256 depositFee, @@ -82,16 +89,20 @@ contract Utils is Test { address to, bool saveTokenMapping ) public returns (address childToken, bytes memory predictedPayload) { - predictedPayload = abi.encode(rootBridge.DEPOSIT_SIG(), address(token), address(this), to, tokenAmount); + predictedPayload = abi.encode(rootBridge.DEPOSIT_SIG(), token, address(this), to, tokenAmount); if (saveTokenMapping) { - childToken = rootBridge.mapToken{value: mapTokenFee}(token); + childToken = rootBridge.mapToken{value: mapTokenFee}(ERC20PresetMinterPauser(token)); } - if (address(token) == address(0xeee)) { + if (token == address(0xeee)) { + vm.deal(to, tokenAmount + depositFee); + } else if(address(token) == address(0xddd)) { vm.deal(to, tokenAmount + depositFee); + IWETH(token).deposit{value: tokenAmount}(); + IWETH(token).approve(address(rootBridge), tokenAmount); } else { - token.mint(address(this), tokenAmount); - token.approve(address(rootBridge), tokenAmount); + ERC20PresetMinterPauser(token).mint(address(this), tokenAmount); + ERC20PresetMinterPauser(token).approve(address(rootBridge), tokenAmount); } return (childToken, predictedPayload); From ea4dcdc159fe7ddd4ccc146fd561832299633df1 Mon Sep 17 00:00:00 2001 From: Ermyas Abebe Date: Wed, 25 Oct 2023 16:16:43 +1100 Subject: [PATCH 02/15] Fix failing test --- .gitignore | 3 +- script/InitializeRootContracts.s.sol | 9 ++++- src/interfaces/root/IERC20.sol | 6 +-- src/root/RootERC20Bridge.sol | 31 ++++++++------- test/integration/root/RootERC20Bridge.t.sol | 5 +-- test/unit/root/RootERC20Bridge.t.sol | 42 +++++++++++---------- test/utils.t.sol | 6 ++- 7 files changed, 53 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index dc2e9557..f7152630 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Compiler files cache/ out/ +.idea/ # Ignores development broadcast logs !/broadcast @@ -22,4 +23,4 @@ docs/ # Contract addresses addresses.json -/node_modules \ No newline at end of file +/node_modules diff --git a/script/InitializeRootContracts.s.sol b/script/InitializeRootContracts.s.sol index 5c01698a..0a61d1c8 100644 --- a/script/InitializeRootContracts.s.sol +++ b/script/InitializeRootContracts.s.sol @@ -14,7 +14,7 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; contract InitializeRootContracts is Script { function run() public { - RootERC20Bridge rootERC20Bridge = RootERC20Bridge(vm.envAddress("ROOT_ERC20_BRIDGE")); + RootERC20Bridge rootERC20Bridge = RootERC20Bridge(payable(vm.envAddress("ROOT_ERC20_BRIDGE"))); RootAxelarBridgeAdaptor rootBridgeAdaptor = RootAxelarBridgeAdaptor(vm.envAddress("ROOT_BRIDGE_ADAPTOR")); address rootChainChildTokenTemplate = vm.envAddress("ROOTCHAIN_CHILD_TOKEN_TEMPLATE"); address childBridgeAdaptor = vm.envAddress("CHILD_BRIDGE_ADAPTOR"); @@ -31,7 +31,12 @@ contract InitializeRootContracts is Script { vm.startBroadcast(rootPrivateKey); rootERC20Bridge.initialize( - address(rootBridgeAdaptor), childERC20Bridge, childBridgeAdaptor, rootChainChildTokenTemplate, rootIMXToken, rootWETHToken + address(rootBridgeAdaptor), + childERC20Bridge, + childBridgeAdaptor, + rootChainChildTokenTemplate, + rootIMXToken, + rootWETHToken ); rootBridgeAdaptor.setChildBridgeAdaptor(); diff --git a/src/interfaces/root/IERC20.sol b/src/interfaces/root/IERC20.sol index 3d0aa3bc..1d05ecf7 100644 --- a/src/interfaces/root/IERC20.sol +++ b/src/interfaces/root/IERC20.sol @@ -61,11 +61,7 @@ interface IERC20 { * * Emits a {Transfer} event. */ - function transferFrom( - address sender, - address recipient, - uint256 amount - ) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index d7f794a7..a208f534 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -13,8 +13,7 @@ import {IRootERC20BridgeEvents, IRootERC20BridgeErrors} from "../interfaces/root import {IRootERC20BridgeAdaptor} from "../interfaces/root/IRootERC20BridgeAdaptor.sol"; import {IChildERC20} from "../interfaces/child/IChildERC20.sol"; import {IWETH} from "../interfaces/root/IWETH.sol"; -import {console2} from "forge-std/Test.sol"; - +import "forge-std/console.sol"; /** * @notice RootERC20Bridge is a bridge that allows ERC20 tokens to be transferred from the root chain to the child chain. * @dev This contract is designed to be upgradeable. @@ -23,6 +22,7 @@ import {console2} from "forge-std/Test.sol"; * @dev Because of this pattern, any checks or logic that is agnostic to the messaging protocol should be done in RootERC20Bridge. * @dev Any checks or logic that is specific to the underlying messaging protocol should be done in the bridge adaptor. */ + contract RootERC20Bridge is Ownable2Step, Initializable, @@ -101,15 +101,17 @@ contract RootERC20Bridge is } function depositETH(uint256 amount) external payable { - //override removed? _depositETH(msg.sender, amount); } function depositToETH(address receiver, uint256 amount) external payable { - //override removed? _depositETH(receiver, amount); } + function _depositUnwrappedETH(address receiver, uint256 amount) private { + _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount, msg.value); + } + function _depositETH(address receiver, uint256 amount) private { if (msg.value < amount) { revert InsufficientValue(); @@ -117,7 +119,7 @@ contract RootERC20Bridge is uint256 expectedBalance = address(this).balance - (msg.value - amount); - _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount); + _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount, msg.value - amount); // invariant check to ensure that the root native balance has increased by the amount deposited if (address(this).balance != expectedBalance) { @@ -143,20 +145,20 @@ contract RootERC20Bridge is * @inheritdoc IRootERC20Bridge */ function deposit(IERC20Metadata rootToken, uint256 amount) external payable override { - _depositWETHorERC20(rootToken, msg.sender, amount); + _depositToken(rootToken, msg.sender, amount); } /** * @inheritdoc IRootERC20Bridge */ function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external payable override { - _depositWETHorERC20(rootToken, receiver, amount); + _depositToken(rootToken, receiver, amount); } - function _depositWETHorERC20(IERC20Metadata rootToken, address receiver, uint256 amount) private { + function _depositToken(IERC20Metadata rootToken, address receiver, uint256 amount) private { if (address(rootToken) == rootWETHToken) { _unwrapWETH(amount); - _depositETH(receiver, amount); + _depositUnwrappedETH(receiver, amount); } else { _depositERC20(rootToken, receiver, amount); } @@ -164,7 +166,7 @@ contract RootERC20Bridge is function _depositERC20(IERC20Metadata rootToken, address receiver, uint256 amount) private { uint256 expectedBalance = rootToken.balanceOf(address(this)) + amount; - _deposit(rootToken, receiver, amount); + _deposit(rootToken, receiver, amount, msg.value); // invariant check to ensure that the root token balance has increased by the amount deposited // slither-disable-next-line incorrect-equality if (rootToken.balanceOf(address(this)) != expectedBalance) { @@ -208,17 +210,15 @@ contract RootERC20Bridge is return childToken; } - function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount) private { + function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount, uint256 feeAmount) private { if (receiver == address(0) || address(rootToken) == address(0)) { revert ZeroAddress(); } - if (amount == 0) { revert ZeroAmount(); } address childToken; - uint256 feeAmount; // The native token does not need to be mapped since it should have been mapped on initialization // The native token also cannot be transferred since it was received in the payable function call @@ -234,9 +234,6 @@ contract RootERC20Bridge is } // ERC20 must be transferred explicitly rootToken.safeTransferFrom(msg.sender, address(this), amount); - feeAmount = msg.value; - } else { - feeAmount = msg.value - amount; } // Deposit sig, root token address, depositor, receiver, amount @@ -260,4 +257,6 @@ contract RootERC20Bridge is } rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor); } + + receive() external payable {} } diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index a3741723..a9569caf 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -161,9 +161,8 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx uint256 tokenAmount = 300; string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); - (, bytes memory predictedPayload) = setupDeposit( - IMX_TOKEN_ADDRESS, rootBridge, mapTokenFee, depositFee, tokenAmount, false - ); + (, bytes memory predictedPayload) = + setupDeposit(IMX_TOKEN_ADDRESS, rootBridge, mapTokenFee, depositFee, tokenAmount, false); vm.expectEmit(address(axelarAdaptor)); emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index bbdbef38..87c81cbf 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -46,7 +46,9 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid mockAxelarAdaptor = new MockAdaptor(); // The specific ERC20 token template does not matter for these unit tests - rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, WRAPPED_ETH); + rootBridge.initialize( + address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, WRAPPED_ETH + ); } /** @@ -61,7 +63,9 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIfInitializeTwice() public { vm.expectRevert("Initializable: contract is already initialized"); - rootBridge.initialize(address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, WRAPPED_ETH); + rootBridge.initialize( + address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, address(token), IMX_TOKEN, WRAPPED_ETH + ); } function test_RevertIf_InitializeWithAZeroAddressRootAdapter() public { @@ -208,8 +212,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositETHCallsSendMessage() public { uint256 amount = 1000; - (, bytes memory predictedPayload) = - setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, false); + (, bytes memory predictedPayload) = setupDeposit(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectCall( address(mockAxelarAdaptor), @@ -244,9 +247,8 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositToETHCallsSendMessage() public { uint256 amount = 1000; address receiver = address(12345); - (, bytes memory predictedPayload) = setupDepositTo( - NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false - ); + (, bytes memory predictedPayload) = + setupDepositTo(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false); vm.expectCall( address(mockAxelarAdaptor), depositFee, @@ -259,9 +261,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositToETHEmitsNativeEthDepositEvent() public { uint256 amount = 1000; address receiver = address(12345); - setupDepositTo( - NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false - ); + setupDepositTo(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false); vm.expectEmit(); emit NativeEthDeposit(NATIVE_ETH, rootBridge.childETHToken(), address(this), receiver, amount); @@ -271,9 +271,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIf_depositToETHInsufficientValue() public { uint256 amount = 1000; address receiver = address(12345); - setupDepositTo( - NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false - ); + setupDepositTo(NATIVE_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false); vm.expectRevert(InsufficientValue.selector); rootBridge.depositToETH{value: (amount / 2) + depositFee}(receiver, amount); @@ -318,21 +316,23 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.depositTo{value: depositFee}(token, receiver, amount); } - /** + /** * DEPOSIT WETH */ function test_depositWETHCallsSendMessage() public { uint256 amount = 100; - (, bytes memory predictedPayload) = setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, false); + (, bytes memory predictedPayload) = + setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, false); + + bytes memory pp = abi.encode(rootBridge.DEPOSIT_SIG(), NATIVE_ETH, address(this), address(this), amount); vm.expectCall( address(mockAxelarAdaptor), - depositFee, - abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, pp, address(this)) ); - rootBridge.deposit{value: depositFee}(ERC20PresetMinterPauser(WRAPPED_ETH), amount); + rootBridge.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), amount); } /** @@ -341,7 +341,8 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_depositCallsSendMessage() public { uint256 amount = 100; - (, bytes memory predictedPayload) = setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, amount, true); + (, bytes memory predictedPayload) = + setupDeposit(address(token), rootBridge, mapTokenFee, depositFee, amount, true); vm.expectCall( address(mockAxelarAdaptor), @@ -453,7 +454,8 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid uint256 amount = 100; address receiver = address(12345); - (address childToken,) = setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, amount, receiver, true); + (address childToken,) = + setupDepositTo(address(token), rootBridge, mapTokenFee, depositFee, amount, receiver, true); vm.expectEmit(); emit ERC20Deposit(address(token), childToken, address(this), receiver, amount); diff --git a/test/utils.t.sol b/test/utils.t.sol index 94182e7e..3475aa5c 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -53,7 +53,9 @@ contract Utils is Test { address(axelarGasService) ); - rootBridge.initialize(address(axelarAdaptor), childBridge, childBridgeAdaptor, address(token), imxTokenAddress, wethTokenAddress); + rootBridge.initialize( + address(axelarAdaptor), childBridge, childBridgeAdaptor, address(token), imxTokenAddress, wethTokenAddress + ); axelarAdaptor.setChildBridgeAdaptor(); } @@ -96,7 +98,7 @@ contract Utils is Test { if (token == address(0xeee)) { vm.deal(to, tokenAmount + depositFee); - } else if(address(token) == address(0xddd)) { + } else if (address(token) == address(0xddd)) { vm.deal(to, tokenAmount + depositFee); IWETH(token).deposit{value: tokenAmount}(); IWETH(token).approve(address(rootBridge), tokenAmount); From 853c365d8c83865fc97f338b2375b3ea1733ddd0 Mon Sep 17 00:00:00 2001 From: Craig M Date: Wed, 25 Oct 2023 19:04:39 +1300 Subject: [PATCH 03/15] last patches and tests added --- src/root/RootERC20Bridge.sol | 3 + test/integration/root/RootERC20Bridge.t.sol | 62 ++++++++++++++++++++- test/unit/root/RootERC20Bridge.t.sol | 56 ++++++++++++++++++- test/utils.t.sol | 3 + 4 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index a208f534..2acc9dce 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -258,5 +258,8 @@ contract RootERC20Bridge is rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor); } + /** + * @dev method to receive the ETH back from the WETH contract when it is unwrapped + */ receive() external payable {} } diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index a9569caf..52eaaae5 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -10,6 +10,7 @@ import {MockAxelarGasService} from "../../../src/test/root/MockAxelarGasService. import {RootERC20Bridge, IRootERC20BridgeEvents, IERC20Metadata} from "../../../src/root/RootERC20Bridge.sol"; import {RootAxelarBridgeAdaptor, IRootAxelarBridgeAdaptorEvents} from "../../../src/root/RootAxelarBridgeAdaptor.sol"; import {Utils} from "../../utils.t.sol"; +import {WETH} from "../../../src/test/root/WETH.sol"; contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAxelarBridgeAdaptorEvents, Utils { address constant CHILD_BRIDGE = address(3); @@ -18,7 +19,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN"); address constant IMX_TOKEN_ADDRESS = address(0xccc); address constant NATIVE_ETH = address(0xeee); - address constant WRAPPED_ETH = address(0xeee); + address constant WRAPPED_ETH = address(0xddd); uint256 constant mapTokenFee = 300; uint256 constant depositFee = 200; @@ -31,6 +32,9 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx MockAxelarGasService public axelarGasService; function setUp() public { + + deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH); + (imxToken, token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) = integrationSetup(CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR, CHILD_CHAIN_NAME, IMX_TOKEN_ADDRESS, WRAPPED_ETH); } @@ -214,6 +218,62 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); } + // TODO split into multiple tests + function test_depositWETH() public { + uint256 tokenAmount = 300; + string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); + (, bytes memory predictedPayload) = + setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, tokenAmount, false); + + vm.expectEmit(address(axelarAdaptor)); + emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); + vm.expectEmit(address(rootBridge)); + emit NativeEthDeposit( + address(NATIVE_ETH), rootBridge.childETHToken(), address(this), address(this), tokenAmount + ); + vm.expectCall( + address(axelarAdaptor), + depositFee, + abi.encodeWithSelector(axelarAdaptor.sendMessage.selector, predictedPayload, address(this)) + ); + + vm.expectCall( + address(axelarGasService), + depositFee, + abi.encodeWithSelector( + axelarGasService.payNativeGasForContractCall.selector, + address(axelarAdaptor), + CHILD_CHAIN_NAME, + childBridgeAdaptorString, + predictedPayload, + address(this) + ) + ); + + vm.expectCall( + address(mockAxelarGateway), + 0, + abi.encodeWithSelector( + mockAxelarGateway.callContract.selector, CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload + ) + ); + + uint256 thisPreBal = IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)); + uint256 bridgePreBal = address(rootBridge).balance; + + uint256 thisNativePreBal = address(this).balance; + uint256 gasServiceNativePreBal = address(axelarGasService).balance; + + rootBridge.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), tokenAmount); + + // Check that tokens are transferred + assertEq(thisPreBal - tokenAmount, IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), "Tokens not transferred from user"); + assertEq(bridgePreBal + tokenAmount, address(rootBridge).balance, "ETH not transferred to Bridge"); + // Check that native asset transferred to gas service + assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH for fee not paid from user"); + assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); + } + // TODO split into multiple tests function test_depositToken() public { uint256 tokenAmount = 300; diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 87c81cbf..4549fab2 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -325,16 +325,66 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid (, bytes memory predictedPayload) = setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, false); - bytes memory pp = abi.encode(rootBridge.DEPOSIT_SIG(), NATIVE_ETH, address(this), address(this), amount); - vm.expectCall( address(mockAxelarAdaptor), - abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, pp, address(this)) + abi.encodeWithSelector(mockAxelarAdaptor.sendMessage.selector, predictedPayload, address(this)) ); rootBridge.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), amount); } + function test_depositWETHEmitsNativeDepositEvent() public { + uint256 amount = 100; + setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, false); + + vm.expectEmit(); + emit NativeEthDeposit(NATIVE_ETH, rootBridge.childETHToken(), address(this), address(this), amount); + rootBridge.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), amount); + } + + function test_depositToWETHEmitsNativeEthDepositEvent() public { + uint256 amount = 1000; + address receiver = address(12345); + setupDepositTo(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false); + + vm.expectEmit(); + emit NativeEthDeposit(NATIVE_ETH, rootBridge.childETHToken(), address(this), receiver, amount); + rootBridge.depositTo{value: depositFee}(IERC20Metadata(WRAPPED_ETH), receiver, amount); + } + + function test_depositWETHTransfersTokens() public { + uint256 amount = 100; + + setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, false); + + uint256 thisPreBal = IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)); + uint256 bridgePreBal = address(rootBridge).balance; + + rootBridge.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), amount); + + // Check that tokens are transferred + assertEq(thisPreBal - amount, IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), "Tokens not transferred from user"); + assertEq(bridgePreBal + amount, address(rootBridge).balance, "ETH not transferred to Bridge"); + + } + + function test_depositToWETHTransfersTokens() public { + uint256 amount = 100; + address receiver = address(12345); + + setupDepositTo(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false); + + uint256 thisPreBal = IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)); + uint256 bridgePreBal = address(rootBridge).balance; + + rootBridge.depositTo{value: depositFee}(IERC20Metadata(WRAPPED_ETH), receiver, amount); + + // Check that tokens are transferred + assertEq(thisPreBal - amount, IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), "Tokens not transferred from user"); + assertEq(bridgePreBal + amount, address(rootBridge).balance, "ETH not transferred to Bridge"); + + } + /** * DEPOSIT TOKEN */ diff --git a/test/utils.t.sol b/test/utils.t.sol index 3475aa5c..2585d450 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -92,6 +92,7 @@ contract Utils is Test { bool saveTokenMapping ) public returns (address childToken, bytes memory predictedPayload) { predictedPayload = abi.encode(rootBridge.DEPOSIT_SIG(), token, address(this), to, tokenAmount); + if (saveTokenMapping) { childToken = rootBridge.mapToken{value: mapTokenFee}(ERC20PresetMinterPauser(token)); } @@ -99,6 +100,8 @@ contract Utils is Test { if (token == address(0xeee)) { vm.deal(to, tokenAmount + depositFee); } else if (address(token) == address(0xddd)) { + // set the payload to expect native eth when depositing wrapped eth + predictedPayload = abi.encode(rootBridge.DEPOSIT_SIG(), address(0xeee), address(this), to, tokenAmount); vm.deal(to, tokenAmount + depositFee); IWETH(token).deposit{value: tokenAmount}(); IWETH(token).approve(address(rootBridge), tokenAmount); From 1335e14cda40bd56094ad7af486ee7a1fa6be693 Mon Sep 17 00:00:00 2001 From: Craig M Date: Wed, 25 Oct 2023 19:05:33 +1300 Subject: [PATCH 04/15] formatting --- test/integration/root/RootERC20Bridge.t.sol | 7 +++++-- test/unit/root/RootERC20Bridge.t.sol | 14 ++++++++++---- test/utils.t.sol | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index 52eaaae5..cabd4285 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -32,7 +32,6 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx MockAxelarGasService public axelarGasService; function setUp() public { - deployCodeTo("WETH.sol", abi.encode("Wrapped ETH", "WETH"), WRAPPED_ETH); (imxToken, token, rootBridge, axelarAdaptor, mockAxelarGateway, axelarGasService) = @@ -267,7 +266,11 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx rootBridge.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), tokenAmount); // Check that tokens are transferred - assertEq(thisPreBal - tokenAmount, IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), "Tokens not transferred from user"); + assertEq( + thisPreBal - tokenAmount, + IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), + "Tokens not transferred from user" + ); assertEq(bridgePreBal + tokenAmount, address(rootBridge).balance, "ETH not transferred to Bridge"); // Check that native asset transferred to gas service assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH for fee not paid from user"); diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 4549fab2..544aebc6 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -363,9 +363,12 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), amount); // Check that tokens are transferred - assertEq(thisPreBal - amount, IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), "Tokens not transferred from user"); + assertEq( + thisPreBal - amount, + IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), + "Tokens not transferred from user" + ); assertEq(bridgePreBal + amount, address(rootBridge).balance, "ETH not transferred to Bridge"); - } function test_depositToWETHTransfersTokens() public { @@ -380,9 +383,12 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid rootBridge.depositTo{value: depositFee}(IERC20Metadata(WRAPPED_ETH), receiver, amount); // Check that tokens are transferred - assertEq(thisPreBal - amount, IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), "Tokens not transferred from user"); + assertEq( + thisPreBal - amount, + IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), + "Tokens not transferred from user" + ); assertEq(bridgePreBal + amount, address(rootBridge).balance, "ETH not transferred to Bridge"); - } /** diff --git a/test/utils.t.sol b/test/utils.t.sol index 2585d450..4da49b74 100644 --- a/test/utils.t.sol +++ b/test/utils.t.sol @@ -92,7 +92,7 @@ contract Utils is Test { bool saveTokenMapping ) public returns (address childToken, bytes memory predictedPayload) { predictedPayload = abi.encode(rootBridge.DEPOSIT_SIG(), token, address(this), to, tokenAmount); - + if (saveTokenMapping) { childToken = rootBridge.mapToken{value: mapTokenFee}(ERC20PresetMinterPauser(token)); } From 05a4e937f959c88694a12ce1013286d89e043be2 Mon Sep 17 00:00:00 2001 From: Craig M Date: Wed, 25 Oct 2023 19:07:35 +1300 Subject: [PATCH 05/15] Delete IERC20.sol --- src/interfaces/root/IERC20.sol | 79 ---------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 src/interfaces/root/IERC20.sol diff --git a/src/interfaces/root/IERC20.sol b/src/interfaces/root/IERC20.sol deleted file mode 100644 index 1d05ecf7..00000000 --- a/src/interfaces/root/IERC20.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.9; - -/** - * @dev Interface of the ERC20 standard as defined in the EIP. - */ -interface IERC20 { - error InvalidAccount(); - - /** - * @dev Returns the amount of tokens in existence. - */ - function totalSupply() external view returns (uint256); - - /** - * @dev Returns the amount of tokens owned by `account`. - */ - function balanceOf(address account) external view returns (uint256); - - /** - * @dev Moves `amount` tokens from the caller's account to `recipient`. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transfer(address recipient, uint256 amount) external returns (bool); - - /** - * @dev Returns the remaining number of tokens that `spender` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - * - * This value changes when {approve} or {transferFrom} are called. - */ - function allowance(address owner, address spender) external view returns (uint256); - - /** - * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * IMPORTANT: Beware that changing an allowance with this method brings the risk - * that someone may use both the old and the new allowance by unfortunate - * transaction ordering. One possible solution to mitigate this race - * condition is to first reduce the spender's allowance to 0 and set the - * desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * - * Emits an {Approval} event. - */ - function approve(address spender, uint256 amount) external returns (bool); - - /** - * @dev Moves `amount` tokens from `sender` to `recipient` using the - * allowance mechanism. `amount` is then deducted from the caller's - * allowance. - * - * Returns a boolean value indicating whether the operation succeeded. - * - * Emits a {Transfer} event. - */ - function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); - - /** - * @dev Emitted when `value` tokens are moved from one account (`from`) to - * another (`to`). - * - * Note that `value` may be zero. - */ - event Transfer(address indexed from, address indexed to, uint256 value); - - /** - * @dev Emitted when the allowance of a `spender` for an `owner` is set by - * a call to {approve}. `value` is the new allowance. - */ - event Approval(address indexed owner, address indexed spender, uint256 value); -} From 29487d86939348ceb2c895a8a835e11c4edb5b97 Mon Sep 17 00:00:00 2001 From: Craig M Date: Wed, 25 Oct 2023 19:09:52 +1300 Subject: [PATCH 06/15] Update RootERC20Bridge.sol --- src/root/RootERC20Bridge.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index 2acc9dce..ee3ca444 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -13,7 +13,7 @@ import {IRootERC20BridgeEvents, IRootERC20BridgeErrors} from "../interfaces/root import {IRootERC20BridgeAdaptor} from "../interfaces/root/IRootERC20BridgeAdaptor.sol"; import {IChildERC20} from "../interfaces/child/IChildERC20.sol"; import {IWETH} from "../interfaces/root/IWETH.sol"; -import "forge-std/console.sol"; + /** * @notice RootERC20Bridge is a bridge that allows ERC20 tokens to be transferred from the root chain to the child chain. * @dev This contract is designed to be upgradeable. From 11aa4ba96812216eafec8dd5dd80142a095d75b1 Mon Sep 17 00:00:00 2001 From: Craig M Date: Thu, 26 Oct 2023 11:44:22 +1300 Subject: [PATCH 07/15] updated comment, reordered functions --- src/root/RootERC20Bridge.sol | 53 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index ee3ca444..e75ee033 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -60,7 +60,7 @@ contract RootERC20Bridge is * @param newChildBridgeAdaptor Address of child bridge adaptor to communicate with. * @param newChildTokenTemplate Address of child token template to clone. * @param newRootIMXToken Address of ERC20 IMX on the root chain. - * @param newRootWETHToken Address of ERC20 IMX on the root chain. + * @param newRootWETHToken Address of ERC20 WETH on the root chain. * @dev Can only be called once. */ function initialize( @@ -89,6 +89,13 @@ contract RootERC20Bridge is childBridgeAdaptor = Strings.toHexString(newChildBridgeAdaptor); } + function updateRootBridgeAdaptor(address newRootBridgeAdaptor) external onlyOwner { + if (newRootBridgeAdaptor == address(0)) { + revert ZeroAddress(); + } + rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor); + } + /** * @inheritdoc IRootERC20Bridge * @dev TODO when this becomes part of the deposit flow on a token's first bridge, this logic will need to be mostly moved into an internal function. @@ -108,6 +115,25 @@ contract RootERC20Bridge is _depositETH(receiver, amount); } + /** + * @inheritdoc IRootERC20Bridge + */ + function deposit(IERC20Metadata rootToken, uint256 amount) external payable override { + _depositToken(rootToken, msg.sender, amount); + } + + /** + * @inheritdoc IRootERC20Bridge + */ + function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external payable override { + _depositToken(rootToken, receiver, amount); + } + + /** + * @dev method to receive the ETH back from the WETH contract when it is unwrapped + */ + receive() external payable {} + function _depositUnwrappedETH(address receiver, uint256 amount) private { _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount, msg.value); } @@ -141,20 +167,6 @@ contract RootERC20Bridge is } } - /** - * @inheritdoc IRootERC20Bridge - */ - function deposit(IERC20Metadata rootToken, uint256 amount) external payable override { - _depositToken(rootToken, msg.sender, amount); - } - - /** - * @inheritdoc IRootERC20Bridge - */ - function depositTo(IERC20Metadata rootToken, address receiver, uint256 amount) external payable override { - _depositToken(rootToken, receiver, amount); - } - function _depositToken(IERC20Metadata rootToken, address receiver, uint256 amount) private { if (address(rootToken) == rootWETHToken) { _unwrapWETH(amount); @@ -251,15 +263,4 @@ contract RootERC20Bridge is } } - function updateRootBridgeAdaptor(address newRootBridgeAdaptor) external onlyOwner { - if (newRootBridgeAdaptor == address(0)) { - revert ZeroAddress(); - } - rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor); - } - - /** - * @dev method to receive the ETH back from the WETH contract when it is unwrapped - */ - receive() external payable {} } From 364d73f998d7380e489b66cd1c541ce537093bfa Mon Sep 17 00:00:00 2001 From: Craig M Date: Thu, 26 Oct 2023 11:46:56 +1300 Subject: [PATCH 08/15] Update RootERC20Bridge.sol --- src/root/RootERC20Bridge.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index e75ee033..b605a9c0 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -96,6 +96,11 @@ contract RootERC20Bridge is rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor); } + /** + * @dev method to receive the ETH back from the WETH contract when it is unwrapped + */ + receive() external payable {} + /** * @inheritdoc IRootERC20Bridge * @dev TODO when this becomes part of the deposit flow on a token's first bridge, this logic will need to be mostly moved into an internal function. @@ -129,11 +134,6 @@ contract RootERC20Bridge is _depositToken(rootToken, receiver, amount); } - /** - * @dev method to receive the ETH back from the WETH contract when it is unwrapped - */ - receive() external payable {} - function _depositUnwrappedETH(address receiver, uint256 amount) private { _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount, msg.value); } From f2beb02af36160a532bcf427307d091e29b5db7e Mon Sep 17 00:00:00 2001 From: Craig M Date: Thu, 26 Oct 2023 16:26:58 +1300 Subject: [PATCH 09/15] feedback applied --- src/interfaces/root/IRootERC20Bridge.sol | 1 + src/root/RootERC20Bridge.sol | 60 +++++++++++---------- test/integration/root/RootERC20Bridge.t.sol | 4 +- test/unit/root/RootERC20Bridge.t.sol | 6 +-- 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index df9bd051..eb1a75c0 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -46,6 +46,7 @@ interface IRootERC20BridgeEvents { uint256 amount ); event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount); + event WETHDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount); event NativeEthDeposit( address indexed rootToken, address indexed childToken, diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index b605a9c0..42bf9564 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -134,10 +134,6 @@ contract RootERC20Bridge is _depositToken(rootToken, receiver, amount); } - function _depositUnwrappedETH(address receiver, uint256 amount) private { - _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount, msg.value); - } - function _depositETH(address receiver, uint256 amount) private { if (msg.value < amount) { revert InsufficientValue(); @@ -145,7 +141,7 @@ contract RootERC20Bridge is uint256 expectedBalance = address(this).balance - (msg.value - amount); - _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount, msg.value - amount); + _deposit(IERC20Metadata(NATIVE_ETH), receiver, amount); // invariant check to ensure that the root native balance has increased by the amount deposited if (address(this).balance != expectedBalance) { @@ -153,7 +149,16 @@ contract RootERC20Bridge is } } - function _unwrapWETH(uint256 amount) private { + function _depositToken(IERC20Metadata rootToken, address receiver, uint256 amount) private { + if (address(rootToken) == rootWETHToken) { + _unwrapETH(amount); + _deposit(IERC20Metadata(rootWETHToken), receiver, amount); + } else { + _depositERC20(rootToken, receiver, amount); + } + } + + function _unwrapETH(uint256 amount) private { uint256 expectedBalance = address(this).balance + amount; IERC20Metadata erc20WETH = IERC20Metadata(rootWETHToken); @@ -167,18 +172,9 @@ contract RootERC20Bridge is } } - function _depositToken(IERC20Metadata rootToken, address receiver, uint256 amount) private { - if (address(rootToken) == rootWETHToken) { - _unwrapWETH(amount); - _depositUnwrappedETH(receiver, amount); - } else { - _depositERC20(rootToken, receiver, amount); - } - } - function _depositERC20(IERC20Metadata rootToken, address receiver, uint256 amount) private { uint256 expectedBalance = rootToken.balanceOf(address(this)) + amount; - _deposit(rootToken, receiver, amount, msg.value); + _deposit(rootToken, receiver, amount); // invariant check to ensure that the root token balance has increased by the amount deposited // slither-disable-next-line incorrect-equality if (rootToken.balanceOf(address(this)) != expectedBalance) { @@ -222,7 +218,7 @@ contract RootERC20Bridge is return childToken; } - function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount, uint256 feeAmount) private { + function _deposit(IERC20Metadata rootToken, address receiver, uint256 amount) private { if (receiver == address(0) || address(rootToken) == address(0)) { revert ZeroAddress(); } @@ -230,32 +226,42 @@ contract RootERC20Bridge is revert ZeroAmount(); } + // ETH, WETH and IMX do not need to be mapped since it should have been mapped on initialization + // ETH also cannot be transferred since it was received in the payable function call + // WETH is also not transferred here since it was earlier unwrapped to ETH + + // TODO We can call _mapToken here, but ordering in the GMP is not guaranteed. + // Therefore, we need to decide how to handle this and it may be a UI decision to wait until map token message is executed on child chain. + // Discuss this, and add this decision to the design doc. + address childToken; + uint256 feeAmount = msg.value; + address payloadToken = address(rootToken); - // The native token does not need to be mapped since it should have been mapped on initialization - // The native token also cannot be transferred since it was received in the payable function call - // TODO We can call _mapToken here, but ordering in the GMP is not guaranteed. - // Therefore, we need to decide how to handle this and it may be a UI decision to wait until map token message is executed on child chain. - // Discuss this, and add this decision to the design doc. - if (address(rootToken) != NATIVE_ETH) { + if (address(rootToken) == NATIVE_ETH) { + feeAmount = msg.value - amount; + } else if (address(rootToken) == rootWETHToken) { + payloadToken = NATIVE_ETH; + } else { if (address(rootToken) != rootIMXToken) { childToken = rootTokenToChildToken[address(rootToken)]; if (childToken == address(0)) { revert NotMapped(); } } - // ERC20 must be transferred explicitly rootToken.safeTransferFrom(msg.sender, address(this), amount); - } + } // Deposit sig, root token address, depositor, receiver, amount - bytes memory payload = abi.encode(DEPOSIT_SIG, rootToken, msg.sender, receiver, amount); + bytes memory payload = abi.encode(DEPOSIT_SIG, payloadToken, msg.sender, receiver, amount); + // TODO investigate using delegatecall to keep the axelar message sender as the bridge contract, since adaptor can change. - rootBridgeAdaptor.sendMessage{value: feeAmount}(payload, msg.sender); if (address(rootToken) == NATIVE_ETH) { emit NativeEthDeposit(address(rootToken), childETHToken, msg.sender, receiver, amount); + } else if (address(rootToken) == rootWETHToken) { + emit WETHDeposit(address(rootToken), msg.sender, receiver, amount); } else if (address(rootToken) == rootIMXToken) { emit IMXDeposit(address(rootToken), msg.sender, receiver, amount); } else { diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index cabd4285..c12b3c74 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -227,8 +227,8 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx vm.expectEmit(address(axelarAdaptor)); emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); vm.expectEmit(address(rootBridge)); - emit NativeEthDeposit( - address(NATIVE_ETH), rootBridge.childETHToken(), address(this), address(this), tokenAmount + emit WETHDeposit( + address(WRAPPED_ETH), address(this), address(this), tokenAmount ); vm.expectCall( address(axelarAdaptor), diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 544aebc6..8a130322 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -338,17 +338,17 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectEmit(); - emit NativeEthDeposit(NATIVE_ETH, rootBridge.childETHToken(), address(this), address(this), amount); + emit WETHDeposit(WRAPPED_ETH, address(this), address(this), amount); rootBridge.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), amount); } - function test_depositToWETHEmitsNativeEthDepositEvent() public { + function test_depositToWETHEmitsWETHDepositEvent() public { uint256 amount = 1000; address receiver = address(12345); setupDepositTo(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false); vm.expectEmit(); - emit NativeEthDeposit(NATIVE_ETH, rootBridge.childETHToken(), address(this), receiver, amount); + emit WETHDeposit(WRAPPED_ETH, address(this), receiver, amount); rootBridge.depositTo{value: depositFee}(IERC20Metadata(WRAPPED_ETH), receiver, amount); } From 0a6034a9131b6c92cafc1ab12d324314714cca20 Mon Sep 17 00:00:00 2001 From: Craig M Date: Thu, 26 Oct 2023 16:27:23 +1300 Subject: [PATCH 10/15] formatting --- src/root/RootERC20Bridge.sol | 7 +++---- test/integration/root/RootERC20Bridge.t.sol | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index 42bf9564..ad6a3e3d 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -229,7 +229,7 @@ contract RootERC20Bridge is // ETH, WETH and IMX do not need to be mapped since it should have been mapped on initialization // ETH also cannot be transferred since it was received in the payable function call // WETH is also not transferred here since it was earlier unwrapped to ETH - + // TODO We can call _mapToken here, but ordering in the GMP is not guaranteed. // Therefore, we need to decide how to handle this and it may be a UI decision to wait until map token message is executed on child chain. // Discuss this, and add this decision to the design doc. @@ -250,11 +250,11 @@ contract RootERC20Bridge is } } rootToken.safeTransferFrom(msg.sender, address(this), amount); - } + } // Deposit sig, root token address, depositor, receiver, amount bytes memory payload = abi.encode(DEPOSIT_SIG, payloadToken, msg.sender, receiver, amount); - + // TODO investigate using delegatecall to keep the axelar message sender as the bridge contract, since adaptor can change. rootBridgeAdaptor.sendMessage{value: feeAmount}(payload, msg.sender); @@ -268,5 +268,4 @@ contract RootERC20Bridge is emit ERC20Deposit(address(rootToken), childToken, msg.sender, receiver, amount); } } - } diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index c12b3c74..691791ab 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -227,9 +227,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx vm.expectEmit(address(axelarAdaptor)); emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); vm.expectEmit(address(rootBridge)); - emit WETHDeposit( - address(WRAPPED_ETH), address(this), address(this), tokenAmount - ); + emit WETHDeposit(address(WRAPPED_ETH), address(this), address(this), tokenAmount); vm.expectCall( address(axelarAdaptor), depositFee, From d07713c8798e3d079d7b9eb92667c6d6447e450f Mon Sep 17 00:00:00 2001 From: Craig M Date: Thu, 26 Oct 2023 16:40:05 +1300 Subject: [PATCH 11/15] formatting --- src/root/RootERC20Bridge.sol | 7 ++----- test/unit/root/RootERC20Bridge.t.sol | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index e97b00b3..8cd59ff6 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -72,11 +72,8 @@ contract RootERC20Bridge is address newRootWETHToken ) public initializer { if ( - newRootBridgeAdaptor == address(0) - || newChildERC20Bridge == address(0) - || newChildTokenTemplate == address(0) - || newRootIMXToken == address(0) - || newRootWETHToken == address(0) + newRootBridgeAdaptor == address(0) || newChildERC20Bridge == address(0) + || newChildTokenTemplate == address(0) || newRootIMXToken == address(0) || newRootWETHToken == address(0) ) { revert ZeroAddress(); } diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 7bbf3a85..24358c37 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -48,7 +48,12 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid // The specific ERC20 token template does not matter for these unit tests rootBridge.initialize( - address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR_STRING, address(token), IMX_TOKEN, WRAPPED_ETH + address(mockAxelarAdaptor), + CHILD_BRIDGE, + CHILD_BRIDGE_ADAPTOR_STRING, + address(token), + IMX_TOKEN, + WRAPPED_ETH ); } @@ -65,7 +70,12 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid function test_RevertIfInitializeTwice() public { vm.expectRevert("Initializable: contract is already initialized"); rootBridge.initialize( - address(mockAxelarAdaptor), CHILD_BRIDGE, CHILD_BRIDGE_ADAPTOR_STRING, address(token), IMX_TOKEN, WRAPPED_ETH + address(mockAxelarAdaptor), + CHILD_BRIDGE, + CHILD_BRIDGE_ADAPTOR_STRING, + address(token), + IMX_TOKEN, + WRAPPED_ETH ); } @@ -105,7 +115,6 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid bridge.initialize(address(1), address(1), CHILD_BRIDGE_ADAPTOR_STRING, address(1), address(1), address(0)); } - function test_RevertIf_InitializeWithAZeroAddressAll() public { RootERC20Bridge bridge = new RootERC20Bridge(); vm.expectRevert(ZeroAddress.selector); From c8069013b15bfbc3455e3f70103cb38d3a1c3e02 Mon Sep 17 00:00:00 2001 From: Craig M Date: Thu, 26 Oct 2023 17:21:52 +1300 Subject: [PATCH 12/15] deploy scripts WIP --- deploy.sh | 7 +++++++ script/DeployRootContracts.s.sol | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/deploy.sh b/deploy.sh index 2db86279..8a106b18 100755 --- a/deploy.sh +++ b/deploy.sh @@ -13,6 +13,7 @@ function get_deployed_contract() { } function main() { + forge script script/DeployRootContracts.s.sol:DeployRootContracts --broadcast forge script script/DeployChildContracts.s.sol:DeployChildContracts --broadcast @@ -23,6 +24,11 @@ function main() { root_erc20_bridge=$( get_deployed_contract "$root_filename" "RootERC20Bridge" ) root_bridge_adaptor=$( get_deployed_contract "$root_filename" "RootAxelarBridgeAdaptor" ) root_chain_child_token_template=$( get_deployed_contract "$root_filename" "ChildERC20" ) + + if [ "$ENVIRONMENT" = "local" ]; then + root_weth_contract=$( get_deployed_contract "$root_filename" "WETH" ) + export ROOT_WETH_ADDRESS=$root_weth_contract + fi child_erc20_bridge=$( get_deployed_contract "$child_filename" "ChildERC20Bridge" ) child_bridge_adaptor=$( get_deployed_contract "$child_filename" "ChildAxelarBridgeAdaptor" ) @@ -31,6 +37,7 @@ function main() { export ROOT_ERC20_BRIDGE=$root_erc20_bridge export ROOT_BRIDGE_ADAPTOR=$root_bridge_adaptor export ROOTCHAIN_CHILD_TOKEN_TEMPLATE=$root_chain_child_token_template + export CHILD_BRIDGE_ADAPTOR=$child_bridge_adaptor export CHILD_ERC20_BRIDGE=$child_erc20_bridge export CHILDCHAIN_CHILD_TOKEN_TEMPLATE=$child_chain_child_token_template diff --git a/script/DeployRootContracts.s.sol b/script/DeployRootContracts.s.sol index 5f56d595..0c3d4102 100644 --- a/script/DeployRootContracts.s.sol +++ b/script/DeployRootContracts.s.sol @@ -9,6 +9,7 @@ import {RootAxelarBridgeAdaptor} from "../src/root/RootAxelarBridgeAdaptor.sol"; import {ChildERC20Bridge} from "../src/child/ChildERC20Bridge.sol"; import {ChildAxelarBridgeAdaptor} from "../src/child/ChildAxelarBridgeAdaptor.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {WETH} from "../src/test/root/WETH.sol"; // TODO update private key usage to be more secure: https://book.getfoundry.sh/reference/forge/forge-script#wallet-options---raw @@ -19,6 +20,7 @@ contract DeployRootContracts is Script { address rootGateway = vm.envAddress("ROOT_GATEWAY_ADDRESS"); address rootGasService = vm.envAddress("ROOT_GAS_SERVICE_ADDRESS"); string memory childChainName = vm.envString("CHILD_CHAIN_NAME"); + string memory deployEnvironment = vm.envString("ENVIRONMENT"); /** * DEPLOY ROOT CHAIN CONTRACTS @@ -39,11 +41,15 @@ contract DeployRootContracts is Script { rootGasService // axelar gas service ); + if (Strings.equal(deployEnvironment, "local")) { + new WETH(); + } + + vm.stopBroadcast(); + console2.log("====ROOT ADDRESSES===="); console2.log("Root ERC20 Bridge: %s", address(rootERC20Bridge)); console2.log("Root Axelar Bridge Adaptor: %s", address(rootBridgeAdaptor)); console2.log("ROOT CHAIN childTokenTemplate: %s", address(rootChainChildTokenTemplate)); - - vm.stopBroadcast(); } } From 140b51b3220bd8e43716be5b7af8155ca15c1a88 Mon Sep 17 00:00:00 2001 From: Craig M Date: Thu, 26 Oct 2023 17:33:27 +1300 Subject: [PATCH 13/15] readme updates --- .env.sample | 1 + README.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.env.sample b/.env.sample index 127b87b6..2fd22f8e 100644 --- a/.env.sample +++ b/.env.sample @@ -12,3 +12,4 @@ ROOT_CHAIN_NAME= CHILD_CHAIN_NAME= ROOT_IMX_ADDRESS= ROOT_WETH_ADDRESS= +ENVIRONMENT= diff --git a/README.md b/README.md index 8acd6cc0..5b0bd87d 100644 --- a/README.md +++ b/README.md @@ -138,10 +138,11 @@ ROOT_GATEWAY_ADDRESS="0x013459EC3E8Aeced878C5C4bFfe126A366cd19E9" CHILD_GATEWAY_ADDRESS="0xc7B788E88BAaB770A6d4936cdcCcd5250E1bbAd8" ROOT_GAS_SERVICE_ADDRESS="0x28f8B50E1Be6152da35e923602a2641491E71Ed8" CHILD_GAS_SERVICE_ADDRESS="0xC573c722e21eD7fadD38A8f189818433e01Ae466" - +ENVIRONMENT="local" ``` (Note that `{ROOT,CHILD}_PRIVATE_KEY` can be any of the standard localhost private keys that get funded) (Note that `ROOT_IMX_ADDRESS` is not currently used in this local environment. Therefore, any non-zero address is fine.) +(Note that `ENVIRONMENT` if the environment is not set to "local" then ROOT_WETH_ADDRESS will need to be manually set in the .env file since it is expected WETH to be already deployed on testnet or mainnet) 3. In a separate terminal window, deploy the smart contracts ```shell From cd63d623c543aa5390b646e9c58de6643dc91d24 Mon Sep 17 00:00:00 2001 From: Craig M Date: Thu, 26 Oct 2023 17:47:06 +1300 Subject: [PATCH 14/15] final patches --- src/interfaces/root/IRootERC20Bridge.sol | 2 +- src/root/RootERC20Bridge.sol | 8 ++++---- test/integration/root/RootERC20Bridge.t.sol | 2 +- test/unit/root/RootERC20Bridge.t.sol | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index e23c8a29..ef92c2b5 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -46,7 +46,7 @@ interface IRootERC20BridgeEvents { uint256 amount ); event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount); - event WETHDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount); + event WETHDeposit(address indexed rootToken, address indexed childToken, address depositor, address indexed receiver, uint256 amount); event NativeEthDeposit( address indexed rootToken, address indexed childToken, diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index 8cd59ff6..23ae2835 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -153,14 +153,13 @@ contract RootERC20Bridge is function _depositToken(IERC20Metadata rootToken, address receiver, uint256 amount) private { if (address(rootToken) == rootWETHToken) { - _unwrapETH(amount); - _deposit(IERC20Metadata(rootWETHToken), receiver, amount); + _depositWrappedETH(receiver, amount); } else { _depositERC20(rootToken, receiver, amount); } } - function _unwrapETH(uint256 amount) private { + function _depositWrappedETH(address receiver, uint256 amount) private { uint256 expectedBalance = address(this).balance + amount; IERC20Metadata erc20WETH = IERC20Metadata(rootWETHToken); @@ -172,6 +171,7 @@ contract RootERC20Bridge is if (address(this).balance != expectedBalance) { revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance); } + _deposit(IERC20Metadata(rootWETHToken), receiver, amount); } function _depositERC20(IERC20Metadata rootToken, address receiver, uint256 amount) private { @@ -263,7 +263,7 @@ contract RootERC20Bridge is if (address(rootToken) == NATIVE_ETH) { emit NativeEthDeposit(address(rootToken), childETHToken, msg.sender, receiver, amount); } else if (address(rootToken) == rootWETHToken) { - emit WETHDeposit(address(rootToken), msg.sender, receiver, amount); + emit WETHDeposit(address(rootToken), childETHToken, msg.sender, receiver, amount); } else if (address(rootToken) == rootIMXToken) { emit IMXDeposit(address(rootToken), msg.sender, receiver, amount); } else { diff --git a/test/integration/root/RootERC20Bridge.t.sol b/test/integration/root/RootERC20Bridge.t.sol index 691791ab..374ad9ff 100644 --- a/test/integration/root/RootERC20Bridge.t.sol +++ b/test/integration/root/RootERC20Bridge.t.sol @@ -227,7 +227,7 @@ contract RootERC20BridgeIntegrationTest is Test, IRootERC20BridgeEvents, IRootAx vm.expectEmit(address(axelarAdaptor)); emit MapTokenAxelarMessage(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); vm.expectEmit(address(rootBridge)); - emit WETHDeposit(address(WRAPPED_ETH), address(this), address(this), tokenAmount); + emit WETHDeposit(address(WRAPPED_ETH), rootBridge.childETHToken(), address(this), address(this), tokenAmount); vm.expectCall( address(axelarAdaptor), depositFee, diff --git a/test/unit/root/RootERC20Bridge.t.sol b/test/unit/root/RootERC20Bridge.t.sol index 24358c37..901a8f0b 100644 --- a/test/unit/root/RootERC20Bridge.t.sol +++ b/test/unit/root/RootERC20Bridge.t.sol @@ -349,7 +349,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid setupDeposit(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, false); vm.expectEmit(); - emit WETHDeposit(WRAPPED_ETH, address(this), address(this), amount); + emit WETHDeposit(WRAPPED_ETH, rootBridge.childETHToken(), address(this), address(this), amount); rootBridge.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), amount); } @@ -359,7 +359,7 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid setupDepositTo(WRAPPED_ETH, rootBridge, mapTokenFee, depositFee, amount, receiver, false); vm.expectEmit(); - emit WETHDeposit(WRAPPED_ETH, address(this), receiver, amount); + emit WETHDeposit(WRAPPED_ETH, rootBridge.childETHToken(), address(this), receiver, amount); rootBridge.depositTo{value: depositFee}(IERC20Metadata(WRAPPED_ETH), receiver, amount); } From d4bc8cb83afcf891e3d14e9dbe0414be99ace101 Mon Sep 17 00:00:00 2001 From: Craig M Date: Thu, 26 Oct 2023 17:48:11 +1300 Subject: [PATCH 15/15] formatting --- src/interfaces/root/IRootERC20Bridge.sol | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/interfaces/root/IRootERC20Bridge.sol b/src/interfaces/root/IRootERC20Bridge.sol index ef92c2b5..75fa06c4 100644 --- a/src/interfaces/root/IRootERC20Bridge.sol +++ b/src/interfaces/root/IRootERC20Bridge.sol @@ -46,7 +46,13 @@ interface IRootERC20BridgeEvents { uint256 amount ); event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount); - event WETHDeposit(address indexed rootToken, address indexed childToken, address depositor, address indexed receiver, uint256 amount); + event WETHDeposit( + address indexed rootToken, + address indexed childToken, + address depositor, + address indexed receiver, + uint256 amount + ); event NativeEthDeposit( address indexed rootToken, address indexed childToken,