Changes from CBSWv2 research - #46
Merged
Merged
Conversation
- DefaultAccount: add single-call execute(address,uint256,bytes) (V1-compatible selector 0xb61d27f6) - CanonicalHighRatePayerAccount: lock-gate the new execute() outbound value transfer - PolicyManager: remove executeAttested; resolve the acting actorId from the tx-context precompile with a fallback to the account's own ITransactionContext surface, and enforce actor expiry on execute() - Add EIP7702ProxyFor8130: minimal EIP-8130-native 7702 delegation proxy (ported from base-account-v2), registry-gated setImplementation + default-implementation fallback, no EOA-key override Unreviewed prototype for team review.
- Require the resolved actor be gated to this manager (getPolicyManager == address(this)), mirroring the external path; closes an off-8130 confinement gap - Reject actorId == 0 explicitly (the "no acting actor" sentinel) - Require exact 32-byte returns in _resolveActorId so malformed data degrades to "no actor" instead of reverting decode - Document the off-8130 account-side trust assumption in NatSpec - Tests: add manager-mismatch rejection + execute() reentrancy regression coverage
…tePayerAccount - DefaultAccount now blocks outbound value transfers while locked (execute + executeBatch) via a shared _isLocked() helper, with an AccountLocked error — high-rate-payer-safe by default - Remove CanonicalHighRatePayerAccount: its lock-gate now lives in the base account. High-rate admission is ultimately a node allowlist, so an immutable ERC-1167 clone of DefaultAccount (or another allowlisted implementation) is the admission path; an immutable proxy can be added later if ever needed - Deploy.s.sol: deploy only DefaultAccount and log its ERC-1167 match bytecode for high-rate payers - README: document the single lock-respecting account + allowlist-based admission - Tests: fold lock-gating + execute() coverage into DefaultAccount.t.sol; drop the CanonicalHighRatePayerAccount suite
A policy-gated session key carrying a PAYER scope cannot be safely confined under ERC-4337, so policies are supported ONLY via native EIP-8130 protocol dispatch. - Remove the account-exposed ITransactionContext fallback in actor resolution: execute() reads the transaction-context precompile only; where it is absent the acting actorId is 0 and the call reverts NoActivePolicy (it just fails) - Revert execute() to the protocol-only form: drop the fallback-motivated manager-match, actorId==0, and local expiry checks (the 8130 protocol gate already guarantees these on the only reachable path) - Restore _actingActorId (precompile-only); document that there is deliberately no account-attested / 4337 policy path - Tests: drop the account-fallback / precedence / expiry / manager-match cases; keep the execute() reentrancy regression test
…o-EOA-fallback invariant Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
…comment Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
…r works on any chain Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
amiecorso
marked this pull request as ready for review
July 29, 2026 21:51
A policy-gated (confined) actor may no longer also hold a payer scope (SCOPE_SELF_PAYER or SCOPE_SPONSOR_PAYER). Payer authority lets a key spend the account's funds on gas, and that outflow cannot be metered or capped per-key, so a confined key with payer scope could drain gas regardless of its policy gate. Its gas must be covered by a separate, non-policy payer or a paymaster. Enforced in _authorizeActor (the single authorization chokepoint), so it applies to createAccount, importAccount, and applySignedActorChanges alike. New error PolicyActorCannotBePayer. Updates the SCOPE_POLICY and _authorizeActor docs, which previously stated no scope combinations were rejected. Tests: replaced authorizePolicyActor_allowsAnyScopeCombination with rejectsPayerScope (non-payer combos succeed; SELF_PAYER|POLICY and SPONSOR_PAYER|POLICY revert); masked payer bits out of the fuzzed gated scope in the policy-accessor and authenticate suites. 331 tests pass; fmt clean. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
…t high-rate, drop 7702 proxy - Restore CanonicalHighRatePayerAccount with both executeBatch and execute lock-gated overrides (keeps high-rate behavior on the dedicated account and gives it the execute interface) - Revert DefaultAccount to a plain single-call execute; remove all high-rate/lock specifics (AccountLocked error, _isLocked, value>0 lock checks, high-rate NatSpec) - Remove EIP7702ProxyFor8130 (contract + test) - Restore Deploy.s.sol and README to the two-account layout - Tests: move execute lock-gating coverage back onto CanonicalHighRatePayerAccount.t.sol; keep plain execute coverage on DefaultAccount.t.sol Unchanged: executeAttested stays removed; PolicyManager and the AccountConfiguration payer-scope rejection are untouched.
Rename: - Contract AccountConfiguration -> Keystore (src/Keystore.sol) - Test base AccountConfigurationTest -> KeystoreTest (test/lib/KeystoreTest.sol) - Test dir test/unit/AccountConfiguration/ -> test/unit/Keystore/ - All type/import references, plus ACCOUNT_CONFIGURATION -> KEYSTORE and accountConfiguration -> keystore, and NatSpec/README prose. The EIP-8130 title "Account Abstraction by Account Configuration" in the README is intentionally left unchanged. Revert: - Undo "reject payer scope on policy-gated actors" (former 77793e5); Keystore no longer rejects SCOPE_POLICY combined with a payer scope.
chunter-cb
reviewed
Aug 6, 2026
| /// @param value Wei forwarded with the call. | ||
| /// @param data Calldata passed to `target`. | ||
| function execute(address target, uint256 value, bytes calldata data) external virtual { | ||
| if (!_isAuthorizedCaller(msg.sender)) revert UnauthorizedCaller(); |
Collaborator
There was a problem hiding this comment.
I think we will consider a follow up PR to bubble up errors here too. Tracking
chunter-cb
approved these changes
Aug 6, 2026
chunter-cb
added a commit
that referenced
this pull request
Aug 6, 2026
…mpty, drop public isActor Rebased onto main (post-#46 Keystore rename / single-call execute) and resliced to three changes: - Widen actor scope uint8 -> uint16 and reorder ActorConfig/InitialActor/AccountState to the normative slot layout authenticator ‖ expiry ‖ scope ‖ reserved. Updated the ACTOR*_TYPEHASH strings, the import digest / actors-commitment encodings, and the ActorAuthorized event packing to match. - getActorConfig resolves an expired actor (non-zero expiry, block.timestamp past it) to the all-zero config, identical to an unknown actor. PolicyManager's external-path gate becomes a presence check (_requireActiveActor) since expiry is no longer observable via getActorConfig. - Drop the public isActor getter; keep an internal _isActor for the revoke path. Tests read liveness via getActorConfig(account, actorId).authenticator != 0.
chunter-cb
added a commit
that referenced
this pull request
Aug 6, 2026
… public isActor (#55) * refactor: uint16 scope + normative slot layout, expired actors read empty, drop public isActor Rebased onto main (post-#46 Keystore rename / single-call execute) and resliced to three changes: - Widen actor scope uint8 -> uint16 and reorder ActorConfig/InitialActor/AccountState to the normative slot layout authenticator ‖ expiry ‖ scope ‖ reserved. Updated the ACTOR*_TYPEHASH strings, the import digest / actors-commitment encodings, and the ActorAuthorized event packing to match. - getActorConfig resolves an expired actor (non-zero expiry, block.timestamp past it) to the all-zero config, identical to an unknown actor. PolicyManager's external-path gate becomes a presence check (_requireActiveActor) since expiry is no longer observable via getActorConfig. - Drop the public isActor getter; keep an internal _isActor for the revoke path. Tests read liveness via getActorConfig(account, actorId).authenticator != 0. * refactor: drop unused policyTarget from authenticateActor return authenticateActor now returns (actorId, scope) only. policyTarget was never consumed by any caller; the policy manager is resolved separately via getPolicyManager (an execution-time read). Removes the conditional policy-manager SLOAD from the auth path and the now-dead _policyTargetFor helper. Tests updated to resolve the manager via getPolicyManager.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Captures several changes surfaced while researching a CBSWv1 -> v2 migration on EIP-8130.
1. DefaultAccount: single-call
executeAdds
execute(address,uint256,bytes)(V1-compatible selector0xb61d27f6) alongsideexecuteBatch, so integrations that call the V1 ABI directly (e.g. SpendPermissionManager) work against an 8130 account. Same authorization asexecuteBatch. DefaultAccount stays minimal, with no lock/high-rate behavior.2. CanonicalHighRatePayerAccount: add
executeThe high-rate payer account keeps its lock-respecting behavior (outbound ETH value transfers blocked while the account is locked) and now exposes the same single-call
executein addition toexecuteBatch, both lock-gated.3. PolicyManager: remove
executeAttested; policies are 8130-native onlyRemoves the
executeAttestedentrypoint.executeresolves the actingactorIdsolely from the EIP-8130 transaction-context precompile; where the precompile is absent the acting actorId is0and the call revertsNoActivePolicy. There is deliberately no ERC-4337 / off-8130 policy path: a policy-gated session key carrying a PAYER scope cannot be safely confined under 4337.4. Rename
AccountConfiguration->KeystoreRenames the registry contract to
Keystorethroughout: contract + file (src/Keystore.sol), theKeystoreTesttest base, thetest/unit/Keystore/directory, all type/import references,ACCOUNT_CONFIGURATION->KEYSTORE,accountConfiguration->keystore, and NatSpec/README prose. The EIP-8130 title "Account Abstraction by Account Configuration" is left unchanged.Testing
forge test: 333 pass, 0 fail.forge fmt --checkclean.Deploy.s.sol/SmokeTest.s.solcompile.Unreviewed prototype for team review.