ethereum/EIPs

ERC 1410: Partially Fungible Token Standard

adamdossa opened this issue · 38 comments


eip: ERC-1410
title: Partially Fungible Token Standard (part of the ERC-1400 Security Token Standards)
author: Adam Dossa (@adamdossa), Pablo Ruiz (@pabloruiz55), Fabian Vogelsteller (@frozeman), Stephane Gosselin (@thegostep)
discussions-to: #1411
status: Draft
type: Standards Track
category: ERC
created: 2018-09-13
require: ERC-1066 (#1066)


Simple Summary

A standard interface for organising an owners tokens into a set of partitions.

Abstract

This standard sits under the ERC-1400 (#1411) umbrella set of standards related to security tokens.

Describes an interface to support an owners tokens being grouped into partitions, with each partition being represented by an identifying key and a balance.

Tokens are operated upon at a partition granularity, but data about the overall supply of tokens and overall balances of owners is also tracked.

This standard can be combined with ERC-20 (#20) or ERC-777 (#777) to provide an additional layer of granular transparency as to the behaviour of a token contract on different partitions of a token holders balance.

Motivation

Being able to associate metadata with individual fungible tokens is useful when building functionality associated with those tokens.

For example, knowing when an individual token was minted allows vesting or lockup logic to be implemented for a portion of a token holders balance.

Tokens that represent securities often require metadata to be attached to individual tokens, such as restrictions associated with the share.

Being able to associate arbitrary metadata with groups of tokens held by users is useful in a variety of use-cases. It can be used for token provenance (i.e. recording the previous owner(s) of tokens) or to attach data to a token which is then used to determine any transfer restrictions of that token.

In general it may be that whilst tokens are fungible under some circumstances, they are not under others (for example in-game credits and deposited balances). Being able to define such groupings and operate on them whilst maintaining data about the overall distribution of a token irrespective of this is useful in modelling these types of assets.

Having a standard way to identify groupings of tokens within an overall balance helps provides token holders transparency over their balances.

Rationale

A Partially-Fungible Token allows for attaching metadata to a partial balance of a token holder. These partial balances are called partitions and are indexed by a bytes32 _partition key which can be associated with metadata on-chain or off-chain.

The specification for this metadata, beyond the existence of the _partition key to identify it, does not form part of this standard. The token holders address can be paired with the partition to use as a metadata key if data varies across token holders with the same partition (e.g. a "restricted" partition may be associated with different lock up dates for each token holder).

For an individual owner, each token in a partition therefore shares common metadata.

Token fungibility includes metadata so we have:

  • for a specific user, tokens within a given partition are fungible
  • for a specific user, tokens from different partitions may not be fungible

Note - partitions with the same bytes32 key across different users may be associated with different metadata depending on the implementation.

Backwards Compatibility

This standard is un-opinionated on ERC-20 vs. ERC-777. It can be easily combined with either standard, and we expect this to usually be the case. We don't define the standard token view functions (name, symbol, decimals) as a consequence.

In order to remain backwards compatible with ERC-20 / ERC-777 (and other fungible token standards) it is necessary to define what partition or partitions are used when a transfer / send operation is executed (i.e. when not explicitly specifying the partition). However this is seen as an implementation detail (could be via a fixed list, or programatically determined). One option is to simple iterate over all partitionsOf for the token holder, although this approach needs to be cognisant of block gas limits.

Specification

Token Information

balanceOf

Aggregates a token holders balances across all partitions. Equivalent to balanceOf in the ERC-20/777 specification.

MUST count the sum of all partition balances assigned to a token holder.

function balanceOf(address _tokenHolder) external view returns (uint256);

balanceOfByPartition

As well as querying total balances across all partitions through balanceOf there may be a need to determine the balance of a specific partition.

For a given token holder, the sum of balanceOfByPartition across partitionsOf MUST be equal to balanceOf.

function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256);

partitionsOf

A token holder may have their balance split into several partitions (partitions) - this function will return all of the partitions that could be associated with a particular token holder address. This can include empty partitions, but MUST include any partitions which have a non-zero balance.

function partitionsOf(address _tokenHolder) external view returns (bytes32[]);

totalSupply

Returns the total amount of tokens issued across all token holders and partitions.

MUST count all tokens tracked by this contract.

function totalSupply() external view returns (uint256);

Tokens Transfers

Token transfers always have an associated source and destination partition, as well as the usual amounts and sender / receiver addresses.

As an example, a permissioned token may use partition metadata to enforce transfer restrictions based on:

  • the _partition value
  • any additional data associated with the _partition value (e.g. a lockup timestamp that may be associated with _partition)
  • any details associated with the sender or receiver of tokens (e.g. has their identity been established)
  • the amount of tokens being transferred (e.g. does it respect any daily or other period-based volume restrictions)
  • the _data parameter allows the caller to supply any additional authorisation or details associated with the transfer (e.g. signed data from an authorised entity who is permissioned to authorise the transfer)

Other use-cases include tracking provenance of tokens by associating previous holders with destination partitions.

transferByPartition

This function MUST throw if the transfer of tokens is not successful for any reason.

When transferring tokens from a particular partition, it is useful to know on-chain (i.e. not just via an event being fired) the destination partition of those tokens. The destination partition will be determined by the implementation of this function and will vary depending on use-case.

The function MUST return the bytes32 _partition of the receiver.

The bytes _data allows arbitrary data to be submitted alongside the transfer, for the token contract to interpret or record. This could be signed data authorising the transfer (e.g. a dynamic whitelist), or provide some input for the token contract to determine the receivers partition.

This function MUST emit a TransferByPartition event for successful transfers.

function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32);

operatorTransferByPartition

Allows an operator to transfer security tokens on behalf of a token holder, within a specified partition.

This function MUST revert if called by an address lacking the appropriate approval as defined by isOperatorForPartition or isOperator.

This function MUST emit a TransferByPartition event for successful token transfers, and include the operator address.

The return data is interpreted consistently with transferByPartition.

function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32);

