Various token bridge and solitaire improvements

Derive message accounts from nonce and data vs sequence because sequence can lead to collision with parallel tx submission

Change-Id: I82d5b3a3c7fd96b5a6c74933c773a32e1c58bdd4
This commit is contained in:
Hendrik Hofstadt 2021-06-18 14:34:31 +02:00
parent c3fa835196
commit 5eb7d0b7d0
13 changed files with 173 additions and 93 deletions

View File

@ -57,6 +57,8 @@ dependencies = [
"solana-sdk",
"solitaire",
"solitaire-client",
"spl-token",
"token-bridge",
]
[[package]]
@ -70,9 +72,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.40"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
[[package]]
name = "arrayref"
@ -1146,7 +1148,7 @@ dependencies = [
"http",
"indexmap",
"slab",
"tokio 1.6.1",
"tokio 1.6.2",
"tokio-util",
"tracing",
]
@ -1304,7 +1306,7 @@ dependencies = [
"itoa",
"pin-project-lite",
"socket2 0.4.0",
"tokio 1.6.1",
"tokio 1.6.2",
"tower-service",
"tracing",
"want",
@ -1320,7 +1322,7 @@ dependencies = [
"hyper",
"log",
"rustls",
"tokio 1.6.1",
"tokio 1.6.2",
"tokio-rustls",
"webpki",
]
@ -1387,9 +1389,9 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
[[package]]
name = "itertools"
@ -1466,9 +1468,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.96"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5600b4e6efc5421841a2138a6b082e07fe12f9aaa12783d50e5d13325b26b4fc"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
[[package]]
name = "libloading"
@ -1611,9 +1613,9 @@ dependencies = [
[[package]]
name = "mio"
version = "0.7.11"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
dependencies = [
"libc",
"log",
@ -1780,9 +1782,9 @@ checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
[[package]]
name = "once_cell"
version = "1.7.2"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
dependencies = [
"parking_lot 0.11.1",
]
@ -2275,7 +2277,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
"tokio 1.6.1",
"tokio 1.6.2",
"tokio-rustls",
"url",
"wasm-bindgen",
@ -2722,7 +2724,7 @@ dependencies = [
"solana-version",
"solana-vote-program",
"thiserror",
"tokio 1.6.1",
"tokio 1.6.2",
"tungstenite",
"url",
]
@ -2787,7 +2789,7 @@ dependencies = [
"solana-version",
"spl-memo",
"thiserror",
"tokio 1.6.1",
"tokio 1.6.2",
]
[[package]]
@ -2875,7 +2877,7 @@ dependencies = [
"solana-clap-utils",
"solana-logger",
"solana-version",
"tokio 1.6.1",
"tokio 1.6.2",
"url",
]
@ -3426,6 +3428,22 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "token-bridge"
version = "0.1.0"
dependencies = [
"borsh",
"bridge",
"byteorder",
"primitive-types",
"rocksalt",
"sha3",
"solana-program",
"solitaire",
"solitaire-client",
"spl-token",
]
[[package]]
name = "tokio"
version = "0.1.22"
@ -3452,15 +3470,15 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.6.1"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a38d31d7831c6ed7aad00aa4c12d9375fd225a6dd77da1d25b707346319a975"
checksum = "aea337f72e96efe29acc234d803a5981cd9a2b6ed21655cd7fc21cfe021e8ec7"
dependencies = [
"autocfg",
"bytes 1.0.1",
"libc",
"memchr",
"mio 0.7.11",
"mio 0.7.13",
"num_cpus",
"once_cell",
"parking_lot 0.11.1",
@ -3560,7 +3578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
dependencies = [
"rustls",
"tokio 1.6.1",
"tokio 1.6.2",
"webpki",
]
@ -3661,7 +3679,7 @@ dependencies = [
"futures-sink",
"log",
"pin-project-lite",
"tokio 1.6.1",
"tokio 1.6.2",
]
[[package]]

View File

@ -9,7 +9,8 @@ crate-type = ["cdylib", "lib"]
name = "bridge"
[features]
no-entrypoint = ["solitaire/no-entrypoint", "solitaire-client"]
no-entrypoint = ["solitaire/no-entrypoint"]
client = ["solitaire-client"]
no-idl = []
cpi = ["no-entrypoint"]
default = []

View File

@ -12,8 +12,10 @@ use solitaire::{
AccountState,
Data,
Derive,
Info,
};
pub type FeeCollector<'a> = Derive<Info<'a>, "fee_collector">;
pub type Bridge<'a, const State: AccountState> = Derive<Data<'a, BridgeData, { State }>, "Bridge">;
pub type GuardianSet<'b, const State: AccountState> = Data<'b, types::GuardianSetData, { State }>;
@ -30,6 +32,24 @@ impl<'b, const State: AccountState> Seeded<&GuardianSetDerivationData>
}
}
pub type Claim<'b, const State: AccountState> = Data<'b, types::ClaimData, { State }>;
pub struct ClaimDerivationData {
pub emitter_address: [u8; 32],
pub emitter_chain: u16,
pub sequence: u64,
}
impl<'b, const State: AccountState> Seeded<&ClaimDerivationData> for Claim<'b, { State }> {
fn seeds(data: &ClaimDerivationData) -> Vec<Vec<u8>> {
return vec![
data.emitter_address.to_vec(),
data.emitter_chain.to_be_bytes().to_vec(),
data.sequence.to_be_bytes().to_vec(),
];
}
}
pub type SignatureSet<'b, const State: AccountState> = Data<'b, types::SignatureSet, { State }>;
pub struct SignaturesSetDerivationData {
@ -48,15 +68,21 @@ pub type Message<'b, const State: AccountState> = Data<'b, PostedMessage, { Stat
pub struct MessageDerivationData {
pub emitter_key: [u8; 32],
pub sequence: u64,
pub emitter_chain: u16,
pub nonce: u32,
pub payload: Vec<u8>,
}
impl<'b, const State: AccountState> Seeded<&MessageDerivationData> for Message<'b, { State }> {
fn seeds(data: &MessageDerivationData) -> Vec<Vec<u8>> {
vec![
let mut seeds = vec![
data.emitter_key.to_vec(),
data.sequence.to_be_bytes().to_vec(),
]
data.emitter_chain.to_be_bytes().to_vec(),
data.nonce.to_be_bytes().to_vec(),
];
seeds.append(&mut data.payload.chunks(32).map(|v| v.to_vec()).collect());
seeds
}
}

View File

@ -1,6 +1,7 @@
use crate::{
accounts::{
Bridge,
FeeCollector,
Message,
MessageDerivationData,
Sequence,
@ -28,15 +29,6 @@ use solitaire::{
type UninitializedMessage<'b> = Message<'b, { AccountState::Uninitialized }>;
impl<'a> From<&PostMessage<'a>> for MessageDerivationData {
fn from(accs: &PostMessage<'a>) -> Self {
MessageDerivationData {
emitter_key: accs.emitter.key.to_bytes(),
sequence: accs.sequence.sequence,
}
}
}
impl<'a> From<&PostMessage<'a>> for SequenceDerivationData<'a> {
fn from(accs: &PostMessage<'a>) -> Self {
SequenceDerivationData {
@ -45,12 +37,9 @@ impl<'a> From<&PostMessage<'a>> for SequenceDerivationData<'a> {
}
}
pub type FeeAccount<'a> = Derive<Info<'a>, "Fees">;
#[derive(FromAccounts)]
pub struct PostMessage<'b> {
pub bridge: Bridge<'b, { AccountState::Initialized }>,
pub fee_vault: FeeAccount<'b>,
/// Account to store the posted message
pub message: UninitializedMessage<'b>,
@ -65,17 +54,13 @@ pub struct PostMessage<'b> {
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 fee_collector: FeeCollector<'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.into())?;
self.sequence.verify_derivation(program_id, &self.into())?;
Ok(())
}
@ -87,8 +72,6 @@ pub struct PostMessageData {
pub nonce: u32,
/// message payload
pub payload: Vec<u8>,
/// Emitter address
pub emitter: Pubkey,
}
pub fn post_message(
@ -96,6 +79,15 @@ pub fn post_message(
accs: &mut PostMessage,
data: PostMessageData,
) -> Result<()> {
let msg_derivation = MessageDerivationData {
emitter_key: accs.emitter.key.to_bytes(),
emitter_chain: 1,
nonce: data.nonce,
payload: data.payload.clone(),
};
accs.message
.verify_derivation(ctx.program_id, &msg_derivation)?;
// Fee handling
let fee = transfer_fee();
if accs
@ -115,18 +107,18 @@ pub fn post_message(
.create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?;
}
// Create message account
accs.message
.create(&(&*accs).into(), 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.payload = data.payload;
accs.message.sequence = accs.sequence.sequence;
// Create message account
accs.message
.create(&msg_derivation, ctx, accs.payer.key, Exempt)?;
// Bump sequence number
accs.sequence.sequence += 1;

View File

@ -49,15 +49,6 @@ impl<'a> From<&PostVAA<'a>> for SignaturesSetDerivationData {
}
}
impl From<&PostVAAData> for MessageDerivationData {
fn from(data: &PostVAAData) -> Self {
MessageDerivationData {
emitter_key: data.emitter_address,
sequence: data.sequence,
}
}
}
impl From<&PostVAAData> for GuardianSetDerivationData {
fn from(data: &PostVAAData) -> Self {
GuardianSetDerivationData {
@ -131,8 +122,14 @@ pub struct PostVAAData {
}
pub fn post_vaa(ctx: &ExecutionContext, accs: &mut PostVAA, vaa: PostVAAData) -> Result<()> {
let msg_derivation = MessageDerivationData {
emitter_key: vaa.emitter_address,
emitter_chain: vaa.emitter_chain,
nonce: vaa.nonce,
payload: vaa.payload.clone(),
};
accs.message
.verify_derivation(ctx.program_id, &(&vaa).into())?;
.verify_derivation(ctx.program_id, &msg_derivation)?;
accs.guardian_set
.verify_derivation(ctx.program_id, &(&vaa).into())?;
// Verify any required invariants before we process the instruction.
@ -166,14 +163,13 @@ pub fn post_vaa(ctx: &ExecutionContext, accs: &mut PostVAA, vaa: PostVAAData) ->
// 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(&(&vaa).into(), 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;
accs.message
.create(&msg_derivation, ctx, accs.payer.key, Exempt)?;
}
// Store VAA data in associated message.

View File

@ -1,6 +1,7 @@
// #![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
#![feature(const_generics)]
#![allow(warnings)]
// Salt contains the framework definition, single file for now but to be extracted into a cargo
// package as soon as possible.
pub mod accounts;

View File

@ -22,9 +22,12 @@ use solitaire::{
},
SolitaireError,
};
use std::io::{
Cursor,
Read,
use std::{
io::{
Cursor,
Read,
},
str::FromStr,
};
#[derive(Default, BorshSerialize, BorshDeserialize)]
@ -101,7 +104,7 @@ impl Owned for SignatureSet {
}
}
#[derive(Default, BorshSerialize, BorshDeserialize)]
#[derive(Default, BorshSerialize, BorshDeserialize, Clone)]
pub struct PostedMessage {
/// Header of the posted VAA
pub vaa_version: u8,
@ -133,7 +136,9 @@ pub struct PostedMessage {
impl Owned for PostedMessage {
fn owner(&self) -> AccountOwner {
AccountOwner::This
AccountOwner::Other(
Pubkey::from_str("96RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE").unwrap(),
) // TODO key of the bridge
}
}

View File

@ -1,4 +1,8 @@
use crate::{
accounts::{
Claim,
ClaimDerivationData,
},
types::{
ClaimData,
PostedMessage,
@ -115,21 +119,13 @@ impl<'b, T: DeserializePayload> PayloadMessage<'b, T> {
}
}
data_wrapper!(Claim, ClaimData, AccountState::Uninitialized);
impl<'b, T: DeserializePayload> Seeded<&ClaimableVAA<'b, T>> for Claim<'b> {
fn seeds(_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>,
pub claim: Claim<'b, { AccountState::Uninitialized }>,
}
impl<'b, T: DeserializePayload> Deref for ClaimableVAA<'b, T> {
@ -144,7 +140,14 @@ impl<'b, T: DeserializePayload> InstructionContext<'b> for ClaimableVAA<'b, T> {
// Do the Posted Message verification
// Verify that the claim account is derived correctly
self.claim.verify_derivation(program_id, self)?;
self.claim.verify_derivation(
program_id,
&ClaimDerivationData {
emitter_address: self.message.meta().emitter_address,
emitter_chain: self.message.meta().emitter_chain,
sequence: self.message.meta().sequence,
},
)?;
Ok(())
}
@ -160,7 +163,16 @@ impl<'b, T: DeserializePayload> ClaimableVAA<'b, T> {
return Err(VAAAlreadyExecuted.into());
}
self.claim.create(self, ctx, payer, Exempt)?;
self.claim.create(
&ClaimDerivationData {
emitter_address: self.message.meta().emitter_address,
emitter_chain: self.message.meta().emitter_chain,
sequence: self.message.meta().sequence,
},
ctx,
payer,
Exempt,
)?;
self.claim.claimed = true;
Ok(())

View File

@ -81,6 +81,7 @@ macro_rules! solitaire {
}
use instruction::solitaire;
#[cfg(not(feature = "no-entrypoint"))]
solana_program::entrypoint!(solitaire);
}
}
@ -153,10 +154,14 @@ macro_rules! pack_type {
impl BorshDeserialize for $name {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
Ok($name(
let acc = $name(
solana_program::program_pack::Pack::unpack(buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?,
))
);
// We need to clear the buf to show to Borsh that we've read all data
*buf = &buf[..0];
Ok(acc)
}
}

View File

@ -22,6 +22,8 @@ use borsh::{
BorshSerialize,
};
use solana_program::{
entrypoint::ProgramResult,
instruction::Instruction,
program::invoke_signed,
pubkey::Pubkey,
};
@ -82,6 +84,10 @@ pub trait Seeded<I> {
seeds
}
fn self_bumped_seeds(&self, accs: I, program_id: &Pubkey) -> Vec<Vec<u8>> {
Self::bumped_seeds(accs, program_id)
}
fn verify_derivation<'a, 'b: 'a>(&'a self, program_id: &'a Pubkey, accs: I) -> Result<()>
where
Self: Keyed<'a, 'b>,
@ -147,3 +153,15 @@ impl<'a, const Seed: &'static str, T> Seeded<Option<()>> for Derive<T, Seed> {
vec![Seed.as_bytes().to_vec()]
}
}
pub fn invoke_seeded<I, T: Seeded<I>>(
instruction: &Instruction,
context: &ExecutionContext,
seeded_acc: &T,
accs: I,
) -> ProgramResult {
let seeds = seeded_acc.self_bumped_seeds(accs, context.program_id);
let s: Vec<&[u8]> = seeds.iter().map(|item| item.as_slice()).collect();
let seed_slice = s.as_slice();
invoke_signed(instruction, context.accounts, &[seed_slice])
}

View File

@ -19,6 +19,7 @@ use proc_macro2::{
use quote::{
quote,
quote_spanned,
ToTokens,
};
use std::borrow::BorrowMut;
use syn::{
@ -162,9 +163,12 @@ fn generate_fields(name: &syn::Ident, data: &Data) -> TokenStream2 {
let recurse = fields.named.iter().map(|f| {
// Field name, to assign to.
let name = &f.ident;
let name_string =
format!("Peeling: {}", name.to_token_stream().to_string());
let ty = &f.ty;
quote! {
solana_program::msg!(#name_string);
let #name: #ty = solitaire::Peel::peel(&mut solitaire::Context::new(
pid,
iter,

View File

@ -51,11 +51,11 @@ pub fn generate_to_instruction(
quote! {
/// Solitaire-generated client-side #name representation
#[cfg(feature = "no-entrypoint")]
#[cfg(feature = "client")]
#client_struct_decl
/// Solitaire-generatied ToInstruction implementation
#[cfg(feature = "no-entrypoint")]
#[cfg(feature = "client")]
impl #impl_generics solitaire_client::ToInstruction for #client_struct_name {
fn to_ix(
self,

View File

@ -156,16 +156,18 @@ where
{
fn wrap(a: &AccEntry) -> StdResult<Vec<AccountMeta>, ErrBox> {
if let AccEntry::Sysvar(k) = a {
if Var::check_id(k) {
Ok(vec![AccountMeta::new_readonly(k.clone(), false)])
} else {
Err(format!("{} does not point at sysvar {}", k, std::any::type_name::<Var>()).into())
}
if Var::check_id(k) {
Ok(vec![AccountMeta::new_readonly(k.clone(), false)])
} else {
Err(format!(
"{} does not point at sysvar {}",
k,
std::any::type_name::<Var>()
)
.into())
}
} else {
Err(format!(
"{} must be passed as Sysvar",
std::any::type_name::<Self>()
).into())
Err(format!("{} must be passed as Sysvar", std::any::type_name::<Self>()).into())
}
}
}