Skip to content
This repository has been archived by the owner on Mar 18, 2019. It is now read-only.

ERC1400 Support

Brecht Devos edited this page Nov 24, 2018 · 14 revisions

ERC1400 Support

Support for security tokens was added to the protocol by implementing support for the ERC1400 Security Token Standard. This standard builds on top of the ERC1410 Partially Fungible Token Standard and ERC777.

ERC1400 Standard Overview

The main difference (for Loopring) with ERC20 is the introduction of tranches to support partially fungible tokens and the extra limitations on token transfers.

Tranches

Tranches allow attaching metadata to a certain group of tokens. This allows the token to implement additional logic for these groups. How (and if) tokens can switch tranches is completely left up to the specific implementation of the token.

Tokens are always transferred from a specified tranche. ERC1400 allows us to check the balance for every tranche individually by using balanceOfTranche.

Operators

Operators are allowed to send security tokens on behalf of a token holder. Unlike ERC20, we do not have to specify a specific allowance amount.

Operators can be authorised for:

  • all token holders and tranches (defaultOperators inherited from ERC777)
  • all token holders for a specific tranche (defaultOperatorsByTranche)
  • all tranches (current and future) for a specific token holder (isOperatorFor inherited from ERC777)
  • a specific tranche for a specific token holder (isOperatorForTranche)

Token Transfers

Operators can transfer tokens using operatorSendTranche:

function operatorSendTranche(
    bytes32 tranche,
    address from,
    address to,
    uint256 amount,
    bytes data,
    bytes operatorData
    ) 
    external
    returns (byte errorCode, bytes32 destTranche)

Next to the expected parameters, we also have data and operatorData. data can be interpreted by every token differently. For example, it can be used to verify the token transfer using off-chain data or it can contain the desired destination tranche. operatorData is data the operator can send which gets logged in the transfer event.

Note that the destination tranche is not a function parameter, but a return value. There is no standard way to switch tranches for tokens. Tokens can use the data parameter to implement a feature like this if desired (this is done in our ERC1400 test tokens to test transferring tokens between tranches).

ERC1400 allows tokens to impose many limitations on token transfers, above and beyond the simple balance checking done in ERC20. For this reason, ERC1400 allows us to verify if a token transfer will succeed by calling canSend, which takes the same input arguments as operatorSendTranche (except for the unneeded operatorData):

function canSend(
    address from,
    address to,
    bytes32 tranche,
    uint256 amount,
    bytes data
    )
    external
    view
    returns (byte errorCode, bytes32 description, bytes32 destTranche)

canSend not only allows us to check if a token transfer would succeed, it also gives us the destination tranche if we would transfer the tokens using the given data. Successful errorCodes are given below:

0xA0 	Transfer Verified - Unrestricted
0xA1 	Transfer Verified - On-Chain approval for restricted token
0xA2 	Transfer Verified - Off-Chain approval for restricted token

If another error code is returned than the transfer was blocked by the token contract.

The description return value is a more detailed, but token specific, error code than the standardized errorCode.

ERC1400 Tokens in Protocol Overview

New Order data

TokenType tokenTypeS;
TokenType tokenTypeB;
TokenType tokenTypeFee;

bytes32 trancheS;
bytes32 trancheB;

bytes transferDataS;

All the data above is signed by the order owner. We cannot trust the miner to send the correct token type because the same token address can implement different token standards which can behave differently.

We expect the order to define the source tranche when selling an ERC1400 token and we expect the order to define the destination tranche when buying an ERC1400 token. There is no trancheFee defined in the order because we assume (and will enforce) feeToken always points to a fully fungible token.

Protocol behavior

Ring settlement

  • Check if the TradeDelegate contract is an operator for the order owner and trancheS using isOperatorForTranche
  • Check the available balance in the tranche using balanceOfTranche
  • Do the ring settlement calculations like usual
  • Check if we can do the necessary token transfer to the buyer with the actual transfer amounts and data using canSend. canSend also returns the destination tranche which we use to make sure it matches with the expected tranche of the buyer
  • Transfer the tokens in the same TradeDelegate contract as the ERC20 token transfers

Protocol imposed limitations on ERC1400 tokens

Because of the many limitations that can be imposed on token transfers with security tokens we do not allow paying fees in ERC1400 tokens. So feeToken cannot be an ERC1400 token and tokenSFeePercentage/tokenBFeePercentage cannot be non-zero when used for an ERC1400 token.

If there is margin that needs to be paid in an ERC1400 token it does not get paid out to the miner, the order owner selling the ERC1400 token keeps the margin.

Caution is needed when calling submitRings using a token address with multiple token types for the same owner

Let's say MyToken implements the ERC20 and ERC1400 token standard. An order owner uses MyToken as an ERC20 token AND as an ERC1400 token. We check the balances and see that the available ERC20 balance is 10 MyToken and the available ERC1400 balance on tranche keccak256("default") is also 10 MyToken. We do an ERC1400 transfer spending 10 MyTokens and then we try to do an ERC20 transfer also spending 10 MyTokens. This last transfer could fail because the tokens transferred using the ERC20 interface could actually use the keccak256("default") tranche tokens internally, which we just spent in the ERC1400 transfer. The protocol cannot easily know this so cases like this should be avoided to make sure submitRings does not fail.

The protocol could disallow this if needed.