Skip to content

Commit

Permalink
Merge pull request #95 from valory-xyz/token_bridge
Browse files Browse the repository at this point in the history
feat: ERC20 token bridging
  • Loading branch information
kupermind authored Nov 24, 2023
2 parents 2932bbd + ad52fe3 commit 93c03a2
Show file tree
Hide file tree
Showing 19 changed files with 1,007 additions and 2 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
coverage*
audit/*
lib/*
4 changes: 4 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ c4f2bb1997ed9f4689a807d494e7d29ed3c6bd2f:scripts/lbp/fjord_enable_swap.js:generi
260edea55dc19cebf830f7a68fa34c72685523f9:scripts/deployment/globals_mainnet.json:generic-api-key:2
4aec13ea3e18189b9f647459b84758efcea40785:scripts/deployment/globals_mainnet.json:generic-api-key:2
672f4a7e75e45bb8903e61a88246f735ba3f3b91:scripts/deployment/globals_mainnet.json:generic-api-key:2
1f3a9deea388a55014ca6d39fb67d9d932e9a5fa:scripts/deployment/bridges/polygon/test/globals.json:generic-api-key:1
1f3a9deea388a55014ca6d39fb67d9d932e9a5fa:scripts/deployment/bridges/polygon/test/globals.json:generic-api-key:2
4b70a4ad9afdd4a2b965795fc580697cb9a282f8:scripts/deployment/bridges/polygon/test/globals.json:generic-api-key:1
4b70a4ad9afdd4a2b965795fc580697cb9a282f8:scripts/deployment/bridges/polygon/test/globals.json:generic-api-key:2
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
path = lib/solmate
url = https://github.com/rari-capital/solmate
branch = v7
[submodule "lib/fx-portal"]
path = lib/fx-portal
url = https://github.com/0xPolygon/fx-portal
67 changes: 67 additions & 0 deletions contracts/bridges/BridgedERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {ERC20} from "../../lib/solmate/src/tokens/ERC20.sol";

/// @dev Only `owner` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param owner Required sender address as an owner.
error OwnerOnly(address sender, address owner);

/// @dev Provided zero address.
error ZeroAddress();


/// @title BridgedERC20 - Smart contract for bridged ERC20 token
/// @dev Bridged token contract is owned by the bridge mediator contract, and thus the token representation from
/// another chain must be minted and burned solely by the bridge mediator contract.
contract BridgedERC20 is ERC20 {
event OwnerUpdated(address indexed owner);

// Bridged token owner
address public owner;

constructor(string memory _name, string memory _symbol) ERC20(_name, symbol, 18) {
owner = msg.sender;
}

/// @dev Changes the owner address.
/// @param newOwner Address of a new owner.
function changeOwner(address newOwner) external {
// Only the contract owner is allowed to change the owner
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}

// Zero address check
if (newOwner == address(0)) {
revert ZeroAddress();
}

owner = newOwner;
emit OwnerUpdated(newOwner);
}

/// @dev Mints bridged tokens.
/// @param account Account address.
/// @param amount Bridged token amount.
function mint(address account, uint256 amount) external {
// Only the contract owner is allowed to mint
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}

_mint(account, amount);
}

/// @dev Burns bridged tokens.
/// @param amount Bridged token amount to burn.
function burn(uint256 amount) external {
// Only the contract owner is allowed to burn
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}

_burn(msg.sender, amount);
}
}
107 changes: 107 additions & 0 deletions contracts/bridges/FxERC20ChildTunnel.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {FxBaseChildTunnel} from "../../lib/fx-portal/contracts/tunnel/FxBaseChildTunnel.sol";
import {IERC20} from "../interfaces/IERC20.sol";

/// @dev Provided zero address.
error ZeroAddress();

/// @dev Zero value when it has to be different from zero.
error ZeroValue();

/// @title FxERC20ChildTunnel - Smart contract for the L2 token management part
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract FxERC20ChildTunnel is FxBaseChildTunnel {
event FxDepositERC20(address indexed childToken, address indexed rootToken, address from, address indexed to, uint256 amount);
event FxWithdrawERC20(address indexed rootToken, address indexed childToken, address from, address indexed to, uint256 amount);

// Child token address
address public immutable childToken;
// Root token address
address public immutable rootToken;

/// @dev FxERC20ChildTunnel constructor.
/// @param _fxChild Fx Child contract address.
/// @param _childToken L2 token address.
/// @param _rootToken Corresponding L1 token address.
constructor(address _fxChild, address _childToken, address _rootToken) FxBaseChildTunnel(_fxChild) {
// Check for zero addresses
if (_fxChild == address(0) || _childToken == address(0) || _rootToken == address(0)) {
revert ZeroAddress();
}

childToken = _childToken;
rootToken = _rootToken;
}

/// @dev Deposits tokens on L2 in order to obtain their corresponding bridged version on L1.
/// @notice Destination address is the same as the sender address.
/// @param amount Token amount to be deposited.
function deposit(uint256 amount) external {
_deposit(msg.sender, amount);
}

/// @dev Deposits tokens on L2 in order to obtain their corresponding bridged version on L1 by a specified address.
/// @param to Destination address on L1.
/// @param amount Token amount to be deposited.
function depositTo(address to, uint256 amount) external {
// Check for the address to deposit tokens to
if (to == address(0)) {
revert ZeroAddress();
}

_deposit(to, amount);
}

/// @dev Receives the token message from L1 and transfers L2 tokens to a specified address.
/// @param sender FxERC20RootTunnel contract address from L1.
/// @param message Incoming bridge message.
function _processMessageFromRoot(
uint256 /* stateId */,
address sender,
bytes memory message
) internal override validateSender(sender) {
// Decode incoming message from root: (address, address, uint96)
address from;
address to;
// The token amount is limited to be no bigger than 2^96 - 1
uint96 amount;
// solhint-disable-next-line no-inline-assembly
assembly {
// Offset 20 bytes for the address from (160 bits)
from := mload(add(message, 20))
// Offset 20 bytes for the address to (160 bits)
to := mload(add(message, 40))
// Offset 12 bytes of amount (96 bits)
amount := mload(add(message, 52))
}

// Transfer decoded amount of tokens to a specified address
IERC20(childToken).transfer(to, amount);

emit FxWithdrawERC20(rootToken, childToken, from, to, amount);
}

