solitaire: Fix prefunded accounts blocking creation

Change-Id: I8841e6595b7f8bb217991515bb9ae1ae3d4f4607
This commit is contained in:
Hendrik Hofstadt 2022-03-17 13:58:05 +01:00 committed by Evan Gray
parent 4e13d9d49c
commit 2b56fcc7da
5 changed files with 139 additions and 57 deletions

View File

@ -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);

View File

@ -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 {

View File

@ -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<T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
// Check for 0-account
if ctx.info().key == &Pubkey::new_from_array([0u8; 32]) {
trace!(&format!("Peeled {} is None, returning", std::any::type_name::<Option<T>>()));
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::<Option<T>>()
));
Ok(None)
} else {
Ok(Some(T::peel(ctx)?))
}
}
fn deps() -> Vec<Pubkey> {
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::<Option<T>>()));
Ok(())
}
} else {
trace!(&format!(
"Peeled {} is None, not persisting",
std::any::type_name::<Option<T>>()
));
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())?)

View File

@ -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<K> + 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]),
)
}
}

View File

@ -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<const Seed: &'static str> Derive<AccountInfo<'_>, 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<const Seed: &'static str, T: BorshSerialize + Owned + Default>
) -> 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(())
}