Token Standard Converter


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:

  1. Call the approve function of the ERC-20 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 tokens back to ERC-20

In order to convert ERC-20 tokens to ERC-223 the token holder should:

  1. 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

  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 to ERC-223 conversion methods.
  2. 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.
  3. Token Converter relies on ERC-20 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 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.

Copyright

Copyright and related rights waived via CC0.