solitaire: remove recursive FromAccounts and 'c lifetime.

Solitaire parses accounts using two traits, FromAccounts and Peel. The
FromAccounts derive macro generates a function, `FromAccounts::from()`,
which calls `Peel::peel` to construct each field in the struct:

```
let example = Example {
    foo: Peel::peel(next_account_iter(accs)?),
    bar: Peel::peel(next_account_iter(accs)?),
    baz: Peel::peel(next_account_iter(accs)?),
    ...,
}
```

The FromAccounts derivation also attempts to implement Peel for the
structure itself however, which means `Example` itself is embeddable as
a field in another accounts struct. This is only used in ClaimableVAA
which is a large source of confusion and the complexity is not worth
maintaining.

This commit removes the recursion by:

1) Removing the `impl Peel` derived by FromAccounts.
2) Removes the AccountInfo iterator from Context as it is no longer needed.
3) Adds the current parsed account to Context instead, safe thanks to (2)
4) Move message out of ClaimableVAA and pass in to verify everywhere instead.
5) Removes Peel/FromAccounts from ClaimableVAA.
This commit is contained in:
Reisen 2022-05-30 18:55:54 +00:00 committed by Csongor Kiss
parent ec3bcfb40c
commit 82ea938317
12 changed files with 190 additions and 212 deletions

View File

