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);