diff --git a/solana/solitaire/client/src/lib.rs b/solana/solitaire/client/src/lib.rs index bb8eed9b..b0839d96 100644 --- a/solana/solitaire/client/src/lib.rs +++ b/solana/solitaire/client/src/lib.rs @@ -26,6 +26,7 @@ use solitaire::{ CPICall, FromAccounts, Info, + Many, Mut, Sysvar, }; @@ -59,6 +60,9 @@ pub enum AccEntry { /// Program addresses for privileged cross calls CPIProgramSigner(Keypair, Box), + /// General case of nested structs not representing a CPI call + Many(Box), + /// Key decided from SPL constants Sysvar(Pubkey), @@ -68,8 +72,8 @@ pub enum AccEntry { DerivedRO(Pubkey), } -/// Types implementing Wrap are those that can be turned into a -/// partial account vector for a program call. +/// Types implementing Wrap can be used in top-level Solana calls as +/// the accepted, implementation-dependent AccEntry variants. pub trait Wrap { fn wrap(_: &AccEntry) -> StdResult, ErrBox>; @@ -143,15 +147,10 @@ where SignerRO(pair) => Ok(vec![AccountMeta::new_readonly(pair.pubkey(), true)]), _other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged, Signer or the respective read-only variant", std::any::type_name::(), a).into()) }, - Uninitialized => match a { + Uninitialized | MaybeInitialized => match a { Unprivileged(k) => Ok(vec![AccountMeta::new(*k, false)]), Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]), - _other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged or Signer (write access required for initialization)", std::any::type_name::(), a).into()) - } - MaybeInitialized => match a { - Unprivileged(k) => Ok(vec![AccountMeta::new(*k, false)]), - Signer(pair) => Ok(vec![AccountMeta::new(pair.pubkey(), true)]), - _other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged or Signer (write access required in case of initialization)", std::any::type_name::(), a).into()) + _other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged or Signer (write access required for initialization)", std::any::type_name::(), IsInitialized).into()) } } } @@ -213,7 +212,7 @@ impl<'a, 'b: 'a, 'c, T> Wrap for CPICall { match a { AccEntry::CPIProgram(xprog_k, xprog_accs) => { let mut v = vec![AccountMeta::new_readonly(xprog_k.clone(), false)]; - v.append(&mut xprog_accs.acc_metas(&xprog_k)?); + v.append(&mut xprog_accs.gen_client_metas()?); Ok(v) } AccEntry::CPIProgramSigner(xprog_pair, xprog_accs) => { @@ -221,7 +220,7 @@ impl<'a, 'b: 'a, 'c, T> Wrap for CPICall { xprog_pair.pubkey().clone(), false, )]; - v.append(&mut xprog_accs.acc_metas(&xprog_pair.pubkey())?); + v.append(&mut xprog_accs.gen_client_metas()?); Ok(v) } _other => Err(format!( @@ -234,10 +233,10 @@ impl<'a, 'b: 'a, 'c, T> Wrap for CPICall { fn partial_signer_keypairs(a: &AccEntry) -> Vec { match a { - AccEntry::CPIProgram(xprog_k, xprog_accs) => xprog_accs.signer_keypairs(), + AccEntry::CPIProgram(xprog_k, xprog_accs) => xprog_accs.gen_client_signers(), AccEntry::CPIProgramSigner(xprog_pair, xprog_accs) => { let mut v = vec![Keypair::from_bytes(&xprog_pair.to_bytes()[..]).unwrap()]; - v.append(&mut xprog_accs.signer_keypairs()); + v.append(&mut xprog_accs.gen_client_signers()); v } _other => vec![], @@ -245,15 +244,30 @@ impl<'a, 'b: 'a, 'c, T> Wrap for CPICall { } } -/// Trait used on client side to easily validate a program account struct + ix_data for a bare Solana call +impl<'a, 'b: 'a, 'c, T: FromAccounts<'a, 'b, 'c>> Wrap for Many { + fn wrap(a: &AccEntry) -> StdResult, ErrBox> { + if let AccEntry::Many(nested_accs) = a { + Ok(nested_accs.gen_client_metas()?) + } else { + Err(format!("{} must be passed as Many", std::any::type_name::()).into()) + } + } +} + +/// Trait used on top-level client side to easily validate a program account struct + ix_data for a bare Solana call pub trait ToInstruction: std::fmt::Debug { - fn to_ix( + /// Convenience method that will create an Instruction struct and + /// a vector of signer keypairs for use in a Solana RPC contract + /// call. + fn gen_client_ix( &self, program_id: Pubkey, ix_data: &[u8], ) -> StdResult<(Instruction, Vec), ErrBox>; - fn acc_metas(&self, program_id: &Pubkey) -> StdResult, ErrBox>; + /// Serialize the implementor into a vec of appropriate AccountMetas + fn gen_client_metas(&self) -> StdResult, ErrBox>; - fn signer_keypairs(&self) -> Vec; + /// Gather all keypairs required by the implementor's Solana program call + fn gen_client_signers(&self) -> Vec; } diff --git a/solana/solitaire/program/src/processors/peel.rs b/solana/solitaire/program/src/processors/peel.rs index bf974e1c..5c672880 100644 --- a/solana/solitaire/program/src/processors/peel.rs +++ b/solana/solitaire/program/src/processors/peel.rs @@ -290,7 +290,7 @@ impl< } } -impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c> + FromAccounts<'a, 'b, 'c>> Peel<'a, 'b, 'c> +impl<'a, 'b: 'a, 'c, T: FromAccounts<'a, 'b, 'c>> Peel<'a, 'b, 'c> for CPICall { fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result @@ -317,7 +317,7 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c> + FromAccounts<'a, 'b, 'c>> Peel<'a, 'b } fn deps() -> Vec { - todo!() + vec![system_program::id()] } fn persist(&self, program_id: &Pubkey) -> Result<()> { @@ -333,3 +333,29 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c> + FromAccounts<'a, 'b, 'c>> Peel<'a, 'b T::size_in_accounts() + 1 // Nested type size + 1 for cross program ID } } + +impl<'a, 'b: 'a, 'c, T: FromAccounts<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Many { + fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result + where + Self: Sized, + { + Ok(Self(T::from(ctx.this, ctx.iter, ctx.data)?)) + } + + fn deps() -> Vec { + // deps handled in peel() using ::from() + vec![] + } + + fn persist(&self, program_id: &Pubkey) -> Result<()> { + self.persist(program_id) + } + + fn to_partial_cpi_metas(infos: &'c mut Iter>) -> Result> { + T::to_cpi_metas(infos) + } + + fn partial_size_in_accounts() -> usize { + T::size_in_accounts() + } +} diff --git a/solana/solitaire/program/src/types/accounts.rs b/solana/solitaire/program/src/types/accounts.rs index 360cb34c..6845b851 100644 --- a/solana/solitaire/program/src/types/accounts.rs +++ b/solana/solitaire/program/src/types/accounts.rs @@ -121,6 +121,7 @@ impl } } +/// Model a cross-program invocation with its program ID and AccountMetas #[derive(Debug)] pub struct CPICall { pub xprog_id: Pubkey, @@ -128,3 +129,21 @@ pub struct CPICall { /// Helps preserve information about the type of cross program's arguments pub callee_type: PhantomData, } + +/// Model a nested FromAccounts struct + +pub struct Many(pub T); + + +impl Deref for Many { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Many { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/solana/solitaire/rocksalt/src/lib.rs b/solana/solitaire/rocksalt/src/lib.rs index 509891ac..f9857f9d 100644 --- a/solana/solitaire/rocksalt/src/lib.rs +++ b/solana/solitaire/rocksalt/src/lib.rs @@ -126,34 +126,6 @@ pub fn derive_from_accounts(input: TokenStream) -> TokenStream { } } - /// Macro generated implementation of Peel by Solitaire. - impl #combined_impl_g solitaire::Peel<'a, 'b, 'c> for #name #type_g { - fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> solitaire::Result where Self: Sized { - let v: #name #type_g = FromAccounts::from(ctx.this, ctx.iter, ctx.data)?; - - // Verify the instruction constraints - solitaire::InstructionContext::verify(&v, ctx.this)?; - - Ok(v) - } - - fn deps() -> Vec { - #deps_method - } - - fn persist(&self, program_id: &solana_program::pubkey::Pubkey) -> solitaire::Result<()> { - solitaire::Persist::persist(self, program_id) - } - - fn partial_size_in_accounts() -> usize { - Self::size_in_accounts() - } - - fn to_partial_cpi_metas(infos: &'c mut std::slice::Iter>) -> solitaire::Result> { - Self::to_cpi_metas(infos) - } - } - /// Macro generated implementation of Persist by Solitaire. impl #type_impl_g solitaire::Persist for #name #type_g { fn persist(&self, program_id: &solana_program::pubkey::Pubkey) -> solitaire::Result<()> { diff --git a/solana/solitaire/rocksalt/src/to_instruction.rs b/solana/solitaire/rocksalt/src/to_instruction.rs index 0631f1da..48a09e0a 100644 --- a/solana/solitaire/rocksalt/src/to_instruction.rs +++ b/solana/solitaire/rocksalt/src/to_instruction.rs @@ -59,7 +59,7 @@ pub fn generate_to_instruction( #[cfg(feature = "client")] impl #impl_generics solitaire_client::ToInstruction for #client_struct_ident { - fn to_ix( + fn gen_client_ix( &self, program_id: solana_program::pubkey::Pubkey, ix_data: &[u8]) -> std::result::Result< @@ -86,7 +86,7 @@ pub fn generate_to_instruction( } - fn acc_metas(&self, aprogram_id: &solana_program::pubkey::Pubkey) -> std::result::Result , solitaire::ErrBox> { + fn gen_client_metas(&self) -> std::result::Result , solitaire::ErrBox> { let mut #acc_metas_ident = Vec::new(); #acc_metas_appends @@ -94,7 +94,7 @@ pub fn generate_to_instruction( Ok(#acc_metas_ident) } - fn signer_keypairs(&self) -> Vec { + fn gen_client_signers(&self) -> Vec { let mut #signers_ident = Vec::new(); #signers_appends