Bridge Converted to Solitaire

Change-Id: I6223c5d51d6bda7f3581339a93f9519725a337b9
This commit is contained in:
Reisen 2021-06-03 03:11:18 +00:00
parent 2202a38c65
commit 32b2f11def
11 changed files with 438 additions and 102 deletions

View File

@ -1,3 +1,5 @@
mod initialize;
mod post_vaa;
pub use initialize::*;
pub use post_vaa::*;

View File

@ -1,45 +1,22 @@
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 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<()> {

View File

@ -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::<Bridge>() as u64)
* 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 SignatureSet<'b> = Derive<Data<'b, types::SignatureSet>, "Signatures">;
type Message<'b> = Derive<Data<'b, types::PostedMessage>, "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<Info<'b>, 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<Signature>,
// Body part
pub timestamp: u32,
pub nonce: u32,
pub emitter_chain: u8,
pub emitter_address: ForeignAddress,
pub payload: Vec<u8>,
}
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<Info<'r>, 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::<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

@ -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<Error> for SolitaireError {
fn from(e: Error) -> SolitaireError {
SolitaireError::Custom(e as u64)
}
}
solitaire! {
Initialize(BridgeConfig) => initialize,
PostVAA(PostVAAData) => post_vaa,
}

View File

@ -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<u8>,
}
#[repr(u8)]
#[derive(BorshSerialize, BorshDeserialize)]
pub enum Chain {
Unknown,
Solana = 1u8,
}
impl Default for Chain {
fn default() -> Self {
Chain::Unknown
}
}

View File

@ -0,0 +1,55 @@
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>;
/// Quality of life type alias for wrapping up boxed errors.
pub type ErrBox = Box<dyn std::error::Error>;
/// 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<ProgramError> for SolitaireError {
fn from(e: ProgramError) -> Self {
SolitaireError::ProgramError(e)
}
}
impl From<std::io::Error> for SolitaireError {
fn from(e: std::io::Error) -> Self {
SolitaireError::IoError(e)
}
}
impl Into<ProgramError> for SolitaireError {
fn into(self) -> ProgramError {
if let SolitaireError::ProgramError(e) = self {
return e;
}
// TODO
ProgramError::Custom(0)
}
}

View File

@ -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<ProgramError> for SolitaireError {
fn from(e: ProgramError) -> Self {
SolitaireError::ProgramError(e)
}
}
impl From<std::io::Error> for SolitaireError {
fn from(e: std::io::Error) -> Self {
SolitaireError::IoError(e)
}
}
impl Into<ProgramError> 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<T> = std::result::Result<T, SolitaireError>;
pub type ErrBox = Box<dyn std::error::Error>;
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<Instruction, ErrBox> {
@ -615,7 +570,7 @@ macro_rules! solitaire {
}
use instruction::solitaire;
entrypoint!(solitaire);
solana_program::entrypoint!(solitaire);
}
}

View File

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

@ -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<DataType>(pid: &'a solana_program::pubkey::Pubkey, iter: &'c mut std::slice::Iter<'a, AccountInfo<'b>>, data: &'a DataType) -> solitaire::Result<(Self, Vec<solana_program::pubkey::Pubkey>)> {
fn from<DataType>(pid: &'a solana_program::pubkey::Pubkey, iter: &'c mut std::slice::Iter<'a, solana_program::account_info::AccountInfo<'b>>, data: &'a DataType) -> solitaire::Result<(Self, Vec<solana_program::pubkey::Pubkey>)> {
#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<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> solitaire::Result<Self> where Self: Sized {
let v: #name #type_g = FromAccounts::from(ctx.this, ctx.iter, ctx.data).map(|v| v.0)?;

View File

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