Skip to content

Commit

Permalink
ERC20 Bridging pre-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjimmutable committed Nov 1, 2023
1 parent 3c5b7c7 commit 080dedc
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 26 deletions.
1 change: 0 additions & 1 deletion script/DeployChildContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion script/InitializeChildContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
}
Expand Down
38 changes: 35 additions & 3 deletions src/child/ChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}

Expand All @@ -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();
}

Expand All @@ -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.
Expand Down
15 changes: 3 additions & 12 deletions src/child/ChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions src/interfaces/child/IChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
11 changes: 9 additions & 2 deletions src/interfaces/child/IChildERC20BridgeAdaptor.sol
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 0 additions & 2 deletions src/root/RootAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion test/integration/child/ChildAxelarBridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -25,13 +26,15 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil
ChildERC20 public childERC20;
ChildAxelarBridgeAdaptor public childAxelarBridgeAdaptor;
MockChildAxelarGateway public mockChildAxelarGateway;
MockChildAxelarGasService public mockChildAxelarGasService;

function setUp() public {
childERC20 = new ChildERC20();
childERC20.initialize(address(123), "Test", "TST", 18);

childERC20Bridge = new ChildERC20Bridge();
mockChildAxelarGateway = new MockChildAxelarGateway();
mockChildAxelarGasService = new MockChildAxelarGasService();
childAxelarBridgeAdaptor = new ChildAxelarBridgeAdaptor(address(mockChildAxelarGateway));

childERC20Bridge.initialize(
Expand All @@ -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 {
Expand Down
10 changes: 7 additions & 3 deletions test/unit/child/ChildAxelarBridgeAdaptor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -15,23 +16,26 @@ 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 {
assertEq(address(childAxelarBridgeAdaptor.childBridge()), address(mockChildERC20Bridge), "childBridge not set");
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 {
Expand Down

0 comments on commit 080dedc

Please sign in to comment.