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