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

AA-159 prevent recursive call into handleOps #257

Merged
merged 28 commits into from
Apr 9, 2023
Merged

Conversation

drortirosh
Copy link
Contributor

@drortirosh drortirosh commented Apr 2, 2023

The Problem:

Currently, it is possible to make recursive calls to handleOps() during UserOp execution.

In order to associate events with UserOps, we parse all events since the previous UserOperationEvent. This means that recursive handleOp will break parsing, potentially causing events to be associated with the wrong UserOp.

Note that there is no execution risk: everything is executed in order, as it should. It is only the EXTERNAL view on the events that gets split the wrong way.

Example:

Consider a “flashloan” contract that emits “BeforeLoan” and “AfterLoan” events.
A user submits a UserOp that calls the flashloan contract, with a transaction that recursively calls handleOps with another UserOp which emits a “Transfer” event.
The events sent are:

  1. BeforeLoan
  2. Transfer
  3. UserOperationEvent (inner)
  4. AfterLoan
  5. UserOperationEvent (outer)

When parsing the events, the transactions look like:
UserOp1: BeforeLoan, Transfer
UserOp2: AfterLoan
The above split is the way the bundlers (using getUserOperationReceipt) would currently return the events.
Anyone parsing the flashloan events, will get confused - a single atomic flashloan is broken over 2 UserOps.

Additional issues with recursion:

  • With EOA, it is impossible for an account to make a "recursive" call. Making it possible with AA could break user assumptions.
  • Even with contracts, there is a strong implicit assumption that there are no recursions, and some famous hacks happened when this assumption didn't hold.
  • Allowing recursive calls on accounts means that an "execute" of an account might be called from within the execution of the same account (though this arguably has to be accepted and signed (twice) by the wallet).
  • Paymasters might assume they are not called recursively when handling the "postop" operation.
  • Some chains enshrine account-abstraction into the protocol. In such cases, the non-recursion is also prevented at the protocol level, and we would like to keep "feature parity" across different networks.

Solution:

One possible solution is to "enshrine" recursions, and provide mechanisms for bundlers and applications to detect them. However, this would put the burden of handling recursions, however rare, by each account and each paymaster implementation. In practice, accounts and paymasters might end up spending gas on a reentrancy guard in each UserOp.

A more efficient way to solve the issue, along with the additional issues above, is to prevent recursion entirely, by adding non-reentrancy guard in EntryPoint itself. The cost of the reentrancy guard is split among all UserOps rather than having each account spend gas on its own protection.

drortirosh and others added 24 commits March 2, 2023 02:35
- NonceManager to handle 2d nonces
  - _validateAndUpdateNonce() called after validateUserOp
  - getNonce(sender, key)
- remove nonce checking from BaseAccount
- BaseAccount implements nonce() using ep.getNonce
create account doesn't increment nonce automatically.
The nonce is incremented if it is created through a UserOp

Side-effect:
if account is created NOT through AA, then first UserOp will cost 20000
gas more.
(if created as a userop, then this extra cost is part of the "creation"
and not on a separate transaction.
nonce() is no longer "do-nothing" method. use instead account()
without this event, all events in validation are attributed to the first
UserOperation.
We can't attribute them to specific UseROp (unless there is exactly one)
- but at least not always assign the to the first.
use ReentrancyGuard directly from OpenZeppelin, not a copy.
@drortirosh drortirosh merged commit 9b5f2e4 into develop Apr 9, 2023
@drortirosh drortirosh deleted the non-recursion branch May 4, 2023 09:37
ptescher pushed a commit to BitskiCo/account-abstraction that referenced this pull request May 16, 2023
* AA-159 prevent recursive call into handleOps
* added BeforeExecution event
  without this event, all events in validation are attributed to the first UserOperation.
  We can't attribute them to specific UseROp (unless there is exactly one) - but at least not always assign the to the first.
ptescher pushed a commit to BitskiCo/account-abstraction that referenced this pull request Jun 14, 2023
* AA-159 prevent recursive call into handleOps
* added BeforeExecution event
  without this event, all events in validation are attributed to the first UserOperation.
  We can't attribute them to specific UseROp (unless there is exactly one) - but at least not always assign the to the first.
kaiix pushed a commit to kaiix/account-abstraction that referenced this pull request Nov 7, 2023
* AA-159 prevent recursive call into handleOps
* added BeforeExecution event
  without this event, all events in validation are attributed to the first UserOperation.
  We can't attribute them to specific UseROp (unless there is exactly one) - but at least not always assign the to the first.
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.

4 participants