Skip to content

Commit

Permalink
Merge branch 'SMR-1782-eth-bridging-child' of github.com:immutable/zk…
Browse files Browse the repository at this point in the history
…evm-bridge-contracts into SMR-1773-axelar-local
  • Loading branch information
Benjimmutable committed Oct 25, 2023
2 parents 54bfe8a + 59e4a8c commit 2eaf469
Show file tree
Hide file tree
Showing 13 changed files with 390 additions and 90 deletions.
3 changes: 1 addition & 2 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ ROOT_GAS_SERVICE_ADDRESS=
CHILD_GAS_SERVICE_ADDRESS=
ROOT_CHAIN_NAME=
CHILD_CHAIN_NAME=
ROOT_IMX_ADDRESS=
CHILD_ETH_ADDRESS=
ROOT_IMX_ADDRESS=
28 changes: 28 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Lint Check

on: [push]

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Forge Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Run Forge fmt --check
run: |
forge fmt --check
id: fmt
2 changes: 1 addition & 1 deletion script/DeployChildContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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.
//address childGasService = vm.envAddress("CHILD_GAS_SERVICE_ADDRESS"); // Not yet used.
string memory childRpcUrl = vm.envString("CHILD_RPC_URL");

vm.createSelectFork(childRpcUrl);
Expand Down
4 changes: 1 addition & 3 deletions script/InitializeRootContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ contract InitializeRootContracts is Script {
string[] memory checksumInputs = Utils.getChecksumInputs(childBridgeAdaptor);
bytes memory checksumOutput = vm.ffi(checksumInputs);
string memory childBridgeAdaptorChecksum = string(Utils.removeZeroByteValues(checksumOutput));
address childETHToken = vm.envAddress("CHILD_ETH_ADDRESS");
/**
* INITIALIZE ROOT CHAIN CONTRACTS
*/
Expand All @@ -39,8 +38,7 @@ contract InitializeRootContracts is Script {
childERC20Bridge,
childBridgeAdaptorChecksum,
rootChainChildTokenTemplate,
rootIMXToken,
childETHToken
rootIMXToken
);

rootBridgeAdaptor.setChildBridgeAdaptor();
Expand Down
56 changes: 41 additions & 15 deletions src/child/ChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,36 +33,43 @@ contract ChildERC20Bridge is
{
using SafeERC20 for IERC20Metadata;

/// @dev leave this as the first param for the integration tests
mapping(address => address) public rootTokenToChildToken;

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

IChildERC20BridgeAdaptor public bridgeAdaptor;

/// @dev The address that will be sending messages to, and receiving messages from, the child chain.
string public rootERC20BridgeAdaptor;
/// @dev The address of the token template that will be cloned to create tokens.
address public childTokenTemplate;
/// @dev The name of the chain that this bridge is connected to.
string public rootChain;
mapping(address => address) public rootTokenToChildToken;

bytes32 public constant MAP_TOKEN_SIG = keccak256("MAP_TOKEN");
bytes32 public constant DEPOSIT_SIG = keccak256("DEPOSIT");
address public imxToken;
/// @dev The address of the IMX ERC20 token on L1.
address public rootIMXToken;
/// @dev The address of the ETH ERC20 token on L2.
address public childETHToken;

/**
* @notice Initilization function for RootERC20Bridge.
* @param newBridgeAdaptor Address of StateSender to send deposit information to.
* @param newRootERC20BridgeAdaptor Stringified address of root ERC20 bridge adaptor to communicate with.
* @param newChildTokenTemplate Address of child token template to clone.
* @param newRootChain A stringified representation of the chain that this bridge is connected to. Used for validation.
* @param newIMXToken Address of ECR20 IMX on the root chain.
* @param newRootIMXToken Address of ECR20 IMX on the root chain.
* @dev Can only be called once.
*/
function initialize(
address newBridgeAdaptor,
string memory newRootERC20BridgeAdaptor,
address newChildTokenTemplate,
string memory newRootChain,
address newIMXToken
address newRootIMXToken
) public initializer {
if (newBridgeAdaptor == address(0) || newChildTokenTemplate == address(0) || newIMXToken == address(0)) {
if (newBridgeAdaptor == address(0) || newChildTokenTemplate == address(0) || newRootIMXToken == address(0)) {
revert ZeroAddress();
}

Expand All @@ -78,7 +85,12 @@ contract ChildERC20Bridge is
childTokenTemplate = newChildTokenTemplate;
bridgeAdaptor = IChildERC20BridgeAdaptor(newBridgeAdaptor);
rootChain = newRootChain;
imxToken = newIMXToken;
rootIMXToken = newRootIMXToken;

IChildERC20 clonedETHToken =
IChildERC20(Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(NATIVE_ETH))));
clonedETHToken.initialize(NATIVE_ETH, "Ethereum", "ETH", 18);
childETHToken = address(clonedETHToken);
}

/**
Expand Down Expand Up @@ -121,10 +133,14 @@ contract ChildERC20Bridge is
revert ZeroAddress();
}

if (address(rootToken) == imxToken) {
if (address(rootToken) == rootIMXToken) {
revert CantMapIMX();
}

if (address(rootToken) == NATIVE_ETH) {
revert CantMapETH();
}

if (rootTokenToChildToken[rootToken] != address(0)) {
revert AlreadyMapped();
}
Expand All @@ -148,19 +164,29 @@ contract ChildERC20Bridge is

address childToken;

if (address(rootToken) != imxToken) {
childToken = rootTokenToChildToken[address(rootToken)];
if (childToken == address(0)) {
revert NotMapped();
if (address(rootToken) != rootIMXToken) {
if (address(rootToken) == NATIVE_ETH) {
childToken = childETHToken;
} else {
childToken = rootTokenToChildToken[address(rootToken)];
if (childToken == address(0)) {
revert NotMapped();
}
}

if (address(childToken).code.length == 0) {
revert EmptyTokenContract();
}

if (!IChildERC20(childToken).mint(receiver, amount)) {
revert MintFailed();
}
emit ERC20Deposit(address(rootToken), childToken, sender, receiver, amount);

if (address(rootToken) == NATIVE_ETH) {
emit NativeEthDeposit(address(rootToken), childToken, sender, receiver, amount);
} else {
emit ERC20Deposit(address(rootToken), childToken, sender, receiver, amount);
}
} else {
Address.sendValue(payable(receiver), amount);
emit IMXDeposit(address(rootToken), sender, receiver, amount);
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces/child/IChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface IChildERC20BridgeEvents {
uint256 amount
);
event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount);
event NativeDeposit(
event NativeEthDeposit(
address indexed rootToken,
address indexed childToken,
address depositor,
Expand All @@ -58,6 +58,8 @@ interface IChildERC20BridgeErrors {
error NotMapped();
/// @notice Error when attempting to map IMX.
error CantMapIMX();
/// @notice Error when attempting to map ETH.
error CantMapETH();
/// @notice Error when a token is already mapped.
error AlreadyMapped();
/// @notice Error when a message is given to the bridge from an address not the designated bridge adaptor.
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces/root/IRootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ interface IRootERC20BridgeEvents {
uint256 amount
);
event IMXDeposit(address indexed rootToken, address depositor, address indexed receiver, uint256 amount);
event NativeDeposit(
event NativeEthDeposit(
address indexed rootToken,
address indexed childToken,
address depositor,
Expand All @@ -68,6 +68,8 @@ interface IRootERC20BridgeErrors {
error NotMapped();
/// @notice Error when attempting to map IMX.
error CantMapIMX();
/// @notice Error when attempting to map ETH.
error CantMapETH();
/// @notice Error when token balance invariant check fails.
error BalanceInvariantCheckFailed(uint256 actualBalance, uint256 expectedBalance);
/// @notice Error when the given child chain bridge adaptor is invalid.
Expand Down
32 changes: 20 additions & 12 deletions src/root/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {IAxelarGateway} from "@axelar-cgp-solidity/contracts/interfaces/IAxelarG
import {IRootERC20Bridge, IERC20Metadata} from "../interfaces/root/IRootERC20Bridge.sol";
import {IRootERC20BridgeEvents, IRootERC20BridgeErrors} from "../interfaces/root/IRootERC20Bridge.sol";
import {IRootERC20BridgeAdaptor} from "../interfaces/root/IRootERC20BridgeAdaptor.sol";
import {IChildERC20} from "../interfaces/child/IChildERC20.sol";

/**
* @notice RootERC20Bridge is a bridge that allows ERC20 tokens to be transferred from the root chain to the child chain.
Expand All @@ -29,19 +30,20 @@ contract RootERC20Bridge is
{
using SafeERC20 for IERC20Metadata;

/// @dev leave this as the first param for the integration tests
mapping(address => address) public rootTokenToChildToken;

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

IRootERC20BridgeAdaptor public rootBridgeAdaptor;
/// @dev Used to verify source address in messages sent from child chain.
/// @dev Stringified version of address.
string public childBridgeAdaptor;
/// @dev The address that will be minting tokens on the child chain.
address public childERC20Bridge;
/// @dev The address of the token template that will be cloned to create tokens on the child chain.
address public childTokenTemplate;
mapping(address => address) public rootTokenToChildToken;
/// @dev The address of the IMX ERC20 token on L1.
address public rootIMXToken;
/// @dev The address of the ETH ERC20 token on L2.
Expand All @@ -54,20 +56,19 @@ contract RootERC20Bridge is
* @param newChildBridgeAdaptor Address of child bridge adaptor to communicate with (As a checksummed string).
* @param newChildTokenTemplate Address of child token template to clone.
* @param newRootIMXToken Address of ERC20 IMX on the root chain.
* @param newChildETHToken Address of ERC20 ETH on the child chain.
* @dev Can only be called once.
*/
function initialize(
address newRootBridgeAdaptor,
address newChildERC20Bridge,
string memory newChildBridgeAdaptor,
address newChildTokenTemplate,
address newRootIMXToken,
address newChildETHToken
address newRootIMXToken
) public initializer {
if (
newRootBridgeAdaptor == address(0) || newChildERC20Bridge == address(0)
|| newChildTokenTemplate == address(0) || newRootIMXToken == address(0) || newChildETHToken == address(0)
|| newChildTokenTemplate == address(0)
|| newRootIMXToken == address(0)
) {
revert ZeroAddress();
}
Expand All @@ -77,7 +78,9 @@ contract RootERC20Bridge is
childERC20Bridge = newChildERC20Bridge;
childTokenTemplate = newChildTokenTemplate;
rootIMXToken = newRootIMXToken;
childETHToken = newChildETHToken;
IChildERC20 clonedETHToken =
IChildERC20(Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(NATIVE_ETH))));
childETHToken = address(clonedETHToken);
rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor);
childBridgeAdaptor = newChildBridgeAdaptor;
}
Expand Down Expand Up @@ -110,7 +113,7 @@ contract RootERC20Bridge is

