New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC-1404: Simple Restricted Token Standard #1404

Open
masonicGIT opened this Issue Sep 12, 2018 · 23 comments

Comments

Projects
None yet
@masonicGIT

masonicGIT commented Sep 12, 2018

eip title authors status discussions-to type category created
1404 Simple Restricted Token Standard Ron Gierlach <@rongierlach>, James Poole <@pooleja>, Mason Borda <@masonicGIT>, Lawson Baker <@lwsnbaker> Draft https://github.com/simple-restricted-token/simple-restricted-token/issues Standards ERC 2018-07-27

Simple Restricted Token Standard

Simple Summary

A simple and interoperable standard for issuing tokens with transfer restrictions. The following draws on input from top issuers, law firms, relevant US regulatory bodies, and exchanges.

Abstract

Current ERC token standards have provided the community with a platform on which to develop a decentralized economy that is focused on building Ethereum applications for the real world. As these applications mature and face consumer adoption, they begin to interface with corporate governance requirements as well as regulations. They must not only be able to meet corporate and regulatory requirements but must also be able to integrate with technology platforms underpinning their associated businesses. What follows is a simple and extendable standard that seeks to ease the burden of integration for wallets, exchanges, and issuers.

Motivation

Token issuers need a way to restrict transfers of ERC-20 tokens to be compliant with securities laws and other contractual obligations. Current implementations do not address these requirements.

A few emergent examples:

  • Enforcing Token Lock-Up Periods
  • Enforcing Passed AML/KYC Checks
  • Private Real-Estate Investment Trusts
  • Delaware General Corporations Law Shares

Furthermore, standards adoption amongst token issuers has the potential to evolve into a dynamic and interoperable landscape of automated compliance.

The following design gives greater freedom / upgradability to token issuers and simultaneously decreases the burden of integration for developers and exchanges.

Additionally, we see fit to provide a pattern by which human-readable messages may be returned when token transfers are reverted. Transparency as to why a token's transfer was reverted is of equal importance to the successful enforcement of the transfer restriction itself.

A widely adopted standard for detecting restrictions and messaging errors within token transfers will highly convenience the exchanges, wallets, and issuers of the future.

Specification

The ERC-20 token provides the following basic features:

contract ERC20 {
  function totalSupply() public view returns (uint256);
  function balanceOf(address who) public view returns (uint256);
  function transfer(address to, uint256 value) public returns (bool);
  function allowance(address owner, address spender) public view returns (uint256);
  function transferFrom(address from, address to, uint256 value) public returns (bool);
  function approve(address spender, uint256 value) public returns (bool);
  event Approval(address indexed owner, address indexed spender, uint256 value);
  event Transfer(address indexed from, address indexed to, uint256 value);
}

The ERC-1404 standard builds on ERC-20's interface, adding two functions:

contract ERC1404 is ERC20 {
  function detectTransferRestriction (address from, address to, uint256 value) public view returns (uint8);
  function messageForTransferRestriction (uint8 restrictionCode) public view returns (string);
}

The logic of detectTransferRestriction and messageForTransferRestriction are left up to the issuer.

The only requirement is that detectTransferRestriction must be evaluated inside a token's transfer and transferFrom methods.

If, inside these transfer methods, detectTransferRestriction returns a value other than 0, the transaction should be reverted.

Rationale

The standard proposes two functions and an event on top of the ERC-20 standard. Let's discuss the rationale for each.

  1. detectTransferRestriction - This function is where an issuer enforces the restriction logic of their token transfers. Some examples of this might include, checking if the token recipient is whitelisted, checking if a sender's tokens are frozen in a lock-up period, etc. Because implementation is up to the issuer, this function serves solely to standardize where execution of such logic should be initiated. Additionally, 3rd parties may publicly call this function to check the expected outcome of a transfer. Because this function returns a uint8 code rather than a boolean or just reverting, it allows the function caller to know the reason why a transfer might fail and report this to relevant counterparties.
  2. messageForTransferRestriction - This function is effectively an accessor for the "message", a human-readable explanation as to why a transaction is restricted. By standardizing message look-ups, we empower user interface builders to effectively report errors to users.

