Fix seed derivation and implement proper dependency system

Seeds currently did not account for the requirements of PDAs not being on the curve. This change
uses the `find_program_address` method to also be able to derive "bumped seeds" from types implementing
the Seeded trait. This is now also used for account creation.

Also the way we originally planned to pass through dependencies in Peel is not compatible with the client as some
of the context is not given there. I introduced a new method named "deps" with the same "chaining mechanic" that
can provide deps that are appended to the accounts passed into the instruction.

Change-Id: I116c8a3611e54b5a7fe5285310e4bc047119d656
This commit is contained in:
Hendrik Hofstadt 2021-06-11 14:01:31 +02:00
parent 388743fa6c
commit c3829b9266
9 changed files with 116 additions and 46 deletions

View File

@ -92,6 +92,10 @@ impl<'a, 'b: 'a, 'c, T: DeserializePayload> Peel<'a, 'b, 'c> for PayloadMessage<
Ok(PayloadMessage(data, payload)) Ok(PayloadMessage(data, payload))
} }
fn deps() -> Vec<Pubkey> {
Data::<'b, PostedMessage, { AccountState::Initialized }>::deps()
}
} }
impl<'b, T: DeserializePayload> Deref for PayloadMessage<'b, T> { impl<'b, T: DeserializePayload> Deref for PayloadMessage<'b, T> {

View File

@ -60,7 +60,6 @@ pub mod types;
// We can also re-export a set of types at module scope, this defines the intended API we expect // We can also re-export a set of types at module scope, this defines the intended API we expect
// people to be able to use from top-level. // people to be able to use from top-level.
pub use crate::processors::seeded::Owned;
pub use crate::{ pub use crate::{
error::{ error::{
ErrBox, ErrBox,
@ -72,7 +71,10 @@ pub use crate::{
keyed::Keyed, keyed::Keyed,
peel::Peel, peel::Peel,
persist::Persist, persist::Persist,
seeded::Creatable, seeded::{
Creatable,
Owned,
},
}, },
types::*, types::*,
}; };
@ -117,11 +119,7 @@ pub trait InstructionContext<'a> {
/// Trait definition that describes types that can be constructed from a list of solana account /// Trait definition that describes types that can be constructed from a list of solana account
/// references. A list of dependent accounts is produced as a side effect of the parsing stage. /// references. A list of dependent accounts is produced as a side effect of the parsing stage.
pub trait FromAccounts<'a, 'b: 'a, 'c> { pub trait FromAccounts<'a, 'b: 'a, 'c> {
fn from<T>( fn from<T>(_: &'a Pubkey, _: &'c mut Iter<'a, AccountInfo<'b>>, _: &'a T) -> Result<Self>
_: &'a Pubkey,
_: &'c mut Iter<'a, AccountInfo<'b>>,
_: &'a T,
) -> Result<(Self, Vec<Pubkey>)>
where where
Self: Sized; Self: Sized;
} }

View File

@ -29,7 +29,7 @@ macro_rules! solitaire {
/// function calls can be found below in the `api` module. /// function calls can be found below in the `api` module.
#[derive(BorshSerialize, BorshDeserialize)] #[derive(BorshSerialize, BorshDeserialize)]
enum Instruction { pub enum Instruction {
$($row($kind),)* $($row($kind),)*
} }
@ -39,7 +39,7 @@ macro_rules! solitaire {
match BorshDeserialize::try_from_slice(d).map_err(|_| SolitaireError::InstructionDeserializeFailed)? { match BorshDeserialize::try_from_slice(d).map_err(|_| SolitaireError::InstructionDeserializeFailed)? {
$( $(
Instruction::$row(ix_data) => { Instruction::$row(ix_data) => {
let (mut accounts, _deps): ($row, _) = FromAccounts::from(p, &mut a.iter(), &()).unwrap(); let (mut accounts): ($row) = FromAccounts::from(p, &mut a.iter(), &()).unwrap();
$fn(&ExecutionContext{program_id: p, accounts: a}, &mut accounts, ix_data)?; $fn(&ExecutionContext{program_id: p, accounts: a}, &mut accounts, ix_data)?;
accounts.persist(); accounts.persist();
Ok(()) Ok(())
@ -124,6 +124,10 @@ macro_rules! data_wrapper {
{ {
Data::peel(ctx).map(|v| $name(v)) Data::peel(ctx).map(|v| $name(v))
} }
fn deps() -> Vec<Pubkey> {
Data::<'_, $embed, { $state }>::deps()
}
} }
impl<'b> solitaire::processors::seeded::Owned for $name<'b> { impl<'b> solitaire::processors::seeded::Owned for $name<'b> {

View File

@ -33,6 +33,8 @@ pub trait Peel<'a, 'b: 'a, 'c> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
where where
Self: Sized; Self: Sized;
fn deps() -> Vec<Pubkey>;
} }
/// Peel a Derived Key /// Peel a Derived Key
@ -47,6 +49,9 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, const Seed: &'static str> Peel<'a, 'b,
_ => Err(SolitaireError::InvalidDerive(*ctx.info().key).into()), _ => Err(SolitaireError::InvalidDerive(*ctx.info().key).into()),
} }
} }
fn deps() -> Vec<Pubkey> {
T::deps()
}
} }
/// Peel a Signer. /// Peel a Signer.
@ -57,6 +62,9 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Signer<T> {
_ => Err(SolitaireError::InvalidSigner(*ctx.info().key).into()), _ => Err(SolitaireError::InvalidSigner(*ctx.info().key).into()),
} }
} }
fn deps() -> Vec<Pubkey> {
T::deps()
}
} }
/// Expicitly depend upon the System account. /// Expicitly depend upon the System account.
@ -67,6 +75,9 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for System<T> {
_ => panic!(), _ => panic!(),
} }
} }
fn deps() -> Vec<Pubkey> {
T::deps()
}
} }
/// Peel a Sysvar /// Peel a Sysvar
@ -83,6 +94,9 @@ where
_ => Err(SolitaireError::InvalidSysvar(*ctx.info().key).into()), _ => Err(SolitaireError::InvalidSysvar(*ctx.info().key).into()),
} }
} }
fn deps() -> Vec<Pubkey> {
vec![]
}
} }
/// This is our structural recursion base case, the trait system will stop generating new nested /// This is our structural recursion base case, the trait system will stop generating new nested
@ -91,6 +105,9 @@ impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for Info<'b> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> { fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
Ok(ctx.info().clone()) Ok(ctx.info().clone())
} }
fn deps() -> Vec<Pubkey> {
vec![]
}
} }
/// This is our structural recursion base case, the trait system will stop generating new nested /// This is our structural recursion base case, the trait system will stop generating new nested
@ -103,9 +120,6 @@ impl<'a, 'b: 'a, 'c, T: BorshDeserialize + Owned + Default, const IsInitialized:
// If we're initializing the type, we should emit system/rent as deps. // If we're initializing the type, we should emit system/rent as deps.
let data: T = match IsInitialized { let data: T = match IsInitialized {
AccountState::Uninitialized => { AccountState::Uninitialized => {
ctx.deps.push(sysvar::rent::ID);
ctx.deps.push(system_program::ID);
if **ctx.info().lamports.borrow() != 0 { if **ctx.info().lamports.borrow() != 0 {
return Err(SolitaireError::AlreadyInitialized(*ctx.info().key)); return Err(SolitaireError::AlreadyInitialized(*ctx.info().key));
} }
@ -116,9 +130,6 @@ impl<'a, 'b: 'a, 'c, T: BorshDeserialize + Owned + Default, const IsInitialized:
T::try_from_slice(&mut *ctx.info().data.borrow_mut())? T::try_from_slice(&mut *ctx.info().data.borrow_mut())?
} }
AccountState::MaybeInitialized => { AccountState::MaybeInitialized => {
ctx.deps.push(sysvar::rent::ID);
ctx.deps.push(system_program::ID);
if **ctx.info().lamports.borrow() == 0 { if **ctx.info().lamports.borrow() == 0 {
T::default() T::default()
} else { } else {
@ -146,4 +157,12 @@ impl<'a, 'b: 'a, 'c, T: BorshDeserialize + Owned + Default, const IsInitialized:
Ok(Data(ctx.info().clone(), data)) Ok(Data(ctx.info().clone(), data))
} }
fn deps() -> Vec<Pubkey> {
if IsInitialized == AccountState::Initialized {
return vec![];
}
vec![sysvar::rent::ID, system_program::ID]
}
} }

View File

@ -59,6 +59,16 @@ impl<'a, T: Owned + Default, const IsInitialized: AccountState> Owned
pub trait Seeded<I> { pub trait Seeded<I> {
fn seeds(&self, accs: I) -> Vec<Vec<u8>>; fn seeds(&self, accs: I) -> Vec<Vec<u8>>;
fn bumped_seeds(&self, accs: I, program_id: &Pubkey) -> Vec<Vec<u8>> {
let mut seeds = self.seeds(accs);
let mut s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let mut seed_slice = s.as_slice();
let (_, bump_seed) = Pubkey::find_program_address(seed_slice, program_id);
seeds.push(vec![bump_seed]);
seeds
}
fn verify_derivation<'a, 'b: 'a>(&'a self, program_id: &'a Pubkey, accs: I) -> Result<()> fn verify_derivation<'a, 'b: 'a>(&'a self, program_id: &'a Pubkey, accs: I) -> Result<()>
where where
Self: Keyed<'a, 'b>, Self: Keyed<'a, 'b>,
@ -102,11 +112,11 @@ impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed<'a, 'b> + Owned> Creatabl
payer: &'a Pubkey, payer: &'a Pubkey,
lamports: CreationLamports, lamports: CreationLamports,
) -> Result<()> { ) -> Result<()> {
let seeds = self.seeds(accs); let seeds = self.bumped_seeds(accs, ctx.program_id);
let size = self.size(); let size = self.size();
let s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect(); let mut s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let seed_slice = s.as_slice(); let mut seed_slice = s.as_slice();
let ix = system_instruction::create_account( let ix = system_instruction::create_account(
payer, payer,

View File

@ -45,9 +45,9 @@ pub enum AccountState {
/// ///
/// Data<(), { AccountState::Uninitialized }> /// Data<(), { AccountState::Uninitialized }>
#[rustfmt::skip] #[rustfmt::skip]
pub struct Data < 'r, T: Owned + Default + Default, const IsInitialized: AccountState> ( pub struct Data<'r, T: Owned + Default + Default, const IsInitialized: AccountState> (
pub Info<'r >, pub Info<'r>,
pub T, pub T,
); );
impl<'r, T: Owned + Default, const IsInitialized: AccountState> Deref impl<'r, T: Owned + Default, const IsInitialized: AccountState> Deref
@ -99,7 +99,8 @@ impl<const Seed: &'static str> Derive<AccountInfo<'_>, Seed> {
space as u64, space as u64,
owner, owner,
); );
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]]).map_err(|e| e.into()) let (_, bump_seed) = Pubkey::find_program_address(&[Seed.as_bytes()][..], ctx.program_id);
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes(), &[bump_seed]]]).map_err(|e| e.into())
} }
} }
@ -121,6 +122,7 @@ impl<const Seed: &'static str, T: BorshSerialize + Owned + Default>
size as u64, size as u64,
ctx.program_id, ctx.program_id,
); );
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]]).map_err(|e| e.into()) let (_, bump_seed) = Pubkey::find_program_address(&[Seed.as_bytes()][..], ctx.program_id);
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes(), &[bump_seed]]]).map_err(|e| e.into())
} }
} }

