From 080dedc4c296ffe1a750b8852794a4a5b9923ef5 Mon Sep 17 00:00:00 2001 From: Benjamin Patch Date: Wed, 1 Nov 2023 11:44:03 +1100 Subject: [PATCH] ERC20 Bridging pre-tests --- lib/openzeppelin-contracts | 2 +- script/DeployChildContracts.s.sol | 1 - script/InitializeChildContracts.s.sol | 3 +- src/child/ChildAxelarBridgeAdaptor.sol | 38 +++++++++++++++++-- src/child/ChildERC20Bridge.sol | 15 ++------ .../child/IChildAxelarBridgeAdaptor.sol | 9 +++++ .../child/IChildERC20BridgeAdaptor.sol | 11 +++++- src/root/RootAxelarBridgeAdaptor.sol | 2 - .../integration/child/ChildAxelarBridge.t.sol | 5 ++- .../unit/child/ChildAxelarBridgeAdaptor.t.sol | 10 +++-- 10 files changed, 70 insertions(+), 26 deletions(-) diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 9329cfacd..fd81a96f0 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 9329cfacd4c7d20bcb43d772d947ff9e39b65df9 +Subproject commit fd81a96f01cc42ef1c9a5399364968d0e07e9e90 diff --git a/script/DeployChildContracts.s.sol b/script/DeployChildContracts.s.sol index a8d1f7c0a..530eadb4e 100644 --- a/script/DeployChildContracts.s.sol +++ b/script/DeployChildContracts.s.sol @@ -17,7 +17,6 @@ contract DeployChildContracts is Script { function run() public { uint256 deployerPrivateKey = vm.envUint("CHILD_PRIVATE_KEY"); address childGateway = vm.envAddress("CHILD_GATEWAY_ADDRESS"); - address childGasService = vm.envAddress("CHILD_GAS_SERVICE_ADDRESS"); // Not yet used. string memory childRpcUrl = vm.envString("CHILD_RPC_URL"); vm.createSelectFork(childRpcUrl); diff --git a/script/InitializeChildContracts.s.sol b/script/InitializeChildContracts.s.sol index ec29a6cd9..314ade367 100644 --- a/script/InitializeChildContracts.s.sol +++ b/script/InitializeChildContracts.s.sol @@ -20,6 +20,7 @@ contract InitializeChildContracts is Script { string memory childRpcUrl = vm.envString("CHILD_RPC_URL"); string memory rootChainName = vm.envString("ROOT_CHAIN_NAME"); address rootIMXToken = vm.envAddress("ROOT_IMX_ADDRESS"); + address childGasService = vm.envAddress("CHILD_GAS_SERVICE_ADDRESS"); // Not yet used. /** * INITIALIZE CHILD CONTRACTS @@ -35,7 +36,7 @@ contract InitializeChildContracts is Script { address(childAxelarBridgeAdaptor), rootBridgeAdaptorString, childTokenTemplate, rootChainName, rootIMXToken ); - childAxelarBridgeAdaptor.initialize(address(childERC20Bridge)); + childAxelarBridgeAdaptor.initialize(rootChainName, address(childERC20Bridge), childGasService); vm.stopBroadcast(); } diff --git a/src/child/ChildAxelarBridgeAdaptor.sol b/src/child/ChildAxelarBridgeAdaptor.sol index 4408bb77d..3c6d2a64b 100644 --- a/src/child/ChildAxelarBridgeAdaptor.sol +++ b/src/child/ChildAxelarBridgeAdaptor.sol @@ -2,14 +2,20 @@ pragma solidity ^0.8.21; import {AxelarExecutable} from "@axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; +import {IAxelarGasService} from "@axelar-cgp-solidity/contracts/interfaces/IAxelarGasService.sol"; +import {IAxelarGateway} from "@axelar-cgp-solidity/contracts/interfaces/IAxelarGateway.sol"; import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {IChildERC20Bridge} from "../interfaces/child/IChildERC20Bridge.sol"; -import {IChildAxelarBridgeAdaptorErrors} from "../interfaces/child/IChildAxelarBridgeAdaptor.sol"; +import {IChildAxelarBridgeAdaptorErrors, IChildAxelarBridgeAdaptorEvents} from "../interfaces/child/IChildAxelarBridgeAdaptor.sol"; +import {IChildERC20BridgeAdaptor} from "../interfaces/child/IChildERC20BridgeAdaptor.sol"; -contract ChildAxelarBridgeAdaptor is AxelarExecutable, Initializable, IChildAxelarBridgeAdaptorErrors { +contract ChildAxelarBridgeAdaptor is AxelarExecutable, IChildERC20BridgeAdaptor, Initializable, IChildAxelarBridgeAdaptorErrors, + IChildAxelarBridgeAdaptorEvents { /// @notice Address of bridge to relay messages to. IChildERC20Bridge public childBridge; + IAxelarGasService public gasService; string public rootBridgeAdaptor; + string public rootChain; constructor(address _gateway) AxelarExecutable(_gateway) {} @@ -18,12 +24,14 @@ contract ChildAxelarBridgeAdaptor is AxelarExecutable, Initializable, IChildAxel * @param _childBridge Address of the child bridge contract. * @dev Always sets the rootBridgeAdaptor to whatever the rootERC20BridgeAdaptor of the bridge contract is. */ - function initialize(address _childBridge) external initializer { + function initialize(string memory _rootChain, address _childBridge, address _gasService) external initializer { if (_childBridge == address(0)) { revert ZeroAddress(); } childBridge = IChildERC20Bridge(_childBridge); + rootChain = _rootChain; + gasService = IAxelarGasService(_gasService); rootBridgeAdaptor = childBridge.rootERC20BridgeAdaptor(); } @@ -37,6 +45,30 @@ contract ChildAxelarBridgeAdaptor is AxelarExecutable, Initializable, IChildAxel rootBridgeAdaptor = childBridge.rootERC20BridgeAdaptor(); } + /** + * TODO + */ + function sendMessage(bytes calldata payload, address refundRecipient) external payable override { + if (msg.value == 0) { + revert NoGas(); + } + if (msg.sender != address(childBridge)) { + revert CallerNotBridge(); + } + + // Load from storage. + string memory _rootBridgeAdaptor = rootBridgeAdaptor; + string memory _rootChain = rootChain; + + // 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), _rootChain, _rootBridgeAdaptor, payload, refundRecipient + ); + + gateway.callContract(_rootChain, _rootBridgeAdaptor, payload); + emit MapTokenAxelarMessage(_rootChain, _rootBridgeAdaptor, payload); + } + /** * @dev This function is called by the parent `AxelarExecutable` contract to execute the payload. * @custom:assumes `sourceAddress_` is a 20 byte address. diff --git a/src/child/ChildERC20Bridge.sol b/src/child/ChildERC20Bridge.sol index bc845ffdb..c77938009 100644 --- a/src/child/ChildERC20Bridge.sol +++ b/src/child/ChildERC20Bridge.sol @@ -132,9 +132,7 @@ contract ChildERC20Bridge is } function withdrawTo(IChildERC20 childToken, address receiver, uint256 amount) external { - _beforeTokenWithdraw(); _withdraw(childToken, receiver, amount); - _afterTokenWithdraw(); } function _withdraw(IChildERC20 childToken, address receiver, uint256 amount) private { @@ -166,16 +164,9 @@ contract ChildERC20Bridge is bytes memory payload = abi.encode(WITHDRAW_SIG, rootToken, msg.sender, receiver, amount); // Send the message to the bridge adaptor and up to root chain - bridgeAdaptor.sendMessage(rootChain, rootERC20BridgeAdaptor, payload); - - if (address(childToken) == childETHToken) { - childToken.burn(msg.sender, amount); - Address.sendValue(payable(receiver), amount); - } else { - childToken.burn(msg.sender, amount); - IERC20Metadata rootToken = IERC20Metadata(childToken.rootToken()); - rootToken.safeTransfer(receiver, amount); - } + bridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender); + + // TODO emit event } function _mapToken(bytes calldata data) private { diff --git a/src/interfaces/child/IChildAxelarBridgeAdaptor.sol b/src/interfaces/child/IChildAxelarBridgeAdaptor.sol index c35d82fc1..0e13f2ae1 100644 --- a/src/interfaces/child/IChildAxelarBridgeAdaptor.sol +++ b/src/interfaces/child/IChildAxelarBridgeAdaptor.sol @@ -4,4 +4,13 @@ pragma solidity ^0.8.21; interface IChildAxelarBridgeAdaptorErrors { /// @notice Error when a zero address is given when not valid. error ZeroAddress(); + /// @notice Error when a message is sent with no gas payment. + error NoGas(); + /// @notice Error when the caller is not the bridge. + error CallerNotBridge(); } + +interface IChildAxelarBridgeAdaptorEvents { + /// @notice Emitted when an Axelar message is sent to the root chain. + event MapTokenAxelarMessage(string indexed rootChain, string indexed rootBridgeAdaptor, bytes indexed payload); +} \ No newline at end of file diff --git a/src/interfaces/child/IChildERC20BridgeAdaptor.sol b/src/interfaces/child/IChildERC20BridgeAdaptor.sol index 620d3e46b..1407beb0b 100644 --- a/src/interfaces/child/IChildERC20BridgeAdaptor.sol +++ b/src/interfaces/child/IChildERC20BridgeAdaptor.sol @@ -1,5 +1,12 @@ // SPDX-License-Identifier: Apache 2.0 pragma solidity ^0.8.21; -// TODO to be used when sending messages L2 -> L1 -interface IChildERC20BridgeAdaptor {} +interface IChildERC20BridgeAdaptor { + /** + * @notice Send an arbitrary message to the root chain via the message passing protocol. + * @param payload The message to send, encoded in a `bytes` array. + * @param refundRecipient Used if the message passing protocol requires fees & pays back excess to a refund recipient. + * @dev `payable` because the message passing protocol may require a fee to be paid. + */ + function sendMessage(bytes calldata payload, address refundRecipient) external payable; +} diff --git a/src/root/RootAxelarBridgeAdaptor.sol b/src/root/RootAxelarBridgeAdaptor.sol index 0f8816438..e2642245c 100644 --- a/src/root/RootAxelarBridgeAdaptor.sol +++ b/src/root/RootAxelarBridgeAdaptor.sol @@ -30,8 +30,6 @@ contract RootAxelarBridgeAdaptor is address public rootBridge; string public childBridgeAdaptor; - /// @dev childChain could be immutable, but as of writing this Solidity does not support immutable strings. - /// see: https://ethereum.stackexchange.com/questions/127622/typeerror-immutable-variables-cannot-have-a-non-value-type string public childChain; IAxelarGateway public axelarGateway; IAxelarGasService public gasService; diff --git a/test/integration/child/ChildAxelarBridge.t.sol b/test/integration/child/ChildAxelarBridge.t.sol index 945176926..282499b5d 100644 --- a/test/integration/child/ChildAxelarBridge.t.sol +++ b/test/integration/child/ChildAxelarBridge.t.sol @@ -13,6 +13,7 @@ import { } from "../../../src/child/ChildERC20Bridge.sol"; import {IChildERC20, ChildERC20} from "../../../src/child/ChildERC20.sol"; import {MockChildAxelarGateway} from "../../../src/test/child/MockChildAxelarGateway.sol"; +import {MockChildAxelarGasService} from "../../../src/test/child/MockChildAxelarGasService.sol"; import {Utils} from "../../utils.t.sol"; contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChildERC20BridgeErrors, Utils { @@ -25,6 +26,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil ChildERC20 public childERC20; ChildAxelarBridgeAdaptor public childAxelarBridgeAdaptor; MockChildAxelarGateway public mockChildAxelarGateway; + MockChildAxelarGasService public mockChildAxelarGasService; function setUp() public { childERC20 = new ChildERC20(); @@ -32,6 +34,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil childERC20Bridge = new ChildERC20Bridge(); mockChildAxelarGateway = new MockChildAxelarGateway(); + mockChildAxelarGasService = new MockChildAxelarGasService(); childAxelarBridgeAdaptor = new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway)); childERC20Bridge.initialize( @@ -42,7 +45,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil IMX_TOKEN_ADDRESS ); - childAxelarBridgeAdaptor.initialize(address(childERC20Bridge)); + childAxelarBridgeAdaptor.initialize(ROOT_CHAIN_NAME, address(childERC20Bridge), address(mockChildAxelarGasService)); } function test_ChildTokenMap() public { diff --git a/test/unit/child/ChildAxelarBridgeAdaptor.t.sol b/test/unit/child/ChildAxelarBridgeAdaptor.t.sol index 18e324a1f..5e80de4cf 100644 --- a/test/unit/child/ChildAxelarBridgeAdaptor.t.sol +++ b/test/unit/child/ChildAxelarBridgeAdaptor.t.sol @@ -7,6 +7,7 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {ChildAxelarBridgeAdaptor} from "../../../src/child/ChildAxelarBridgeAdaptor.sol"; import {MockChildERC20Bridge} from "../../../src/test/child/MockChildERC20Bridge.sol"; import {MockChildAxelarGateway} from "../../../src/test/child/MockChildAxelarGateway.sol"; +import {MockChildAxelarGasService} from "../../../src/test/child/MockChildAxelarGasService.sol"; import {IChildAxelarBridgeAdaptorErrors} from "../../../src/interfaces/child/IChildAxelarBridgeAdaptor.sol"; contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErrors { @@ -15,12 +16,14 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro ChildAxelarBridgeAdaptor public childAxelarBridgeAdaptor; MockChildERC20Bridge public mockChildERC20Bridge; MockChildAxelarGateway public mockChildAxelarGateway; + MockChildAxelarGasService public mockChildAxelarGasService; function setUp() public { mockChildERC20Bridge = new MockChildERC20Bridge(); mockChildAxelarGateway = new MockChildAxelarGateway(); + mockChildAxelarGasService = new MockChildAxelarGasService(); childAxelarBridgeAdaptor = new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway)); - childAxelarBridgeAdaptor.initialize(address(mockChildERC20Bridge)); + childAxelarBridgeAdaptor.initialize("root", address(mockChildERC20Bridge), address(mockChildAxelarGasService)); } function test_Constructor_SetsValues() public { @@ -28,10 +31,11 @@ contract ChildAxelarBridgeAdaptorUnitTest is Test, IChildAxelarBridgeAdaptorErro assertEq(address(childAxelarBridgeAdaptor.gateway()), address(mockChildAxelarGateway), "gateway not set"); } - function test_RevertIf_ConstructorGivenZeroAddress() public { + // TODO add more initialize tests + function test_RevertIf_InitializeGivenZeroAddress() public { ChildAxelarBridgeAdaptor newAdaptor = new ChildAxelarBridgeAdaptor(GATEWAY_ADDRESS); vm.expectRevert(ZeroAddress.selector); - newAdaptor.initialize(address(0)); + newAdaptor.initialize("root", address(0), address(mockChildAxelarGasService)); } function test_Execute() public {