Solitaire: Add an explicit Many<T> type for nested structs

Change-Id: I6a721e8a88652f807f2317cfe4d82be9d629a849
This commit is contained in:
Stan Drozd 2021-07-12 13:53:46 +02:00
parent ee96b6b793
commit 6868cc7177
5 changed files with 81 additions and 50 deletions

View File

@ -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<dyn ToInstruction>),
/// General case of nested structs not representing a CPI call
Many(Box<dyn ToInstruction>),
/// 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<Vec<AccountMeta>, 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::<Self>(), 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::<Self>(), 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::<Self>(), a).into())
_other => Err(format!("{} with IsInitialized = {:?} must be passed as Unprivileged or Signer (write access required for initialization)", std::any::type_name::<Self>(), IsInitialized).into())
}
}
}
@ -213,7 +212,7 @@ impl<'a, 'b: 'a, 'c, T> Wrap for CPICall<T> {
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<T> {
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<T> {
fn partial_signer_keypairs(a: &AccEntry) -> Vec<Keypair> {
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<T> {
}
}
/// 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<T> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, 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::<Self>()).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<Keypair>), ErrBox>;
fn acc_metas(&self, program_id: &Pubkey) -> StdResult<Vec<AccountMeta>, ErrBox>;
/// Serialize the implementor into a vec of appropriate AccountMetas
fn gen_client_metas(&self) -> StdResult<Vec<AccountMeta>, ErrBox>;
fn signer_keypairs(&self) -> Vec<Keypair>;
/// Gather all keypairs required by the implementor's Solana program call
fn gen_client_signers(&self) -> Vec<Keypair>;
}

View File

@ -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<T>
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
@ -317,7 +317,7 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c> + FromAccounts<'a, 'b, 'c>> Peel<'a, 'b
}
fn deps() -> Vec<Pubkey> {
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<T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
where
Self: Sized,
{
Ok(Self(T::from(ctx.this, ctx.iter, ctx.data)?))
}
fn deps() -> Vec<Pubkey> {
// deps handled in peel() using <T as FromAccounts>::from()
vec![]
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
self.persist(program_id)
}
fn to_partial_cpi_metas(infos: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>> {
T::to_cpi_metas(infos)
}
fn partial_size_in_accounts() -> usize {
T::size_in_accounts()
}
}

View File

@ -121,6 +121,7 @@ impl<const Seed: &'static str, T: BorshSerialize + Owned + Default>
}
}
/// Model a cross-program invocation with its program ID and AccountMetas
#[derive(Debug)]
pub struct CPICall<T> {
pub xprog_id: Pubkey,
@ -128,3 +129,21 @@ pub struct CPICall<T> {
/// Helps preserve information about the type of cross program's arguments
pub callee_type: PhantomData<T>,
}
/// Model a nested FromAccounts struct
pub struct Many<T>(pub T);
impl<T> Deref for Many<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Many<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

View File

@ -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<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> solitaire::Result<Self> 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<solana_program::pubkey::Pubkey> {
#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<Info<'b>>) -> solitaire::Result<Vec<solana_program::instruction::AccountMeta>> {
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<()> {

View File

@ -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 <Vec<solana_program::instruction::AccountMeta>, solitaire::ErrBox> {
fn gen_client_metas(&self) -> std::result::Result <Vec<solana_program::instruction::AccountMeta>, 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<solitaire_client::solana_sdk::signature::Keypair> {
fn gen_client_signers(&self) -> Vec<solitaire_client::solana_sdk::signature::Keypair> {
let mut #signers_ident = Vec::new();
#signers_appends