Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SMR-1727] Map token L1 #103

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
81cf0d0
chore: forge init
Benjimmutable Sep 27, 2023
f76af8a
forge install: forge-std
Benjimmutable Sep 27, 2023
89af25a
Revert README.md change
Benjimmutable Sep 27, 2023
bfa7198
Add out/ to gitignore
Benjimmutable Sep 27, 2023
c5c0ea7
Remove boilerplate foundry
Benjimmutable Sep 27, 2023
a93890c
forge install: openzeppelin-contracts
Benjimmutable Sep 27, 2023
4988d45
Change to foundry-out in gitignore
Benjimmutable Sep 27, 2023
c992b22
Remove openzeppelin forge dependency
Benjimmutable Sep 27, 2023
32a08d6
L1 side of map token message
Benjimmutable Sep 28, 2023
6ddc5da
Merge branch 'main' of github.com:immutable/zkevm-contracts into map-…
Benjimmutable Sep 28, 2023
0cff795
Match yarn migration
Benjimmutable Sep 28, 2023
9fc308b
Downgrade solc version
Benjimmutable Sep 28, 2023
bca225e
lint
Benjimmutable Sep 28, 2023
8dedcfb
add todo
Benjimmutable Sep 28, 2023
83fe995
Add licenses
Benjimmutable Sep 28, 2023
44a535b
Clarify natspec
Benjimmutable Sep 28, 2023
89698a6
Fix natspec
Benjimmutable Sep 28, 2023
19610b6
integrate feedback
Benjimmutable Sep 29, 2023
2cd3ff9
Add forge test to test.yml
Benjimmutable Sep 29, 2023
d6185e0
Fix workflow
Benjimmutable Sep 29, 2023
6a275c7
libs in CI profile
Benjimmutable Sep 29, 2023
6f02e28
Set foundry profile in CI
Benjimmutable Sep 29, 2023
de2cb56
Install dependencies in CI
Benjimmutable Sep 29, 2023
9a10347
Remove unused withdrawEth
Benjimmutable Sep 29, 2023
6da8ddf
Merge branch 'nojira-forge-test-ci' of github.com:immutable/zkevm-con…
Benjimmutable Sep 29, 2023
4e43878
Update licenses
Benjimmutable Sep 29, 2023
42c2050
Smr 1728 map token l2 (#108)
Benjimmutable Oct 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ on:
branches: [main]

jobs:
forge-test:
name: Run Forge Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run tests
run: forge test -vvv
hardhat-test:
name: Run Hardhat Tests
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ dist/

# Forge files
foundry-out/

# VSCode Config
.vscode/
31 changes: 31 additions & 0 deletions contracts/bridge/child/ChildAxelarBridgeAdaptor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
import {IChildERC20Bridge} from "../interfaces/child/IChildERC20Bridge.sol";
import {IChildAxelarBridgeAdaptorErrors} from "../interfaces/child/IChildAxelarBridgeAdaptor.sol";

contract ChildAxelarBridgeAdaptor is AxelarExecutable, IChildAxelarBridgeAdaptorErrors {
/// @notice Address of bridge to relay messages to.
IChildERC20Bridge public immutable CHILD_BRIDGE;

constructor(address _gateway, address _childBridge) AxelarExecutable(_gateway) {
if (_childBridge == address(0)) {
revert ZeroAddress();
}

CHILD_BRIDGE = IChildERC20Bridge(_childBridge);
}

/**
* @dev This function is called by the parent `AxelarExecutable` contract to execute the payload.
* @custom:assumes `sourceAddress_` is a 20 byte address.
*/
function _execute(
string calldata sourceChain_,
string calldata sourceAddress_,
bytes calldata payload_
) internal override {
CHILD_BRIDGE.onMessageReceive(sourceChain_, sourceAddress_, payload_);
}
}
89 changes: 89 additions & 0 deletions contracts/bridge/child/ChildERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: MIT
// Adapted from OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.17;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "../lib/EIP712MetaTransaction.sol";
import "../interfaces/child/IChildERC20.sol";

/**
* @title ChildERC20
* @author Polygon Technology (@QEDK)
* @notice Child token template for ChildERC20 predicate deployments
* @dev All child tokens are clones of this contract. Burning and minting is controlled by respective predicates only.
*/
// solhint-disable reason-string
contract ChildERC20 is EIP712MetaTransaction, ERC20Upgradeable, IChildERC20 {
address private _bridge;
address private _rootToken;
uint8 private _decimals;

modifier onlyBridge() {
require(msg.sender == _bridge, "ChildERC20: Only bridge can call");
_;
}

/**
* @inheritdoc IChildERC20
*/
function initialize(
address rootToken_,
string calldata name_,
string calldata symbol_,
uint8 decimals_
) external initializer {
require(
rootToken_ != address(0) && bytes(name_).length != 0 && bytes(symbol_).length != 0,
"ChildERC20: BAD_INITIALIZATION"
);
_rootToken = rootToken_;
_decimals = decimals_;
_bridge = msg.sender;
__ERC20_init(name_, symbol_);
_initializeEIP712(name_, "1");
}

/**
* @notice Returns the decimals places of the token
* @return uint8 Returns the decimals places of the token.
*/
function decimals() public view virtual override(ERC20Upgradeable, IERC20MetadataUpgradeable) returns (uint8) {
return _decimals;
}

/**
* @inheritdoc IChildERC20
*/
function bridge() external view virtual returns (address) {
return _bridge;
}

/**
* @inheritdoc IChildERC20
*/
function rootToken() external view virtual returns (address) {
return _rootToken;
}

/**
* @inheritdoc IChildERC20
*/
function mint(address account, uint256 amount) external virtual onlyBridge returns (bool) {
_mint(account, amount);

return true;
}

/**
* @inheritdoc IChildERC20
*/
function burn(address account, uint256 amount) external virtual onlyBridge returns (bool) {
_burn(account, amount);

return true;
}

function _msgSender() internal view virtual override(EIP712MetaTransaction, ContextUpgradeable) returns (address) {
return EIP712MetaTransaction._msgSender();
}
}
137 changes: 137 additions & 0 deletions contracts/bridge/child/ChildERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17; // TODO hardhat config compiles with 0.8.17. We should investigate upgrading this.

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {IChildERC20BridgeEvents, IChildERC20BridgeErrors, IChildERC20Bridge, IERC20Metadata} from "../interfaces/child/IChildERC20Bridge.sol";
import {IChildERC20BridgeAdaptor} from "../interfaces/child/IChildERC20BridgeAdaptor.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.
* @dev This contract is designed to be upgradeable.
* @dev Follows a pattern of using a bridge adaptor to communicate with the child chain. This is because the underlying communication protocol may change,
* and also allows us to decouple vendor-specific messaging logic from the bridge logic.
* @dev Because of this pattern, any checks or logic that is agnostic to the messaging protocol should be done in RootERC20Bridge.
* @dev Any checks or logic that is specific to the underlying messaging protocol should be done in the bridge adaptor.
*/
contract ChildERC20Bridge is
Ownable2Step,
Initializable,
IChildERC20BridgeErrors,
IChildERC20Bridge,
IChildERC20BridgeEvents
{
using SafeERC20 for IERC20Metadata;

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

/**
* @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.
* @dev Can only be called once.
*/
function initialize(
address newBridgeAdaptor,
string memory newRootERC20BridgeAdaptor,
address newChildTokenTemplate,
string memory newRootChain
) public initializer {
if (
newBridgeAdaptor == address(0) ||
newChildTokenTemplate == address(0)
) {
revert ZeroAddress();
}

if (bytes(newRootERC20BridgeAdaptor).length == 0) {
revert InvalidRootERC20BridgeAdaptor();
}

if (bytes(newRootChain).length == 0) {
revert InvalidRootChain();
}

rootERC20BridgeAdaptor = newRootERC20BridgeAdaptor;
childTokenTemplate = newChildTokenTemplate;
bridgeAdaptor = IChildERC20BridgeAdaptor(newBridgeAdaptor);
rootChain = newRootChain;
}

/**
* @inheritdoc IChildERC20Bridge
* @dev This is only callable by the child chain bridge adaptor.
* @dev Validates `sourceAddress` is the root chain's bridgeAdaptor.
*/
function onMessageReceive(
string calldata messageSourceChain,
string calldata sourceAddress,
bytes calldata data
) external override {
if (msg.sender != address(bridgeAdaptor)) {
revert NotBridgeAdaptor();
}
if (!Strings.equal(messageSourceChain, rootChain)) {
revert InvalidSourceChain();
}
if (!Strings.equal(sourceAddress, rootERC20BridgeAdaptor)) {
revert InvalidSourceAddress();
}
if (data.length == 0) {
revert InvalidData();
}

if (bytes32(data[:32]) == MAP_TOKEN_SIG) {
_mapToken(data);
} else {
revert InvalidData();
}
}

function _mapToken(bytes calldata data) private {
(, address rootToken, string memory name, string memory symbol, uint8 decimals) = abi.decode(
data,
(bytes32, address, string, string, uint8)
);

if (address(rootToken) == address(0)) {
revert ZeroAddress();
}

if (rootTokenToChildToken[address(rootToken)] != address(0)) {
revert AlreadyMapped();
}

IChildERC20 childToken = IChildERC20(
Clones.cloneDeterministic(childTokenTemplate, keccak256(abi.encodePacked(rootToken)))
);

rootTokenToChildToken[rootToken] = address(childToken);
childToken.initialize(rootToken, name, symbol, decimals);

emit L2TokenMapped(address(rootToken), address(childToken));
}

function updateBridgeAdaptor(address newBridgeAdaptor) external override onlyOwner {
if (newBridgeAdaptor == address(0)) {
revert ZeroAddress();
}
bridgeAdaptor = IChildERC20BridgeAdaptor(newBridgeAdaptor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IChildAxelarBridgeAdaptorErrors {
error ZeroAddress();
}
49 changes: 49 additions & 0 deletions contracts/bridge/interfaces/child/IChildERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.17;

import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";

/**
* @dev Interface of IChildERC20
*/
interface IChildERC20 is IERC20MetadataUpgradeable {
/**
* @dev Sets the values for {rootToken}, {name}, {symbol} and {decimals}.
*
* All these values are immutable: they can only be set once during
* initialization.
*/
function initialize(address rootToken_, string calldata name_, string calldata symbol_, uint8 decimals_) external;

/**
* @notice Returns bridge address controlling the child token
* @return address Returns the address of the Bridge
*/
function bridge() external view returns (address);

/**
* @notice Returns bridge address controlling the child token
* @return address Returns the address of the Bridge
*/
function rootToken() external view returns (address);

/**
* @notice Mints an amount of tokens to a particular address
* @dev Can only be called by the predicate address
* @param account Account of the user to mint the tokens to
* @param amount Amount of tokens to mint to the account
* @return bool Returns true if function call is succesful
*/
function mint(address account, uint256 amount) external returns (bool);

/**
* @notice Burns an amount of tokens from a particular address
* @dev Can only be called by the predicate address
* @param account Account of the user to burn the tokens from
* @param amount Amount of tokens to burn from the account
* @return bool Returns true if function call is succesful
*/
function burn(address account, uint256 amount) external returns (bool);
}
35 changes: 35 additions & 0 deletions contracts/bridge/interfaces/child/IChildERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

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

interface IChildERC20Bridge {
/**
* @notice Receives a bridge message from root chain, parsing the message type then executing.
* @param sourceChain The chain the message originated from.
* @param sourceAddress The address the message originated from.
* @param data The data payload of the message.
*/
function onMessageReceive(string calldata sourceChain, string calldata sourceAddress, bytes calldata data) external;

/**
* @notice Sets a new bridge adaptor address to receive and send function calls for L1 messages
* @param newBridgeAdaptor The new child chain bridge adaptor address.
*/
function updateBridgeAdaptor(address newBridgeAdaptor) external;
}

interface IChildERC20BridgeEvents {
event L2TokenMapped(address rootToken, address childToken);
}

interface IChildERC20BridgeErrors {
error ZeroAddress();
error AlreadyMapped();
error NotBridgeAdaptor();
error InvalidData();
error InvalidSourceChain();
error InvalidSourceAddress();
error InvalidRootChain();
error InvalidRootERC20BridgeAdaptor();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IChildERC20BridgeAdaptor {}
Loading
Loading