diff --git a/solana/anchor-bridge/programs/bridge/src/api/initialize.rs b/solana/anchor-bridge/programs/bridge/src/api/initialize.rs index b41e58e3..f2973372 100644 --- a/solana/anchor-bridge/programs/bridge/src/api/initialize.rs +++ b/solana/anchor-bridge/programs/bridge/src/api/initialize.rs @@ -1,15 +1,15 @@ use crate::types::*; use solitaire::*; -type Payer<'a> = Signer>; +type Payer<'a> = Signer>; type GuardianSet<'a> = Derive, "GuardianSet">; -type Bridge<'a> = Derive, "Bridge">; +type Bridge<'a> = Derive, "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> { diff --git a/solana/anchor-bridge/programs/bridge/src/api/post_vaa.rs b/solana/anchor-bridge/programs/bridge/src/api/post_vaa.rs index 368bfe8e..13f645d7 100644 --- a/solana/anchor-bridge/programs/bridge/src/api/post_vaa.rs +++ b/solana/anchor-bridge/programs/bridge/src/api/post_vaa.rs @@ -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, "GuardianSet">; +type GuardianSet<'b> = Derive, "GuardianSet">; type SignatureSet<'b> = Derive, "Signatures">; -type Message<'b> = Derive, "Message">; +type Message<'b> = Derive, "Message">; #[derive(FromAccounts)] pub struct PostVAA<'b> { @@ -82,11 +97,7 @@ pub struct PostVAAData { pub payload: Vec, } -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, 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::(vaa.timestamp)?; -// v.write_u32::(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::(vaa.timestamp)?; + // v.write_u32::(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(()) } diff --git a/solana/anchor-bridge/programs/bridge/src/lib.rs b/solana/anchor-bridge/programs/bridge/src/lib.rs index 4887dbe0..435817c7 100644 --- a/solana/anchor-bridge/programs/bridge/src/lib.rs +++ b/solana/anchor-bridge/programs/bridge/src/lib.rs @@ -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; diff --git a/solana/anchor-bridge/programs/bridge/src/types.rs b/solana/anchor-bridge/programs/bridge/src/types.rs index b5007db2..b4d61ad3 100644 --- a/solana/anchor-bridge/programs/bridge/src/types.rs +++ b/solana/anchor-bridge/programs/bridge/src/types.rs @@ -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 { diff --git a/solana/anchor-bridge/programs/solitaire/src/error.rs b/solana/anchor-bridge/programs/solitaire/src/error.rs index 3322457a..b5d6d314 100644 --- a/solana/anchor-bridge/programs/solitaire/src/error.rs +++ b/solana/anchor-bridge/programs/solitaire/src/error.rs @@ -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 = std::result::Result; @@ -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 for SolitaireError { ProgramError::Custom(0) } } - - diff --git a/solana/anchor-bridge/programs/solitaire/src/lib.rs b/solana/anchor-bridge/programs/solitaire/src/lib.rs index 117689a0..a4f08991 100644 --- a/solana/anchor-bridge/programs/solitaire/src/lib.rs +++ b/solana/anchor-bridge/programs/solitaire/src/lib.rs @@ -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); - -impl BorshDeserialize for Packed { - fn deserialize(buf: &mut &[u8]) -> std::io::Result { - Ok(Packed( - T::unpack(buf).map_err(|e| std::io::Error::new(ErrorKind::Other, e))?, - )) - } -} - -impl BorshSerialize for Packed { - fn serialize(&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 Deref for Packed { - 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); - -#[repr(transparent)] -pub struct System(Next); - -#[repr(transparent)] -pub struct Sysvar(Next, PhantomData); - -#[repr(transparent)] -pub struct Derive(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, - - 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, - ) -> 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 Deref for Signer { - type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { std::mem::transmute(&self.0) } - } -} - -impl DerefMut for Signer { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { std::mem::transmute(&mut self.0) } - } -} - -impl Deref for Sysvar { - type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { std::mem::transmute(&self.0) } - } -} - -impl DerefMut for Sysvar { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { std::mem::transmute(&mut self.0) } - } -} - -impl Deref for System { - type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { std::mem::transmute(&self.0) } - } -} - -impl DerefMut for System { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { std::mem::transmute(&mut self.0) } - } -} - -impl Deref for Derive { - type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { std::mem::transmute(&self.0) } - } -} - -impl DerefMut for Derive { - 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 Keyed for Signer - where - T: Keyed, -{ - fn pubkey(&self) -> &Pubkey { - self.0.pubkey() - } -} - -impl Keyed for Sysvar - where - T: Keyed, -{ - fn pubkey(&self) -> &Pubkey { - self.0.pubkey() - } -} - -impl Keyed for System - where - T: Keyed, -{ - fn pubkey(&self) -> &Pubkey { - self.0.pubkey() - } -} - -impl Keyed for Derive - 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 Derive, 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 Derive, 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(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result - 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 -{ - fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { - // 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 { - fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { - 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 { - fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { - 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 { - fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { - match ::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(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { - 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(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { - // 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; } impl Wrap for T - where - T: ToAccounts, +where + T: ToAccounts, { fn wrap(&self) -> Vec { self.to() @@ -463,8 +124,8 @@ impl Wrap for Derive { } } -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 { todo!() @@ -481,6 +142,10 @@ pub trait InstructionContext<'a> { } } +pub trait ToAccounts { + fn to(&self) -> Vec; +} + /// 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)> - where - Self: Sized; -} - -pub trait ToAccounts { - fn to(&self) -> Vec; -} - -/// 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 { - 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(ctx: &'c mut Context<'a, 'b, 'c, T>) -> Result - 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(ctx: &'c mut Context<'a, 'b, 'c, T>) -> Result - 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(ctx: &'c mut Context<'a, 'b, 'c, T>) -> Result - where - Self: Sized, - { - Data::peel(ctx).map(|v| $name(v)) - } - } - }; + where + Self: Sized; } diff --git a/solana/anchor-bridge/programs/solitaire/src/macros.rs b/solana/anchor-bridge/programs/solitaire/src/macros.rs new file mode 100644 index 00000000..7d0c38e6 --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/macros.rs @@ -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 { + 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(ctx: &'c mut Context<'a, 'b, 'c, T>) -> Result + 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 { + 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(&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) } + } + } + }; +} diff --git a/solana/anchor-bridge/programs/solitaire/src/processors.rs b/solana/anchor-bridge/programs/solitaire/src/processors.rs new file mode 100644 index 00000000..fb5b2477 --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/processors.rs @@ -0,0 +1,4 @@ +pub mod keyed; +pub mod peel; +pub mod persist; +pub mod seeded; diff --git a/solana/anchor-bridge/programs/solitaire/src/processors/keyed.rs b/solana/anchor-bridge/programs/solitaire/src/processors/keyed.rs new file mode 100644 index 00000000..c409a870 --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/processors/keyed.rs @@ -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 +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 +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 +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 +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 + } +} diff --git a/solana/anchor-bridge/programs/solitaire/src/processors/peel.rs b/solana/anchor-bridge/programs/solitaire/src/processors/peel.rs new file mode 100644 index 00000000..bf63b353 --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/processors/peel.rs @@ -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(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result + 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 +{ + fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { + // 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 { + fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { + 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 { + fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { + 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 +where + T: Peel<'a, 'b, 'c>, + Var: SolanaSysvar + SysvarId, +{ + fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { + match ::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(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { + 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(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result { + // 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)) + } +} diff --git a/solana/anchor-bridge/programs/solitaire/src/processors/persist.rs b/solana/anchor-bridge/programs/solitaire/src/processors/persist.rs new file mode 100644 index 00000000..46b94592 --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/processors/persist.rs @@ -0,0 +1,3 @@ +pub trait Persist { + fn persist(self); +} diff --git a/solana/anchor-bridge/programs/solitaire/src/processors/seeded.rs b/solana/anchor-bridge/programs/solitaire/src/processors/seeded.rs new file mode 100644 index 00000000..a10a955e --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/processors/seeded.rs @@ -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 { + 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 { + fn seeds(&self, accs: I) -> Vec>>; + + 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 AccountSize + for Data<'_, T, IsInitialized> +{ + fn size(&self) -> usize { + self.1.try_to_vec().unwrap().len() + } +} + +impl<'a, 'b: 'a, K, T: AccountSize + Seeded + 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, 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()]])?) + } +} diff --git a/solana/anchor-bridge/programs/solitaire/src/seeded.rs b/solana/anchor-bridge/programs/solitaire/src/seeded.rs deleted file mode 100644 index fe33a450..00000000 --- a/solana/anchor-bridge/programs/solitaire/src/seeded.rs +++ /dev/null @@ -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 { - fn seeds(&self, accs: I) -> Vec>>; - 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 { - fn create( - &self, - accs: I, - ctx: &ExecutionContext, - payer: &Pubkey, - lamports: CreationLamports, - ) -> Result<()>; -} - -impl AccountSize for Data<'_, T, IsInitialized> { - fn size(&self) -> usize { - self.1.try_to_vec().unwrap().len() - } -} - -impl<'a, 'b: 'a, K, T: AccountSize + Seeded + Keyed> Creatable 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 Creatable> - for Derive, 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()]])?) - } -} diff --git a/solana/anchor-bridge/programs/solitaire/src/types.rs b/solana/anchor-bridge/programs/solitaire/src/types.rs index e69de29b..98f59726 100644 --- a/solana/anchor-bridge/programs/solitaire/src/types.rs +++ b/solana/anchor-bridge/programs/solitaire/src/types.rs @@ -0,0 +1,7 @@ +mod accounts; +mod context; +mod layers; + +pub use accounts::*; +pub use context::*; +pub use layers::*; diff --git a/solana/anchor-bridge/programs/solitaire/src/types/accounts.rs b/solana/anchor-bridge/programs/solitaire/src/types/accounts.rs new file mode 100644 index 00000000..f4d1d268 --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/types/accounts.rs @@ -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 Derive, 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 Derive, 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()) + } +} diff --git a/solana/anchor-bridge/programs/solitaire/src/types/context.rs b/solana/anchor-bridge/programs/solitaire/src/types/context.rs new file mode 100644 index 00000000..aafe3f6b --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/types/context.rs @@ -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, + + 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, + ) -> 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, + } + } +} diff --git a/solana/anchor-bridge/programs/solitaire/src/types/layers.rs b/solana/anchor-bridge/programs/solitaire/src/types/layers.rs new file mode 100644 index 00000000..6b98f1d4 --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/types/layers.rs @@ -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(pub Next); + +#[repr(transparent)] +pub struct System(pub Next); + +#[repr(transparent)] +pub struct Sysvar(pub Next, pub PhantomData); + +#[repr(transparent)] +pub struct Derive(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 Deref for Signer { + type Target = T; + fn deref(&self) -> &Self::Target { + unsafe { std::mem::transmute(&self.0) } + } +} + +impl DerefMut for Signer { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { std::mem::transmute(&mut self.0) } + } +} + +impl Deref for Sysvar { + type Target = T; + fn deref(&self) -> &Self::Target { + unsafe { std::mem::transmute(&self.0) } + } +} + +impl DerefMut for Sysvar { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { std::mem::transmute(&mut self.0) } + } +} + +impl Deref for System { + type Target = T; + fn deref(&self) -> &Self::Target { + unsafe { std::mem::transmute(&self.0) } + } +} + +impl DerefMut for System { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { std::mem::transmute(&mut self.0) } + } +} + +impl Deref for Derive { + type Target = T; + fn deref(&self) -> &Self::Target { + unsafe { std::mem::transmute(&self.0) } + } +} + +impl DerefMut for Derive { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { std::mem::transmute(&mut self.0) } + } +} diff --git a/solana/anchor-bridge/programs/token_bridge/src/lib.rs b/solana/anchor-bridge/programs/token_bridge/src/lib.rs deleted file mode 100644 index 2ca0924d..00000000 --- a/solana/anchor-bridge/programs/token_bridge/src/lib.rs +++ /dev/null @@ -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 From for TokenBridgeError { - fn from(t: T) -> Self { - return TokenBridgeError::Unknown(t.to_string()); - } -} - -impl Into for TokenBridgeError { - fn into(self) -> SolitaireError { - SolitaireError::Custom(0) - } -} - -solitaire! { - Initialize(Pubkey) => initialize, -} diff --git a/solana/anchor-bridge/rocksalt/src/lib.rs b/solana/anchor-bridge/rocksalt/src/lib.rs index 74b0a7fd..4f3ff508 100644 --- a/solana/anchor-bridge/rocksalt/src/lib.rs +++ b/solana/anchor-bridge/rocksalt/src/lib.rs @@ -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, diff --git a/solana/anchor-bridge/rocksalt/src/to_accounts.rs b/solana/anchor-bridge/rocksalt/src/to_accounts.rs index 4333454a..ccb20460 100644 --- a/solana/anchor-bridge/rocksalt/src/to_accounts.rs +++ b/solana/anchor-bridge/rocksalt/src/to_accounts.rs @@ -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, diff --git a/solana/anchor-bridge/rustfmt.toml b/solana/anchor-bridge/rustfmt.toml index acf72263..3b9ff103 100644 --- a/solana/anchor-bridge/rustfmt.toml +++ b/solana/anchor-bridge/rustfmt.toml @@ -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"