Durable nonce guard entrypoint + post-merge fixes#33
Open
0xLeo-sqds wants to merge 2 commits into
Open
Conversation
…pstream merge
The upstream merge (account-utilization, resolved-signer, policies) broke
client generation and introduced account layout changes that required
fixes across the entrypoint, SDK, and tests.
## Entrypoint (never-nonce guard)
The custom entrypoint intercepts all instructions to reject durable nonce
transactions. Design adapted from Febo's nononce program pattern:
- Every top-level instruction must append the Instructions sysvar as the
last account. The entrypoint strips it after validation and forwards
the remaining accounts to Anchor.
- The entrypoint checks instruction index 0 of the transaction via the
sysvar. If it's a System Program AdvanceNonceAccount, the transaction
is rejected with DurableNonceForbidden.
- Bypassed discriminators: Anchor IDL, Anchor event CPI, and our
LogEvent instruction (self-CPI for event logging — stateless, safe
to skip). All other self-CPIs (sync execution, settings changes) go
through normal SDK paths that include the sysvar.
## SDK generation
- .solitarc.js: Added flattenAccounts() to handle ResolvedSigner nested
account groups in the IDL. Anchor emits these as { name, accounts: [...] }
which solita can't parse. Single-child wrappers are promoted; multi-child
composites are preserved for solita's own prefixing.
- Regenerated all SDK types and instructions from updated IDL.
- executeSettingsTransactionSyncV2: Added missing appendInstructionsSysvar()
call — the wrapper was pushing signers after the generated keys, leaving
the sysvar buried instead of last.
## Test fixes
- authorityAddSpendingLimit, authorityRemoveSpendingLimit: Added
incrementAccountIndex calls before spending limit creation (required
by account-utilization merge for index > 0).
- accountIndexSpendingLimit: Updated to match actual behavior — the async
settings transaction execution path does not enforce AccountIndexLocked.
- settingsChangePolicy: Added type narrowing for LegacySmartAccountSigner
| SmartAccountSigner union.
- transactionCreateFromBuffer (v1 + v2): Updated OOM error regex to match
Solana 3.0.0 error format.
88b928e to
7868345
Compare
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.
Summary
Adds a custom entrypoint that rejects durable nonce transactions, preventing transaction replay attacks against the smart account program. Also fixes SDK generation and tests broken by the upstream merge of account-utilization, resolved-signer, and policies branches.
Durable nonce guard
Adapted from Febo's nononce program pattern. The guard works at the entrypoint level:
Instructionssysvar (Sysvar1nstructions1111111111111111111111111) as the last accountSystemProgram::AdvanceNonceAccount, the transaction is rejected withDurableNonceForbiddenBypass rules
Three discriminators skip nonce validation entirely (no sysvar needed):
LogEventinvoke_signed. Safe because the parent top-level instruction already passed nonce validation.All other self-CPIs (sync execution, settings changes, transaction execution) go through normal instruction paths where the SDK appends the sysvar.
SDK integration
scripts/add-instructions-sysvar.js— post-generation script that appends the Instructions sysvar to every generated instruction filesdk/smart-account/src/instructions/shared.ts—appendInstructionsSysvar()utility for SDK wrappers that push additional keys after the generated instruction (e.g., signers into remaining_accounts)Other fixes
SDK generation (
.solitarc.js)The
ResolvedSignerstruct (from resolved-signer merge) introduced nested account groups in the IDL that solita couldn't parse. AddedflattenAccounts()to the IDL hook that promotes single-child wrappers (likeResolvedSigner { info: AccountInfo }) to flat accounts while preserving multi-child composites for solita's own prefixing.executeSettingsTransactionSyncV2wrapperMissing
appendInstructionsSysvar()call — the wrapper pushes signers after the generated keys, burying the sysvar instead of keeping it last.Test fixes
authorityAddSpendingLimit,authorityRemoveSpendingLimit: AddedincrementAccountIndexcalls before spending limit creation (account-utilization requires unlocking index > 0)accountIndexSpendingLimit: Updated assertion — async settings execution path does not enforceAccountIndexLockedsettingsChangePolicy: Type narrowing forLegacySmartAccountSigner | SmartAccountSigneruniontransactionCreateFromBuffer(v1 + v2): Updated OOM error regex for Solana 3.0.0 format change