canTransferByPartition

Transfers of partially fungible tokens may fail for a number of reasons, relating either to the token holders partial balance, or rules associated with the partition being transferred.

The standard provides an on-chain function to determine whether a transfer will succeed, and return details indicating the reason if the transfer is not valid.

These rules can either be defined using smart contracts and on-chain data, or rely on _data passed as part of the transferByPartition function which could represent authorisation for the transfer (e.g. a signed message by a transfer agent attesting to the validity of this specific transfer).

The function will return both a ESC (Ethereum Status Code) following the EIP-1066 standard, and an additional bytes32 parameter that can be used to define application specific reason codes with additional details (for example the transfer restriction rule responsible for making the transfer operation invalid).

It also returns the destination partition of the tokens being transferred in an analogous way to transferByPartition.

function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32);

Operators

Operators can be authorised by individual token holders for either all partitions, or a specific partition.

  • a specific token holder and all partitions (authorizeOperator, revokeOperator, isOperator)
  • a specific token holder for a specific partition (authorizeOperatorByPartition, revokeOperatorByPartition, isOperatorForPartition)

authorizeOperator

Allows a token holder to set an operator for their tokens across all partitions.

MUST authorise an operator for all partitions of msg.sender

This function MUST emit the event AuthorizedOperator every time it is called.

function authorizeOperator(address _operator) external;

revokeOperator

Allows a token holder to revoke an operator for their tokens across all partitions.

NB - it is possible the operator will retain authorisation over this token holder and some partitions through authorizeOperatorByPartition.

MUST revoke authorisation of an operator previously given for all partitions of msg.sender

This function MUST emit the event RevokedOperator every time it is called.

function revokeOperator(address _operator) external;

isOperator

Returns whether a specified address is an operator for the given token holder and all partitions.

This should return TRUE if the address is an operator under any of the above categories.

MUST query whether _operator is an operator for all partitions of _tokenHolder.

function isOperator(address _operator, address _tokenHolder) external view returns (bool);

authorizeOperatorByPartition