/// @dev Deposits tokens on L2 to get their representation on L1 by a specified address.
/// @param to Destination address on L1.
/// @param amount Token amount to be deposited.
function _deposit(address to, uint256 amount) internal {
// Check for the non-zero amount
if (amount == 0) {
revert ZeroValue();
}

// Deposit tokens on an L2 bridge contract (lock)
IERC20(childToken).transferFrom(msg.sender, address(this), amount);

// Encode message for root: (address, address, uint96)
bytes memory message = abi.encodePacked(msg.sender, to, uint96(amount));
// Send message to root
_sendMessageToRoot(message);

emit FxDepositERC20(childToken, rootToken, msg.sender, to, amount);
}
}
109 changes: 109 additions & 0 deletions contracts/bridges/FxERC20RootTunnel.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {FxBaseRootTunnel} from "../../lib/fx-portal/contracts/tunnel/FxBaseRootTunnel.sol";
import {IERC20} from "../interfaces/IERC20.sol";

/// @dev Provided zero address.
error ZeroAddress();

/// @dev Zero value when it has to be different from zero.
error ZeroValue();

/// @title FxERC20RootTunnel - Smart contract for the L1 token management part
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract FxERC20RootTunnel is FxBaseRootTunnel {
event FxDepositERC20(address indexed childToken, address indexed rootToken, address from, address indexed to, uint256 amount);
event FxWithdrawERC20(address indexed rootToken, address indexed childToken, address from, address indexed to, uint256 amount);

// Child token address
address public immutable childToken;
// Root token address
address public immutable rootToken;

/// @dev FxERC20RootTunnel constructor.
/// @param _checkpointManager Checkpoint manager contract.
/// @param _fxRoot Fx Root contract address.
/// @param _childToken L2 token address.
/// @param _rootToken Corresponding L1 token address.
constructor(address _checkpointManager, address _fxRoot, address _childToken, address _rootToken)
FxBaseRootTunnel(_checkpointManager, _fxRoot)
{
// Check for zero addresses
if (_checkpointManager == address(0) || _fxRoot == address(0) || _childToken == address(0) ||
_rootToken == address(0)) {
revert ZeroAddress();
}

childToken = _childToken;
rootToken = _rootToken;
}

/// @dev Withdraws bridged tokens on L1 in order to obtain their original version on L2.
/// @notice Destination address is the same as the sender address.
/// @param amount Token amount to be withdrawn.
function withdraw(uint256 amount) external {
_withdraw(msg.sender, amount);
}

/// @dev Withdraws bridged tokens on L1 in order to obtain their original version on L2 by a specified address.
/// @param to Destination address on L2.
/// @param amount Token amount to be withdrawn.
function withdrawTo(address to, uint256 amount) external {
// Check for the address to withdraw tokens to
if (to == address(0)) {
revert ZeroAddress();
}

_withdraw(to, amount);
}

/// @dev Receives the token message from L2 and transfers bridged tokens to a specified address.
/// @param message Incoming bridge message.
function _processMessageFromChild(bytes memory message) internal override {
// Decode incoming message from child: (address, address, uint96)
address from;
address to;
// The token amount is limited to be no bigger than 2^96 - 1
uint96 amount;
// solhint-disable-next-line no-inline-assembly
assembly {
// Offset 20 bytes for the address from (160 bits)
from := mload(add(message, 20))
// Offset 20 bytes for the address to (160 bits)
to := mload(add(message, 40))
// Offset 12 bytes of amount (96 bits)
amount := mload(add(message, 52))
}

// Mints bridged amount of tokens to a specified address
IERC20(rootToken).mint(to, amount);

emit FxDepositERC20(childToken, rootToken, from, to, amount);
}

/// @dev Withdraws bridged tokens from L1 to get their original tokens on L1 by a specified address.
/// @param to Destination address on L2.
/// @param amount Token amount to be withdrawn.
function _withdraw(address to, uint256 amount) internal {
// Check for the non-zero amount
if (amount == 0) {
revert ZeroValue();
}

// Transfer tokens from sender to this contract address
IERC20(rootToken).transferFrom(msg.sender, address(this), amount);

// Burn bridged tokens
IERC20(rootToken).burn(amount);

// Encode message for child: (address, address, uint96)
bytes memory message = abi.encodePacked(msg.sender, to, uint96(amount));
// Send message to child
_sendMessageToChild(message);

emit FxWithdrawERC20(rootToken, childToken, msg.sender, to, amount);
}
}
22 changes: 22 additions & 0 deletions contracts/bridges/test/FxRootMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

