Revert "Solitaire: Serialize CPI accounts as an AccountMeta vect..."

Revert submission 693

Reason for revert: Moving away from CPI abstraction
Reverted Changes:
I8c252e137:bridge, token_bridge: Use Many<T>
I6a721e8a8:Solitaire: Add an explicit Many<T> type for nested...
Ibdc94b4c6:Solitaire: Serialize CPI accounts as an AccountMet...
Iefa59f5d4:Solitaire: Extend Peel to support CPI re-wrapping

Change-Id: I01733d16862aeac79ad76dff4f58386641488b3e
This commit is contained in:
Stanislaw Drozd 2021-07-15 13:22:50 +00:00
parent f2490339de
commit cbc5ae3ed1
8 changed files with 116 additions and 343 deletions

View File

@ -17,10 +17,7 @@ use byteorder::{
BigEndian,
ReadBytesExt,
};
use solana_program::{
instruction::AccountMeta,
pubkey::Pubkey,
};
use solana_program::{instruction::AccountMeta, pubkey::Pubkey};
use solitaire::{
processors::seeded::Seeded,
trace,
@ -106,8 +103,8 @@ impl<'a, 'b: 'a, 'c, T: DeserializePayload> Peel<'a, 'b, 'c> for PayloadMessage<
Data::persist(&self.0, program_id)
}
fn to_partial_cpi_metas(infos: &'c mut std::slice::Iter<Info<'b>>) -> Result<Vec<AccountMeta>> {
Data::<'b, PostedMessage, {AccountState::Initialized}>::to_partial_cpi_metas(infos)
fn to_partial_cpi_meta(&self) -> Vec<AccountMeta> {
Data::to_partial_cpi_meta(&self.0)
}
}

View File

