forked from ethereum/EIPs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merged by EIP-Bot.
- Loading branch information
Showing
1 changed file
with
239 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,239 @@ | ||
--- | ||
eip: 7417 | ||
title: Token Converter | ||
description: Smart-contract service that converts token of one ERC version to another | ||
author: Dexaran (@Dexaran) <[email protected]> | ||
discussions-to: https://ethereum-magicians.org/t/token-standard-converter/15252 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2023-07-27 | ||
requires: 20, 165, 223 | ||
--- | ||
|
||
## Abstract | ||
|
||
There are multiple token standards on Ethereum chain currently. This EIP introduces a concept of cross-standard interoperability by creating a service that allows [ERC-20](./eip-20.md) tokens to be upgraded to [ERC-223](./eip-223.md) tokens anytime. [ERC-223](./eip-223.md) tokens can be converted back to [ERC-20](./eip-20.md) version without any restrictions to avoid any problems with backwards compatibility and allow different standards to co-exist and become interoperable and interchangeable. | ||
|
||
To perform the conversion, the user must send tokens of one standard to the Converter contract and he will automatically receive tokens of another standard. | ||
|
||
## Motivation | ||
|
||
When an ERC-20 contract is upgraded, finding the new address introduces risk. This proposal creates a service that performs token conversion and prevents potentially unsafe implementations from spreading. | ||
|
||
## Specification | ||
|
||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. | ||
|
||
The Token Converter system comprises two main parts: | ||
|
||
- Converter contract | ||
|
||
- [ERC-223](./eip-223.md) wrapper contract for each [ERC-20](./eip-20.md) token | ||
|
||
Converter contract can deploy new [ERC-223](./eip-223.md) wrapper contracts for any [ERC-20](./eip-20.md) token that does not have a [ERC-223](./eip-223.md) wrapper currently. There MUST be exactly one [ERC-223](./eip-223.md) wrapper for each [ERC-20](./eip-20.md) token. | ||
|
||
Converter contract MUST accept deposits of [ERC-20](./eip-20.md) tokens and send [ERC-223](./eip-223.md) tokens to the depositor at 1:1 ratio. Upon depositing 1234 units of `ERC-20 token_A` the depositor MUST receive exactly 1234 units of `ERC-223 token_A`. This is done by issuing new [ERC-223](./eip-223.md) tokens at the moment of [ERC-20](./eip-20.md) deposit. The original [ERC-20](./eip-20.md) tokens MUST be frozen in the Converter contract and available for claiming back. | ||
|
||
Converter contract MUST accept deposits of [ERC-223](./eip-223.md) tokens and send [ERC-20](./eip-20.md) tokens to the depositor at 1:1 ratio. This is done by releasing the original [ERC-20](./eip-20.md) tokens at the moment of [ERC-223](./eip-223.md) deposit. The deposited [ERC-223](./eip-223.md) tokens must be burned. | ||
|
||
### Token Converter | ||
|
||
#### Conver contract methods | ||
|
||
##### `getWrapperFor` | ||
|
||
```solidity | ||
function getWrapperFor(address _erc20Token) public view returns (address) | ||
``` | ||
|
||
Returns the address of the [ERC-223](./eip-223.md) wrapper for a given [ERC-20](./eip-20.md) original token. Returns `0x0` if there is no [ERC-223](./eip-223.md) version for the provided [ERC-20](./eip-20.md) token address. There MUST be exactly one wrapper for any given [ERC-20](./eip-20.md) token address created by the Token Converter contract. | ||
|
||
##### `getOriginFor` | ||
|
||
```solidity | ||
function getOriginFor(address _erc223Token) public view returns (address) | ||
``` | ||
|
||
Returns the address of the original [ERC-20](./eip-20.md) token for a given [ERC-223](./eip-223.md) wrapper. Returns `0x0` if the provided `_erc223Token` is not an address of any [ERC-223](./eip-223.md) wrapper created by the Token Converter contract. | ||
|
||
##### `createERC223Wrapper` | ||
|
||
```solidity | ||
function createERC223Wrapper(address _erc20Token) public returns (address) | ||
``` | ||
|
||
Creates a new [ERC-223](./eip-223.md) wrapper for a given `_erc20Token` if it does not exist yet. Reverts the transaction if the wrapper already exist. Returns the address of the new wrapper token contract on success. | ||
|
||
##### `convertERC20toERC223` | ||
|
||
```solidity | ||
function convertERC20toERC223(address _erc20token, uint256 _amount) public returns (bool) | ||
``` | ||
|
||
Withdraws `_amount` of [ERC-20](./eip-20.md) token from the transaction senders balance with `transferFrom` function. Sends the `_amount` of [ERC-223](./eip-223.md) wrapper tokens to the sender of the transaction. Stores the original tokens at the balance of the Token Converter contract for future claims. Returns `true` on success. The Token Converter must keep record of the amount of [ERC-20](./eip-20.md) tokens that were deposited with `convertERC20toERC223` function because it is possible to deposit [ERC-20](./eip-20.md) tokens to any contract by directly sending them with `transfer` function. | ||
|
||
If there is no [ERC-223](./eip-223.md) wrapper for the `_ERC20token` then creates it by calling a `createERC223Wrapper(_erc20toke)` function. | ||
|
||
If the provided `_erc20token` address is an address of a [ERC-223](./eip-223.md) wrapper reverts the transaction. | ||
|
||
##### `tokenReceived` | ||
|
||
```solidity | ||
function tokenReceived(address _from, uint _value, bytes memory _data) public override returns (bytes4) | ||
``` | ||
|
||
This is a standard [ERC-223](./eip-223.md) transaction handler function and it is called by the [ERC-223](./eip-223.md) token contract when `_from` is sending `_value` of [ERC-223](./eip-223.md) tokens to `address(this)` address. In the scope of this function `msg.sender` is the address of the [ERC-223](./eip-223.md) token contract and `_from` is the initiator of the transaction. | ||
|
||
If `msg.sender` is an address of [ERC-223](./eip-223.md) wrapper created by the Token Converter then `_value` of [ERC-20](./eip-20.md) original token must be sent to the `_from` address. | ||
|
||
If `msg.sender` is not an address of any [ERC-223](./eip-223.md) wrapper known to the Token Converter then revert the transaction thus returning any `ERC-223` tokens back to the sender. | ||
|
||
This is the function that MUST be used to convert [ERC-223](./eip-223.md) wrapper tokens back to original [ERC-20](./eip-20.md) tokens. This function is automatically executed when [ERC-223](./eip-223.md) tokens are sent to the address of the Token Converter. If any arbitrary [ERC-223](./eip-223.md) token is sent to the Token Converter it will be rejected. | ||
|
||
Returns `0x8943ec02`. | ||
|
||
##### `rescueERC20` | ||
|
||
```solidity | ||
function rescueERC20(address _token) external | ||
``` | ||
|
||
This function allows to extract the [ERC-20](./eip-20.md) tokens that were directly deposited to the contract with `transfer` function to prevent users who may send tokens by mistake from permanently freezing their assets. Since the Token Converter calculates the amount of tokens that were deposited legitimately with `convertERC20toERC223` function it is always possible to calculate the amount of "accidentally deposited tokens" by subtracting the recorded amount from the returned value of the `balanceOf( address(this) )` function called on the [ERC-20](./eip-20.md) token contract. | ||
|
||
### Converting [ERC-20](./eip-20.md) tokens to [ERC-223](./eip-223.md) | ||
|
||
In order to convert [ERC-20](./eip-20.md) tokens to [ERC-223](./eip-223.md) the token holder should: | ||
|
||
1. Call the `approve` function of the [ERC-20](./eip-20.md) token and allow Token Converter to withdraw tokens from the token holders address via `transferFrom` function. | ||
2. Wait for the transaction with `approve` to be submitted to the blockchain. | ||
3. Call the `convertERC20toERC223` function of the Token Converter contract. | ||
|
||
### Converting [ERC-223](./eip-223.md) tokens back to [ERC-20](./eip-20.md) | ||
|
||
In order to convert [ERC-20](./eip-20.md) tokens to [ERC-223](./eip-223.md) the token holder should: | ||
|
||
1. Send [ERC-223](./eip-223.md) tokens to the address of the Token Converter contract via `transfer` function of the [ERC-223](./eip-223.md) token contract. | ||
|
||
## Rationale | ||
|
||
<!-- | ||
The rationale fleshes out the specification by describing what motivated the design and why particular design decisions were made. It should describe alternate designs that were considered and related work, e.g. how the feature is supported in other languages. | ||
The current placeholder is acceptable for a draft. | ||
TODO: Remove this comment before submitting | ||
--> | ||
|
||
TBD | ||
|
||
## Backwards Compatibility | ||
|
||
This proposal is supposed to eliminate the backwards compatibility concerns for different token standards making them interchangeable and interoperable. | ||
|
||
This service is the first of its kind and therefore does not have any backwards compatibility issues as it does not have any predecessors. | ||
|
||
## Reference Implementation | ||
|
||
```solidity | ||
address public ownerMultisig; | ||
mapping (address => ERC223WrapperToken) public erc223Wrappers; // A list of token wrappers. | ||
// First one is [ERC-20](./eip-20.md) origin, | ||
// second one is [ERC-223](./eip-223.md) version. | ||
mapping (address => address) public erc20Origins; | ||
mapping (address => uint256) public erc20Supply; // Token => how much was deposited. | ||
function getWrapperFor(address _erc20Token) public view returns (address) | ||
{ | ||
return address(erc223Wrappers[_erc20Token]); | ||
} | ||
function getOriginFor(address _erc223WrappedToken) public view returns (address) | ||
{ | ||
return erc20Origins[_erc223WrappedToken]; | ||
} | ||
function tokenReceived(address _from, uint _value, bytes memory _data) public override returns (bytes4) | ||
{ | ||
require(erc20Origins[msg.sender] != address(0), "ERROR: The received token is not a [ERC-223](./eip-223.md) Wrapper for any [ERC-20](./eip-20.md) token."); | ||
safeTransfer(erc20Origins[msg.sender], _from, _value); | ||
erc20Supply[erc20Origins[msg.sender]] -= _value; | ||
erc223Wrappers[msg.sender].burn(_value); | ||
return 0x8943ec02; | ||
} | ||
function createERC223Wrapper(address _erc20Token) public returns (address) | ||
{ | ||
require(address(erc223Wrappers[_erc20Token]) == address(0), "ERROR: Wrapper already exists."); | ||
require(getOriginFor(_erc20Token) == address(0), "ERROR: Cannot convert [ERC-223](./eip-223.md) to [ERC-223](./eip-223.md)."); | ||
ERC223WrapperToken _newERC223Wrapper = new ERC223WrapperToken(_erc20Token); | ||
erc223Wrappers[_erc20Token] = _newERC223Wrapper; | ||
erc20Origins[address(_newERC223Wrapper)] = _erc20Token; | ||
return address(_newERC223Wrapper); | ||
} | ||
function convertERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool) | ||
{ | ||
// If there is no active wrapper for a token that user wants to wrap | ||
// then create it. | ||
if(address(erc223Wrappers[_ERC20token]) == address(0)) | ||
{ | ||
createERC223Wrapper(_ERC20token); | ||
} | ||
uint256 _converterBalance = IERC20(_ERC20token).balanceOf(address(this)); // Safety variable. | ||
safeTransferFrom(_ERC20token, msg.sender, address(this), _amount); | ||
erc20Supply[_ERC20token] += _amount; | ||
require( | ||
IERC20(_ERC20token).balanceOf(address(this)) - _amount == _converterBalance, | ||
"ERROR: The transfer have not subtracted tokens from callers balance."); | ||
erc223Wrappers[_ERC20token].mint(msg.sender, _amount); | ||
return true; | ||
} | ||
function rescueERC20(address _token) external { | ||
require(msg.sender == ownerMultisig, "ERROR: Only owner can do this."); | ||
uint256 _stuckTokens = IERC20(_token).balanceOf(address(this)) - erc20Supply[_token]; | ||
safeTransfer(_token, msg.sender, IERC20(_token).balanceOf(address(this))); | ||
} | ||
function transferOwnership(address _newOwner) public | ||
{ | ||
require(msg.sender == ownerMultisig, "ERROR: Only owner can do this."); | ||
ownerMultisig = _newOwner; | ||
} | ||
function safeTransfer(address token, address to, uint value) internal { | ||
// bytes4(keccak256(bytes('transfer(address,uint256)'))); | ||
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value)); | ||
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED'); | ||
} | ||
function safeTransferFrom(address token, address from, address to, uint value) internal { | ||
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))); | ||
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); | ||
require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED'); | ||
} | ||
``` | ||
|
||
## Security Considerations | ||
|
||
1. While it is possible to implement a service that converts any token standard to any other standard - it is better to keep different standard convertors separate from one another as different standards may contain specific logic. Therefore this proposal focuses on [ERC-20](./eip-20.md) to [ERC-223](./eip-223.md) conversion methods. | ||
2. [ERC-20](./eip-20.md) tokens can be deposited to any contract directly with `transfer` function. This may result in a permanent loss of tokens because it is not possible to recognize this transaction on the recipients side. `rescueERC20` function is implemented to address this problem. | ||
3. Token Converter relies on [ERC-20](./eip-20.md) `approve` & `transferFrom` method of depositing assets. Any related issues must be taken into account. `approve` and `transferFrom` are two separate transactions so it is required to make sure `approval` was successful before relying on `transferFrom`. | ||
4. This is a common practice for UI services to prompt a user to issue unlimited `approval` on any contract that may withdraw tokens from the user. This puts users funds at high risk and therefore not recommended. | ||
5. It is possible to artificially construct a token that will pretend it is a [ERC-20](./eip-20.md) token that implements `approve & transferFrom` but at the same time implements [ERC-223](./eip-223.md) logic of transferring via `transfer` function in its internal logic. It can be possible to create a [ERC-223](./eip-223.md) wrapper for this [ERC-20](./eip-20.md)-[ERC-223](./eip-223.md) hybrid implementation in the Token Converter. This doesn't pose any threat for the workflow of the Token Converter but it must be taken into account that if a token has [ERC-223](./eip-223.md) wrapper in the Token Converter it does not automatically mean the origin is fully compatible with the [ERC-20](./eip-20.md) standard and methods of introspection must be used to determine the origins compatibility with any existing standard. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |