Skip to content

feat: add executeWithPreCalls native function#359

Closed
dancoombs wants to merge 3 commits into
developfrom
danc/precalls
Closed

feat: add executeWithPreCalls native function#359
dancoombs wants to merge 3 commits into
developfrom
danc/precalls

Conversation

@dancoombs

Copy link
Copy Markdown
Collaborator

Summary

  • Adds executeWithPreCalls(Call[] preCalls, Call[] calls) as a new native function on ModularAccountBase
  • PreCalls execute sequentially and hard-revert on failure (same as executeBatch)
  • Calls execute atomically via self-call to executeBatch — if any call fails, the batch reverts but the overall function succeeds
  • Emits ExecuteWithPreCallsResult(bool success, bytes[] results) event with outcome
  • Enables setup-then-attempt patterns (approvals, unwraps, state setup) without wasting the entire UO when the main operation legitimately fails

Changes

  • src/interfaces/IModularAccountBase.sol — Interface + event definition
  • src/account/ModularAccountBase.sol — Implementation with wrapNativeFunction, self-call re-entrancy protections in _checkIfValidationAppliesCallData, _checkExecuteBatchValidationApplicability, and new _checkExecuteWithPreCallsValidationApplicability/_checkCallArrayValidation helpers
  • src/account/ModularAccountView.sol — Registered in _isWrappedNativeFunction
  • test/account/ExecuteWithPreCalls.t.sol — 18 tests covering happy path, call failures, atomic rollback, preCall failures, empty arrays, ETH forwarding, event emission, and self-call recursion protection

Test plan

  • All 18 new tests pass
  • All 314 existing tests pass (zero regressions)
  • Review self-call re-entrancy protection coverage
  • Review gas impact of self-call to executeBatch for the try/catch boundary

🤖 Generated with Claude Code

Two-phase batch execution: preCalls must all succeed (hard revert),
then calls execute atomically with failures caught and reported via
event. Enables setup-then-attempt patterns (approvals, unwraps)
without wasting the entire UO on a legitimate call failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dancoombs
dancoombs marked this pull request as draft April 6, 2026 21:57
@github-actions

github-actions Bot commented Apr 6, 2026

Copy link
Copy Markdown

Contract sizes:

 | Contract                     | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) |
 |------------------------------|------------------|-------------------|--------------------|---------------------|
 | AccountFactory               | 6,604            | 7,161             | 17,972             | 41,991              |
 | Address                      | 16               | 44                | 24,560             | 49,108              |
-| AllowlistModule              | 10,148           | 10,174            | 14,428             | 38,978              |
+| AllowlistModule              | 10,635           | 10,661            | 13,941             | 38,491              |
 | Base64                       | 16               | 44                | 24,560             | 49,108              |
 | Create2                      | 16               | 44                | 24,560             | 49,108              |
 | ECDSA                        | 16               | 44                | 24,560             | 49,108              |
 | ERC165Checker                | 16               | 44                | 24,560             | 49,108              |
 | ERC1967Utils                 | 16               | 44                | 24,560             | 49,108              |
 | ExecutionInstallDelegate     | 5,835            | 5,880             | 18,741             | 43,272              |
 | FCL_Elliptic_ZZ              | 16               | 44                | 24,560             | 49,108              |
 | FCL_ecdsa                    | 16               | 44                | 24,560             | 49,108              |
 | Math                         | 16               | 44                | 24,560             | 49,108              |
 | MessageHashUtils             | 16               | 44                | 24,560             | 49,108              |
-| ModularAccount               | 22,935           | 23,323            | 1,641              | 25,829              |
-| NativeTokenLimitModule       | 4,917            | 4,943             | 19,659             | 44,209              |
+| ModularAccount               | 25,083           | 25,471            | -507               | 23,681              |
+| NativeTokenLimitModule       | 5,199            | 5,225             | 19,377             | 43,927              |
 | PaymasterGuardModule         | 2,028            | 2,054             | 22,548             | 47,098              |
 | SafeERC20                    | 16               | 44                | 24,560             | 49,108              |
-| SemiModularAccount7702       | 23,336           | 23,717            | 1,240              | 25,435              |
-| SemiModularAccountBytecode   | 23,812           | 24,200            | 764                | 24,952              |
+| SemiModularAccount7702       | 25,385           | 25,766            | -809               | 23,386              |
+| SemiModularAccountBytecode   | 25,836           | 26,224            | -1,260             | 22,928              |
 | SignatureChecker             | 16               | 44                | 24,560             | 49,108              |
 | SignedMath                   | 16               | 44                | 24,560             | 49,108              |
 | SingleSignerValidationModule | 3,853            | 3,879             | 20,723             | 45,273              |
 | StorageSlot                  | 16               | 44                | 24,560             | 49,108              |
 | Strings                      | 16               | 44                | 24,560             | 49,108              |
 | TimeRangeModule              | 2,435            | 2,461             | 22,141             | 46,691              |
 | WebAuthn                     | 16               | 44                | 24,560             | 49,108              |
 | WebAuthnValidationModule     | 9,277            | 9,303             | 15,299             | 39,849              |

