Skip to content

Commit

Permalink
first draft
Browse files Browse the repository at this point in the history
  • Loading branch information
proletesseract committed Oct 20, 2023
1 parent 877195d commit c4c59a4
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 37 deletions.
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
32 changes: 23 additions & 9 deletions src/child/ChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "../interfaces/child/IChildERC20Bridge.sol";
import {IChildERC20BridgeAdaptor} from "../interfaces/child/IChildERC20BridgeAdaptor.sol";
import {IChildERC20} from "../interfaces/child/IChildERC20.sol";
import {console2} from "forge-std/Test.sol";

/**
* @notice RootERC20Bridge is a bridge that allows ERC20 tokens to be transferred from the root chain to the child chain.
Expand All @@ -38,7 +39,7 @@ contract ChildERC20Bridge is

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);

IChildERC20BridgeAdaptor public bridgeAdaptor;
/// @dev The address that will be sending messages to, and receiving messages from, the child chain.
Expand All @@ -60,7 +61,7 @@ contract ChildERC20Bridge is
* @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 newRootIMXToken Address of ECR20 IMX on the root chain.
* @param newChildETHToken Address of ERC20 ETH on the child chain.
* @param newRootETHToken Address used to denote ETH on the root chain.
* @dev Can only be called once.
*/
function initialize(
Expand All @@ -69,13 +70,13 @@ contract ChildERC20Bridge is
address newChildTokenTemplate,
string memory newRootChain,
address newRootIMXToken,
address newChildETHToken)
address newRootETHToken)
public initializer
{
if (newBridgeAdaptor == address(0)
|| newChildTokenTemplate == address(0)
|| newRootIMXToken == address(0)
|| newChildETHToken == address(0)) {
|| newRootETHToken == address(0)) {
revert ZeroAddress();
}

Expand All @@ -92,8 +93,11 @@ contract ChildERC20Bridge is
bridgeAdaptor = IChildERC20BridgeAdaptor(newBridgeAdaptor);
rootChain = newRootChain;
rootIMXToken = newRootIMXToken;
childETHToken = newChildETHToken;

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

/**
Expand Down Expand Up @@ -163,18 +167,28 @@ contract ChildERC20Bridge is
address childToken;

if (address(rootToken) != rootIMXToken) {
childToken = rootTokenToChildToken[address(rootToken)];
if (childToken == address(0)) {
revert NotMapped();
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
2 changes: 1 addition & 1 deletion src/interfaces/child/IChildERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ interface IChildERC20BridgeEvents {
address indexed receiver,
uint256 amount
);
event NativeDeposit(
event NativeEthDeposit(
address indexed rootToken,
address indexed childToken,
address depositor,
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/root/IRootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ interface IRootERC20BridgeEvents {
address indexed receiver,
uint256 amount
);
event NativeDeposit(
event NativeEthDeposit(
address indexed rootToken,
address indexed childToken,
address depositor,
Expand Down
10 changes: 5 additions & 5 deletions src/root/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ contract RootERC20Bridge is

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.
Expand Down Expand Up @@ -111,7 +111,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 @@ -187,7 +187,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 @@ -207,8 +207,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
88 changes: 72 additions & 16 deletions test/unit/child/ChildERC20Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.21;

import {Test, console2} from "forge-std/Test.sol";
import {ERC20PresetMinterPauser} from "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {
Expand All @@ -10,6 +11,7 @@ import {
IERC20Metadata,
IChildERC20BridgeErrors
} from "../../../src/child/ChildERC20Bridge.sol";
import {IChildERC20} from "../../../src/interfaces/child/IChildERC20.sol";
import {ChildERC20} from "../../../src/child/ChildERC20.sol";
import {MockAdaptor} from "../../../src/test/root/MockAdaptor.sol";
import {Utils} from "../../utils.t.sol";
Expand All @@ -18,12 +20,11 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B
address constant ROOT_BRIDGE = address(3);
string public ROOT_BRIDGE_ADAPTOR = Strings.toHexString(address(4));
string constant ROOT_CHAIN_NAME = "test";

address constant IMX_TOKEN = address(0xccc);
address constant CHILD_ETH_TOKEN = address(0xddd);
address constant NATIVE_TOKEN = address(0xeee);
address constant ROOT_IMX_TOKEN = address(0xccc);
address constant NATIVE_ETH = address(0xeee);
ChildERC20 public childTokenTemplate;
ChildERC20 public rootToken;
address public childETHToken;
ChildERC20Bridge public childBridge;

function setUp() public {
Expand All @@ -32,22 +33,24 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B

childTokenTemplate = new ChildERC20();
childTokenTemplate.initialize(address(123), "Test", "TST", 18);

childBridge = new ChildERC20Bridge();

childBridge.initialize(address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, IMX_TOKEN, CHILD_ETH_TOKEN);
childBridge.initialize(address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN, NATIVE_ETH);
}

function test_Initialize() public {
assertEq(address(childBridge.bridgeAdaptor()), address(address(this)), "bridgeAdaptor not set");
assertEq(childBridge.rootERC20BridgeAdaptor(), ROOT_BRIDGE_ADAPTOR, "rootERC20BridgeAdaptor not set");
assertEq(childBridge.childTokenTemplate(), address(childTokenTemplate), "childTokenTemplate not set");
assertEq(childBridge.rootChain(), ROOT_CHAIN_NAME, "rootChain not set");
assertEq(childBridge.rootIMXToken(), ROOT_IMX_TOKEN, "rootIMXToken not set");
assertNotEq(childBridge.childETHToken(), address(0), "childETHToken not set");
assertNotEq(address(childBridge.childETHToken()).code.length, 0, "childETHToken contract empty");
}

function test_RevertIfInitializeTwice() public {
vm.expectRevert("Initializable: contract is already initialized");
childBridge.initialize(address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, IMX_TOKEN, CHILD_ETH_TOKEN);
childBridge.initialize(address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN, NATIVE_ETH);
}

function test_RevertIf_InitializeWithAZeroAddressAdapter() public {
Expand All @@ -68,7 +71,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B
bridge.initialize(address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(0), address(1));
}

function test_RevertIf_InitializeWithAZeroAddressEthChildToken() public {
function test_RevertIf_InitializeWithAZeroAddressEthRootToken() public {
ChildERC20Bridge bridge = new ChildERC20Bridge();
vm.expectRevert(ZeroAddress.selector);
bridge.initialize(address(1), ROOT_BRIDGE_ADAPTOR, address(1), ROOT_CHAIN_NAME, address(1), address(0));
Expand All @@ -83,13 +86,13 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B
function test_RevertIf_InitializeWithAnEmptyBridgeAdaptorString() public {
ChildERC20Bridge bridge = new ChildERC20Bridge();
vm.expectRevert(InvalidRootERC20BridgeAdaptor.selector);
bridge.initialize(address(this), "", address(childTokenTemplate), ROOT_CHAIN_NAME, IMX_TOKEN, CHILD_ETH_TOKEN);
bridge.initialize(address(this), "", address(childTokenTemplate), ROOT_CHAIN_NAME, ROOT_IMX_TOKEN, NATIVE_ETH);
}

function test_RevertIf_InitializeWithAnEmptyChainNameString() public {
ChildERC20Bridge bridge = new ChildERC20Bridge();
vm.expectRevert(InvalidRootChain.selector);
bridge.initialize(address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), "", IMX_TOKEN, CHILD_ETH_TOKEN);
bridge.initialize(address(this), ROOT_BRIDGE_ADAPTOR, address(childTokenTemplate), "", ROOT_IMX_TOKEN, NATIVE_ETH);
}

function test_onMessageReceive_EmitsTokenMappedEvent() public {
Expand Down Expand Up @@ -191,7 +194,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B

function test_RevertIf_mapTokenCalledWithIMXAddress() public {
bytes memory data = abi.encode(
childBridge.MAP_TOKEN_SIG(), IMX_TOKEN, "ImmutableX", "IMX", 18
childBridge.MAP_TOKEN_SIG(), ROOT_IMX_TOKEN, "ImmutableX", "IMX", 18
);
vm.expectRevert(CantMapIMX.selector);
childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, data);
Expand Down Expand Up @@ -225,6 +228,59 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B
childBridge.updateBridgeAdaptor(address(0));
}

//Deposit ETH

function test_onMessageReceive_DepositETH_EmitsETHDepositEvent() public {
address sender = address(100);
address receiver = address(200);
uint256 amount = 1000;

bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), address(NATIVE_ETH), sender, receiver, amount);

address predictedChildETHToken = Clones.predictDeterministicAddress(
address(childTokenTemplate), keccak256(abi.encodePacked(NATIVE_ETH)), address(childBridge)
);

vm.expectEmit(address(childBridge));
emit NativeEthDeposit(address(NATIVE_ETH), predictedChildETHToken, sender, receiver, amount);
childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData);
}

function test_onMessageReceive_DepositETH_TransfersTokensToReceiver() public {
address sender = address(100);
address receiver = address(200);
uint256 amount = 1000;

bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), address(NATIVE_ETH), sender, receiver, amount);

address predictedChildETHToken = Clones.predictDeterministicAddress(
address(childTokenTemplate), keccak256(abi.encodePacked(NATIVE_ETH)), address(childBridge)
);

uint256 receiverPreBal = ChildERC20(predictedChildETHToken).balanceOf(receiver);

childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData);

assertEq(ChildERC20(predictedChildETHToken).balanceOf(receiver), receiverPreBal + amount, "receiver balance not increased");
}

function test_onMessageReceive_DepositETH_IncreasesTotalSupply() public {
address sender = address(100);
address receiver = address(200);
uint256 amount = 1000;

bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), address(NATIVE_ETH), sender, receiver, amount);

address predictedChildETHToken = Clones.predictDeterministicAddress(
address(childTokenTemplate), keccak256(abi.encodePacked(NATIVE_ETH)), address(childBridge)
);
uint256 totalSupplyPre = ChildERC20(predictedChildETHToken).totalSupply();

childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData);

assertEq(ChildERC20(predictedChildETHToken).totalSupply(), totalSupplyPre + amount, "totalSupply not increased");
}

//Deposit

function test_onMessageReceive_DepositIMX_EmitsIMXDepositEvent() public {
Expand All @@ -236,10 +292,10 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B
address receiver = address(200);
uint256 amount = 1 ether;

bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), IMX_TOKEN, sender, receiver, amount);
bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), ROOT_IMX_TOKEN, sender, receiver, amount);

vm.expectEmit(address(childBridge));
emit IMXDeposit(IMX_TOKEN, sender, receiver, amount);
emit IMXDeposit(ROOT_IMX_TOKEN, sender, receiver, amount);
childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData);
}

Expand All @@ -251,7 +307,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B
address receiver = address(200);
uint256 amount = 1 ether;

bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), IMX_TOKEN, sender, receiver, amount);
bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), ROOT_IMX_TOKEN, sender, receiver, amount);

childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData);

Expand All @@ -267,7 +323,7 @@ contract ChildERC20BridgeUnitTest is Test, IChildERC20BridgeEvents, IChildERC20B
address receiver = address(200);
uint256 amount = 10 ether;

bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), IMX_TOKEN, sender, receiver, amount);
bytes memory depositData = abi.encode(childBridge.DEPOSIT_SIG(), ROOT_IMX_TOKEN, sender, receiver, amount);

vm.expectRevert("Address: insufficient balance");
childBridge.onMessageReceive(ROOT_CHAIN_NAME, ROOT_BRIDGE_ADAPTOR, depositData);
Expand Down
8 changes: 4 additions & 4 deletions test/unit/root/RootERC20Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,12 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid
rootBridge.depositETH{value: amount+depositFee}(amount);
}

function test_depositETHEmitsNativeDepositEvent() public {
function test_depositETHEmitsNativeEthDepositEvent() public {
uint256 amount = 1000;
setupDeposit(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, false);

vm.expectEmit();
emit NativeDeposit(NATIVE_TOKEN, CHILD_ETH_TOKEN, address(this), address(this), amount);
emit NativeEthDeposit(NATIVE_TOKEN, CHILD_ETH_TOKEN, address(this), address(this), amount);
rootBridge.depositETH{value: amount+depositFee}(amount);
}

Expand Down Expand Up @@ -246,13 +246,13 @@ contract RootERC20BridgeUnitTest is Test, IRootERC20BridgeEvents, IRootERC20Brid
rootBridge.depositToETH{value: amount+depositFee}(receiver, amount);
}

function test_depositToETHEmitsNativeDepositEvent() public {
function test_depositToETHEmitsNativeEthDepositEvent() public {
uint256 amount = 1000;
address receiver = address(12345);
setupDepositTo(ERC20PresetMinterPauser(NATIVE_TOKEN), rootBridge, mapTokenFee, depositFee, amount, receiver, false);

vm.expectEmit();
emit NativeDeposit(NATIVE_TOKEN, CHILD_ETH_TOKEN, address(this), receiver, amount);
emit NativeEthDeposit(NATIVE_TOKEN, CHILD_ETH_TOKEN, address(this), receiver, amount);
rootBridge.depositToETH{value: amount+depositFee}(receiver, amount);
}

Expand Down

0 comments on commit c4c59a4

Please sign in to comment.