EIP: 7417
title: Token Standard Converter
description: Smart-contract service that converts token of one standard to another
author: Dexaran (@Dexaran) dexaran@ethereumclassic.org
status: Draft
type: Standards Track
category: ERC
created: 2023-07-27
requires: ERC-20, ERC-165, ERC-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 tokens to be upgraded to ERC-223 tokens anytime. ERC-223 tokens can be converted back to ERC-20 version without any restrictions to avoid any problems with backwards compatibility and allow different standards to co-exist and become interoperable and interchangeable.
Motivation
The main motivation behind this EIP is to formalize the implementation of a service that performs token conversion and provide the address of the contract on mainnet in order to avoid potentially unsafe implementation from spreading.
The idea is to record the contract address here after the deployment of the contract for everyone to publicly access and use.
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 wrapper contract for each ERC-20 token
Converter contract can deploy new ERC-223 wrapper contracts for any ERC-20 token that does not have a ERC-223 wrapper currently. There MUST be exactly one ERC-223 wrapper for each ERC-20 token.
Converter contract MUST accept deposits of ERC-20 tokens and send ERC-223 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 tokens at the moment of ERC-20 deposit. The original ERC-20 tokens MUST be frozen in the Converter contract and available for claiming back.
Converter contract MUST accept deposits of ERC-223 tokens and send ERC-20 tokens to the depositor at 1:1 ratio. This is done by releasing the original ERC-20 tokens at the moment of ERC-223 deposit. The deposited ERC-223 tokens must be burned.
Token Converter contract specification
getWrapperFor
function getWrapperFor(address _erc20Token) public view returns (address)
Returns the address of the ERC-223 wrapper for a given ERC-20 original token. Returns 0x0
if there is no ERC-223 version for the provided ERC-20 token address. There MUST be exactly one wrapper for any given ERC-20 token address created by the Token Converter contract.
getOriginFor
function getOriginFor(address _erc223Token) public view returns (address)
Returns the address of the original ERC-20 token for a given ERC-223 wrapper. Returns 0x0
if the provided _erc223Token
is not an address of any ERC-223 wrapper created by the Token Converter contract.
createERC223Wrapper
function createERC223Wrapper(address _erc20Token) public returns (address)
Creates a new ERC-223 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
function convertERC20toERC223(address _erc20token, uint256 _amount) public returns (bool)
Withdraws _amount
of ERC-20 token from the transaction senders balance with transferFrom
function. Sends the _amount
of ERC-223 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 tokens that were deposited with convertERC20toERC223
function because it is possible to deposit ERC-20 tokens to any contract by directly sending them with transfer
function.
If there is no ERC-223 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 wrapper reverts the transaction.
tokenReceived
function tokenReceived(address _from, uint _value, bytes memory _data) public override returns (bytes4)
This is a standard ERC-223 transaction handler function and it is called by the ERC-223 token contract when _from
is sending _value
of ERC-223 tokens to address(this)
address. In the scope of this function msg.sender
is the address of the ERC-223 token contract and _from
is the initiator of the transaction.
If msg.sender
is an address of ERC-223 wrapper created by the Token Converter then _value
of ERC-20 original token must be sent to the _from
address.
If msg.sender
is not an address of any ERC-223 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 wrapper tokens back to original ERC-20 tokens. This function is automatically executed when ERC-223 tokens are sent to the address of the Token Converter. If any arbitrary ERC-223 token is sent to the Token Converter it will be rejected.
Returns 0x8943ec02
.
rescueERC20
function rescueERC20(address _token) external
This function allows to extract the ERC-20 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 token contract.
Converting ERC-20 tokens to ERC-223
In order to convert ERC-20 tokens to ERC-223 the token holder should:
- Call the
approve
function of the ERC-20 token and allow Token Converter to withdraw tokens from the token holders address viatransferFrom
function. - Wait for the transaction with
approve
to be submitted to the blockchain. - Call the
convertERC20toERC223
function of the Token Converter contract.
Converting ERC-223 tokens back to ERC-20
In order to convert ERC-20 tokens to ERC-223 the token holder should:
- Send ERC-223 tokens to the address of the Token Converter contract via
transfer
function of the ERC-223 token contract.
Rationale
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
pragma solidity >=0.8.0;
contract TokenStandardConverter is IERC223Recipient
{
address public ownerMultisig;
mapping (address => ERC223WrapperToken) public erc223Wrappers; // A list of token wrappers. First one is ERC-20 origin, second one is ERC-223 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 Wrapper for any ERC-20 token.");
//IERC20(erc20Origins[msg.sender]).transfer(_from, _value);
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 to ERC-223.");
ERC223WrapperToken _newERC223Wrapper = new ERC223WrapperToken(_erc20Token);
erc223Wrappers[_erc20Token] = _newERC223Wrapper;
erc20Origins[address(_newERC223Wrapper)] = _erc20Token;
return address(_newERC223Wrapper);
}
function convertERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool)
{
//require(address(erc223Wrappers[_ERC20token]) != address(0), "ERROR: ERC-223 wrapper for this ERC-20 token does not exist yet.");
// 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.
//IERC20(_ERC20token).transferFrom(msg.sender, address(this), _amount);
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
- 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 to ERC-223 conversion methods.
- ERC-20 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. - Token Converter relies on ERC-20
approve
&transferFrom
method of depositing assets. Any related issues must be taken into account.approve
andtransferFrom
are two separate transactions so it is required to make sureapproval
was successful before relying ontransferFrom
. - 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. - It is possible to artificially construct a token that will pretend it is a ERC-20 token that implements
approve & transferFrom
but at the same time implements ERC-223 logic of transferring viatransfer
function in its internal logic. It can be possible to create a ERC-223 wrapper for this ERC-20-ERC-223 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 wrapper in the Token Converter it does not automatically mean the origin is fully compatible with the ERC-20 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.