Allows a token holder to set an operator for their tokens on a specific partition.

This function MUST emit the event AuthorizedOperatorByPartition every time it is called.

function authorizeOperatorByPartition(bytes32 _partition, address _operator) external;

revokeOperatorByPartition

Allows a token holder to revoke an operator for their tokens on a specific partition.

NB - it is possible the operator will retain authorisation over this token holder and partition through either defaultOperatorsByPartition or defaultOperators.

This function MUST emit the event RevokedOperatorByPartition every time it is called.

function revokeOperatorByPartition(bytes32 _partition, address _operator) external;

isOperatorForPartition

Returns whether a specified address is an operator for the given token holder and partition.

This should return TRUE if the address is an operator under any of the above categories.

function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool);

Interface

/// @title ERC-1410 Partially Fungible Token Standard
/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec

interface IERC1410 {

    // Token Information
    function balanceOf(address _tokenHolder) external view returns (uint256);
    function balanceOfByPartition(bytes32 _partition, address _tokenHolder) external view returns (uint256);
    function partitionsOf(address _tokenHolder) external view returns (bytes32[]);
    function totalSupply() external view returns (uint256);

    // Token Transfers
    function transferByPartition(bytes32 _partition, address _to, uint256 _value, bytes _data) external returns (bytes32);
    function operatorTransferByPartition(bytes32 _partition, address _from, address _to, uint256 _value, bytes _data, bytes _operatorData) external returns (bytes32);
    function canTransferByPartition(address _from, address _to, bytes32 _partition, uint256 _value, bytes _data) external view returns (byte, bytes32, bytes32);    

    // Operator Information
    function isOperator(address _operator, address _tokenHolder) external view returns (bool);
    function isOperatorForPartition(bytes32 _partition, address _operator, address _tokenHolder) external view returns (bool);

    // Operator Management
    function authorizeOperator(address _operator) external;
    function revokeOperator(address _operator) external;
    function authorizeOperatorByPartition(bytes32 _partition, address _operator) external;
    function revokeOperatorByPartition(bytes32 _partition, address _operator) external;

    // Issuance / Redemption
    function issueByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _data) external;
    function redeemByPartition(bytes32 _partition, uint256 _value, bytes _data) external;
    function operatorRedeemByPartition(bytes32 _partition, address _tokenHolder, uint256 _value, bytes _operatorData) external;

    // Transfer Events
    event TransferByPartition(
        bytes32 indexed _fromPartition,
        address _operator,
        address indexed _from,
        address indexed _to,
        uint256 _value,
        bytes _data,
        bytes _operatorData
    );

    // Operator Events
    event AuthorizedOperator(address indexed operator, address indexed tokenHolder);
    event RevokedOperator(address indexed operator, address indexed tokenHolder);
    event AuthorizedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);
    event RevokedOperatorByPartition(bytes32 indexed partition, address indexed operator, address indexed tokenHolder);

    // Issuance / Redemption Events
    event IssuedByPartition(bytes32 indexed partition, address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData);
    event RedeemedByPartition(bytes32 indexed partition, address indexed operator, address indexed from, uint256 amount, bytes operatorData);

}

Operators may not be required when using this ERC. I would suggest the functionality relating to operators be separated in the interface and described as optional to support use cases where it is not required for the functionality of an SFT.

The implication of this is that the SentByTranche and BurnedByTranche events may need separate operator-specific versions to account for the additional data required by having operators.

e.g. SentByTrancheOperator and BurnedByTrancheOperator

setDefaultTranche takes as an argument multiple tranches... That doesn't make sense to me. You shouldn't be able to specify multiple "defaults" because a default means only one choice is delegated. Is the intention to allow the user to specify an order to the which the tranches should resolve? (i.e. this one first, then that one, then that one over there, ... and finally, this one!)

If that is the case, I would use some other terminology because that is confusing.


Also, I think that the "multiple send" variants of the functions specified by the standard (sendByTranches and operatorSendByTranches) should perhaps be optional or suggested. It has not been typical to a standard to define a mechanism for multiple transfers as there may be multiple ways one might like to perform it.

