Skip to content
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

Status code proposal #1066

Merged
merged 1 commit into from
May 15, 2018
Merged

Status code proposal #1066

merged 1 commit into from
May 15, 2018

Conversation

expede
Copy link
Contributor

@expede expede commented May 5, 2018

This proposal outlines a common set of Ethereum status codes (ESC) in the same vein as HTTP statuses or BEAM tagged tuples. This is a shared set of signals to allow smart contracts to react to situations autonomously, expose localized error messages to users, and so on.

ERC-1066 is purely about convention, requires no changes to the EVM, and is usable today. Further investigation is being done into potentially adding these to the status field of transactions, but is out of scope of this EIP.

We have intentionally left portions of the status code chart blank. Rather than impose a complete set up front, we hope to consult to broader community on what may be the most useful and widely applicable codes.

@expede expede force-pushed the master branch 2 times, most recently from 83830cf to 454e56c Compare May 5, 2018 20:58
EIPS/eip-1066.md Outdated
---
eip: 1066
title: Status Codes
author: Brooklyn Zelenka (@expede) <[email protected]>, Tom Carchrae (@carchrae) <[email protected]>, Gleb Naumenko (@naumenkogs) <[email protected]>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid the formatting doesn't presently support linking both emails and usernames - please pick one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay. Will update!

EIPS/eip-1066.md Outdated

### Code Table

| X. Low Nibble | 0. Generic | 10. Permission | 20. Find/Match/&c | 30. Negotiation / Offers | 40. Availability | 50. | 60. | 70. | 80. | 90. | A0. | B0. | C0. | D0. | E0. Cryptography & Tokens | F0. Off Chain |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest reformatting this with one status code per line, with a divider (or separate table) between ranges.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Arachnid 🤔 I'm not entirely certain that I follow? The intent is to show that they break cleanly into two dimensions, and meanings should be understandable when you know the categories and reasons.

Is it because of the width of the table on GitHub? If so, I can just move the table to a website or similar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once merged it'll show up on eips.ethereum.org either way. I'm just suggesting that it'd look clearer grouped by type, rather than trying to show it in a 2d matrix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that you're using the low nybble for categories and the high nybble for codes in that category. That seems really weird - why not the other way around?

Copy link
Contributor Author

@expede expede May 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the end of the day it makes no difference to the machine, and is purely mnemonic. Totally happy to hear where the community lands on this one. This is just where our team ended up, after a few weeks of working with them.

You're probably feel some "weirdness" because it's a different scheme from HTTP. We had it the other way initially, but flipped it for a few reasons. Mostly it makes a handful of things somewhat easier. Below are the main ones. There's nothing earth shattering, but we've found it to be slightly easier, and very natural once you've used it for an hour or two.

Short Forms

Generic is 0x0_, general codes are consistent with their integer representations

hex"1" == hex"01" == 1 // with casting

Contract Categories

Many applications will always be part of the same category. For instance, validation will generally be in the 0x10 range.

contract Whitelist {
    mapping(address => bool) private whitelist;
    uint256 private deadline;
    byte constant private prefix = hex"10";

    check(address _, address _user) returns (byte _status) {
        if (now >= deadline)  { return prefix | 5; } 
        if (whitelist[_user]) { return prefix | 1; } 
        return prefix;
    }
}

Helpers

This above also means that working with app-specific enums is slightly easier:

enum Sleep {
  Awake,
  Asleep,
  REM,
  FallingAsleep
}

// From the helper library

function appCode(Sleep _state) returns (byte code) {
    return byte(160 + _state); // 160 = 0xA0
}

// Versus

function appCode(Sleep _state) returns (byte code) {
  return byte((16 * _state) + 10); // 10 = 0xA
}

Would love to hear thoughts from the community, though 👍🏻

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be worth adding that to the rationale section.


### Encoding

ESCs are encoded as a `byte`. Hex values break nicely into high and low nibbles:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given existing ABI encoding expands all values to word size (256 bits / 32 bytes), it might make sense to use a larger type for this, providing more flexibility.

Copy link
Contributor Author

@expede expede May 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yes perhaps for transmission format. Will change 👍

However, we feel that the actual codes themselves should be limited to one byte. 256 codes is plenty, and showing restraint gives us a number of advantages:

  • 256 codes is already plenty to express many ideas, and to handle as return values
  • Easy to decode 16x16 without a reference guide once you're familiar with them
  • 32-bytes would be 2^256 ~ 1.158^77 codes, which largely defeats the purpose of a finite set
  • You can pack multiple single bytes into a bytes32 for cases where multiple codes are appropriate (though what to do about padding is another question)

Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a wider type would permit application-specific return codes without collisions, which seems useful to me.