View File

@ -19,26 +19,15 @@ pub struct Context<'a, 'b: 'a, 'c, T> {
/// account for. /// account for.
pub data: &'a T, pub data: &'a T,
/// This is a list of dependent keys that are emitted by this verification pipeline. This
/// allows things such as `rent`/`system` to be emitted as required for an account without
/// having to specify them in the original instruction account data.
pub deps: &'c mut Vec<Pubkey>,
pub info: Option<&'a AccountInfo<'b>>, pub info: Option<&'a AccountInfo<'b>>,
} }
impl<'a, 'b: 'a, 'c, T> Context<'a, 'b, 'c, T> { impl<'a, 'b: 'a, 'c, T> Context<'a, 'b, 'c, T> {
pub fn new( pub fn new(program: &'a Pubkey, iter: &'c mut Iter<'a, AccountInfo<'b>>, data: &'a T) -> Self {
program: &'a Pubkey,
iter: &'c mut Iter<'a, AccountInfo<'b>>,
data: &'a T,
deps: &'c mut Vec<Pubkey>,
) -> Self {
Context { Context {
this: program, this: program,
iter, iter,
data, data,
deps,
info: None, info: None,
} }
} }

View File

@ -66,7 +66,6 @@ pub fn derive_to_instruction(input: TokenStream) -> TokenStream {
} }
let (combined_impl_g, _, _) = combined_generics.split_for_impl(); let (combined_impl_g, _, _) = combined_generics.split_for_impl();
let from_method = generate_fields(&name, &input.data);
let expanded = generate_to_instruction(&name, &combined_impl_g, &input.data); let expanded = generate_to_instruction(&name, &combined_impl_g, &input.data);
TokenStream::from(expanded) TokenStream::from(expanded)
} }
@ -108,25 +107,28 @@ pub fn derive_from_accounts(input: TokenStream) -> TokenStream {
let from_method = generate_fields(&name, &input.data); let from_method = generate_fields(&name, &input.data);
let persist_method = generate_persist(&name, &input.data); let persist_method = generate_persist(&name, &input.data);
let deps_method = generate_deps_fields(&name, &input.data);
let expanded = quote! { let expanded = quote! {
/// Macro generated implementation of FromAccounts by Solitaire. /// Macro generated implementation of FromAccounts by Solitaire.
impl #combined_impl_g solitaire::FromAccounts #peel_type_g for #name #type_g { impl #combined_impl_g solitaire::FromAccounts #peel_type_g for #name #type_g {
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, Vec<solana_program::pubkey::Pubkey>)> { 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 #from_method
} }
} }
impl #combined_impl_g solitaire::Peel<'a, 'b, 'c> for #name #type_g { 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 { 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).map(|v| v.0)?; let v: #name #type_g = FromAccounts::from(ctx.this, ctx.iter, ctx.data)?;
// Verify the instruction constraints // Verify the instruction constraints
solitaire::InstructionContext::verify(&v, ctx.this)?; solitaire::InstructionContext::verify(&v, ctx.this)?;
// Append instruction level dependencies
ctx.deps.append(&mut solitaire::InstructionContext::deps(&v));
Ok(v) Ok(v)
} }
fn deps() -> Vec<solana_program::pubkey::Pubkey> {
#deps_method
}
} }
/// Macro generated implementation of Persist by Solitaire. /// Macro generated implementation of Persist by Solitaire.
@ -166,7 +168,6 @@ fn generate_fields(name: &syn::Ident, data: &Data) -> TokenStream2 {
pid, pid,
iter, iter,
data, data,
&mut deps,
))?; ))?;
} }
}); });
@ -179,9 +180,47 @@ fn generate_fields(name: &syn::Ident, data: &Data) -> TokenStream2 {
// Write out our iterator and return the filled structure. // Write out our iterator and return the filled structure.
quote! { quote! {
use solana_program::account_info::next_account_info; use solana_program::account_info::next_account_info;
#(#recurse;)*
Ok(#name { #(#names,)* })
}
}
Fields::Unnamed(_) => {
unimplemented!()
}
Fields::Unit => {
unimplemented!()
}
}
}
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}
/// This function does the heavy lifting of generating the field parsers.
fn generate_deps_fields(name: &syn::Ident, data: &Data) -> TokenStream2 {
match *data {
// We only care about structures.
Data::Struct(ref data) => {
// We want to inspect its fields.
match data.fields {
// For now, we only care about struct { a: T } forms, not struct(T);
Fields::Named(ref fields) => {
// For each field, generate an expression appends it deps
let recurse = fields.named.iter().map(|f| {
let ty = &f.ty;
quote! {
deps.append(&mut <#ty as Peel>::deps());
}
});
// Write out our iterator and return the filled structure.
quote! {
let mut deps = Vec::new(); let mut deps = Vec::new();
#(#recurse;)* #(#recurse;)*
Ok((#name { #(#names,)* }, deps)) deps
} }
} }
@ -221,7 +260,6 @@ fn generate_persist(name: &syn::Ident, data: &Data) -> TokenStream2 {
pid, pid,
iter, iter,
data, data,
&mut deps,
))?; ))?;
} }
}); });
@ -234,9 +272,8 @@ fn generate_persist(name: &syn::Ident, data: &Data) -> TokenStream2 {
// Write out our iterator and return the filled structure. // Write out our iterator and return the filled structure.
quote! { quote! {
use solana_program::account_info::next_account_info; use solana_program::account_info::next_account_info;
let mut deps = Vec::new();
#(#recurse;)* #(#recurse;)*
Ok((#name { #(#names,)* }, deps)) Ok(#name { #(#names,)* })
} }
} }

View File

@ -37,6 +37,7 @@ pub fn generate_to_instruction(
let ty = &field.ty; let ty = &field.ty;
quote! { quote! {
deps.append(&mut <#ty as solitaire::Peel>::deps());
account_metas.append(&mut <#ty as solitaire_client::Wrap>::wrap(&self.#name)?); account_metas.append(&mut <#ty as solitaire_client::Wrap>::wrap(&self.#name)?);
if let Some(pair) = <#ty as solitaire_client::Wrap>::keypair(self.#name) { if let Some(pair) = <#ty as solitaire_client::Wrap>::keypair(self.#name) {
signers.push(pair); signers.push(pair);
@ -66,8 +67,14 @@ pub fn generate_to_instruction(
use solana_program::{pubkey::Pubkey, instruction::Instruction}; use solana_program::{pubkey::Pubkey, instruction::Instruction};
let mut account_metas = Vec::new(); let mut account_metas = Vec::new();
let mut signers = Vec::new(); let mut signers = Vec::new();
let mut deps = Vec::new();
#(#expanded_appends;)* #(#expanded_appends;)*
// 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);
Ok((solana_program::instruction::Instruction::new_with_bytes(program_id, Ok((solana_program::instruction::Instruction::new_with_bytes(program_id,
ix_data, ix_data,