Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ OPTIMISM_MAINNET_RPC=
ARBITRUM_MAINNET_RPC=
GNOSIS_RPC=
BSC_RPC=
ETHEREUM_GOERLI_RPC=
ETHEREUM_SEPOLIA_RPC=
POLYGON_MUMBAI_RPC=
ARBITRUM_GOERLI_RPC=
OPTIMISM_GOERLI_RPC=
ARBITRUM_SEPOLIA_RPC=
OPTIMISM_SEPOLIA_RPC=

ETHERSCAN_API_KEY=

DEPLOYER_PRIVATE_KEY=

XERC20_FACTORY_ADDRESS=
14 changes: 3 additions & 11 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@
path = lib/prb-test
url = https://github.com/paulrberg/prb-test
branch = 0.1.1
[submodule "lib/isolmate"]
path = lib/isolmate
url = https://github.com/defi-wonderland/isolmate
branch = main
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
branch = v4.9.3
[submodule "lib/permit2"]
path = lib/permit2
url = https://github.com/Uniswap/permit2
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ xERC20 is a standard for bridged tokens. A common interface to be used across di

### Contracts

_XERC20_: A standard for bridges to manage the same liquidity when bridging. It allows its owner to approve bridges and add limits to them for minting and burning the XERC20 token. The XERC20 standard is compatible with two different types of bridge behaviours, bridges calling mint/burn from the user (or other function names through adapters) and bridges that transfer from the user to the bridge contract. On the latter, XERC20 tokens that are received by the bridge get burned and when a bridge transfers tokens out, they get minted again.
`XERC20`: A standard for bridges to manage the same liquidity when bridging. It allows its owner to approve bridges and add limits to them for minting and burning the XERC20 token. The XERC20 standard is compatible with two different types of bridge behaviors, bridges calling mint/burn from the user (or other function names through adapters) and bridges that transfer from the user to the bridge contract. On the latter, XERC20 tokens that are received by the bridge get burned and when a bridge transfers tokens out, they get minted again.

_XERC20Lockbox_: The lockbox works as a wrapper of an ERC20. It mints XERC20 tokens at a 1:1 ratio when receiving ERC20 tokens and it unlocks the ERC20 the other way around. The lockbox can be deployed on any chain that has a canonical token representation, chains that do not currently have a canonical representation can avoid deploying a Lockbox and use the XERC20 as the default implementation for the chain.
`XERC20Lockbox`: The lockbox works as a wrapper of an ERC20. It mints XERC20 tokens at a 1:1 ratio when receiving ERC20 tokens and it unlocks the ERC20 the other way around. The lockbox can be deployed on any chain that has a canonical token representation, chains that do not currently have a canonical representation can avoid deploying a Lockbox and use the XERC20 as the default implementation for the chain.

_XERC20Factory_: The factory is used as a helper to deploy an xToken. It allows the owner to deploy the XERC20 and the Lockbox in one transaction while keeping the same token address on every chain used.
`XERC20Factory`: The factory is used as a helper to deploy an xToken. It allows the owner to deploy the XERC20 and the Lockbox in one transaction while keeping the same token address on every chain used.

### Architectural Spec

Expand Down Expand Up @@ -97,7 +97,7 @@ You will need to set your custom `name` and `symbol` for your XERC20 to be deplo

## Setup Guide to Deploy an xERC20 Token

This guide provides a detailed, step-by-step process to deploy an xERC20 token using this repository. We will first demonstrate how to deploy the xERC20 token alone, and then well cover the scenario in which you want to deploy both the xERC20 token and the lockbox.
This guide provides a detailed, step-by-step process to deploy an xERC20 token using this repository. We will first demonstrate how to deploy the xERC20 token alone, and then we'll cover the scenario in which you want to deploy both the xERC20 token and the lockbox.

## Deploying xERC20 Without the Lockbox

