Skip to content

Add SETDELEGATE factory example#17

Draft
chunter-cb wants to merge 2 commits into
mainfrom
setdelegate-factory-example
Draft

Add SETDELEGATE factory example#17
chunter-cb wants to merge 2 commits into
mainfrom
setdelegate-factory-example

Conversation

@chunter-cb

@chunter-cb chunter-cb commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Reference, unaudited example of an external account factory that combines EIP-7819 (SETDELEGATE) with EIP-8130 (importAccount) to deploy lightweight, upgradeable delegate accounts at deterministic addresses.

The headline property: this needs no changes to AccountConfiguration. A keyless factory account has no signature to present at import time, so it must answer "am I in my one-time bootstrap window?" during its own importAccount ERC-1271 callback. It answers that from its own transient (EIP-1153) latch, not by reading AccountConfiguration's localSequence.

BootstrapAccount.bootstrap does it all in one frame: set latch → call importAccount on itself → clear latch. While the latch is set (only across that nested import), isValidSignature validates the import digest against the primed actorsHash; otherwise it defers to authenticateActor. The latch is transient, so it cannot leak past the tx, and importAccount's one-time guard prevents any second import — the bootstrap branch is reachable exactly once.

Changes

  • AccountConfiguration.sol: no change vs main (an earlier revision of this PR deferred the localSequence write; that's now reverted — the pattern no longer needs it).
  • src/examples/factory/BootstrapAccount.sol: transient bootstrap latch + self-driven import; bootstrap-aware ERC-1271.
  • src/examples/factory/SetDelegateFactory.sol: deploy = SETDELEGATE + account.bootstrap(...); drops the now-unused AccountConfiguration reference.
  • src/examples/factory/IBootstrap.sol: bootstrap(actorsHash, initialActors).
  • src/examples/factory/README.md: rewritten for the self-contained design.
  • Tests for the factory flow + a codeless-import revert test.

Squatting / front-running defenses

  • SETDELEGATE derivation includes the factory (msg.sender) and a salt that commits to the actor set, so address ↔ actor-set binding is collision-hard.
  • SETDELEGATE → bootstrap is atomic in one tx; no interposition window.
  • Codeless counterfactual addresses can't be imported (no-code staticcall → empty → magic check fails).

Spec prerequisite (already on main)

  • importAccount does not reject 0xef0100… delegated code. That rule never closed an attack surface (a compromised k1 key already drains a delegated EOA via standard 7702/1559 txs) but did block this pattern. The ERC-1271 callback is the sole account ↔ actor-set binding.

Caveats

  • EIP-7819 is Draft; SETDELEGATE isn't executable on most chains. Tests simulate the opcode via vm.etch (TestableSetDelegateFactory).
  • BootstrapAccount is intentionally minimal (no execution/auth plumbing beyond Solady Receiver).

Test plan

  • forge build (evm_version osaka)
  • forge test — 179 passed, 0 failed
  • forge test --match-path 'test/unit/examples/factory/*' — 7 passed
  • forge test --match-path '.../importAccount.t.sol' — 10 passed
  • forge fmt --check clean

@chunter-cb chunter-cb marked this pull request as draft June 16, 2026 14:14
Rebased onto main, which already removed the importAccount 0xef0100
delegation-indicator reject and the _isDelegated helper. The remaining
spec-side change is small: importAccount now writes localSequence = 1
*after* the ERC-1271 STATICCALL instead of before. A view callback cannot
write state, so there is no reentrancy risk in deferring the write, and
deferring it keeps getChangeSequences(this).local == 0 observable during
the callback — which a bootstrap-aware delegate branches on.

Adds an example external factory under src/examples/factory/ that combines
EIP-7819 SETDELEGATE with EIP-8130 importAccount to deploy accounts as
23-byte delegation indicators instead of full runtime bytecode, with
factory-mediated upgradeability and no AccountConfiguration coupling:
  - SetDelegateFactory  — atomic SETDELEGATE -> bootstrap -> importAccount
  - BootstrapAccount    — bootstrap-aware ERC-1271 reference implementation
  - IBootstrap          — minimal priming interface
  - README.md           — flow, squatting defenses, caveats, future options

EIP-7819 is Draft and SETDELEGATE (opcode 0xf6) is not yet executable, so
SetDelegateFactory._setDelegate is virtual and reverts; the test subclass
simulates it via vm.etch. Squatting/front-running is prevented by the
SETDELEGATE address derivation (binds to msg.sender), the actor-set
commitment in the salt, and the atomic single-tx-frame guarantee.

Adds a codeless-account import test (squat prevention) to importAccount.t.sol
and full factory-flow tests. forge test: 179/179 pass.
@chunter-cb chunter-cb force-pushed the setdelegate-factory-example branch from 30c37e7 to 12f4c60 Compare June 18, 2026 13:46
@chunter-cb chunter-cb changed the title Relax importAccount 0xef0100 reject; add SETDELEGATE factory example Add SETDELEGATE factory example; defer importAccount localSequence write Jun 18, 2026
@chunter-cb chunter-cb changed the title Add SETDELEGATE factory example; defer importAccount localSequence write Add SETDELEGATE factory example Jun 18, 2026
Track the one-time bootstrap window in the account's own transient (EIP-1153)
latch instead of reading AccountConfiguration's local sequence. The account
now drives importAccount on itself in `bootstrap`: set latch -> import ->
clear latch, all in one frame. This reverts the importAccount localSequence
deferral so AccountConfiguration is used exactly as it ships on main.

- AccountConfiguration.sol: revert deferral (matches main; no core change).
- BootstrapAccount: transient latch + self-driven import; ERC-1271 bootstrap
  branch gated by the latch, else defers to authenticateActor.
- SetDelegateFactory: deploy = SETDELEGATE + account.bootstrap; drops the
  now-unused AccountConfiguration reference.
- IBootstrap: bootstrap(actorsHash, initialActors).
- README + tests updated.
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