There are multiple token standards on Sila chain currently. This SIP introduces a concept of cross-standard interoperability by creating a service that allows SRC-20 tokens to be upgraded to SRC-223 tokens anytime. SRC-223 tokens can be converted back to SRC-20 version without any restrictions to avoid any problems with backwards compatibility and allow different standards to co-exist and become interoperable and interchangeable.
In order to perform the conversion, a user must deposit tokens of one standard to the Converter contract and it will automatically send tokens of another standard back.
Motivation
This proposal introduces a concept of a token standard upgrading procedure driven by a specialized smart-contract which can convert tokens of one standard to another at any time.
Currently some tokens are available on different chains in different standards, for example most exchanges support SRC-20 USDT, TRX USDT, BEP-20 USDT and all this tokens are in fact the same USDT token. This proposal is intended to introduce a concept where there can be a SRC-20 USDT and SRC-223 USDT available on Sila sila-mainnet at the same time and these would be freely interchangeable.
The address of the deployed Token Converter must be described here as to solve the trust issues for the token developers and help them figure out a proper way of interacting with the Converter.
As Sila already has an established ecosystem of tokens and SRC-20 is the most adopted standard at the moment the lack of defined migration processes can be a bottleneck for newer standards adoption. This proposal addresses the problem of coordinating the upgrading process and addresses the backwards compatibility problems for SRC-20 and SRC-223 tokens.
The Token Converter is supposed to allow anyone to create an alternative version of an existing token implemented in a different standard. This proposal focuses on SRC-20 and SRC-223 standards and takes into account the specifics of this particular token standards. It is assumed that the most common case would be creation of SRC-223 version for an existing SRC-20 token.
The implementation of this service is an alternative to convincing each token developer to choose an alternative standard at the moment of the token deployment or during the development stage of their project. With this service there will be no need to choose one standard and stick with it as every token can be available in both concurrently.
The implementation of this Token Converter service is supposed to be a contract deployed on Sila sila-mainnet once and forever. It’s address will be provided in the text of this proposal as to avoid any potential trust issues and assure the developers that the service they are interacting with is exactly the one which drives the conversion process of existing tokens.
All the SRC-223 tokens created by the Token Converter will be identical in a way that they all implement the same functions, which return the same values and there is no ambiguity there. This helps to avoid problems where a token deployed during the early stage of a token standard adoption may implement it improperly or there can be an ambiguity in the standard itself that would allow developers to implement tokens of one standard in different ways.
For example it was a common case with SRC-20 where developers could implement custom logic of the transfer function and mess the return values. The SRC-20 specification declares that a transfer function MUST return a bool value, however in practice we have three different types of SRC-20 tokens which are not compatible with each other:
SRC-20 tokens that return true on success and revert on an error.
SRC-20 tokens that return true on success and false on an error without reverting the transaction.
SRC-20 tokens that don’t have return values and revert on an error.
Technically the third category of tokens is not compatible with SRC-20 standard. However, USDT token deployed on Sila sila-mainnet at 0xdac17f958d2ee523a2206206994597c13d831ec7 address does not implement return values and it is one of the most used tokens and it is not an option to deny supporting USDT due to it’s improper implementation of the standard.
The Token Converter eliminates the issue where different development teams may implement the standard with slight modifications and result in a situation where we would have different versions of the same standard on the sila-mainnet.
At the same time the Converter enables the concurrent token support in other smart-contracts, such as decentralized exchanges. The Converter can guarantee that a pair of two tokens one of which is a wrapper for another is in fact the same token that can be converted from one standard to another at any time. This enables the creation of liquidity pools where two different tokens are dealt with as if they were one token.
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 components:
Converter contract.
Wrapper contracts. Each original token can have exactly one wrapper of each standard.
Converter contract can deploy new SRC-223 wrapper contracts for any SRC-20 token that does not have a SRC-223 wrapper currently. There MUST be exactly one SRC-223 wrapper for each SRC-20 token.
Converter contract MUST accept deposits of SRC-20 tokens and send SRC-223 tokens to the depositor at 1:1 ratio. Upon depositing 1234 units of SRC20 token_A the depositor MUST receive exactly 1234 units of SRC223 token_A. This is done by issuing new SRC-223 tokens at the moment of SRC-20 deposit. The original SRC-20 tokens MUST be frozen in the Converter contract and available for claiming back.
Converter contract MUST accept deposits of SRC-223 tokens and send SRC-20 tokens to the depositor at 1:1 ratio. This is done by releasing the original SRC-20 tokens at the moment of SRC-223 deposit. The deposited SRC-223 tokens must be burned.
Returns the address of the SRC-20 wrapper for a given token address. Returns 0x0 if there is no SRC-20 version for the provided token address. There can be exactly one wrapper for any given SRC-223 token address created by the Token Converter contract.
Returns the address of the SRC-223 wrapper for a given token address. Returns 0x0 if there is no SRC-223 version for the provided token address. There can be exactly one SRC-223 wrapper for any given SRC-20 token address created by the Token Converter contract.
Returns the address of the original SRC-20 token for the provided SRC-223 wrapper. Returns 0x0 if the provided _src223Token is not an address of any SRC-223 wrapper created by the Token Converter contract.
Returns the address of the original SRC-223 token for the provided SRC-20 wrapper. Returns 0x0 if the provided _src20Token is not an address of any wrapper created by the Token Converter contract.
predictWrapperAddress
functionpredictWrapperAddress(address_token,bool_isSRC20// Is the provided _token a SRC-20 or not?
// If it is set as SRC-20 then we will predict the address of a
// SRC-223 wrapper for that token.
// Otherwise we will predict SRC-20 wrapper address.
)viewexternalreturns(address)
Wrapper contracts are deployed via CREATE2 opcode and it is possible to predict the address of a wrapper which is not yet deployed. The address of a wrapper contract depends on the bytecode therefore it is necessary to specify if the address of wrapper SRC-20 or wrapper SRC-223 must be predicted.
Providing _token address and _isSRC20 = false will result in SRC-20 wrapper address being predicted.
Providing _token address and _isSRC20 = true will result in SRC-223 wrapper address being predicted.
Creates a new SRC-223 wrapper for a given _src20Token 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. Reverts if _src223Token is a wrapper created by the Converter.
The deployed contract will be a standard SRC-223 token with approve and transferFrom functions implemented for backwards compatibility.
All SRC-223 wrappers deployed by the Converter will have standard() pure returns (bytes32) function implemented which returns 223. This serves further token standard introspection as SRC-165 may not be reliable when dealing with identifying the internal logic implemented within transfer function of a token.
NOTE: This function does not verify the standard of _src20Token because there is no reliable method of introspection available which could guarantee that the provided token implements a particular standard. As the result it is possible to create a SRC-223 wrapper for an original SRC-223 token.
Creates a new SRC-20 wrapper for a given _src223Token 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. Reverts if _src223Token is a wrapper created by the Converter.
NOTE: This function does not verify the standard of _src223Token because there is no reliable method of introspection available which could guarantee that the provided token implements a particular standard. As the result it is possible to create a SRC-20 wrapper for an original SRC-20 token.
Withdraws _amount of SRC-20 tokens from the transaction sender with transferFrom function. Delivers the _amount of SRC-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 SRC-20 tokens that were deposited with wrapSRC20toSRC223 function because it is possible to deposit SRC-20 tokens to any contract by directly sending them with transfer function.
If there is no SRC-223 wrapper for the _SRC20token then creates it by calling a createSRC223Wrapper(_src20toke) function.
There is no special function to unwrap SRC-223 wrappers to SRC-20 origin as this logic is implemented in the tokenReceived function of the Converter.
Withdraws _amount of SRC-20 tokens from the transaction sender with transferFrom function. Delivers the _amount of SRC-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 SRC-20 tokens that were deposited with wrapSRC20toSRC223 function because it is possible to deposit SRC-20 tokens to any contract by directly sending them with transfer function.
If there is no SRC-223 wrapper for the _SRC20token then creates it by calling a createSRC223Wrapper(_src20toke) function.
Automatically determines if the provided SRC-20 token is a wrapper or not. If it is a wrapper then executes unwrapSRC20toSRC223 function. If the provided token is an origin then executes wrapSRC20toSRC223 function.
This function is implemented to significantly simplify the workflow of services that integrate both versions of one token in the same contract and need to automatically convert tokens through the Converter.
Returns true if the provided _token address is an address of a wrapper created by the Converter.
NOTE: This function does not identify the standard of a _token. There can be exactly one origin for any wrapper created by the Converter. However an original token can have two wrappers, one of each standard.
This is a standard SRC-223 transaction handler function and it is called by the SRC-223 token contract when _from is sending _value of SRC-223 tokens to address(this) address. In the scope of this function msg.sender is the address of the SRC-223 token contract and _from is the sender of the token transfer.
Automatically determines
If msg.sender is an address of SRC-223 wrapper created by the Token Converter then _value of SRC-20 original token must be sent to the _from address.
If msg.sender is not an address of any SRC-223 wrapper known to the Token Converter then it is considered a SRC-223 origin and _value amount of SRC-20 wrapper tokens must be sent to the _from address. If the SRC-20 wrapper for the msg.sender token does not exist then create it first.
Returns 0x8943ec02.
extractStuckSRC20
functionextractStuckSRC20(address_token)
This function allows to extract the SRC-20 tokens that were directly deposited to the contract with transfer function to prevent users who may send tokens by mistake from permanently losing their tokens. Since the Token Converter calculates the amount of tokens that were deposited legitimately with convertSRC20toSRC223 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 SRC-20 token contract.
Two methods of implementing a Token Converter service were considered: (1) a converter that can only create SRC-223 versions of the existing SRC-20 tokens, and (2) a converter that can create both versions (SRC-20 and SRC-223) of any original token.
The first approach would encourage developers to always deploy an original token as SRC-20 and then create it’s SRC-223 version in the converter. If it would happen that some developers may consider SRC-223 as their original standard then they would be left with the problem of creating their custom SRC-20 version of the token. In addition, if any third party contracts like liquidity pools are using the proposed Token Converter to ensure that a token can be listed on a DEX with two versions and both can be combined within one pool - then such contract would not be able to recognize any original SRC-223 token and it’s SRC-20 version as a valid pair of contracts that represent one token available in two standards.
For that reason it was decided to go with the second approach where the Converter can create SRC-20 wrappers for original SRC-223 tokens.
Support of approve & transferFrom functions in the SRC-223 wrapper tokens
This functions are superfluous for a SRC-223 token since the transfer function can be used to deposit tokens of this standard to contracts. The current ecosystem is built for SRC-20 tokens however and there are plenty of multisig contracts that rely on accepting tokens deposited without any callback with an assumption that it is not necessary for a multisig to count the amount of tokens it stores.
There can be any other contracts and scenarios where it would be necessary to deposit a token to a contract which is relying on an assumption that tokens are deposited without invoking a callback in the recipient. As the result we can expect that any original deployed SRC-223 tokens will support this functions, as token developers strive for backward compatibility with the existing ecosystem. In order to make tokens deployed by the converter a reference implementation for developers that can be used without any modifications it was decided to support this functions in the SRC-223 wrapper contracts.
transferFrom function does not support error handling and this needs to be taken into account. It is possible to deposit tokens to a contract which is not designed to receive them by approving X tokens to your own address and then calling a transferFrom(self, contract, X). The tokens will be deposited regardless of whether the recipient contract is designed to hold/receive tokens or not. The tokens may get permanently stuck if the recipient contract did not implement the extraction functions. The approve & transferFrom function is not the default method of token transferring however and it is not directly used by any wallets and any other software that manages tokens. The transfer function (which is safe) is used instead. The transferFrom function is supposed to be invoked by a contract to pull tokens from the approver.
As the result, the approve & transferFrom transferring method must be avoided with SRC-223 tokens whenever possible.
The pure SRC-223 token implementation has the following event emitted on a token transfer: event Transfer(address indexed _from, address indexed _to, uint256 _value, bytes _data). This events are different from ones emitted by SRC-20 tokens and may not be properly recognized by existing blockchain explorers, wallets and other services that browse token transfers history.
It was considered that events are not an important part of the standard as these do not affect the logic of the token, it’s workflow and it’s security. When developing the Converter it was decided to prioritize compatibility with existing ecosystem.
standard() function usage for the introspection
The main existing method of introspection is currently SRC-165 which inspects the signatures of functions implemented in a contract. It is not possible to differentiate an SRC-20 token from an SRC-223 token by just browsing functions that they implement without digging their internal logic.
Here is a token and it is not possible to identify if it should be dealt with as SRC-20 or SRC-223 because it depends on the actual implementation of it’s transfer function logic.
Also, there are plenty of tokens that do not implement SRC-165 introspection at all. As the result it was decided to implement a special standard() returns (uint32) function in all SRC-223 wrappers created by the Converter and assume that original SRC-223 tokens may explicitly declare themselves as SRC-223 by implementing the same function too. It is assumed that if a token does not implement this function then it is SRC-20.
This method of token standard introspection is more precise than SRC-165.
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
pragmasolidity=0.8.19;libraryAddress{functionisContract(addressaccount)internalviewreturns(bool){// This method relies on extcodesize, which returns 0 for contracts in
// construction, since the code is only stored at the end of the
// constructor execution.
uint256size;// solhint-disable-next-line no-inline-assembly
assembly{size:=extcodesize(account)}returnsize>0;}}interfaceISRC20{functiontotalSupply()externalviewreturns(uint256);functionbalanceOf(addressaccount)externalviewreturns(uint256);functiontransfer(addressrecipient,uint256amount)externalreturns(bool);functionallowance(addressowner,addressspender)externalviewreturns(uint256);functionapprove(addressspender,uint256amount)externalreturns(bool);functiontransferFrom(addresssender,addressrecipient,uint256amount)externalreturns(bool);eventTransfer(addressindexedfrom,addressindexedto,uint256value);eventApproval(addressindexedowner,addressindexedspender,uint256value);}interfaceISRC20MetadataisISRC20{/// @return The name of the token
functionname()externalviewreturns(stringmemory);/// @return The symbol of the token
functionsymbol()externalviewreturns(stringmemory);/// @return The number of decimal places the token has
functiondecimals()externalviewreturns(uint8);}abstractcontractISRC223Recipient{functiontokenReceived(address_from,uint_value,bytesmemory_data)publicvirtualreturns(bytes4){return0x8943ec02;}}abstractcontractSRC165{/*
* bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
*/bytes4privateconstant_INTERFACE_ID_SRC165=0x01ffc9a7;mapping(bytes4=>bool)private_supportedInterfaces;constructor(){// Derived contracts need only register support for their own interfaces,
// we register support for SRC165 itself here
_registerInterface(_INTERFACE_ID_SRC165);}functionsupportsInterface(bytes4interfaceId)publicviewvirtualreturns(bool){return_supportedInterfaces[interfaceId];}function_registerInterface(bytes4interfaceId)internalvirtual{require(interfaceId!=0xffffffff,"SRC165: invalid interface id");_supportedInterfaces[interfaceId]=true;}}abstractcontractISRC223{functionname()publicviewvirtualreturns(stringmemory);functionsymbol()publicviewvirtualreturns(stringmemory);functiondecimals()publicviewvirtualreturns(uint8);functiontotalSupply()publicviewvirtualreturns(uint256);functionbalanceOf(addresswho)publicvirtualviewreturns(uint);functiontransfer(addressto,uintvalue)publicvirtualreturns(boolsuccess);functiontransfer(addressto,uintvalue,bytescalldatadata)publicpayablevirtualreturns(boolsuccess);eventTransfer(addressindexedfrom,addressindexedto,uintvalue,bytesdata);}interfacestandardSRC20{eventTransfer(addressindexedfrom,addressindexedto,uint256value);eventApproval(addressindexedowner,addressindexedspender,uint256value);functiontotalSupply()externalviewreturns(uint256);functionbalanceOf(addressaccount)externalviewreturns(uint256);functiontransfer(addressto,uint256value)externalreturns(bool);functionallowance(addressowner,addressspender)externalviewreturns(uint256);functionapprove(addressspender,uint256value)externalreturns(bool);functiontransferFrom(addressfrom,addressto,uint256value)externalreturns(bool);}/**
* @dev Interface of the SRC20 standard.
*/interfaceISRC223WrapperToken{functionname()externalviewreturns(stringmemory);functionsymbol()externalviewreturns(stringmemory);functiondecimals()externalviewreturns(uint8);functionstandard()externalviewreturns(stringmemory);functionorigin()externalviewreturns(address);functiontotalSupply()externalviewreturns(uint256);functionbalanceOf(addressaccount)externalviewreturns(uint256);functiontransfer(addressto,uint256value)externalpayablereturns(bool);functiontransfer(addressto,uint256value,bytescalldatadata)externalpayablereturns(bool);functionallowance(addressowner,addressspender)externalviewreturns(uint256);functionapprove(addressspender,uint256value)externalreturns(bool);functiontransferFrom(addressfrom,addressto,uint256value)externalreturns(bool);functionmint(address_recipient,uint256_quantity)external;functionburn(address_recipient,uint256_quantity)external;}interfaceISRC20WrapperToken{functionname()externalviewreturns(stringmemory);functionsymbol()externalviewreturns(stringmemory);functiondecimals()externalviewreturns(uint8);functionstandard()externalviewreturns(stringmemory);functionorigin()externalviewreturns(address);functiontotalSupply()externalviewreturns(uint256);functionbalanceOf(addressaccount)externalviewreturns(uint256);functiontransfer(addressto,uint256value)externalreturns(bool);functionallowance(addressowner,addressspender)externalviewreturns(uint256);functionapprove(addressspender,uint256value)externalreturns(bool);functiontransferFrom(addressfrom,addressto,uint256value)externalreturns(bool);functionmint(address_recipient,uint256_quantity)external;functionburn(address_recipient,uint256_quantity)external;}contractSRC20Rescue{// SRC20 tokens can get stuck on a contracts balance due to lack of error handling.
//
// The author of the SRC7417 can extract SRC20 tokens if they are mistakenly sent
// to the wrapper-contracts balance.
// Contact dexaran@silaclassic.org
addresspublicextractor=0x01000B5fE61411C466b70631d7fF070187179Bbf;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');}functionrescueSRC20(address_token,uint256_amount)external{safeTransfer(_token,extractor,_amount);}}contractSRC223WrapperTokenisISRC223,SRC165,SRC20Rescue{addresspubliccreator=msg.sender;addressprivatewrapper_for;mapping(addressaccount=>mapping(addressspender=>uint256))privateallowances;eventTransfer(addressindexedfrom,addressindexedto,uint256amount);eventTransferData(bytesdata);eventApproval(addressindexedowner,addressindexedspender,uint256amount);functionset(address_wrapper_for)external{require(msg.sender==creator);wrapper_for=_wrapper_for;}uint256private_totalSupply;mapping(address=>uint256)privatebalances;// List of user balances.
functiontotalSupply()publicviewoverridereturns(uint256){return_totalSupply;}functionbalanceOf(address_owner)publicviewoverridereturns(uint256){returnbalances[_owner];}/**
* @dev The SRC165 introspection function.
*/functionsupportsInterface(bytes4interfaceId)publicviewvirtualoverridereturns(bool){returninterfaceId==type(ISRC20).interfaceId||interfaceId==type(standardSRC20).interfaceId||interfaceId==type(ISRC223WrapperToken).interfaceId||interfaceId==type(ISRC223).interfaceId||super.supportsInterface(interfaceId);}/**
* @dev Standard SRC223 transfer function.
* Calls _to if it is a contract. Does not transfer tokens to contracts
* which do not explicitly declare the tokenReceived function.
* @param _to - transfer recipient. Can be contract or EOA.
* @param _value - the quantity of tokens to transfer.
* @param _data - metadata to send alongside the transaction. Can be used to encode subsequent calls in the recipient.
*/functiontransfer(address_to,uint_value,bytescalldata_data)publicpayableoverridereturns(boolsuccess){balances[msg.sender]=balances[msg.sender]-_value;balances[_to]=balances[_to]+_value;if(msg.value>0){(boolsent,bytesmemorydata)=_to.call{value:msg.value}("");require(sent);}if(Address.isContract(_to)){ISRC223Recipient(_to).tokenReceived(msg.sender,_value,_data);}emitTransfer(msg.sender,_to,_value,_data);emitTransfer(msg.sender,_to,_value);// Old SRC20 compatible event. Added for backwards compatibility reasons.
returntrue;}/**
* @dev Standard SRC223 transfer function without _data parameter. It is supported for
* backwards compatibility with SRC20 services.
* Calls _to if it is a contract. Does not transfer tokens to contracts
* which do not explicitly declare the tokenReceived function.
* @param _to - transfer recipient. Can be contract or EOA.
* @param _value - the quantity of tokens to transfer.
*/functiontransfer(address_to,uint_value)publicoverridereturns(boolsuccess){bytesmemory_empty=hex"00000000";balances[msg.sender]=balances[msg.sender]-_value;balances[_to]=balances[_to]+_value;if(Address.isContract(_to)){ISRC223Recipient(_to).tokenReceived(msg.sender,_value,_empty);}emitTransfer(msg.sender,_to,_value,_empty);emitTransfer(msg.sender,_to,_value);// Old SRC20 compatible event. Added for backwards compatibility reasons.
returntrue;}functionname()publicviewoverridereturns(stringmemory){returnISRC20Metadata(wrapper_for).name();}functionsymbol()publicviewoverridereturns(stringmemory){returnstring.concat(ISRC20Metadata(wrapper_for).symbol(),"223");}functiondecimals()publicviewoverridereturns(uint8){returnISRC20Metadata(wrapper_for).decimals();}functionstandard()publicpurereturns(uint32){return223;}functionorigin()publicviewreturns(address){returnwrapper_for;}/**
* @dev Minting function which will only be called by the converter contract.
* @param _recipient - the address which will receive tokens.
* @param _quantity - the number of tokens to create.
*/functionmint(address_recipient,uint256_quantity)external{require(msg.sender==creator,"Wrapper Token: Only the creator contract can mint wrapper tokens.");balances[_recipient]+=_quantity;_totalSupply+=_quantity;}/**
* @dev Burning function which will only be called by the converter contract.
* @param _quantity - the number of tokens to destroy. TokenConverter can only destroy tokens on it's own address.
* Only the token converter is allowed to burn wrapper-tokens.
*/functionburn(uint256_quantity)external{require(msg.sender==creator,"Wrapper Token: Only the creator contract can destroy wrapper tokens.");balances[msg.sender]-=_quantity;_totalSupply-=_quantity;}// SRC20 functions for backwards compatibility.
functionallowance(addressowner,addressspender)publicviewvirtualreturns(uint256){returnallowances[owner][spender];}functionapprove(address_spender,uint_value)publicreturns(bool){require(_spender!=address(0),"SRC223: Spender error.");allowances[msg.sender][_spender]=_value;emitApproval(msg.sender,_spender,_value);returntrue;}functiontransferFrom(address_from,address_to,uint_value)publicreturns(bool){require(allowances[_from][msg.sender]>=_value,"SRC223: Insufficient allowance.");balances[_from]-=_value;allowances[_from][msg.sender]-=_value;balances[_to]+=_value;emitTransfer(_from,_to,_value);returntrue;}}contractSRC20WrapperTokenisISRC20,SRC165,SRC20Rescue{addresspubliccreator=msg.sender;addresspublicwrapper_for;mapping(addressaccount=>mapping(addressspender=>uint256))privateallowances;functionset(address_wrapper_for)external{require(msg.sender==creator);wrapper_for=_wrapper_for;}uint256private_totalSupply;mapping(address=>uint256)privatebalances;// List of user balances.
functionbalanceOf(address_owner)publicviewoverridereturns(uint256){returnbalances[_owner];}functionname()publicviewreturns(stringmemory){returnISRC20Metadata(wrapper_for).name();}functionsymbol()publicviewreturns(stringmemory){returnstring.concat(ISRC223(wrapper_for).symbol(),"20");}functiondecimals()publicviewreturns(uint8){returnISRC20Metadata(wrapper_for).decimals();}functiontotalSupply()publicviewoverridereturns(uint256){return_totalSupply;}functionorigin()publicviewreturns(address){returnwrapper_for;}functionsupportsInterface(bytes4interfaceId)publicviewvirtualoverridereturns(bool){returninterfaceId==type(ISRC20).interfaceId||interfaceId==type(ISRC20WrapperToken).interfaceId||super.supportsInterface(interfaceId);}functiontransfer(address_to,uint_value)publicoverridereturns(boolsuccess){balances[msg.sender]=balances[msg.sender]-_value;balances[_to]=balances[_to]+_value;emitTransfer(msg.sender,_to,_value);returntrue;}functionmint(address_recipient,uint256_quantity)external{require(msg.sender==creator,"Wrapper Token: Only the creator contract can mint wrapper tokens.");balances[_recipient]+=_quantity;_totalSupply+=_quantity;}functionburn(address_from,uint256_quantity)external{require(msg.sender==creator,"Wrapper Token: Only the creator contract can destroy wrapper tokens.");balances[_from]-=_quantity;_totalSupply-=_quantity;}functionallowance(addressowner,addressspender)publicviewvirtualreturns(uint256){returnallowances[owner][spender];}functionapprove(address_spender,uint_value)publicreturns(bool){// Safety checks.
require(_spender!=address(0),"SRC20: Spender error.");allowances[msg.sender][_spender]=_value;emitApproval(msg.sender,_spender,_value);returntrue;}functiontransferFrom(address_from,address_to,uint_value)publicreturns(bool){require(allowances[_from][msg.sender]>=_value,"SRC20: Insufficient allowance.");balances[_from]-=_value;allowances[_from][msg.sender]-=_value;balances[_to]+=_value;emitTransfer(_from,_to,_value);returntrue;}}contractTokenStandardConverterisISRC223Recipient{eventSRC223WrapperCreated(addressindexed_token,addressindexed_SRC223Wrapper);eventSRC20WrapperCreated(addressindexed_token,addressindexed_SRC20Wrapper);mapping(address=>SRC223WrapperToken)publicsrc223Wrappers;// A list of token wrappers. First one is SRC20 origin, second one is SRC223 version.
mapping(address=>SRC20WrapperToken)publicsrc20Wrappers;mapping(address=>address)publicsrc223Origins;mapping(address=>address)publicsrc20Origins;mapping(address=>uint256)publicsrc20Supply;// Token => how much was deposited.
functiongetSRC20WrapperFor(address_token)publicviewreturns(address){returnaddress(src20Wrappers[_token]);}functiongetSRC223WrapperFor(address_token)publicviewreturns(address){returnaddress(src223Wrappers[_token]);}functiongetSRC20OriginFor(address_token)publicviewreturns(address){return(address(src20Origins[_token]));}functiongetSRC223OriginFor(address_token)publicviewreturns(address){return(address(src223Origins[_token]));}functionpredictWrapperAddress(address_token,bool_isSRC20// Is the provided _token a SRC20 or not?
// If it is set as SRC20 then we will predict the address of a
// SRC223 wrapper for that token.
// Otherwise we will predict SRC20 wrapper address.
)viewexternalreturns(address){bytesmemory_bytecode;if(_isSRC20){_bytecode=type(SRC223WrapperToken).creationCode;}else{_bytecode=type(SRC20WrapperToken).creationCode;}bytes32hash=keccak256(abi.encodePacked(bytes1(0xff),address(this),keccak256(abi.encode(_token)),keccak256(_bytecode)));returnaddress(uint160(uint(hash)));}functiontokenReceived(address_from,uint_value,bytesmemory/* _data */)publicoverridereturns(bytes4){require(src223Origins[msg.sender]==address(0),"Error: creating wrapper for a wrapper token.");// There are two possible cases:
// 1. A user deposited SRC223 origin token to convert it to SRC20 wrapper
// 2. A user deposited SRC223 wrapper token to unwrap it to SRC20 origin.
if(src20Origins[msg.sender]!=address(0)){// Origin for deposited token exists.
// Unwrap SRC-223 wrapper.
src20Supply[src20Origins[msg.sender]]-=_value;safeTransfer(src20Origins[msg.sender],_from,_value);SRC223WrapperToken(msg.sender).burn(_value);returnthis.tokenReceived.selector;}// Otherwise origin for the sender token doesn't exist
// There are two possible cases:
// 1. SRC20 wrapper for the deposited token exists
// 2. SRC20 wrapper for the deposited token doesn't exist and must be created.
elseif(address(src20Wrappers[msg.sender])==address(0)){// Create SRC-20 wrapper if it doesn't exist.
createSRC20Wrapper(msg.sender);}// Mint SRC-20 wrapper tokens for the deposited SRC-223 token
// if the SRC-20 wrapper didn't exist then it was just created in the above statement.
src20Wrappers[msg.sender].mint(_from,_value);returnthis.tokenReceived.selector;}functioncreateSRC223Wrapper(address_token)publicreturns(address){require(address(src223Wrappers[_token])==address(0),"ERROR: Wrapper exists");require(!isWrapper(_token),"Error: Creating wrapper for a wrapper token");SRC223WrapperToken_newSRC223Wrapper=newSRC223WrapperToken{salt:keccak256(abi.encode(_token))}();_newSRC223Wrapper.set(_token);src223Wrappers[_token]=_newSRC223Wrapper;src20Origins[address(_newSRC223Wrapper)]=_token;emitSRC223WrapperCreated(_token,address(_newSRC223Wrapper));returnaddress(_newSRC223Wrapper);}functioncreateSRC20Wrapper(address_token)publicreturns(address){require(address(src20Wrappers[_token])==address(0),"ERROR: Wrapper already exists.");require(!isWrapper(_token),"Error: Creating wrapper for a wrapper token");SRC20WrapperToken_newSRC20Wrapper=newSRC20WrapperToken{salt:keccak256(abi.encode(_token))}();_newSRC20Wrapper.set(_token);src20Wrappers[_token]=_newSRC20Wrapper;src223Origins[address(_newSRC20Wrapper)]=_token;emitSRC20WrapperCreated(_token,address(_newSRC20Wrapper));returnaddress(_newSRC20Wrapper);}functionwrapSRC20toSRC223(address_SRC20token,uint256_amount)publicreturns(bool){// If there is no active wrapper for a token that user wants to wrap
// then create it.
if(address(src223Wrappers[_SRC20token])==address(0)){createSRC223Wrapper(_SRC20token);}uint256_converterBalance=ISRC20(_SRC20token).balanceOf(address(this));// Safety variable.
safeTransferFrom(_SRC20token,msg.sender,address(this),_amount);_amount=ISRC20(_SRC20token).balanceOf(address(this))-_converterBalance;src20Supply[_SRC20token]+=_amount;src223Wrappers[_SRC20token].mint(msg.sender,_amount);returntrue;}functionunwrapSRC20toSRC223(address_SRC20token,uint256_amount)publicreturns(bool){require(ISRC20(_SRC20token).balanceOf(msg.sender)>=_amount,"Error: Insufficient balance.");require(src223Origins[_SRC20token]!=address(0),"Error: provided token is not a SRC-20 wrapper.");SRC20WrapperToken(_SRC20token).burn(msg.sender,_amount);safeTransfer(src223Origins[_SRC20token],msg.sender,_amount);returntrue;}functionconvertSRC20(address_token,uint256_amount)publicreturns(bool){if(isWrapper(_token))returnunwrapSRC20toSRC223(_token,_amount);elsereturnwrapSRC20toSRC223(_token,_amount);}functionisWrapper(address_token)publicviewreturns(bool){returnsrc20Origins[_token]!=address(0)||src223Origins[_token]!=address(0);}functionextractStuckSRC20(address_token)external{require(msg.sender==address(0x01000B5fE61411C466b70631d7fF070187179Bbf));safeTransfer(_token,address(0x01000B5fE61411C466b70631d7fF070187179Bbf),ISRC20(_token).balanceOf(address(this))-src20Supply[_token]);}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 and therefore require different conversion approach. This proposal focuses on SRC-20 and SRC-223 upgradeability.
SRC-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. Therefore wrapper-SRC-20 tokens are prone to this problem as they are compatible with the SRC-20 standard. rescueSRC20 function is implemented to address this problem.
Token Converter relies on SRC-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 risk and therefore is not recommended.
There is no reliable token standard introspection method available that could guarantee that a token implements a particular token standard. It is possible to artificially construct a token that will pretend it is a SRC-20 token that implements approve & transferFrom but at the same time implements SRC-223 logic of transferring via transfer function. It can be possible to create a SRC-223 wrapper for this SRC-20-SRC-223 hybrid implementation in the Token Converter. This doesn’t pose any threat for the workflow of the Token Converter itself but it must be taken into account that if a token has SRC-223 wrapper in the Token Converter it does not automatically mean the origin is fully compatible with the SRC-20 standard and methods of introspection must be used to determine the origins compatibility with any existing standard.
Token Converter does not verify the standard of a provided token when it is asked to create a wrapper for it due to the lack of reliable standard introspection method. It is possible to call createSRC20Wrapper function and provide an address of an existing SRC-20 token. The Token Converter will successfully create a SRC-20 wrapper for that SRC-20 original token. It is also possible to create a SRC-223 wrapper for that exact original SRC-20 token. This doesn’t pose any threat to the workflow of the Converter but it must be taken into account that any token regardless of it’s original standard may have up to two wrappers created by the Converter, one for each standard. Any wrapper token must have exactly one origin. It is not possible to create a wrapper for a wrapper.
The Token Converter only holds the original tokens that were deposited during the conversion process and it assumes that tokens do not decay over time and the token balance of the Converter does not decrease on its own. If some token implements burning logic or decaying supply and it may impact the balance of the Converter then the Converter must not be used to deploy an alternative version of that token as it will not be able to guarantee that there is enough tokens for the conversion at any time.