Implement token bridge
Change-Id: Ibb9cdf6ed5a18a576691bcad96b4fe6dfd72b981
This commit is contained in:
parent
aa909c218f
commit
d84b61fda5
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
|||
[workspace]
|
||||
members = ["program", "client"]
|
|
@ -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"] }
|
|
@ -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")) })
|
||||
}
|
|
@ -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 }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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(),
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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::*;
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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()?,
|
||||
})
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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()));
|
Loading…
Reference in New Issue