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.
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 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.
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.
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.
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.
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.
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
functionrescueERC20(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.
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
addresspublicownerMultisig;mapping(address=>ERC223WrapperToken)publicerc223Wrappers;// A list of token wrappers. First one is ERC-20 origin, second one is ERC-223 version.
mapping(address=>ERC20WrapperToken)publicerc20Wrappers;mapping(address=>address)publicerc223Origins;mapping(address=>address)publicerc20Origins;mapping(address=>uint256)publicerc20Supply;// Token => how much was deposited.
functiongetERC20WrapperFor(address_token)publicviewreturns(address,stringmemory){if(address(erc20Wrappers[_token])!=address(0)){return(address(erc20Wrappers[_token]),"ERC-20");}return(address(0),"Error");}functiongetERC223WrapperFor(address_token)publicviewreturns(address,stringmemory){if(address(erc223Wrappers[_token])!=address(0)){return(address(erc223Wrappers[_token]),"ERC-223");}return(address(0),"Error");}functiongetERC20OriginFor(address_token)publicviewreturns(address){return(address(erc20Origins[_token]));}functiongetERC223OriginFor(address_token)publicviewreturns(address){return(address(erc223Origins[_token]));}functiontokenReceived(address_from,uint_value,bytesmemory_data)publicoverridereturns(bytes4){require(erc223Origins[msg.sender]==address(0),"Error: creating wrapper for a wrapper token.");// There are two possible cases:
// 1. A user deposited ERC-223 origin token to convert it to ERC-20 wrapper
// 2. A user deposited ERC-223 wrapper token to unwrap it to ERC-20 origin.
if(erc20Origins[msg.sender]!=address(0)){// Origin for deposited token exists.
// Unwrap ERC-223 wrapper.
safeTransfer(erc20Origins[msg.sender],_from,_value);erc20Supply[erc20Origins[msg.sender]]-=_value;//erc223Wrappers[msg.sender].burn(_value);
ERC223WrapperToken(msg.sender).burn(_value);returnthis.tokenReceived.selector;}// Otherwise origin for the sender token doesn't exist
// There are two possible cases:
// 1. ERC-20 wrapper for the deposited token exists
// 2. ERC-20 wrapper for the deposited token doesn't exist and must be created.
elseif(address(erc20Wrappers[msg.sender])==address(0)){// Create ERC-20 wrapper if it doesn't exist.
createERC20Wrapper(msg.sender);}// Mint ERC-20 wrapper tokens for the deposited ERC-223 token
// if the ERC-20 wrapper didn't exist then it was just created in the above statement.
erc20Wrappers[msg.sender].mint(_from,_value);returnthis.tokenReceived.selector;}functioncreateERC223Wrapper(address_token)publicreturns(address){require(address(erc223Wrappers[_token])==address(0),"ERROR: Wrapper exists");require(getERC20OriginFor(_token)==address(0),"ERROR: 20 wrapper creation");require(getERC223OriginFor(_token)==address(0),"ERROR: 223 wrapper creation");ERC223WrapperToken_newERC223Wrapper=newERC223WrapperToken(_token);erc223Wrappers[_token]=_newERC223Wrapper;erc20Origins[address(_newERC223Wrapper)]=_token;returnaddress(_newERC223Wrapper);}functioncreateERC20Wrapper(address_token)publicreturns(address){require(address(erc20Wrappers[_token])==address(0),"ERROR: Wrapper already exists.");require(getERC20OriginFor(_token)==address(0),"ERROR: 20 wrapper creation");require(getERC223OriginFor(_token)==address(0),"ERROR: 223 wrapper creation");ERC20WrapperToken_newERC20Wrapper=newERC20WrapperToken(_token);erc20Wrappers[_token]=_newERC20Wrapper;erc223Origins[address(_newERC20Wrapper)]=_token;returnaddress(_newERC20Wrapper);}functiondepositERC20(address_token,uint256_amount)publicreturns(bool){if(erc223Origins[_token]!=address(0)){returnunwrapERC20toERC223(_token,_amount);}elsereturnwrapERC20toERC223(_token,_amount);}functionwrapERC20toERC223(address_ERC20token,uint256_amount)publicreturns(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);returntrue;}functionunwrapERC20toERC223(address_ERC20token,uint256_amount)publicreturns(bool){require(IERC20(_ERC20token).balanceOf(msg.sender)>=_amount,"Error: Insufficient balance.");require(erc223Origins[_ERC20token]!=address(0),"Error: provided token is not a ERC-20 wrapper.");ERC20WrapperToken(_ERC20token).burn(msg.sender,_amount);IERC223(erc223Origins[_ERC20token]).transfer(msg.sender,_amount);returntrue;}functionisWrapper(address_token)publicviewreturns(bool){returnerc20Origins[_token]!=address(0)||erc223Origins[_token]!=address(0);}/*
function convertERC223toERC20(address _from, uint256 _amount) public returns (bool)
{
// If there is no active wrapper for a token that user wants to wrap
// then create it.
if(address(erc20Wrappers[msg.sender]) == address(0))
{
createERC223Wrapper(msg.sender);
}
erc20Wrappers[msg.sender].mint(_from, _amount);
return true;
}
*/functionrescueERC20(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)));}functiontransferOwnership(address_newOwner)public{require(msg.sender==ownerMultisig,"ERROR: Only owner can call this function.");ownerMultisig=_newOwner;}// ************************************************************
// Functions that address problems with tokens that pretend to be ERC-20
// but in fact are not compatible with the ERC-20 standard transferring methods.
// EIP20 https://eips.ethereum.org/EIPS/eip-20
// ************************************************************
functionsafeTransfer(addresstoken,addressto,uintvalue)internal{// bytes4(keccak256(bytes('transfer(address,uint256)')));
(boolsuccess,bytesmemorydata)=token.call(abi.encodeWithSelector(0xa9059cbb,to,value));require(success&&(data.length==0||abi.decode(data,(bool))),'TransferHelper: TRANSFER_FAILED');}functionsafeTransferFrom(addresstoken,addressfrom,addressto,uintvalue)internal{// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(boolsuccess,bytesmemorydata)=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-20approve & 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.
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 via transfer 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.