Implement bridge core

Change-Id: I6d5b382702b40584f962221dad20af3d6ec50d8c
This commit is contained in:
Hendrik Hofstadt 2021-06-07 09:04:12 +02:00
parent 13c7e693c2
commit 2b473f1f12
13 changed files with 1115 additions and 183 deletions

View File

@ -131,6 +131,18 @@ dependencies = [
"syn",
]
[[package]]
name = "bridge"
version = "0.1.0"
dependencies = [
"borsh",
"byteorder",
"primitive-types",
"sha3",
"solana-program",
"solitaire",
]
[[package]]
name = "bs58"
version = "0.3.1"
@ -243,17 +255,6 @@ dependencies = [
"termcolor",
]
[[package]]
name = "example"
version = "0.1.0"
dependencies = [
"borsh",
"byteorder",
"sha3",
"solana-program",
"solitaire",
]
[[package]]
name = "feature-probe"
version = "0.1.1"

View File

@ -1,12 +1,12 @@
[package]
name = "example"
name = "bridge"
version = "0.1.0"
description = "Created with Rocksalt"
description = "Wormhole bridge core contract"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "example"
name = "bridge"
[features]
no-entrypoint = []
@ -20,3 +20,4 @@ byteorder = "1.4.3"
solitaire = { path = "../solitaire" }
sha3 = "0.9.1"
solana-program = "*"
primitive-types = { version = "0.9.0", default-features = false }

View File

@ -1,5 +1,11 @@
mod governance;
mod initialize;
mod post_message;
mod post_vaa;
mod verify_signature;
pub use governance::*;
pub use initialize::*;
pub use post_message::*;
pub use post_vaa::*;
pub use verify_signature::*;

View File

@ -0,0 +1,226 @@
use solitaire::*;
use solana_program::{self,};
use crate::types::{
self,
GovernancePayloadSetMessageFee,
GovernancePayloadTransferFees,
};
use solana_program::pubkey::Pubkey;
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
};
use crate::{
types::{
BridgeData,
GovernancePayloadGuardianSetChange,
GovernancePayloadUpgrade,
},
vaa::ClaimableVAA,
Error::{
InvalidFeeRecipient,
InvalidGuardianSetUpgrade,
},
};
use solana_program::program::invoke_signed;
#[derive(FromAccounts)]
pub struct UpgradeContract<'b> {
/// Payer for account creation (vaa-claim)
pub payer: Signer<Info<'b>>,
/// Upgrade VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadUpgrade>,
/// PDA authority for the loader
pub upgrade_authority: Derive<Info<'b>, "upgrade">,
/// Spill address for the upgrade excess lamports
pub spill: Info<'b>,
}
impl<'b> InstructionContext<'b> for UpgradeContract<'b> {
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct UpgradeContractData {}
pub fn upgrade_contract(
ctx: &ExecutionContext,
accs: &mut UpgradeContract,
_data: UpgradeContractData,
) -> Result<()> {
accs.vaa.claim(ctx, accs.payer.key)?;
let upgrade_ix = solana_program::bpf_loader_upgradeable::upgrade(
ctx.program_id,
&accs.vaa.message.new_contract,
accs.upgrade_authority.key,
accs.spill.key,
);
let _seeds = accs.upgrade_authority.seeds(None);
invoke_signed(&upgrade_ix, ctx.accounts, &[])?;
Ok(())
}
type GuardianSet<'b> = Data<'b, types::GuardianSetData, { AccountState::Initialized }>;
type GuardianSetNew<'b> = Data<'b, types::GuardianSetData, { AccountState::Uninitialized }>;
impl<'b> Seeded<&UpgradeGuardianSet<'b>> for GuardianSet<'b> {
fn seeds(&self, accs: &UpgradeGuardianSet<'b>) -> Vec<Vec<u8>> {
vec![(accs.vaa.new_guardian_set_index - 1).to_be_bytes().to_vec()]
}
}
impl<'b> Seeded<&UpgradeGuardianSet<'b>> for GuardianSetNew<'b> {
fn seeds(&self, accs: &UpgradeGuardianSet<'b>) -> Vec<Vec<u8>> {
vec![accs.vaa.new_guardian_set_index.to_be_bytes().to_vec()]
}
}
pub type Bridge<'a> = Derive<Data<'a, BridgeData, { AccountState::Initialized }>, "Bridge">;
#[derive(FromAccounts)]
pub struct UpgradeGuardianSet<'b> {
/// Payer for account creation (vaa-claim)
pub payer: Signer<Info<'b>>,
/// Bridge config
pub bridge: Bridge<'b>,
/// GuardianSet change VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadGuardianSetChange>,
/// Old guardian set
pub guardian_set_old: GuardianSet<'b>,
/// New guardian set
pub guardian_set_new: GuardianSetNew<'b>,
}
impl<'b> InstructionContext<'b> for UpgradeGuardianSet<'b> {
fn verify(&self, _program_id: &Pubkey) -> Result<()> {
if self.guardian_set_old.index != self.vaa.new_guardian_set_index - 1 {
return Err(InvalidGuardianSetUpgrade.into());
}
if self.bridge.guardian_set_index != self.vaa.new_guardian_set_index - 1 {
return Err(InvalidGuardianSetUpgrade.into());
}
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct UpgradeGuardianSetData {}
pub fn upgrade_guardian_set(
ctx: &ExecutionContext,
accs: &mut UpgradeGuardianSet,
_data: UpgradeGuardianSetData,
) -> Result<()> {
accs.vaa.claim(ctx, accs.payer.key)?;
// Set expiration time for the old set
accs.guardian_set_old.expiration_time =
accs.vaa.meta().vaa_time + accs.bridge.config.guardian_set_expiration_time;
// Initialize new guardian Set
accs.guardian_set_new.index = accs.vaa.new_guardian_set_index;
accs.guardian_set_new.creation_time = accs.vaa.meta().vaa_time;
accs.guardian_set_new.keys = accs.vaa.new_guardian_set.clone();
// Create new guardian set
// This is done after populating it to properly allocate space according to key vec length.
accs.guardian_set_new
.create(accs, ctx, accs.payer.key, Exempt)?;
// Set guardian set index
accs.bridge.guardian_set_index = accs.vaa.new_guardian_set_index;
Ok(())
}
#[derive(FromAccounts)]
pub struct SetFees<'b> {
/// Payer for account creation (vaa-claim)
pub payer: Signer<Info<'b>>,
/// Bridge config
pub bridge: Bridge<'b>,
/// Governance VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadSetMessageFee>,
}
impl<'b> InstructionContext<'b> for SetFees<'b> {
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct SetFeesData {}
pub fn set_fees(ctx: &ExecutionContext, accs: &mut SetFees, _data: SetFeesData) -> Result<()> {
accs.vaa.claim(ctx, accs.payer.key)?;
// Set expiration time for the old set
accs.bridge.config.fee = accs.vaa.fee;
Ok(())
}
#[derive(FromAccounts)]
pub struct TransferFees<'b> {
/// Payer for account creation (vaa-claim)
pub payer: Signer<Info<'b>>,
/// Bridge config
pub bridge: Bridge<'b>,
/// Governance VAA
pub vaa: ClaimableVAA<'b, GovernancePayloadTransferFees>,
/// Account collecting tx fees
pub fee_collector: Derive<Info<'b>, "fee_collector">,
/// Fee recipient
pub recipient: Info<'b>,
}
impl<'b> InstructionContext<'b> for TransferFees<'b> {
fn verify(&self, _program_id: &Pubkey) -> Result<()> {
if self.vaa.to != self.recipient.key.to_bytes() {
return Err(InvalidFeeRecipient.into());
}
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct TransferFeesData {}
pub fn transfer_fees(
ctx: &ExecutionContext,
accs: &mut TransferFees,
_data: TransferFeesData,
) -> Result<()> {
accs.vaa.claim(ctx, accs.payer.key)?;
// Transfer fees
let transfer_ix = solana_program::system_instruction::transfer(
accs.fee_collector.key,
accs.recipient.key,
accs.vaa.amount.as_u64(),
);
let seeds = accs.fee_collector.seeds(None);
let s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let seed_slice = s.as_slice();
invoke_signed(&transfer_ix, ctx.accounts, &[seed_slice])?;
Ok(())
}

View File

@ -1,9 +1,13 @@
use crate::types::*;
use solitaire::*;
use solitaire::{
CreationLamports::Exempt,
*,
};
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 GuardianSet<'a> =
Derive<Data<'a, GuardianSetData, { AccountState::Uninitialized }>, "GuardianSet">;
type Bridge<'a> = Derive<Data<'a, BridgeData, { AccountState::Uninitialized }>, "Bridge">;
#[derive(FromAccounts, ToAccounts)]
pub struct Initialize<'b> {
@ -16,20 +20,19 @@ impl<'b> InstructionContext<'b> for Initialize<'b> {
}
pub fn initialize(
_ctx: &ExecutionContext,
ctx: &ExecutionContext,
accs: &mut Initialize,
config: BridgeConfig,
) -> Result<()> {
// Initialize the Guardian Set for the first time.
let index = Index::new(0);
// Initialize Guardian Set
accs.guardian_set.index = index;
accs.guardian_set.index = 0;
accs.guardian_set.creation_time = 0;
accs.guardian_set.keys = Vec::with_capacity(20);
accs.bridge.create(ctx, accs.payer.key, Exempt)?;
// Initialize the Bridge state for the first time.
accs.bridge.guardian_set_index = index;
accs.bridge.guardian_set_index = 0;
accs.bridge.config = config;
Ok(())

View File

@ -0,0 +1,128 @@
use crate::{
types::{
BridgeData,
PostedMessage,
SequenceTracker,
},
Error::{
InsufficientFees,
MathOverflow,
},
};
use solana_program::{
pubkey::Pubkey,
sysvar::clock::Clock,
};
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
*,
};
type Message<'b> = Data<'b, PostedMessage, { AccountState::Uninitialized }>;
type Sequence<'b> = Data<'b, SequenceTracker, { AccountState::MaybeInitialized }>;
impl<'a, 'b: 'a> Seeded<&PostMessage<'b>> for Message<'b> {
fn seeds(&self, accs: &PostMessage<'b>) -> Vec<Vec<u8>> {
vec![
accs.emitter.key.to_bytes().to_vec(),
accs.sequence.sequence.to_be_bytes().to_vec(),
]
}
}
impl<'b> Seeded<&PostMessage<'b>> for Sequence<'b> {
fn seeds(&self, accs: &PostMessage<'b>) -> Vec<Vec<u8>> {
vec![accs.emitter.key.to_bytes().to_vec()]
}
}
pub type Bridge<'a> = Derive<Data<'a, BridgeData, { AccountState::Initialized }>, "Bridge">;
pub type FeeAccount<'a> = Derive<Info<'a>, "Fees">;
#[derive(FromAccounts)]
pub struct PostMessage<'b> {
pub bridge: Bridge<'b>,
pub fee_vault: FeeAccount<'b>,
/// Account to store the posted message
pub message: Message<'b>,
/// Emitter of the VAA
pub emitter: Signer<Info<'b>>,
/// Tracker for the emitter sequence
pub sequence: Sequence<'b>,
/// Payer for account creation
pub payer: Signer<Info<'b>>,
/// Account to collect tx fee
pub fee_collector: Derive<Info<'b>, "fee_collector">,
/// Instruction reflection account (special sysvar)
pub instruction_acc: Info<'b>,
pub clock: Sysvar<'b, Clock>,
}
impl<'b> InstructionContext<'b> for PostMessage<'b> {
fn verify(&self, program_id: &Pubkey) -> Result<()> {
self.message.verify_derivation(program_id, self)?;
self.sequence.verify_derivation(program_id, self)?;
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct PostMessageData {
/// unique nonce for this message
pub nonce: u32,
/// message payload
pub payload: Vec<u8>,
}
pub fn post_message(
ctx: &ExecutionContext,
accs: &mut PostMessage,
data: PostMessageData,
) -> Result<()> {
// Fee handling
let fee = transfer_fee();
if accs
.fee_collector
.lamports()
.checked_sub(accs.bridge.last_lamports)
.ok_or(MathOverflow)?
< fee
{
return Err(InsufficientFees.into());
}
accs.bridge.last_lamports = accs.fee_collector.lamports();
// Init sequencce tracker if it does not exist yet.
if !accs.sequence.is_initialized() {
accs.sequence.create(accs, ctx, accs.payer.key, Exempt)?;
}
// Create message account
accs.message.create(accs, ctx, accs.payer.key, Exempt)?;
// Initialize transfer
accs.message.submission_time = accs.clock.unix_timestamp as u32;
accs.message.emitter_chain = 1;
accs.message.emitter_address = accs.emitter.key.to_bytes();
accs.message.nonce = data.nonce;
accs.message.payload = data.payload.clone();
accs.message.sequence = accs.sequence.sequence;
// Bump sequence number
accs.sequence.sequence += 1;
Ok(())
}
pub fn transfer_fee() -> u64 {
500
}

View File

@ -4,37 +4,55 @@ use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solana_program::{
self,
sysvar::clock::Clock,
};
use crate::{
types::{self,},
Error,
Error::GuardianSetMismatch,
};
use byteorder::{
BigEndian,
WriteBytesExt,
};
use sha3::Digest;
use solana_program::{
self,
sysvar::clock::Clock,
program_error::ProgramError,
pubkey::Pubkey,
};
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
};
use std::io::{
Cursor,
Write,
};
use crate::{
types::{
self,
Bridge,
},
Error,
VAA_TX_FEE,
};
type GuardianSet<'b> = Data<'b, types::GuardianSetData, { AccountState::Initialized }>;
type SignatureSet<'b> = Data<'b, types::SignatureSet, { AccountState::Initialized }>;
type Message<'b> = Data<'b, types::PostedMessage, { AccountState::MaybeInitialized }>;
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;
impl<'b> Seeded<&PostVAA<'b>> for SignatureSet<'b> {
fn seeds(&self, accs: &PostVAA<'b>) -> Vec<Vec<u8>> {
vec![accs.signature_set.hash.to_vec()]
}
}
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">;
impl<'b> Seeded<&PostVAA<'b>> for Message<'b> {
fn seeds(&self, accs: &PostVAA<'b>) -> Vec<Vec<u8>> {
vec![accs.signature_set.hash.to_vec()]
}
}
impl<'b> Seeded<&PostVAAData> for GuardianSet<'b> {
fn seeds(&self, data: &PostVAAData) -> Vec<Vec<u8>> {
vec![data.guardian_set_index.to_be_bytes().to_vec()]
}
}
#[derive(FromAccounts)]
pub struct PostVAA<'b> {
@ -45,7 +63,7 @@ pub struct PostVAA<'b> {
pub rent: Info<'b>,
/// Clock used for timestamping.
pub clock: Sysvar<Info<'b>, Clock>,
pub clock: Sysvar<'b, Clock>,
/// State struct, derived by #[state], used for associated accounts.
pub state: Info<'b>,
@ -56,9 +74,6 @@ pub struct PostVAA<'b> {
/// Bridge Info
pub bridge_info: Info<'b>,
/// Claim Info
pub claim: Info<'b>,
/// Signature Info
pub signature_set: SignatureSet<'b>,
@ -70,6 +85,11 @@ pub struct PostVAA<'b> {
}
impl<'b> InstructionContext<'b> for PostVAA<'b> {
fn verify(&self, program_id: &Pubkey) -> Result<()> {
self.signature_set.verify_derivation(program_id, self)?;
self.message.verify_derivation(program_id, self)?;
Ok(())
}
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
@ -92,12 +112,14 @@ pub struct PostVAAData {
// Body part
pub timestamp: u32,
pub nonce: u32,
pub emitter_chain: u8,
pub emitter_chain: u16,
pub emitter_address: ForeignAddress,
pub sequence: u64,
pub payload: Vec<u8>,
}
pub fn post_vaa(ctx: &ExecutionContext, accs: &mut PostVAA, vaa: PostVAAData) -> Result<()> {
accs.guardian_set.verify_derivation(ctx.program_id, &vaa)?;
// 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)?;
@ -127,80 +149,67 @@ pub fn post_vaa(ctx: &ExecutionContext, accs: &mut PostVAA, vaa: PostVAAData) ->
return Err(Error::PostVAAConsensusFailed.into());
}
// If the VAA originates from another chain we need to create the account and populate all fields
if vaa.emitter_chain != 1 {
accs.message.create(accs, ctx, accs.payer.key, Exempt)?;
accs.message.nonce = vaa.nonce;
accs.message.emitter_chain = vaa.emitter_chain;
accs.message.emitter_address = vaa.emitter_address;
accs.message.sequence = vaa.sequence;
accs.message.payload = vaa.payload;
}
// 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.info().key;
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.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)?;
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());
// }
fn check_active<'r>(guardian_set: &GuardianSet, clock: &Sysvar<'r, Clock>) -> Result<()> {
if guardian_set.expiration_time != 0
&& (guardian_set.expiration_time as i64) < clock.unix_timestamp
{
return Err(Error::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());
// }
if signatures.guardian_set_index != guardian_set.index {
return Err(GuardianSetMismatch.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());
// }
// 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_u16::<BigEndian>(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

@ -0,0 +1,229 @@
use solitaire::*;
use solana_program::{self,};
use crate::{
types::{self,},
Error::{
GuardianSetMismatch,
InstructionAtWrongIndex,
InvalidHash,
InvalidSecpInstruction,
},
MAX_LEN_GUARDIAN_KEYS,
};
use byteorder::ByteOrder;
use sha3::Digest;
use solana_program::program_error::ProgramError;
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
};
use std::io::Write;
type GuardianSet<'b> =
Derive<Data<'b, types::GuardianSetData, { AccountState::Initialized }>, "GuardianSet">;
type SignatureSet<'b> = Data<'b, types::SignatureSet, { AccountState::MaybeInitialized }>;
impl<'b> Seeded<&VerifySignaturesData> for SignatureSet<'b> {
fn seeds(&self, data: &VerifySignaturesData) -> Vec<Vec<u8>> {
vec![data.hash.to_vec()]
}
}
#[derive(FromAccounts)]
pub struct VerifySignatures<'b> {
/// Payer for account creation
pub payer: Signer<Info<'b>>,
/// Guardian set of the signatures
pub guardian_set: GuardianSet<'b>,
/// Signature Account
pub signature_set: SignatureSet<'b>,
/// Instruction reflection account (special sysvar)
pub instruction_acc: Info<'b>,
}
impl<'b> InstructionContext<'b> for VerifySignatures<'b> {
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct VerifySignaturesData {
/// Guardian set of the signatures
pub hash: [u8; 32],
/// instruction indices of signers (-1 for missing)
pub signers: [i8; MAX_LEN_GUARDIAN_KEYS as usize],
/// indicates whether this verification should only succeed if the sig account does not exist
pub initial_creation: bool,
}
/// SigInfo contains metadata about signers in a VerifySignature ix
struct SigInfo {
/// index of the signer in the guardianset
signer_index: u8,
/// index of the signature in the secp instruction
sig_index: u8,
}
struct SecpInstructionPart<'a> {
address: &'a [u8],
signature: &'a [u8],
msg_offset: u16,
msg_size: u16,
}
pub fn verify_signatures(
ctx: &ExecutionContext,
accs: &mut VerifySignatures,
data: VerifySignaturesData,
) -> Result<()> {
// TODO this needs to be done here because we don't have data in the context
accs.signature_set
.verify_derivation(ctx.program_id, &data)?;
let sig_infos: Vec<SigInfo> = data
.signers
.iter()
.enumerate()
.filter_map(|(i, p)| {
if *p == -1 {
return None;
}
return Some(SigInfo {
sig_index: *p as u8,
signer_index: i as u8,
});
})
.collect();
let current_instruction = solana_program::sysvar::instructions::load_current_index(
&accs.instruction_acc.try_borrow_mut_data()?,
);
if current_instruction == 0 {
return Err(InstructionAtWrongIndex.into());
}
// The previous ix must be a secp verification instruction
let secp_ix_index = (current_instruction - 1) as u8;
let secp_ix = solana_program::sysvar::instructions::load_instruction_at(
secp_ix_index as usize,
&accs.instruction_acc.try_borrow_mut_data()?,
)
.map_err(|_| ProgramError::InvalidAccountData)?;
// Check that the instruction is actually for the secp program
if secp_ix.program_id != solana_program::secp256k1_program::id() {
return Err(InvalidSecpInstruction.into());
}
let secp_data_len = secp_ix.data.len();
if secp_data_len < 2 {
return Err(InvalidSecpInstruction.into());
}
let sig_len = secp_ix.data[0];
let mut index = 1;
let mut secp_ixs: Vec<SecpInstructionPart> = Vec::with_capacity(sig_len as usize);
for i in 0..sig_len {
let sig_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
index += 2;
let sig_ix = secp_ix.data[index];
index += 1;
let address_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]) as usize;
index += 2;
let address_ix = secp_ix.data[index];
index += 1;
let msg_offset = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
index += 2;
let msg_size = byteorder::LE::read_u16(&secp_ix.data[index..index + 2]);
index += 2;
let msg_ix = secp_ix.data[index];
index += 1;
if address_ix != secp_ix_index || msg_ix != secp_ix_index || sig_ix != secp_ix_index {
return Err(InvalidSecpInstruction.into());
}
let address: &[u8] = &secp_ix.data[address_offset..address_offset + 20];
let signature: &[u8] = &secp_ix.data[sig_offset..sig_offset + 65];
// Make sure that all messages are equal
if i > 0 {
if msg_offset != secp_ixs[0].msg_offset || msg_size != secp_ixs[0].msg_size {
return Err(InvalidSecpInstruction.into());
}
}
secp_ixs.push(SecpInstructionPart {
address,
signature,
msg_offset,
msg_size,
});
}
if sig_infos.len() != secp_ixs.len() {
return Err(ProgramError::InvalidArgument.into());
}
// Check message
let message = &secp_ix.data
[secp_ixs[0].msg_offset as usize..(secp_ixs[0].msg_offset + secp_ixs[0].msg_size) as usize];
let mut h = sha3::Keccak256::default();
if let Err(e) = h.write(message) {
return Err(e.into());
};
let msg_hash: [u8; 32] = h.finalize().into();
if msg_hash != data.hash {
return Err(InvalidHash.into());
}
// Track whether the account needs initialization
let mut initialize_account = false;
// Prepare message/payload-specific sig_info account
if !accs.signature_set.is_initialized() {
accs.signature_set
.create(&data, ctx, accs.payer.key, Exempt)?;
initialize_account = true;
}
if initialize_account {
accs.signature_set.guardian_set_index = accs.guardian_set.index;
accs.signature_set.hash = data.hash;
} else {
// If the account already existed, check that the parameters match
if accs.signature_set.guardian_set_index != accs.guardian_set.index {
return Err(GuardianSetMismatch.into());
}
if accs.signature_set.hash != data.hash {
return Err(InvalidHash.into());
}
}
// Write sigs of checked addresses into sig_state
for s in sig_infos {
if s.signer_index > accs.guardian_set.num_guardians() {
return Err(ProgramError::InvalidArgument.into());
}
if s.sig_index + 1 > sig_len {
return Err(ProgramError::InvalidArgument.into());
}
let key = accs.guardian_set.keys[s.signer_index as usize];
// Check key in ix
if key != secp_ixs[s.sig_index as usize].address {
return Err(ProgramError::InvalidArgument.into());
}
// Overwritten content should be zeros except double signs by the signer or harmless replays
accs.signature_set.signatures[s.signer_index as usize]
.copy_from_slice(secp_ixs[s.sig_index as usize].signature);
}
Ok(())
}

