Solitaire Refactor
Change-Id: I48306fbb00a813d83e0411a38b15123e922f8766
This commit is contained in:
parent
32b2f11def
commit
05ea24faf7
|
@ -1,15 +1,15 @@
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
use solitaire::*;
|
use solitaire::*;
|
||||||
|
|
||||||
type Payer<'a> = Signer<Info<'a>>;
|
type Payer<'a> = Signer<Info<'a>>;
|
||||||
type GuardianSet<'a> = Derive<Data<'a, GuardianSetData, Uninitialized>, "GuardianSet">;
|
type GuardianSet<'a> = Derive<Data<'a, GuardianSetData, Uninitialized>, "GuardianSet">;
|
||||||
type Bridge<'a> = Derive<Data<'a, BridgeData, Uninitialized>, "Bridge">;
|
type Bridge<'a> = Derive<Data<'a, BridgeData, Uninitialized>, "Bridge">;
|
||||||
|
|
||||||
#[derive(FromAccounts, ToAccounts)]
|
#[derive(FromAccounts, ToAccounts)]
|
||||||
pub struct Initialize<'b> {
|
pub struct Initialize<'b> {
|
||||||
pub bridge: Bridge<'b>,
|
pub bridge: Bridge<'b>,
|
||||||
pub guardian_set: GuardianSet<'b>,
|
pub guardian_set: GuardianSet<'b>,
|
||||||
pub payer: Payer<'b>,
|
pub payer: Payer<'b>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'b> InstructionContext<'b> for Initialize<'b> {
|
impl<'b> InstructionContext<'b> for Initialize<'b> {
|
||||||
|
|
|
@ -1,13 +1,28 @@
|
||||||
use solitaire::*;
|
use solitaire::*;
|
||||||
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{
|
||||||
use byteorder::{BigEndian, WriteBytesExt};
|
BorshDeserialize,
|
||||||
|
BorshSerialize,
|
||||||
|
};
|
||||||
|
use byteorder::{
|
||||||
|
BigEndian,
|
||||||
|
WriteBytesExt,
|
||||||
|
};
|
||||||
use sha3::Digest;
|
use sha3::Digest;
|
||||||
use solana_program::{self, sysvar::clock::Clock};
|
use solana_program::{
|
||||||
use std::io::{Cursor, Write};
|
self,
|
||||||
|
sysvar::clock::Clock,
|
||||||
|
};
|
||||||
|
use std::io::{
|
||||||
|
Cursor,
|
||||||
|
Write,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{self, Bridge},
|
types::{
|
||||||
|
self,
|
||||||
|
Bridge,
|
||||||
|
},
|
||||||
Error,
|
Error,
|
||||||
VAA_TX_FEE,
|
VAA_TX_FEE,
|
||||||
};
|
};
|
||||||
|
@ -17,9 +32,9 @@ const MIN_BRIDGE_BALANCE: u64 = (((solana_program::rent::ACCOUNT_STORAGE_OVERHEA
|
||||||
* solana_program::rent::DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64
|
* solana_program::rent::DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64
|
||||||
* solana_program::rent::DEFAULT_EXEMPTION_THRESHOLD) as u64;
|
* solana_program::rent::DEFAULT_EXEMPTION_THRESHOLD) as u64;
|
||||||
|
|
||||||
type GuardianSet<'b> = Derive<Data<'b, types::GuardianSet>, "GuardianSet">;
|
type GuardianSet<'b> = Derive<Data<'b, types::GuardianSet>, "GuardianSet">;
|
||||||
type SignatureSet<'b> = Derive<Data<'b, types::SignatureSet>, "Signatures">;
|
type SignatureSet<'b> = Derive<Data<'b, types::SignatureSet>, "Signatures">;
|
||||||
type Message<'b> = Derive<Data<'b, types::PostedMessage>, "Message">;
|
type Message<'b> = Derive<Data<'b, types::PostedMessage>, "Message">;
|
||||||
|
|
||||||
#[derive(FromAccounts)]
|
#[derive(FromAccounts)]
|
||||||
pub struct PostVAA<'b> {
|
pub struct PostVAA<'b> {
|
||||||
|
@ -82,11 +97,7 @@ pub struct PostVAAData {
|
||||||
pub payload: Vec<u8>,
|
pub payload: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn post_vaa(
|
pub fn post_vaa(ctx: &ExecutionContext, accs: &mut PostVAA, vaa: PostVAAData) -> Result<()> {
|
||||||
_ctx: &ExecutionContext,
|
|
||||||
accs: &mut PostVAA,
|
|
||||||
vaa: PostVAAData
|
|
||||||
) -> Result<()> {
|
|
||||||
// Verify any required invariants before we process the instruction.
|
// Verify any required invariants before we process the instruction.
|
||||||
check_active(&accs.guardian_set, &accs.clock)?;
|
check_active(&accs.guardian_set, &accs.clock)?;
|
||||||
check_valid_sigs(&accs.guardian_set, &accs.signature_set)?;
|
check_valid_sigs(&accs.guardian_set, &accs.signature_set)?;
|
||||||
|
@ -119,82 +130,77 @@ pub fn post_vaa(
|
||||||
// Store VAA data in associated message.
|
// Store VAA data in associated message.
|
||||||
accs.message.vaa_version = vaa.version;
|
accs.message.vaa_version = vaa.version;
|
||||||
accs.message.vaa_time = vaa.timestamp;
|
accs.message.vaa_time = vaa.timestamp;
|
||||||
accs.message.vaa_signature_account = *accs.signature_set.pubkey();
|
/* accs.message.vaa_signature_account = */
|
||||||
|
*accs.signature_set.info().key;
|
||||||
|
|
||||||
// If the bridge has enough balance, refund the SOL to the transaction payer.
|
// If the bridge has enough balance, refund the SOL to the transaction payer.
|
||||||
if VAA_TX_FEE + MIN_BRIDGE_BALANCE < accs.state.to_account_info().lamports() {
|
// if VAA_TX_FEE + MIN_BRIDGE_BALANCE < accs.state.info().lamports() {
|
||||||
transfer_sol(
|
// transfer_sol(
|
||||||
&ctx.accounts.state.to_account_info(),
|
// &accs.state.info(),
|
||||||
&ctx.accounts.payer,
|
// &accs.payer,
|
||||||
VAA_TX_FEE,
|
// VAA_TX_FEE,
|
||||||
)?;
|
// )?;
|
||||||
}
|
// }
|
||||||
//
|
////
|
||||||
// // Claim the VAA
|
// // Claim the VAA
|
||||||
// ctx.accounts.claim.vaa_time = ctx.accounts.clock.unix_timestamp as u32;
|
// ctx.accounts.claim.vaa_time = ctx.accounts.clock.unix_timestamp as u32;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer_sol(sender: &Info, recipient: &Info, amount: u64) -> Result<()> {
|
fn transfer_sol(sender: &Info, recipient: &Info, amount: u64) -> Result<()> {
|
||||||
// let mut payer_balance = sender.try_borrow_mut_lamports()?;
|
// let mut payer_balance = sender.try_borrow_mut_lamports()?;
|
||||||
// **payer_balance = payer_balance
|
// **payer_balance = payer_balance
|
||||||
// .checked_sub(amount)
|
// .checked_sub(amount)
|
||||||
// .ok_or(ProgramError::InsufficientFunds)?;
|
// .ok_or(ProgramError::InsufficientFunds)?;
|
||||||
// let mut recipient_balance = recipient.try_borrow_mut_lamports()?;
|
// let mut recipient_balance = recipient.try_borrow_mut_lamports()?;
|
||||||
// **recipient_balance = recipient_balance
|
// **recipient_balance = recipient_balance
|
||||||
// .checked_add(amount)
|
// .checked_add(amount)
|
||||||
// .ok_or(ProgramError::InvalidArgument)?;
|
// .ok_or(ProgramError::InvalidArgument)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A guardian set must not have expired.
|
/// A guardian set must not have expired.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn check_active<'r>(guardian_set: &GuardianSet, clock: &Sysvar<Info<'r>, Clock>) -> Result<()> {
|
fn check_active<'r>(guardian_set: &GuardianSet, clock: &Sysvar<Info<'r>, Clock>) -> Result<()> {
|
||||||
// if guardian_set.expiration_time != 0
|
// if guardian_set.expiration_time != 0
|
||||||
// && (guardian_set.expiration_time as i64) < clock.unix_timestamp
|
// && (guardian_set.expiration_time as i64) < clock.unix_timestamp
|
||||||
// {
|
// {
|
||||||
// return Err(ErrorCode::PostVAAGuardianSetExpired.into());
|
// return Err(ErrorCode::PostVAAGuardianSetExpired.into());
|
||||||
// }
|
// }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The signatures in this instruction must be from the right guardian set.
|
/// The signatures in this instruction must be from the right guardian set.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn check_valid_sigs<'r>(
|
fn check_valid_sigs<'r>(guardian_set: &GuardianSet, signatures: &SignatureSet<'r>) -> Result<()> {
|
||||||
guardian_set: &GuardianSet,
|
// if sig_info.guardian_set_index != guardian_set.index {
|
||||||
signatures: &SignatureSet<'r>,
|
// return Err(ErrorCode::PostVAAGuardianSetMismatch.into());
|
||||||
) -> Result<()> {
|
// }
|
||||||
// if sig_info.guardian_set_index != guardian_set.index {
|
|
||||||
// return Err(ErrorCode::PostVAAGuardianSetMismatch.into());
|
|
||||||
// }
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn check_integrity<'r>(
|
fn check_integrity<'r>(vaa: &PostVAAData, signatures: &SignatureSet<'r>) -> Result<()> {
|
||||||
vaa: &PostVAAData,
|
// // Serialize the VAA body into an array of bytes.
|
||||||
signatures: &SignatureSet<'r>,
|
// let body = {
|
||||||
) -> Result<()> {
|
// let mut v = Cursor::new(Vec::new());
|
||||||
// // Serialize the VAA body into an array of bytes.
|
// v.write_u32::<BigEndian>(vaa.timestamp)?;
|
||||||
// let body = {
|
// v.write_u32::<BigEndian>(vaa.nonce)?;
|
||||||
// let mut v = Cursor::new(Vec::new());
|
// v.write_u8(vaa.emitter_chain)?;
|
||||||
// v.write_u32::<BigEndian>(vaa.timestamp)?;
|
// v.write(&vaa.emitter_address)?;
|
||||||
// v.write_u32::<BigEndian>(vaa.nonce)?;
|
// v.write(&vaa.payload)?;
|
||||||
// v.write_u8(vaa.emitter_chain)?;
|
// v.into_inner()
|
||||||
// v.write(&vaa.emitter_address)?;
|
// };
|
||||||
// v.write(&vaa.payload)?;
|
// // Hash this body, which is expected to be the same as the hash currently stored in the
|
||||||
// v.into_inner()
|
// // signature account, binding that set of signatures to this VAA.
|
||||||
// };
|
// let body_hash: [u8; 32] = {
|
||||||
// // Hash this body, which is expected to be the same as the hash currently stored in the
|
// let mut h = sha3::Keccak256::default();
|
||||||
// // signature account, binding that set of signatures to this VAA.
|
// h.write(body.as_slice())
|
||||||
// let body_hash: [u8; 32] = {
|
// .map_err(|_| ProgramError::InvalidArgument);
|
||||||
// let mut h = sha3::Keccak256::default();
|
// h.finalize().into()
|
||||||
// h.write(body.as_slice())
|
// };
|
||||||
// .map_err(|_| ProgramError::InvalidArgument);
|
// if signatures.hash != body_hash {
|
||||||
// h.finalize().into()
|
// return Err(ProgramError::InvalidAccountData.into());
|
||||||
// };
|
// }
|
||||||
// if signatures.hash != body_hash {
|
|
||||||
// return Err(ProgramError::InvalidAccountData.into());
|
|
||||||
// }
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,13 @@ mod types;
|
||||||
|
|
||||||
use solitaire::*;
|
use solitaire::*;
|
||||||
|
|
||||||
use api::{initialize, Initialize};
|
use api::{
|
||||||
use api::{post_vaa, PostVAA, PostVAAData};
|
initialize,
|
||||||
|
post_vaa,
|
||||||
|
Initialize,
|
||||||
|
PostVAA,
|
||||||
|
PostVAAData,
|
||||||
|
};
|
||||||
use types::BridgeConfig;
|
use types::BridgeConfig;
|
||||||
|
|
||||||
const VAA_TX_FEE: u64 = 0;
|
const VAA_TX_FEE: u64 = 0;
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSerialize,
|
||||||
|
};
|
||||||
use solana_program::pubkey::Pubkey;
|
use solana_program::pubkey::Pubkey;
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, BorshSerialize, BorshDeserialize)]
|
#[derive(BorshSerialize, BorshDeserialize, Clone, Copy, Default, PartialEq)]
|
||||||
pub struct Index(u8);
|
pub struct Index(u8);
|
||||||
|
|
||||||
impl Index {
|
impl Index {
|
||||||
|
@ -54,7 +57,7 @@ pub struct Bridge {
|
||||||
|
|
||||||
/// Bridge configuration, which is set once upon initialization.
|
/// Bridge configuration, which is set once upon initialization.
|
||||||
pub config: BridgeConfig,
|
pub config: BridgeConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, BorshSerialize, BorshDeserialize)]
|
#[derive(Default, BorshSerialize, BorshDeserialize)]
|
||||||
pub struct SignatureSet {
|
pub struct SignatureSet {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use solana_program::{program_error::ProgramError, pubkey::Pubkey};
|
use solana_program::{
|
||||||
|
program_error::ProgramError,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
/// Quality of life Result type for the Solitaire stack.
|
/// Quality of life Result type for the Solitaire stack.
|
||||||
pub type Result<T> = std::result::Result<T, SolitaireError>;
|
pub type Result<T> = std::result::Result<T, SolitaireError>;
|
||||||
|
@ -18,6 +21,9 @@ pub enum SolitaireError {
|
||||||
/// The AccountInfo parser tried to derive the provided key, but it did not match.
|
/// The AccountInfo parser tried to derive the provided key, but it did not match.
|
||||||
InvalidDerive(Pubkey),
|
InvalidDerive(Pubkey),
|
||||||
|
|
||||||
|
/// The AccountInfo has an invalid owner.
|
||||||
|
InvalidOwner(Pubkey),
|
||||||
|
|
||||||
/// The instruction payload itself could not be deserialized.
|
/// The instruction payload itself could not be deserialized.
|
||||||
InstructionDeserializeFailed,
|
InstructionDeserializeFailed,
|
||||||
|
|
||||||
|
@ -27,6 +33,9 @@ pub enum SolitaireError {
|
||||||
/// An solana program error
|
/// An solana program error
|
||||||
ProgramError(ProgramError),
|
ProgramError(ProgramError),
|
||||||
|
|
||||||
|
/// Owner of the account is ambiguous
|
||||||
|
AmbiguousOwner,
|
||||||
|
|
||||||
Custom(u64),
|
Custom(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,5 +60,3 @@ impl Into<ProgramError> for SolitaireError {
|
||||||
ProgramError::Custom(0)
|
ProgramError::Custom(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
#![feature(const_generics_defaults)]
|
#![feature(const_generics_defaults)]
|
||||||
#![allow(warnings)]
|
#![allow(warnings)]
|
||||||
|
|
||||||
pub mod seeded;
|
|
||||||
|
|
||||||
pub use seeded::*;
|
|
||||||
pub use rocksalt::*;
|
pub use rocksalt::*;
|
||||||
|
|
||||||
// Lacking:
|
// Lacking:
|
||||||
|
@ -14,7 +11,10 @@ pub use rocksalt::*;
|
||||||
|
|
||||||
// We need a few Solana things in scope in order to properly abstract Solana.
|
// We need a few Solana things in scope in order to properly abstract Solana.
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
account_info::{next_account_info, AccountInfo},
|
account_info::{
|
||||||
|
next_account_info,
|
||||||
|
AccountInfo,
|
||||||
|
},
|
||||||
entrypoint,
|
entrypoint,
|
||||||
entrypoint::ProgramResult,
|
entrypoint::ProgramResult,
|
||||||
instruction::AccountMeta,
|
instruction::AccountMeta,
|
||||||
|
@ -25,125 +25,55 @@ use solana_program::{
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
system_instruction,
|
system_instruction,
|
||||||
system_program,
|
system_program,
|
||||||
sysvar::{self, SysvarId},
|
sysvar::{
|
||||||
|
self,
|
||||||
|
SysvarId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{ErrorKind, Write},
|
io::{
|
||||||
|
ErrorKind,
|
||||||
|
Write,
|
||||||
|
},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
ops::{Deref, DerefMut},
|
ops::{
|
||||||
|
Deref,
|
||||||
|
DerefMut,
|
||||||
|
},
|
||||||
slice::Iter,
|
slice::Iter,
|
||||||
string::FromUtf8Error,
|
string::FromUtf8Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
pub use borsh::{
|
||||||
|
BorshDeserialize,
|
||||||
pub use crate::{
|
BorshSerialize,
|
||||||
error::{ErrBox, Result, SolitaireError},
|
|
||||||
seeded::Creatable,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod error;
|
// Expose all submodules for consumption.
|
||||||
|
pub mod error;
|
||||||
|
pub mod macros;
|
||||||
|
pub mod processors;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
pub trait Persist {
|
// We can also re-export a set of types at module scope, this defines the intended API we expect
|
||||||
fn persist(self);
|
// people to be able to use from top-level.
|
||||||
}
|
use crate::processors::seeded::Owned;
|
||||||
|
pub use crate::{
|
||||||
#[repr(transparent)]
|
error::{
|
||||||
pub struct Packed<T: Pack + solana_program::program_pack::IsInitialized>(T);
|
ErrBox,
|
||||||
|
Result,
|
||||||
impl<T: Pack + solana_program::program_pack::IsInitialized> BorshDeserialize for Packed<T> {
|
SolitaireError,
|
||||||
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
|
},
|
||||||
Ok(Packed(
|
macros::*,
|
||||||
T::unpack(buf).map_err(|e| std::io::Error::new(ErrorKind::Other, e))?,
|
processors::{
|
||||||
))
|
keyed::Keyed,
|
||||||
}
|
peel::Peel,
|
||||||
}
|
persist::Persist,
|
||||||
|
seeded::Creatable,
|
||||||
impl<T: Pack + solana_program::program_pack::IsInitialized> BorshSerialize for Packed<T> {
|
},
|
||||||
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
|
types::*,
|
||||||
let mut data: [u8; 2000] = [0u8; 2000];
|
};
|
||||||
T::pack_into_slice(self, &mut data);
|
|
||||||
writer.write(&data);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Pack + solana_program::program_pack::IsInitialized> Deref for Packed<T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following set of recursive types form the basis of this library, each one represents a
|
|
||||||
// constraint to be fulfilled during parsing an account info.
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct Signer<Next>(Next);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct System<Next>(Next);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct Sysvar<Next, Var>(Next, PhantomData<Var>);
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct Derive<Next, const Seed: &'static str>(Next);
|
|
||||||
|
|
||||||
/// A shorter type alias for AccountInfo to make types slightly more readable.
|
|
||||||
pub type Info<'r> = AccountInfo<'r>;
|
|
||||||
|
|
||||||
/// Another alias for an AccountInfo that pairs it with the deserialized data that resides in the
|
|
||||||
/// accounts storage.
|
|
||||||
///
|
|
||||||
/// Note on const generics:
|
|
||||||
///
|
|
||||||
/// Solana's Rust version is JUST old enough that it cannot use constant variables in its default
|
|
||||||
/// parameter assignments. But these DO work in the consumption side so a user can still happily
|
|
||||||
/// use this type by writing for example:
|
|
||||||
///
|
|
||||||
/// Data<(), Uninitialized, Lazy>
|
|
||||||
///
|
|
||||||
/// But here, we must write `Lazy: bool = true` for now unfortunately.
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub struct Data < 'r, T, const IsInitialized: bool = true, const Lazy: bool = false > (
|
|
||||||
pub Info<'r >,
|
|
||||||
pub T,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// A tag for accounts that should be deserialized lazily.
|
|
||||||
pub const Lazy: bool = true;
|
|
||||||
|
|
||||||
/// A tag for accounts that should be deserialized immediately (the default).
|
|
||||||
pub const Strict: bool = false;
|
|
||||||
|
|
||||||
/// A tag for accounts that are expected to have already been initialized.
|
|
||||||
pub const Initialized: bool = true;
|
|
||||||
|
|
||||||
/// A tag for accounts that must be uninitialized.
|
|
||||||
pub const Uninitialized: bool = false;
|
|
||||||
|
|
||||||
/// 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> {
|
|
||||||
/// A reference to the program_id of the current program.
|
|
||||||
pub this: &'a Pubkey,
|
|
||||||
|
|
||||||
pub iter: &'c mut Iter<'a, AccountInfo<'b>>,
|
|
||||||
|
|
||||||
/// This is a reference to the instruction data we are processing this
|
|
||||||
/// account for.
|
|
||||||
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>,
|
|
||||||
|
|
||||||
info: Option<&'a AccountInfo<'b>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ExecutionContext<'a, 'b: 'a> {
|
pub struct ExecutionContext<'a, 'b: 'a> {
|
||||||
/// A reference to the program_id of the current program.
|
/// A reference to the program_id of the current program.
|
||||||
|
@ -153,157 +83,6 @@ pub struct ExecutionContext<'a, 'b: 'a> {
|
||||||
pub accounts: &'a [AccountInfo<'b>],
|
pub accounts: &'a [AccountInfo<'b>],
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
deps: &'c mut Vec<Pubkey>,
|
|
||||||
) -> Self {
|
|
||||||
Context {
|
|
||||||
this: program,
|
|
||||||
iter,
|
|
||||||
data,
|
|
||||||
deps,
|
|
||||||
info: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn info<'d>(&'d mut self) -> &'a AccountInfo<'b> {
|
|
||||||
match self.info {
|
|
||||||
None => {
|
|
||||||
let info = next_account_info(self.iter).unwrap();
|
|
||||||
self.info = Some(info);
|
|
||||||
info
|
|
||||||
}
|
|
||||||
Some(v) => v,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r, T, const IsInitialized: bool, const Lazy: bool> Deref
|
|
||||||
for Data<'r, T, IsInitialized, Lazy>
|
|
||||||
{
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r, T, const IsInitialized: bool, const Lazy: bool> DerefMut
|
|
||||||
for Data<'r, T, IsInitialized, Lazy>
|
|
||||||
{
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for Signer<T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DerefMut for Signer<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&mut self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, Var> Deref for Sysvar<T, Var> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, Var> DerefMut for Sysvar<T, Var> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&mut self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for System<T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DerefMut for System<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&mut self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const Seed: &'static str> Deref for Derive<T, Seed> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const Seed: &'static str> DerefMut for Derive<T, Seed> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&mut self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Keyed {
|
|
||||||
fn pubkey(&self) -> &Pubkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r, T, const IsInitialized: bool, const Lazy: bool> Keyed
|
|
||||||
for Data<'r, T, IsInitialized, Lazy>
|
|
||||||
{
|
|
||||||
fn pubkey(&self) -> &Pubkey {
|
|
||||||
self.0.key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Keyed for Signer<T>
|
|
||||||
where
|
|
||||||
T: Keyed,
|
|
||||||
{
|
|
||||||
fn pubkey(&self) -> &Pubkey {
|
|
||||||
self.0.pubkey()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, Var> Keyed for Sysvar<T, Var>
|
|
||||||
where
|
|
||||||
T: Keyed,
|
|
||||||
{
|
|
||||||
fn pubkey(&self) -> &Pubkey {
|
|
||||||
self.0.pubkey()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Keyed for System<T>
|
|
||||||
where
|
|
||||||
T: Keyed,
|
|
||||||
{
|
|
||||||
fn pubkey(&self) -> &Pubkey {
|
|
||||||
self.0.pubkey()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const Seed: &'static str> Keyed for Derive<T, Seed>
|
|
||||||
where
|
|
||||||
T: Keyed,
|
|
||||||
{
|
|
||||||
fn pubkey(&self) -> &Pubkey {
|
|
||||||
self.0.pubkey()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r> Keyed for Info<'r> {
|
|
||||||
fn pubkey(&self) -> &Pubkey {
|
|
||||||
self.key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lamports to pay to an account being created
|
/// Lamports to pay to an account being created
|
||||||
pub enum CreationLamports {
|
pub enum CreationLamports {
|
||||||
Exempt,
|
Exempt,
|
||||||
|
@ -320,131 +99,13 @@ impl CreationLamports {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const Seed: &'static str> Derive<AccountInfo<'_>, Seed> {
|
|
||||||
pub fn create(
|
|
||||||
&self,
|
|
||||||
ctx: &ExecutionContext,
|
|
||||||
payer: &Pubkey,
|
|
||||||
lamports: CreationLamports,
|
|
||||||
space: usize,
|
|
||||||
owner: &Pubkey,
|
|
||||||
) -> Result<()> {
|
|
||||||
let ix = system_instruction::create_account(
|
|
||||||
payer,
|
|
||||||
self.0.key,
|
|
||||||
lamports.amount(space),
|
|
||||||
space as u64,
|
|
||||||
owner,
|
|
||||||
);
|
|
||||||
Ok(invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]])?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const Seed: &'static str, T: BorshSerialize> Derive<Data<'_, T, Uninitialized>, Seed> {
|
|
||||||
pub fn create(
|
|
||||||
&self,
|
|
||||||
ctx: &ExecutionContext,
|
|
||||||
payer: &Pubkey,
|
|
||||||
lamports: CreationLamports,
|
|
||||||
) -> 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,
|
|
||||||
);
|
|
||||||
Ok(invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]])?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
let (derived, bump) = Pubkey::find_program_address(&[Seed.as_ref()], ctx.this);
|
|
||||||
match derived == *ctx.info().key {
|
|
||||||
true => T::peel(ctx).map(|v| Derive(v)),
|
|
||||||
_ => Err(SolitaireError::InvalidDerive(*ctx.info().key).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 {
|
|
||||||
true => T::peel(ctx).map(|v| Signer(v)),
|
|
||||||
_ => Err(SolitaireError::InvalidSigner(*ctx.info().key).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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> {
|
|
||||||
match true {
|
|
||||||
true => T::peel(ctx).map(|v| System(v)),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Peel a Sysvar
|
|
||||||
impl<'a, 'b: 'a, 'c, T: Peel<'a, 'b, 'c>, Var: SysvarId> Peel<'a, 'b, 'c> for Sysvar<T, Var> {
|
|
||||||
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
|
|
||||||
match <Var as SysvarId>::check_id(ctx.info().key) {
|
|
||||||
true => T::peel(ctx).map(|v| Sysvar(v, PhantomData)),
|
|
||||||
_ => Err(SolitaireError::InvalidSysvar(*ctx.info().key).into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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> {
|
|
||||||
Ok(ctx.info().clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is our structural recursion base case, the trait system will stop
|
|
||||||
/// generating new nested calls here.
|
|
||||||
impl<'a, 'b: 'a, 'c, T: BorshDeserialize, const IsInitialized: bool, const Lazy: bool>
|
|
||||||
Peel<'a, 'b, 'c> for Data<'b, T, IsInitialized, Lazy>
|
|
||||||
{
|
|
||||||
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
|
|
||||||
// If we're initializing the type, we should emit system/rent as deps.
|
|
||||||
if !IsInitialized {
|
|
||||||
ctx.deps.push(sysvar::rent::ID);
|
|
||||||
ctx.deps.push(system_program::ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = T::try_from_slice(&mut *ctx.info().data.borrow_mut())?;
|
|
||||||
|
|
||||||
Ok(Data(ctx.info().clone(), data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Wrap {
|
pub trait Wrap {
|
||||||
fn wrap(&self) -> Vec<AccountMeta>;
|
fn wrap(&self) -> Vec<AccountMeta>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Wrap for T
|
impl<T> Wrap for T
|
||||||
where
|
where
|
||||||
T: ToAccounts,
|
T: ToAccounts,
|
||||||
{
|
{
|
||||||
fn wrap(&self) -> Vec<AccountMeta> {
|
fn wrap(&self) -> Vec<AccountMeta> {
|
||||||
self.to()
|
self.to()
|
||||||
|
@ -463,8 +124,8 @@ impl<T, const Seed: &'static str> Wrap for Derive<T, Seed> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: BorshSerialize, const IsInitialized: bool, const Lazy: bool> Wrap
|
impl<'a, T: BorshSerialize + Owned, const IsInitialized: bool, const Lazy: bool> Wrap
|
||||||
for Data<'a, T, IsInitialized, Lazy>
|
for Data<'a, T, IsInitialized, Lazy>
|
||||||
{
|
{
|
||||||
fn wrap(&self) -> Vec<AccountMeta> {
|
fn wrap(&self) -> Vec<AccountMeta> {
|
||||||
todo!()
|
todo!()
|
||||||
|
@ -481,6 +142,10 @@ pub trait InstructionContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ToAccounts {
|
||||||
|
fn to(&self) -> Vec<AccountMeta>;
|
||||||
|
}
|
||||||
|
|
||||||
/// 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> {
|
||||||
|
@ -489,199 +154,6 @@ pub trait FromAccounts<'a, 'b: 'a, 'c> {
|
||||||
_: &'c mut Iter<'a, AccountInfo<'b>>,
|
_: &'c mut Iter<'a, AccountInfo<'b>>,
|
||||||
_: &'a T,
|
_: &'a T,
|
||||||
) -> Result<(Self, Vec<Pubkey>)>
|
) -> Result<(Self, Vec<Pubkey>)>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ToAccounts {
|
|
||||||
fn to(&self) -> Vec<AccountMeta>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is our main codegen macro. It takes as input a list of enum-like variants mapping field
|
|
||||||
/// types to function calls. The generated code produces:
|
|
||||||
///
|
|
||||||
/// - An `Instruction` enum with the enum variants passed in.
|
|
||||||
/// - A set of functions which take as arguments the enum fields.
|
|
||||||
/// - A Dispatcher that deserializes bytes into the enum and dispatches the function call.
|
|
||||||
/// - A set of client calls scoped to the module `api` that can generate instructions.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! solitaire {
|
|
||||||
{ $($row:ident($kind:ty) => $fn:ident),+ $(,)* } => {
|
|
||||||
mod instruction {
|
|
||||||
use super::*;
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
|
||||||
use solana_program::{
|
|
||||||
account_info::AccountInfo,
|
|
||||||
entrypoint::ProgramResult,
|
|
||||||
pubkey::Pubkey,
|
|
||||||
};
|
|
||||||
use solitaire::{FromAccounts, Persist, Result};
|
|
||||||
|
|
||||||
/// Generated:
|
|
||||||
/// This Instruction contains a 1-1 mapping for each enum variant to function call. The
|
|
||||||
/// function calls can be found below in the `api` module.
|
|
||||||
|
|
||||||
#[derive(BorshSerialize, BorshDeserialize)]
|
|
||||||
enum Instruction {
|
|
||||||
$($row($kind),)*
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This entrypoint is generated from the enum above, it deserializes incoming bytes
|
|
||||||
/// and automatically dispatches to the correct method.
|
|
||||||
pub fn dispatch<'a, 'b: 'a, 'c>(p: &Pubkey, a: &'c [AccountInfo<'b>], d: &[u8]) -> Result<()> {
|
|
||||||
match BorshDeserialize::try_from_slice(d).map_err(|_| SolitaireError::InstructionDeserializeFailed)? {
|
|
||||||
$(
|
|
||||||
Instruction::$row(ix_data) => {
|
|
||||||
let (mut accounts, _deps): ($row, _) = FromAccounts::from(p, &mut a.iter(), &()).unwrap();
|
|
||||||
$fn(&ExecutionContext{program_id: p, accounts: a}, &mut accounts, ix_data)?;
|
|
||||||
accounts.persist();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn solitaire<'a, 'b: 'a>(p: &Pubkey, a: &'a [AccountInfo<'b>], d: &[u8]) -> ProgramResult {
|
|
||||||
if let Err(err) = dispatch(p, a, d) {
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This module contains a 1-1 mapping for each function to an enum variant. The variants
|
|
||||||
/// can be matched to the Instruction found above.
|
|
||||||
mod client {
|
|
||||||
use super::*;
|
|
||||||
use borsh::BorshSerialize;
|
|
||||||
use solana_program::{instruction::Instruction, pubkey::Pubkey};
|
|
||||||
|
|
||||||
/// Generated from Instruction Field
|
|
||||||
$(pub(crate) fn $fn(pid: &Pubkey, accounts: $row, ix_data: $kind) -> std::result::Result<Instruction, ErrBox> {
|
|
||||||
Ok(Instruction {
|
|
||||||
program_id: *pid,
|
|
||||||
accounts: vec![],
|
|
||||||
data: ix_data.try_to_vec()?,
|
|
||||||
})
|
|
||||||
})*
|
|
||||||
}
|
|
||||||
|
|
||||||
use instruction::solitaire;
|
|
||||||
solana_program::entrypoint!(solitaire);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! info_wrapper {
|
|
||||||
($name:ident) => {
|
|
||||||
pub struct $name<'b>(Info<'b>);
|
|
||||||
|
|
||||||
impl<'b> Deref for $name<'b> {
|
|
||||||
type Target = Info<'b>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
return &self.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> Keyed for $name<'b> {
|
|
||||||
fn pubkey(&self) -> &Pubkey {
|
|
||||||
self.key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for $name<'b> {
|
|
||||||
fn peel<T>(ctx: &'c mut Context<'a, 'b, 'c, T>) -> Result<Self>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
return Ok($name(ctx.info().clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($name:ident, size: $size:expr) => {
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct $name<'b>(Info<'b>);
|
|
||||||
|
|
||||||
impl<'b> Deref for $name<'b> {
|
|
||||||
type Target = Info<'b>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> DerefMut for $name<'b> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&mut self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> AccountSize for $name<'b> {
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
return $size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> Keyed for $name<'b> {
|
|
||||||
fn pubkey(&self) -> &Pubkey {
|
|
||||||
self.key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for $name<'b> {
|
|
||||||
fn peel<T>(ctx: &'c mut Context<'a, 'b, 'c, T>) -> Result<Self>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
Ok($name(ctx.info().clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! data_wrapper {
|
|
||||||
($name:ident, $embed:ty) => {
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct $name<'b>(Data<'b, $embed>);
|
|
||||||
|
|
||||||
impl<'b> Deref for $name<'b> {
|
|
||||||
type Target = Data<'b, $embed>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
return &self.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> DerefMut for $name<'b> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
unsafe { std::mem::transmute(&mut self.0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> Keyed for $name<'b> {
|
|
||||||
fn pubkey(&self) -> &Pubkey {
|
|
||||||
self.0.pubkey()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> AccountSize for $name<'b> {
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
return self.0.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for $name<'b> {
|
|
||||||
fn peel<T>(ctx: &'c mut Context<'a, 'b, 'c, T>) -> Result<Self>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
Data::peel(ctx).map(|v| $name(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
use std::ops::{
|
||||||
|
Deref,
|
||||||
|
DerefMut,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This is our main codegen macro. It takes as input a list of enum-like variants mapping field
|
||||||
|
/// types to function calls. The generated code produces:
|
||||||
|
///
|
||||||
|
/// - An `Instruction` enum with the enum variants passed in.
|
||||||
|
/// - A set of functions which take as arguments the enum fields.
|
||||||
|
/// - A Dispatcher that deserializes bytes into the enum and dispatches the function call.
|
||||||
|
/// - A set of client calls scoped to the module `api` that can generate instructions.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! solitaire {
|
||||||
|
{ $($row:ident($kind:ty) => $fn:ident),+ $(,)* } => {
|
||||||
|
mod instruction {
|
||||||
|
use super::*;
|
||||||
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
|
use solana_program::{
|
||||||
|
account_info::AccountInfo,
|
||||||
|
entrypoint::ProgramResult,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
use solitaire::{FromAccounts, Persist, Result};
|
||||||
|
|
||||||
|
/// Generated:
|
||||||
|
/// This Instruction contains a 1-1 mapping for each enum variant to function call. The
|
||||||
|
/// function calls can be found below in the `api` module.
|
||||||
|
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)]
|
||||||
|
enum Instruction {
|
||||||
|
$($row($kind),)*
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This entrypoint is generated from the enum above, it deserializes incoming bytes
|
||||||
|
/// and automatically dispatches to the correct method.
|
||||||
|
pub fn dispatch<'a, 'b: 'a, 'c>(p: &Pubkey, a: &'c [AccountInfo<'b>], d: &[u8]) -> Result<()> {
|
||||||
|
match BorshDeserialize::try_from_slice(d).map_err(|_| SolitaireError::InstructionDeserializeFailed)? {
|
||||||
|
$(
|
||||||
|
Instruction::$row(ix_data) => {
|
||||||
|
let (mut accounts, _deps): ($row, _) = FromAccounts::from(p, &mut a.iter(), &()).unwrap();
|
||||||
|
$fn(&ExecutionContext{program_id: p, accounts: a}, &mut accounts, ix_data)?;
|
||||||
|
accounts.persist();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn solitaire<'a, 'b: 'a>(p: &Pubkey, a: &'a [AccountInfo<'b>], d: &[u8]) -> ProgramResult {
|
||||||
|
if let Err(err) = dispatch(p, a, d) {
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This module contains a 1-1 mapping for each function to an enum variant. The variants
|
||||||
|
/// can be matched to the Instruction found above.
|
||||||
|
mod client {
|
||||||
|
use super::*;
|
||||||
|
use borsh::BorshSerialize;
|
||||||
|
use solana_program::{instruction::Instruction, pubkey::Pubkey};
|
||||||
|
|
||||||
|
/// Generated from Instruction Field
|
||||||
|
$(pub(crate) fn $fn(pid: &Pubkey, accounts: $row, ix_data: $kind) -> std::result::Result<Instruction, ErrBox> {
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id: *pid,
|
||||||
|
accounts: vec![],
|
||||||
|
data: ix_data.try_to_vec()?,
|
||||||
|
})
|
||||||
|
})*
|
||||||
|
}
|
||||||
|
|
||||||
|
use instruction::solitaire;
|
||||||
|
solana_program::entrypoint!(solitaire);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! data_wrapper {
|
||||||
|
($name:ident, $embed:ty) => {
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct $name<'b>(Data<'b, $embed>);
|
||||||
|
|
||||||
|
impl<'b> std::ops::Deref for $name<'b> {
|
||||||
|
type Target = Data<'b, $embed>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
return &self.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> std::ops::DerefMut for $name<'b> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&mut self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a> solitaire::processors::keyed::Keyed<'a, 'b> for $name<'b> {
|
||||||
|
fn info(&'a self) -> &'a Info<'b> {
|
||||||
|
self.0.info()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> solitaire::processors::seeded::AccountSize for $name<'b> {
|
||||||
|
fn size(&self) -> usize {
|
||||||
|
return self.0.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a, 'c> solitaire::Peel<'a, 'b, 'c> for $name<'b> {
|
||||||
|
fn peel<T>(ctx: &'c mut Context<'a, 'b, 'c, T>) -> Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Data::peel(ctx).map(|v| $name(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> solitaire::processors::seeded::Owned for $name<'b> {
|
||||||
|
fn owner(&self) -> solitaire::processors::seeded::AccountOwner {
|
||||||
|
return self.1.owner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! pack_type {
|
||||||
|
($name:ident, $embed:ty, $owner:expr) => {
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct $name(pub $embed);
|
||||||
|
|
||||||
|
impl BorshDeserialize for $name {
|
||||||
|
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
|
||||||
|
Ok($name(
|
||||||
|
solana_program::program_pack::Pack::unpack(buf)
|
||||||
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BorshSerialize for $name {
|
||||||
|
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
|
||||||
|
let mut data = [0u8; <$embed as solana_program::program_pack::Pack>::LEN];
|
||||||
|
solana_program::program_pack::Pack::pack_into_slice(&self.0, &mut data);
|
||||||
|
writer.write(&data);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl solitaire::processors::seeded::Owned for $name {
|
||||||
|
fn owner(&self) -> solitaire::processors::seeded::AccountOwner {
|
||||||
|
return $owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for $name {
|
||||||
|
type Target = $embed;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod keyed;
|
||||||
|
pub mod peel;
|
||||||
|
pub mod persist;
|
||||||
|
pub mod seeded;
|
|
@ -0,0 +1,65 @@
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
processors::seeded::Owned,
|
||||||
|
Data,
|
||||||
|
Derive,
|
||||||
|
Info,
|
||||||
|
Signer,
|
||||||
|
System,
|
||||||
|
Sysvar,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait Keyed<'a, 'b: 'a> {
|
||||||
|
fn info(&'a self) -> &Info<'b>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a, T: Owned, const IsInitialized: bool, const Lazy: bool> Keyed<'a, 'b>
|
||||||
|
for Data<'b, T, IsInitialized, Lazy>
|
||||||
|
{
|
||||||
|
fn info(&'a self) -> &'a Info<'b> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a, T> Keyed<'a, 'b> for Signer<T>
|
||||||
|
where
|
||||||
|
T: Keyed<'a, 'b>,
|
||||||
|
{
|
||||||
|
fn info(&'a self) -> &'a Info<'b> {
|
||||||
|
self.0.info()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a, T, Var> Keyed<'a, 'b> for Sysvar<T, Var>
|
||||||
|
where
|
||||||
|
T: Keyed<'a, 'b>,
|
||||||
|
{
|
||||||
|
fn info(&'a self) -> &'a Info<'b> {
|
||||||
|
self.0.info()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a, T> Keyed<'a, 'b> for System<T>
|
||||||
|
where
|
||||||
|
T: Keyed<'a, 'b>,
|
||||||
|
{
|
||||||
|
fn info(&'a self) -> &'a Info<'b> {
|
||||||
|
self.0.info()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a, T, const Seed: &'static str> Keyed<'a, 'b> for Derive<T, Seed>
|
||||||
|
where
|
||||||
|
T: Keyed<'a, 'b>,
|
||||||
|
{
|
||||||
|
fn info(&'a self) -> &'a Info<'b> {
|
||||||
|
self.0.info()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a> Keyed<'a, 'b> for Info<'b> {
|
||||||
|
fn info(&'a self) -> &'a Info<'b> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
//! Peeling.
|
||||||
|
//!
|
||||||
|
//! The accounts in Solitaire programs are defined via layers of types, when each layer is peeled
|
||||||
|
//! off it performs checks, parsing, and any other desired side-effect. The mechanism for this is
|
||||||
|
//! the peel trait, which defines a set of types that recursively construct the desired type.
|
||||||
|
|
||||||
|
use borsh::BorshDeserialize;
|
||||||
|
use solana_program::{
|
||||||
|
pubkey::Pubkey,
|
||||||
|
system_program,
|
||||||
|
sysvar::{
|
||||||
|
self,
|
||||||
|
Sysvar as SolanaSysvar,
|
||||||
|
SysvarId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
processors::seeded::{
|
||||||
|
AccountOwner,
|
||||||
|
Owned,
|
||||||
|
},
|
||||||
|
types::*,
|
||||||
|
Context,
|
||||||
|
Result,
|
||||||
|
SolitaireError,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
let (derived, bump) = Pubkey::find_program_address(&[Seed.as_ref()], ctx.this);
|
||||||
|
match derived == *ctx.info().key {
|
||||||
|
true => T::peel(ctx).map(|v| Derive(v)),
|
||||||
|
_ => Err(SolitaireError::InvalidDerive(*ctx.info().key).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
true => T::peel(ctx).map(|v| Signer(v)),
|
||||||
|
_ => Err(SolitaireError::InvalidSigner(*ctx.info().key).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
match true {
|
||||||
|
true => T::peel(ctx).map(|v| System(v)),
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peel a Sysvar
|
||||||
|
impl<'a, 'b: 'a, 'c, T, Var> Peel<'a, 'b, 'c> for Sysvar<T, Var>
|
||||||
|
where
|
||||||
|
T: Peel<'a, 'b, 'c>,
|
||||||
|
Var: SolanaSysvar + SysvarId,
|
||||||
|
{
|
||||||
|
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
|
||||||
|
match <Var as SysvarId>::check_id(ctx.info().key) {
|
||||||
|
true => T::peel(ctx).map(|v| Sysvar(v, PhantomData)),
|
||||||
|
_ => Err(SolitaireError::InvalidSysvar(*ctx.info().key).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
Ok(ctx.info().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is our structural recursion base case, the trait system will stop generating new nested
|
||||||
|
/// calls here.
|
||||||
|
impl<'a, 'b: 'a, 'c, T: BorshDeserialize + Owned, const IsInitialized: bool, const Lazy: bool>
|
||||||
|
Peel<'a, 'b, 'c> for Data<'b, T, IsInitialized, Lazy>
|
||||||
|
{
|
||||||
|
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self> {
|
||||||
|
// If we're initializing the type, we should emit system/rent as deps.
|
||||||
|
if !IsInitialized {
|
||||||
|
ctx.deps.push(sysvar::rent::ID);
|
||||||
|
ctx.deps.push(system_program::ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = T::try_from_slice(&mut *ctx.info().data.borrow_mut())?;
|
||||||
|
|
||||||
|
if IsInitialized {
|
||||||
|
match data.owner() {
|
||||||
|
AccountOwner::This => {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AccountOwner::Any => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Data(ctx.info().clone(), data))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub trait Persist {
|
||||||
|
fn persist(self);
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
use super::keyed::Keyed;
|
||||||
|
use crate::{
|
||||||
|
system_instruction,
|
||||||
|
AccountInfo,
|
||||||
|
CreationLamports,
|
||||||
|
Data,
|
||||||
|
Deref,
|
||||||
|
Derive,
|
||||||
|
ExecutionContext,
|
||||||
|
FromAccounts,
|
||||||
|
Info,
|
||||||
|
Peel,
|
||||||
|
Result,
|
||||||
|
Signer,
|
||||||
|
SolitaireError,
|
||||||
|
System,
|
||||||
|
Sysvar,
|
||||||
|
Uninitialized,
|
||||||
|
};
|
||||||
|
use borsh::{
|
||||||
|
BorshSchema,
|
||||||
|
BorshSerialize,
|
||||||
|
};
|
||||||
|
use solana_program::{
|
||||||
|
program::invoke_signed,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait AccountSize {
|
||||||
|
fn size(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AccountOwner {
|
||||||
|
This,
|
||||||
|
Other(Pubkey),
|
||||||
|
Any,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Owned {
|
||||||
|
fn owner(&self) -> AccountOwner;
|
||||||
|
|
||||||
|
fn owner_pubkey(&self, program_id: &Pubkey) -> Result<Pubkey> {
|
||||||
|
match self.owner() {
|
||||||
|
AccountOwner::This => Ok(*program_id),
|
||||||
|
AccountOwner::Other(v) => Ok(v),
|
||||||
|
AccountOwner::Any => Err(SolitaireError::AmbiguousOwner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Owned, const IsInitialized: bool, const Lazy: bool> Owned
|
||||||
|
for Data<'a, T, IsInitialized, Lazy>
|
||||||
|
{
|
||||||
|
fn owner(&self) -> AccountOwner {
|
||||||
|
self.1.owner()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Seeded<I> {
|
||||||
|
fn seeds(&self, accs: I) -> Vec<Vec<Vec<u8>>>;
|
||||||
|
|
||||||
|
fn verify_derivation<'a, 'b: 'a>(&'a self, program_id: &'a Pubkey, accs: I) -> Result<()>
|
||||||
|
where
|
||||||
|
Self: Keyed<'a, 'b>,
|
||||||
|
{
|
||||||
|
let seeds = self.seeds(accs);
|
||||||
|
let (derived, bump) = Pubkey::find_program_address(&[], program_id); //TODO
|
||||||
|
if &derived == self.info().key {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(SolitaireError::InvalidDerive(*self.info().key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Creatable<'a, I> {
|
||||||
|
fn create(
|
||||||
|
&'a self,
|
||||||
|
accs: I,
|
||||||
|
ctx: &'a ExecutionContext,
|
||||||
|
payer: &'a Pubkey,
|
||||||
|
lamports: CreationLamports,
|
||||||
|
) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BorshSerialize + Owned, const IsInitialized: bool> AccountSize
|
||||||
|
for Data<'_, T, IsInitialized>
|
||||||
|
{
|
||||||
|
fn size(&self) -> usize {
|
||||||
|
self.1.try_to_vec().unwrap().len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed<'a, 'b> + Owned> Creatable<'a, K> for T {
|
||||||
|
fn create(
|
||||||
|
&'a self,
|
||||||
|
accs: K,
|
||||||
|
ctx: &'a ExecutionContext<'_, '_>,
|
||||||
|
payer: &'a Pubkey,
|
||||||
|
lamports: CreationLamports,
|
||||||
|
) -> Result<()> {
|
||||||
|
let seeds = self.seeds(accs);
|
||||||
|
let size = self.size();
|
||||||
|
|
||||||
|
let ix = system_instruction::create_account(
|
||||||
|
payer,
|
||||||
|
self.info().key,
|
||||||
|
lamports.amount(size),
|
||||||
|
size as u64,
|
||||||
|
&self.owner_pubkey(ctx.program_id)?,
|
||||||
|
);
|
||||||
|
Ok(invoke_signed(&ix, ctx.accounts, &[])?) // TODO use seeds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, const Seed: &'static str, T: BorshSerialize + Owned> Creatable<'a, Option<()>>
|
||||||
|
for Derive<Data<'_, T, Uninitialized>, Seed>
|
||||||
|
{
|
||||||
|
fn create(
|
||||||
|
&'a self,
|
||||||
|
_: Option<()>,
|
||||||
|
ctx: &'a ExecutionContext,
|
||||||
|
payer: &'a Pubkey,
|
||||||
|
lamports: CreationLamports,
|
||||||
|
) -> 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,
|
||||||
|
&self.0 .1.owner_pubkey(ctx.program_id)?,
|
||||||
|
);
|
||||||
|
Ok(invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]])?)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,102 +0,0 @@
|
||||||
use crate::{
|
|
||||||
system_instruction,
|
|
||||||
AccountInfo,
|
|
||||||
CreationLamports,
|
|
||||||
Data,
|
|
||||||
Deref,
|
|
||||||
Derive,
|
|
||||||
ExecutionContext,
|
|
||||||
FromAccounts,
|
|
||||||
Info,
|
|
||||||
Keyed,
|
|
||||||
Peel,
|
|
||||||
Result,
|
|
||||||
Signer,
|
|
||||||
SolitaireError,
|
|
||||||
System,
|
|
||||||
Sysvar,
|
|
||||||
Uninitialized,
|
|
||||||
};
|
|
||||||
use borsh::{BorshSchema, BorshSerialize};
|
|
||||||
use solana_program::{program::invoke_signed, pubkey::Pubkey};
|
|
||||||
|
|
||||||
pub trait AccountSize {
|
|
||||||
fn size(&self) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Seeded<I> {
|
|
||||||
fn seeds(&self, accs: I) -> Vec<Vec<Vec<u8>>>;
|
|
||||||
fn verify_derivation(&self, program_id: &Pubkey, accs: I) -> Result<()>
|
|
||||||
where
|
|
||||||
Self: Keyed,
|
|
||||||
{
|
|
||||||
let seeds = self.seeds(accs);
|
|
||||||
let (derived, bump) = Pubkey::find_program_address(&[], program_id); //TODO
|
|
||||||
if &derived == self.pubkey() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(SolitaireError::InvalidDerive(*self.pubkey()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Creatable<I> {
|
|
||||||
fn create(
|
|
||||||
&self,
|
|
||||||
accs: I,
|
|
||||||
ctx: &ExecutionContext,
|
|
||||||
payer: &Pubkey,
|
|
||||||
lamports: CreationLamports,
|
|
||||||
) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: BorshSerialize, const IsInitialized: bool> AccountSize for Data<'_, T, IsInitialized> {
|
|
||||||
fn size(&self) -> usize {
|
|
||||||
self.1.try_to_vec().unwrap().len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed> Creatable<K> for T {
|
|
||||||
fn create(
|
|
||||||
&self,
|
|
||||||
accs: K,
|
|
||||||
ctx: &ExecutionContext<'_, '_>,
|
|
||||||
payer: &Pubkey,
|
|
||||||
lamports: CreationLamports,
|
|
||||||
) -> Result<()> {
|
|
||||||
let seeds = self.seeds(accs);
|
|
||||||
let size = self.size();
|
|
||||||
|
|
||||||
let ix = system_instruction::create_account(
|
|
||||||
payer,
|
|
||||||
self.pubkey(),
|
|
||||||
lamports.amount(size),
|
|
||||||
size as u64,
|
|
||||||
ctx.program_id,
|
|
||||||
);
|
|
||||||
Ok(invoke_signed(&ix, ctx.accounts, &[])?) // TODO use seeds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const Seed: &'static str, T: BorshSerialize> Creatable<Option<()>>
|
|
||||||
for Derive<Data<'_, T, Uninitialized>, Seed>
|
|
||||||
{
|
|
||||||
fn create(
|
|
||||||
&self,
|
|
||||||
_: Option<()>,
|
|
||||||
ctx: &ExecutionContext,
|
|
||||||
payer: &Pubkey,
|
|
||||||
lamports: CreationLamports,
|
|
||||||
) -> 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,
|
|
||||||
);
|
|
||||||
Ok(invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]])?)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
mod accounts;
|
||||||
|
mod context;
|
||||||
|
mod layers;
|
||||||
|
|
||||||
|
pub use accounts::*;
|
||||||
|
pub use context::*;
|
||||||
|
pub use layers::*;
|
|
@ -0,0 +1,103 @@
|
||||||
|
//! Accounts.
|
||||||
|
//!
|
||||||
|
//! Solana provides a single primitive `AccountInfo` that represents an account on Solana. It
|
||||||
|
//! provides no information about what the account means however. This file provides a set of
|
||||||
|
//! types that describe different kinds of accounts to target.
|
||||||
|
|
||||||
|
use borsh::BorshSerialize;
|
||||||
|
use solana_program::{
|
||||||
|
account_info::AccountInfo,
|
||||||
|
program::invoke_signed,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
system_instruction,
|
||||||
|
};
|
||||||
|
use std::ops::{
|
||||||
|
Deref,
|
||||||
|
DerefMut,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
processors::seeded::Owned,
|
||||||
|
CreationLamports,
|
||||||
|
Derive,
|
||||||
|
ExecutionContext,
|
||||||
|
Result,
|
||||||
|
Uninitialized,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A short alias for AccountInfo.
|
||||||
|
pub type Info<'r> = AccountInfo<'r>;
|
||||||
|
|
||||||
|
/// An account that is known to contain serialized data.
|
||||||
|
///
|
||||||
|
/// Note on const generics:
|
||||||
|
///
|
||||||
|
/// Solana's Rust version is JUST old enough that it cannot use constant variables in its default
|
||||||
|
/// parameter assignments. But these DO work in the consumption side so a user can still happily
|
||||||
|
/// use this type by writing for example:
|
||||||
|
///
|
||||||
|
/// Data<(), Uninitialized, Lazy>
|
||||||
|
///
|
||||||
|
/// But here, we must write `Lazy: bool = true` for now unfortunately.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub struct Data < 'r, T: Owned, const IsInitialized: bool = true, const Lazy: bool = false > (
|
||||||
|
pub Info<'r >,
|
||||||
|
pub T,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<'r, T: Owned, const IsInitialized: bool, const Lazy: bool> Deref
|
||||||
|
for Data<'r, T, IsInitialized, Lazy>
|
||||||
|
{
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, T: Owned, const IsInitialized: bool, const Lazy: bool> DerefMut
|
||||||
|
for Data<'r, T, IsInitialized, Lazy>
|
||||||
|
{
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const Seed: &'static str> Derive<AccountInfo<'_>, Seed> {
|
||||||
|
pub fn create(
|
||||||
|
&self,
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
payer: &Pubkey,
|
||||||
|
lamports: CreationLamports,
|
||||||
|
space: usize,
|
||||||
|
owner: &Pubkey,
|
||||||
|
) -> Result<()> {
|
||||||
|
let ix = system_instruction::create_account(
|
||||||
|
payer,
|
||||||
|
self.0.key,
|
||||||
|
lamports.amount(space),
|
||||||
|
space as u64,
|
||||||
|
owner,
|
||||||
|
);
|
||||||
|
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]]).map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const Seed: &'static str, T: BorshSerialize + Owned> Derive<Data<'_, T, Uninitialized>, Seed> {
|
||||||
|
pub fn create(
|
||||||
|
&self,
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
payer: &Pubkey,
|
||||||
|
lamports: CreationLamports,
|
||||||
|
) -> 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,
|
||||||
|
);
|
||||||
|
invoke_signed(&ix, ctx.accounts, &[&[Seed.as_bytes()]]).map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
use solana_program::{
|
||||||
|
account_info::{
|
||||||
|
next_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> {
|
||||||
|
/// A reference to the program_id of the current program.
|
||||||
|
pub this: &'a Pubkey,
|
||||||
|
|
||||||
|
pub iter: &'c mut Iter<'a, AccountInfo<'b>>,
|
||||||
|
|
||||||
|
/// This is a reference to the instruction data we are processing this
|
||||||
|
/// account for.
|
||||||
|
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>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
deps: &'c mut Vec<Pubkey>,
|
||||||
|
) -> Self {
|
||||||
|
Context {
|
||||||
|
this: program,
|
||||||
|
iter,
|
||||||
|
data,
|
||||||
|
deps,
|
||||||
|
info: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn info<'d>(&'d mut self) -> &'a AccountInfo<'b> {
|
||||||
|
match self.info {
|
||||||
|
None => {
|
||||||
|
let info = next_account_info(self.iter).unwrap();
|
||||||
|
self.info = Some(info);
|
||||||
|
info
|
||||||
|
}
|
||||||
|
Some(v) => v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
//! This file contains several single-field wrapper structs. Each one represents a layer that must
|
||||||
|
//! be checked in order to parse a Solana account.
|
||||||
|
//!
|
||||||
|
//! These structs are always single field (or single + PhantomData) and so can be represented with
|
||||||
|
//! the transparent repr layout. When each layer is removed the data can be transmuted safely to
|
||||||
|
//! the layer below, allowing for optimized recursion.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
io::{
|
||||||
|
ErrorKind,
|
||||||
|
Write,
|
||||||
|
},
|
||||||
|
marker::PhantomData,
|
||||||
|
ops::{
|
||||||
|
Deref,
|
||||||
|
DerefMut,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use borsh::{
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSerialize,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Signer<Next>(pub Next);
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct System<Next>(pub Next);
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Sysvar<Next, Var>(pub Next, pub PhantomData<Var>);
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Derive<Next, const Seed: &'static str>(pub Next);
|
||||||
|
|
||||||
|
/// A tag for accounts that should be deserialized lazily.
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const Lazy: bool = true;
|
||||||
|
|
||||||
|
/// A tag for accounts that should be deserialized immediately (the default).
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const Strict: bool = false;
|
||||||
|
|
||||||
|
/// A tag for accounts that are expected to have already been initialized.
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const Initialized: bool = true;
|
||||||
|
|
||||||
|
/// A tag for accounts that must be uninitialized.
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const Uninitialized: bool = false;
|
||||||
|
|
||||||
|
// Several traits are required for types defined here, they cannot be defined in another file due
|
||||||
|
// to orphan instance limitations.
|
||||||
|
|
||||||
|
impl<T> Deref for Signer<T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for Signer<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&mut self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Var> Deref for Sysvar<T, Var> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, Var> DerefMut for Sysvar<T, Var> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&mut self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for System<T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for System<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&mut self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const Seed: &'static str> Deref for Derive<T, Seed> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const Seed: &'static str> DerefMut for Derive<T, Seed> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
unsafe { std::mem::transmute(&mut self.0) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,40 +0,0 @@
|
||||||
#![feature(const_generics)]
|
|
||||||
#![feature(const_generics_defaults)]
|
|
||||||
#![allow(warnings)]
|
|
||||||
|
|
||||||
// #![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
|
|
||||||
|
|
||||||
mod api;
|
|
||||||
mod messages;
|
|
||||||
mod types;
|
|
||||||
mod vaa;
|
|
||||||
|
|
||||||
use api::{initialize, Initialize};
|
|
||||||
|
|
||||||
use solitaire::*;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
pub enum TokenBridgeError {
|
|
||||||
InvalidPayload,
|
|
||||||
Unknown(String),
|
|
||||||
InvalidMint,
|
|
||||||
WrongAccountOwner,
|
|
||||||
InvalidUTF8String,
|
|
||||||
AlreadyExecuted,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Error> From<T> for TokenBridgeError {
|
|
||||||
fn from(t: T) -> Self {
|
|
||||||
return TokenBridgeError::Unknown(t.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<SolitaireError> for TokenBridgeError {
|
|
||||||
fn into(self) -> SolitaireError {
|
|
||||||
SolitaireError::Custom(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
solitaire! {
|
|
||||||
Initialize(Pubkey) => initialize,
|
|
||||||
}
|
|
|
@ -13,7 +13,10 @@ use solana_program::{
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{quote, quote_spanned};
|
use quote::{
|
||||||
|
quote,
|
||||||
|
quote_spanned,
|
||||||
|
};
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input,
|
parse_macro_input,
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{quote, quote_spanned};
|
use quote::{
|
||||||
|
quote,
|
||||||
|
quote_spanned,
|
||||||
|
};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input,
|
parse_macro_input,
|
||||||
parse_quote,
|
parse_quote,
|
||||||
|
|
|
@ -5,7 +5,7 @@ imports_granularity = "Crate"
|
||||||
empty_item_single_line = false
|
empty_item_single_line = false
|
||||||
|
|
||||||
# Easier editing when arbitrary mixed use statements do not collapse.
|
# Easier editing when arbitrary mixed use statements do not collapse.
|
||||||
imports_layout = "HorizontalVertical"
|
imports_layout = "Vertical"
|
||||||
|
|
||||||
# Default rustfmt formatting of match arms with branches is awful.
|
# Default rustfmt formatting of match arms with branches is awful.
|
||||||
match_arm_leading_pipes = "Preserve"
|
match_arm_leading_pipes = "Preserve"
|
||||||
|
|
Loading…
Reference in New Issue