Add single-byte account discriminators via a SettlementAccount enum#64
Add single-byte account discriminators via a SettlementAccount enum#64kaze-cow wants to merge 9 commits into
Conversation
Prefixes every account this program owns with real storage with a
1-byte discriminator, mirroring how SettlementInstruction already
identifies instructions:
- New SettlementAccount enum (interface/src/lib.rs), following the same
num_enum::TryFromPrimitive pattern as SettlementInstruction. Discriminators
start at 128 and increment per account type, kept distinct from the
instruction discriminators (0-4).
- OrderAccount: discriminator 128 (OrderAccount), account grows
199 -> 200 bytes.
- Settlement state PDA: new interface::data::state module, discriminator
129 (SettlementState), account grows 0 -> 1 byte (written on Initialize).
An Anchor-style IDL's `discriminator` field can be any byte length, not
just Anchor's own 8-byte sha256("account:...") convention, so a single
byte is enough for IDL-driven tooling (e.g. Solscan) to identify the
account type while costing far less rent than an 8-byte discriminator.
Also adds a hand-written Anchor-compatible IDL (programs/settlement/idl/
cow_settlement.json) describing the program's instructions/accounts/types.
Since this is a native/Pinocchio program rather than an Anchor program,
several spots can't be fully expressed in the IDL grammar (BeginSettle's
dynamically-shaped tail, order_pda's hashed PDA seed) and are documented
inline via `docs` fields instead.
Builds on the little-endian encoding from the previous PR.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The hand-written IDL doesn't need to land together with the code changes it describes; splitting it out so it can be reviewed and iterated on separately (and marked draft while we figure out how to validate it). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| if *discriminator != EncodedOrderAccount::DISCRIMINATOR { | ||
| return Err(ProgramError::InvalidAccountData); | ||
| } |
There was a problem hiding this comment.
this check may be superfluous if we decide we want to go more efficient.
There was a problem hiding this comment.
Imho it looks reasonable to keep it.
fedgiac
left a comment
There was a problem hiding this comment.
Looks good, just a few small comments.
| /// ┌──── discriminator | ||
| /// │ ┌─── cancelled | ||
| /// ┌──┬───────┬───────┬───────────────────────────────┬─────────────────...─────────────────┐ | ||
| /// │ │amount_│amount_│ │ │ | ||
| /// │ │with- │re- │ created_by │ intent (EncodedOrderIntent) │ | ||
| /// │ │drawn │ceived │ │ │ | ||
| /// └──┴───────┴───────┴───────────────────────────────┴─────────────────...─────────────────┘ | ||
| /// 0 2 10 18 50 ... 200 |
There was a problem hiding this comment.
The spacing is broken:
| /// ┌──── discriminator | |
| /// │ ┌─── cancelled | |
| /// ┌──┬───────┬───────┬───────────────────────────────┬─────────────────...─────────────────┐ | |
| /// │ │amount_│amount_│ │ │ | |
| /// │ │with- │re- │ created_by │ intent (EncodedOrderIntent) │ | |
| /// │ │drawn │ceived │ │ │ | |
| /// └──┴───────┴───────┴───────────────────────────────┴─────────────────...─────────────────┘ | |
| /// 0 2 10 18 50 ... 200 | |
| /// ┌──── discriminator | |
| /// │┌─── cancelled | |
| /// ┌┬┬───────┬───────┬───────────────────────────────┬─────────────────...─────────────────┐ | |
| /// ││|amount_│amount_│ │ │ | |
| /// │││with- │re- │ created_by │ intent (EncodedOrderIntent) │ | |
| /// │││drawn │ceived │ │ │ | |
| /// └┴┴───────┴───────┴───────────────────────────────┴─────────────────...─────────────────┘ | |
| /// 0 1 2 10 18 50 ... 200 |
In general, I've noticed Claude is really bad at doing this and I always had to do it by hand.
There was a problem hiding this comment.
tbh I don't see what is broken about it. I actually manually made the spacing this way 😆
| if *discriminator != EncodedOrderAccount::DISCRIMINATOR { | ||
| return Err(ProgramError::InvalidAccountData); | ||
| } |
There was a problem hiding this comment.
Imho it looks reasonable to keep it.
| } | ||
|
|
||
| #[test] | ||
| fn decode_rejects_wrong_discriminator() { |
There was a problem hiding this comment.
Nit: for the discriminator negative tests, you could try them all except the correct one and always assert failure.
There was a problem hiding this comment.
what would we be testing for? the logic isn't all that complicated, so simple check is probably ok, right?
Co-authored-by: Federico Giacon <58218759+fedgiac@users.noreply.github.com>
Co-authored-by: Federico Giacon <58218759+fedgiac@users.noreply.github.com>
Summary
SettlementInstructionalready identifies instructions.SettlementAccountenum (interface/src/lib.rs), following the samenum_enum::TryFromPrimitivepattern asSettlementInstruction. Discriminators start at 128 and increment per account type, kept visually/numerically distinct from the instruction discriminators (0-4).OrderAccount: discriminator 128, account grows 199 -> 200 bytes.interface::data::statemodule, discriminator 129, account grows 0 -> 1 byte (written onInitialize).discriminatorfield can be any byte length — it doesn't have to be Anchor's own 8-bytesha256("account:...")convention — so a single byte is enough for IDL-driven tooling (e.g. Solscan) to identify the account type, while costing far less rent than an 8-byte discriminator would.Builds on #63 (little-endian encoding) — based on that branch since decoding an account now also depends on its fields already being little-endian.
Test plan
just build-verifiedto rebuild the on-chain.socargo test --workspace— all tests pass, including new discriminator-rejection tests andSettlementAccountenum testsjust fmt-checkpasses🤖 Generated with Claude Code