I don't think people should be returning multiple status codes; that seems liable to complicate matters for little or no gain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late reply; it's been a bit of a crazy week. I really do appreciate you hashing out these details 🙏

I don't think people should be returning multiple status codes; that seems liable to complicate matters for little or no gain.

👍 I actually agree about the multiple status code. This is something that keeps coming up as an ask in local community conversations, but I'm not a fan. It's good to hear that I'm not the only one!

Using a wider type would permit application-specific return codes without collisions, which seems useful to me.

I'm still not sure if such a large space for application-specific codes would be useful or harmful. Perhaps if you could provide some use cases for such a large space. Below is some more detailed reasoning on why I believe that 2^8 codes is the right call.

I'm not totally convinced about the added space, especially since (if?) people will be choosing codes, not a random hash. A random hash would be inconsistent with a semantic code system, which in turn adds friction. People will tend towards things that are easy to remember, or have some other value. For example: 0xCAFE and 0xBAD_ may get a lot of use.

Even with a random hash for say, the first two digits of four, I question why they need to live in the same namespace. A contract that forwards other codes really has that as part of it's contract (in the DbC sense), and needs to maintain them itself. Law of Demeter aside, an explosion of forwarded codes makes it very difficult to know what to expect, and thus limits the usefulness of such a system.

I totally get that bytes32 is one machine word, and padding a byte with a bunch of 0s to fill out the word can seem like a waste of space. The purpose of this design is to be constrained for developers, with the goal of making it easy to choose a code, for other contracts to be able to react autonomously, and to translate messages. These all require a manageable number of responses to be effective. With bytes32, we're allocating a very small fraction of a percent to known codes, and the other [75 nines] would be unspecified. With 256 status known codes, if someone needs to use another scheme for their application, they're still free to. Adding 2^256 (2^32*8) codes to the same space seems to defeat the purpose.

If we later wanted to expand the code-space, bytes1 casts to bytes32, so we have simple interoperability between the ESCv0 and the hypothetical ESCv1.

Phew, that ended up being a bit longer than intended 😅 Not trying to brow beat with walls of text; just trying to clarify the intent and design!

@Arachnid
Copy link
Contributor

Arachnid commented May 6, 2018

I really like this idea. My only concern is that, with the current state of Ethereum tooling, it may motivate people to use return codes instead of reverts, when the latter are much better suited to many types of failure.

@shrugs
Copy link

shrugs commented May 6, 2018