@fubuloubu

re. setDefaultTranche

My thinking was that you may want to be able to transfer from multiple tranches when using the send / transfer functions defined in ERC777 / ERC20.

For example, you could define a users default tranches as tranchesOf(user) which would effectively make the token transparent from an ERC777 / ERC20 perspective (with the exception of any additional logic used under the covers by sendByTranche to manage transfers).

This does however imply that the ordering of defaultTranches is important, i.e.:

Suppose Alice calls transfer(Bob, 100) with defaultTranches being [tranche_1, tranche_2], then this could be interpreted as:

  1. transfer up to 100 tokens from tranche_1
  2. transfer any remaining tokens (100 - (number of tokens submitted in 1.)) from tranche_2
  3. if it wasn't possible to transfer a total of 100 tokens, then revert

sendByTranches could then be interpreted in the same way, although it would then make sense to change the signature from:
function sendByTranches(bytes32[] _tranches, address[] _tos, uint256[] _amounts, bytes _data) external returns (bytes32[]);
to:
function sendByTranches(bytes32[] _tranches, address _to, uint256 _amount, bytes _data) external returns (bytes32);
so that it is consistent with this.

This opens up another question which is whether the sendByTranche functions should return (bytes32[], uint256[]) to cover the case that the token contract transfers the balances into multiple tranches for the receiver so should return the set of tranches, and the balances transferred into each.

These "multiple send" functions are then a bit different to the more common batchTransfer type functions as they are still a single atomic action (transferring tokens from msg.sender to another user) just that the source tranches and corresponding destination tranches may not be singular.

Interested in your thoughts on the above! Will think about the best way to clarify the operators within this EIP.

Yeah, the ordering thing I think is very important if you are attempting to define complex transfers within the standard. If you left it as one default, that would be equally as useful if the transfers were more straightforward.

When I was originally thinking of my own PFT use case, I was considering the "tranches" to be an enumeration, such that I could specify a global order within the standard by just numbering them in that way. It makes sense that different users may want to specify different orders though, it just becomes much more difficult to deal with.

Removing the ability to send multiple tranches is an option that may be useful. It makes the underlying implementation a bit simpler to deal with, although clients would then need to submit multiple requests if moving from different tranches. But, in my opinion, clearer is better.

Lastly, the event SentByTranche does not compile with 0.4.25. You can only have 3 indexed arguments to an event. It seems like you are attempting to define a "conversion" inside that event, basically that you can send from one tranch and into another. I would think conversions would have more complex rules governing when they are appropriate to conduct. A separate event may be better to convey this.

Another potential use case: Coin voting

A token could have a voting flow that would taint coins already used for voting, "locking" them against the possibility of future voting by disallowing re-use of tainted tokens until the voting was over. This would be an excellent tool against concerns arising from non-locking token votes, where users can simply transfer their tokens to a new address to get around vote sanctions on token holders.

@ALL We have scheduled a second community call for 11am EST, Tuesday, October 2nd.

We will try using Zoom this time as we hit the 25 attendee limit on Google Hangouts on the last call.

Ethereum Magicians thread for the call is at:
https://ethereum-magicians.org/t/community-call-erc1400-feedback-discussion-questions/1475
we will make sure this stays updated.

Please use the HackMD document linked here to add any agenda items and your details if you're able to attend.

@fubuloubu - re. tranches as an enumeration - I’ve been thinking of them more like primary keys for metadata. So they could be something like gold, silver & bronze. Alternatively if you have more than one type of metadata you can still partition by tranches by using a hash of the metadata types as the tranche name (e.g. hash(locked, voting), hash(unlocked, voting)) - this way if a token holder receives tokens with the same metadata types, they’ll continue to be grouped together.

I wrote a medium article explaining a bit more along these lines:
https://blog.polymath.network/what-do-in-game-credits-plasma-cash-and-security-tokens-have-in-common-1b490843ab85

I cleaned up the:

