diff --git a/script/InitializeChildContracts.s.sol b/script/InitializeChildContracts.s.sol index 15912cbe..8fa4d37d 100644 --- a/script/InitializeChildContracts.s.sol +++ b/script/InitializeChildContracts.s.sol @@ -62,7 +62,6 @@ contract InitializeChildContracts is Script { vm.createSelectFork(params.childRpcUrl); vm.startBroadcast(params.deployerPrivateKey); - // TODO update IChildERC20Bridge.InitializationRoles memory roles = IChildERC20Bridge.InitializationRoles({ defaultAdmin: params.childAdminAddress, pauser: params.childPauserAddress, diff --git a/script/InitializeRootContracts.s.sol b/script/InitializeRootContracts.s.sol index dd9e076d..7defcbf4 100644 --- a/script/InitializeRootContracts.s.sol +++ b/script/InitializeRootContracts.s.sol @@ -66,8 +66,6 @@ contract InitializeRootContracts is Script { vm.createSelectFork(params.rootRpcUrl); vm.startBroadcast(params.rootPrivateKey); - // TODO add pauser, unpauser roles. variable manager and Adaptor manager will be privileged transaction multisg - IRootERC20Bridge.InitializationRoles memory roles = IRootERC20Bridge.InitializationRoles({ defaultAdmin: params.rootAdminAddress, pauser: params.rootPauserAddress, diff --git a/src/root/RootAxelarBridgeAdaptor.sol b/src/root/RootAxelarBridgeAdaptor.sol index 26a21a04..8b2d2593 100644 --- a/src/root/RootAxelarBridgeAdaptor.sol +++ b/src/root/RootAxelarBridgeAdaptor.sol @@ -130,7 +130,6 @@ contract RootAxelarBridgeAdaptor is string memory _childBridgeAdaptor = IRootERC20Bridge(rootBridge).childBridgeAdaptor(); string memory _childChain = childChain; - // TODO For `sender` (first param), should likely be refundRecipient (and in which case refundRecipient should be renamed to sender and used as refund recipient) gasService.payNativeGasForContractCall{value: msg.value}( address(this), _childChain, _childBridgeAdaptor, payload, refundRecipient ); diff --git a/src/root/RootERC20Bridge.sol b/src/root/RootERC20Bridge.sol index 96e66c14..f435d7c0 100644 --- a/src/root/RootERC20Bridge.sol +++ b/src/root/RootERC20Bridge.sol @@ -398,7 +398,6 @@ contract RootERC20Bridge is BridgeRoles, IRootERC20Bridge, IRootERC20BridgeEvent bytes memory payload = abi.encode(MAP_TOKEN_SIG, rootToken, rootToken.name(), rootToken.symbol(), rootToken.decimals()); - // TODO investigate using delegatecall to keep the axelar message sender as the bridge contract, since adaptor can change. rootBridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender); emit L1TokenMapped(address(rootToken), childToken); @@ -451,7 +450,6 @@ contract RootERC20Bridge is BridgeRoles, IRootERC20Bridge, IRootERC20BridgeEvent // 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); if (address(rootToken) == NATIVE_ETH) { diff --git a/test/integration/child/ChildAxelarBridge.t.sol b/test/integration/child/ChildAxelarBridge.t.sol index 6dfd7c0c..408a0d8e 100644 --- a/test/integration/child/ChildAxelarBridge.t.sol +++ b/test/integration/child/ChildAxelarBridge.t.sol @@ -270,8 +270,6 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil ); } - // TODO add mapToken calls to these to isolate the specific error we are testing for vvvv - function test_RevertIf_depositWithRootTokenZeroAddress() public { address rootTokenAddress = address(0); address sender = address(0xff); @@ -280,6 +278,15 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil bytes32 commandId = bytes32("testCommandId"); bytes32 depositSig = childERC20Bridge.DEPOSIT_SIG(); + { + // Found by running `forge inspect src/child/ChildERC20Bridge.sol:ChildERC20Bridge storageLayout | grep -B3 -A5 -i "rootTokenToChildToken"` + uint256 rootTokenToChildTokenMappingSlot = 201; + address childAddress = address(444444); + bytes32 slot = getMappingStorageSlotFor(rootTokenAddress, rootTokenToChildTokenMappingSlot); + bytes32 data = bytes32(uint256(uint160(childAddress))); + vm.store(address(childERC20Bridge), slot, data); + } + vm.expectRevert(ZeroAddress.selector); childAxelarBridgeAdaptor.execute( commandId, @@ -297,6 +304,15 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil bytes32 commandId = bytes32("testCommandId"); bytes32 depositSig = childERC20Bridge.DEPOSIT_SIG(); + { + // Found by running `forge inspect src/child/ChildERC20Bridge.sol:ChildERC20Bridge storageLayout | grep -B3 -A5 -i "rootTokenToChildToken"` + uint256 rootTokenToChildTokenMappingSlot = 201; + address childAddress = address(444444); + bytes32 slot = getMappingStorageSlotFor(rootTokenAddress, rootTokenToChildTokenMappingSlot); + bytes32 data = bytes32(uint256(uint160(childAddress))); + vm.store(address(childERC20Bridge), slot, data); + } + vm.expectRevert(ZeroAddress.selector); childAxelarBridgeAdaptor.execute( commandId, diff --git a/test/integration/root/RootERC20BridgeFlowRate.t.sol b/test/integration/root/RootERC20BridgeFlowRate.t.sol index da95a453..cd12454f 100644 --- a/test/integration/root/RootERC20BridgeFlowRate.t.sol +++ b/test/integration/root/RootERC20BridgeFlowRate.t.sol @@ -61,8 +61,25 @@ contract RootERC20BridgeFlowRateIntegrationTest is * This test uses the same code as the mapToken function does to calculate this address, so we can * not consider it sufficient. */ - function test_mapToken() public { - // TODO split this up into multiple tests. + + function test_mapTokenTransfersValue() public { + address childToken = + Clones.predictDeterministicAddress(address(token), keccak256(abi.encodePacked(token)), CHILD_BRIDGE); + + // Check that we pay mapTokenFee to the axelarGasService. + uint256 thisPreBal = address(this).balance; + uint256 axelarGasServicePreBal = address(axelarGasService).balance; + + rootBridgeFlowRate.mapToken{value: mapTokenFee}(token); + + // Should update ETH balances as gas payment for message. + assertEq(address(this).balance, thisPreBal - mapTokenFee, "ETH balance not decreased"); + assertEq(address(axelarGasService).balance, axelarGasServicePreBal + mapTokenFee, "ETH not paid to gas service"); + + assertEq(rootBridgeFlowRate.rootTokenToChildToken(address(token)), childToken, "childToken not set"); + } + + function test_mapTokenEmitsEvents() public { address childToken = Clones.predictDeterministicAddress(address(token), keccak256(abi.encodePacked(token)), CHILD_BRIDGE); @@ -73,6 +90,12 @@ contract RootERC20BridgeFlowRateIntegrationTest is vm.expectEmit(true, true, false, false, address(rootBridgeFlowRate)); emit L1TokenMapped(address(token), childToken); + rootBridgeFlowRate.mapToken{value: mapTokenFee}(token); + } + + function test_mapTokenCallsAxelarServices() public { + bytes memory payload = abi.encode(MAP_TOKEN_SIG, address(token), token.name(), token.symbol(), token.decimals()); + // Instead of using expectCalls, we could use expectEmit in combination with mock contracts emitting events. // expectCalls requires less boilerplate and is less dependant on mock code. vm.expectCall( @@ -106,29 +129,38 @@ contract RootERC20BridgeFlowRateIntegrationTest is ) ); - // Check that we pay mapTokenFee to the axelarGasService. - uint256 thisPreBal = address(this).balance; - uint256 axelarGasServicePreBal = address(axelarGasService).balance; - rootBridgeFlowRate.mapToken{value: mapTokenFee}(token); + } - // Should update ETH balances as gas payment for message. - assertEq(address(this).balance, thisPreBal - mapTokenFee, "ETH balance not decreased"); - assertEq(address(axelarGasService).balance, axelarGasServicePreBal + mapTokenFee, "ETH not paid to gas service"); + /** + * DEPOSIT ETH + */ - assertEq(rootBridgeFlowRate.rootTokenToChildToken(address(token)), childToken, "childToken not set"); + function test_depositETHTransfersValue() public { + uint256 tokenAmount = 300; + setupDeposit(NATIVE_ETH, rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, false); + + uint256 bridgePreBal = address(rootBridgeFlowRate).balance; + + uint256 thisNativePreBal = address(this).balance; + uint256 gasServiceNativePreBal = address(axelarGasService).balance; + + rootBridgeFlowRate.depositETH{value: tokenAmount + depositFee}(tokenAmount); + + // Check that tokens are transferred + assertEq(bridgePreBal + tokenAmount, address(rootBridgeFlowRate).balance, "ETH not transferred to bridge"); + // Check that native asset transferred to gas service + assertEq(thisNativePreBal - (depositFee + tokenAmount), address(this).balance, "ETH not paid from user"); + assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); } - // TODO split into multiple tests - function test_depositETH() public { + function test_depositETHEmitsEvents() public { uint256 tokenAmount = 300; string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); (, bytes memory predictedPayload) = setupDeposit(NATIVE_ETH, rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, false); - console2.logBytes(predictedPayload); - vm.expectEmit(address(axelarAdaptor)); emit AxelarMessageSent(CHILD_CHAIN_NAME, childBridgeAdaptorString, predictedPayload); vm.expectEmit(address(rootBridgeFlowRate)); @@ -136,6 +168,16 @@ contract RootERC20BridgeFlowRateIntegrationTest is address(NATIVE_ETH), rootBridgeFlowRate.childETHToken(), address(this), address(this), tokenAmount ); + rootBridgeFlowRate.depositETH{value: tokenAmount + depositFee}(tokenAmount); + } + + function test_depositETHCallsAxelarServices() public { + uint256 tokenAmount = 300; + string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); + + (, bytes memory predictedPayload) = + setupDeposit(NATIVE_ETH, rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, false); + vm.expectCall( address(axelarAdaptor), depositFee, @@ -163,22 +205,39 @@ contract RootERC20BridgeFlowRateIntegrationTest is ) ); - uint256 bridgePreBal = address(rootBridgeFlowRate).balance; + rootBridgeFlowRate.depositETH{value: tokenAmount + depositFee}(tokenAmount); + } + + /** + * DEPOSIT IMX + */ + + function test_depositIMXTokenTransfersValue() public { + uint256 tokenAmount = 300; + + setupDeposit(IMX_TOKEN_ADDRESS, rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, false); + + uint256 thisPreBal = imxToken.balanceOf(address(this)); + uint256 bridgePreBal = imxToken.balanceOf(address(rootBridgeFlowRate)); uint256 thisNativePreBal = address(this).balance; uint256 gasServiceNativePreBal = address(axelarGasService).balance; - rootBridgeFlowRate.depositETH{value: tokenAmount + depositFee}(tokenAmount); + rootBridgeFlowRate.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN_ADDRESS), tokenAmount); // Check that tokens are transferred - assertEq(bridgePreBal + tokenAmount, address(rootBridgeFlowRate).balance, "ETH not transferred to bridge"); + assertEq(thisPreBal - tokenAmount, imxToken.balanceOf(address(this)), "Tokens not transferred from user"); + assertEq( + bridgePreBal + tokenAmount, + imxToken.balanceOf(address(rootBridgeFlowRate)), + "Tokens not transferred to bridge" + ); // Check that native asset transferred to gas service - assertEq(thisNativePreBal - (depositFee + tokenAmount), address(this).balance, "ETH not paid from user"); + assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH not paid from user"); assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); } - // TODO split into multiple tests - function test_depositIMXToken() public { + function test_depositIMXTokenEmitsEvents() public { uint256 tokenAmount = 300; string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); @@ -190,6 +249,16 @@ contract RootERC20BridgeFlowRateIntegrationTest is vm.expectEmit(address(rootBridgeFlowRate)); emit IMXDeposit(address(IMX_TOKEN_ADDRESS), address(this), address(this), tokenAmount); + rootBridgeFlowRate.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN_ADDRESS), tokenAmount); + } + + function test_depositIMXTokenCallsAxelarServices() public { + uint256 tokenAmount = 300; + string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); + + (, bytes memory predictedPayload) = + setupDeposit(IMX_TOKEN_ADDRESS, rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, false); + vm.expectCall( address(axelarAdaptor), depositFee, @@ -217,28 +286,38 @@ contract RootERC20BridgeFlowRateIntegrationTest is ) ); - uint256 thisPreBal = imxToken.balanceOf(address(this)); - uint256 bridgePreBal = imxToken.balanceOf(address(rootBridgeFlowRate)); + rootBridgeFlowRate.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN_ADDRESS), tokenAmount); + } + + /** + * DEPOSIT WETH + */ + + function test_depositWETHTransfersValue() public { + uint256 tokenAmount = 300; + setupDeposit(WRAPPED_ETH, rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, false); + + uint256 thisPreBal = IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)); + uint256 bridgePreBal = address(rootBridgeFlowRate).balance; uint256 thisNativePreBal = address(this).balance; uint256 gasServiceNativePreBal = address(axelarGasService).balance; - rootBridgeFlowRate.deposit{value: depositFee}(IERC20Metadata(IMX_TOKEN_ADDRESS), tokenAmount); + rootBridgeFlowRate.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), tokenAmount); // Check that tokens are transferred - assertEq(thisPreBal - tokenAmount, imxToken.balanceOf(address(this)), "Tokens not transferred from user"); assertEq( - bridgePreBal + tokenAmount, - imxToken.balanceOf(address(rootBridgeFlowRate)), - "Tokens not transferred to bridge" + thisPreBal - tokenAmount, + IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), + "Tokens not transferred from user" ); + assertEq(bridgePreBal + tokenAmount, address(rootBridgeFlowRate).balance, "ETH not transferred to Bridge"); // Check that native asset transferred to gas service - assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH not paid from user"); + 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_depositWETH() public { + function test_depositWETHEmitsEvents() public { uint256 tokenAmount = 300; string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); (, bytes memory predictedPayload) = @@ -250,6 +329,15 @@ contract RootERC20BridgeFlowRateIntegrationTest is emit WETHDeposit( address(WRAPPED_ETH), rootBridgeFlowRate.childETHToken(), address(this), address(this), tokenAmount ); + rootBridgeFlowRate.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), tokenAmount); + } + + function test_depositWETHCallsAxelarServices() public { + uint256 tokenAmount = 300; + string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); + (, bytes memory predictedPayload) = + setupDeposit(WRAPPED_ETH, rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, false); + vm.expectCall( address(axelarAdaptor), depositFee, @@ -277,28 +365,35 @@ contract RootERC20BridgeFlowRateIntegrationTest is ) ); - uint256 thisPreBal = IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)); - uint256 bridgePreBal = address(rootBridgeFlowRate).balance; + rootBridgeFlowRate.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), tokenAmount); + } + + /** + * DEPOSIT TOKEN + */ + function test_depositTokenTransfersValue() public { + uint256 tokenAmount = 300; + setupDeposit(address(token), rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, true); + + uint256 thisPreBal = token.balanceOf(address(this)); + uint256 bridgePreBal = token.balanceOf(address(rootBridgeFlowRate)); uint256 thisNativePreBal = address(this).balance; uint256 gasServiceNativePreBal = address(axelarGasService).balance; - rootBridgeFlowRate.deposit{value: depositFee}(IERC20Metadata(WRAPPED_ETH), tokenAmount); + rootBridgeFlowRate.deposit{value: depositFee}(token, tokenAmount); // Check that tokens are transferred + assertEq(thisPreBal - tokenAmount, token.balanceOf(address(this)), "Tokens not transferred from user"); assertEq( - thisPreBal - tokenAmount, - IERC20Metadata(WRAPPED_ETH).balanceOf(address(this)), - "Tokens not transferred from user" + bridgePreBal + tokenAmount, token.balanceOf(address(rootBridgeFlowRate)), "Tokens not transferred to bridge" ); - assertEq(bridgePreBal + tokenAmount, address(rootBridgeFlowRate).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(thisNativePreBal - depositFee, address(this).balance, "ETH not paid from user"); assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); } - // TODO split into multiple tests - function test_depositToken() public { + function test_depositTokenEmitsEvents() public { uint256 tokenAmount = 300; string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); (address childToken, bytes memory predictedPayload) = @@ -309,6 +404,15 @@ contract RootERC20BridgeFlowRateIntegrationTest is vm.expectEmit(address(rootBridgeFlowRate)); emit ChildChainERC20Deposit(address(token), childToken, address(this), address(this), tokenAmount); + rootBridgeFlowRate.deposit{value: depositFee}(token, tokenAmount); + } + + function test_depositTokenCallsAxelarServices() public { + uint256 tokenAmount = 300; + string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); + (, bytes memory predictedPayload) = + setupDeposit(address(token), rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, true); + vm.expectCall( address(axelarAdaptor), depositFee, @@ -336,13 +440,25 @@ contract RootERC20BridgeFlowRateIntegrationTest is ) ); + rootBridgeFlowRate.deposit{value: depositFee}(token, tokenAmount); + } + + /** + * DEPOSIT TO + */ + + function test_depositToTransfersValue() public { + uint256 tokenAmount = 300; + address recipient = address(9876); + setupDepositTo(address(token), rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, recipient, true); + uint256 thisPreBal = token.balanceOf(address(this)); uint256 bridgePreBal = token.balanceOf(address(rootBridgeFlowRate)); uint256 thisNativePreBal = address(this).balance; uint256 gasServiceNativePreBal = address(axelarGasService).balance; - rootBridgeFlowRate.deposit{value: depositFee}(token, tokenAmount); + rootBridgeFlowRate.depositTo{value: depositFee}(token, recipient, tokenAmount); // Check that tokens are transferred assertEq(thisPreBal - tokenAmount, token.balanceOf(address(this)), "Tokens not transferred from user"); @@ -354,8 +470,7 @@ contract RootERC20BridgeFlowRateIntegrationTest is assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); } - // TODO split into multiple tests - function test_depositTo() public { + function test_depositToEmitsEvents() public { uint256 tokenAmount = 300; address recipient = address(9876); string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); @@ -367,6 +482,16 @@ contract RootERC20BridgeFlowRateIntegrationTest is vm.expectEmit(address(rootBridgeFlowRate)); emit ChildChainERC20Deposit(address(token), childToken, address(this), recipient, tokenAmount); + rootBridgeFlowRate.depositTo{value: depositFee}(token, recipient, tokenAmount); + } + + function test_depositToCallsAxelarServices() public { + uint256 tokenAmount = 300; + address recipient = address(9876); + string memory childBridgeAdaptorString = Strings.toHexString(CHILD_BRIDGE_ADAPTOR); + (, bytes memory predictedPayload) = + setupDepositTo(address(token), rootBridgeFlowRate, mapTokenFee, depositFee, tokenAmount, recipient, true); + vm.expectCall( address(axelarAdaptor), depositFee, @@ -394,21 +519,6 @@ contract RootERC20BridgeFlowRateIntegrationTest is ) ); - uint256 thisPreBal = token.balanceOf(address(this)); - uint256 bridgePreBal = token.balanceOf(address(rootBridgeFlowRate)); - - uint256 thisNativePreBal = address(this).balance; - uint256 gasServiceNativePreBal = address(axelarGasService).balance; - rootBridgeFlowRate.depositTo{value: depositFee}(token, recipient, tokenAmount); - - // Check that tokens are transferred - assertEq(thisPreBal - tokenAmount, token.balanceOf(address(this)), "Tokens not transferred from user"); - assertEq( - bridgePreBal + tokenAmount, token.balanceOf(address(rootBridgeFlowRate)), "Tokens not transferred to bridge" - ); - // Check that native asset transferred to gas service - assertEq(thisNativePreBal - depositFee, address(this).balance, "ETH not paid from user"); - assertEq(gasServiceNativePreBal + depositFee, address(axelarGasService).balance, "ETH not paid to adaptor"); } }