Implement bridge core
Change-Id: I6d5b382702b40584f962221dad20af3d6ec50d8c
This commit is contained in:
parent
13c7e693c2
commit
2b473f1f12
|
@ -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"
|
||||
|
|
|
@ -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 }
|
|
@ -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::*;
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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()]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue