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

Implement assert() #1678

Merged
merged 6 commits into from
Feb 10, 2017
Merged

Implement assert() #1678

merged 6 commits into from
Feb 10, 2017

Conversation

axic
Copy link
Member

@axic axic commented Feb 9, 2017

Implement #1130.

@axic
Copy link
Member Author

axic commented Feb 9, 2017

I think this shouldn't be a breaking change given we allow user methods to shadow globals. (Even with the other PR it will only be a warning.)

case Location::Assert:
{
arguments.front()->accept(*this);
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true);
Copy link
Contributor

Choose a reason for hiding this comment

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

You can actually use false here.

Copy link
Contributor

@chriseth chriseth left a comment

Choose a reason for hiding this comment

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

There is a section in the documentation that lists all reasons for exceptions, please add assert also there.

@axic
Copy link
Member Author

axic commented Feb 10, 2017

@chriseth Updated both.

@axic axic mentioned this pull request Feb 10, 2017
Changelog.md Outdated
@@ -1,6 +1,7 @@
### 0.4.10 (unreleased)

Features:
* Add ``assert(condition)`` to abort execution.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit too sloppy (people might stop reading here). What about:

Add ``assert(condition)``, which throws if condition is false.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can change these late today. Can you approve it? Would like to merge it before the weekend.

@@ -460,6 +460,7 @@ Global Variables
- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error
- ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256``
- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256``
- ``assert(bool condition)``: throws if the condition is not met
Copy link
Contributor

Choose a reason for hiding this comment

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

if condition is false?

@axic axic merged commit 14ded49 into develop Feb 10, 2017
@axic axic removed the in progress label Feb 10, 2017
@axic axic deleted the assert branch February 10, 2017 22:39
@axic axic mentioned this pull request Feb 13, 2017
@wuestholz
Copy link
Contributor

@axic @chriseth Could you please provide some more information about why you decided to treat assert-statements similar to throw-statements by encoding them using revert instructions?

Based on an earlier discussion (#1598) it would seem more natural to me to treat assertions like runtime errors (e.g., division by zero) by encoding them using invalid instructions.

Just to provide some background, I'm working on a formal analysis tool for EVM bytecode and it would be very useful if errors (i.e., implicit or explicit assertion violations) could be distinguished from parameter validation (i.e., explicit throws or assumption/precondition violations in the verification community).

@chriseth
Copy link
Contributor

@wuestholz thanks for your input and for closely following the progress here! I indeed spent some thoughts on this but it seems not enough :-)

So the idea would be that safety conditions are violated if and only if INVALID can be reached in any way. External failures (failed call) and invalid input (manual throw) would not be considered violations of the safety conditions. I wonder whether conditions for invalid inputs can also be handled more expressively than if (x) revert();.

@chriseth
Copy link
Contributor

chriseth commented Feb 16, 2017

Please see #1702

@axic
Copy link
Member Author

axic commented Feb 16, 2017

assert() can be pretty much used for input validation, e.g. it falls into the same category as throw. Additionally, we plan to have the assert reason text supported, which is only possible with using REVERT.

@wuestholz
Copy link
Contributor

@axic @chriseth Thank you very much for the quick reply! It seems like there are certain discrepancies here.

I believe my view is consistent with what @chriseth wrote above. All errors or violations of safety conditions would use INVALID, whereas throws would use REVERT.

@chriseth What exactly do you mean by failed calls? For instance, does this include calls that run out of gas?

Another way to express such input validation would be a precondition or an assume-statement: assume(expr). I think this would be somewhat nicer than throw statements (assume(false) is essentially throw), but this might be just due to my background. :) However, this wouldn't necessarily be backwards-compatible, if one would deprecate throw afterwards.

I understand the point about providing some indication of what went wrong (e.g., using a message). I think this would be nice for both errors and throws/reverts.

The cleanest solution might be to advocate an ASSERT opcode for the EVM that works similar to REVERT; i.e., take same inputs (incl. message), but possibly draining the gas like INVALID. What do you think? Would that be an option?

@axic
Copy link
Member Author

axic commented Feb 16, 2017

My motivation was to use assert as means of input validation (I'd be lazy adding a if (x) revert(y) for every field).

Then again we could think about having:

  • assume() (invalid) and assert() (revert)
  • assert() (invalid) and ensure() (revert)

I don't see the possibility to introducing an ASSERT opcode anytime soon.

would deprecate throw afterwards.

Note, throw is kind of deprecated with the introduction of assert(). At least it was by the time it was merged.

@chriseth
Copy link
Contributor

assume / assert sounds good. @axic your comment about not being able to specify a message only concerns messages where callers might react to automatically. If you want to provide a message to the user, you can do that also with an invalid opcode: PUSH "message" INVALID - it will be visible in the debugger. A caller might respond differently to a failed input assumption (this might be invalid internal state because some other transaction went in earlier, invalid input that can be reported back to the user or an interface that is not supported to be used in a certain way), but a failed internal assertions - there is nothing you can do and they should all be treated identically.

@wuestholz
Copy link
Contributor

@chriseth @axic Great that PUSH allows for a convenient way to provide a message to the user in case of an assertion violation! (This would also be quite nice for "built-in" exceptions, such as division-by-zero.)

If deprecating throw is indeed an option I would go with the assume-assert solution I sketched earlier and allow for both commands to optionally take a message. Otherwise, a less invasive solution would keep throw and just change the encoding of assert as implemented in #1702.

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.

3 participants