Solitaire Refactor

Change-Id: I48306fbb00a813d83e0411a38b15123e922f8766
This commit is contained in:
Reisen 2021-06-03 13:23:25 +00:00
parent 32b2f11def
commit 05ea24faf7
21 changed files with 939 additions and 805 deletions

View File

@ -1,15 +1,15 @@
use crate::types::*;
use solitaire::*;
type Payer<'a> = Signer<Info<'a>>;
type Payer<'a> = Signer<Info<'a>>;
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)]
pub struct Initialize<'b> {
pub bridge: Bridge<'b>,
pub bridge: Bridge<'b>,
pub guardian_set: GuardianSet<'b>,
pub payer: Payer<'b>,
pub payer: Payer<'b>,
}
impl<'b> InstructionContext<'b> for Initialize<'b> {

View File

@ -1,13 +1,28 @@
use solitaire::*;
use borsh::{BorshDeserialize, BorshSerialize};
use byteorder::{BigEndian, WriteBytesExt};
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use byteorder::{
BigEndian,
WriteBytesExt,
};
use sha3::Digest;
use solana_program::{self, sysvar::clock::Clock};
use std::io::{Cursor, Write};
use solana_program::{
self,
sysvar::clock::Clock,
};
use std::io::{
Cursor,
Write,
};
use crate::{
types::{self, Bridge},
types::{
self,
Bridge,
},
Error,
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_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 Message<'b> = Derive<Data<'b, types::PostedMessage>, "Message">;
type Message<'b> = Derive<Data<'b, types::PostedMessage>, "Message">;
#[derive(FromAccounts)]
pub struct PostVAA<'b> {
@ -82,11 +97,7 @@ pub struct PostVAAData {
pub payload: Vec<u8>,
}
pub fn post_vaa(
_ctx: &ExecutionContext,
accs: &mut PostVAA,
vaa: PostVAAData
) -> Result<()> {
pub fn post_vaa(ctx: &ExecutionContext, accs: &mut PostVAA, vaa: PostVAAData) -> Result<()> {
// Verify any required invariants before we process the instruction.
check_active(&accs.guardian_set, &accs.clock)?;
check_valid_sigs(&accs.guardian_set, &accs.signature_set)?;
@ -119,82 +130,77 @@ pub fn post_vaa(
// Store VAA data in associated message.
accs.message.vaa_version = vaa.version;
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 VAA_TX_FEE + MIN_BRIDGE_BALANCE < accs.state.to_account_info().lamports() {
transfer_sol(
&ctx.accounts.state.to_account_info(),
&ctx.accounts.payer,
VAA_TX_FEE,
)?;
}
//
// // Claim the VAA
// ctx.accounts.claim.vaa_time = ctx.accounts.clock.unix_timestamp as u32;
// if VAA_TX_FEE + MIN_BRIDGE_BALANCE < accs.state.info().lamports() {
// transfer_sol(
// &accs.state.info(),
// &accs.payer,
// VAA_TX_FEE,
// )?;
// }
////
// // Claim the VAA
// ctx.accounts.claim.vaa_time = ctx.accounts.clock.unix_timestamp as u32;
Ok(())
}
fn transfer_sol(sender: &Info, recipient: &Info, amount: u64) -> Result<()> {
// let mut payer_balance = sender.try_borrow_mut_lamports()?;
// **payer_balance = payer_balance
// .checked_sub(amount)
// .ok_or(ProgramError::InsufficientFunds)?;
// let mut recipient_balance = recipient.try_borrow_mut_lamports()?;
// **recipient_balance = recipient_balance
// .checked_add(amount)
// .ok_or(ProgramError::InvalidArgument)?;
// let mut payer_balance = sender.try_borrow_mut_lamports()?;
// **payer_balance = payer_balance
// .checked_sub(amount)
// .ok_or(ProgramError::InsufficientFunds)?;
// let mut recipient_balance = recipient.try_borrow_mut_lamports()?;
// **recipient_balance = recipient_balance
// .checked_add(amount)
// .ok_or(ProgramError::InvalidArgument)?;
Ok(())
}
/// A guardian set must not have expired.
#[inline(always)]
fn check_active<'r>(guardian_set: &GuardianSet, clock: &Sysvar<Info<'r>, Clock>) -> Result<()> {
// if guardian_set.expiration_time != 0
// && (guardian_set.expiration_time as i64) < clock.unix_timestamp
// {
// return Err(ErrorCode::PostVAAGuardianSetExpired.into());
// }
// if guardian_set.expiration_time != 0
// && (guardian_set.expiration_time as i64) < clock.unix_timestamp
// {
// return Err(ErrorCode::PostVAAGuardianSetExpired.into());
// }
Ok(())
}
/// The signatures in this instruction must be from the right guardian set.
#[inline(always)]
fn check_valid_sigs<'r>(
guardian_set: &GuardianSet,
signatures: &SignatureSet<'r>,
) -> Result<()> {
// if sig_info.guardian_set_index != guardian_set.index {
// return Err(ErrorCode::PostVAAGuardianSetMismatch.into());
// }
fn check_valid_sigs<'r>(guardian_set: &GuardianSet, signatures: &SignatureSet<'r>) -> Result<()> {
// if sig_info.guardian_set_index != guardian_set.index {
// return Err(ErrorCode::PostVAAGuardianSetMismatch.into());
// }
Ok(())
}
#[inline(always)]
fn check_integrity<'r>(
vaa: &PostVAAData,
signatures: &SignatureSet<'r>,
) -> Result<()> {
// // Serialize the VAA body into an array of bytes.
// let body = {
// let mut v = Cursor::new(Vec::new());
// v.write_u32::<BigEndian>(vaa.timestamp)?;
// v.write_u32::<BigEndian>(vaa.nonce)?;
// v.write_u8(vaa.emitter_chain)?;
// v.write(&vaa.emitter_address)?;
// v.write(&vaa.payload)?;
// v.into_inner()
// };
// // Hash this body, which is expected to be the same as the hash currently stored in the
// // signature account, binding that set of signatures to this VAA.
// let body_hash: [u8; 32] = {
// let mut h = sha3::Keccak256::default();
// h.write(body.as_slice())
// .map_err(|_| ProgramError::InvalidArgument);
// h.finalize().into()
// };
// if signatures.hash != body_hash {
// return Err(ProgramError::InvalidAccountData.into());
// }
fn check_integrity<'r>(vaa: &PostVAAData, signatures: &SignatureSet<'r>) -> Result<()> {
// // Serialize the VAA body into an array of bytes.
// let body = {
// let mut v = Cursor::new(Vec::new());
// v.write_u32::<BigEndian>(vaa.timestamp)?;
// v.write_u32::<BigEndian>(vaa.nonce)?;
// v.write_u8(vaa.emitter_chain)?;
// v.write(&vaa.emitter_address)?;
// v.write(&vaa.payload)?;
// v.into_inner()
// };
// // Hash this body, which is expected to be the same as the hash currently stored in the
// // signature account, binding that set of signatures to this VAA.
// let body_hash: [u8; 32] = {
// let mut h = sha3::Keccak256::default();
// h.write(body.as_slice())
// .map_err(|_| ProgramError::InvalidArgument);
// h.finalize().into()
// };
// if signatures.hash != body_hash {
// return Err(ProgramError::InvalidAccountData.into());
// }
Ok(())
}

