-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #95 from valory-xyz/token_bridge
feat: ERC20 token bridging
- Loading branch information
Showing
19 changed files
with
1,007 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
coverage* | ||
audit/* | ||
lib/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.