diff --git a/solana/anchor-bridge/programs/bridge/src/api.rs b/solana/anchor-bridge/programs/bridge/src/api.rs index 52d32669..77386c9f 100644 --- a/solana/anchor-bridge/programs/bridge/src/api.rs +++ b/solana/anchor-bridge/programs/bridge/src/api.rs @@ -1,3 +1,5 @@ mod initialize; +mod post_vaa; pub use initialize::*; +pub use post_vaa::*; diff --git a/solana/anchor-bridge/programs/bridge/src/api/initialize.rs b/solana/anchor-bridge/programs/bridge/src/api/initialize.rs index 7b7e8158..b41e58e3 100644 --- a/solana/anchor-bridge/programs/bridge/src/api/initialize.rs +++ b/solana/anchor-bridge/programs/bridge/src/api/initialize.rs @@ -1,45 +1,22 @@ 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 payer: Payer<'b>, + pub bridge: Bridge<'b>, pub guardian_set: GuardianSet<'b>, - pub bridge: Bridge<'b>, - pub transfer: Transfer<'b>, + pub payer: Payer<'b>, } impl<'b> InstructionContext<'b> for Initialize<'b> { } -#[derive(FromAccounts, ToAccounts)] -pub struct Transfer<'b> { - pub mint: Data<'b, Test, Initialized>, - pub from: Data<'b, Test, Initialized>, - pub to: Data<'b, Test, Initialized>, -} - -impl<'b> InstructionContext<'b> for Transfer<'b> { - fn verify(&self) -> Result<()> { - return if self.mint.mint == self.from.mint { - Ok(()) - } else { - Err(SolitaireError::InvalidDerive(*self.mint.0.key).into()) - }; - } -} - -#[derive(BorshDeserialize, BorshSerialize)] -pub struct Test { - mint: Pubkey, -} - pub fn initialize( - ctx: &ExecutionContext, + _ctx: &ExecutionContext, accs: &mut Initialize, config: BridgeConfig, ) -> Result<()> { diff --git a/solana/anchor-bridge/programs/bridge/src/api/post_vaa.rs b/solana/anchor-bridge/programs/bridge/src/api/post_vaa.rs new file mode 100644 index 00000000..368bfe8e --- /dev/null +++ b/solana/anchor-bridge/programs/bridge/src/api/post_vaa.rs @@ -0,0 +1,200 @@ +use solitaire::*; + +use borsh::{BorshDeserialize, BorshSerialize}; +use byteorder::{BigEndian, WriteBytesExt}; +use sha3::Digest; +use solana_program::{self, sysvar::clock::Clock}; +use std::io::{Cursor, Write}; + +use crate::{ + types::{self, Bridge}, + Error, + VAA_TX_FEE, +}; + +const MIN_BRIDGE_BALANCE: u64 = (((solana_program::rent::ACCOUNT_STORAGE_OVERHEAD + + std::mem::size_of::() as u64) + * solana_program::rent::DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64 + * solana_program::rent::DEFAULT_EXEMPTION_THRESHOLD) as u64; + +type GuardianSet<'b> = Derive, "GuardianSet">; +type SignatureSet<'b> = Derive, "Signatures">; +type Message<'b> = Derive, "Message">; + +#[derive(FromAccounts)] +pub struct PostVAA<'b> { + /// Required by Anchor for associated accounts. + pub system_program: Info<'b>, + + /// Required by Anchor for associated accounts. + pub rent: Info<'b>, + + /// Clock used for timestamping. + pub clock: Sysvar, Clock>, + + /// State struct, derived by #[state], used for associated accounts. + pub state: Info<'b>, + + /// Information about the current guardian set. + pub guardian_set: GuardianSet<'b>, + + /// Bridge Info + pub bridge_info: Info<'b>, + + /// Claim Info + pub claim: Info<'b>, + + /// Signature Info + pub signature_set: SignatureSet<'b>, + + /// Account used to pay for auxillary instructions. + pub payer: Info<'b>, + + /// Message the VAA is associated with. + pub message: Message<'b>, +} + +impl<'b> InstructionContext<'b> for PostVAA<'b> { +} + +#[derive(Default, BorshSerialize, BorshDeserialize)] +pub struct Signature { + pub index: u8, + pub r: [u8; 32], + pub s: [u8; 32], + pub v: u8, +} + +pub type ForeignAddress = [u8; 32]; + +#[derive(Default, BorshSerialize, BorshDeserialize)] +pub struct PostVAAData { + // Header part + pub version: u8, + pub guardian_set_index: u32, + pub signatures: Vec, + + // Body part + pub timestamp: u32, + pub nonce: u32, + pub emitter_chain: u8, + pub emitter_address: ForeignAddress, + pub payload: Vec, +} + +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)?; + check_integrity(&vaa, &accs.signature_set)?; + + // Count the numnber of signatures currently present. + let signature_count: usize = accs + .signature_set + .signatures + .iter() + .filter(|v| v.iter().filter(|v| **v != 0).count() != 0) + .count(); + + // Calculate how many signatures are required to reach consensus. This calculation is in + // expanded form to ease auditing. + let required_consensus_count = { + let len = accs.guardian_set.keys.len(); + // Fixed point number transformation with one decimal to deal with rounding. + let len = (len * 10) / 3; + // Multiplication by two to get a 2/3 quorum. + let len = len * 2; + // Division by 10+1 to bring the number back into range. + len / (10 + 1) + }; + + if signature_count < required_consensus_count { + return Err(Error::PostVAAConsensusFailed.into()); + } + + // 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(); + + // 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; + 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)?; + 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()); +// } + 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()); +// } + 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()); +// } + Ok(()) +} diff --git a/solana/anchor-bridge/programs/bridge/src/lib.rs b/solana/anchor-bridge/programs/bridge/src/lib.rs index 7d42ccda..4887dbe0 100644 --- a/solana/anchor-bridge/programs/bridge/src/lib.rs +++ b/solana/anchor-bridge/programs/bridge/src/lib.rs @@ -5,11 +5,30 @@ mod api; mod types; +use solitaire::*; + use api::{initialize, Initialize}; +use api::{post_vaa, PostVAA, PostVAAData}; use types::BridgeConfig; -use solitaire::*; +const VAA_TX_FEE: u64 = 0; +const MAX_LEN_GUARDIAN_KEYS: u64 = 0; + +enum Error { + InvalidSysVar, + InsufficientFees, + PostVAAGuardianSetExpired, + PostVAAGuardianSetMismatch, + PostVAAConsensusFailed, +} + +impl From for SolitaireError { + fn from(e: Error) -> SolitaireError { + SolitaireError::Custom(e as u64) + } +} solitaire! { Initialize(BridgeConfig) => initialize, + PostVAA(PostVAAData) => post_vaa, } diff --git a/solana/anchor-bridge/programs/bridge/src/types.rs b/solana/anchor-bridge/programs/bridge/src/types.rs index 026a5183..b5007db2 100644 --- a/solana/anchor-bridge/programs/bridge/src/types.rs +++ b/solana/anchor-bridge/programs/bridge/src/types.rs @@ -1,4 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; #[derive(Default, Clone, Copy, BorshSerialize, BorshDeserialize)] pub struct Index(u8); @@ -45,3 +46,79 @@ pub struct BridgeData { /// Bridge configuration, which is set once upon initialization. pub config: BridgeConfig, } + +#[derive(Default, BorshSerialize, BorshDeserialize)] +pub struct Bridge { + /// The current guardian set index, used to decide which signature sets to accept. + pub guardian_set_index: Index, + + /// Bridge configuration, which is set once upon initialization. + pub config: BridgeConfig, +} + +#[derive(Default, BorshSerialize, BorshDeserialize)] +pub struct SignatureSet { + /// Signatures of validators + pub signatures: Vec<[u8; 32]>, + + /// Hash of the data + pub hash: [u8; 32], + + /// Index of the guardian set + pub guardian_set_index: Index, +} + +#[derive(Default, BorshSerialize, BorshDeserialize)] +pub struct GuardianSet { + /// Index of this guardian set. + pub index: Index, + + /// Public key hashes of the guardian set + pub keys: Vec<[u8; 20]>, + + /// Creation time + pub creation_time: u32, + + /// Expiration time when VAAs issued by this set are no longer valid + pub expiration_time: u32, +} + +#[derive(Default, BorshSerialize, BorshDeserialize)] +pub struct PostedMessage { + /// Header of the posted VAA + pub vaa_version: u8, + + /// Time the vaa was submitted + pub vaa_time: u32, + + /// Account where signatures are stored + pub vaa_signature_account: Pubkey, + + /// Time the posted message was created + pub submission_time: u32, + + /// Unique nonce for this message + pub nonce: u32, + + /// Emitter of the message + pub emitter_chain: Chain, + + /// Emitter of the message + pub emitter_address: [u8; 32], + + /// Message payload + pub payload: Vec, +} + +#[repr(u8)] +#[derive(BorshSerialize, BorshDeserialize)] +pub enum Chain { + Unknown, + Solana = 1u8, +} + +impl Default for Chain { + fn default() -> Self { + Chain::Unknown + } +} diff --git a/solana/anchor-bridge/programs/solitaire/src/error.rs b/solana/anchor-bridge/programs/solitaire/src/error.rs new file mode 100644 index 00000000..3322457a --- /dev/null +++ b/solana/anchor-bridge/programs/solitaire/src/error.rs @@ -0,0 +1,55 @@ +use solana_program::{program_error::ProgramError, pubkey::Pubkey}; + +/// Quality of life Result type for the Solitaire stack. +pub type Result = std::result::Result; + +/// Quality of life type alias for wrapping up boxed errors. +pub type ErrBox = Box; + +/// There are several places in Solitaire that might fail, we want descriptive errors. +#[derive(Debug)] +pub enum SolitaireError { + /// The AccountInfo parser expected a Signer, but the account did not sign. + InvalidSigner(Pubkey), + + /// The AccountInfo parser expected a Sysvar, but the key was invalid. + InvalidSysvar(Pubkey), + + /// The AccountInfo parser tried to derive the provided key, but it did not match. + InvalidDerive(Pubkey), + + /// The instruction payload itself could not be deserialized. + InstructionDeserializeFailed, + + /// An IO error was captured, wrap it up and forward it along. + IoError(std::io::Error), + + /// An solana program error + ProgramError(ProgramError), + + Custom(u64), +} + +impl From for SolitaireError { + fn from(e: ProgramError) -> Self { + SolitaireError::ProgramError(e) + } +} + +impl From for SolitaireError { + fn from(e: std::io::Error) -> Self { + SolitaireError::IoError(e) + } +} + +impl Into for SolitaireError { + fn into(self) -> ProgramError { + if let SolitaireError::ProgramError(e) = self { + return e; + } + // TODO + ProgramError::Custom(0) + } +} + + diff --git a/solana/anchor-bridge/programs/solitaire/src/lib.rs b/solana/anchor-bridge/programs/solitaire/src/lib.rs index c0fce1a3..117689a0 100644 --- a/solana/anchor-bridge/programs/solitaire/src/lib.rs +++ b/solana/anchor-bridge/programs/solitaire/src/lib.rs @@ -13,88 +13,37 @@ pub use rocksalt::*; // - Client generation incomplete. // We need a few Solana things in scope in order to properly abstract Solana. -pub use solana_program::{ - account_info::AccountInfo, +use solana_program::{ + account_info::{next_account_info, AccountInfo}, entrypoint, entrypoint::ProgramResult, + instruction::AccountMeta, + program::invoke_signed, + program_error::ProgramError, + program_pack::Pack, pubkey::Pubkey, + rent::Rent, system_instruction, system_program, sysvar::{self, SysvarId}, }; -// Later on we will define types that don't actually contain data, PhantomData will help us. -pub use std::marker::PhantomData; - -// We'll need these traits to make any wrappers we define more ergonomic for users. -pub use std::ops::{Deref, DerefMut}; - -// Borsh is Solana's goto serialization target, so we'll need this if we want to do any -// serialization on the users behalf. -pub use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{ - account_info::next_account_info, - instruction::AccountMeta, - program::invoke_signed, - program_error::ProgramError, - program_pack::Pack, - rent::Rent, -}; use std::{ io::{ErrorKind, Write}, + marker::PhantomData, + ops::{Deref, DerefMut}, slice::Iter, string::FromUtf8Error, }; -/// There are several places in Solitaire that might fail, we want descriptive errors. -#[derive(Debug)] -pub enum SolitaireError { - /// The AccountInfo parser expected a Signer, but the account did not sign. - InvalidSigner(Pubkey), +use borsh::{BorshDeserialize, BorshSerialize}; - /// The AccountInfo parser expected a Sysvar, but the key was invalid. - InvalidSysvar(Pubkey), +pub use crate::{ + error::{ErrBox, Result, SolitaireError}, + seeded::Creatable, +}; - /// The AccountInfo parser tried to derive the provided key, but it did not match. - InvalidDerive(Pubkey), - - /// The instruction payload itself could not be deserialized. - InstructionDeserializeFailed, - - /// An IO error was captured, wrap it up and forward it along. - IoError(std::io::Error), - - /// An solana program error - ProgramError(ProgramError), - - Custom(u64), -} - -impl From for SolitaireError { - fn from(e: ProgramError) -> Self { - SolitaireError::ProgramError(e) - } -} - -impl From for SolitaireError { - fn from(e: std::io::Error) -> Self { - SolitaireError::IoError(e) - } -} - -impl Into for SolitaireError { - fn into(self) -> ProgramError { - if let SolitaireError::ProgramError(e) = self { - return e; - } - // TODO - ProgramError::Custom(0) - } -} - -/// Quality of life Result type for the Solitaire stack. -pub type Result = std::result::Result; -pub type ErrBox = Box; +mod error; pub trait Persist { fn persist(self); @@ -160,8 +109,8 @@ pub type Info<'r> = AccountInfo<'r>; /// 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, + pub Info<'r >, + pub T, ); /// A tag for accounts that should be deserialized lazily. @@ -561,7 +510,12 @@ macro_rules! solitaire { mod instruction { use super::*; use borsh::{BorshDeserialize, BorshSerialize}; - use solitaire::{Persist, FromAccounts, Result}; + 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 @@ -602,7 +556,8 @@ macro_rules! solitaire { /// can be matched to the Instruction found above. mod client { use super::*; - use solana_program::instruction::Instruction; + 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 { @@ -615,7 +570,7 @@ macro_rules! solitaire { } use instruction::solitaire; - entrypoint!(solitaire); + solana_program::entrypoint!(solitaire); } } diff --git a/solana/anchor-bridge/programs/solitaire/src/types.rs b/solana/anchor-bridge/programs/solitaire/src/types.rs new file mode 100644 index 00000000..e69de29b diff --git a/solana/anchor-bridge/programs/token_bridge/src/lib.rs b/solana/anchor-bridge/programs/token_bridge/src/lib.rs new file mode 100644 index 00000000..2ca0924d --- /dev/null +++ b/solana/anchor-bridge/programs/token_bridge/src/lib.rs @@ -0,0 +1,40 @@ +#![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 1491a345..74b0a7fd 100644 --- a/solana/anchor-bridge/rocksalt/src/lib.rs +++ b/solana/anchor-bridge/rocksalt/src/lib.rs @@ -85,12 +85,12 @@ pub fn derive_from_accounts(input: TokenStream) -> TokenStream { let expanded = quote! { /// Macro generated implementation of FromAccounts by Solitaire. impl #combined_impl_g solitaire::FromAccounts #peel_type_g for #name #type_g { - fn from(pid: &'a solana_program::pubkey::Pubkey, iter: &'c mut std::slice::Iter<'a, AccountInfo<'b>>, data: &'a DataType) -> solitaire::Result<(Self, Vec)> { + fn from(pid: &'a solana_program::pubkey::Pubkey, iter: &'c mut std::slice::Iter<'a, solana_program::account_info::AccountInfo<'b>>, data: &'a DataType) -> solitaire::Result<(Self, Vec)> { #from_method } } - impl #combined_impl_g Peel<'a, 'b, 'c> for #name #type_g { + impl #combined_impl_g solitaire::Peel<'a, 'b, 'c> for #name #type_g { fn peel(ctx: &'c mut Context<'a, 'b, 'c, I>) -> solitaire::Result where Self: Sized { let v: #name #type_g = FromAccounts::from(ctx.this, ctx.iter, ctx.data).map(|v| v.0)?; diff --git a/solana/anchor-bridge/rustfmt.toml b/solana/anchor-bridge/rustfmt.toml new file mode 100644 index 00000000..acf72263 --- /dev/null +++ b/solana/anchor-bridge/rustfmt.toml @@ -0,0 +1,11 @@ +# Merge similar crates together to avoid multiple use statements. +imports_granularity = "Crate" + +# Consistency in formatting makes tool based searching/editing better. +empty_item_single_line = false + +# Easier editing when arbitrary mixed use statements do not collapse. +imports_layout = "HorizontalVertical" + +# Default rustfmt formatting of match arms with branches is awful. +match_arm_leading_pipes = "Preserve"