function sendByTranches(bytes32[] _tranches, address _to, uint256[] _amounts, bytes _data) external returns (bytes32[]);
function operatorSendByTranches(bytes32[] _tranches, address _from, address _to, uint256[] _amounts, bytes _data, bytes _operatorData) external returns (bytes32[]);

to take multiple tranche values but a single destination address.

I split out the change of tranche from the SentByTranche event and de-indexed the operator (which fields are indexed kind of feels like an implementation detail).

I moved the redeemByTranche and operatorRedeemByTranche up to the security token standard so as to not specify minting / burning mechanics at the PFT level.

For simplicity's sake, can the base standard have sendByTranche (singular send) as the required functionality for specifying a specific send pattern from one particular tranche, and have sendByTranches (multiple send) be optional syntax. Different implementations might have different assumptions on "priority" for sending by tranches, and the multiple send can be a wrapper around the singular sends, or implement alternative priority mechanisms as needed. This way, a user always has access to a simple function to move some amount of a particular tranche to a particular person without being locked in to how that contract leverages it's particular priority scheme.

For example, you might want to send amounts from tranches A, B, and C, but they have complicated inter-dependancies leading you to only send one at a time. The basic "singular send" would let you do that independantly if the multiple send was failing due to those dependencies. I believe strongly the basic sending mechanism be as atomic as possible without involving too much complexity in the priority scheme of handling multiple tranches.

jllaw commented

Interesting dialog. I'm not technical, so I can't follow the technical discussion fully, but happy to contribute knowledge for my experience in finance and in law.

jllaw commented

@adamdossa Would #1410 be helpful for distinguishing between affiliate and non-affiliate tokens? Also, are there mechanisms to expand or contract a tranche, or convert one tranche into another tranche? Apologies, if this should be clear from the code; you know I'm not technically inclined.

@jllaw - Yep - you could distinguish between affiliate / non-affiliate tokens using tranches (i.e. having a tranche called affiliate and a tranche called non-affiliate, or by associating these tags with whatever tranche names you choose).

It is possible to move tokens between tranches (e.g. from non-affiliate to affiliate) and this logic can be determined on-chain (in the sendByTranche functions) or use off-chain data (bytes _data) to drive the change. If you wanted to move tokens between tranches for a single user (rather than as a result of transferring tokens between users) that user could just transfer tokens to themselves with the above logic.

What’s the point of having the standards so specific they are practically designed to work for specific platforms? You might want to have a look at ERC-1462
(https://github.com/AtlantPlatform/BaseSecurityToken/). It’s a really general standard for security tokens, unlike the proprietary methods 1400 and others trying to pump their own projects and hidden agendas. The new standard for security tokens should support as many use-cases as possible, so all teams can start embracing it such as ERC 20.

Well, I think that's unfair. This team has taken advice to segregate different parts of their proposal that had alternative use cases (like this PFT spec) with the goal of advancing other possible use cases.

I know I have been interested in a partially fungible token spec exploring ideas I've had with reputation tokens, which may be held in different contexts.

@morpheus499 we don't have a hidden agenda beyond trying to push forward the conversation on standards for security tokens (and other more sophisticated token types). I have looked at ERC 1462 and there is certainly an argument for a simpler standard which we've discussed openly on our open community calls.

My view is that it is better to try and design a future proofed standard that acknowledges that tokens can be more complex than what is easily represented with ERC20 - some of the potential use-cases for more sophisticated tokens (outside of security tokens) are in the article:
https://blog.polymath.network/what-do-in-game-credits-plasma-cash-and-security-tokens-have-in-common-1b490843ab85

Being able to check the validity of transactions before executing them (which is covered by #1462) is certainly one part of the puzzle, but IMO we also need the other parts (differentiated ownership, ability to inject off-chain data into transfer operations and so on).

Hey all, I had a great chat with @thegostep today. I think ERC20 compatibility is really important for many reasons, so I have a proposal for an implementation that preserves this past the idea of “default tranches”.

    contract ERC1410v2Tranche extends ERC20 {
       function getTrancheId() public view returns (bytes32 trancheId);
    }
    
    contract ERC1410v2 extends ERC1410 {
       mapping (bytes32 => ERC1410v2Tranche) tranches;
       
       constructor() public {
          tranches[keccak256("gold")] = new ERC1410v2Tranche();
          tranches[keccak256("silver")] = new ERC1410v2Tranche();
       }
    
        function balanceOfByTranche(bytes32 _tranche, address _tokenHolder) external view returns (uint256) {
        require(tranches[_tranche] != address(0));
        return tranches[_tranche].balanceOf(_tokenHolder);
        }
      // ...
    }

Downsides:

  • higher deployment costs
  • larger code complexity (if someone is writing their own)
    • mitigated by having good documentation and SDKs. This is not as complicated as, say, ERC 735.
  • infeasible with micro-tranches (e.g. if there are hundreds or thousands of tranches)

Upsides:

  • backwards compatibility with ERC20
    • what if someone wants to use a governance platform built on ERC20? (aragon?)
    • what if someone wants to use a wallet built on ERC20? (metamask?)
    • what if someone wants to see transaction history through an ERC20 browser? (etherscan?)
    • There are lots of benefits to being backwards compatible.
  • Easier to reason about different transfer restrictions per tranche, e.g. affiliate vs non-affiliate tranches
  • Modularity is generally a good thing
  • Upgradeable tranches
  • Easier to add a new tranche with different logic

As part of the Ethereum Magicians hosted Council of Prague before DevCon some of us will be attending the Token Ring (Monday 29th October) to discuss ERC 1400 / security token standards.

Some links to sign up for this and mark your attendance are:
https://github.com/ethereum-magicians/scrolls/wiki/Council-of-Prague-Agenda
https://www.picatic.com/ethmagicians-prague-2018
https://hackmd.io/DaJhrasLQteUk3IwX5bQAg#7-Token-Ring

I added a reference implementation for ERC 1410 - still some improvement to be done to factor out common base classes or make a robust implementation, but hopefully good reference for anyone looking at these standards.
https://github.com/SecurityTokenStandard/EIP-Spec/blob/master/contracts/PartialFungibleToken.sol

ijxy commented

Any progress on this, or further thoughts?

I am trying to see the utility of the proposed tranche idea but I must admit I'm not convinced yet. Mostly I am wondering why we wouldn't simply use multiple (security) tokens (i.e., one per tranche) -- why must they collectively be stored in one place?

The securities that I'm aware of using a tranche system (CDOs, corporate bonds) do so because each "tranche" has different properties; really, they are different securities and trade as such (i.e., there is a market for senior bonds and a separate market for junior bonds, etc).

Therefore my gut instinct is that the most basic security token is actually a single tranche, which is probably closer to ERC1462 (#1462).

In a word, convertibility. With multiple tokens, you would have to design a complicated atomic swap, mint/burn or locking system to trade vesting to unlimited shares, and accurately represent the restrictions on tradability on multiple tokens that essentially represent the same thing. If they are all subcategories of the same token, that logic is much simpler to represent. I am not a "security tokens" guy, but that much makes sense to me.

This proposal also has wider utility. Allowing subcategories for a general token (in a non-security use case) would allow different ideas like locking voting tokens temporarily or making allocations to something like staking without delegating control to a smart contract you don't necessarily trust. That little bit of state helps have additional logic for transferability be much easier to implement.

I do still wish tranche was called category or something to reflect these broader use cases.

That's not it at all. Tranche is probably a bad word, because it implies that the securities in the different tranches are different or have different rights -like the tranches of an MBS. However, that is NOT the purpose of this particular data structure. This data structure describes securities that are the SAME security, but issued on different days, or in different jurisdictions. The most important use case is to implement the rules of US Regulation D and Regulation S. For example, I can issue the SAME common stock in many different events. The stock that I sell to US investors has a one year lockup. So, stock issued on each date can go into a separate tranche that tracks the origination date. There are many ways to implement lockup, so this tranche data structure is not required. However, it IS required for the stock that I sell to non-US investors under Reg S. This is covered by a rule that says the stock cannot be sold back to US investors until one year has passed. So, we have to track each share of stock that was issued on that date (using a tranche) to see if those shares have been outstanding for a year. It also happens that securities jump from Reg D into Reg S, and the date of issue follows them, through purchase by a large fund that is exempt from Reg D under rule 144a. You might end up with 20 different issuance dates and jurisdictions. For most purposes, we don't care about the different tranches. We only care about the full set of outstanding securities for voting, and distribution, and trade volume restrictions. And, in most cases these tranches would collapse to the same security after a year or so. Keeping track of 20 different tokens in that case, and adding them up, and recombining them, would be silly.

This type of tranche would describe different issuances of the same security. As a corollary, if you have several securities from one issuer, but with different rights, I think you should make different tokens.

ijxy commented

@fubuloubu @zingleton Thanks for the detailed responses. The examples given make a lot of sense - especially the point about lock-up times/issuance dates/recombining being a nightmare with separate tokens. I can definitely see advantages to this approach.

Since it seems to be a potential source of confusion (at least in my case), perhaps the name should be changed from tranche to something that doesn't already have a well-understood meaning in the context of a security. The suggestion of category seems reasonable to me. collection, issue and I'm sure many others could also work. Obviously this standard isn't just about securities as has been pointed out, but it is likely a major application and it makes sense to reduce the potential for confusion where it is unnecessary, IMO.

ijxy commented

@adamdossa What do you think about changing tranche to something else that doesn't already have an established meaning in the context of securities?

Also, one point. Again I am not an expert on securities at all, but focusing on US requirements is too narrowly focused to define a standard on. ERC standards are all about giving developers tools to build different use cases with, one of the main reasons I pushed back against the original monolithic proposal.

As noted, the fact is you really have multiple options for implementing a "security token" using this ERC, other ERCs (like using multiple ERC20s), or any combination of the above. It depends on what makes sense for how you're implementing it.


A lot of people think ERC proposals are somehow a "stamp of legitimacy", when in reality it's just an idea. Unlike core protocol EIPs, ERCs just serve as a rallying point for people to adopt them. By focusing on something more broadly useful, that means faster adoption of the ERC, which means it's "legitimacy" increases with each adoption.

@ParkinsonJ I want to point you to this WIP update to the 1400 proposals. It includes renaming 'tranches' to 'partitions' in order to remove the association with MBS and CDOs.

SecurityTokenStandard/EIP-Spec#6

ijxy commented

@thegostep Thank you, I shall take a look at it. Sounds like a positive step!

@fubuloubu I think that's a fair point, although as you've pointed out the partitioning is relevant to a berth of applications outside of US securities.

Regarding "legitimacy", I'm not sure I agree in this particular context if only because a securities platform is not very useful if it only works with a small fraction of security tokens. It's utility that is driving the push for a standard - utility for platform builders, token designers and ultimately end users (who then won't need to use different tokens on different platforms, etc).

Oh yeah, don't get me wrong, the securities use case is a really big driver of adoption, in the same way it has also driven the push for defining multiple, "competing" standards, basically all at the same time.

I'm saying that non-security use cases can be a way to help engender adoption of a particular standard, as you have defined something more generally useful than if it were focused more towards one niche of the community.

Following feedback from the token ring, community calls, GitHub & the Telegram group, I have made the following changes:

Motivation

  • Clarify use of tranches / partitions primarily as a mechanism to add transparency to the non-fungible subsets of a token holders balance.
  • Ease adoption by decomposing the security token standard into a library of related and interoperable standards.
  • Move to ERC-20 from ERC-777 as a base.
  • Provide a route to adoption / implementation which is more incremental.

Updates

  • Modify ERC-1410 to make it un-opinionated on ERC-20 vs. ERC-777 rather than a direct descendant of ERC-777.
  • Rename “tranches” to “partitions”
  • Create ERC-1594 which splits out the non-partition related core security token functionality.
  • Create ERC-1643 which splits out document management functionality.
  • Create ERC-1644 which splits out controller operation functionality.
  • Modify ERC-1594 be based on ERC-20 rather than ERC-777.
  • Modify ERC-1400 to be an umbrella of ERC-1410, ERC-1594, ERC-1643 and ERC-1644 with a few additional constraints to make these standards interoperate.

The changes in this update along with some related conversation are collected in a GitHub PR at:
SecurityTokenStandard/EIP-Spec#6.

@adamdossa Spec says isOperatorFor but interface is isOperator. I guess the isOperator is the right name as it follows the convention of not using For or By without Partition in the name, right?

Hi @adamdossa,

Kindly help me on below points.

  1. According to transferByPartition

This function MUST throw if the transfer of tokens is not successful for any reason.
When transferring tokens from a particular partition, it is useful to know on-chain (i.e. not just via an event being fired) the destination partition of those tokens. The destination partition will be determined by the implementation of this function and will vary depending on use-case.

The function MUST return the bytes32 _partition of the receiver.

Does that mean, when we sends tokens from a perticular _partition to receiver, it generates a new _partition key? If thats true then what if the receiver partition is already holding the partition tokens (simliar attribute of tokens)

  1. How should we generated the _partition key? can we genearte it creating a hash by passing certain attributes?

hiya @adamdossa:

"Modify ERC-1400 to be an umbrella of ERC-1410, ERC-1594, ERC-1643 and ERC-1644 with a few additional constraints to make these standards interoperate."

Could you please elaborate on this or drop some #umbrella #constraint markers into the specification itself. I'm interested to know what are the sticking points for these standards to interoperate. It sounds like good work, kudos!

@adamdossa Spec says isOperatorFor but interface is isOperator. I guess the isOperator is the right name as it follows the convention of not using For or By without Partition in the name, right?

Agreed - just updated to fix this - thanks for spotting.

Hi @adamdossa,

Kindly help me on below points.

  1. According to transferByPartition

This function MUST throw if the transfer of tokens is not successful for any reason.
When transferring tokens from a particular partition, it is useful to know on-chain (i.e. not just via an event being fired) the destination partition of those tokens. The destination partition will be determined by the implementation of this function and will vary depending on use-case.

The function MUST return the bytes32 _partition of the receiver.

Does that mean, when we sends tokens from a perticular _partition to receiver, it generates a new _partition key? If thats true then what if the receiver partition is already holding the partition tokens (simliar attribute of tokens)

  1. How should we generated the _partition key? can we genearte it creating a hash by passing certain attributes?

Partition keys can be anything - for example, in our current implementation we are using LOCKED and UNLOCKED as the only partitions we support. One concern with this approach though that came up during implementation is what happens if transferred tokens end up in two (or more) partitions. We took the view that this returned partition key is an "indicative" value. A more precise thing to do might be to return a list of [amount, partition] pairs which fully describe the destination partitions of the transferred tokens.

hiya @adamdossa:

"Modify ERC-1400 to be an umbrella of ERC-1410, ERC-1594, ERC-1643 and ERC-1644 with a few additional constraints to make these standards interoperate."

Could you please elaborate on this or drop some #umbrella #constraint markers into the specification itself. I'm interested to know what are the sticking points for these standards to interoperate. It sounds like good work, kudos!

There weren't a lot of constraints - mainly just making sure that operators etc. were treated consistently across the different standards. Ideally every standard in the ERC1400 umbrella would be able to be combined with every other standard which does mean that they need to be consistent if they are using concepts which are defined in other standards (for example operators).

"Operators can be authorised at by individual token holders for either all partitions, or a specific partition."

^typo

Re: Operator Aggregation. It seems to be something important where it concerns trust networks —for example, the Circles design (as well as others, BrightId, etc.)

In any case, any old list of operators would get the job done, I suppose. With an aggregate model, however, we would have more abstraction of details away from the organisations that will ostensibly be using these things — or away from the user— which is a basic side-effect of employing such standards per se.

Fixed typo - thanks.

Do you have a link to some more details on the aggregate operator model?

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.