Backwards Compatibility

By design ERC-1404 is fully backwards compatible with ERC-20.
Some examples of how it may be integrated with common types of restricted tokens may be found here.

Test Cases & Implementation

See the reference implementation and tests here.
See some examples of common usage patterns for ERC-1404 here.

Copyright

Copyright and related rights waived via CC0.

@bmann

This comment has been minimized.

bmann commented Sep 19, 2018

This seems very similar to #902?

902 uses a check function (it's broader than just security tokens, but effectively this is the same as detectTransferRestriction).

No worries either way, but happy to discuss updates into 902 if you found something missing in it.

Thanks for taking the time to write up and submit this as an EIP!

@youfoundron

This comment has been minimized.

youfoundron commented Sep 20, 2018

@bmann Thanks for chiming in!

Looks like #902's TokenValidator contract shares more similarities to Harbor R-Token's RegulatorService approach. We include an R-Token integration example here! 😄

In ERC1404, detectTransferRestriction is meant to live on the token contract itself, in there you could easily kick off the call to validate (what looks to be one of #902's "validation helpers") and achieve a similar result.

@veikkoeeva

This comment has been minimized.

veikkoeeva commented Sep 20, 2018

Some questions (might be dumb):

  1. If the securities are non-fungible, it would be checked in detectTransferRestriction? Just to make sure as there's a bit of cognitive disonance about ERC-20.
  2. Should there be a possibility to have batch transfers?
  3. I'm wondering if/how this would work with surety bonds (e.g. three parties, more complex terms, still in detectTransferRestriction?). I might off with this one on the purpose of this (though in that case pointers and clarifications welcome).
@Graeme-Code

This comment has been minimized.

Graeme-Code commented Sep 20, 2018

Hi, I'm very interested in using this standard for a governance project with regards to access control. Anybody looked into how this would work with Giveth's mini-me contract?

@youfoundron

This comment has been minimized.

youfoundron commented Sep 20, 2018

@Graeme-Code, I have not personally used the MiniMe pattern but at a cursory glance of Giveth's repo, it seems you would need to use a version of the MiniMeToken.sol contract modified to implement ERC1404 -- the place to look is function doTransfer() on this contract.

If there is considerable interest in this use-case I would be happy to add an implementation to our examples repo.

@Graeme-Code

This comment has been minimized.

Graeme-Code commented Sep 20, 2018

@youfoundron

This comment has been minimized.

youfoundron commented Sep 20, 2018

@veikkoeeva No dumb questions! Will try and address each of these to the best of my ability.

  1. If the securities are non-fungible, it would be checked in detectTransferRestriction? Just to make sure as there's a bit of cognitive disonance about ERC-20.

    By non-fungible do you mean "partially fungible" a la #1410, or full on NFT as in #721? In the case of the former, there are no special implementation requirements. To make an ERC1404 + ERC721 compatible token you could easily modify this implementation to handle the _isApprovedOrOwner and zero address logic inside of detectTransferRestriction. If there is considerable interest in this use-case I would be happy to add an implementation to our examples repo.
  2. Should there be a possibility to have batch transfers?

    This is functionality outside of the scope of ERC20 and by extension outside of the proposed scope of this standard. It would be useful to a lot of people however, and I'd encourage you to pursue this idea further!
  3. I'm wondering if/how this would work with surety bonds (e.g. three parties, more complex terms, still in detectTransferRestriction?). I might off with this one on the purpose of this (though in that case pointers and clarifications welcome).

    I'm not certain, but this scenario doesn't sound like a straight up transfer of tokens. Correct me if I'm wrong, but I believe you're describing an arrangement where tokens would go into escrow, a third party would give some sort of approval, and (if all goes well) tokens would depart escrow en route to a predetermined recipient. The transfers to / from escrow could be regulated by transfer restrictions of your designing, but unless this process can be executed in a single transaction it is beyond the scope of this standard.
@veikkoeeva

This comment has been minimized.

veikkoeeva commented Sep 22, 2018

@rongierlach (or @masonicGIT). Hey, I appreciate you took the time to get back. I'll keep the numbering in the following, but instead of quoting just paraphrase.

  1. By non-fungible securities I mean that some securities are non-fungible, as explained at
    https://www.repetico.de/card-34407474. In the EU there has been some bonds that are non-fungible due to some extra national serial codes (see here [pdf]), but the desire is to move towards fully fungible ones. I understand it's very desirably to have fungible securities (and even to be trade them globally), but I'm especially looking into green bonds and the problems faced there. Some noted, e.g., at https://www.eco-business.com/news/will-a-lack-of-transparency-hurt-green-bonds/. The Finnish Financial Regulatory Authority, I believe, recently warned about due diligence risks in green bonds. I don't have deep enough knowledge on securities to know if some sort of non-fungibility could solve problems but still keep trading liquid.

  2. This idea has been thought of already, as noted. There's a new standard in the makings that allows batch transfers of fungible and non-fungible tokens, more information at #1155. There's also a new 721x (mentioned at the end of 1155) that builds on 721 directly.

  3. That's true as far as I understand. As I see it, the other way/term looking into sureties is bank guarantees. I don't think these are tradeable assets usually, but can be used to make so, as noted at https://www.bankguaranteefacts.com/trading-bank-guarantees/. Then there are tradeable bank guarantees, though, as noted at https://www.linkedin.com/pulse/fraud-trading-discounted-bank-guarantees-lady-mj-santos.

The reason why I noted these might be dumb, or confused questions, are that I don't have deep knowledge on securities and trading and English isn't my native language (the terminology feels a bit problematic). I see there are plenty of inefficiencies in trading globally and fungibility is very much desired. I'm wondering also if there were a use case, now in the age of blockchain, for non-fungible securities as in with green bonds. I could imagine -- maybe wrongly -- greend bonds issued to construction could be tied to particular buildings are in the power plant building noted in the earlier green bond link. In that sense the bonds might not be fully fungible. Or they might be fully tradeable, but still maintain an unique link to off-chain data (like in ERC-721/ERC-721x/ERC-1155).

While writing this, I noticed Dharma protocol which seems to aim tokenizing debt into securities using ERC-721 (link to Ethereum contracts at https://blog.dharma.io/dharma-bug-bounty-erc721-collateralization-137b2baeca24). In my mind securities and debt securities are around the same issue, but I might be misguided here.

If there is something I could find more information on, I'm happy to do put some effort. I'm equally happy to be noted this is out-of-scope and/or I need to check some sources to increase my understanding otherwise.

Once again, I appreciate the effort to get back with explanations. I for one would appreciate an added sample to the sample repo on non-fungible asset.

@zxlzy

This comment has been minimized.

zxlzy commented Sep 22, 2018

I am an eth fan and solidity beginner from China. I think this this standard is very useful. I do some simple implement for this standard. I do not implement the ban function. But it's easy.
Every tx can teturn just one restrictionCode .But may have more than one reson for be restricted. Should we solve it?
Insert this at transfer and transferFrom function.

uint8 restrictionCode = detectTransferRestriction(_from,  _to, _value);
if( restrictionCode != 0 ) {
 revert(restrictionCode);
}

There is the ERC1404 contract.

contract ERC1404 is StandardToken , Pausable  {
  mapping(uint8 => string) restrictionMap;
  mapping(address => bool) blackList;
  constructor () public {
    restrictionMap[1] = "Token is pausing!";
    restrictionMap[2] = "sender has been banned!";
    restrictionMap[3] = "receiver has been banned!";
  }

  function detectTransferRestriction (address from, address to, uint256 value) public view returns (uint8){
    if (paused == true ) {
      return 1;
    }
    if (blackList[from] == true ) {
      return 2;
    }
    if ( blackList[to] == true ) {
      return 3;
    }
  return 0;
  }
  function messageForTransferRestriction (uint8 restrictionCode) public view returns (string){
    if (bytes(restrictionMap[restrictionCode]).length == 0 ){
      return "Restriction code incorrect!";
    }
    return restrictionMap[restrictionCode];
  }

}
@youfoundron

This comment has been minimized.

youfoundron commented Sep 24, 2018

@zxlzy At a cursory glance this implementation seems correct. Nice work! 👍
A transaction failing because of more than one reason is a likely possibility. However, modifying the standard to return multiple restriction codes from say a function called detectTransferRestrictions would be tricky because Solidity does not support returning dynamic arrays. There are workarounds for this, but I feel they would sacrifice the simplicity of the standard.

@AC0DEM0NK3Y

This comment has been minimized.

AC0DEM0NK3Y commented Sep 25, 2018

Hi I noticed #1155 was linked here regarding the batch transfer possibility. I should note that I don't see a problem with someone implementing "AContract is 1155,1404" and in the transferring of tokens simply calling the detectTransferRestriction each time...

However if you did want to do 100 transfers in one batch tx perhaps, the requirement to revert on one non-zero return would mean that for eg. instead of 99 transfers passing and 1 failing, all 100 transfers would fail.

Maybe that is something to consider when developing this standard?

Of course however if the detectTransferRestriction is called for each transaction off chain first, then you could do the above batching without having to enforce the check on chain but I'm not aware of the use cases of this standard enough to say that the detection has to always happen on chain during the transfer to make things transparent and secure.

Perhaps, you would just have to do both off and on checks to "guarantee" a batch would make it through in the majority of cases and still enforce security and transparency.

So offline at least (without regard for usable/nice code):

bool doTheBatchTx = true;
int numberOfTransfers=sizeof(fromArray); // better check your arrays all match and numberOfTransfers > 0
while(numberOfTransfers--) {
	if (contract.detectTransferRestriction(fromArray[numberOfTransfers]),toArray[numberOfTransfers],valueArray[numberOfTransfers])) {
		doTheBatchTx = false;
		break;
	}
}

if (doTheBatchTx)
	contract.multicastTransferFrom(fromArray,toArray,idArray,valueArray);
else
	errorMsg("One of the batch transfers failed the detectTransferRestriction check");

any that might fail the above you could drop from the arrays and at least salvage things (unless you needed to guarantee a set and so dropping the entire set might be desirable).

@jllaw

This comment has been minimized.

jllaw commented Oct 1, 2018

Really nice and clean. As someone who was involved with the genesis of #1400 (#1410 & #1411), I can really appreciate the simplicity of this. Will monitor and contribute if I can (hard to follow all the conversation as I'm not technically trained).

@jllaw

This comment has been minimized.

jllaw commented Oct 1, 2018

"The logic of detectTransferRestriction and messageForTransferRestriction are left up to the issuer."

Just to be clear, the detectTransferRestriction doesn't give a third party any knowledge of the full set of transfer restrictions and rules which might apply to a token right? All this standard would do is 1) check if something can be transferred against something offchain and 2) report some sort of error (but not necessarily in any standardized format).

@xlab

This comment has been minimized.

Contributor

xlab commented Oct 1, 2018

  1. report some sort of error (but not necessarily in any standardized format).

I'd suggest use of ERC-1066 and ERC-1444 for this purpose. It's a good thing they've appeared just in time.

@jllaw

This comment has been minimized.

jllaw commented Oct 1, 2018

Yes, #1066 is used by #1400 / #1411 I'll have to take a look at #1444

@youfoundron

This comment has been minimized.

youfoundron commented Oct 1, 2018

@JILaw
The detectTransferRestriction function is the entry-point for enforcing transfer restriction logic. If said logic is implemented on-chain and the contract source code is public, then it will be obvious to third parties.

However, if a portion or all of this logic depends on a call to an oracle dealing with an off-chain check, this may not be so obvious to a third party -- all this remains up to the issuer.

Standard error codes are not spec'd in #1404 but are a good to be thinking about!

@xlab
Error code standardization is an important next step for not just #1404 but the restricted / security token space in general.

#1066 and the discussions branching off of it seem like really promising avenues towards accomplishing this end, thank you for bringing to my attention!

Along these lines and in your opinion, should detectTransferRestriction be modified to return type byte1 rather than uint8?

Appreciate all the productive discussion around this issue!

@jeffishjeff

This comment has been minimized.

Contributor

jeffishjeff commented Oct 6, 2018

Nice proposal! A couple initial thoughts if I may:

  • Fully agree #1066 is quite suitable for this EIP.

  • Any particular reason for using byte1/uint8 rather than uint256? Seems artificially restrictive and cost 4x more gas as the EVM just works with uint256 internally

  • @youfoundron returning dynamic array is supported in newer EVMs. The doc is just catching up to the fact but it's been there since 0.4.22

  • Do we really need messageForTransferRestriction? I appreciate that human readable message is useful for the UI layer but seems this should be provided by a web service rather than on-chain function. Do other contracts need to retrieve text messages for interops?

  • What does the upgrade path look like for this EIP? Regulations or even just developers' self-imposed restrictions change often so the token implementation need to take that into account for its own longevity. I wonder if it'd be a better idea to separate restriction logic from token logic in the reference implementation, below is one way of doing that:

function Token is Erc20, Ownable {
    IErc1404 restrictor;

    function updateRestrictor(address addr) ownerOnly {
        restrictor = (IErc1404) addr;
    }

    modifier notRestricted (address from, address to, uint256 value) {
        uint256 restrictionCode = restrictor.detectTransferRestriction(from, to, value);
        require(restrictionCode == SUCCESS_CODE, restrictionCode);
        _;
    }
}
@morpheus499

This comment has been minimized.

morpheus499 commented Oct 8, 2018

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.

@youfoundron

This comment has been minimized.

youfoundron commented Oct 10, 2018

@jeffishjeff - Excellent feedback, really appreciate your thoughts on this.

  1. We'll explore providing an implementation that leverages #1066 in our examples.

  2. Noted. I agree this should be changed.

  3. This is good to know, thanks for catching me up to speed.

  4. messageForTransferRestriction is anticipating a best-practice, ie the way the community added details (decimals, name, and symbol) to token contracts. Perhaps it is unnecessary in the event that error codes are standardized, I see your point but I'm on the fence still.

  5. I agree that issuers would do best to put their restriction logic somewhere they can upgrade later and that the reference implementation is fairly contrived. However I feel the reference implementation should be as simple as possible. Will explore providing an implementation that uses detectTransferRestriction as a proxy for calling the check on a separate contract containing restriction logic that the token owner can swap out the address for -- this is what R-Token does btw.

Really appreciate the thoughtfulness of your feedback. Cheers! 😎

@masonicGIT

This comment has been minimized.

masonicGIT commented Nov 7, 2018

@jeffishjeff

Hey @jeffishjeff - #4 was direct and consistent feedback from technical integrators of the standard. Would be hard to err from this one at the moment.

@youfoundron

This comment has been minimized.

youfoundron commented Nov 9, 2018

tagging #1444, an expansion of ERC-1066, to the thread

@stvenyin

This comment has been minimized.

stvenyin commented Nov 11, 2018

ik

@stvenyin

This comment has been minimized.

stvenyin commented Nov 11, 2018

OK

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment