Skip to content

Commit

Permalink
Merge branch 'sprint-1-poc' of github.com:immutable/zkevm-bridge-cont…
Browse files Browse the repository at this point in the history
…racts into SMR-1773-axelar-local
  • Loading branch information
Benjimmutable committed Oct 23, 2023
2 parents 02ffdaf + e5b5428 commit 04eb50f
Show file tree
Hide file tree
Showing 15 changed files with 786 additions and 107 deletions.
3 changes: 2 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ ROOT_GAS_SERVICE_ADDRESS=
CHILD_GAS_SERVICE_ADDRESS=
ROOT_CHAIN_NAME=
CHILD_CHAIN_NAME=
ROOT_IMX_ADDRESS=
ROOT_IMX_ADDRESS=
CHILD_ETH_ADDRESS=
13 changes: 13 additions & 0 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Slither Analysis

on: [push]

jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: crytic/[email protected]
with:
fail-on: high
slither-args: --filter-paths "./lib|./test"
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: test
name: Build and Test

on: workflow_dispatch
on: [push]

env:
FOUNDRY_PROFILE: ci
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ out/
# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/*/2501/
/broadcast/*/31338/
/broadcast/*/2500/
/broadcast/**/dry-run/

# Docs
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ CHILD_GAS_SERVICE_ADDRESS=
ROOT_CHAIN_NAME="ROOT"
CHILD_CHAIN_NAME="CHILD"
ROOT_IMX_ADDRESS=
CHILD_ETH_ADDRESS=
```
where `{ROOT,CHILD}_{GATEWAY,GAS_SERVICE}_ADDRESS` refers to the gateway and gas service addresses used by Axelar.

Expand Down
4 changes: 4 additions & 0 deletions script/DeployChildContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {Script, console2} from "forge-std/Script.sol";
import {ChildERC20Bridge} from "../src/child/ChildERC20Bridge.sol";
import {ChildAxelarBridgeAdaptor} from "../src/child/ChildAxelarBridgeAdaptor.sol";
import {ChildERC20} from "../src/child/ChildERC20.sol";
import {WIMX} from "../src/child/WIMX.sol";

// TODO update private key usage to be more secure: https://book.getfoundry.sh/reference/forge/forge-script#wallet-options---raw

Expand All @@ -30,11 +31,14 @@ contract DeployChildContracts is Script {
address(childBridge) // child bridge
);

WIMX wrappedIMX = new WIMX();

vm.stopBroadcast();

console2.log("====ADDRESSES====");
console2.log("Child ERC20 Bridge: %s", address(childBridge));
console2.log("Child Axelar Bridge Adaptor: %s", address(childBridgeAdaptor));
console2.log("childTokenTemplate: %s", address(childTokenTemplate));
console2.log("Wrapped IMX: %s", address(wrappedIMX));
}
}
8 changes: 7 additions & 1 deletion script/InitializeRootContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@ 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
*/
vm.createSelectFork(rootRpcUrl);
vm.startBroadcast(rootPrivateKey);

rootERC20Bridge.initialize(
address(rootBridgeAdaptor), childERC20Bridge, childBridgeAdaptorChecksum, rootChainChildTokenTemplate, rootIMXToken
address(rootBridgeAdaptor),
childERC20Bridge,
childBridgeAdaptorChecksum,
rootChainChildTokenTemplate,
rootIMXToken,
childETHToken
);

rootBridgeAdaptor.setChildBridgeAdaptor();
Expand Down
98 changes: 98 additions & 0 deletions src/child/WIMX.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.21;

import {IWIMX} from "../interfaces/child/IWIMX.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";

/**
* @notice WIMX is a wrapped IMX contract that allows users to wrap their native IMX.
* @dev This contract is adapted from the official Wrapped ETH contract.
*/
contract WIMX is IWIMX {
string public name = "Wrapped IMX";
string public symbol = "WIMX";
uint8 public decimals = 18;

mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

/**
* @notice Fallback function on recieving native IMX.
*/
receive() external payable {
deposit();
}

/**
* @notice Deposit native IMX in the function call and mint the equal amount of wrapped IMX to msg.sender.
*/
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}

/**
* @notice Withdraw given amount of native IMX to msg.sender and burn the equal amount of wrapped IMX.
* @param wad The amount to withdraw.
*/
function withdraw(uint256 wad) public {
require(balanceOf[msg.sender] >= wad, "Wrapped IMX: Insufficient balance");
balanceOf[msg.sender] -= wad;

Address.sendValue(payable(msg.sender), wad);
emit Withdrawal(msg.sender, wad);
}

/**
* @notice Obtain the current total supply of wrapped IMX.
* @return uint The amount of supplied wrapped IMX.
*/
function totalSupply() public view returns (uint256) {
return address(this).balance;
}

/**
* @notice Approve given spender the ability to spend a given amount of msg.sender's tokens.
* @param guy Approved spender.
* @param wad Amount of allowance.
* @return bool Returns true if function call is successful.
*/
function approve(address guy, uint256 wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
emit Approval(msg.sender, guy, wad);
return true;
}

/**
* @notice Transfer given amount of tokens from msg.sender to given destination.
* @param dst Destination of this transfer.
* @param wad Amount of this transfer.
* @return bool Returns true if function call is successful.
*/
function transfer(address dst, uint256 wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}

/**
* @notice Transfer given amount of tokens from given source to given destination.
* @param src Source of this transfer.
* @param dst Destination of this transfer.
* @param wad Amount of this transfer.
* @return bool Returns true if function call is successful.
*/
function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
require(balanceOf[src] >= wad, "Wrapped IMX: Insufficient balance");

if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
require(allowance[src][msg.sender] >= wad, "Wrapped IMX: Insufficient allowance");
allowance[src][msg.sender] -= wad;
}

balanceOf[src] -= wad;
balanceOf[dst] += wad;

emit Transfer(src, dst, wad);

return true;
}
}
30 changes: 30 additions & 0 deletions src/interfaces/child/IWIMX.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.21;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @dev Interface of Wrapped IMX.
*/
interface IWIMX is IERC20 {
/**
* @dev Emitted when `value` native IMX are deposited from `account`.
*/
event Deposit(address indexed account, uint256 value);

/**
* @dev Emitted when `value` wIMX tokens are withdrawn to `account`.
*/
event Withdrawal(address indexed account, uint256 value);

/**
* @notice Deposit native IMX in the function call and mint the equal amount of wrapped IMX to msg.sender.
*/
function deposit() external payable;

/**
* @notice Withdraw given amount of native IMX to msg.sender and burn the equal amount of wrapped IMX.
* @param value The amount to withdraw.
*/
function withdraw(uint256 value) external;
}
4 changes: 4 additions & 0 deletions src/interfaces/root/IRootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ interface IRootERC20BridgeEvents {
}

interface IRootERC20BridgeErrors {
/// @notice Error when the amount requested is less than the value sent.
error InsufficientValue();
/// @notice Error when there is no gas payment received.
error ZeroAmount();
/// @notice Error when a zero address is given when not valid.
error ZeroAddress();
/// @notice Error when a token is already mapped.
Expand Down
70 changes: 54 additions & 16 deletions src/root/RootERC20Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,37 @@ contract RootERC20Bridge is
/// @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.
address public childETHToken;

/**
* @notice Initilization function for RootERC20Bridge.
* @param newRootBridgeAdaptor Address of StateSender to send bridge messages to, and receive messages from.
* @param newChildERC20Bridge Address of child ERC20 bridge to communicate with.
* @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 ECR20 IMX on the root chain.
* @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
) public initializer {
if (
newRootBridgeAdaptor == address(0) || newChildERC20Bridge == address(0)
|| newChildTokenTemplate == address(0)
|| newRootIMXToken == address(0)
) {
address newChildTokenTemplate,
address newRootIMXToken,
address newChildETHToken)
public
initializer
{
if (newRootBridgeAdaptor == address(0)
|| newChildERC20Bridge == address(0)
|| newChildTokenTemplate == address(0)
|| newRootIMXToken == address(0)
|| newChildETHToken == address(0))
{
revert ZeroAddress();
}
if (bytes(newChildBridgeAdaptor).length == 0) {
Expand All @@ -74,6 +81,7 @@ contract RootERC20Bridge is
childERC20Bridge = newChildERC20Bridge;
childTokenTemplate = newChildTokenTemplate;
rootIMXToken = newRootIMXToken;
childETHToken = newChildETHToken;
rootBridgeAdaptor = IRootERC20BridgeAdaptor(newRootBridgeAdaptor);
childBridgeAdaptor = newChildBridgeAdaptor;
}
Expand All @@ -89,6 +97,29 @@ contract RootERC20Bridge is
return _mapToken(rootToken);
}

function depositETH(uint256 amount) external payable { //override removed?
_depositETH(msg.sender, amount);
}

function depositToETH(address receiver, uint256 amount) external payable { //override removed?
_depositETH(receiver, amount);
}

function _depositETH(address receiver, uint256 amount) private {
if (msg.value < amount) {
revert InsufficientValue();
}

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

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

// invariant check to ensure that the root native balance has increased by the amount deposited
if (address(this).balance != expectedBalance) {
revert BalanceInvariantCheckFailed(address(this).balance, expectedBalance);
}
}

/**
* @inheritdoc IRootERC20Bridge
*/
Expand Down Expand Up @@ -145,33 +176,40 @@ contract RootERC20Bridge is
revert ZeroAddress();
}

if (amount == 0) {
revert ZeroAmount();
}

address childToken;
uint256 feeAmount;

// The native token does not need to be mapped since it should have been mapped on initialization
// The native token also cannot be transferred since it was received in the payable function call
// 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.
// TODO NATIVE TOKEN BRIDGING NOT YET SUPPORTED
if (address(rootToken) != NATIVE_TOKEN) {
if (address(rootToken) != NATIVE_TOKEN) {
if (address(rootToken) != rootIMXToken) {
childToken = rootTokenToChildToken[address(rootToken)];
if (childToken == address(0)) {
revert NotMapped();
}
}

// ERC20 must be transferred explicitly
rootToken.safeTransferFrom(msg.sender, address(this), amount);
feeAmount = msg.value;
} else {
feeAmount = msg.value - amount;
}

// Deposit sig, root token address, depositor, receiver, amount
bytes memory payload = abi.encode(DEPOSIT_SIG, rootToken, msg.sender, receiver, amount);
// TODO investigate using delegatecall to keep the axelar message sender as the bridge contract, since adaptor can change.
rootBridgeAdaptor.sendMessage{value: msg.value}(payload, msg.sender);

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

if (address(rootToken) == NATIVE_TOKEN) {
// not used yet
emit NativeDeposit(address(rootToken), childToken, msg.sender, receiver, amount);
emit NativeDeposit(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 04eb50f

Please sign in to comment.