@ -33,20 +33,18 @@ use crate::{
},
vaa::ClaimableVAA,
DeserializePayload,
PayloadMessage,
CHAIN_ID_SOLANA,
};
fn verify_governance<T>(vaa: &ClaimableVAA<T>) -> Result<()>
fn verify_governance<T>(vaa: &PayloadMessage<T>) -> Result<()>
where
T: DeserializePayload,
{
let expected_emitter = std::env!("EMITTER_ADDRESS");
let current_emitter = format!(
"{}",
Pubkey::new_from_array(vaa.message.meta().emitter_address)
);
let current_emitter = format!("{}", Pubkey::new_from_array(vaa.meta().emitter_address));
// Fail if the emitter is not the known governance key, or the emitting chain is not Solana.
if expected_emitter != current_emitter || vaa.message.meta().emitter_chain != CHAIN_ID_SOLANA {
if expected_emitter != current_emitter || vaa.meta().emitter_chain != CHAIN_ID_SOLANA {
Err(InvalidGovernanceKey.into())
} else {
Ok(())
@ -62,7 +60,10 @@ pub struct UpgradeContract<'b> {
pub bridge: Mut<Bridge<'b, { AccountState::Initialized }>>,
/// GuardianSet change VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadUpgrade>,
pub vaa: PayloadMessage<'b, GovernancePayloadUpgrade>,
/// Claim account representing whether the vaa has already been consumed.
pub vaa_claim: ClaimableVAA<'b>,
/// PDA authority for the loader
pub upgrade_authority: Derive<Info<'b>, "upgrade">,
@ -95,12 +96,11 @@ pub fn upgrade_contract(
_data: UpgradeContractData,
) -> Result<()> {
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
let upgrade_ix = solana_program::bpf_loader_upgradeable::upgrade(
ctx.program_id,
&accs.vaa.message.new_contract,
&accs.vaa.new_contract,
accs.upgrade_authority.key,
accs.spill.key,
);
@ -124,7 +124,10 @@ pub struct UpgradeGuardianSet<'b> {
pub bridge: Mut<Bridge<'b, { AccountState::Initialized }>>,
/// GuardianSet change VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadGuardianSetChange>,
pub vaa: PayloadMessage<'b, GovernancePayloadGuardianSetChange>,
/// Claim account representing whether the vaa has already been consumed.
pub vaa_claim: ClaimableVAA<'b>,
/// Old guardian set
pub guardian_set_old: Mut<GuardianSet<'b, { AccountState::Initialized }>>,
@ -152,7 +155,6 @@ pub fn upgrade_guardian_set(
}
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.guardian_set_old.verify_derivation(
ctx.program_id,
&GuardianSetDerivationData {
@ -166,7 +168,7 @@ pub fn upgrade_guardian_set(
},
)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
// Set expiration time for the old set
accs.guardian_set_old.expiration_time =
@ -203,7 +205,10 @@ pub struct SetFees<'b> {
pub bridge: Mut<Bridge<'b, { AccountState::Initialized }>>,
/// Governance VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadSetMessageFee>,
pub vaa: PayloadMessage<'b, GovernancePayloadSetMessageFee>,
/// Claim account representing whether the vaa has already been consumed.
pub vaa_claim: ClaimableVAA<'b>,
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
@ -211,8 +216,7 @@ pub struct SetFeesData {}
pub fn set_fees(ctx: &ExecutionContext, accs: &mut SetFees, _data: SetFeesData) -> Result<()> {
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
accs.bridge.config.fee = accs.vaa.fee.as_u64();
Ok(())
@ -227,7 +231,10 @@ pub struct TransferFees<'b> {
pub bridge: Mut<Bridge<'b, { AccountState::Initialized }>>,
/// Governance VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadTransferFees>,
pub vaa: PayloadMessage<'b, GovernancePayloadTransferFees>,
/// Claim account representing whether the vaa has already been consumed.
pub vaa_claim: ClaimableVAA<'b>,
/// Account collecting tx fees
pub fee_collector: Mut<Derive<Info<'b>, "fee_collector">>,
@ -266,7 +273,8 @@ pub fn transfer_fees(
accs.bridge.last_lamports = new_balance;
accs.vaa.claim(ctx, accs.payer.key)?;
verify_governance(&accs.vaa)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
// Transfer fees
let transfer_ix = solana_program::system_instruction::transfer(

View File

@ -115,8 +115,8 @@ pub struct PayloadMessage<'b, T: DeserializePayload>(
T,
);
impl<'a, 'b: 'a, 'c, T: DeserializePayload> Peel<'a, 'b, 'c> for PayloadMessage<'b, T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
impl<'a, 'b: 'a, T: DeserializePayload> Peel<'a, 'b> for PayloadMessage<'b, T> {
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self>
where
Self: Sized,
{
@ -148,62 +148,73 @@ impl<'b, T: DeserializePayload> PayloadMessage<'b, T> {
}
}
#[derive(FromAccounts)]
pub struct ClaimableVAA<'b, T: DeserializePayload> {
// Signed message for the transfer
pub message: PayloadMessage<'b, T>,
/// Claim account to prevent double spending.
pub struct ClaimableVAA<'b>(Mut<Claim<'b, { AccountState::Uninitialized }>>);
// Claim account to prevent double spending
pub claim: Mut<Claim<'b, { AccountState::Uninitialized }>>,
}
impl<'a, 'b: 'a> Peel<'a, 'b> for ClaimableVAA<'b> {
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
Mut::<Claim<'b, { AccountState::Uninitialized }>>::peel(ctx).map(|c| Self(c))
}
impl<'b, T: DeserializePayload> Deref for ClaimableVAA<'b, T> {
type Target = PayloadMessage<'b, T>;
fn deref(&self) -> &Self::Target {
&self.message
fn persist(&self, program_id: &Pubkey) -> Result<()> {
Mut::<Claim<'b, { AccountState::Uninitialized }>>::persist(&self.0, program_id)
}
}
impl<'b, T: DeserializePayload> ClaimableVAA<'b, T> {
pub fn verify(&self, program_id: &Pubkey) -> Result<()> {
trace!("Seq: {}", self.message.meta().sequence);
impl<'b> ClaimableVAA<'b> {
pub fn verify<T>(&self, ctx: &ExecutionContext, message: &PayloadMessage<'b, T>) -> Result<()>
where
T: DeserializePayload,
{
trace!("Seq: {}", message.meta().sequence);
// Verify that the claim account is derived correctly
self.claim.verify_derivation(
program_id,
self.0.verify_derivation(
ctx.program_id,
&ClaimDerivationData {
emitter_address: self.message.meta().emitter_address,
emitter_chain: self.message.meta().emitter_chain,
sequence: self.message.meta().sequence,
emitter_address: message.meta().emitter_address,
emitter_chain: message.meta().emitter_chain,
sequence: message.meta().sequence,
},
)?;
Ok(())
}
}
impl<'b, T: DeserializePayload> ClaimableVAA<'b, T> {
pub fn is_claimed(&self) -> bool {
self.claim.claimed
self.0.claimed
}
pub fn claim(&mut self, ctx: &ExecutionContext, payer: &Pubkey) -> Result<()> {
pub fn claim<T>(
&mut self,
ctx: &ExecutionContext,
payer: &Pubkey,
message: &PayloadMessage<'b, T>,
) -> Result<()>
where
T: DeserializePayload,
{
// Verify that the claim account is derived correctly before claiming.
self.verify(ctx, message)?;
// Exit if the claim has already been made.
if self.is_claimed() {
return Err(VAAAlreadyExecuted.into());
}
self.claim.create(
// Claim the account by initializing it with a true boolean.
self.0.create(
&ClaimDerivationData {
emitter_address: self.message.meta().emitter_address,
emitter_chain: self.message.meta().emitter_chain,
sequence: self.message.meta().sequence,
emitter_address: message.meta().emitter_address,
emitter_chain: message.meta().emitter_chain,
sequence: message.meta().sequence,
},
ctx,
payer,
Exempt,
)?;
self.claim.claimed = true;
self.0.claimed = true;
Ok(())
}

View File

@ -44,7 +44,8 @@ pub struct CompleteNative<'b> {
pub payer: Mut<Signer<AccountInfo<'b>>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub vaa: ClaimableVAA<'b, PayloadTransfer>,
pub vaa: PayloadMessage<'b, PayloadTransfer>,
pub vaa_claim: ClaimableVAA<'b>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
pub to: Mut<Data<'b, SplAccount, { AccountState::MaybeInitialized }>>,
@ -119,8 +120,7 @@ pub fn complete_native(
}
// Prevent vaa double signing
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
if !accs.to.is_initialized() {
let associated_addr = spl_associated_token_account::get_associated_token_address(
@ -161,7 +161,8 @@ pub struct CompleteWrapped<'b> {
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
// Signed message for the transfer
pub vaa: ClaimableVAA<'b, PayloadTransfer>,
pub vaa: PayloadMessage<'b, PayloadTransfer>,
pub vaa_claim: ClaimableVAA<'b>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
@ -226,8 +227,7 @@ pub fn complete_wrapped(
return Err(InvalidRecipient.into());
}
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
// Initialize the NFT if it doesn't already exist
if !accs.meta.is_initialized() {

View File

@ -18,6 +18,7 @@ use bridge::{
ClaimableVAA,
DeserializePayload,
},
PayloadMessage,
CHAIN_ID_SOLANA,
};
use solana_program::{
@ -36,17 +37,14 @@ use solitaire::{
};
// Confirm that a ClaimableVAA came from the correct chain, signed by the right emitter.
fn verify_governance<T>(vaa: &ClaimableVAA<T>) -> Result<()>
fn verify_governance<T>(vaa: &PayloadMessage<T>) -> Result<()>
where
T: DeserializePayload,
{
let expected_emitter = std::env!("EMITTER_ADDRESS");
let current_emitter = format!(
"{}",
Pubkey::new_from_array(vaa.message.meta().emitter_address)
);
let current_emitter = format!("{}", Pubkey::new_from_array(vaa.meta().emitter_address));
// Fail if the emitter is not the known governance key, or the emitting chain is not Solana.
if expected_emitter != current_emitter || vaa.message.meta().emitter_chain != CHAIN_ID_SOLANA {
if expected_emitter != current_emitter || vaa.meta().emitter_chain != CHAIN_ID_SOLANA {
Err(InvalidGovernanceKey.into())
} else {
Ok(())
@ -59,7 +57,10 @@ pub struct UpgradeContract<'b> {
pub payer: Mut<Signer<Info<'b>>>,
/// GuardianSet change VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadUpgrade>,
pub vaa: PayloadMessage<'b, GovernancePayloadUpgrade>,
/// Claim account representing whether the vaa has already been consumed.
pub vaa_claim: ClaimableVAA<'b>,
/// PDA authority for the loader
pub upgrade_authority: Derive<Info<'b>, "upgrade">,
@ -92,13 +93,11 @@ pub fn upgrade_contract(
_data: UpgradeContractData,
) -> Result<()> {
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
let upgrade_ix = solana_program::bpf_loader_upgradeable::upgrade(
ctx.program_id,
&accs.vaa.message.new_contract,
&accs.vaa.new_contract,
accs.upgrade_authority.key,
accs.spill.key,
);
@ -117,10 +116,9 @@ pub fn upgrade_contract(
pub struct RegisterChain<'b> {
pub payer: Mut<Signer<AccountInfo<'b>>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub endpoint: Mut<Endpoint<'b, { AccountState::Uninitialized }>>,
pub vaa: ClaimableVAA<'b, PayloadGovernanceRegisterChain>,
pub vaa: PayloadMessage<'b, PayloadGovernanceRegisterChain>,
pub vaa_claim: ClaimableVAA<'b>,
}
impl<'a> From<&RegisterChain<'a>> for EndpointDerivationData {
@ -146,8 +144,7 @@ pub fn register_chain(
// Claim VAA
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
if accs.vaa.chain == CHAIN_ID_SOLANA {
return Err(InvalidChain.into());

View File

@ -19,6 +19,7 @@ use crate::{
};
use bridge::{
vaa::ClaimableVAA,
PayloadMessage,
CHAIN_ID_SOLANA,
};
use solana_program::account_info::AccountInfo;
@ -35,7 +36,8 @@ pub struct CompleteNative<'b> {
pub payer: Mut<Signer<AccountInfo<'b>>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub vaa: ClaimableVAA<'b, PayloadTransfer>,
pub vaa: PayloadMessage<'b, PayloadTransfer>,
pub vaa_claim: ClaimableVAA<'b>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
pub to: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
@ -108,13 +110,12 @@ pub fn complete_native(
if accs.vaa.to != accs.to.info().key.to_bytes() {
return Err(InvalidRecipient.into());
}
if INVALID_VAAS.contains(&&*accs.vaa.message.info().key.to_string()) {
if INVALID_VAAS.contains(&&*accs.vaa.info().key.to_string()) {
return Err(InvalidVAA.into());
}
// Prevent vaa double signing
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
let mut amount = accs.vaa.amount.as_u64();
let mut fee = accs.vaa.fee.as_u64();
@ -156,7 +157,8 @@ pub struct CompleteWrapped<'b> {
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
// Signed message for the transfer
pub vaa: ClaimableVAA<'b, PayloadTransfer>,
pub vaa: PayloadMessage<'b, PayloadTransfer>,
pub vaa_claim: ClaimableVAA<'b>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
@ -227,12 +229,11 @@ pub fn complete_wrapped(
if accs.vaa.to != accs.to.info().key.to_bytes() {
return Err(InvalidRecipient.into());
}
if INVALID_VAAS.contains(&&*accs.vaa.message.info().key.to_string()) {
if INVALID_VAAS.contains(&&*accs.vaa.info().key.to_string()) {
return Err(InvalidVAA.into());
}
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
// Mint tokens
let mint_ix = spl_token::instruction::mint_to(

View File

@ -18,6 +18,7 @@ use crate::{
};
use bridge::{
vaa::ClaimableVAA,
PayloadMessage,
CHAIN_ID_SOLANA,
};
use solana_program::account_info::AccountInfo;
@ -94,7 +95,8 @@ pub struct CompleteNativeWithPayload<'b> {
pub payer: Mut<Signer<AccountInfo<'b>>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub vaa: ClaimableVAA<'b, PayloadTransferWithPayload>,
pub vaa: PayloadMessage<'b, PayloadTransferWithPayload>,
pub vaa_claim: ClaimableVAA<'b>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
pub to: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
@ -179,8 +181,7 @@ pub fn complete_native_with_payload(
}
// Prevent vaa double signing
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
let mut amount = accs.vaa.amount.as_u64();
@ -209,7 +210,8 @@ pub struct CompleteWrappedWithPayload<'b> {
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
/// Signed message for the transfer
pub vaa: ClaimableVAA<'b, PayloadTransferWithPayload>,
pub vaa: PayloadMessage<'b, PayloadTransferWithPayload>,
pub vaa_claim: ClaimableVAA<'b>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
@ -291,8 +293,7 @@ pub fn complete_wrapped_with_payload(
return Err(InvalidRecipient.into());
}
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
// Mint tokens
let mint_ix = spl_token::instruction::mint_to(

View File

@ -21,6 +21,7 @@ use crate::{
};
use bridge::{
vaa::ClaimableVAA,
PayloadMessage,
CHAIN_ID_SOLANA,
};
use solana_program::{
@ -48,7 +49,8 @@ pub struct CreateWrapped<'b> {
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
pub vaa: ClaimableVAA<'b, PayloadAssetMeta>,
pub vaa: PayloadMessage<'b, PayloadAssetMeta>,
pub vaa_claim: ClaimableVAA<'b>,
// New Wrapped
pub mint: Mut<WrappedMint<'b, { AccountState::MaybeInitialized }>>,
@ -111,12 +113,11 @@ pub fn create_wrapped(
accs.chain_registration
.verify_derivation(ctx.program_id, &derivation_data)?;
if INVALID_VAAS.contains(&&*accs.vaa.message.info().key.to_string()) {
if INVALID_VAAS.contains(&&*accs.vaa.info().key.to_string()) {
return Err(InvalidVAA.into());
}
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
if accs.mint.is_initialized() {
update_accounts(ctx, accs, data)

View File

@ -19,6 +19,7 @@ use bridge::{
ClaimableVAA,
DeserializePayload,
},
PayloadMessage,
CHAIN_ID_SOLANA,
};
use solana_program::{
@ -36,18 +37,15 @@ use solitaire::{
*,
};
/// Confirm that a ClaimableVAA came from the correct chain, signed by the right emitter.
fn verify_governance<T>(vaa: &ClaimableVAA<T>) -> Result<()>
// Confirm that a ClaimableVAA came from the correct chain, signed by the right emitter.
fn verify_governance<T>(vaa: &PayloadMessage<T>) -> Result<()>
where
T: DeserializePayload,
{
let expected_emitter = std::env!("EMITTER_ADDRESS");
let current_emitter = format!(
"{}",
Pubkey::new_from_array(vaa.message.meta().emitter_address)
);
let current_emitter = format!("{}", Pubkey::new_from_array(vaa.meta().emitter_address));
// Fail if the emitter is not the known governance key, or the emitting chain is not Solana.
if expected_emitter != current_emitter || vaa.message.meta().emitter_chain != CHAIN_ID_SOLANA {
if expected_emitter != current_emitter || vaa.meta().emitter_chain != CHAIN_ID_SOLANA {
Err(InvalidGovernanceKey.into())
} else {
Ok(())
@ -60,7 +58,8 @@ pub struct UpgradeContract<'b> {
pub payer: Mut<Signer<Info<'b>>>,
/// GuardianSet change VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadUpgrade>,
pub vaa: PayloadMessage<'b, GovernancePayloadUpgrade>,
pub vaa_claim: ClaimableVAA<'b>,
/// PDA authority for the loader
pub upgrade_authority: Derive<Info<'b>, "upgrade">,
@ -92,17 +91,16 @@ pub fn upgrade_contract(
accs: &mut UpgradeContract,
_data: UpgradeContractData,
) -> Result<()> {
if INVALID_VAAS.contains(&&*accs.vaa.message.info().key.to_string()) {
if INVALID_VAAS.contains(&&*accs.vaa.info().key.to_string()) {
return Err(InvalidVAA.into());
}
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
let upgrade_ix = solana_program::bpf_loader_upgradeable::upgrade(
ctx.program_id,
&accs.vaa.message.new_contract,
&accs.vaa.new_contract,
accs.upgrade_authority.key,
accs.spill.key,
);
@ -124,7 +122,8 @@ pub struct RegisterChain<'b> {
pub endpoint: Mut<Endpoint<'b, { AccountState::Uninitialized }>>,
pub vaa: ClaimableVAA<'b, PayloadGovernanceRegisterChain>,
pub vaa: PayloadMessage<'b, PayloadGovernanceRegisterChain>,
pub vaa_claim: ClaimableVAA<'b>,
}
impl<'a> From<&RegisterChain<'a>> for EndpointDerivationData {
@ -148,14 +147,13 @@ pub fn register_chain(
accs.endpoint
.verify_derivation(ctx.program_id, &derivation_data)?;
if INVALID_VAAS.contains(&&*accs.vaa.message.info().key.to_string()) {
if INVALID_VAAS.contains(&&*accs.vaa.info().key.to_string()) {
return Err(InvalidVAA.into());
}
// Claim VAA
verify_governance(&accs.vaa)?;
accs.vaa.verify(ctx.program_id)?;
accs.vaa.claim(ctx, accs.payer.key)?;
accs.vaa_claim.claim(ctx, accs.payer.key, &accs.vaa)?;
// Create endpoint
accs.endpoint

View File

@ -83,8 +83,8 @@ impl CreationLamports {
/// 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.
pub trait FromAccounts<'a, 'b: 'a, 'c> {
fn from<T>(_: &'a Pubkey, _: &'c mut Iter<'a, AccountInfo<'b>>, _: &'a T) -> Result<Self>
pub trait FromAccounts<'a, 'b: 'a> {
fn from<T>(_: &'a Pubkey, _: &mut Iter<'a, AccountInfo<'b>>, _: &'a T) -> Result<Self>
where
Self: Sized;
}

View File

@ -25,8 +25,8 @@ use borsh::BorshSerialize;
/// Generic Peel trait. This provides a way to describe what each "peeled"
/// layer of our constraints should check.
pub trait Peel<'a, 'b: 'a, 'c> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
pub trait Peel<'a, 'b: 'a> {
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self>
where
Self: Sized;
@ -34,10 +34,10 @@ 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> {
impl<'a, 'b: 'a, T: Peel<'a, 'b>> Peel<'a, 'b> for Option<T> {
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
// Check for 0-account
if ctx.info().key == &Pubkey::new_from_array([0u8; 32]) {
if ctx.info.key == &Pubkey::new_from_array([0u8; 32]) {
trace!(&format!(
"Peeled {} is None, returning",
std::any::type_name::<Option<T>>()
@ -62,15 +62,13 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Option<T> {
}
/// Peel a Derived Key
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, const SEED: &'static str> Peel<'a, 'b, 'c>
for Derive<T, SEED>
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
// Attempt to Derive Seed
impl<'a, 'b: 'a, T: Peel<'a, 'b>, const SEED: &'static str> Peel<'a, 'b> for Derive<T, SEED> {
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
// Attempt to Derive SEED
let (derived, _bump) = Pubkey::find_program_address(&[SEED.as_ref()], ctx.this);
match derived == *ctx.info().key {
match derived == *ctx.info.key {
true => T::peel(ctx).map(|v| Derive(v)),
_ => Err(SolitaireError::InvalidDerive(*ctx.info().key, derived)),
_ => Err(SolitaireError::InvalidDerive(*ctx.info.key, derived)),
}
}
@ -80,14 +78,15 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, const SEED: &'static str> Peel<'a, 'b,
}
/// Peel a Mutable key.
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> {
impl<'a, 'b: 'a, T: Peel<'a, 'b>> Peel<'a, 'b> for Mut<T> {
fn peel<I>(mut ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
ctx.immutable = false;
match ctx.info().is_writable {
match ctx.info.is_writable {
true => T::peel(ctx).map(|v| Mut(v)),
_ => Err(
SolitaireError::InvalidMutability(*ctx.info().key, ctx.info().is_writable),
),
_ => Err(SolitaireError::InvalidMutability(
*ctx.info.key,
ctx.info.is_writable,
)),
}
}
@ -96,9 +95,9 @@ 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 MaybeMut<T> {
fn peel<I>(mut ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
ctx.immutable = !ctx.info().is_writable;
impl<'a, 'b: 'a, T: Peel<'a, 'b>> Peel<'a, 'b> for MaybeMut<T> {
fn peel<I>(mut ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
ctx.immutable = !ctx.info.is_writable;
T::peel(ctx).map(|v| MaybeMut(v))
}
@ -108,11 +107,11 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for MaybeMut<T> {
}
/// Peel a Signer.
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Signer<T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
match ctx.info().is_signer {
impl<'a, 'b: 'a, T: Peel<'a, 'b>> Peel<'a, 'b> for Signer<T> {
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
match ctx.info.is_signer {
true => T::peel(ctx).map(|v| Signer(v)),
_ => Err(SolitaireError::InvalidSigner(*ctx.info().key)),
_ => Err(SolitaireError::InvalidSigner(*ctx.info.key)),
}
}
@ -122,8 +121,8 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for Signer<T> {
}
/// Expicitly depend upon the System account.
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for System<T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
impl<'a, 'b: 'a, T: Peel<'a, 'b>> Peel<'a, 'b> for System<T> {
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
match true {
true => T::peel(ctx).map(|v| System(v)),
_ => panic!(),
@ -136,17 +135,14 @@ impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>> Peel<'a, 'b, 'c> for System<T> {
}
/// Peel a Sysvar
impl<'a, 'b: 'a, 'c, Var> Peel<'a, 'b, 'c> for Sysvar<'b, Var>
impl<'a, 'b: 'a, Var> Peel<'a, 'b> for Sysvar<'b, Var>
where
Var: SolanaSysvar,
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
match Var::check_id(ctx.info().key) {
true => Ok(Sysvar(
ctx.info().clone(),
Var::from_account_info(ctx.info())?,
)),
_ => Err(SolitaireError::InvalidSysvar(*ctx.info().key)),
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
match Var::check_id(ctx.info.key) {
true => Ok(Sysvar(ctx.info.clone(), Var::from_account_info(ctx.info)?)),
_ => Err(SolitaireError::InvalidSysvar(*ctx.info.key)),
}
}
@ -157,15 +153,16 @@ where
/// This is our structural recursion base case, the trait system will stop generating new nested
/// calls here.
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> {
if ctx.immutable && ctx.info().is_writable {
return Err(
SolitaireError::InvalidMutability(*ctx.info().key, ctx.info().is_writable),
);
impl<'a, 'b: 'a> Peel<'a, 'b> for Info<'b> {
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
if ctx.immutable && ctx.info.is_writable {
return Err(SolitaireError::InvalidMutability(
*ctx.info.key,
ctx.info.is_writable,
));
}
Ok(ctx.info().clone())
Ok(ctx.info.clone())
}
fn persist(&self, _program_id: &Pubkey) -> Result<()> {
@ -178,34 +175,32 @@ impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for Info<'b> {
impl<
'a,
'b: 'a,
'c,
T: BorshDeserialize + BorshSerialize + Owned + Default,
const IS_INITIALIZED: AccountState,
> Peel<'a, 'b, 'c> for Data<'b, T, IS_INITIALIZED>
> Peel<'a, 'b> for Data<'b, T, IS_INITIALIZED>
{
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
if ctx.immutable && ctx.info().is_writable {
return Err(
SolitaireError::InvalidMutability(*ctx.info().key, ctx.info().is_writable),
);
fn peel<I>(ctx: &mut Context<'a, 'b, I>) -> Result<Self> {
if ctx.immutable && ctx.info.is_writable {
return Err(SolitaireError::InvalidMutability(
*ctx.info.key,
ctx.info.is_writable,
));
}
// If we're initializing the type, we should emit system/rent as deps.
let (initialized, data): (bool, T) = match IS_INITIALIZED {
AccountState::Uninitialized => {
if !ctx.info().data.borrow().is_empty() {
return Err(SolitaireError::AlreadyInitialized(*ctx.info().key));
if !ctx.info.data.borrow().is_empty() {
return Err(SolitaireError::AlreadyInitialized(*ctx.info.key));
}
(false, T::default())
}
AccountState::Initialized => {
(true, T::try_from_slice(*ctx.info().data.borrow_mut())?)
}
AccountState::Initialized => (true, T::try_from_slice(*ctx.info.data.borrow_mut())?),
AccountState::MaybeInitialized => {
if ctx.info().data.borrow().is_empty() {
if ctx.info.data.borrow().is_empty() {
(false, T::default())
} else {
(true, T::try_from_slice(*ctx.info().data.borrow_mut())?)
(true, T::try_from_slice(*ctx.info.data.borrow_mut())?)
}
}
};
@ -213,20 +208,20 @@ impl<
if initialized {
match data.owner() {
AccountOwner::This => {
if ctx.info().owner != ctx.this {
return Err(SolitaireError::InvalidOwner(*ctx.info().owner));
if ctx.info.owner != ctx.this {
return Err(SolitaireError::InvalidOwner(*ctx.info.owner));
}
}
AccountOwner::Other(v) => {
if *ctx.info().owner != v {
return Err(SolitaireError::InvalidOwner(*ctx.info().owner));
if *ctx.info.owner != v {
return Err(SolitaireError::InvalidOwner(*ctx.info.owner));
}
}
AccountOwner::Any => {}
};
}
Ok(Data(Box::new(ctx.info().clone()), data))
Ok(Data(Box::new(ctx.info.clone()), data))
}
fn persist(&self, program_id: &Pubkey) -> Result<()> {

View File

@ -1,54 +1,31 @@
use crate::trace;
use solana_program::{
account_info::{
next_account_info,
AccountInfo,
},
account_info::AccountInfo,
pubkey::Pubkey,
};
use std::slice::Iter;
/// The context is threaded through each check. Include anything within this structure that you
/// would like to have access to as each layer of dependency is peeled off.
pub struct Context<'a, 'b: 'a, 'c, T> {
pub struct Context<'a, 'b: 'a, T> {
/// A reference to the program_id of the current program.
pub this: &'a Pubkey,
/// A reference to the instructions account list, one or more keys may be extracted during
/// the peeling process.
pub iter: &'c mut Iter<'a, AccountInfo<'b>>,
/// This is a reference to the AccountInfo of the field that is currently being parsed.
pub info: &'a AccountInfo<'b>,
/// Reference to the data passed to the current instruction.
pub data: &'a T,
/// An optional account info for this Peelable item, some fields may be other structures that
/// do not themselves have an account info associated with the field.
pub info: Option<&'a AccountInfo<'b>>,
/// Whether to enforce immutability.
pub immutable: bool,
}
impl<'a, 'b: 'a, 'c, T> Context<'a, 'b, 'c, T> {
pub fn new(program: &'a Pubkey, iter: &'c mut Iter<'a, AccountInfo<'b>>, data: &'a T) -> Self {
impl<'a, 'b: 'a, T> Context<'a, 'b, T> {
pub fn new(this: &'a Pubkey, info: &'a AccountInfo<'b>, data: &'a T) -> Self {
Context {
this: program,
info: None,
immutable: true,
iter,
this,
info,
data,
}
}
pub fn info<'d>(&'d mut self) -> &'a AccountInfo<'b> {
match self.info {
None => {
let info = next_account_info(self.iter).unwrap();
trace!("{}", info.key);
self.info = Some(info);
info
}
Some(v) => v,
}
}
}

View File

@ -27,7 +27,7 @@ pub fn derive_from_accounts(input: TokenStream) -> TokenStream {
// Generics lifetimes of the peel type
let mut peel_g = input.generics.clone();
peel_g.params = parse_quote!('a, 'b: 'a, 'c);
peel_g.params = parse_quote!('a, 'b: 'a);
let (_, peel_type_g, _) = peel_g.split_for_impl();
// Params of the instruction context
@ -53,22 +53,11 @@ pub fn derive_from_accounts(input: TokenStream) -> TokenStream {
let expanded = quote! {
/// Macro generated implementation of FromAccounts by Solitaire.
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> {
fn from<DataType>(pid: &'a solana_program::pubkey::Pubkey, iter: &mut std::slice::Iter<'a, solana_program::account_info::AccountInfo<'b>>, data: &'a DataType) -> solitaire::Result<Self> {
#from_method
}
}
impl #combined_impl_g solitaire::Peel<'a, 'b, 'c> for #name #type_g {
fn peel<I>(ctx: &'c mut solitaire::Context<'a, 'b, 'c, I>) -> solitaire::Result<Self> where Self: Sized {
let v: #name #type_g = FromAccounts::from(ctx.this, ctx.iter, ctx.data)?;
Ok(v)
}
fn persist(&self, program_id: &solana_program::pubkey::Pubkey) -> solitaire::Result<()> {
solitaire::Persist::persist(self, program_id)
}
}
/// 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<()> {
@ -102,7 +91,7 @@ fn generate_fields(name: &syn::Ident, data: &Data) -> TokenStream2 {
trace!(stringify!(#name));
let #name: #ty = solitaire::Peel::peel(&mut solitaire::Context::new(
pid,
iter,
next_account_info(iter)?,
data,
))?;
}