Implement token bridge

Change-Id: Ibb9cdf6ed5a18a576691bcad96b4fe6dfd72b981
This commit is contained in:
Hendrik Hofstadt 2021-06-07 09:04:23 +02:00
parent aa909c218f
commit d84b61fda5
18 changed files with 6225 additions and 0 deletions

4131
solana/modules/token_bridge/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
[workspace]
members = ["program", "client"]

View File

@ -0,0 +1,20 @@
[package]
name = "anchor-bridge-client"
version = "0.1.0"
authors = ["Stan Drozd <stan@nexantic.com>"]
edition = "2018"
[dependencies]
anyhow = "1.0.40"
borsh = "0.8.1"
bridge = { path = "../../../bridge/program", features = ["no-idl", "no-entrypoint", "client"] }
clap = "3.0.0-beta.2"
rand = "0.7.3"
shellexpand = "2.1.0"
solana-client = "=1.7.0"
solana-program = "=1.7.0"
solana-sdk = "=1.7.0"
spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
solitaire = { path = "../../../solitaire/program" }
solitaire-client = { path = "../../../solitaire/client" }
token-bridge = { path = "../program", features = ["no-idl", "no-entrypoint", "client"] }

View File

@ -0,0 +1,177 @@
use borsh::BorshSerialize;
use bridge::{
api,
types,
};
use clap::Clap;
use solana_client::{
rpc_client::RpcClient,
rpc_config::RpcSendTransactionConfig,
};
use solana_program::pubkey::Pubkey;
use solana_sdk::{
commitment_config::CommitmentConfig,
signature::{
read_keypair_file,
Signer as SolSigner,
},
transaction::Transaction,
};
use solitaire_client::{
AccEntry,
ToInstruction,
};
use bridge::accounts::{
GuardianSet,
GuardianSetDerivationData,
};
use solitaire::{
processors::seeded::Seeded,
AccountState,
};
use std::error;
use solana_sdk::instruction::Instruction;
use solana_sdk::signature::Keypair;
use solana_sdk::client::Client;
use solana_client::rpc_request::RpcError;
use solana_client::client_error::{ClientError, ClientErrorKind};
use solana_sdk::program_pack::Pack;
use solana_sdk::rent::Rent;
use token_bridge::api::{AttestTokenData, TransferNativeData};
use spl_token::instruction::TokenInstruction::Transfer;
use spl_token::state::Mint;
use solitaire_client::solana_sdk::account::ReadableAccount;
#[derive(Clap)]
pub struct Opts {
#[clap(long)]
bridge_address: Pubkey,
#[clap(long)]
token_address: Pubkey,
}
pub type ErrBox = Box<dyn error::Error>;
pub const DEFAULT_MESSAGE_FEE: u64 = 42;
pub const DEFAULT_GUARDIAN_SET_EXPIRATION_TIME: u32 = 42;
fn main() -> Result<(), ErrBox> {
let opts = Opts::parse();
let payer = read_keypair_file(&*shellexpand::tilde("~/.config/solana/id.json"))
.expect("Example requires a keypair file");
// Keypair is not Clone
let url = "http://localhost:8899".to_owned();
let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
let program_id = opts.bridge_address;
use AccEntry::*;
let init = api::InitializeAccounts {
bridge: Derived(program_id.clone()),
guardian_set: Unprivileged(<GuardianSet<'_, { AccountState::Uninitialized }>>::key(
&GuardianSetDerivationData { index: 0 },
&program_id,
)),
payer: Signer(payer),
};
let init_args = bridge::instruction::Instruction::Initialize(types::BridgeConfig {
guardian_set_expiration_time: DEFAULT_GUARDIAN_SET_EXPIRATION_TIME,
fee: DEFAULT_MESSAGE_FEE,
});
let ix_data = init_args.try_to_vec()?;
let payer = read_keypair_file(&*shellexpand::tilde("~/.config/solana/id.json"))
.expect("Example requires a keypair file");
let (ix, signers) = init.to_ix(program_id, ix_data.as_slice())?;
send_ix_in_tx(&client, ix, &payer, signers.iter().collect())?;
let payer = read_keypair_file(&*shellexpand::tilde("~/.config/solana/id.json"))
.expect("Example requires a keypair file");
let token_program_id = opts.token_address;
let init_token = token_bridge::instructions::initialize(token_program_id, payer.pubkey(), program_id).unwrap();
send_ix_in_tx(&client, init_token, &payer, vec![&payer])?;
// Create a token
let mint_authority = Keypair::new();
let mint = Keypair::new();
let init_mint_account = solana_sdk::system_instruction::create_account(&payer.pubkey(), &mint.pubkey(), Rent::default().minimum_balance(spl_token::state::Mint::LEN), spl_token::state::Mint::LEN as u64, &spl_token::id());
let init_mint = spl_token::instruction::initialize_mint(&spl_token::id(), &mint.pubkey(), &mint_authority.pubkey(), None, 8)?;
send_ix_in_tx(&client, init_mint_account, &payer, vec![&payer, &mint])?;
send_ix_in_tx(&client, init_mint, &payer, vec![&payer])?;
// Attest a token
let rando = Keypair::new();
let mint_data = get_mint(&client, &mint.pubkey())?;
let attest_token = token_bridge::instructions::attest(token_program_id, program_id, payer.pubkey(), mint.pubkey(), mint_data, rando.pubkey(), 0).unwrap();
send_ix_in_tx(&client, attest_token, &payer, vec![&payer])?;
// Create a token account
let token_authority = Keypair::new();
let token_acc = Keypair::new();
let init_token_sys = solana_sdk::system_instruction::create_account(&payer.pubkey(), &token_acc.pubkey(), Rent::default().minimum_balance(spl_token::state::Account::LEN), spl_token::state::Account::LEN as u64, &spl_token::id());
let init_token_account = spl_token::instruction::initialize_account(&spl_token::id(), &token_acc.pubkey(), &mint.pubkey(), &token_authority.pubkey())?;
send_ix_in_tx(&client, init_token_sys, &payer, vec![&payer, &token_acc])?;
send_ix_in_tx(&client, init_token_account, &payer, vec![&payer])?;
// Mint tokens
let mint_ix = spl_token::instruction::mint_to(&spl_token::id(), &mint.pubkey(), &token_acc.pubkey(), &mint_authority.pubkey(), &[], 1000)?;
send_ix_in_tx(&client, mint_ix, &payer, vec![&payer, &mint_authority])?;
// Give allowance
let bridge_token_authority = token_bridge::accounts::AuthoritySigner::key(None, &token_program_id);
let allowance_ix = spl_token::instruction::approve(&spl_token::id(), &token_acc.pubkey(), &bridge_token_authority, &token_authority.pubkey(), &[], 1000)?;
send_ix_in_tx(&client, allowance_ix, &payer, vec![&payer, &token_authority])?;
// Transfer to ETH
let transfer_eth = token_bridge::instructions::transfer_native(token_program_id, program_id, payer.pubkey(), token_acc.pubkey(), mint.pubkey(), TransferNativeData {
nonce: 1,
amount: 500,
fee: 0,
target_address: [2; 32],
target_chain: 2,
}).unwrap();
send_ix_in_tx(&client, transfer_eth, &payer, vec![&payer])?;
let transfer_eth = token_bridge::instructions::transfer_native(token_program_id, program_id, payer.pubkey(), token_acc.pubkey(), mint.pubkey(), TransferNativeData {
nonce: 2,
amount: 500,
fee: 0,
target_address: [2; 32],
target_chain: 2,
}).unwrap();
send_ix_in_tx(&client, transfer_eth, &payer, vec![&payer])?;
Ok(())
}
fn send_ix_in_tx(client: &RpcClient, ix: Instruction, payer: &Keypair, signers: Vec<&Keypair>) -> Result<(), ClientError> {
let mut tx = Transaction::new_with_payer(&[ix], Some(&payer.pubkey()));
let (recent_blockhash, _) = client.get_recent_blockhash()?;
tx.try_sign(&signers, recent_blockhash)?;
println!("Transaction signed.");
let signature = client.send_and_confirm_transaction_with_spinner_and_config(
&tx,
CommitmentConfig::processed(),
RpcSendTransactionConfig {
skip_preflight: true,
preflight_commitment: None,
encoding: None,
},
)?;
println!("Signature: {}", signature);
Ok(())
}
fn get_mint(client: &RpcClient, mint: &Pubkey) -> Result<Mint, ClientError> {
let acc = client.get_account(mint)?;
Mint::unpack(acc.data()).map_err(|e| ClientError { request: None, kind: ClientErrorKind::Custom(String::from("Could not deserialize mint")) })
}