View File

@ -7,8 +7,13 @@ mod types;
use solitaire::*;
use api::{initialize, Initialize};
use api::{post_vaa, PostVAA, PostVAAData};
use api::{
initialize,
post_vaa,
Initialize,
PostVAA,
PostVAAData,
};
use types::BridgeConfig;
const VAA_TX_FEE: u64 = 0;

View File

@ -1,7 +1,10 @@
use borsh::{BorshDeserialize, BorshSerialize};
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solana_program::pubkey::Pubkey;
#[derive(Default, Clone, Copy, BorshSerialize, BorshDeserialize)]
#[derive(BorshSerialize, BorshDeserialize, Clone, Copy, Default, PartialEq)]
pub struct Index(u8);
impl Index {
@ -54,7 +57,7 @@ pub struct Bridge {
/// Bridge configuration, which is set once upon initialization.
pub config: BridgeConfig,
}
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct SignatureSet {

View File

@ -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.
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.
InvalidDerive(Pubkey),
/// The AccountInfo has an invalid owner.
InvalidOwner(Pubkey),
/// The instruction payload itself could not be deserialized.
InstructionDeserializeFailed,
@ -27,6 +33,9 @@ pub enum SolitaireError {
/// An solana program error
ProgramError(ProgramError),
/// Owner of the account is ambiguous
AmbiguousOwner,
Custom(u64),
}
@ -51,5 +60,3 @@ impl Into<ProgramError> for SolitaireError {
ProgramError::Custom(0)
}
}

View File

@ -2,9 +2,6 @@
#![feature(const_generics_defaults)]
#![allow(warnings)]
pub mod seeded;
pub use seeded::*;
pub use rocksalt::*;
// Lacking:
@ -14,7 +11,10 @@ pub use rocksalt::*;
// We need a few Solana things in scope in order to properly abstract Solana.
use solana_program::{
account_info::{next_account_info, AccountInfo},
account_info::{
next_account_info,
AccountInfo,
},
entrypoint,
entrypoint::ProgramResult,
instruction::AccountMeta,
@ -25,125 +25,55 @@ use solana_program::{
rent::Rent,
system_instruction,
system_program,
sysvar::{self, SysvarId},
sysvar::{
self,
SysvarId,
},
};
use std::{
io::{ErrorKind, Write},
io::{
ErrorKind,
Write,
},
marker::PhantomData,
ops::{Deref, DerefMut},
ops::{
Deref,
DerefMut,
},
slice::Iter,
string::FromUtf8Error,
};
use borsh::{BorshDeserialize, BorshSerialize};
pub use crate::{
error::{ErrBox, Result, SolitaireError},
seeded::Creatable,
pub use borsh::{
BorshDeserialize,
BorshSerialize,
};
mod error;
// Expose all submodules for consumption.
pub mod error;
pub mod macros;
pub mod processors;
pub mod types;
pub trait Persist {
fn persist(self);
}
#[repr(transparent)]
pub struct Packed<T: Pack + solana_program::program_pack::IsInitialized>(T);
impl<T: Pack + solana_program::program_pack::IsInitialized> BorshDeserialize for Packed<T> {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
Ok(Packed(
T::unpack(buf).map_err(|e| std::io::Error::new(ErrorKind::Other, e))?,
))
}
}
impl<T: Pack + solana_program::program_pack::IsInitialized> BorshSerialize for Packed<T> {
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
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>>,
}
// We can also re-export a set of types at module scope, this defines the intended API we expect
// people to be able to use from top-level.
use crate::processors::seeded::Owned;
pub use crate::{
error::{
ErrBox,
Result,
SolitaireError,
},
macros::*,
processors::{
keyed::Keyed,
peel::Peel,
persist::Persist,
seeded::Creatable,
},
types::*,
};
pub struct ExecutionContext<'a, 'b: 'a> {
/// 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>],
}
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
pub enum CreationLamports {
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 {
fn wrap(&self) -> Vec<AccountMeta>;
}
impl<T> Wrap for T
where
T: ToAccounts,
where
T: ToAccounts,
{
fn wrap(&self) -> Vec<AccountMeta> {
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
for Data<'a, T, IsInitialized, Lazy>
impl<'a, T: BorshSerialize + Owned, const IsInitialized: bool, const Lazy: bool> Wrap
for Data<'a, T, IsInitialized, Lazy>
{
fn wrap(&self) -> Vec<AccountMeta> {
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
/// references. A list of dependent accounts is produced as a side effect of the parsing stage.
pub trait FromAccounts<'a, 'b: 'a, 'c> {
@ -489,199 +154,6 @@ pub trait FromAccounts<'a, 'b: 'a, 'c> {
_: &'c mut Iter<'a, AccountInfo<'b>>,
_: &'a T,
) -> Result<(Self, Vec<Pubkey>)>
where
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))
}
}
};
where
Self: Sized;
}

View File

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

View File

@ -0,0 +1,4 @@
pub mod keyed;
pub mod peel;
pub mod persist;
pub mod seeded;

View File

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

View File

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

View File

@ -0,0 +1,3 @@
pub trait Persist {
fn persist(self);
}

View File

@ -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()]])?)
}
}

View File

@ -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()]])?)
}
}

View File

@ -0,0 +1,7 @@
mod accounts;
mod context;
mod layers;
pub use accounts::*;
pub use context::*;
pub use layers::*;

View File

@ -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())
}
}

View File

@ -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,
}
}
}

View File

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

View File

@ -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,
}

View File

@ -13,7 +13,10 @@ use solana_program::{
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned};
use quote::{
quote,
quote_spanned,
};
use std::borrow::BorrowMut;
use syn::{
parse_macro_input,

View File

@ -2,7 +2,10 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, quote_spanned};
use quote::{
quote,
quote_spanned,
};
use syn::{
parse_macro_input,
parse_quote,

View File

@ -5,7 +5,7 @@ imports_granularity = "Crate"
empty_item_single_line = false
# 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.
match_arm_leading_pipes = "Preserve"