interface IFxERC20ChildTunnel {
function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes calldata data) external;
}

/// @title FxRootMock - Root mock contract for fx-portal
contract FxRootMock {
address public fxERC20RootTunnel;

function setRootTunnel(address _fxERC20RootTunnel) external {
fxERC20RootTunnel = _fxERC20RootTunnel;
}

/// @dev Mock of the send message to child.
/// @param _receiver FxERC20RootTunnel contract address.
/// @param _data Message to send to L2.
function sendMessageToChild(address _receiver, bytes calldata _data) external {
IFxERC20ChildTunnel(_receiver).processMessageFromRoot(0, fxERC20RootTunnel, _data);
}
}
48 changes: 48 additions & 0 deletions contracts/interfaces/IERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

/// @dev ERC20 token interface.
interface IERC20 {
/// @dev Gets the amount of tokens owned by a specified account.
/// @param account Account address.
/// @return Amount of tokens owned.
function balanceOf(address account) external view returns (uint256);

/// @dev Gets the total amount of tokens stored by the contract.
/// @return Amount of tokens.
function totalSupply() external view returns (uint256);

/// @dev Gets remaining number of tokens that the `spender` can transfer on behalf of `owner`.
/// @param owner Token owner.
/// @param spender Account address that is able to transfer tokens on behalf of the owner.
/// @return Token amount allowed to be transferred.
function allowance(address owner, address spender) external view returns (uint256);

/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
/// @param spender Account address that will be able to transfer tokens on behalf of the caller.
/// @param amount Token amount.
/// @return True if the function execution is successful.
function approve(address spender, uint256 amount) external returns (bool);

/// @dev Transfers the token amount.
/// @param to Address to transfer to.
/// @param amount The amount to transfer.
/// @return True if the function execution is successful.
function transfer(address to, uint256 amount) external returns (bool);

/// @dev Transfers the token amount that was previously approved up until the maximum allowance.
/// @param from Account address to transfer from.
/// @param to Account address to transfer to.
/// @param amount Amount to transfer to.
/// @return True if the function execution is successful.
function transferFrom(address from, address to, uint256 amount) external returns (bool);

/// @dev Mints tokens.
/// @param account Account address.
/// @param amount Token amount.
function mint(address account, uint256 amount) external;

/// @dev Burns tokens.
/// @param amount Token amount to burn.
function burn(uint256 amount) external;
}
1 change: 1 addition & 0 deletions lib/fx-portal
Submodule fx-portal added at 296ac8
Loading

0 comments on commit 93c03a2

Please sign in to comment.