-
Notifications
You must be signed in to change notification settings - Fork 0
Add single-byte account discriminators via a SettlementAccount enum #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kaze-cow
wants to merge
9
commits into
le-encoding-fixes
Choose a base branch
from
discriminators
base: le-encoding-fixes
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
ce23f0c
Add single-byte account discriminators via a SettlementAccount enum
kaze-cow 0e74418
fmt
kaze-cow c181269
Move the IDL to its own PR
kaze-cow 8f0d37c
fix minor issues
kaze-cow f6c80f2
Apply suggestion from @fedgiac
kaze-cow 0932f83
Update programs/settlement/tests/initialize.rs
kaze-cow 564576b
Merge branch 'le-encoding-fixes' into discriminators
kaze-cow a3ff6f8
introduce DISCRIMINATOR_OFFSET
kaze-cow 7d5b2d7
add write_account function for state_pda
kaze-cow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,3 +4,4 @@ | |
|
|
||
| pub mod intent; | ||
| pub mod order; | ||
| pub mod state; | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,7 +51,7 @@ pub struct OrderAccount { | |
| pub intent: OrderIntent, | ||
| } | ||
|
|
||
| /// Canonical 199-byte representation of an [`OrderAccount`]. The bytes | ||
| /// Canonical 200-byte representation of an [`OrderAccount`]. The bytes | ||
| /// written to/read from the order PDA's data area. | ||
| /// | ||
| /// Layout: one character per byte, cell widths proportional to field size, | ||
|
|
@@ -60,31 +60,40 @@ pub struct OrderAccount { | |
| /// [`EncodedOrderIntent`]; see that type's docs for its inner layout. | ||
| /// | ||
| /// ```text | ||
| /// ┌──── cancelled | ||
| /// ┌──── discriminator | ||
| /// │┌─── cancelled | ||
| /// ┌┬───────┬───────┬───────────────────────────────┬─────────────────...─────────────────┐ | ||
| /// ││amount_│amount_│ │ │ | ||
| /// ││with- │re- │ created_by │ intent (EncodedOrderIntent) │ | ||
| /// ││drawn │ceived │ │ │ | ||
| /// └┴───────┴───────┴───────────────────────────────┴─────────────────...─────────────────┘ | ||
| /// 0 1 9 17 49 ... 199 | ||
| /// 0 1 2 10 18 ... 200 | ||
| /// ``` | ||
| /// | ||
| /// The first byte is an IDL-style account discriminator (see | ||
| /// [`EncodedOrderAccount::DISCRIMINATOR`]), letting IDL-driven tooling (e.g. | ||
| /// Solscan) identify the account type before decoding the rest. | ||
| #[derive(Clone, Debug, Deref, Eq, PartialEq)] | ||
| pub struct EncodedOrderAccount([u8; Self::SIZE]); | ||
|
|
||
| impl EncodedOrderAccount { | ||
| // Per-field widths, derived from the `OrderAccount` field types. | ||
| const W_DISCRIMINATOR: usize = 1; | ||
| const W_CANCELLED: usize = size_of::<bool>(); | ||
| const W_AMOUNT_WITHDRAWN: usize = size_of::<u64>(); | ||
| const W_AMOUNT_RECEIVED: usize = size_of::<u64>(); | ||
| const W_CREATED_BY: usize = size_of::<Pubkey>(); | ||
| const W_INTENT: usize = EncodedOrderIntent::SIZE; | ||
|
|
||
| pub const SIZE: usize = 199; | ||
| pub const SIZE: usize = 200; | ||
|
|
||
| /// Single-byte account discriminator. See [`crate::SettlementAccount`]. | ||
| pub const DISCRIMINATOR: [u8; 1] = [crate::SettlementAccount::OrderAccount.discriminator()]; | ||
|
|
||
| /// Decode the account body and compute the embedded intent's UID in one | ||
| /// shot, mirroring [`EncodedOrderIntent::decode_and_hash`]. Decoding | ||
| /// validates the intent; returns [`ProgramError::InvalidAccountData`] on a | ||
| /// decode error. | ||
| /// validates the discriminator and the intent; returns | ||
| /// [`ProgramError::InvalidAccountData`] on a decode error. | ||
| pub fn decode_and_hash(bytes: &[u8; Self::SIZE]) -> Result<(OrderAccount, Hash), ProgramError> { | ||
| let order_account = OrderAccount::try_from(*bytes)?; | ||
| // The order UID is the hash of the intent's canonical bytes. Decoding | ||
|
|
@@ -111,14 +120,23 @@ pub fn write_account( | |
| created_by: &Pubkey, | ||
| encoded_intent: &[u8; EncodedOrderIntent::SIZE], | ||
| ) { | ||
| let (cancelled_slot, amount_withdrawn_slot, amount_received_slot, created_by_slot, intent_slot) = mut_array_refs![ | ||
| let ( | ||
| discriminator_slot, | ||
| cancelled_slot, | ||
| amount_withdrawn_slot, | ||
| amount_received_slot, | ||
| created_by_slot, | ||
| intent_slot, | ||
| ) = mut_array_refs![ | ||
| buffer, | ||
| EncodedOrderAccount::W_DISCRIMINATOR, | ||
| EncodedOrderAccount::W_CANCELLED, | ||
| EncodedOrderAccount::W_AMOUNT_WITHDRAWN, | ||
| EncodedOrderAccount::W_AMOUNT_RECEIVED, | ||
| EncodedOrderAccount::W_CREATED_BY, | ||
| EncodedOrderAccount::W_INTENT | ||
| ]; | ||
| *discriminator_slot = EncodedOrderAccount::DISCRIMINATOR; | ||
| *cancelled_slot = [cancelled as u8]; | ||
| *amount_withdrawn_slot = amount_withdrawn.to_le_bytes(); | ||
| *amount_received_slot = amount_received.to_le_bytes(); | ||
|
|
@@ -151,15 +169,20 @@ impl TryFrom<[u8; EncodedOrderAccount::SIZE]> for OrderAccount { | |
| type Error = ProgramError; | ||
|
|
||
| fn try_from(bytes: [u8; EncodedOrderAccount::SIZE]) -> Result<Self, Self::Error> { | ||
| let (cancelled, amount_withdrawn, amount_received, created_by, intent) = array_refs![ | ||
| let (discriminator, cancelled, amount_withdrawn, amount_received, created_by, intent) = array_refs![ | ||
| &bytes, | ||
| EncodedOrderAccount::W_DISCRIMINATOR, | ||
| EncodedOrderAccount::W_CANCELLED, | ||
| EncodedOrderAccount::W_AMOUNT_WITHDRAWN, | ||
| EncodedOrderAccount::W_AMOUNT_RECEIVED, | ||
| EncodedOrderAccount::W_CREATED_BY, | ||
| EncodedOrderAccount::W_INTENT | ||
| ]; | ||
|
|
||
| if *discriminator != EncodedOrderAccount::DISCRIMINATOR { | ||
| return Err(ProgramError::InvalidAccountData); | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this check may be superfluous if we decide we want to go more efficient.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Imho it looks reasonable to keep it. |
||
|
|
||
| Ok(OrderAccount { | ||
| cancelled: match cancelled { | ||
| [0] => false, | ||
|
|
@@ -193,8 +216,8 @@ pub mod fixtures { | |
| }; | ||
|
|
||
| // Hardcoded but verified in a sanity-check test. | ||
| pub const CANCELLED_OFFSET: usize = 0; | ||
| pub const INTENT_OFFSET: usize = 49; | ||
| pub const CANCELLED_OFFSET: usize = 1; | ||
| pub const INTENT_OFFSET: usize = 50; | ||
|
|
||
| /// Hand-picked example order account wrapping [`sample_intent`]. | ||
| pub fn sample_account(cancelled: bool) -> OrderAccount { | ||
|
|
@@ -321,6 +344,15 @@ mod tests { | |
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn decode_rejects_wrong_discriminator() { | ||
| let mut bytes: [u8; EncodedOrderAccount::SIZE] = | ||
| EncodedOrderAccount::from(sample_account(false)).into(); | ||
| bytes[0] ^= 0xff; | ||
| let err = OrderAccount::try_from(bytes).expect_err("wrong discriminator must be rejected"); | ||
| assert_eq!(err, ProgramError::InvalidAccountData); | ||
| } | ||
|
|
||
| #[test] | ||
| fn decode_rejects_non_boolean_cancelled() { | ||
| let mut bytes: [u8; EncodedOrderAccount::SIZE] = | ||
|
|
@@ -416,6 +448,8 @@ mod tests { | |
| kind in arb_order_kind(), | ||
| partially_fillable in any::<bool>(), | ||
| ) { | ||
| bytes[..EncodedOrderAccount::DISCRIMINATOR.len()] | ||
| .copy_from_slice(&EncodedOrderAccount::DISCRIMINATOR); | ||
|
kaze-cow marked this conversation as resolved.
Outdated
|
||
| bytes[CANCELLED_OFFSET] = cancelled as u8; | ||
| bytes[INTENT_OFFSET + KIND_OFFSET] = kind as u8; | ||
| bytes[INTENT_OFFSET + PARTIALLY_FILLABLE_OFFSET] = partially_fillable as u8; | ||
|
|
||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| //! Settlement state PDA body. | ||
| //! | ||
| //! The state PDA carries no fields of its own (see [`crate::pda::state`]); its | ||
| //! only content is the account discriminator, so that IDL-driven tooling can | ||
| //! identify the account type. | ||
|
|
||
| use solana_program_error::ProgramError; | ||
|
|
||
| use crate::SettlementAccount; | ||
|
|
||
| /// Canonical size of the settlement state PDA's body: just the discriminator. | ||
| pub const SIZE: usize = 1; | ||
|
|
||
| /// Single-byte account discriminator. See [`crate::SettlementAccount`]. | ||
| pub const DISCRIMINATOR: [u8; SIZE] = [SettlementAccount::SettlementState.discriminator()]; | ||
|
|
||
| /// Validate that `bytes` carries the expected discriminator. | ||
| pub fn decode(bytes: &[u8; SIZE]) -> Result<(), ProgramError> { | ||
| if *bytes != DISCRIMINATOR { | ||
| return Err(ProgramError::InvalidAccountData); | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn decode_accepts_discriminator() { | ||
| assert_eq!(decode(&DISCRIMINATOR), Ok(())); | ||
| } | ||
|
|
||
| #[test] | ||
| fn decode_rejects_wrong_discriminator() { | ||
|
fedgiac marked this conversation as resolved.
|
||
| let mut bytes = DISCRIMINATOR; | ||
| bytes[0] ^= 0xff; | ||
| assert_eq!(decode(&bytes), Err(ProgramError::InvalidAccountData)); | ||
| } | ||
| } | ||
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
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
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
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
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.
Uh oh!
There was an error while loading. Please reload this page.