I like the idea as well, but always thought that the pattern of success booleans was a handicap due to the lack of a good trycatch mechanism in Solidity (and also that it's very hard to tell if your external call actually ran any code). The fact that this works at the application level is awesome, but imo a real solution is adding status codes like this to revert-with-reason. Then you get both machine and human readable errors just like the web.

That said I'm generally down to use this pattern, since it does solve the primary issue of boolean returns.

@fubuloubu
Copy link
Contributor

I always disliked the success booleans.

It would be interesting if making an external call where no code was executed reverted and returned a 404-like mechanism.

@carchrae
Copy link

carchrae commented May 7, 2018

@shrugs - we were also really excited to see revert with reason get merged. but we feel that is complimentary to status codes. revert with reason is an improvement on the mechanics of returning the error, but doesn't attempt to standardise errors. there are cases where you don't want to reverts, but rather handle any error/condition gracefully. if you use revert, it is game over for that (which is fine in some cases)

@carchrae
Copy link

carchrae commented May 7, 2018

but both @expede and i agree that showing how to integrate (or extend) with revert with reason is something worthwhile. we had built out some of our examples to include it (stubbing, since revert with reason is only recently merged)

@shrugs
Copy link

shrugs commented May 7, 2018

Right, the status codes would be different from an exception code, but my opinion is that they should be baked into the language and VM, not an app-level feature. Languages have this error/exception pattern for two features; exceptions (ruby's raise) and control flow (ruby's throw and catch). Because we only have exceptions in the EVM (and no easy way to catch them 🤔) we're basically forced to implement control flow at the application level (see: golang, and all of the discussion around return err, res)

My opinion is: I'm sad that this doesn't exist in the language/vm level, it's sort of a crazy oversight imo, but also this status code standard is the best solution in the short term and I totally support the ethos and implementation of it.

@expede
Copy link
Contributor Author

expede commented May 7, 2018

@shrugs Yeah, 100% would like to see this type of functionality more baked in. It's something that we're exploring for a future EIP (though probably not in the try/catch style). VM and transaction format changes are much deeper, and we'd like to explore the consequences of such a change before making a proposal. Status codes at the application level are a very safe first step, and as @carchrae mentioned, totally orthogonal to VM changes.

Status codes are for more than failure, and can represent states beyond just "that didn't work". There are many types of success, timing/sequence logic, and composing multiple contracts into a decision tree that use specific returns to switch control flow, much like how Erlang and Elixir work.

@Arachnid
Copy link
Contributor

Arachnid commented May 7, 2018

@shrugs There's no VM support needed that isn't already there - both RETURN and REVERT can carry data and be handled by the VM just fine. It's just Solidity lacking revert handling at the user level.

@expede
Copy link
Contributor Author

expede commented May 7, 2018

@Arachnid to expand on what @carchrae said about it being complementary to revert-with-reason:

when [reverts] are much better suited to many types of failure

💯 Fully agree. revert is an essential tool, and should absolutely be used in those scenarios. Yes, codes will cut down on the total number of reverts: that's part of the idea 😜 Only having revert right now causes some pain, and we're already finding that there are a lot of cases that we'd like to be able to recover from or use for control flow without blowing up the transaction.

Codes and reverts have different meanings ("critical failure: exit now and roll back" versus "hey here's what happened"), and returning a status code doesn't preclude reverting. In fact, passing control to the caller gives us a lot of flexibility. For instance, if you receive a hex"40" (disallowed), the caller has the option of trying a different service, or just revert("Sorry, you're not allowed to do that").

We're working to make these easily compatible. For instance, in the ESC helper library, we have requireOk(byte) and requireOk(byte, string). We're also putting together (WIP) early human-readable messages and translations that could revert with message based on the code and language preferences of the msg.sender.

avivash
avivash approved these changes May 7, 2018
@Arachnid
Copy link
Contributor

Arachnid commented May 8, 2018

@expede @carchrae I understand; I'm just concerned that people will be encouraged by this to use the anti-pattern of trying to manually undo things and return rather than reverting in cases where they need to abort an operation. Theres several categories of status codes here that would make more sense as a revert reason than as a return value.

@androlo
Copy link

androlo commented May 8, 2018

@Arachnid totally agree with everything. If i remember correctly this is why previous attempts to do this kind of thing failed. Well that and not supporting multiple return values. But like you point out - anything but reverting on errors is wrong, since it results in code that is prone to side effects, and should not be encouraged.

Now that there is a revert instruction that takes arguments, having standard error codes seems like it could finally become a thing.

@carchrae
Copy link

carchrae commented May 8, 2018

@expede @carchrae I understand; I'm just concerned that people will be encouraged by this to use the anti-pattern of trying to manually undo things and return rather than reverting in cases where they need to abort an operation. Theres several categories of status codes here that would make more sense as a revert reason than as a return value.

this focus of this EIP isn't really about the use of revert vs return. it is more the focus on how we return meaningful information in a standardised way. we've been thinking about this for a few months, and really, your concern of forcing return instead of revert was something implied by returning a status ... but then revert with reason got merged! 🎉 🎈 🎆 so, now it is possible to revert and use an ESC too.

@carchrae
Copy link

carchrae commented May 8, 2018

we may need to change some of the language of the EIP to stress it doesn't force a return or revert choice, or add some explict revert examples with codes. hard to keep up sometimes with what you can/can't do.

all great comments so far - we appreciate your feedback/input.

@expede expede force-pushed the master branch 4 times, most recently from 5b8a012 to 8de015b Compare May 9, 2018 08:10
@expede
Copy link
Contributor Author

expede commented May 14, 2018

Hey everyone 👋 I'm at Consensus this week if anyone want to chat about status codes in person! I'll be the one walking around in a Finhaven t-shirt 👕😉 (Feel free to email if you can't find me in the crowd)

@Arachnid
Copy link
Contributor

I'm happy to merge this as a draft if you add a discussions-to URL where it can be persistently discussed.

@expede expede force-pushed the master branch 2 times, most recently from 1ce269c to 188f83a Compare May 15, 2018 14:14
@expede
Copy link
Contributor Author

expede commented May 15, 2018

Hey @Arachnid, it's now updated with a discussions-to 🎉 I see in the template that it's listed as an email address, but a several on the website have URLs. Please let me know if it does actually need to be an email. Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants