diff --git a/solana/bridge/program/src/api/post_message.rs b/solana/bridge/program/src/api/post_message.rs index 87a4e3587..1f25b6551 100644 --- a/solana/bridge/program/src/api/post_message.rs +++ b/solana/bridge/program/src/api/post_message.rs @@ -11,6 +11,7 @@ use crate::{ MathOverflow, }, types::ConsistencyLevel, + IsSigned::*, CHAIN_ID_SOLANA, }; use solana_program::{ @@ -57,8 +58,7 @@ pub struct PostMessage<'b> { pub clock: Sysvar<'b, Clock>, } -impl<'b> InstructionContext<'b> for PostMessage<'b> { -} +impl<'b> InstructionContext<'b> for PostMessage<'b> {} #[derive(BorshDeserialize, BorshSerialize)] pub struct PostMessageData { @@ -129,14 +129,15 @@ pub fn post_message( // Create message account let size = accs.message.size(); - let ix = solana_program::system_instruction::create_account( + create_account( + ctx, + accs.message.info(), accs.payer.key, - accs.message.info().key, - Exempt.amount(size), - size as u64, + Exempt, + size, ctx.program_id, - ); - solana_program::program::invoke(&ix, ctx.accounts)?; + NotSigned, + )?; // Bump sequence number trace!("New Sequence: {}", accs.sequence.sequence + 1); diff --git a/solana/bridge/program/src/api/verify_signature.rs b/solana/bridge/program/src/api/verify_signature.rs index 1c3d862a2..f3641caf0 100644 --- a/solana/bridge/program/src/api/verify_signature.rs +++ b/solana/bridge/program/src/api/verify_signature.rs @@ -9,6 +9,7 @@ use crate::{ }, GuardianSet, GuardianSetDerivationData, + IsSigned::*, SignatureSet, MAX_LEN_GUARDIAN_KEYS, }; @@ -89,9 +90,8 @@ pub fn verify_signatures( }) .collect(); - let current_instruction = solana_program::sysvar::instructions::load_current_index_checked( - &accs.instruction_acc, - )?; + let current_instruction = + solana_program::sysvar::instructions::load_current_index_checked(&accs.instruction_acc)?; if current_instruction == 0 { return Err(InstructionAtWrongIndex.into()); } @@ -176,14 +176,15 @@ pub fn verify_signatures( accs.signature_set.hash = msg_hash; let size = accs.signature_set.size(); - let ix = solana_program::system_instruction::create_account( + create_account( + ctx, + accs.signature_set.info(), accs.payer.key, - accs.signature_set.info().key, - Exempt.amount(size), - size as u64, + Exempt, + size, ctx.program_id, - ); - solana_program::program::invoke(&ix, ctx.accounts)?; + NotSigned, + )?; } else { // If the account already existed, check that the parameters match if accs.signature_set.guardian_set_index != accs.guardian_set.index { diff --git a/solana/solitaire/program/src/processors/peel.rs b/solana/solitaire/program/src/processors/peel.rs index 263700edd..e09bc3878 100644 --- a/solana/solitaire/program/src/processors/peel.rs +++ b/solana/solitaire/program/src/processors/peel.rs @@ -17,11 +17,11 @@ use solana_program::{ use std::marker::PhantomData; use crate::{ - trace, processors::seeded::{ AccountOwner, Owned, }, + trace, types::*, AccountState::MaybeInitialized, Context, @@ -45,26 +45,32 @@ pub trait Peel<'a, 'b: 'a, 'c> { /// Peel a nullable value (0-account means None) impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Option { fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { - // Check for 0-account - if ctx.info().key == &Pubkey::new_from_array([0u8; 32]) { - trace!(&format!("Peeled {} is None, returning", std::any::type_name::>())); - Ok(None) - } else { - Ok(Some(T::peel(ctx)?)) - } + // Check for 0-account + if ctx.info().key == &Pubkey::new_from_array([0u8; 32]) { + trace!(&format!( + "Peeled {} is None, returning", + std::any::type_name::>() + )); + Ok(None) + } else { + Ok(Some(T::peel(ctx)?)) + } } fn deps() -> Vec { - T::deps() + T::deps() } fn persist(&self, program_id: &Pubkey) -> Result<()> { - if let Some(s) = self.as_ref() { + if let Some(s) = self.as_ref() { T::persist(s, program_id) - } else { - trace!(&format!("Peeled {} is None, not persisting", std::any::type_name::>())); - Ok(()) - } + } else { + trace!(&format!( + "Peeled {} is None, not persisting", + std::any::type_name::>() + )); + Ok(()) + } } } @@ -226,7 +232,7 @@ impl< // If we're initializing the type, we should emit system/rent as deps. let (initialized, data): (bool, T) = match IsInitialized { AccountState::Uninitialized => { - if **ctx.info().lamports.borrow() != 0 { + if !ctx.info().data.borrow().is_empty() { return Err(SolitaireError::AlreadyInitialized(*ctx.info().key)); } (false, T::default()) @@ -235,7 +241,7 @@ impl< (true, T::try_from_slice(&mut *ctx.info().data.borrow_mut())?) } AccountState::MaybeInitialized => { - if **ctx.info().lamports.borrow() == 0 { + if ctx.info().data.borrow().is_empty() { (false, T::default()) } else { (true, T::try_from_slice(&mut *ctx.info().data.borrow_mut())?) diff --git a/solana/solitaire/program/src/processors/seeded.rs b/solana/solitaire/program/src/processors/seeded.rs index a10bb8797..ac38a8f07 100644 --- a/solana/solitaire/program/src/processors/seeded.rs +++ b/solana/solitaire/program/src/processors/seeded.rs @@ -1,5 +1,6 @@ use super::keyed::Keyed; use crate::{ + create_account, system_instruction, AccountInfo, AccountState, @@ -10,6 +11,7 @@ use crate::{ ExecutionContext, FromAccounts, Info, + IsSigned::*, Peel, Result, Signer, @@ -138,15 +140,15 @@ impl<'a, 'b: 'a, K, T: AccountSize + Seeded + Keyed<'a, 'b> + Owned> Creatabl let mut s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect(); let mut seed_slice = s.as_slice(); - let ix = system_instruction::create_account( + create_account( + ctx, + self.info(), payer, - self.info().key, - lamports.amount(size), - size as u64, + lamports, + size, &self.owner_pubkey(ctx.program_id)?, - ); - - Ok(invoke_signed(&ix, ctx.accounts, &[seed_slice])?) + SignedWithSeeds(&[seed_slice]), + ) } } diff --git a/solana/solitaire/program/src/types/accounts.rs b/solana/solitaire/program/src/types/accounts.rs index 0370616ff..31aff0ea1 100644 --- a/solana/solitaire/program/src/types/accounts.rs +++ b/solana/solitaire/program/src/types/accounts.rs @@ -7,7 +7,11 @@ use borsh::BorshSerialize; use solana_program::{ account_info::AccountInfo, - program::invoke_signed, + entrypoint::ProgramResult, + program::{ + invoke, + invoke_signed, + }, pubkey::Pubkey, system_instruction, sysvar::Sysvar as SolanaSysvar, @@ -22,7 +26,9 @@ use crate::{ CreationLamports, Derive, ExecutionContext, + Keyed, Result, + SolitaireError, }; /// A short alias for AccountInfo. @@ -35,6 +41,25 @@ pub enum AccountState { MaybeInitialized, } +/// Describes whether a cross-program invocation (CPI) should be +/// [`SignedWithSeeds`] or [`NotSigned`]. +/// +/// CPI calls inherit the signers of the original transaction, but the calling +/// program may optionally sign additional accounts using the program as the +/// signer. In this case, the signature is derived in a deterministic fashion by +/// using a set of seeds. +/// +/// For more on program signed accounts, see the *[cpi docs]. +/// +/// [cpi docs]: https://docs.solana.com/developing/programming-model/calling-between-programs#program-signed-accounts +#[derive(PartialEq, PartialOrd, Eq, Ord, Debug, Hash)] +pub enum IsSigned<'a> { + SignedWithSeeds(&'a [&'a [&'a [u8]]]), + NotSigned, +} + +use IsSigned::*; + /// An account that is known to contain serialized data. /// /// Note on const generics: @@ -92,15 +117,16 @@ impl Derive, Seed> { space: usize, owner: &Pubkey, ) -> Result<()> { - let ix = system_instruction::create_account( - payer, - self.0.key, - lamports.amount(space), - space as u64, - owner, - ); 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()) + create_account( + ctx, + self.info(), + payer, + lamports, + space, + owner, + SignedWithSeeds(&[&[Seed.as_bytes(), &[bump_seed]]]), + ) } } @@ -115,14 +141,60 @@ impl ) -> Result<()> { // Get serialized struct size let size = self.0.try_to_vec().unwrap().len(); - let ix = system_instruction::create_account( - payer, - self.0 .0.key, - lamports.amount(size), - size as u64, - ctx.program_id, - ); 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()) + create_account( + ctx, + self.info(), + payer, + lamports, + size, + ctx.program_id, + SignedWithSeeds(&[&[Seed.as_bytes(), &[bump_seed]]]), + ) } } + +/// Create an account. +/// +/// This proceeds in the following order: +/// +/// 1. Make sure the account has sufficient funds to cover the desired rent +/// period (top up if necessary). +/// 2. Allocate necessary size +/// 3. Assign ownership +/// +/// We're not using the [`system_instruction::create_account`] instruction, +/// because it refuses to create an account if there's already money in the +/// account. +pub fn create_account( + ctx: &ExecutionContext, + account: &Info<'_>, + payer: &Pubkey, + lamports: CreationLamports, + size: usize, + owner: &Pubkey, + seeds: IsSigned, +) -> Result<()> { + let target_rent = lamports.amount(size); + // top up account to target rent + if account.lamports() < target_rent { + let transfer_ix = + system_instruction::transfer(payer, account.key, target_rent - account.lamports()); + invoke(&transfer_ix, ctx.accounts)? + } + // invoke is just a synonym for invoke_signed with an empty list + let seeds = match seeds { + SignedWithSeeds(v) => v, + NotSigned => &[], + }; + + // allocate space + let allocate_ix = system_instruction::allocate(account.key, size as u64); + invoke_signed(&allocate_ix, ctx.accounts, seeds)?; + + // assign ownership + let assign_ix = system_instruction::assign(account.key, owner); + invoke_signed(&assign_ix, ctx.accounts, seeds)?; + + Ok(()) +}