ERC-1066: Ethereum Status Codes (ESC)

Hey @0age :wave:

great presentation on ERC-1066 at devcon

Yey thanks :smile::tada:

5 is unused so far, and looks a bit like $ so seems a natural fit to me.

To me as well :+1: The 5 = $ is in fact the visual metaphor that I’m using in my working designs, too. They’re currently in a notebook and on sticky notes on a wall.

IMG_2904

but think a minimal implementation might be the best first step

I agree with @pakaplace here. We don’t want to prematurely impose codes, and empty ranges are a feature not a bug. Since these need to keep forward compatibility, a bit of restraint and investment in design early on will go a long way later. It’s only safe to add codes; removing them is problematic since contracts may depend on them.

I do think that there’s value in the codes that @0age proposed; I’d be surprised if these codes need to land in the final design. However, many aren’t specific to finance. For example, “invalid approval” belongs in the authorization column. Since we want consistency for both DX and (autonomous) code efficiency, some of these will need to be laid out differently.

I know that I mentioned this during the Devcon talk, but I truly apologize that ERC1066 v1.1 proposal isn’t up yet :bowing_woman: Our past few weeks (ie: Devcon and Tachyon) have been B-A-N-A-N-A-S, repeatedly flying intercontinentally, working on last-minute presentations, interviewing companies working with FISSION/ERC1066, possibly getting them on ETC, and so on.

Automerge is also currently broken on the EIPs repo :sob:, and we’re waiting for some changes like [restricted] placeholders (ie: @schemar’s suggestion) to be merged.

I’ll try to get a WIP Google Sheet up for everyone to see the current state. I’ll post to FEM when it’s live.

3 Likes

Ok, leaving an empty range for forward-compatibility sounds good to me. I’d say it makes the most sense to rearrange the codes a bit so that 1) the empty range is continuous, and 2) future extensions to the range, if they end up taking the form of the existing suggestions, are in a similar category as those codes at the end of the initial set. The 0x5f code is just keeping with the pattern for the rest of the top-level categories.

code description
0x50 transfer failure
0x51 transfer success
0x52 insufficient balance
0x53 insufficient allowance
0x54 transfers halted (contract paused)
0x55 funds locked (lockup period)
0x56 invalid sender
0x57 invalid receiver
0x58 invalid operator (transfer agent)
0x59
0x5a
0x5b
0x5a
0x5b
0x5c
0x5d
0x5e
0x5f token meta or info

Looking forward to discussing further on the 22nd!

Great discussion here and on Telegram.

Some comments:

  • “insufficient allowance” vs. “invalid operator” - these are somewhat similar concepts I think, the former more tied to the ERC20 approve / transferFrom and the latter more tied to the ERC777 operator semantics. Could possibly be wrapped up in a single code “invalid operator”.

  • “funds locked” - I wonder if this could be made a bit more general. Locked funds could be due to vesting, seasoning periods (e.g. 12 month lock up for Reg. D) etc… There are also restrictions at the token level (e.g. maximum number of investors or maximum volume per period). I wonder if distinguishing these two cases (restricted due to sender properties, restricted due to token properties) might be useful?

I’ll def. be dialing into the community call on this - thanks @bmann for organising!

1 Like

Sharing an example permissioned ERC20 token impementation that utilizes these status codes: https://github.com/TPL-protocol/tpl-contracts/blob/master/contracts/examples/token/ERC20/TPLERC20Permissioned.sol

I also feel like 0x59 invalid balance (for representing cases where a minimum or maximum allowed balance would be exceeded) and 0x5a invalid state (for cases where the maximum number of total token holders would be exceeded or other similar errors) are common enough issues to warrant inclusion in the standard.

Looking forward to the discussion on the 4th!

Hello guys,

I would be very interested to participate with your group.
I am working on a similar issue with Mt Pelerin’s bridge protocol (https://github.com/MtPelerin/MtPelerin-protocol)

I’ve also already submitted an EIP 1592 on this very specific topic.

Best,

FISSION Codes (prev “ESC”) v1.0.0-beta

Hi everyone :wave:

The past few months have been filled with interviewing around two dozen companies and projects, soliciting general feedback form the community, and exploring alternative code layouts. We had a community call earlier this week, and it’s been great to see the interest both on and beyond Ethereum :tada:

One version that seemed promising at first was adding more structure: encoding the referent plus modal & temporal logics. This turned out to be too rigid, so we fell back to using the lowest bit to signify blocking/non-blocking, which is in the current proposal.

Fun Facts

  • Now have more codes than HTTP
  • Nearly two dozen projects interviewed for research
  • Used by at at least three other ERCs (at least one more being announced by a partner soon)
  • Four FISSION presentations (so far)

Why the major version bump?

As mentioned above, we propose moving a few of the rows to make even numbers “blocking / others have control” and odds “unblocking / you have control”. If there is strong opinion that this should not be the case, it is easily undone.

What’s New?

Columns

0x1* Permission & CONTROL

This column already contained permissions. A prior working design included a column for “stoplight” style transitions. We realized that this is really a type of permission (ie: permission to proceed), and merged it into this column.

Some examples:

  • 0x10 Disallowed or STOP
  • 0x11 Allowed or GO

0x3* Negotiation & GOVERNANCE

Negotiation has been expanded to include decision making and governance, which have lot of overlap.

Some examples:

  • 0x30 Sender Disagreed or NAY
  • 0x31 Sender Agrees or YEA
  • 0x34 Quorum Not Reached

0x4* Availability & Time

Simply expanded to include new rows (see below)

Some examples:

  • 0x42 Paused
  • 0x43 Queued
  • 0x48 Already Complete

Rows

0x*4 Lower Limit

Includes items like:

  • 0x24 Underflow
  • 0x54 Insufficient Funds
  • 0xE4 Untrusted/Unsigned

0x*6 Upper Limit

Includes items like:

  • 0x16 Revoked
  • 0x26 Overflow
  • 0x46 Expired

0x*8 Unnecessary or Duplicate

Includes items like:

  • 0x28 Conflict or Duplicate Entry
  • 0x48 Duplicate Request / Already Complete
  • 0x58 Funds Not Required

Feedback?

We’d love any feedback that the community has before we make a PR against the EIPs repo :smile:

1 Like

We spoke about this on the call, but I wanted to put it in the thread for the broader community.

v1.0.0-beta Code v1.0.0-beta Description 0age’s Description
0x50 Transfer Failed transfer failure
0x51 Transfer successful transfer success
0x54 Insufficient funds insufficient balance
0x24 Below Ok Range insufficient allowance
0x42 Paused transfers halted (contract paused)
0x53 Hold or escrow funds locked (lockup period)
0x10 Disallowed invalid sender
0x10 Disallowed invalid receiver
0x10 Disallowed invalid operator (transfer agent)
0x5f Token or financial info token meta or info

The really challenging ones here are the ones that refer to a specific role in the flow (sender/receiver/operator). I really tried to get these to work with absolute and relative roles, but it doesn’t fit well into the current design philosophy. I’m open to options to get these to work, but here are two rough-but-workable solutions in the meantime:

  1. Use application-specific ranges
  2. Have your protocol or other ERC include rider information:
returns (byte statusCode, address subject)
//...
return (hex"10", msg.sender) // specific operator not okay, taken programatically
return (hex"10", receiverAddress) // specific user not authed
return (hex"10", this) // this contract not authed

This pattern is applicable elsewhere, too

returns (byte statusCode, address subject)
//...

return (hex"26", tokenHolder) // holder has hit the max allowed balance for this token
1 Like

When a Solidity function returns a boolean, the returned data is padded to an even 32-byte boundary: 0x0000000000000000000000000000000000000000000000000000000000000001.

I presume there was some sort of engineering decision taken by the designers of the RPC to pad the data so drastically. (My guess is that it had something to do with performance. I can’t think of any other reason other than perhaps laziness). Does anyone know why that choice was made?

The reason I’m asking is that this proposal (which inserts a one-byte value at the start of the returned output) may (for all we know) step on that engineering decision. If this proposal takes hold, every previously 32-byte (or 32-byte aligned) return would be off by one byte at the start.

Does anyone know (or has anyone studied) the effect of this, if it becomes widely adopted, on overall system performance or efficiency? (Maybe it kicks something out of some hardware cache, or it causes Solidity compiler to generate horribly inefficient code – no-one knows and that’s the point.)

I recently started this discussion (Small suggested change to EIP 1), in the hopes that EIP authors would try to think about the effect of their proposal system-wide. I’m not sure this EIP does or doesn’t have system-wide effects, but it should be discussed.

Thoughts?

Hmm, you’re right to ask :thinking: I would be very surprised if this caused issues. It’s something that you can do right now, with no changes to the VM. For performance, EVM-native 32-byte numbers typically take more to emulate than single bytes, and thus should (in my WIP EVM implementation at least) be easy to emulate. My best guess about why that happens is that numbers on the stack will are padded to 32-bytes, but don’t know of any concrete reason.

@fubuloubu any ideas? Perhaps you could illuminate how Vyper behaves for bools?

@tjayrush @expede So the reason why it gets encoded as 0x0…1 (len=32) is because of the ABI encoding. Everything on the ABI uses a 32byte boundary - because of the 32byte “stack-word”. Going sub-32 byte gets really inefficient gas wise (in memory, gains are to be had when storing again).

ERC-1066 as I read it is layered on top of the ABI spec. So looking at ERC-1066 spec briefly the abi would return bytes1 which on the binary level is the same as 1 word \ bytes32. So if one wanted on could easily add 31 bytes more of status codes in for the same cost :wink:

3 Likes

Updated the EIP repo’s copy of ERC-1066 to include the v1.0.0 codes :tada:

Next steps:

  • Update fission-suite/fission-codes JS & Solidity helper libraries
  • Min 3 reusable example contracts that use v1.0.0
  • Set EIP to Last Call
    • 2 week clock starts

Great work with this @expede and a great presentation on status codes at Devcon also.

It would be great if you could illustrate the revert-with-reason with an example also.
I’ve looked at the examples at https://github.com/Finhaven/EthereumStatusCodes/tree/master/contracts/examples and at the https://eips.ethereum.org/EIPS/eip-1066 page, but didn’t find a revert-with-reason example.

If one would revert-with-reason would you just revert with the status code or would you also be able to add an additional message?

1 Like

The Solidity & JS libs just hit v1.0.0-RC1 :muscle:

Ooh, the Finhaven lib is pretty out of date! The current repo has its own GitHub org: Fission · GitHub

On the current version of the helper lib, you have the ability to hand it an #erc-1444 localization directly for the require helpers. Both of these should work today:

// Hardcoded Message
requireOk(someCode, "something went wrong!");

// Using an ERC-1444 automated localization based on the specific code
requireOk(someCode, Localization(0xabcdef));

I’m actively working on getting the singleton language registry set up, which can then get hardcoded into the lib. The above would then look like:

// Fully automatic translated message
requireOk(someCode);

…and it would just work, message and all :unicorn::sparkles:

I hope that helps!

1 Like

Please set a discussion end date

reference https://eips.ethereum.org/EIPS/eip-1

This should be a full 14 days after the date which the discussion end date is added.

Ooh thanks for pointing that out! Will do right now :+1:

UPDATE Add review end date to EIP-1066 by expede · Pull Request #1748 · ethereum/EIPs · GitHub

ERC-1066 Now in Last Call

The review ends on Monday, February 25, 2019. I would love to hear any final thoughts or discussion prior to it making it to Final.

Thanks to everyone for the interest and feedback so far :tada::tada::tada:

4 Likes

Here is my brief review of EIP-1066. I have not reviewed everything but a few items stand out. First are notes about the standardization.

Standardization is premature

No existing implementations are deployed. Nor are similar deployments cited that show a demand for these features. Therefore it is impossible to know if this EIP provides any value.

Reasons are not motivated

No documentation is shown to explain why these codes were chosen.

Nibbles

I recommend encouraging (SHOULD not MUST) people to use two-nibble returns hex"01" in all cases to promote clarity.


Here are more opinions on the topic itself.

It is wasteful in the default case

This ads one word of output for function calls to return a SUCCESS status code. Competing proposals do not require this extra cost. The cost of including an extra output word is not explained or experimented in this EIP.

Another proposal returns status and other outputs during a revert.

It is not backwards compatible

Adding an additional function return makes this incompatible with every existing contract and other standard, including ERC-20 and ERC-721.

It is unsafe

We learned from ERC-20 that returning (rather than reverting) from a “failed” function call is dangerous. Citation --> https://twitter.com/eth_classic/status/971030641406980096

Stronger motivation should be provided to rewrite this history with a new narrative.

The async DEX examples are not realistic

A DEX will not implement in this way.

Codes are ill-defined

Should I return 0xE1 Decrypt Success or 0x01 Success or 0x21 Found?

If there are so many new codes being introduced, they should have well-defined meanings, including motivations.

In other words, this EIP is basically proposing 100 different mini standards so I am expecting the rationale to be an order of magnitude longer than other EIPs I’ve read.


In all, this is a noble effort. And I think it is close to a good solution. Here’s my overall recommendation and a path forward.

Recommendation

As is, two issues are show stoppers for me:

  • Unsafeness on returning when a function fails
  • This ERC is not backwards compatible with any existing contract or standard

Therefore I do not recommend anybody to use this EIP as-is.

BUT

With one quick modification, this can be easily addressed…

Please consider to update this proposal to only use status codes through reverts.

AND that makes it compatible with another proposal — https://github.com/ethereum/EIPs/issues/838#issuecomment-462363456

The result is efficient, backwards compatible and allows very semantic use.

Also, feel free to reach out to me directly.

1 Like

Thanks for the mini-review! Yes, these are the things we hear, explain to folks that it’s a paradigm shift, and they tend to go “oh, yeah that actually makes a lot of sense.” I’m not totally sure how to more get it across in text, so I’m very open to suggestions. Anyhow, in the meantime our standard short rebuttals are below:

In all, this is a noble effort. And I think it is close to a good solution.

Thanks!

No documentation is shown to explain why these codes were chosen.

Fair! You’re right that it should be right in the proposal itself. I’ll copy paste a bunch from the Medium post and the notes from when we did those several dozen company interviews. Thanks :smile:

No existing implementations are deployed

In fact, some contracts are already using this (typically in the the security token world). The ERC1400 Security Token proposal depends on it and we have had discussions with folks looking to use it for blockchain identity use cases, as well.

Nibbles
I recommend encouraging ( SHOULD not MUST ) people to use two-nibble returns hex"01" in all cases to promote clarity.

I’m confused. In all cases, they’ll get cast to two nibbles, even if the first nibble is 0, since you can only work with full bytes. Am I misunderstanding something?

It is wasteful in the default case

Yes, but in what way was it successful? This is important information for many use cases that involve automation. Was it successful accepted but will be run later when quorum is reached? Did the transfer go through now? There are lots of cases here.

Adding an additional function return makes this incompatible with every existing contract and other standard, including ERC-20 and ERC-721.

Good news! You don’t need to change those contracts to wrap them in an interface (in a proxy contract), effectively giving backwards compatibility for those that already rely on ERC20 or ERC721, but giving a separate interface for others. Future contracts can make use of this functionality.

But I should emphasize that this proposal was created to solve problems that we were having when developing security tokens, and enables new use cases that are currently difficult to do interoperatively on Ethereum. Calling ERC20s and NFTs directly as is most commonly done today (largely because of how difficult it is to communicate between contracts autonomously) is not an amazing use case to show off this design pattern.

It is unsafe

As per the EIP, this is in no way a replacement for revert! Exceptional, state-breaking, or dangerous cases should absolutely 100% revert! In fact, the library provides helper functions to aid with a number of revert scenarios!

The async DEX examples are not realistic

Yes, it’s true that today’s DEXes aren’t very smart. They could be smarter and more autonomous by using such a standard, but still IMO doing one on-chain is impractical for most scenarios. And fair, it’s not the greatest use case, but it’s short, and people in the ecosystem understand token-related flows today. Essentially it’s a toy example for illustrating how codes can flow through a system, as per HTTP or BEAM. If you can think of a better example that’s widely understandable to an audience with experience limited to Solidity and JS (and that shockingly often doesn’t fully understand HTTP), I would love to include it!

Codes are ill-defined
Should I return 0xE1 Decrypt Success or 0x01 Success or 0x21 Found?

I mean, it depends on your use case. If you decrypted something, you should use 0xE1, if you looked something up in a table or found something you should use 0x21, and if you want straight dumb compatibility with bools you should use 0x01. HTTP Status Codes have a similar range of more specific codes to help in control flow.

They should have well-defined meanings, including motivations

These are all taken from real-world scenarios from interviews that we did with Ethereum companies, so there are motivations for every code (some cases joined or abstracted), even if they’re not all spelled out. As always with code, we think more documentation and use cases will be useful.

Please consider to update this proposal to only use status codes through reverts.

Evidently the paradigm and purpose of this document is not clear enough at the moment, for which I apologize. I do wonder if people come to this EIP with preexisting assumptions, since (as mentioned before) when I spell it out in detail that “no it doesn’t work like that and isn’t written in that way anywhere”, a light bulb goes off for a lot of people. It’s purpose is to give more semantic information to other contracts, developers, and users, in an automated way, in a similar vein to HTTP status codes. It is not about propagating exceptions: it’s about sending context around for both success and failure, and true errors/exceptional cases should promptly exist this flow and revert.

I’ll give you a simple illustrative real-world example for status codes:

You need to check if someone is 1/n whitelists maintained by several parties. This is key in regulated scenarios, so that not every token has to (for example) check everyone’s photo ID separately, which is time consuming, expensive, and error prone, and does not easily work across borders without multiple domain experts handling this per-domicile.

Since you need to check several of these lists, you need to not revert if it fails, and it’s not an irrecoverable error, it’s a normal part of flow (chances are that you’ll need to check several of these lists). You may not be allowed to read from this list (a closed list), the user being checked may be actively blacklisted, they may not be on that list, or their verification has started off-chain (someone is reviewing their passport) but hasn’t completed. If you need 2/3 to succeed, and 2 have blocking codes (ie: even numbered codes), then you should revert. Likewise, if there is an overflow, it should also revert.

AND that makes it compatible with another proposal — EIP 838: ABI specification for REVERT reason string · Issue #838 · ethereum/EIPs · GitHub

As per the EIP text, this is already fully compatible with revert-with-reason, and the helper lib provides ways of bridging the two with a nice, semantic interface.


Thanks again for the review :tada: Looking forward to further feedback on the above responses!

Relevant Resources

Regarding wasteful.

“In what way was it successful”? This will /always/ depend on the application. If a function casts a vote that is effective later when a quorum is reached then that is something that should be documented and already understood by the caller. When I cast I vote I expect that to work like a vote. When I transfer a token I expect the token to be transferred. “Waiting for a quorum to be achieved” is a ridiculous outcome for a standard, generic token transfer. It might be cool for a DAO action, but again that is application-specific.

Regarding upgrades.

Please explain how to make a proxy for the existing CryptoKitties contract to add this feature using less than $100,000 of gas.

Regarding revert with reason.

The abstract says “They are fully compatible with both revert and revert -with-reason.” This conflicts with the specification “Codes are returned either on their own, or as the first value of a multiple return” since the specification only relates to returns, not reverts.