View File

@ -2,29 +2,50 @@
// Salt contains the framework definition, single file for now but to be extracted into a cargo
// package as soon as possible.
mod api;
mod types;
pub mod api;
pub mod types;
pub mod vaa;
use solitaire::*;
use api::{
initialize,
post_message,
post_vaa,
upgrade_contract,
upgrade_guardian_set,
verify_signatures,
Initialize,
PostMessage,
PostMessageData,
PostVAA,
PostVAAData,
UpgradeContract,
UpgradeContractData,
UpgradeGuardianSet,
UpgradeGuardianSetData,
VerifySignatures,
VerifySignaturesData,
};
use types::BridgeConfig;
const VAA_TX_FEE: u64 = 0;
const MAX_LEN_GUARDIAN_KEYS: u64 = 0;
const MAX_LEN_GUARDIAN_KEYS: u64 = 19;
enum Error {
InvalidSysVar,
InsufficientFees,
PostVAAGuardianSetExpired,
PostVAAGuardianSetMismatch,
PostVAAConsensusFailed,
VAAAlreadyExecuted,
InstructionAtWrongIndex,
InvalidSecpInstruction,
InvalidHash,
GuardianSetMismatch,
InvalidGovernanceModule,
InvalidGovernanceChain,
InvalidGovernanceAction,
InvalidGuardianSetUpgrade,
MathOverflow,
InvalidFeeRecipient,
}
impl From<Error> for SolitaireError {
@ -34,6 +55,10 @@ impl From<Error> for SolitaireError {
}
solitaire! {
Initialize(BridgeConfig) => initialize,
PostVAA(PostVAAData) => post_vaa,
Initialize(BridgeConfig) => initialize,
PostVAA(PostVAAData) => post_vaa,
PostMessage(PostMessageData) => post_message,
VerifySignatures(VerifySignaturesData) => verify_signatures,
UpgradeContract(UpgradeContractData) => upgrade_contract,
UpgradeGuardianSet(UpgradeGuardianSetData) => upgrade_guardian_set,
}

View File

@ -1,30 +1,39 @@
use crate::{
api::ForeignAddress,
vaa::{
DeserializeGovernancePayload,
DeserializePayload,
},
};
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use byteorder::{
BigEndian,
ReadBytesExt,
};
use primitive_types::U256;
use solana_program::pubkey::Pubkey;
#[derive(BorshSerialize, BorshDeserialize, Clone, Copy, Default, PartialEq)]
pub struct Index(u8);
impl Index {
pub fn new(n: u8) -> Self {
Index(n)
}
pub fn bump(mut self) -> Self {
self.0 += 1;
self
}
}
use solitaire::{
processors::seeded::{
AccountOwner,
Owned,
},
SolitaireError,
};
use std::io::{
Cursor,
Read,
};
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct GuardianSetData {
/// Version number of this guardian set.
pub index: Index,
pub index: u32,
/// public key hashes of the guardian set
pub keys: Vec<u8>,
pub keys: Vec<[u8; 20]>,
/// creation time
pub creation_time: u32,
@ -33,30 +42,45 @@ pub struct GuardianSetData {
pub expiration_time: u32,
}
impl GuardianSetData {
/// Number of guardians in the set
pub fn num_guardians(&self) -> u8 {
self.keys.iter().filter(|v| **v != [0u8; 20]).count() as u8
}
}
impl Owned for GuardianSetData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct BridgeConfig {
/// Period for how long a guardian set is valid after it has been replaced by a new one. This
/// guarantees that VAAs issued by that set can still be submitted for a certain period. In
/// this period we still trust the old guardian set.
pub guardian_set_expiration_time: u32,
/// Amount of lamports that needs to be paid to the protocol to post a message
pub fee: u64,
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
pub struct BridgeData {
/// The current guardian set index, used to decide which signature sets to accept.
pub guardian_set_index: Index,
pub guardian_set_index: u32,
/// Lamports in the collection account
pub last_lamports: u64,
/// 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,
impl Owned for BridgeData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
@ -68,22 +92,13 @@ pub struct SignatureSet {
pub hash: [u8; 32],
/// Index of the guardian set
pub guardian_set_index: Index,
pub guardian_set_index: u32,
}
#[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,
impl Owned for SignatureSet {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
@ -103,8 +118,11 @@ pub struct PostedMessage {
/// Unique nonce for this message
pub nonce: u32,
/// Sequence number of this message
pub sequence: u64,
/// Emitter of the message
pub emitter_chain: Chain,
pub emitter_chain: u16,
/// Emitter of the message
pub emitter_address: [u8; 32],
@ -113,15 +131,146 @@ pub struct PostedMessage {
pub payload: Vec<u8>,
}
#[repr(u8)]
#[derive(BorshSerialize, BorshDeserialize)]
pub enum Chain {
Unknown,
Solana = 1u8,
}
impl Default for Chain {
fn default() -> Self {
Chain::Unknown
impl Owned for PostedMessage {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize)]
pub struct SequenceTracker {
pub sequence: u64,
}
impl Owned for SequenceTracker {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize)]
pub struct ClaimData {
pub claimed: bool,
}
impl Owned for ClaimData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
pub struct GovernancePayloadUpgrade {
// Address of the new Implementation
pub new_contract: Pubkey,
}
impl DeserializePayload for GovernancePayloadUpgrade
where
Self: DeserializeGovernancePayload,
{
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut c = Cursor::new(buf);
Self::check_governance_header(&mut c)?;
let mut addr = [0u8; 32];
c.read_exact(&mut addr)?;
Ok(GovernancePayloadUpgrade {
new_contract: Pubkey::new(&addr[..]),
})
}
}
impl DeserializeGovernancePayload for GovernancePayloadUpgrade {
const MODULE: &'static str = "CORE";
const ACTION: u8 = 2;
}
pub struct GovernancePayloadGuardianSetChange {
// New GuardianSetIndex
pub new_guardian_set_index: u32,
// New GuardianSet
pub new_guardian_set: Vec<[u8; 20]>,
}
impl DeserializePayload for GovernancePayloadGuardianSetChange
where
Self: DeserializeGovernancePayload,
{
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut c = Cursor::new(buf);
let new_index = c.read_u32::<BigEndian>()?;
let keys_len = c.read_u8()?;
let mut keys = Vec::with_capacity(keys_len as usize);
for _ in 0..keys_len {
let mut key: [u8; 20] = [0; 20];
c.read(&mut key)?;
keys.push(key);
}
Ok(GovernancePayloadGuardianSetChange {
new_guardian_set_index: new_index,
new_guardian_set: keys,
})
}
}
impl DeserializeGovernancePayload for GovernancePayloadGuardianSetChange {
const MODULE: &'static str = "CORE";
const ACTION: u8 = 1;
}
pub struct GovernancePayloadSetMessageFee {
// New fee in lamports
pub fee: u64,
}
impl DeserializePayload for GovernancePayloadSetMessageFee
where
Self: DeserializeGovernancePayload,
{
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut c = Cursor::new(buf);
let fee = c.read_u64::<BigEndian>()?;
Ok(GovernancePayloadSetMessageFee { fee })
}
}
impl DeserializeGovernancePayload for GovernancePayloadSetMessageFee {
const MODULE: &'static str = "CORE";
const ACTION: u8 = 3;
}
pub struct GovernancePayloadTransferFees {
// Amount to be transferred
pub amount: U256,
// Recipient
pub to: ForeignAddress,
}
impl DeserializePayload for GovernancePayloadTransferFees
where
Self: DeserializeGovernancePayload,
{
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut c = Cursor::new(buf);
let mut amount_data: [u8; 32] = [0; 32];
c.read_exact(&mut amount_data)?;
let amount = U256::from_big_endian(&amount_data);
let mut to = ForeignAddress::default();
c.read_exact(&mut to)?;
Ok(GovernancePayloadTransferFees { amount, to })
}
}
impl DeserializeGovernancePayload for GovernancePayloadTransferFees {
const MODULE: &'static str = "CORE";
const ACTION: u8 = 4;
}

View File

@ -0,0 +1,160 @@
use crate::{
types::{
ClaimData,
PostedMessage,
},
Error::{
InvalidGovernanceAction,
InvalidGovernanceChain,
InvalidGovernanceModule,
VAAAlreadyExecuted,
},
Result,
};
use byteorder::{
BigEndian,
ReadBytesExt,
};
use solana_program::pubkey::Pubkey;
use solitaire::{
processors::seeded::Seeded,
Context,
CreationLamports::Exempt,
Data,
ExecutionContext,
InstructionContext,
Peel,
SolitaireError,
*,
};
use std::{
io::{
Cursor,
Read,
Write,
},
ops::Deref,
};
pub trait SerializePayload: Sized {
fn serialize<W: Write>(&self, writer: &mut W) -> std::result::Result<(), SolitaireError>;
fn try_to_vec(&self) -> std::result::Result<Vec<u8>, SolitaireError> {
let mut result = Vec::with_capacity(256);
self.serialize(&mut result)?;
Ok(result)
}
}
pub trait DeserializePayload: Sized {
fn deserialize(buf: &mut &[u8]) -> std::result::Result<Self, SolitaireError>;
}
pub trait DeserializeGovernancePayload {
const MODULE: &'static str;
const ACTION: u8;
fn check_governance_header(
c: &mut Cursor<&mut &[u8]>,
) -> std::result::Result<(), SolitaireError> {
let mut module = [0u8; 32];
c.read_exact(&mut module)?;
if module != format!("{:\0>32}", Self::MODULE).as_bytes() {
return Err(InvalidGovernanceModule.into());
}
let action = c.read_u8()?;
if action != Self::ACTION {
return Err(InvalidGovernanceAction.into());
}
let chain = c.read_u16::<BigEndian>()?;
if chain != 1 && chain != 0 {
return Err(InvalidGovernanceChain.into());
}
Ok(())
}
}
pub struct PayloadMessage<'b, T: DeserializePayload>(
Data<'b, PostedMessage, { AccountState::Initialized }>,
T,
);
impl<'a, 'b: 'a, 'c, T: DeserializePayload> Peel<'a, 'b, 'c> for PayloadMessage<'b, T> {
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
where
Self: Sized,
{
let data: Data<'b, PostedMessage, { AccountState::Initialized }> = Data::peel(ctx)?;
// Deserialize wrapped payload
let payload = DeserializePayload::deserialize(&mut &data.payload[..])?;
Ok(PayloadMessage(data, payload))
}
}
impl<'b, T: DeserializePayload> Deref for PayloadMessage<'b, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.1
}
}
impl<'b, T: DeserializePayload> PayloadMessage<'b, T> {
pub fn meta(&self) -> &PostedMessage {
&self.0
}
}
data_wrapper!(Claim, ClaimData, AccountState::Uninitialized);
impl<'b, T: DeserializePayload> Seeded<&ClaimableVAA<'b, T>> for Claim<'b> {
fn seeds(&self, _accs: &ClaimableVAA<'b, T>) -> Vec<Vec<u8>> {
return vec![];
}
}
#[derive(FromAccounts)]
pub struct ClaimableVAA<'b, T: DeserializePayload> {
// Signed message for the transfer
pub message: PayloadMessage<'b, T>, // TODO use bridge type here that does verifications
// Claim account to prevent double spending
pub claim: Claim<'b>,
}
impl<'b, T: DeserializePayload> Deref for ClaimableVAA<'b, T> {
type Target = PayloadMessage<'b, T>;
fn deref(&self) -> &Self::Target {
&self.message
}
}
impl<'b, T: DeserializePayload> InstructionContext<'b> for ClaimableVAA<'b, T> {
fn verify(&self, program_id: &Pubkey) -> Result<()> {
// Do the Posted Message verification
// Verify that the claim account is derived correctly
self.claim.verify_derivation(program_id, self)?;
Ok(())
}
}
impl<'b, T: DeserializePayload> ClaimableVAA<'b, T> {
pub fn is_claimed(&self) -> bool {
self.claim.claimed
}
pub fn claim(&mut self, ctx: &ExecutionContext, payer: &Pubkey) -> Result<()> {
if self.is_claimed() {
return Err(VAAAlreadyExecuted.into());
}
self.claim.create(self, ctx, payer, Exempt)?;
self.claim.claimed = true;
Ok(())
}
}

View File

@ -82,12 +82,12 @@ macro_rules! solitaire {
#[macro_export]
macro_rules! data_wrapper {
($name:ident, $embed:ty) => {
($name:ident, $embed:ty, $state:expr) => {
#[repr(transparent)]
pub struct $name<'b>(Data<'b, $embed, { solitaire::AccountState::Uninitialized }>);
pub struct $name<'b>(Data<'b, $embed, { $state }>);
impl<'b> std::ops::Deref for $name<'b> {
type Target = Data<'b, $embed, { solitaire::AccountState::Uninitialized }>;
type Target = Data<'b, $embed, { $state }>;
fn deref(&self) -> &Self::Target {
return &self.0;
@ -166,5 +166,11 @@ macro_rules! pack_type {
unsafe { std::mem::transmute(&self.0) }
}
}
impl std::default::Default for $name {
fn default() -> Self {
$name(<$embed>::default())
}
}
};
}

View File

@ -57,14 +57,17 @@ impl<'a, T: Owned + Default, const IsInitialized: AccountState> Owned
}
pub trait Seeded<I> {
fn seeds(&self, accs: I) -> Vec<Vec<Vec<u8>>>;
fn seeds(&self, accs: I) -> 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
let s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let seed_slice = s.as_slice();
let (derived, bump) = Pubkey::find_program_address(seed_slice, program_id);
if &derived == self.info().key {
Ok(())
} else {
@ -102,6 +105,9 @@ impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed<'a, 'b> + Owned> Creatabl
let seeds = self.seeds(accs);
let size = self.size();
let s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let seed_slice = s.as_slice();
let ix = system_instruction::create_account(
payer,
self.info().key,
@ -109,29 +115,12 @@ impl<'a, 'b: 'a, K, T: AccountSize + Seeded<K> + Keyed<'a, 'b> + Owned> Creatabl
size as u64,
&self.owner_pubkey(ctx.program_id)?,
);
Ok(invoke_signed(&ix, ctx.accounts, &[])?) // TODO use seeds
Ok(invoke_signed(&ix, ctx.accounts, &[seed_slice])?)
}
}
impl<'a, const Seed: &'static str, T: BorshSerialize + Owned + Default> Creatable<'a, Option<()>>
for Derive<Data<'_, T, { AccountState::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()]])?)
impl<'a, const Seed: &'static str, T> Seeded<Option<()>> for Derive<T, Seed> {
fn seeds(&self, accs: Option<()>) -> Vec<Vec<u8>> {
vec![Seed.as_bytes().to_vec()]
}
}