View File

@ -0,0 +1,28 @@
[package]
name = "token-bridge"
version = "0.1.0"
description = "Created with Rocksalt"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "token_bridge"
[features]
no-entrypoint = ["solitaire/no-entrypoint"]
client = ["solitaire-client"]
no-idl = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
bridge = { path = "../../../bridge/program", features = ["no-entrypoint", "cpi"] }
borsh = "0.8.1"
byteorder = "1.4.3"
rocksalt = { path = "../../../solitaire/rocksalt" }
solitaire = { path = "../../../solitaire/program" }
sha3 = "0.9.1"
solana-program = "*"
spl-token = { version = "=3.1.0", features = ["no-entrypoint"] }
primitive-types = { version = "0.9.0", default-features = false }
solitaire-client = { path = "../../../solitaire/client", optional = true }

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,85 @@
use crate::types::*;
use bridge::{
api::ForeignAddress,
vaa::{
DeserializePayload,
PayloadMessage,
},
};
use solana_program::pubkey::Pubkey;
use solitaire::{
processors::seeded::Seeded,
*,
};
pub type AuthoritySigner<'b> = Derive<Info<'b>, "authority_signer">;
pub type CustodySigner<'b> = Derive<Info<'b>, "custody_signer">;
pub type MintSigner<'b> = Derive<Info<'b>, "mint_signer">;
pub type EmitterAccount<'b> = Derive<Info<'b>, "emitter">;
pub type ConfigAccount<'b, const State: AccountState> =
Derive<Data<'b, Config, { State }>, "config">;
pub type CustodyAccount<'b, const State: AccountState> = Data<'b, SplAccount, { State }>;
pub struct CustodyAccountDerivationData {
pub mint: Pubkey,
}
impl<'b, const State: AccountState> Seeded<&CustodyAccountDerivationData>
for CustodyAccount<'b, { State }>
{
fn seeds(accs: &CustodyAccountDerivationData) -> Vec<Vec<u8>> {
vec![accs.mint.to_bytes().to_vec()]
}
}
pub type WrappedMint<'b, const State: AccountState> = Data<'b, SplMint, { State }>;
pub struct WrappedDerivationData {
pub token_chain: ChainID,
pub token_address: ForeignAddress,
}
impl<'b, const State: AccountState> Seeded<&WrappedDerivationData> for WrappedMint<'b, { State }> {
fn seeds(data: &WrappedDerivationData) -> Vec<Vec<u8>> {
vec![
String::from("wrapped").as_bytes().to_vec(),
data.token_chain.to_be_bytes().to_vec(),
data.token_address.to_vec(),
]
}
}
pub type WrappedTokenMeta<'b, const State: AccountState> = Data<'b, WrappedMeta, { State }>;
impl<'b, const State: AccountState> Seeded<&WrappedDerivationData>
for WrappedTokenMeta<'b, { State }>
{
fn seeds(data: &WrappedDerivationData) -> Vec<Vec<u8>> {
vec![
String::from("meta").as_bytes().to_vec(),
data.token_chain.to_be_bytes().to_vec(),
data.token_address.to_vec(),
]
}
}
/// Registered chain endpoint
pub type Endpoint<'b, const State: AccountState> = Data<'b, EndpointRegistration, { State }>;
pub struct EndpointDerivationData {
pub emitter_chain: u16,
pub emitter_address: ForeignAddress,
}
/// Seeded implementation based on an incoming VAA
impl<'b, const State: AccountState> Seeded<&EndpointDerivationData> for Endpoint<'b, { State }> {
fn seeds(data: &EndpointDerivationData) -> Vec<Vec<u8>> {
vec![
data.emitter_chain.to_be_bytes().to_vec(),
data.emitter_address.to_vec(),
]
}
}

View File

@ -0,0 +1,13 @@
pub mod attest;
pub mod complete_transfer;
pub mod create_wrapped;
pub mod initialize;
pub mod register_chain;
pub mod transfer;
pub use attest::*;
pub use complete_transfer::*;
pub use create_wrapped::*;
pub use initialize::*;
pub use register_chain::*;
pub use transfer::*;

View File