Code coverage:

File % Lines % Statements % Branches % Funcs
src/account/AccountBase.sol 100.00% (12/12) 100.00% (7/7) 100.00% (2/2) 100.00% (4/4)
src/account/AccountStorageInitializable.sol 100.00% (21/21) 100.00% (26/26) 100.00% (5/5) 100.00% (2/2)
src/account/ModularAccount.sol 100.00% (6/6) 100.00% (6/6) 100.00% (0/0) 100.00% (3/3)
src/account/ModularAccountBase.sol 98.66% (368/373) 96.40% (402/417) 76.12% (51/67) 100.00% (40/40)
src/account/ModularAccountView.sol 100.00% (32/32) 100.00% (31/31) 100.00% (3/3) 100.00% (5/5)
src/account/ModuleManagerInternals.sol 94.03% (63/67) 95.06% (77/81) 63.64% (7/11) 100.00% (4/4)
src/account/SemiModularAccount7702.sol 0.00% (0/9) 0.00% (0/6) 0.00% (0/1) 0.00% (0/3)
src/account/SemiModularAccountBase.sol 90.48% (76/84) 91.92% (91/99) 64.71% (11/17) 100.00% (16/16)
src/account/SemiModularAccountBytecode.sol 100.00% (8/8) 100.00% (7/7) 100.00% (1/1) 100.00% (2/2)
src/account/SemiModularAccountStorageOnly.sol 55.56% (5/9) 50.00% (5/10) 100.00% (0/0) 33.33% (1/3)
src/account/TokenReceiver.sol 33.33% (2/6) 33.33% (1/3) 100.00% (0/0) 33.33% (1/3)
src/factory/AccountFactory.sol 81.43% (57/70) 89.04% (65/73) 70.00% (7/10) 62.50% (10/16)
src/helpers/ExecutionInstallDelegate.sol 89.39% (59/66) 89.47% (68/76) 25.00% (2/8) 100.00% (7/7)
src/libraries/ExecutionLib.sol 99.66% (297/298) 98.89% (268/271) 90.91% (30/33) 100.00% (24/24)
src/libraries/KnownSelectorsLib.sol 100.00% (18/18) 100.00% (34/34) 100.00% (0/0) 100.00% (2/2)
src/libraries/LinkedListSetLib.sol 95.52% (64/67) 95.12% (78/82) 62.50% (5/8) 100.00% (8/8)
src/libraries/MemManagementLib.sol 100.00% (66/66) 100.00% (70/70) 100.00% (0/0) 100.00% (12/12)
src/libraries/ModuleInstallCommonsLib.sol 64.71% (11/17) 61.54% (8/13) 62.50% (5/8) 100.00% (3/3)
src/libraries/ValidationLocatorLib.sol 70.87% (73/103) 72.04% (67/93) 47.83% (11/23) 85.00% (17/20)
src/modules/ModuleBase.sol 76.92% (20/26) 73.08% (19/26) 66.67% (2/3) 100.00% (4/4)
src/modules/permissions/AllowlistModule.sol 78.15% (93/119) 81.16% (112/138) 71.43% (20/28) 50.00% (9/18)
src/modules/permissions/NativeTokenLimitModule.sol 84.38% (54/64) 89.04% (65/73) 93.33% (14/15) 66.67% (8/12)
src/modules/permissions/PaymasterGuardModule.sol 75.00% (15/20) 77.78% (14/18) 33.33% (1/3) 71.43% (5/7)
src/modules/permissions/TimeRangeModule.sol 88.46% (23/26) 84.62% (22/26) 100.00% (5/5) 87.50% (7/8)
src/modules/validation/SingleSignerValidationModule.sol 82.93% (34/41) 82.05% (32/39) 62.50% (5/8) 90.00% (9/10)
src/modules/validation/WebAuthnValidationModule.sol 63.64% (21/33) 66.67% (18/27) 100.00% (3/3) 60.00% (6/10)
Total 90.19% (1498/1661) 90.92% (1593/1752) 72.52% (190/262) 84.96% (209/246)

dancoombs and others added 2 commits April 6, 2026 19:48
Replaces the self-call to executeBatch with a hookless, self-call-only
performBatchCall helper. Saves gas by skipping wrapNativeFunction
overhead (hook storage reads, pre/post hook dispatch) on the inner
call, and avoids double-execution of executeBatch selector hooks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NativeTokenLimitModule and AllowlistModule both do selector-based
calldata decoding in their hooks and didn't know about
executeWithPreCalls. The token limit silently skipped ETH accounting
(value fell through to 0), and the allowlist reverted on the unknown
selector, completely blocking executeWithPreCalls.

Both modules now decode both Call[] arrays (preCalls and calls) and
apply their checks to each. Also updates checkAllowlistCalldata for
the validation-phase hook path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@dancoombs dancoombs closed this May 5, 2026
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.

1 participant