Skip to content
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

🚑️ Seeds constraint for PDAs within Accounts structure #284

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ incremented upon a breaking change and the patch version will be incremented for

**Added**

- Added support for seeds constraint for accounts structs ([284](https://github.com/Ackee-Blockchain/trident/pull/284))
- Additional methods accessing the AccountsStorage and creating corresponding accounts are now optional ("token","vote", "stake") ([279](https://github.com/Ackee-Blockchain/trident/pull/279))
- Derive macros now use own syn parser for better extensibility and UX ([275](https://github.com/Ackee-Blockchain/trident/pull/275))
- Program ID inside test_fuzz.rs file is now automatically filled in is present in the program IDL ([272](https://github.com/Ackee-Blockchain/trident/pull/272))
Expand Down
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct InitializeInstruction {
}
/// Instruction Accounts
#[derive(Arbitrary, Debug, Clone, TridentAccounts)]
#[instruction_data(InitializeInstructionData)]
pub struct InitializeInstructionAccounts {}
/// Instruction Data
#[derive(Arbitrary, Debug, BorshDeserialize, BorshSerialize, Clone)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct ProcessCustomTypesInstruction {
}
/// Instruction Accounts
#[derive(Arbitrary, Debug, Clone, TridentAccounts)]
#[instruction_data(ProcessCustomTypesInstructionData)]
pub struct ProcessCustomTypesInstructionAccounts {
composite_account_nested: CompositeAccountNestedAccounts,
#[account(signer)]
Expand All @@ -24,17 +25,20 @@ pub struct ProcessCustomTypesInstructionAccounts {
composite_account: CompositeAccountAccounts,
}
#[derive(Arbitrary, Debug, Clone, TridentAccounts)]
#[instruction_data(ProcessCustomTypesInstructionData)]
pub struct NestedInnerAccounts {
some_account: TridentAccount,
#[account(address = "11111111111111111111111111111111")]
system_program: TridentAccount,
}
#[derive(Arbitrary, Debug, Clone, TridentAccounts)]
#[instruction_data(ProcessCustomTypesInstructionData)]
pub struct CompositeAccountNestedAccounts {
some_account: TridentAccount,
nested_inner: NestedInnerAccounts,
}
#[derive(Arbitrary, Debug, Clone, TridentAccounts)]
#[instruction_data(ProcessCustomTypesInstructionData)]
pub struct CompositeAccountAccounts {
some_account: TridentAccount,
#[account(signer)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct ProcessRustTypesInstruction {
}
/// Instruction Accounts
#[derive(Arbitrary, Debug, Clone, TridentAccounts)]
#[instruction_data(ProcessRustTypesInstructionData)]
pub struct ProcessRustTypesInstructionAccounts {
composite_account_nested: CompositeAccountNestedAccounts,
#[account(signer)]
Expand All @@ -24,17 +25,20 @@ pub struct ProcessRustTypesInstructionAccounts {
composite_account: CompositeAccountAccounts,
}
#[derive(Arbitrary, Debug, Clone, TridentAccounts)]
#[instruction_data(ProcessRustTypesInstructionData)]
pub struct NestedInnerAccounts {
some_account: TridentAccount,
#[account(address = "11111111111111111111111111111111")]
system_program: TridentAccount,
}
#[derive(Arbitrary, Debug, Clone, TridentAccounts)]
#[instruction_data(ProcessRustTypesInstructionData)]
pub struct CompositeAccountNestedAccounts {
some_account: TridentAccount,
nested_inner: NestedInnerAccounts,
}
#[derive(Arbitrary, Debug, Clone, TridentAccounts)]
#[instruction_data(ProcessRustTypesInstructionData)]
pub struct CompositeAccountAccounts {
some_account: TridentAccount,
#[account(signer)]
Expand Down
2 changes: 1 addition & 1 deletion crates/fuzz/derive/accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use syn::{parse_macro_input, ItemStruct};

use trident_syn::parser::trident_accounts::parse_trident_accounts;

#[proc_macro_derive(TridentAccounts, attributes(account))]
#[proc_macro_derive(TridentAccounts, attributes(account, instruction_data))]
pub fn derive_trident_accounts(input: TokenStream) -> TokenStream {
let item_struct = parse_macro_input!(input as ItemStruct);

Expand Down
5 changes: 4 additions & 1 deletion crates/fuzz/src/traits/account.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
use solana_sdk::instruction::AccountMeta;
use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey};

use super::FuzzClient;

pub trait AccountsMethods {
type IxAccounts;
type IxData;

#[allow(unused_variables)]
fn resolve_accounts(
&mut self,
client: &mut impl FuzzClient,
ix_accounts: &mut Self::IxAccounts,
_program_id: Pubkey,
instruction_data: &Self::IxData,
) {
}
fn to_account_meta(&mut self) -> Vec<AccountMeta>;
Expand Down
1 change: 1 addition & 0 deletions crates/syn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ edition = "2021"
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full", "parsing"] }
petgraph = "0.6"
153 changes: 110 additions & 43 deletions crates/syn/src/codegen/trident_accounts.rs
Original file line number Diff line number Diff line change
@@ -1,67 +1,130 @@
use proc_macro2::TokenStream;
use petgraph::algo::toposort;
use petgraph::Graph;
use quote::{quote, ToTokens};
use std::collections::HashMap;

use crate::types::trident_accounts::TridentAccountField;
use crate::types::trident_accounts::TridentAccountsStruct;

impl ToTokens for TridentAccountsStruct {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let name = &self.ident;
let instruction_data = &self.instruction_type;
// Build dependency graph and sort fields
let dependencies = self.analyze_seed_dependencies();
let mut graph = Graph::new();
let mut node_indices = HashMap::new();
let mut field_positions = HashMap::new();

// Add all fields as nodes and track their positions
for (idx, field) in self.fields.iter().enumerate() {
let node_idx = graph.add_node(idx); // Store position instead of ident
node_indices.insert(field.ident().to_string(), node_idx);
field_positions.insert(field.ident().to_string(), idx);
}

// Add edges for dependencies - IMPORTANT: Reverse the direction!
for dep in dependencies {
let from = node_indices[&dep.dependent_field.to_string()];
let to = node_indices[&dep.required_field.to_string()];
// We were adding edges in the wrong direction
graph.add_edge(to, from, ()); // Changed order here
}

// Perform topological sort
let sorted_fields = match toposort(&graph, None) {
Ok(nodes) => nodes
.into_iter()
.map(|idx| &self.fields[graph[idx]]) // Use the stored position
.collect::<Vec<_>>(),
Err(_) => {
panic!("Circular dependencies detected in account seeds");
}
};

// Generate storage resolution code for resolve_accounts
let resolve_storage = self.fields.iter().map(|field| {
// Generate individual resolution blocks for each field
let resolve_storage = sorted_fields.iter().map(|field| {
match field {
TridentAccountField::Field(f) => {
let field_name = &f.ident;
let constraints = &f.constraints;

let mut inner_code = TokenStream::new();

if let Some(ref storage_ident) = constraints.storage {
inner_code.extend(quote! {
let address = ix_accounts
.#storage_ident
.get_or_create(self.#field_name.account_id, client, None, None);
self.#field_name.set_address(address);
});
}
let is_signer = f.constraints.signer;
let is_mutable = f.constraints.mutable;

if let Some(ref address) = constraints.address {
inner_code.extend(quote! {
self.#field_name.set_address(#address);
});
}

if constraints.signer {
inner_code.extend(quote! {
self.#field_name.set_is_signer();
});
}

if constraints.mutable {
inner_code.extend(quote! {
self.#field_name.set_is_writable();
});
}

if !inner_code.is_empty() {
// Generate the account resolution code based on constraints
if let Some(address) = &f.constraints.address {
quote! {
{
// Resolve and configure account
#inner_code
let #field_name = {
let account = #address;
self.#field_name.set_address(account);

if #is_signer {
self.#field_name.set_is_signer();
}
if #is_mutable {
self.#field_name.set_is_writable();
}

account
};
}
} else if let Some(storage_ident) = &f.constraints.storage {
let account_resolution = if let Some(seeds) = &f.constraints.seeds {
// Get program_id from constraint if available, otherwise use the passed program_id
let program_id_to_use = if let Some(program_id) = &f.constraints.program_id {
quote!(#program_id)
} else {
quote!(program_id)
};

quote! {
storage_accounts
.#storage_ident
.get_or_create(self.#field_name.account_id, client, Some(PdaSeeds::new(&[#(#seeds),*], #program_id_to_use)), None)
}
} else {
quote! {
storage_accounts
.#storage_ident
.get_or_create(self.#field_name.account_id, client, None, None)
}
};

quote! {
let #field_name = {
let account = #account_resolution;
self.#field_name.set_address(account);

if #is_signer {
self.#field_name.set_is_signer();
}
if #is_mutable {
self.#field_name.set_is_writable();
}

account
};
}
} else {
quote! {}
// No address or storage specified, just set flags
quote! {
let #field_name = {
if #is_signer {
self.#field_name.set_is_signer();
}
if #is_mutable {
self.#field_name.set_is_writable();
}
};
}
}
}
TridentAccountField::CompositeField(f) => {
let field_name = &f.ident;
quote! {
{
// Resolve composite accounts
self.#field_name.resolve_accounts(client, ix_accounts);
}
let #field_name = {
self.#field_name.resolve_accounts(client, storage_accounts, program_id, instruction_data);
&self.#field_name
};
}
}
}
Expand Down Expand Up @@ -104,11 +167,15 @@ impl ToTokens for TridentAccountsStruct {
let expanded = quote! {
impl AccountsMethods for #name {
type IxAccounts = FuzzAccounts;
type IxData = #instruction_data;

#[allow(unused_variables)]
fn resolve_accounts(
&mut self,
client: &mut impl FuzzClient,
ix_accounts: &mut Self::IxAccounts,
storage_accounts: &mut Self::IxAccounts,
program_id: Pubkey,
instruction_data: &Self::IxData,
) {
#(#resolve_storage)*
}
Expand Down
2 changes: 1 addition & 1 deletion crates/syn/src/codegen/trident_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl ToTokens for TridentInstructionStruct {
client: &mut impl FuzzClient,
fuzz_accounts: &mut Self::IxAccounts,
) {
self.#accounts.resolve_accounts(client, fuzz_accounts);
self.#accounts.resolve_accounts(client, fuzz_accounts,self.get_program_id(),&self.data);
}
}

Expand Down
Loading
Loading