uint256 expectedBalance = address(this).balance - (msg.value - amount);

_deposit(IERC20Metadata(NATIVE_TOKEN), receiver, amount);
_deposit(IERC20Metadata(NATIVE_ETH), receiver, amount);

// invariant check to ensure that the root native balance has increased by the amount deposited
if (address(this).balance != expectedBalance) {
Expand Down Expand Up @@ -149,6 +152,11 @@ contract RootERC20Bridge is
if (address(rootToken) == rootIMXToken) {
revert CantMapIMX();
}

if (address(rootToken) == NATIVE_ETH) {
revert CantMapETH();
}

if (rootTokenToChildToken[address(rootToken)] != address(0)) {
revert AlreadyMapped();
}
Expand Down Expand Up @@ -186,7 +194,7 @@ contract RootERC20Bridge is
// TODO We can call _mapToken here, but ordering in the GMP is not guaranteed.
// Therefore, we need to decide how to handle this and it may be a UI decision to wait until map token message is executed on child chain.
// Discuss this, and add this decision to the design doc.
if (address(rootToken) != NATIVE_TOKEN) {
if (address(rootToken) != NATIVE_ETH) {
if (address(rootToken) != rootIMXToken) {
childToken = rootTokenToChildToken[address(rootToken)];
if (childToken == address(0)) {
Expand All @@ -206,8 +214,8 @@ contract RootERC20Bridge is

rootBridgeAdaptor.sendMessage{value: feeAmount}(payload, msg.sender);

if (address(rootToken) == NATIVE_TOKEN) {
emit NativeDeposit(address(rootToken), childETHToken, msg.sender, receiver, amount);
if (address(rootToken) == NATIVE_ETH) {
emit NativeEthDeposit(address(rootToken), childETHToken, msg.sender, receiver, amount);
} else if (address(rootToken) == rootIMXToken) {
emit IMXDeposit(address(rootToken), msg.sender, receiver, amount);
} else {
Expand Down
Loading

0 comments on commit 2eaf469

Please sign in to comment.