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

Require with custom error #14913

Merged
merged 2 commits into from Apr 25, 2024
Merged

Require with custom error #14913

merged 2 commits into from Apr 25, 2024

Conversation

nikola-matic
Copy link
Collaborator

@nikola-matic nikola-matic commented Mar 6, 2024

Fixes: #14442

  • changelog
  • docs
  • more tests
  • forbid in legacy
  • ast id in function signature hash

@ekpyron
Copy link
Member

ekpyron commented Mar 7, 2024

I'm not sure how many of these we may already have, but since we're changing the return type of the error constructor calls, it'd be good to have a few more tests like calsuchling as a standalone statement CustomError(); or using in abi.encode and as a type in abi.decode, passing one to a regular function and so on - maybe also even for uncalled error constructors (CustomError; and passing that around in those cases - we don't need to be too strict there, but it'd be good to see the behaviour in tests anyways, although those we may also already have, I didn't check). The post type checker thing should catch all those calls to error constructors, but better to make sure.

@ekpyron
Copy link
Member

ekpyron commented Mar 7, 2024

Also maybe a small AST test with just a require-with error call, s.t. we can see if and how the new type shows up there. EDIT: I just saw that there's already one at least where it shows up.

@@ -336,27 +342,38 @@ struct EventOutsideEmitErrorOutsideRevertChecker: public PostTypeChecker::Checke
if (*_functionCall.annotation().kind == FunctionCallKind::FunctionCall)
if (auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type))
{
if (!m_inRequire && functionType->kind() == FunctionType::Kind::Require)
m_inRequire = true;
Copy link
Member

@ekpyron ekpyron Mar 7, 2024

Choose a reason for hiding this comment

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

We might also instead reuse m_currentStatement here, i.e. set that here to the outer call? Well, not sure which is better. If you flip m_currentStatement we might be able to assert that it's nullptr when we hit this - since revert and emit statements should only allow errors and events after earlier analysis I guess? But yeah, whatever is easiest to make sure we don't get weird combinations of things slipping through.
I haven't fully thought this through in nested cases yet - might be worthwhile to check for pathological things like revert SomeError(require(false,AnotherError())); or so - none of this should be valid (that example should e.g. be rejected at the very least as an invalid argument type to SomeError), but worthwhile to think about whether there can be a case that slips through.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure; the reason I did it this way is precisely because I couldn't think of a situation where a require would be within a nested call.

@ekpyron
Copy link
Member

ekpyron commented Mar 7, 2024

For the docs by the way, it's important to be clear about the fact that the arguments to the error are evaluated unconditionally (like for any regular function call).

We should also have a pathological test case for that like:

contract C {
  error E(uint);
  function r() internal returns (uint) {
    assembly { mstore(0, 7) return(0,32) }
    return 42;
  }
  function f() public returns (uint) {
    require(false, E(r());
  }
  function g() public returns (uint) {
    require(true, E(r());
    revert E(42);
  }
}

Both calling f and g should return 7 - no reverts.

We can also point out that that's consistent with a require we already have that takes a string.

We can also further point out that the allocation of the memory for the error-based revert reason will only happen in the reverting pass and that constants and string literals will become optimized, so the "expected" use should remain cheap.

@nikola-matic nikola-matic force-pushed the require-with-custom-errors branch 5 times, most recently from d86807e to dbcf2db Compare March 11, 2024 11:25
@nikola-matic nikola-matic force-pushed the require-with-custom-errors branch 3 times, most recently from 5c3d17a to 3bbfe0a Compare March 12, 2024 12:23
@ethereum ethereum deleted a comment from stackenbotten Mar 12, 2024
@nikola-matic nikola-matic self-assigned this Mar 13, 2024
@nikola-matic nikola-matic force-pushed the require-with-custom-errors branch 3 times, most recently from 00948a9 to 3704710 Compare March 26, 2024 14:13
@nikola-matic nikola-matic marked this pull request as ready for review March 26, 2024 15:07
docs/contracts/errors.rst Outdated Show resolved Hide resolved
Comment on lines 97 to 110
// @returns function that has equivalent logic of a require statement, but with a custom
// error constructor parameter.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// @returns function that has equivalent logic of a require statement, but with a custom
// error constructor parameter.
// @returns function that has equivalent logic of a require function, but with a custom
// error constructor parameter.

libsolidity/codegen/YulUtilFunctions.cpp Outdated Show resolved Hide resolved
@nikola-matic nikola-matic force-pushed the require-with-custom-errors branch 2 times, most recently from af62b85 to f32a443 Compare April 22, 2024 15:51
Copy link
Collaborator

@matheusaaguiar matheusaaguiar left a comment

Choose a reason for hiding this comment

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

Just a bunch of nitpicks. I am still looking at the code, but can't see anything wrong.

@@ -1,6 +1,7 @@
### 0.8.26 (unreleased)

Language Features:
* Introduce a new overload ``require(bool, Error)`` that allows usage of ``require`` functions with custom errors. This feature is available in the ``via-ir`` pipeline only.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* Introduce a new overload ``require(bool, Error)`` that allows usage of ``require`` functions with custom errors. This feature is available in the ``via-ir`` pipeline only.
* Introduce a new usage of ``require`` functions with custom errors. This feature is available in the ``via-ir`` pipeline only.

Maybe omitting this detail makes it simpler to the general user?

docs/contracts/errors.rst Outdated Show resolved Hide resolved
docs/contracts/errors.rst Outdated Show resolved Hide resolved
docs/control-structures.rst Outdated Show resolved Hide resolved
Copy link
Member

@cameel cameel left a comment

Choose a reason for hiding this comment

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

I have a few small corrections and final nitpicks, but nothing serious. None of this should really take more than a few minutes to fix and when it's dealt with I'm going to approve and we can merge.

docs/control-structures.rst Outdated Show resolved Hide resolved
libsolidity/codegen/YulUtilFunctions.h Outdated Show resolved Hide resolved
libsolidity/codegen/YulUtilFunctions.cpp Outdated Show resolved Hide resolved
{
solAssert(arg->annotation().type);
if (arg->annotation().type->sizeOnStack() > 0)
errorArgumentVars += IRVariable(*arg).stackSlots();
Copy link
Member

Choose a reason for hiding this comment

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

One thing that I only realized now is that you generate names for the require function's parameters based on the expressions used in the call rather than on the error definition (which has no names for parameters, so fair enough). In other cases, e.g. abi.encode(), we don't do this and we instead generate a sequence of numbered names with suffixedVariableNameList(). In your case we get parameters named like expr_29, which looks a bit odd to me because that's how I expect locals to be named.

I see that it was already in your initial implementation, but back then it was up to you how you name them. Now revertWithError() actually expects this naming pattern so I guess it's fine and maybe better than the alternative of having to explicitly pass those parameter names to it. But I think it's at least worth a comment here pointing out that this is happening. The fact that the variable here is named errorArgumentVars and not errorParameterVars is a hint but it's too subtle. Also, I'd rename the errorArguments template var to something like functionParameterNames, because that's more accurate.

I also started wondering it this won't cause some naming conflicts with locals, but I guess it shouldn't because the name counter is global. Old names won't appear here and the new ones will get higher numbers...

libsolidity/codegen/YulUtilFunctions.cpp Show resolved Hide resolved
test/cmdlineTests/~documentation_examples/test.sh Outdated Show resolved Hide resolved
@nikola-matic

This comment was marked as resolved.

@cameel

This comment was marked as resolved.

cameel
cameel previously approved these changes Apr 25, 2024
Copy link
Member

@cameel cameel left a comment

Choose a reason for hiding this comment

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

I see that #14913 (comment) was still not addressed.

I'm going to tentatively approve the PR because it's the last thing to do there, but please apply my suggestion below before merging it.

libsolidity/codegen/YulUtilFunctions.cpp Outdated Show resolved Hide resolved
Disallow require with error in legacy

More tests

Docs

Use AST ID in generated Yul helper function names

Changelog

Review comments
@cameel cameel enabled auto-merge April 25, 2024 19:09
@cameel cameel merged commit 24e3c30 into develop Apr 25, 2024
73 checks passed
@cameel cameel deleted the require-with-custom-errors branch April 25, 2024 19:24
available: balances[msg.sender]
});

require(amount > balances[msg.sender], InsufficientBalance(amount, balances[msg.sender]));
Copy link

@0xbok 0xbok Apr 25, 2024

Choose a reason for hiding this comment

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

should be require(amount <= balances[msg.sender]?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ha, you're totally right; I'll open a PR to fix it. Thanks!

Copy link

@0xbok 0xbok Apr 26, 2024

Choose a reason for hiding this comment

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

Opened a pr actually #15055

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.

Custom Errors support in require
5 participants