From 30666fd7fff320e965c6f79119487df0b95bc4f3 Mon Sep 17 00:00:00 2001 From: Hendrik Hofstadt Date: Fri, 7 Aug 2020 21:48:22 +0200 Subject: [PATCH] track wrapped asset meta, test CLI with acc creation --- docs/solana_program.md | 4 +- solana/bridge/src/instruction.rs | 15 +- solana/bridge/src/processor.rs | 43 ++-- solana/bridge/src/state.rs | 37 +++ solana/cli/Cargo.lock | 3 +- solana/cli/Cargo.toml | 3 +- solana/cli/src/main.rs | 376 ++++++++++++++++++++++++------- 7 files changed, 379 insertions(+), 102 deletions(-) diff --git a/docs/solana_program.md b/docs/solana_program.md index 36b55f40..f9e3ceec 100644 --- a/docs/solana_program.md +++ b/docs/solana_program.md @@ -106,6 +106,7 @@ followed by: | 6 | token_program | SplToken | | | ️ | | | 7 | token | WrappedAsset | | | opt | ✅ | | 8 | destination | TokenAccount | | ✅ | opt | | +| 9 | wrapped_meta_account | WrappedAssetMeta | | ✅ | opt | ✅ | ##### Transfer: Ethereum (wrapped) -> Solana (native) @@ -120,8 +121,7 @@ followed by: | Index | Name | Type | signer | writeable | empty | derived | | ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- | -| 7 | out_proposal | TransferOutProposal | | ✅ | | ✅ | -| 8 | sender | Account | ✅ | | | | +| 6 | out_proposal | TransferOutProposal | | ✅ | | ✅ | ## Accounts diff --git a/solana/bridge/src/instruction.rs b/solana/bridge/src/instruction.rs index f1b336db..a614a4fa 100644 --- a/solana/bridge/src/instruction.rs +++ b/solana/bridge/src/instruction.rs @@ -11,7 +11,7 @@ use solana_sdk::{ }; use crate::error::Error::VAATooLong; -use crate::instruction::BridgeInstruction::Initialize; +use crate::instruction::BridgeInstruction::{Initialize, PostVAA, TransferOut}; use crate::state::{AssetMeta, Bridge, BridgeConfig}; use crate::syscalls::RawKey; use crate::vaa::{VAABody, VAA}; @@ -118,6 +118,16 @@ impl BridgeInstruction { Initialize(*payload) } + 1 => { + let payload: &TransferOutPayload = unpack(input)?; + + TransferOut(*payload) + } + 2 => { + let payload: &VAAData = unpack(input)?; + + PostVAA(*payload) + } _ => return Err(ProgramError::InvalidInstructionData), }) } @@ -310,9 +320,12 @@ pub fn post_vaa( t.asset.chain, t.asset.address, )?; + let wrapped_meta_key = + Bridge::derive_wrapped_meta_id(program_id, &bridge_key, &wrapped_key)?; accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); accounts.push(AccountMeta::new(wrapped_key, false)); accounts.push(AccountMeta::new(Pubkey::new(&t.target_address), false)); + accounts.push(AccountMeta::new(wrapped_meta_key, false)); } } } diff --git a/solana/bridge/src/processor.rs b/solana/bridge/src/processor.rs index d2bfcd86..d4c553d3 100644 --- a/solana/bridge/src/processor.rs +++ b/solana/bridge/src/processor.rs @@ -133,6 +133,7 @@ impl Bridge { accounts: &[AccountInfo], t: &TransferOutPayload, ) -> ProgramResult { + info!("wrapped transfer out"); let account_info_iter = &mut accounts.iter(); next_account_info(account_info_iter)?; // System program next_account_info(account_info_iter)?; // Token program @@ -145,18 +146,12 @@ impl Bridge { let sender = Bridge::token_account_deserialize(sender_account_info)?; let bridge = Bridge::bridge_deserialize(bridge_info)?; - let mint = Bridge::mint_deserialize(mint_info)?; // Does the token belong to the mint if sender.mint != *mint_info.key { return Err(Error::TokenMintMismatch.into()); } - // Is the mint owned by the program - if mint.owner.unwrap() != *program_id { - return Err(Error::WrongMintOwner.into()); - } - // Check that the mint is actually a wrapped asset belonging to *this* bridge instance let expected_mint_address = Bridge::derive_wrapped_asset_id( program_id, @@ -218,6 +213,7 @@ impl Bridge { accounts: &[AccountInfo], t: &TransferOutPayload, ) -> ProgramResult { + info!("native transfer out"); let account_info_iter = &mut accounts.iter(); next_account_info(account_info_iter)?; // System program next_account_info(account_info_iter)?; // Token program @@ -230,18 +226,12 @@ impl Bridge { let sender = Bridge::token_account_deserialize(sender_account_info)?; let bridge = Bridge::bridge_deserialize(bridge_info)?; - let mint = Bridge::mint_deserialize(mint_info)?; // Does the token belong to the mint if sender.mint != *mint_info.key { return Err(Error::TokenMintMismatch.into()); } - // If the mint is owned by the program, it's a wrapped asset - if mint.owner.unwrap() == *program_id { - return Err(Error::WrongMintOwner.into()); - } - // Create transfer account let transfer_seed = Bridge::derive_transfer_id_seeds( bridge_info.key, @@ -293,7 +283,7 @@ impl Bridge { return Err(Error::WrongTokenAccountOwner.into()); } - // Transfer tokens to custody + // Transfer tokens to custody - This also checks that custody mint = mint Bridge::token_transfer_caller( accounts, &bridge.config.token_program, @@ -500,6 +490,7 @@ impl Bridge { next_account_info(account_info_iter)?; // Token program let mint_info = next_account_info(account_info_iter)?; let destination_info = next_account_info(account_info_iter)?; + let wrapped_meta_info = next_account_info(account_info_iter)?; let destination = Self::token_account_deserialize(destination_info)?; if destination.mint != *mint_info.key { @@ -546,6 +537,25 @@ impl Bridge { payer_info.key, &b.asset, )?; + + // Check and create wrapped asset meta if it is unset + let wrapped_meta_seeds = + Bridge::derive_wrapped_meta_seeds(bridge_info.key, mint_info.key); + Bridge::check_and_create_account::( + program_id, + accounts, + wrapped_meta_info.key, + payer_info.key, + &wrapped_meta_seeds, + )?; + + let mut wrapped_meta_data = wrapped_meta_info.data.borrow_mut(); + let wrapped_meta: &mut WrappedAssetMeta = + Bridge::unpack_unchecked(&mut wrapped_meta_data)?; + + wrapped_meta.is_initialized = true; + wrapped_meta.address = b.asset.address; + wrapped_meta.chain = b.asset.chain; } Bridge::wrapped_mint_to( @@ -735,7 +745,7 @@ impl Bridge { payer: &Pubkey, ) -> Result<(), ProgramError> { Self::create_account::( - program_id, + token_program, accounts, mint, payer, @@ -756,7 +766,7 @@ impl Bridge { asset: &AssetMeta, ) -> Result<(), ProgramError> { Self::create_account::( - program_id, + token_program, accounts, mint, payer, @@ -806,7 +816,8 @@ impl Bridge { program_id, ); let s: Vec<_> = seeds.iter().map(|item| item.as_slice()).collect(); - invoke_signed(&ix, accounts, &[s.as_slice()]) + //invoke_signed(&ix, accounts, &[s.as_slice()]) + Ok(()) } } diff --git a/solana/bridge/src/state.rs b/solana/bridge/src/state.rs index f4015f91..04189365 100644 --- a/solana/bridge/src/state.rs +++ b/solana/bridge/src/state.rs @@ -99,6 +99,25 @@ impl IsInitialized for ClaimedVAA { } } +/// metadata tracking for wrapped assets +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct WrappedAssetMeta { + /// chain id of the native chain of this asset + pub chain: u8, + /// address of the asset on the native chain + pub address: ForeignAddress, + + /// Is `true` if this structure has been initialized. + pub is_initialized: bool, +} + +impl IsInitialized for WrappedAssetMeta { + fn is_initialized(&self) -> bool { + self.is_initialized + } +} + /// Metadata about an asset #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq)] @@ -265,6 +284,15 @@ impl Bridge { ] } + /// Calculates derived seeds for a wrapped asset meta entry + pub fn derive_wrapped_meta_seeds<'a>(bridge: &Pubkey, mint: &Pubkey) -> Vec> { + vec![ + "claim".as_bytes().to_vec(), + bridge.to_bytes().to_vec(), + mint.to_bytes().to_vec(), + ] + } + /// Calculates a derived address for this program pub fn derive_bridge_id(program_id: &Pubkey) -> Result { Self::derive_key(program_id, &Self::derive_bridge_seeds()) @@ -288,6 +316,15 @@ impl Bridge { Self::derive_key(program_id, &Self::derive_claim_seeds(bridge, hash)) } + /// Calculates a derived address for a wrapped asset meta entry + pub fn derive_wrapped_meta_id( + program_id: &Pubkey, + bridge: &Pubkey, + mint: &Pubkey, + ) -> Result { + Self::derive_key(program_id, &Self::derive_wrapped_meta_seeds(bridge, mint)) + } + /// Calculates a derived address for this program pub fn derive_guardian_set_id( program_id: &Pubkey, diff --git a/solana/cli/Cargo.lock b/solana/cli/Cargo.lock index bc3609fa..b4b673eb 100644 --- a/solana/cli/Cargo.lock +++ b/solana/cli/Cargo.lock @@ -291,13 +291,14 @@ name = "cli" version = "0.1.0" dependencies = [ "clap", + "primitive-types", "solana-clap-utils", "solana-cli-config", "solana-client", "solana-faucet", "solana-logger", "solana-sdk", - "spl-token 1.0.6", + "spl-token 1.0.3", "wormhole-bridge", ] diff --git a/solana/cli/Cargo.toml b/solana/cli/Cargo.toml index fa29b012..fec8318e 100644 --- a/solana/cli/Cargo.toml +++ b/solana/cli/Cargo.toml @@ -14,5 +14,6 @@ solana-logger = { version = "1.2.17" } solana-sdk = { version = "1.2.17" } solana-client = { version = "1.2.17" } solana-faucet = "1.2.17" -spl-token = { version = "1.0.6" } +spl-token = { package = "spl-token", git="https://github.com/hendrikhofstadt/solana-program-library", branch="256b-primitive-types" } wormhole-bridge = { path = "../bridge" } +primitive-types = {version ="0.7.2", default-features = false} diff --git a/solana/cli/src/main.rs b/solana/cli/src/main.rs index dbac9b3d..f8b9a42f 100644 --- a/solana/cli/src/main.rs +++ b/solana/cli/src/main.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use std::net::{IpAddr, SocketAddr}; use std::str::FromStr; use std::thread::sleep; @@ -8,13 +9,16 @@ use clap::{ crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg, SubCommand, }; +use primitive_types::U256; use solana_clap_utils::{ input_parsers::{keypair_of, pubkey_of}, input_validators::{is_amount, is_keypair, is_pubkey_or_keypair, is_url}, }; use solana_client::rpc_client::RpcClient; +use solana_client::rpc_request::TokenAccountsFilter; use solana_faucet::faucet::request_airdrop_transaction; use solana_sdk::hash::Hash; +use solana_sdk::system_instruction::create_account; use solana_sdk::{ native_token::*, pubkey::Pubkey, @@ -28,8 +32,13 @@ use spl_token::{ state::{Account, Mint}, }; -use spl_bridge::instruction::initialize; -use spl_bridge::state::BridgeConfig; +use spl_bridge::instruction::{ + initialize, post_vaa, transfer_out, BridgeInstruction, ForeignAddress, TransferOutPayload, + VAAData, CHAIN_ID_SOLANA, +}; +use spl_bridge::state::{ + AssetMeta, Bridge, BridgeConfig, ClaimedVAA, GuardianSet, TransferOutProposal, +}; use spl_bridge::syscalls::RawKey; struct Config { @@ -41,50 +50,6 @@ struct Config { type Error = Box; type CommmandResult = Result, Error>; -fn requestAirdrop(config: &Config, request_sol: u64) -> CommmandResult { - let (blockhash, _fee_calculator) = config.rpc_client.get_recent_blockhash()?; - let faucet_addr = SocketAddr::new(IpAddr::from_str("127.0.0.1").unwrap(), 9900); - match { - let mut retries = 5; - loop { - let result = FaucetKeypair::new_keypair( - &faucet_addr, - &config.owner.pubkey(), - LAMPORTS_PER_SOL * request_sol, - blockhash, - ); - if result.is_ok() || retries == 0 { - break result; - } - retries -= 1; - sleep(Duration::from_secs(1)); - } - } { - Ok(kp) => Ok(Some(kp.airdrop_transaction())), - Err(e) => Err(e), - } -} - -struct FaucetKeypair { - transaction: Transaction, -} - -impl FaucetKeypair { - fn new_keypair( - faucet_addr: &SocketAddr, - to_pubkey: &Pubkey, - lamports: u64, - blockhash: Hash, - ) -> Result { - let transaction = request_airdrop_transaction(faucet_addr, to_pubkey, lamports, blockhash)?; - Ok(Self { transaction }) - } - - fn airdrop_transaction(&self) -> Transaction { - self.transaction.clone() - } -} - fn check_fee_payer_balance(config: &Config, required_balance: u64) -> Result<(), Error> { let balance = config.rpc_client.get_balance(&config.fee_payer.pubkey())?; if balance < required_balance { @@ -115,28 +80,6 @@ fn check_owner_balance(config: &Config, required_balance: u64) -> Result<(), Err } } -fn command_request_airdrop(config: &Config) -> CommmandResult { - let token = Keypair::new(); - println!("Requesting airdrop"); - - let minimum_balance_for_rent_exemption = config - .rpc_client - .get_minimum_balance_for_rent_exemption(size_of::())?; - - let mut transaction: Transaction = requestAirdrop(config, 20)?.unwrap(); - - let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; - check_fee_payer_balance( - config, - minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()), - )?; - transaction.sign( - &[&config.fee_payer, &config.owner, &token], - recent_blockhash, - ); - Ok(Some(transaction)) -} - fn command_deploy_bridge(config: &Config) -> CommmandResult { println!("Deploying bridge program"); @@ -144,20 +87,134 @@ fn command_deploy_bridge(config: &Config) -> CommmandResult { .rpc_client .get_minimum_balance_for_rent_exemption(size_of::())?; + let p = Pubkey::from_str("7AeSppn3AjaeYScZsnRf1ZRQvtyo4Ke5gx7PAJ3r7BFp").unwrap(); let ix = initialize( - &Pubkey::from_str("5x4kJ1G4UgJc3yNsznZpxEAB2vrnJirSBxZDu1uwaqnZ").unwrap(), + &p, &config.owner.pubkey(), RawKey { x: [8; 32], - y: [2; 32], + y_parity: true, }, &BridgeConfig { vaa_expiration_time: 200000000, token_program: spl_token::id(), }, )?; - println!("bridge: {}, ", ix.accounts[0].pubkey.to_string()); + println!("bridge: {}, ", ix.accounts[2].pubkey.to_string()); println!("payer: {}, ", ix.accounts[3].pubkey.to_string()); + + let mut ix_c = create_account( + &config.owner.pubkey(), + &ix.accounts[2].pubkey, + 100000000, + size_of::() as u64, + &p, + ); + ix_c.accounts[1].is_signer = false; + let mut ix_c2 = create_account( + &config.owner.pubkey(), + &ix.accounts[3].pubkey, + 100000000, + size_of::() as u64, + &p, + ); + ix_c2.accounts[1].is_signer = false; + let mut transaction = + Transaction::new_with_payer(&[ix_c, ix_c2, ix], Some(&config.fee_payer.pubkey())); + + let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; + check_fee_payer_balance( + config, + minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()), + )?; + transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); + Ok(Some(transaction)) +} + +fn command_lock_tokens( + config: &Config, + account: Pubkey, + token: Pubkey, + amount: u64, + to_chain: u8, + target: ForeignAddress, + nonce: u32, +) -> CommmandResult { + println!("Initiating transfer to foreign chain"); + + let minimum_balance_for_rent_exemption = config + .rpc_client + .get_minimum_balance_for_rent_exemption(size_of::())?; + + let p = Pubkey::from_str("7AeSppn3AjaeYScZsnRf1ZRQvtyo4Ke5gx7PAJ3r7BFp").unwrap(); + let ix = transfer_out( + &p, + &config.owner.pubkey(), + &account, + &token, + &TransferOutPayload { + amount: U256::from(amount), + chain_id: to_chain, + asset: AssetMeta { + address: token.to_bytes(), // TODO fetch from WASSET (if WASSET) + chain: CHAIN_ID_SOLANA, //TODO fetch from WASSET (if WASSET) + }, + target, + nonce, + }, + )?; + println!("custody: {}, ", ix.accounts[7].pubkey.to_string()); + + // Approve tokens + let mut ix_a = approve( + &spl_token::id(), + &account, + &ix.accounts[3].pubkey, + &config.owner.pubkey(), + &[], + U256::from(amount), + ); + + // TODO remove create calls + let mut ix_c = create_account( + &config.owner.pubkey(), + &ix.accounts[4].pubkey, + 100000000, + size_of::() as u64, + &p, + ); + ix_c.accounts[1].is_signer = false; + let mut ix_c2 = create_account( + &config.owner.pubkey(), + &ix.accounts[7].pubkey, + 100000000, + size_of::() as u64, + &spl_token::id(), + ); + ix_c2.accounts[1].is_signer = false; + + let mut transaction = + Transaction::new_with_payer(&[ix_a, ix_c, ix_c2, ix], Some(&config.fee_payer.pubkey())); + + let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; + check_fee_payer_balance( + config, + minimum_balance_for_rent_exemption + fee_calculator.calculate_fee(&transaction.message()), + )?; + transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); + Ok(Some(transaction)) +} + +fn command_submit_vaa(config: &Config, vaa: &[u8]) -> CommmandResult { + println!("Submitting VAA"); + + let minimum_balance_for_rent_exemption = config + .rpc_client + .get_minimum_balance_for_rent_exemption(size_of::())?; + + let p = Pubkey::from_str("7AeSppn3AjaeYScZsnRf1ZRQvtyo4Ke5gx7PAJ3r7BFp").unwrap(); + let ix = post_vaa(&p, &config.owner.pubkey(), vaa)?; + let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey())); let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; @@ -191,7 +248,7 @@ fn command_create_token(config: &Config) -> CommmandResult { &token.pubkey(), None, Some(&config.owner.pubkey()), - 0, + U256::from(0), 9, // hard code 9 decimal places to match the sol/lamports relationship )?, ], @@ -293,7 +350,37 @@ fn command_transfer( &recipient, &config.owner.pubkey(), &[], - amount, + U256::from(amount), + )?], + Some(&config.fee_payer.pubkey()), + ); + + let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?; + check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?; + transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash); + Ok(Some(transaction)) +} + +fn command_approve( + config: &Config, + sender: Pubkey, + ui_amount: f64, + recipient: Pubkey, +) -> CommmandResult { + println!( + "Approve {} tokens\n Sender: {}\n Recipient: {}", + ui_amount, sender, recipient + ); + let amount = sol_to_lamports(ui_amount); + + let mut transaction = Transaction::new_with_payer( + &[approve( + &spl_token::id(), + &sender, + &recipient, + &config.owner.pubkey(), + &[], + U256::from(amount), )?], Some(&config.fee_payer.pubkey()), ); @@ -314,7 +401,7 @@ fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResu &source, &config.owner.pubkey(), &[], - amount, + U256::from(amount), )?], Some(&config.fee_payer.pubkey()), ); @@ -344,7 +431,7 @@ fn command_mint( &recipient, &config.owner.pubkey(), &[], - amount, + U256::from(amount), )?], Some(&config.fee_payer.pubkey()), ); @@ -414,8 +501,9 @@ fn command_unwrap(config: &Config, address: Pubkey) -> CommmandResult { Ok(Some(transaction)) } -fn command_balance(_config: &Config, address: Pubkey) -> CommmandResult { - println!("balance {}", address); +fn command_balance(config: &Config, address: Pubkey) -> CommmandResult { + let balance = config.rpc_client.get_token_account_balance(&address)?; + println!("{}", balance); Ok(None) } @@ -424,8 +512,28 @@ fn command_supply(_config: &Config, address: Pubkey) -> CommmandResult { Ok(None) } -fn command_accounts(_config: &Config, token: Option) -> CommmandResult { - println!("accounts {:?}", token); +fn command_accounts(config: &Config, token: Option) -> CommmandResult { + let accounts = config.rpc_client.get_token_accounts_by_owner( + &config.owner.pubkey(), + match token { + Some(token) => TokenAccountsFilter::Mint(token), + None => TokenAccountsFilter::ProgramId(spl_token::id()), + }, + )?; + if accounts.is_empty() { + println!("None"); + } + + println!("Account Token Balance"); + println!("-------------------------------------------------------------------------------------------------"); + for (address, account) in accounts { + let balance = match config.rpc_client.get_token_account_balance(&address) { + Ok(response) => response, + Err(err) => 0, + }; + + println!("{:<44} {:<44} {}", address, account.lamports, balance); + } Ok(None) } @@ -482,7 +590,6 @@ fn main() { ) .subcommand(SubCommand::with_name("create-token").about("Create a new token")) .subcommand(SubCommand::with_name("create-bridge").about("Create a new bridge")) - .subcommand(SubCommand::with_name("airdrop").about("Request an airdrop")) .subcommand( SubCommand::with_name("create-account") .about("Create a new token account") @@ -549,6 +656,86 @@ fn main() { .help("The token account address of recipient"), ), ) + .subcommand( + SubCommand::with_name("approve") + .about("Approve token sprending") + .arg( + Arg::with_name("sender") + .validator(is_pubkey_or_keypair) + .value_name("SENDER_TOKEN_ACCOUNT_ADDRESS") + .takes_value(true) + .index(1) + .required(true) + .help("The token account address of the sender"), + ) + .arg( + Arg::with_name("amount") + .validator(is_amount) + .value_name("TOKEN_AMOUNT") + .takes_value(true) + .index(2) + .required(true) + .help("Amount to send, in tokens"), + ) + .arg( + Arg::with_name("recipient") + .validator(is_pubkey_or_keypair) + .value_name("RECIPIENT_TOKEN_ACCOUNT_ADDRESS") + .takes_value(true) + .index(3) + .required(true) + .help("The token account address of recipient"), + ), + ) + .subcommand( + SubCommand::with_name("lock") + .about("Transfer tokens to another chain") + .arg( + Arg::with_name("sender") + .validator(is_pubkey_or_keypair) + .value_name("SENDER_TOKEN_ACCOUNT_ADDRESS") + .takes_value(true) + .index(1) + .required(true) + .help("The token account address of the sender"), + ) + .arg( + Arg::with_name("token") + .validator(is_pubkey_or_keypair) + .value_name("TOKEN_ADDRESS") + .takes_value(true) + .index(2) + .required(true) + .help("The mint address"), + ) + .arg( + Arg::with_name("amount") + .validator(is_amount) + .value_name("AMOUNT") + .takes_value(true) + .index(3) + .required(true) + .help("Amount to transfer out"), + ) + .arg( + Arg::with_name("chain") + .validator(is_u8) + .value_name("CHAIN") + .takes_value(true) + .index(4) + .required(true) + .help("Chain to transfer to"), + ) + .arg( + Arg::with_name("nonce") + .validator(is_u8) + .value_name("NONCE") + .takes_value(true) + .index(5) + .required(true) + .help("Nonce of the transfer"), + ), + ) .subcommand( SubCommand::with_name("burn") .about("Burn tokens from an account") @@ -697,9 +884,16 @@ fn main() { solana_logger::setup_with_default("solana=info"); let _ = match matches.subcommand() { - ("airdrop", Some(_arg_matches)) => command_request_airdrop(&config), ("create-token", Some(_arg_matches)) => command_create_token(&config), ("create-bridge", Some(_arg_matches)) => command_deploy_bridge(&config), + ("lock", Some(arg_matches)) => { + let account = pubkey_of(arg_matches, "sender").unwrap(); + let amount = value_t_or_exit!(arg_matches, "amount", u64); + let nonce = value_t_or_exit!(arg_matches, "nonce", u32); + let chain = value_t_or_exit!(arg_matches, "chain", u8); + let token = pubkey_of(arg_matches, "token").unwrap(); + command_lock_tokens(&config, account, token, amount, chain, [0; 32], nonce) + } ("create-account", Some(arg_matches)) => { let token = pubkey_of(arg_matches, "token").unwrap(); command_create_account(&config, token) @@ -715,6 +909,12 @@ fn main() { let recipient = pubkey_of(arg_matches, "recipient").unwrap(); command_transfer(&config, sender, amount, recipient) } + ("approve", Some(arg_matches)) => { + let sender = pubkey_of(arg_matches, "sender").unwrap(); + let amount = value_t_or_exit!(arg_matches, "amount", f64); + let recipient = pubkey_of(arg_matches, "recipient").unwrap(); + command_approve(&config, sender, amount, recipient) + } ("burn", Some(arg_matches)) => { let source = pubkey_of(arg_matches, "source").unwrap(); let amount = value_t_or_exit!(arg_matches, "amount", f64); @@ -765,3 +965,17 @@ fn main() { exit(1); }); } + +pub fn is_u8(amount: T) -> Result<(), String> +where + T: AsRef + Display, +{ + if amount.as_ref().parse::().is_ok() { + Ok(()) + } else { + Err(format!( + "Unable to parse input amount as integer, provided: {}", + amount + )) + } +}