Expand Down Expand Up @@ -131,10 +131,11 @@ Decide on which blockchains you want to deploy your token and the initial config
{
"name": "Test", // The name of your xERC20.
"symbol": "TST", // The symbol of your xERC20.
"decimals": 18, // The number of decimals of the xERC20.
"chainDetails": [ // The chains that the xERC20 token will be deployed to.
{
"rpcEnvName": "ETHEREUM_GOERLI_RPC", // The name of the RPC to use. It should be added in the .env file.
"erc20": "0x0000000000000000000000000000000000000001", // The address of the canonical token representation for that chain. A lockbox will be deployed pairing the deployed xERC20 with the specified ERC20 1:1. address(0) in case there is non.
"rpcEnvName": "ETHEREUM_SEPOLIA_RPC", // The name of the RPC to use. It should be added in the .env file.
"erc20": "0x0000000000000000000000000000000000000001", // The address of the canonical token representation for that chain. A lockbox will be deployed pairing the deployed xERC20 with the specified ERC20 1:1. address(0) in case there is non.
"governor": "0x0000000000000000000000000000000000000002", // The owner of the xERC20.
"isNativeGasToken": false, // True if the ERC20 token is the native gas token of the chain. Ex: ETH for ethereum or MATIC for polygon.
"bridgeDetails": [ // The bridges to be configured for the xERC20 token on this particular chain.
Expand Down Expand Up @@ -163,7 +164,7 @@ yarn build

You should see a confirmation in your terminal, similar to the screenshot below:

![Screenshot 2023-10-27 at 1 59 00PM](https://github.com/prathmeshkhandelwal1/Chat-App/assets/56167998/e05f8c07-ac4c-4a36-a9ae-05884ff5aad4)
![Screenshot 2023-10-27 at 1 59 00 PM](https://github.com/prathmeshkhandelwal1/Chat-App/assets/56167998/e05f8c07-ac4c-4a36-a9ae-05884ff5aad4)

### 6. Dry-Run Deployment

Expand All @@ -175,9 +176,9 @@ yarn run script:DeployXERC20

You will receive the xERC20 token address, transaction details, and gas estimates as shown in the screenshots below:

![Screenshot 2023-10-27 at 2 02 15PM](https://github.com/prathmeshkhandelwal1/Chat-App/assets/56167998/fc24b67e-3123-43e4-b388-2b6237e150bc)
![Screenshot 2023-10-27 at 2 02 15 PM](https://github.com/prathmeshkhandelwal1/Chat-App/assets/56167998/fc24b67e-3123-43e4-b388-2b6237e150bc)

![Screenshot 2023-10-27 at 2 05 23PM](https://github.com/prathmeshkhandelwal1/Chat-App/assets/56167998/0654c085-4f41-4cf0-b940-8030ba396fec)
![Screenshot 2023-10-27 at 2 05 23 PM](https://github.com/prathmeshkhandelwal1/Chat-App/assets/56167998/0654c085-4f41-4cf0-b940-8030ba396fec)

### 7. Deploy Your xERC20 Token

Expand Down
1 change: 0 additions & 1 deletion lib/isolmate
Submodule isolmate deleted from 73ce8b
1 change: 0 additions & 1 deletion lib/openzeppelin-contracts
Submodule openzeppelin-contracts deleted from fd81a9
1 change: 0 additions & 1 deletion lib/permit2
Submodule permit2 deleted from 576f54
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at ab7596
105 changes: 82 additions & 23 deletions solidity/contracts/XERC20.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
// SPDX-License-Identifier: UNLICENSED
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4 <0.9.0;

import {IXERC20} from '../interfaces/IXERC20.sol';
import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import {ERC20Permit} from '@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol';
import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol';
import {ERC20} from 'solady/tokens/ERC20.sol';
import {Ownable} from 'solady/auth/Ownable.sol';

contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
contract XERC20 is ERC20, Ownable, IXERC20 {
/**
* @notice The duration it takes for the limits to fully replenish
*/
uint256 private constant _DURATION = 1 days;

/**
* @notice The maximum limit of a bridge
*/
uint256 private constant _MAX_LIMIT = type(uint256).max >> 1;

/**
* @notice The number of decimals of the token
*/
uint8 private immutable _DECIMALS;

/**
* @notice The address of the factory which deployed this contract
*/
address public immutable FACTORY;

/**
* @notice The name of the token
*/
string private _name;

/**
* @notice The symbol of the token
*/
string private _symbol;

/**
* @notice The address of the lockbox contract
*/
Expand All @@ -30,13 +49,41 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
/**
* @notice Constructs the initial config of the XERC20
*
* @param _name The name of the token
* @param _symbol The symbol of the token
* @param _factory The factory which deployed this contract
* @param __name The name of the token
* @param __symbol The symbol of the token
* @param __decimals The number of decimals of the token
* @param __factory The factory which deployed this contract
*/
constructor(string memory __name, string memory __symbol, uint8 __decimals, address __factory) {
_name = __name;
_symbol = __symbol;
_DECIMALS = __decimals;
FACTORY = __factory;
_initializeOwner(__factory);
}

/**
* @notice Returns the name of the token
* @return name_ The name of the token
*/
function name() public view override returns (string memory name_) {
return _name;
}

/**
* @notice Returns the symbol of the token
* @return symbol_ The symbol of the token
*/
function symbol() public view override returns (string memory symbol_) {
return _symbol;
}

/**
* @notice Returns the number of decimals of the token
* @return decimals_ The number of decimals of the token
*/
constructor(string memory _name, string memory _symbol, address _factory) ERC20(_name, _symbol) ERC20Permit(_name) {
_transferOwnership(_factory);
FACTORY = _factory;
function decimals() public view override returns (uint8 decimals_) {
return _DECIMALS;
}

/**
Expand Down Expand Up @@ -68,7 +115,9 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
*
* @param _lockbox The address of the lockbox
*/
function setLockbox(address _lockbox) public {
function setLockbox(
address _lockbox
) public {
if (msg.sender != FACTORY) revert IXERC20_NotFactory();
lockbox = _lockbox;

Expand All @@ -83,7 +132,7 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
* @param _bridge The address of the bridge we are setting the limits too
*/
function setLimits(address _bridge, uint256 _mintingLimit, uint256 _burningLimit) external onlyOwner {
if (_mintingLimit > (type(uint256).max / 2) || _burningLimit > (type(uint256).max / 2)) {
if (_mintingLimit > _MAX_LIMIT || _burningLimit > _MAX_LIMIT) {
revert IXERC20_LimitsTooHigh();
}

Expand All @@ -98,7 +147,9 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function mintingMaxLimitOf(address _bridge) public view returns (uint256 _limit) {
function mintingMaxLimitOf(
address _bridge
) public view returns (uint256 _limit) {
_limit = bridges[_bridge].minterParams.maxLimit;
}

Expand All @@ -108,7 +159,9 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function burningMaxLimitOf(address _bridge) public view returns (uint256 _limit) {
function burningMaxLimitOf(
address _bridge
) public view returns (uint256 _limit) {
_limit = bridges[_bridge].burnerParams.maxLimit;
}

Expand All @@ -118,7 +171,9 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function mintingCurrentLimitOf(address _bridge) public view returns (uint256 _limit) {
function mintingCurrentLimitOf(
address _bridge
) public view returns (uint256 _limit) {
_limit = _getCurrentLimit(
bridges[_bridge].minterParams.currentLimit,
bridges[_bridge].minterParams.maxLimit,
Expand All @@ -133,7 +188,9 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
* @param _bridge the bridge we are viewing the limits of
* @return _limit The limit the bridge has
*/
function burningCurrentLimitOf(address _bridge) public view returns (uint256 _limit) {
function burningCurrentLimitOf(
address _bridge
) public view returns (uint256 _limit) {
_limit = _getCurrentLimit(
bridges[_bridge].burnerParams.currentLimit,
bridges[_bridge].burnerParams.maxLimit,
Expand All @@ -147,8 +204,7 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
* @param _bridge The address of the bridge who is being changed
* @param _change The change in the limit
*/
function _useMinterLimits(address _bridge, uint256 _change) internal {
uint256 _currentLimit = mintingCurrentLimitOf(_bridge);
function _useMinterLimits(address _bridge, uint256 _change, uint256 _currentLimit) internal {
bridges[_bridge].minterParams.timestamp = block.timestamp;
bridges[_bridge].minterParams.currentLimit = _currentLimit - _change;
}
Expand All @@ -158,8 +214,7 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
* @param _bridge The address of the bridge who is being changed
* @param _change The change in the limit
*/
function _useBurnerLimits(address _bridge, uint256 _change) internal {
uint256 _currentLimit = burningCurrentLimitOf(_bridge);
function _useBurnerLimits(address _bridge, uint256 _change, uint256 _currentLimit) internal {
bridges[_bridge].burnerParams.timestamp = block.timestamp;
bridges[_bridge].burnerParams.currentLimit = _currentLimit - _change;
}
Expand Down Expand Up @@ -257,10 +312,12 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
* @param _amount The amount to burn
*/
function _burnWithCaller(address _caller, address _user, uint256 _amount) internal {
if (_amount == 0) revert IXERC20_ZeroAmount();

if (_caller != lockbox) {
uint256 _currentLimit = burningCurrentLimitOf(_caller);
if (_currentLimit < _amount) revert IXERC20_NotHighEnoughLimits();
_useBurnerLimits(_caller, _amount);
_useBurnerLimits(_caller, _amount, _currentLimit);
}
_burn(_user, _amount);
}
Expand All @@ -273,10 +330,12 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit {
* @param _amount The amount to mint
*/
function _mintWithCaller(address _caller, address _user, uint256 _amount) internal {
if (_amount == 0) revert IXERC20_ZeroAmount();

if (_caller != lockbox) {
uint256 _currentLimit = mintingCurrentLimitOf(_caller);
if (_currentLimit < _amount) revert IXERC20_NotHighEnoughLimits();
_useMinterLimits(_caller, _amount);
_useMinterLimits(_caller, _amount, _currentLimit);
}
_mint(_user, _amount);
}
Expand Down
Loading