@ -0,0 +1,134 @@
use crate::{
accounts::{
ConfigAccount,
EmitterAccount,
},
messages::{
PayloadAssetMeta,
PayloadTransfer,
},
types::*,
};
use bridge::{
api::{
PostMessage,
PostMessageData,
},
vaa::SerializePayload,
};
use primitive_types::U256;
use solana_program::{
account_info::AccountInfo,
instruction::{
AccountMeta,
Instruction,
},
program::{
invoke,
invoke_signed,
},
program_error::ProgramError,
pubkey::Pubkey,
sysvar::clock::Clock,
};
use solitaire::{
CreationLamports::Exempt,
*,
};
use spl_token::{
error::TokenError::OwnerMismatch,
state::{
Account,
Mint,
},
};
use std::ops::{
Deref,
DerefMut,
};
use solitaire::processors::seeded::invoke_seeded;
#[derive(FromAccounts)]
pub struct AttestToken<'b> {
pub payer: Signer<AccountInfo<'b>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
/// Mint to attest
pub mint: Data<'b, SplMint, { AccountState::Initialized }>,
pub mint_meta: Data<'b, SplMint, { AccountState::MaybeInitialized }>,
/// CPI Context
pub bridge: Info<'b>,
/// Account to store the posted message
pub message: Info<'b>,
/// Emitter of the VAA
pub emitter: EmitterAccount<'b>,
/// Tracker for the emitter sequence
pub sequence: Info<'b>,
/// Account to collect tx fee
pub fee_collector: Info<'b>,
pub clock: Sysvar<'b, Clock>,
}
impl<'b> InstructionContext<'b> for AttestToken<'b> {
fn verify(&self, _: &Pubkey) -> Result<()> {
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct AttestTokenData {
pub nonce: u32,
}
pub fn attest_token(
ctx: &ExecutionContext,
accs: &mut AttestToken,
data: AttestTokenData,
) -> Result<()> {
// Pay fee
let transfer_ix =
solana_program::system_instruction::transfer(accs.payer.key, accs.fee_collector.key, 1000);
invoke(&transfer_ix, ctx.accounts)?;
let payload = PayloadAssetMeta {
token_address: accs.mint.info().key.to_bytes(),
token_chain: 1,
decimals: U256::from(accs.mint.decimals),
symbol: "".to_string(), // TODO metadata
name: "".to_string(),
};
if accs.mint_meta.is_initialized() {
// Populate fields
}
let params = bridge::instruction::Instruction::PostMessage(PostMessageData {
nonce: data.nonce,
payload: payload.try_to_vec()?,
});
let ix = Instruction::new_with_bytes(
accs.config.wormhole_bridge,
params.try_to_vec()?.as_slice(),
vec![
AccountMeta::new_readonly(*accs.bridge.key, false),
AccountMeta::new(*accs.message.key, false),
AccountMeta::new_readonly(*accs.emitter.key, true),
AccountMeta::new(*accs.sequence.key, false),
AccountMeta::new(*accs.payer.key, true),
AccountMeta::new(*accs.fee_collector.key, false),
AccountMeta::new_readonly(*accs.clock.info().key, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
],
);
invoke_seeded(&ix, ctx, &accs.emitter, None)?;
Ok(())
}

View File

@ -0,0 +1,207 @@
use crate::{
accounts::{
ConfigAccount,
CustodyAccount,
CustodyAccountDerivationData,
CustodySigner,
Endpoint,
EndpointDerivationData,
MintSigner,
WrappedDerivationData,
WrappedMint,
},
messages::PayloadTransfer,
types::*,
TokenBridgeError::*,
};
use bridge::vaa::ClaimableVAA;
use solana_program::{
account_info::AccountInfo,
program::invoke_signed,
program_error::ProgramError,
pubkey::Pubkey,
};
use solitaire::{
processors::seeded::{
invoke_seeded,
Seeded,
},
CreationLamports::Exempt,
*,
};
use spl_token::state::{
Account,
Mint,
};
use std::ops::{
Deref,
DerefMut,
};
#[derive(FromAccounts)]
pub struct CompleteNative<'b> {
pub payer: Signer<AccountInfo<'b>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub vaa: ClaimableVAA<'b, PayloadTransfer>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
pub to: Data<'b, SplAccount, { AccountState::Initialized }>,
pub custody: CustodyAccount<'b, { AccountState::Initialized }>,
pub mint: Data<'b, SplMint, { AccountState::Initialized }>,
pub custody_signer: CustodySigner<'b>,
}
impl<'a> From<&CompleteNative<'a>> for EndpointDerivationData {
fn from(accs: &CompleteNative<'a>) -> Self {
EndpointDerivationData {
emitter_chain: accs.vaa.meta().emitter_chain,
emitter_address: accs.vaa.meta().emitter_address,
}
}
}
impl<'a> From<&CompleteNative<'a>> for CustodyAccountDerivationData {
fn from(accs: &CompleteNative<'a>) -> Self {
CustodyAccountDerivationData {
mint: *accs.mint.info().key,
}
}
}
impl<'b> InstructionContext<'b> for CompleteNative<'b> {
fn verify(&self, program_id: &Pubkey) -> Result<()> {
// Verify the chain registration
self.chain_registration
.verify_derivation(program_id, &(self.into()))?;
// Verify that the custody account is derived correctly
self.custody.verify_derivation(program_id, &(self.into()))?;
// Verify mints
if self.mint.info().key != self.to.info().key {
return Err(InvalidMint.into());
}
if self.mint.info().key != self.custody.info().key {
return Err(InvalidMint.into());
}
if &self.custody.owner != self.custody_signer.key {
return Err(InvalidMint.into());
}
// Verify VAA
if self.vaa.token_address != self.mint.info().key.to_bytes() {
return Err(InvalidMint.into());
}
if self.vaa.token_chain != 1 {
return Err(InvalidChain.into());
}
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct CompleteNativeData {}
pub fn complete_native(
ctx: &ExecutionContext,
accs: &mut CompleteNative,
data: CompleteNativeData,
) -> Result<()> {
// Prevent vaa double signing
accs.vaa.claim(ctx, accs.payer.key)?;
// Transfer tokens
let transfer_ix = spl_token::instruction::transfer(
&spl_token::id(),
accs.custody.info().key,
accs.to.info().key,
accs.custody_signer.key,
&[],
accs.vaa.amount.as_u64(),
)?;
invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
// TODO fee
Ok(())
}
#[derive(FromAccounts)]
pub struct CompleteWrapped<'b> {
pub payer: Signer<AccountInfo<'b>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
// Signed message for the transfer
pub vaa: ClaimableVAA<'b, PayloadTransfer>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
pub to: Data<'b, SplAccount, { AccountState::Initialized }>,
pub mint: WrappedMint<'b, { AccountState::Initialized }>,
pub mint_authority: MintSigner<'b>,
}
impl<'a> From<&CompleteWrapped<'a>> for EndpointDerivationData {
fn from(accs: &CompleteWrapped<'a>) -> Self {
EndpointDerivationData {
emitter_chain: accs.vaa.meta().emitter_chain,
emitter_address: accs.vaa.meta().emitter_address,
}
}
}
impl<'a> From<&CompleteWrapped<'a>> for WrappedDerivationData {
fn from(accs: &CompleteWrapped<'a>) -> Self {
WrappedDerivationData {
token_chain: accs.vaa.token_chain,
token_address: accs.vaa.token_address,
}
}
}
impl<'b> InstructionContext<'b> for CompleteWrapped<'b> {
fn verify(&self, program_id: &Pubkey) -> Result<()> {
// Verify the chain registration
self.chain_registration
.verify_derivation(program_id, &(self.into()))?;
// Verify mint
self.mint.verify_derivation(program_id, &(self.into()))?;
// Verify mints
if self.mint.info().key != self.to.info().key {
return Err(InvalidMint.into());
}
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct CompleteWrappedData {}
pub fn complete_wrapped(
ctx: &ExecutionContext,
accs: &mut CompleteWrapped,
data: CompleteWrappedData,
) -> Result<()> {
accs.vaa.claim(ctx, accs.payer.key)?;
// Mint tokens
let mint_ix = spl_token::instruction::mint_to(
&spl_token::id(),
accs.mint.info().key,
accs.to.info().key,
accs.mint_authority.key,
&[],
accs.vaa.amount.as_u64(),
)?;
invoke_seeded(&mint_ix, ctx, &accs.mint_authority, None)?;
// TODO fee
Ok(())
}

View File

@ -0,0 +1,115 @@
use crate::{
accounts::{
ConfigAccount,
Endpoint,
EndpointDerivationData,
MintSigner,
WrappedDerivationData,
WrappedMint,
WrappedTokenMeta,
},
messages::PayloadAssetMeta,
types::*,
};
use bridge::vaa::ClaimableVAA;
use solana_program::{
account_info::AccountInfo,
program::invoke_signed,
program_error::ProgramError,
pubkey::Pubkey,
};
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
*,
};
use spl_token::{
error::TokenError::OwnerMismatch,
state::{
Account,
Mint,
},
};
use std::ops::{
Deref,
DerefMut,
};
#[derive(FromAccounts)]
pub struct CreateWrapped<'b> {
pub payer: Signer<AccountInfo<'b>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
pub vaa: ClaimableVAA<'b, PayloadAssetMeta>,
// New Wrapped
pub mint: WrappedMint<'b, { AccountState::Uninitialized }>,
pub meta: WrappedTokenMeta<'b, { AccountState::Uninitialized }>,
pub mint_authority: MintSigner<'b>,
}
impl<'a> From<&CreateWrapped<'a>> for EndpointDerivationData {
fn from(accs: &CreateWrapped<'a>) -> Self {
EndpointDerivationData {
emitter_chain: accs.vaa.meta().emitter_chain,
emitter_address: accs.vaa.meta().emitter_address,
}
}
}
impl<'a> From<&CreateWrapped<'a>> for WrappedDerivationData {
fn from(accs: &CreateWrapped<'a>) -> Self {
WrappedDerivationData {
token_chain: accs.vaa.token_chain,
token_address: accs.vaa.token_address,
}
}
}
impl<'b> InstructionContext<'b> for CreateWrapped<'b> {
fn verify(&self, program_id: &Pubkey) -> Result<()> {
self.mint.verify_derivation(program_id, &(self.into()))?;
self.meta.verify_derivation(program_id, &(self.into()))?;
self.chain_registration
.verify_derivation(program_id, &(self.into()))?;
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct CreateWrappedData {}
pub fn create_wrapped(
ctx: &ExecutionContext,
accs: &mut CreateWrapped,
data: CreateWrappedData,
) -> Result<()> {
accs.vaa.claim(ctx, accs.payer.key)?;
// Create mint account
accs.mint
.create(&((&*accs).into()), ctx, accs.payer.key, Exempt);
// Initialize mint
let init_ix = spl_token::instruction::initialize_mint(
&spl_token::id(),
accs.mint.info().key,
accs.mint_authority.key,
None,
8,
)?;
invoke_signed(&init_ix, ctx.accounts, &[])?;
// Create meta account
accs.meta
.create(&((&*accs).into()), ctx, accs.payer.key, Exempt);
// Populate meta account
accs.meta.chain = accs.vaa.token_chain;
accs.meta.token_address = accs.vaa.token_address;
Ok(())
}

View File

@ -0,0 +1,37 @@
use crate::{
accounts::ConfigAccount,
types::*,
};
use solana_program::{
account_info::AccountInfo,
program_error::ProgramError,
pubkey::Pubkey,
};
use solitaire::{
CreationLamports::Exempt,
*,
};
use std::ops::{
Deref,
DerefMut,
};
#[derive(FromAccounts)]
pub struct Initialize<'b> {
pub payer: Signer<AccountInfo<'b>>,
pub config: ConfigAccount<'b, { AccountState::Uninitialized }>,
}
impl<'b> InstructionContext<'b> for Initialize<'b> {
}
pub fn initialize(
ctx: &ExecutionContext,
accs: &mut Initialize,
wormhole_bridge: Pubkey,
) -> Result<()> {
// Create the config account
accs.config.create(ctx, accs.payer.key, Exempt)?;
accs.config.wormhole_bridge = wormhole_bridge;
Ok(())
}

View File

@ -0,0 +1,75 @@
use crate::{
accounts::{
ConfigAccount,
Endpoint,
EndpointDerivationData,
},
messages::PayloadGovernanceRegisterChain,
types::*,
};
use bridge::vaa::{
ClaimableVAA,
DeserializePayload,
PayloadMessage,
};
use solana_program::{
account_info::AccountInfo,
program_error::ProgramError,
pubkey::Pubkey,
};
use solitaire::{
processors::seeded::Seeded,
CreationLamports::Exempt,
*,
};
use std::ops::{
Deref,
DerefMut,
};
#[derive(FromAccounts)]
pub struct RegisterChain<'b> {
pub payer: Signer<AccountInfo<'b>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub endpoint: Endpoint<'b, { AccountState::Uninitialized }>,
pub vaa: ClaimableVAA<'b, PayloadGovernanceRegisterChain>,
}
impl<'a> From<&RegisterChain<'a>> for EndpointDerivationData {
fn from(accs: &RegisterChain<'a>) -> Self {
EndpointDerivationData {
emitter_chain: accs.vaa.meta().emitter_chain,
emitter_address: accs.vaa.meta().emitter_address,
}
}
}
impl<'b> InstructionContext<'b> for RegisterChain<'b> {
fn verify(&self, program_id: &Pubkey) -> Result<()> {
self.endpoint.verify_derivation(program_id, &self.into())?;
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct RegisterChainData {}
pub fn register_chain(
ctx: &ExecutionContext,
accs: &mut RegisterChain,
data: RegisterChainData,
) -> Result<()> {
// Claim VAA
accs.vaa.claim(ctx, accs.payer.key)?;
// Create endpoint
accs.endpoint
.create(&((&*accs).into()), ctx, accs.payer.key, Exempt);
accs.endpoint.chain = accs.vaa.chain;
accs.endpoint.contract = accs.vaa.endpoint_address;
Ok(())
}

View File

@ -0,0 +1,322 @@
use crate::{
accounts::{
AuthoritySigner,
ConfigAccount,
CustodyAccount,
CustodyAccountDerivationData,
CustodySigner,
EmitterAccount,
MintSigner,
WrappedDerivationData,
WrappedMint,
WrappedTokenMeta,
},
messages::PayloadTransfer,
types::*,
TokenBridgeError,
TokenBridgeError::WrongAccountOwner,
};
use bridge::{
api::{
PostMessage,
PostMessageData,
},
vaa::SerializePayload,
};
use primitive_types::U256;
use solana_program::{
account_info::AccountInfo,
instruction::{
AccountMeta,
Instruction,
},
program::{
invoke,
invoke_signed,
},
program_error::ProgramError,
program_option::COption,
pubkey::Pubkey,
sysvar::clock::Clock,
};
use solitaire::{
processors::seeded::{
invoke_seeded,
Seeded,
},
CreationLamports::Exempt,
*,
};
use spl_token::{
error::TokenError::OwnerMismatch,
state::{
Account,
Mint,
},
};
use std::ops::{
Deref,
DerefMut,
};
#[derive(FromAccounts)]
pub struct TransferNative<'b> {
pub payer: Signer<AccountInfo<'b>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub from: Data<'b, SplAccount, { AccountState::Initialized }>,
pub mint: Data<'b, SplMint, { AccountState::Initialized }>,
pub custody: CustodyAccount<'b, { AccountState::MaybeInitialized }>,
// This could allow someone to race someone else's tx if they do the approval in a separate tx.
// Therefore the approval must be set in the same tx
pub authority_signer: AuthoritySigner<'b>,
pub custody_signer: CustodySigner<'b>,
/// CPI Context
pub bridge: Info<'b>,
/// Account to store the posted message
pub message: Info<'b>,
/// Emitter of the VAA
pub emitter: EmitterAccount<'b>,
/// Tracker for the emitter sequence
pub sequence: Info<'b>,
/// Account to collect tx fee
pub fee_collector: Info<'b>,
pub clock: Sysvar<'b, Clock>,
}
impl<'a> From<&TransferNative<'a>> for CustodyAccountDerivationData {
fn from(accs: &TransferNative<'a>) -> Self {
CustodyAccountDerivationData {
mint: *accs.mint.info().key,
}
}
}
impl<'b> InstructionContext<'b> for TransferNative<'b> {
fn verify(&self, program_id: &Pubkey) -> Result<()> {
// Verify that the custody account is derived correctly
self.custody.verify_derivation(program_id, &self.into())?;
// Verify mints
if self.mint.info().key != self.from.info().key {
return Err(TokenBridgeError::InvalidMint.into());
}
// Verify that the token is not a wrapped token
if let COption::Some(mint_authority) = self.mint.mint_authority {
if mint_authority == MintSigner::key(None, program_id) {
return Err(TokenBridgeError::TokenNotNative.into());
}
}
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct TransferNativeData {
pub nonce: u32,
pub amount: u64,
pub fee: u64,
pub target_address: Address,
pub target_chain: ChainID,
}
pub fn transfer_native(
ctx: &ExecutionContext,
accs: &mut TransferNative,
data: TransferNativeData,
) -> Result<()> {
if !accs.custody.is_initialized() {
accs.custody
.create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?;
let init_ix = spl_token::instruction::initialize_account(
&spl_token::id(),
accs.custody.info().key,
accs.mint.info().key,
accs.custody_signer.key,
)?;
invoke_signed(&init_ix, ctx.accounts, &[])?;
}
// Transfer tokens
let transfer_ix = spl_token::instruction::transfer(
&spl_token::id(),
accs.from.info().key,
accs.custody.info().key,
accs.authority_signer.key,
&[],
data.amount,
)?;
invoke_seeded(&transfer_ix, ctx, &accs.authority_signer, None)?;
// Pay fee
let transfer_ix =
solana_program::system_instruction::transfer(accs.payer.key, accs.fee_collector.key, 1000);
invoke(&transfer_ix, ctx.accounts)?;
// Post message
let payload = PayloadTransfer {
amount: U256::from(data.amount),
token_address: accs.mint.info().key.to_bytes(),
token_chain: 1,
to: data.target_address,
to_chain: data.target_chain,
fee: U256::from(data.fee),
};
let params = bridge::instruction::Instruction::PostMessage(PostMessageData {
nonce: data.nonce,
payload: payload.try_to_vec()?,
});
let ix = Instruction::new_with_bytes(
accs.config.wormhole_bridge,
params.try_to_vec()?.as_slice(),
vec![
AccountMeta::new_readonly(*accs.bridge.key, false),
AccountMeta::new(*accs.message.key, false),
AccountMeta::new_readonly(*accs.emitter.key, true),
AccountMeta::new(*accs.sequence.key, false),
AccountMeta::new(*accs.payer.key, true),
AccountMeta::new(*accs.fee_collector.key, false),
AccountMeta::new_readonly(*accs.clock.info().key, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
],
);
invoke_seeded(&ix, ctx, &accs.emitter, None)?;
Ok(())
}
#[derive(FromAccounts)]
pub struct TransferWrapped<'b> {
pub payer: Signer<AccountInfo<'b>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub from: Data<'b, SplAccount, { AccountState::Initialized }>,
pub from_owner: Signer<Info<'b>>,
pub mint: WrappedMint<'b, { AccountState::Initialized }>,
pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>,
pub mint_authority: MintSigner<'b>,
/// CPI Context
pub bridge: Info<'b>,
/// Account to store the posted message
pub message: Info<'b>,
/// Emitter of the VAA
pub emitter: EmitterAccount<'b>,
/// Tracker for the emitter sequence
pub sequence: Info<'b>,
/// Account to collect tx fee
pub fee_collector: Info<'b>,
pub clock: Sysvar<'b, Clock>,
}
impl<'a> From<&TransferWrapped<'a>> for WrappedDerivationData {
fn from(accs: &TransferWrapped<'a>) -> Self {
WrappedDerivationData {
token_chain: 1,
token_address: accs.mint.info().key.to_bytes(),
}
}
}
impl<'b> InstructionContext<'b> for TransferWrapped<'b> {
fn verify(&self, program_id: &Pubkey) -> Result<()> {
// Verify that the from account is owned by the from_owner
if &self.from.owner != self.from_owner.key {
return Err(WrongAccountOwner.into());
}
// Verify mints
if self.mint.info().key != &self.from.mint {
return Err(TokenBridgeError::InvalidMint.into());
}
// Verify that meta is correct
self.wrapped_meta
.verify_derivation(program_id, &self.into())?;
Ok(())
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct TransferWrappedData {
pub nonce: u32,
pub amount: u64,
pub fee: u64,
pub target_address: Address,
pub target_chain: ChainID,
}
pub fn transfer_wrapped(
ctx: &ExecutionContext,
accs: &mut TransferWrapped,
data: TransferWrappedData,
) -> Result<()> {
// Burn tokens
let burn_ix = spl_token::instruction::burn(
&spl_token::id(),
accs.from.info().key,
accs.mint.info().key,
accs.mint_authority.key,
&[],
data.amount,
)?;
invoke_seeded(&burn_ix, ctx, &accs.mint_authority, None)?;
// Pay fee
let transfer_ix =
solana_program::system_instruction::transfer(accs.payer.key, accs.fee_collector.key, 1000);
invoke(&transfer_ix, ctx.accounts)?;
// Post message
let payload = PayloadTransfer {
amount: U256::from(data.amount),
token_address: accs.wrapped_meta.token_address,
token_chain: accs.wrapped_meta.chain,
to: data.target_address,
to_chain: data.target_chain,
fee: U256::from(data.fee),
};
let params = bridge::instruction::Instruction::PostMessage(PostMessageData {
nonce: data.nonce,
payload: payload.try_to_vec()?,
});
let ix = Instruction::new_with_bytes(
accs.config.wormhole_bridge,
params.try_to_vec()?.as_slice(),
vec![
AccountMeta::new_readonly(*accs.bridge.key, false),
AccountMeta::new(*accs.message.key, false),
AccountMeta::new_readonly(*accs.emitter.key, true),
AccountMeta::new(*accs.sequence.key, false),
AccountMeta::new(*accs.payer.key, true),
AccountMeta::new(*accs.fee_collector.key, false),
AccountMeta::new_readonly(*accs.clock.info().key, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
],
);
invoke_seeded(&ix, ctx, &accs.emitter, None)?;
Ok(())
}

View File

@ -0,0 +1,513 @@
use crate::{
accounts::{
AuthoritySigner,
ConfigAccount,
CustodyAccount,
CustodyAccountDerivationData,
CustodySigner,
EmitterAccount,
Endpoint,
EndpointDerivationData,
MintSigner,
WrappedDerivationData,
WrappedMint,
WrappedTokenMeta,
},
api::{
complete_transfer::{
CompleteNativeData,
CompleteWrappedData,
},
AttestTokenData,
CreateWrappedData,
RegisterChainData,
TransferNativeData,
TransferWrappedData,
},
messages::{
PayloadAssetMeta,
PayloadGovernanceRegisterChain,
PayloadTransfer,
},
};
use borsh::BorshSerialize;
use bridge::{
accounts::{
Bridge,
Claim,
ClaimDerivationData,
FeeCollector,
Message,
MessageDerivationData,
Sequence,
SequenceDerivationData,
},
api::ForeignAddress,
types::{
BridgeConfig,
PostedMessage,
},
vaa::{
ClaimableVAA,
PayloadMessage,
},
};
use solana_program::instruction::Instruction;
use solitaire::{
processors::seeded::Seeded,
AccountState,
};
use solitaire_client::{
AccountMeta,
Keypair,
Pubkey,
};
use bridge::vaa::SerializePayload;
use primitive_types::U256;
use spl_token::state::Mint;
pub fn initialize(
program_id: Pubkey,
payer: Pubkey,
bridge: Pubkey,
) -> solitaire::Result<Instruction> {
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(config_key, false),
// Dependencies
AccountMeta::new(solana_program::sysvar::rent::id(), false),
AccountMeta::new(solana_program::system_program::id(), false),
],
data: crate::instruction::Instruction::Initialize(bridge).try_to_vec()?,
})
}
pub fn complete_native(
program_id: Pubkey,
bridge_id: Pubkey,
payer: Pubkey,
message_key: Pubkey,
message: PostedMessage,
to: Pubkey,
mint: Pubkey,
data: CompleteNativeData,
) -> solitaire::Result<Instruction> {
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let (message_acc, claim_acc) = claimable_vaa(bridge_id, message_key, message.clone());
let endpoint = Endpoint::<'_, { AccountState::Initialized }>::key(
&EndpointDerivationData {
emitter_chain: message.emitter_chain,
emitter_address: message.emitter_address,
},
&program_id,
);
let custody_key = CustodyAccount::<'_, { AccountState::Initialized }>::key(
&CustodyAccountDerivationData { mint },
&program_id,
);
let custody_signer_key = CustodySigner::key(None, &program_id);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(config_key, false),
message_acc,
claim_acc,
AccountMeta::new_readonly(endpoint, false),
AccountMeta::new(to, false),
AccountMeta::new(custody_key, false),
AccountMeta::new_readonly(mint, false),
AccountMeta::new_readonly(custody_signer_key, false),
// Dependencies
AccountMeta::new(solana_program::sysvar::rent::id(), false),
AccountMeta::new(solana_program::system_program::id(), false),
// Program
AccountMeta::new_readonly(bridge_id, false),
],
data: crate::instruction::Instruction::CompleteNative(data).try_to_vec()?,
})
}
pub fn complete_wrapped(
program_id: Pubkey,
bridge_id: Pubkey,
payer: Pubkey,
message_key: Pubkey,
message: PostedMessage,
payload: PayloadTransfer,
to: Pubkey,
data: CompleteWrappedData,
) -> solitaire::Result<Instruction> {
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let (message_acc, claim_acc) = claimable_vaa(bridge_id, message_key, message.clone());
let endpoint = Endpoint::<'_, { AccountState::Initialized }>::key(
&EndpointDerivationData {
emitter_chain: message.emitter_chain,
emitter_address: message.emitter_address,
},
&bridge_id,
);
let mint_key = WrappedMint::<'_, { AccountState::Uninitialized }>::key(
&WrappedDerivationData {
token_chain: payload.token_chain,
token_address: payload.token_address,
},
&program_id,
);
let mint_authority_key = MintSigner::key(None, &program_id);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(config_key, false),
message_acc,
claim_acc,
AccountMeta::new_readonly(endpoint, false),
AccountMeta::new(to, false),
AccountMeta::new_readonly(mint_key, false),
AccountMeta::new_readonly(mint_authority_key, false),
// Dependencies
AccountMeta::new(solana_program::sysvar::rent::id(), false),
AccountMeta::new(solana_program::system_program::id(), false),
// Program
AccountMeta::new_readonly(bridge_id, false),
],
data: crate::instruction::Instruction::CompleteWrapped(data).try_to_vec()?,
})
}
pub fn create_wrapped(
program_id: Pubkey,
bridge_id: Pubkey,
payer: Pubkey,
message_key: Pubkey,
message: PostedMessage,
payload: PayloadAssetMeta,
data: CreateWrappedData,
) -> solitaire::Result<Instruction> {
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let (message_acc, claim_acc) = claimable_vaa(bridge_id, message_key, message.clone());
let endpoint = Endpoint::<'_, { AccountState::Initialized }>::key(
&EndpointDerivationData {
emitter_chain: message.emitter_chain,
emitter_address: message.emitter_address,
},
&bridge_id,
);
let mint_key = WrappedMint::<'_, { AccountState::Uninitialized }>::key(
&WrappedDerivationData {
token_chain: payload.token_chain,
token_address: payload.token_address,
},
&program_id,
);
let mint_meta_key = WrappedTokenMeta::<'_, { AccountState::Uninitialized }>::key(
&WrappedDerivationData {
token_chain: payload.token_chain,
token_address: payload.token_address,
},
&program_id,
);
let mint_authority_key = MintSigner::key(None, &program_id);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(config_key, false),
AccountMeta::new_readonly(endpoint, false),
message_acc,
claim_acc,
AccountMeta::new(mint_key, false),
AccountMeta::new(mint_meta_key, false),
AccountMeta::new_readonly(mint_authority_key, false),
// Dependencies
AccountMeta::new(solana_program::sysvar::rent::id(), false),
AccountMeta::new(solana_program::system_program::id(), false),
// Program
AccountMeta::new_readonly(bridge_id, false),
],
data: crate::instruction::Instruction::CreateWrapped(data).try_to_vec()?,
})
}
pub fn register_chain(
program_id: Pubkey,
bridge_id: Pubkey,
payer: Pubkey,
message_key: Pubkey,
message: PostedMessage,
payload: PayloadGovernanceRegisterChain,
data: RegisterChainData,
) -> solitaire::Result<Instruction> {
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let (message_acc, claim_acc) = claimable_vaa(bridge_id, message_key, message.clone());
let endpoint = Endpoint::<'_, { AccountState::Initialized }>::key(
&EndpointDerivationData {
emitter_chain: message.emitter_chain,
emitter_address: message.emitter_address,
},
&bridge_id,
);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(config_key, false),
AccountMeta::new_readonly(endpoint, false),
message_acc,
claim_acc,
// Dependencies
AccountMeta::new(solana_program::sysvar::rent::id(), false),
AccountMeta::new(solana_program::system_program::id(), false),
// Program
AccountMeta::new_readonly(bridge_id, false),
],
data: crate::instruction::Instruction::RegisterChain(data).try_to_vec()?,
})
}
fn claimable_vaa(
bridge_id: Pubkey,
message_key: Pubkey,
message: PostedMessage,
) -> (AccountMeta, AccountMeta) {
let claim_key = Claim::<'_, { AccountState::Initialized }>::key(
&ClaimDerivationData {
emitter_address: message.emitter_address,
emitter_chain: message.emitter_chain,
sequence: message.sequence,
},
&bridge_id,
);
(
AccountMeta::new_readonly(message_key, false),
AccountMeta::new(claim_key, false),
)
}
pub fn transfer_native(
program_id: Pubkey,
bridge_id: Pubkey,
payer: Pubkey,
from: Pubkey,
mint: Pubkey,
data: TransferNativeData,
) -> solitaire::Result<Instruction> {
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let custody_key = CustodyAccount::<'_, { AccountState::Initialized }>::key(
&CustodyAccountDerivationData { mint },
&program_id,
);
let authority_signer_key = AuthoritySigner::key(None, &program_id);
let custody_signer_key = CustodySigner::key(None, &program_id);
let emitter_key = EmitterAccount::key(None, &program_id);
// Bridge keys
let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
let payload = PayloadTransfer {
amount: U256::from(data.amount),
token_address: mint.to_bytes(),
token_chain: 1,
to: data.target_address,
to_chain: data.target_chain,
fee: U256::from(data.fee),
};
let message_key = Message::<'_, { AccountState::Uninitialized }>::key(
&MessageDerivationData {
emitter_key: emitter_key.to_bytes(),
emitter_chain: 1,
nonce: data.nonce,
payload: payload.try_to_vec().unwrap(),
},
&bridge_id,
);
let sequence_key = Sequence::key(
&SequenceDerivationData {
emitter_key: &emitter_key,
},
&bridge_id,
);
let fee_collector_key = FeeCollector::key(None, &bridge_id);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(config_key, false),
AccountMeta::new(from, false),
AccountMeta::new(mint, false),
AccountMeta::new(custody_key, false),
AccountMeta::new_readonly(authority_signer_key, false),
AccountMeta::new_readonly(custody_signer_key, false),
AccountMeta::new_readonly(bridge_config, false),
AccountMeta::new(message_key, false),
AccountMeta::new_readonly(emitter_key, false),
AccountMeta::new(sequence_key, false),
AccountMeta::new(fee_collector_key, false),
AccountMeta::new(solana_program::sysvar::clock::id(), false),
// Dependencies
AccountMeta::new(solana_program::sysvar::rent::id(), false),
AccountMeta::new(solana_program::system_program::id(), false),
// Program
AccountMeta::new_readonly(bridge_id, false),
AccountMeta::new_readonly(spl_token::id(), false),
],
data: crate::instruction::Instruction::TransferNative(data).try_to_vec()?,
})
}
pub fn transfer_wrapped(
program_id: Pubkey,
bridge_id: Pubkey,
payer: Pubkey,
from: Pubkey,
from_owner: Pubkey,
token_chain: u16,
token_address: ForeignAddress,
data: TransferWrappedData,
sequence: u64,
) -> solitaire::Result<Instruction> {
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let wrapped_mint_key = WrappedMint::<'_, { AccountState::Uninitialized }>::key(
&WrappedDerivationData {
token_chain,
token_address,
},
&program_id,
);
let wrapped_meta_key = WrappedTokenMeta::<'_, { AccountState::Uninitialized }>::key(
&WrappedDerivationData {
token_chain,
token_address,
},
&program_id,
);
let mint_authority_key = MintSigner::key(None, &program_id);
let emitter_key = EmitterAccount::key(None, &program_id);
// Bridge keys
let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
let payload = PayloadTransfer {
amount: U256::from(data.amount),
token_address,
token_chain,
to: data.target_address,
to_chain: data.target_chain,
fee: U256::from(data.fee),
};
let message_key = Message::<'_, { AccountState::Uninitialized }>::key(
&MessageDerivationData {
emitter_key: emitter_key.to_bytes(),
emitter_chain: 1,
nonce: data.nonce,
payload: payload.try_to_vec().unwrap(),
},
&bridge_id,
);
let sequence_key = Sequence::key(
&SequenceDerivationData {
emitter_key: &emitter_key,
},
&bridge_id,
);
let fee_collector_key = FeeCollector::key(None, &bridge_id);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(config_key, false),
AccountMeta::new(from, false),
AccountMeta::new(from_owner, true),
AccountMeta::new_readonly(wrapped_mint_key, false),
AccountMeta::new_readonly(wrapped_meta_key, false),
AccountMeta::new_readonly(mint_authority_key, false),
AccountMeta::new_readonly(bridge_config, false),
AccountMeta::new(message_key, false),
AccountMeta::new_readonly(emitter_key, false),
AccountMeta::new(sequence_key, false),
AccountMeta::new(fee_collector_key, false),
AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
// Dependencies
AccountMeta::new(solana_program::sysvar::rent::id(), false),
AccountMeta::new(solana_program::system_program::id(), false),
// Program
AccountMeta::new_readonly(bridge_id, false),
AccountMeta::new_readonly(spl_token::id(), false),
],
data: crate::instruction::Instruction::TransferWrapped(data).try_to_vec()?,
})
}
pub fn attest(
program_id: Pubkey,
bridge_id: Pubkey,
payer: Pubkey,
mint: Pubkey,
mint_data: Mint,
mint_meta: Pubkey,
nonce: u32,
) -> solitaire::Result<Instruction> {
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let emitter_key = EmitterAccount::key(None, &program_id);
// Bridge keys
let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
let payload = PayloadAssetMeta {
token_address: mint.to_bytes(),
token_chain: 1,
decimals: U256::from(mint_data.decimals),
symbol: "".to_string(), // TODO metadata
name: "".to_string(),
};
let message_key = Message::<'_, { AccountState::Uninitialized }>::key(
&MessageDerivationData {
emitter_key: emitter_key.to_bytes(),
emitter_chain: 1,
nonce,
payload: payload.try_to_vec().unwrap(),
},
&bridge_id,
);
let sequence_key = Sequence::key(
&SequenceDerivationData {
emitter_key: &emitter_key,
},
&bridge_id,
);
let fee_collector_key = FeeCollector::key(None, &bridge_id);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(config_key, false),
AccountMeta::new_readonly(mint, false),
AccountMeta::new_readonly(mint_meta, false),
// Bridge accounts
AccountMeta::new_readonly(bridge_config, false),
AccountMeta::new(message_key, false),
AccountMeta::new_readonly(emitter_key, false),
AccountMeta::new(sequence_key, false),
AccountMeta::new(fee_collector_key, false),
AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
// Dependencies
AccountMeta::new(solana_program::sysvar::rent::id(), false),
AccountMeta::new(solana_program::system_program::id(), false),
// Program
AccountMeta::new_readonly(bridge_id, false),
],
data: crate::instruction::Instruction::AttestToken(AttestTokenData { nonce }).try_to_vec()?,
})
}

View File

@ -0,0 +1,76 @@
#![feature(const_generics)]
#![feature(const_generics_defaults)]
#![allow(warnings)]
// #![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
#[cfg(feature = "no-entrypoint")]
pub mod instructions;
pub mod accounts;
pub mod api;
pub mod messages;
pub mod types;
use api::{
attest_token,
complete_native,
complete_wrapped,
create_wrapped,
initialize,
register_chain,
transfer_native,
transfer_wrapped,
AttestToken,
AttestTokenData,
CompleteNative,
CompleteNativeData,
CompleteWrapped,
CompleteWrappedData,
CreateWrapped,
CreateWrappedData,
Initialize,
RegisterChain,
RegisterChainData,
TransferNative,
TransferNativeData,
TransferWrapped,
TransferWrappedData,
};
use solitaire::*;
use std::error::Error;
pub enum TokenBridgeError {
InvalidPayload,
Unknown(String),
InvalidMint,
WrongAccountOwner,
InvalidUTF8String,
AlreadyExecuted,
InvalidChain,
TokenNotNative,
}
impl<T: Error> From<T> for TokenBridgeError {
fn from(t: T) -> Self {
return TokenBridgeError::Unknown(t.to_string());
}
}
impl Into<SolitaireError> for TokenBridgeError {
fn into(self) -> SolitaireError {
SolitaireError::Custom(0)
}
}
solitaire! {
Initialize(Pubkey) => initialize,
AttestToken(AttestTokenData) => attest_token,
CompleteNative(CompleteNativeData) => complete_native,
CompleteWrapped(CompleteWrappedData) => complete_wrapped,
TransferWrapped(TransferWrappedData) => transfer_wrapped,
TransferNative(TransferNativeData) => transfer_native,
RegisterChain(RegisterChainData) => register_chain,
CreateWrapped(CreateWrappedData) => create_wrapped,
}

View File

@ -0,0 +1,218 @@
use crate::{
types::{
Address,
ChainID,
},
TokenBridgeError,
};
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use bridge::vaa::{
DeserializePayload,
SerializePayload,
};
use byteorder::{
BigEndian,
ReadBytesExt,
WriteBytesExt,
};
use primitive_types::U256;
use solana_program::{
native_token::Sol,
program_error::ProgramError,
};
use solitaire::SolitaireError;
use std::{
error::Error,
io::{
Cursor,
Read,
Write,
},
str::Utf8Error,
string::FromUtf8Error,
};
pub struct PayloadTransfer {
// Amount being transferred (big-endian uint256)
pub amount: U256,
// Address of the token. Left-zero-padded if shorter than 32 bytes
pub token_address: Address,
// Chain ID of the token
pub token_chain: ChainID,
// Address of the recipient. Left-zero-padded if shorter than 32 bytes
pub to: Address,
// Chain ID of the recipient
pub to_chain: ChainID,
// Amount of tokens (big-endian uint256) that the user is willing to pay as relayer fee. Must be <= Amount.
pub fee: U256,
}
impl DeserializePayload for PayloadTransfer {
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut v = Cursor::new(buf);
if v.read_u8()? != 1 {
return Err(SolitaireError::Custom(0));
};
let mut am_data: [u8; 32] = [0; 32];
v.read_exact(&mut am_data)?;
let amount = U256::from_big_endian(&am_data);
let mut token_address = Address::default();
v.read_exact(&mut token_address)?;
let token_chain = v.read_u16::<BigEndian>()?;
let mut to = Address::default();
v.read_exact(&mut to)?;
let to_chain = v.read_u16::<BigEndian>()?;
let mut fee_data: [u8; 32] = [0; 32];
v.read_exact(&mut fee_data)?;
let fee = U256::from_big_endian(&fee_data);
Ok(PayloadTransfer {
amount,
token_address,
token_chain,
to,
to_chain,
fee,
})
}
}
impl SerializePayload for PayloadTransfer {
fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), SolitaireError> {
// Payload ID
writer.write_u8(1)?;
let mut am_data: [u8; 32] = [0; 32];
self.amount.to_big_endian(&mut am_data);
writer.write(&am_data)?;
writer.write(&self.token_address)?;
writer.write_u16::<BigEndian>(self.token_chain)?;
writer.write(&self.to)?;
writer.write_u16::<BigEndian>(self.to_chain)?;
let mut fee_data: [u8; 32] = [0; 32];
self.fee.to_big_endian(&mut fee_data);
writer.write(&fee_data)?;
Ok(())
}
}
pub struct PayloadAssetMeta {
// Address of the token. Left-zero-padded if shorter than 32 bytes
pub token_address: Address,
// Chain ID of the token
pub token_chain: ChainID,
// Number of decimals of the token (big-endian uint256)
pub decimals: U256,
// Symbol of the token
pub symbol: String,
// Name of the token
pub name: String,
}
impl DeserializePayload for PayloadAssetMeta {
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut v = Cursor::new(buf);
if v.read_u8()? != 2 {
return Err(SolitaireError::Custom(0));
};
let mut token_address = Address::default();
v.read_exact(&mut token_address)?;
let token_chain = v.read_u16::<BigEndian>()?;
let mut decimals_data: [u8; 32] = [0; 32];
v.read_exact(&mut decimals_data)?;
let decimals = U256::from_big_endian(&decimals_data);
let mut symbol_data: [u8; 32] = [0; 32];
v.read_exact(&mut symbol_data)?;
let symbol = String::from_utf8(symbol_data.to_vec())
.map_err::<SolitaireError, _>(|_| TokenBridgeError::InvalidUTF8String.into())?;
let mut name_data: [u8; 32] = [0; 32];
v.read_exact(&mut name_data)?;
let name = String::from_utf8(name_data.to_vec())
.map_err::<SolitaireError, _>(|_| TokenBridgeError::InvalidUTF8String.into())?;
Ok(PayloadAssetMeta {
token_address,
token_chain,
decimals,
symbol,
name,
})
}
}
impl SerializePayload for PayloadAssetMeta {
fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), SolitaireError> {
// Payload ID
writer.write_u8(2)?;
writer.write(&self.token_address)?;
writer.write_u16::<BigEndian>(self.token_chain)?;
let mut decimal_data: [u8; 32] = [0; 32];
self.decimals.to_big_endian(&mut decimal_data);
writer.write(&decimal_data)?;
let mut symbol: [u8; 32] = [0; 32];
for i in 0..self.symbol.len() {
symbol[(32 - self.symbol.len()) + i] = self.symbol.as_bytes()[i];
}
writer.write(&symbol);
let mut name: [u8; 32] = [0; 32];
for i in 0..self.name.len() {
name[(32 - self.name.len()) + i] = self.name.as_bytes()[i];
}
writer.write(&name);
Ok(())
}
}
pub struct PayloadGovernanceRegisterChain {
// Chain ID of the chain to be registered
pub chain: ChainID,
// Address of the endpoint on the chain
pub endpoint_address: Address,
}
impl DeserializePayload for PayloadGovernanceRegisterChain {
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
let mut v = Cursor::new(buf);
if v.read_u8()? != 2 {
return Err(SolitaireError::Custom(0));
};
Ok(PayloadGovernanceRegisterChain {
chain: 0,
endpoint_address: [0u8; 32],
})
}
}
impl SerializePayload for PayloadGovernanceRegisterChain {
fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), SolitaireError> {
// Payload ID
writer.write_u8(2)?;
Ok(())
}
}

View File

@ -0,0 +1,70 @@
use borsh::{
BorshDeserialize,
BorshSerialize,
};
use solana_program::pubkey::Pubkey;
use solitaire::{
pack_type,
processors::seeded::{
AccountOwner,
Owned,
},
};
use spl_token::state::{
Account,
Mint,
};
pub type Address = [u8; 32];
pub type ChainID = u16;
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize)]
pub struct Config {
pub wormhole_bridge: Pubkey,
pub fees: FeeStructure,
}
impl Owned for Config {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize)]
pub struct FeeStructure {
pub usd_ephemeral: u64,
pub usd_persistent: u64,
}
impl Owned for FeeStructure {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize)]
pub struct EndpointRegistration {
pub chain: ChainID,
pub contract: Address,
}
impl Owned for EndpointRegistration {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize)]
pub struct WrappedMeta {
pub chain: ChainID,
pub token_address: Address,
}
impl Owned for WrappedMeta {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));