@ -23,10 +23,7 @@ use borsh::BorshSerialize;
use solitaire::{
AccountState,
CPICall,
FromAccounts,
Info,
Mut,
Sysvar,
};
pub use solitaire::{
@ -55,9 +52,9 @@ pub enum AccEntry {
SignerRO(Keypair),
/// Program addresses for unprivileged cross calls
CPIProgram(Pubkey, Box<dyn ToInstruction>),
CPIProgram(Pubkey),
/// Program addresses for privileged cross calls
CPIProgramSigner(Keypair, Box<dyn ToInstruction>),
CPIProgramSigner(Keypair),
/// Key decided from SPL constants
Sysvar(Pubkey),
@ -74,16 +71,13 @@ pub trait Wrap {
fn wrap(_: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox>;
/// If the implementor wants to sign using other AccEntry
/// variants, they should override this. Multiple keypairs may be
/// used in multi-account AccEntry variants.
fn partial_signer_keypairs(a: &AccEntry) -> Vec<Keypair> {
/// variants, they should override this.
fn keypair(a: AccEntry) -> Option<Keypair> {
use AccEntry::*;
match a {
// A panic on unwrap below here would imply solana keypair code does not understand its own bytes
Signer(pair) | SignerRO(pair) => {
vec![Keypair::from_bytes(pair.to_bytes().to_vec().as_slice()).unwrap()]
}
_other => vec![],
Signer(pair) => Some(pair),
SignerRO(pair) => Some(pair),
_other => None,
}
}
}
@ -193,67 +187,11 @@ impl<'b> Wrap for Info<'b> {
}
}
impl<'b, T: Wrap> Wrap for Mut<T> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
match a {
AccEntry::Unprivileged(_) | AccEntry::Signer(_) | AccEntry::Derived(_) => {
Ok(T::wrap(a)?)
}
_other => Err(format!(
"{} must be passed as a mutable AccEntry, such as Unprivileged, Signer or Derived",
std::any::type_name::<Self>()
)
.into()),
}
}
}
impl<'a, 'b: 'a, 'c, T> Wrap for CPICall<T> {
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
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)?);
Ok(v)
}
AccEntry::CPIProgramSigner(xprog_pair, xprog_accs) => {
let mut v = vec![AccountMeta::new_readonly(
xprog_pair.pubkey().clone(),
false,
)];
v.append(&mut xprog_accs.acc_metas(&xprog_pair.pubkey())?);
Ok(v)
}
_other => Err(format!(
"{} must be passed as CPIProgram or CPIProgramSigner",
std::any::type_name::<Self>()
)
.into()),
}
}
fn partial_signer_keypairs(a: &AccEntry) -> Vec<Keypair> {
match a {
AccEntry::CPIProgram(xprog_k, xprog_accs) => xprog_accs.signer_keypairs(),
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
}
_other => vec![],
}
}
}
/// Trait used on client side to easily validate a program account struct + ix_data for a bare Solana call
pub trait ToInstruction: std::fmt::Debug {
/// Trait used on client side to easily validate a program accounts + ix_data for a bare Solana call
pub trait ToInstruction {
fn to_ix(
&self,
self,
program_id: Pubkey,
ix_data: &[u8],
) -> StdResult<(Instruction, Vec<Keypair>), ErrBox>;
fn acc_metas(&self, program_id: &Pubkey) -> StdResult<Vec<AccountMeta>, ErrBox>;
fn signer_keypairs(&self) -> Vec<Keypair>;
}

View File

@ -124,9 +124,6 @@ pub trait FromAccounts<'a, 'b: 'a, 'c> {
where
Self: Sized;
/// How many accounts do we expect to use for reconstructing Self?
fn size_in_accounts() -> usize;
/// Turn a bunch of infos into relevant metas to facilitate CPI calls.
fn to_cpi_metas(infos: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>>;
/// Converts the accounts back to vector form to facilitate CPI calls.
fn to_cpi_metas(&self) -> Vec<AccountMeta>;
}

View File

@ -156,8 +156,8 @@ macro_rules! data_wrapper {
Data::<'_, $embed, { $state }>::persist(self, program_id)
}
fn to_partial_cpi_metas(infos: &'c mut std::slice::Iter<solitaire::Info<'b>>) -> solitaire::Result<Vec<solana_program::instruction::AccountMeta>> {
solitaire::Data::<'b, $embed, {$state}>::to_partial_cpi_metas(infos)
fn to_partial_cpi_meta(&self) -> Vec<solana_program::instruction::AccountMeta> {
self.0.to_partial_cpi_meta()
}
}

View File

@ -7,7 +7,6 @@
use borsh::BorshDeserialize;
use solana_program::{
instruction::AccountMeta,
program_error::ProgramError,
pubkey::Pubkey,
system_program,
sysvar::{
@ -16,10 +15,7 @@ use solana_program::{
SysvarId,
},
};
use std::{
marker::PhantomData,
slice::Iter,
};
use std::marker::PhantomData;
use crate::{
processors::seeded::{
@ -28,7 +24,6 @@ use crate::{
},
types::*,
Context,
FromAccounts,
Result,
SolitaireError,
};
@ -45,15 +40,8 @@ pub trait Peel<'a, 'b: 'a, 'c> {
fn persist(&self, program_id: &Pubkey) -> Result<()>;
/// How many accounts from the on-chain account iterator
/// constitute this peelable type? Important to customizefor
/// multi-account types like FromAccounts implementors.
fn partial_size_in_accounts() -> usize {
1
}
/// Special method for turning an iterator into AccountMetas for CPI use cases.
fn to_partial_cpi_metas(_: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>>;
/// Special method for turning the type back into AccountMeta for CPI use cases.
fn to_partial_cpi_meta(&self) -> Vec<AccountMeta>;
}
/// Peel a Derived Key
@ -77,13 +65,14 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, const Seed: &'static str> Peel<'a, 'b,
T::persist(self, program_id)
}
fn to_partial_cpi_metas(infos: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>> {
T::to_partial_cpi_metas(infos)
fn to_partial_cpi_meta(&self) -> Vec<AccountMeta> {
self.0.to_partial_cpi_meta()
}
}
/// Peel a Mutable key.
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Mut<T> {
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Mut<T>
{
fn peel<I>(mut ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
ctx.immutable = false;
match ctx.info().is_writable {
@ -99,9 +88,6 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Mut<T> {
fn persist(&self, program_id: &Pubkey) -> Result<()> {
T::persist(self, program_id)
}
fn to_partial_cpi_metas(infos: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>> {
T::to_partial_cpi_metas(infos)
}
}
/// Peel a Signer.
@ -121,8 +107,8 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Signer<T> {
T::persist(self, program_id)
}
fn to_partial_cpi_metas(infos: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>> {
T::to_partial_cpi_metas(infos)
fn to_partial_cpi_meta(&self) -> Vec<AccountMeta> {
self.0.to_partial_cpi_meta()
}
}
@ -143,8 +129,8 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for System<T> {
T::persist(self, program_id)
}
fn to_partial_cpi_metas(infos: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>> {
T::to_partial_cpi_metas(infos)
fn to_partial_cpi_meta(&self) -> Vec<AccountMeta> {
self.0.to_partial_cpi_meta()
}
}
@ -171,8 +157,8 @@ where
Ok(())
}
fn to_partial_cpi_metas(infos: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>> {
Info::to_partial_cpi_metas(infos)
fn to_partial_cpi_meta(&self) -> Vec<AccountMeta> {
self.to_partial_cpi_meta()
}
}
@ -193,17 +179,14 @@ impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for Info<'b> {
Ok(())
}
fn to_partial_cpi_metas(infos: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>> {
let acc = infos
.next()
.ok_or_else(|| SolitaireError::ProgramError(ProgramError::AccountDataTooSmall))?;
let meta = if acc.is_writable {
AccountMeta::new(acc.key.clone(), acc.is_signer)
fn to_partial_cpi_meta(&self) -> Vec<AccountMeta> {
let meta = if self.is_writable {
AccountMeta::new(self.key.clone(), self.is_signer)
} else {
AccountMeta::new_readonly(acc.key.clone(), acc.is_signer)
AccountMeta::new_readonly(self.key.clone(), self.is_signer)
};
Ok(vec![meta])
vec![meta]
}
}
@ -285,51 +268,7 @@ impl<
Ok(())
}
fn to_partial_cpi_metas(infos: &'c mut Iter<Info<'b>>) -> Result<Vec<AccountMeta>> {
Info::to_partial_cpi_metas(infos)
}
}
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c> + FromAccounts<'a, 'b, 'c>> Peel<'a, 'b, 'c>
for CPICall<T>
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
where
Self: Sized,
{
let xprog = ctx
.iter
.next()
.ok_or_else(|| SolitaireError::ProgramError(ProgramError::AccountDataTooSmall))?;
let xprog_accounts = T::to_cpi_metas(ctx.iter)?;
if xprog_accounts.len() != T::size_in_accounts() {
return Err(SolitaireError::ProgramError(
ProgramError::AccountDataTooSmall,
));
}
Ok(Self {
xprog_id: xprog.key.clone(),
xprog_accounts,
callee_type: PhantomData,
})
}
fn deps() -> Vec<Pubkey> {
todo!()
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {
// Persisting cross accounts is not our business
Ok(())
}
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() + 1 // Nested type size + 1 for cross program ID
fn to_partial_cpi_meta(&self) -> Vec<AccountMeta> {
self.0.to_partial_cpi_meta()
}
}

View File

@ -5,11 +5,17 @@
//! types that describe different kinds of accounts to target.
use borsh::BorshSerialize;
use solana_program::{account_info::AccountInfo, instruction::AccountMeta, program::invoke_signed, pubkey::Pubkey, system_instruction, sysvar::Sysvar as SolanaSysvar};
use std::{marker::PhantomData, ops::{
use solana_program::{
account_info::AccountInfo,
program::invoke_signed,
pubkey::Pubkey,
system_instruction,
sysvar::Sysvar as SolanaSysvar,
};
use std::ops::{
Deref,
DerefMut,
}};
};
use crate::{
processors::seeded::Owned,
@ -120,11 +126,3 @@ impl<const Seed: &'static str, T: BorshSerialize + Owned + Default>
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes(), &[bump_seed]]]).map_err(|e| e.into())
}
}
#[derive(Debug)]
pub struct CPICall<T> {
pub xprog_id: Pubkey,
pub xprog_accounts: Vec<AccountMeta>,
/// Helps preserve information about the type of cross program's arguments
pub callee_type: PhantomData<T>,
}

View File

@ -109,7 +109,6 @@ pub fn derive_from_accounts(input: TokenStream) -> TokenStream {
let from_method = generate_from(&name, &input.data);
let to_cpi_metas_method = generate_to_cpi_metas(&name, &input.data);
let size_in_accounts_method = generate_size_in_accounts(&name, &input.data);
let persist_method = generate_persist(&name, &input.data);
let deps_method = generate_deps_fields(&name, &input.data);
let expanded = quote! {
@ -118,10 +117,7 @@ pub fn derive_from_accounts(input: TokenStream) -> TokenStream {
fn from<DataType>(pid: &'a solana_program::pubkey::Pubkey, iter: &'c mut std::slice::Iter<'a, solana_program::account_info::AccountInfo<'b>>, data: &'a DataType) -> solitaire::Result<Self> {
#from_method
}
fn size_in_accounts() -> usize {
#size_in_accounts_method
}
fn to_cpi_metas(infos: &'c mut std::slice::Iter<Info<'b>>) -> solitaire::Result<Vec<solana_program::instruction::AccountMeta>> {
fn to_cpi_metas(&self) -> Vec<solana_program::instruction::AccountMeta> {
#to_cpi_metas_method
}
}
@ -145,12 +141,8 @@ pub fn derive_from_accounts(input: TokenStream) -> TokenStream {
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)
fn to_partial_cpi_meta(&self) -> Vec<solana_program::instruction::AccountMeta> {
self.to_cpi_metas()
}
}
@ -231,10 +223,10 @@ fn generate_to_cpi_metas(name: &syn::Ident, data: &Data) -> TokenStream2 {
let v_appends = fields.named.iter().map(|f| {
// Field name, to assign to.
let field_name = &f.ident;
let field_ty = &f.ty;
let ty = &f.ty;
quote! {
v.append(&mut <#field_ty as Peel>::to_partial_cpi_metas(infos)?);
v.append(&mut self.#field_name.to_partial_cpi_meta());
}
});
@ -246,11 +238,8 @@ fn generate_to_cpi_metas(name: &syn::Ident, data: &Data) -> TokenStream2 {
quote! {
let mut v = Vec::new();
#(#v_appends;)*
if v.len() != Self::size_in_accounts() {
return Err(solitaire::SolitaireError::ProgramError(solana_program::program_error::ProgramError::InvalidAccountData));
}
Ok(v)
}
v
}
} else {
unimplemented!()
}
@ -337,30 +326,3 @@ fn generate_persist(name: &syn::Ident, data: &Data) -> TokenStream2 {
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}
fn generate_size_in_accounts(name: &syn::Ident, data: &Data) -> TokenStream2 {
if let Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) = data
{
// For each field, call the relevant partial_size_in_accounts method
let size_additions = fields.named.iter().map(|f| {
// Field name, to assign to.
let field_name = &f.ident;
let ty = &f.ty;
quote! {
size += <#ty as Peel>::partial_size_in_accounts(); // #field_name
}
});
quote! {
let mut size = 0;
#(#size_additions;)*
size
}
} else {
unimplemented!()
}
}

View File

@ -17,14 +17,13 @@ use syn::{
DataStruct,
DeriveInput,
Fields,
FieldsNamed,
GenericParam,
Generics,
Index,
};
pub fn generate_to_instruction(
orig_struct_ident: &syn::Ident,
name: &syn::Ident,
impl_generics: &syn::ImplGenerics,
data: &Data,
) -> TokenStream2 {
@ -33,144 +32,87 @@ pub fn generate_to_instruction(
fields: Fields::Named(ref fields),
..
}) => {
let client_struct_ident = syn::Ident::new(
&format!("{}Accounts", orig_struct_ident.to_string()),
Span::call_site(),
);
let client_struct_decl =
generate_clientside_struct(&orig_struct_ident, &client_struct_ident, &fields);
let expanded_appends = fields.named.iter().map(|field| {
let name = &field.ident;
let ty = &field.ty;
let acc_metas_ident = syn::Ident::new("acc_metas", Span::call_site());
let acc_metas_appends = generate_acc_metas_appends(&acc_metas_ident, &fields);
quote! {
deps.append(&mut <#ty as solitaire::Peel>::deps());
account_metas.append(&mut <#ty as solitaire_client::Wrap>::wrap(&self.#name)?);
if let Some(pair) = <#ty as solitaire_client::Wrap>::keypair(self.#name) {
signers.push(pair);
}
}
});
let client_struct_name =
syn::Ident::new(&format!("{}Accounts", name.to_string()), Span::call_site());
let signers_ident = syn::Ident::new("signers", Span::call_site());
let signers_appends = generate_signers_appends(&signers_ident, &fields);
let deps_ident = syn::Ident::new("deps", Span::call_site());
let deps_appends = generate_deps_appends(&deps_ident, &fields);
let client_struct_decl = generate_clientside_struct(&name, &client_struct_name, &data);
quote! {
/// Solitaire-generated client-side #orig_struct_ident representation
#[cfg(feature = "client")]
#[derive(Debug)]
#client_struct_decl
/// Solitaire-generated client-side #name representation
#[cfg(feature = "client")]
#client_struct_decl
/// Solitaire-generatied ToInstruction implementation
#[cfg(feature = "client")]
impl #impl_generics solitaire_client::ToInstruction for #client_struct_ident {
/// Solitaire-generatied ToInstruction implementation
#[cfg(feature = "client")]
impl #impl_generics solitaire_client::ToInstruction for #client_struct_name {
fn to_ix(
self,
program_id: solana_program::pubkey::Pubkey,
ix_data: &[u8]) -> std::result::Result<
(solitaire_client::Instruction, Vec<solitaire_client::Keypair>),
solitaire::ErrBox
> {
use solana_program::{pubkey::Pubkey, instruction::Instruction};
let mut account_metas = Vec::new();
let mut signers = Vec::new();
let mut deps = Vec::new();
fn to_ix(
&self,
program_id: solana_program::pubkey::Pubkey,
ix_data: &[u8]) -> std::result::Result<
(solitaire_client::Instruction, Vec<solitaire_client::Keypair>),
solitaire::ErrBox> {
#(#expanded_appends;)*
use solana_program::{pubkey::Pubkey, instruction::Instruction};
let mut #acc_metas_ident = Vec::new();
let mut #signers_ident = Vec::new();
let mut #deps_ident = Vec::new();
// Add dependencies
deps.dedup();
let mut dep_ams = deps.iter().map(|v| solana_program::instruction::AccountMeta::new_readonly(*v, false)).collect();
account_metas.append(&mut dep_ams);
#acc_metas_appends
#deps_appends
#signers_appends
Ok((solana_program::instruction::Instruction::new_with_bytes(program_id,
ix_data,
account_metas), signers))
// Add dependencies
#deps_ident.dedup();
let mut dep_ams = deps.iter().map(|v| solana_program::instruction::AccountMeta::new_readonly(*v, false)).collect();
#acc_metas_ident.append(&mut dep_ams);
}
Ok((solana_program::instruction::Instruction::new_with_bytes(program_id,
ix_data,
#acc_metas_ident), #signers_ident))
}
fn acc_metas(&self, aprogram_id: &solana_program::pubkey::Pubkey) -> std::result::Result <Vec<solana_program::instruction::AccountMeta>, solitaire::ErrBox> {
let mut #acc_metas_ident = Vec::new();
#acc_metas_appends
Ok(#acc_metas_ident)
}
fn signer_keypairs(&self) -> Vec<solitaire_client::solana_sdk::signature::Keypair> {
let mut #signers_ident = Vec::new();
#signers_appends
#signers_ident
}
}
}
}
}
}
_ => unimplemented!(),
}
}
/// Generates Wrap::wrap() calls and appends to the account metas vec
/// for the specified vec name ident and provided field data.
pub fn generate_acc_metas_appends(vec_ident: &syn::Ident, data: &FieldsNamed) -> TokenStream2 {
let appends = data.named.iter().map(|f| {
let f_ty = &f.ty;
let f_ident = &f.ident;
quote! {
#vec_ident.append(&mut <#f_ty as solitaire_client::Wrap>::wrap(&self.#f_ident)?);
}
});
quote! {
#(#appends;)*
}
}
/// Generates Peel::deps() calls and appends to the dependency pubkey
/// vec for the specified vec name ident and provided field data.
pub fn generate_deps_appends(vec_ident: &syn::Ident, data: &FieldsNamed) -> TokenStream2 {
let appends = data.named.iter().map(|f| {
let f_ty = &f.ty;
quote! {
#vec_ident.append(&mut <#f_ty as solitaire::Peel>::deps());
}
});
quote! {
#(#appends;)*
}
}
/// Generates Wrap::partial_signer_keypairs() calls and appends to the
/// dependency pubkey vec for the specified vec name ident and
/// provided field data.
pub fn generate_signers_appends(vec_ident: &syn::Ident, data: &FieldsNamed) -> TokenStream2 {
let appends = data.named.iter().map(|f| {
let f_ty = &f.ty;
let f_ident = &f.ident;
quote! {
#vec_ident.append(&mut <#f_ty as solitaire_client::Wrap>::partial_signer_keypairs(&self.#f_ident));
}
});
quote! {
#(#appends;)*
}
}
/// Generates a client-side struct for sending relevant account pubkeys/keypairs/sysvars to RPC
pub fn generate_clientside_struct(
orig_struct_ident: &syn::Ident,
client_struct_ident: &syn::Ident,
fields: &FieldsNamed,
name: &syn::Ident,
client_struct_name: &syn::Ident,
data: &Data,
) -> TokenStream2 {
let expanded_fields = fields.named.iter().map(|field| {
let field_name = &field.ident;
match *data {
Data::Struct(DataStruct {
fields: Fields::Named(ref fields),
..
}) => {
let expanded_fields = fields.named.iter().map(|field| {
let field_name = &field.ident;
quote! {
#field_name: solitaire_client::AccEntry
}
});
quote! {
#field_name: solitaire_client::AccEntry
}
});
quote! {
/// This Solitaire-generated account represents #orig_struct_ident off-chain on client side.
pub struct #client_struct_ident {
#(pub #expanded_fields,)*
quote! {
pub struct #client_struct_name {
#(pub #expanded_fields,)*
}
}
}
_ => unimplemented!(),
}
}