Skip to content

Commit

Permalink
Merge branch 'main' of github.com:immutable/zkevm-bridge-contracts in…
Browse files Browse the repository at this point in the history
…to smr-1904-imx-deposit-limit
  • Loading branch information
Benjimmutable committed Nov 3, 2023
2 parents c5450a6 + a7fe486 commit 92546c9
Show file tree
Hide file tree
Showing 24 changed files with 891 additions and 58 deletions.
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
47 changes: 44 additions & 3 deletions src/child/ChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,67 @@
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) {}

/**
* @notice Initializes the contract.
* @param _childBridge Address of the child bridge contract.
*/
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();
}

/**
* 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;

gasService.payNativeGasForContractCall{value: msg.value}(
address(this), _rootChain, _rootBridgeAdaptor, payload, refundRecipient
);

gateway.callContract(_rootChain, _rootBridgeAdaptor, payload);
emit AxelarMessage(_rootChain, _rootBridgeAdaptor, payload);
}

/**
Expand Down
47 changes: 46 additions & 1 deletion src/child/ChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ contract ChildERC20Bridge is

bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN");
bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT");
bytes32 public constant WITHDRAW_SIG = keccak256("WITHDRAW");
address public constant NATIVE_ETH = address(0xeee);

IChildERC20BridgeAdaptor public bridgeAdaptor;
Expand Down Expand Up @@ -125,6 +126,50 @@ contract ChildERC20Bridge is
}
}

function withdraw(IChildERC20 childToken, uint256 amount) external payable {
_withdraw(childToken, msg.sender, amount);
}

function withdrawTo(IChildERC20 childToken, address receiver, uint256 amount) external payable {
_withdraw(childToken, receiver, amount);
}

function _withdraw(IChildERC20 childToken, address receiver, uint256 amount) private {
if (address(childToken).code.length == 0) {
revert EmptyTokenContract();
}

address rootToken = childToken.rootToken();

if (rootTokenToChildToken[rootToken] != address(childToken)) {
revert NotMapped();
}

// A mapped token should never have root token unset
if (rootToken == address(0)) {
revert ZeroAddressRootToken();
}

// A mapped token should never have the bridge unset
if (childToken.bridge() != address(this)) {
revert BridgeNotSet();
}

if (!childToken.burn(msg.sender, amount)) {
revert BurnFailed();
}

// TODO Should we enforce receiver != 0? old poly contracts don't

// Encode the message payload
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{value: msg.value}(payload, msg.sender);

emit ChildChainERC20Withdraw(rootToken, address(childToken), msg.sender, receiver, amount);
}

function _mapToken(bytes calldata data) private {
(, address rootToken, string memory name, string memory symbol, uint8 decimals) =
abi.decode(data, (bytes32, address, string, string, uint8));
Expand Down Expand Up @@ -185,7 +230,7 @@ contract ChildERC20Bridge is
if (address(rootToken) == NATIVE_ETH) {
emit NativeEthDeposit(address(rootToken), childToken, sender, receiver, amount);
} else {
emit ERC20Deposit(address(rootToken), childToken, sender, receiver, amount);
emit ChildChainERC20Deposit(address(rootToken), childToken, sender, receiver, amount);
}
} else {
Address.sendValue(payable(receiver), amount);
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 AxelarMessage(string indexed rootChain, string indexed rootBridgeAdaptor, bytes indexed payload);
}
16 changes: 15 additions & 1 deletion src/interfaces/child/IChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ interface IChildERC20BridgeEvents {
/// @notice Emitted when a map token message is received from the root chain and executed successfully.
event L2TokenMapped(address rootToken, address childToken);

event ERC20Deposit(
event ChildChainERC20Withdraw(
address indexed rootToken,
address indexed childToken,
address depositor,
address indexed receiver,
uint256 amount
);

event ChildChainERC20Deposit(
address indexed rootToken,
address indexed childToken,
address depositor,
Expand Down Expand Up @@ -70,4 +78,10 @@ interface IChildERC20BridgeErrors {
error InvalidSourceChain();
/// @notice Error when the source chain's message sender is not a recognised address.
error InvalidSourceAddress();
/// @notice Error when a given child token's root token is the zero address.
error ZeroAddressRootToken();
/// @notice Error when a given child token's bridge address is not set.
error BridgeNotSet();
/// @notice Error when a call to the given child token's `burn` function fails.
error BurnFailed();
}
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: 1 addition & 1 deletion src/interfaces/root/IRootAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ interface IRootAxelarBridgeAdaptorErrors {

interface IRootAxelarBridgeAdaptorEvents {
/// @notice Emitted when an Axelar message is sent to the child chain.
event MapTokenAxelarMessage(string indexed childChain, string indexed childBridgeAdaptor, bytes indexed payload);
event AxelarMessage(string indexed childChain, string indexed childBridgeAdaptor, bytes indexed payload);
}
2 changes: 1 addition & 1 deletion src/interfaces/root/IRootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ interface IRootERC20BridgeEvents {
/// @notice Emitted when a map token message is sent to the child chain.
event L1TokenMapped(address indexed rootToken, address indexed childToken);
/// @notice Emitted when an ERC20 deposit message is sent to the child chain.
event ERC20Deposit(
event ChildChainERC20Deposit(
address indexed rootToken,
address indexed childToken,
address depositor,
Expand Down
5 changes: 2 additions & 3 deletions src/root/RootAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ contract RootAxelarBridgeAdaptor is
using SafeERC20 for IERC20Metadata;

address public rootBridge;
/// @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 childBridgeAdaptor;
string public childChain;
IAxelarGateway public axelarGateway;
IAxelarGasService public gasService;
Expand Down Expand Up @@ -81,6 +80,6 @@ contract RootAxelarBridgeAdaptor is
);

axelarGateway.callContract(_childChain, _childBridgeAdaptor, payload);
emit MapTokenAxelarMessage(_childChain, _childBridgeAdaptor, payload);
emit AxelarMessage(_childChain, _childBridgeAdaptor, payload);
}
}
2 changes: 1 addition & 1 deletion src/root/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ contract RootERC20Bridge is
} else if (address(rootToken) == rootIMXToken) {
emit IMXDeposit(address(rootToken), msg.sender, receiver, amount);
} else {
emit ERC20Deposit(address(rootToken), childToken, msg.sender, receiver, amount);
emit ChildChainERC20Deposit(address(rootToken), childToken, msg.sender, receiver, amount);
}
}
}
18 changes: 18 additions & 0 deletions src/test/child/ChildERC20FailOnBurn.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache 2.0
// Adapted from OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.21;

import "../../child/ChildERC20.sol";

/**
* @title ChildERC20FailOnBurn
* @author Immutable (@Benjimmutable)
* @notice ChildERC20 contract, except burn always returns false. Used for testing.
* @dev USED FOR TESTING
*/
// solhint-disable reason-string
contract ChildERC20FailOnBurn is ChildERC20 {
function burn(address account, uint256 amount) public virtual override returns (bool) {
return false;
}
}
12 changes: 12 additions & 0 deletions src/test/child/MockChildAxelarGasService.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.21;

contract MockChildAxelarGasService {
function payNativeGasForContractCall(
address sender,
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload,
address refundAddress
) external payable {}
}
2 changes: 2 additions & 0 deletions src/test/child/MockChildAxelarGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ contract MockChildAxelarGateway {
function validateContractCall(bytes32, string calldata, string calldata, bytes32) external pure returns (bool) {
return true;
}

function callContract(string memory childChain, string memory childBridgeAdaptor, bytes memory payload) external {}
}
14 changes: 10 additions & 4 deletions test/integration/child/ChildAxelarBridge.t.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.21;

import {Test, console2} from "forge-std/Test.sol";
Expand All @@ -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,9 @@ 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 Expand Up @@ -130,7 +135,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil
/*
* DEPOSIT
*/
function test_deposit_EmitsERC20Deposit() public {
function test_deposit_EmitsChildChainERC20Deposit() public {
address rootTokenAddress = address(456);
address sender = address(0xff);
address receiver = address(0xee);
Expand All @@ -139,7 +144,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil
bytes32 commandId = bytes32("testCommandId");

vm.expectEmit(address(childERC20Bridge));
emit ERC20Deposit(rootTokenAddress, childToken, sender, receiver, amount);
emit ChildChainERC20Deposit(rootTokenAddress, childToken, sender, receiver, amount);

childAxelarBridgeAdaptor.execute(
commandId,
Expand Down Expand Up @@ -294,6 +299,7 @@ contract ChildERC20BridgeIntegrationTest is Test, IChildERC20BridgeEvents, IChil
address rootAddress = address(0x123);
{
// Slot is 2 because of the Ownable, Initializable contracts coming first.
// Found by running `forge inspect src/child/ChildERC20Bridge.sol:ChildERC20Bridge storageLayout | grep -B3 -A5 -i "rootTokenToChildToken"`
uint256 rootTokenToChildTokenMappingSlot = 2;
address childAddress = address(444444);
bytes32 slot = getMappingStorageSlotFor(rootAddress, rootTokenToChildTokenMappingSlot);
Expand Down
Loading

0 comments on commit 92546c9

Please sign in to comment.