diff --git a/docs/solana_program.md b/docs/solana_program.md index ddf8da672..4fd33fc3e 100644 --- a/docs/solana_program.md +++ b/docs/solana_program.md @@ -26,13 +26,14 @@ Parameters: | Index | Name | Type | signer | writeable | empty | derived | | ----- | -------- | ------------------- | ------ | --------- | ----- | ------- | -| 0 | sys | SystemProgram | | | ️ | | -| 1 | token_program | SplToken | | | ️ | | -| 2 | token_account | TokenAccount | | ✅ | | | -| 3 | bridge | BridgeConfig | | | | | -| 4 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | -| 5 | token | WrappedAsset | | ✅ | | ✅ | -| 6 | payer | Account | ✅ | | | | +| 0 | bridge_p | BridgeProgram | | | ️ | | +| 1 | sys | SystemProgram | | | ️ | | +| 2 | token_program | SplToken | | | ️ | | +| 3 | token_account | TokenAccount | | ✅ | | | +| 4 | bridge | BridgeConfig | | | | | +| 5 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | +| 6 | token | WrappedAsset | | ✅ | | ✅ | +| 7 | payer | Account | ✅ | | | | #### TransferOutNative @@ -43,14 +44,15 @@ The transfer proposal will be tracked at a new account `proposal` where a VAA wi | Index | Name | Type | signer | writeable | empty | derived | | ----- | --------------- | ------------------- | ------ | --------- | ----- | ------- | -| 0 | sys | SystemProgram | | | ️ | | -| 1 | token_program | SplToken | | | ️ | | -| 2 | token_account | TokenAccount | | ✅ | | | -| 3 | bridge | BridgeConfig | | | | | -| 4 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | -| 5 | token | Mint | | ✅ | | | -| 6 | payer | Account | ✅ | | | | -| 7 | custody_account | TokenAccount | | ✅ | opt | ✅ | +| 0 | bridge_p | BridgeProgram | | | ️ | | +| 1 | sys | SystemProgram | | | ️ | | +| 2 | token_program | SplToken | | | ️ | | +| 3 | token_account | TokenAccount | | ✅ | | | +| 4 | bridge | BridgeConfig | | | | | +| 5 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | +| 6 | token | Mint | | ✅ | | | +| 7 | payer | Account | ✅ | | | | +| 8 | custody_account | TokenAccount | | ✅ | opt | ✅ | #### EvictTransferOut @@ -58,10 +60,11 @@ Deletes a `proposal` after the `VAA_EXPIRATION_TIME` to free up space on chain. | Index | Name | Type | signer | writeable | empty | derived | | ----- | -------- | ------------------- | ------ | --------- | ----- | ------- | -| 0 | guardian | Account | ✅ | | | | -| 1 | clock | Sysvar | | | ️ | ✅ | -| 2 | bridge | BridgeConfig | | | | | -| 3 | proposal | TransferOutProposal | | ✅ | | ✅ | +| 0 | bridge_p | BridgeProgram | | | ️ | | +| 1 | guardian | Account | ✅ | | | | +| 2 | clock | Sysvar | | | ️ | ✅ | +| 3 | bridge | BridgeConfig | | | | | +| 4 | proposal | TransferOutProposal | | ✅ | | ✅ | #### EvictClaimedVAA @@ -69,10 +72,11 @@ Deletes a `ClaimedVAA` after the `VAA_EXPIRATION_TIME` to free up space on chain | Index | Name | Type | signer | writeable | empty | derived | | ----- | -------- | ------------------- | ------ | --------- | ----- | ------- | -| 0 | guardian | Account | ✅ | | | | -| 1 | clock | Sysvar | | | ️ | ✅ | -| 2 | bridge | BridgeConfig | | | | | -| 3 | claim | ClaimedVAA | | ✅ | | ✅ | +| 0 | bridge_p | BridgeProgram | | | ️ | | +| 1 | guardian | Account | ✅ | | | | +| 2 | clock | Sysvar | | | ️ | ✅ | +| 3 | bridge | BridgeConfig | | | | | +| 4 | claim | ClaimedVAA | | ✅ | | ✅ | #### CreateWrappedAsset @@ -80,12 +84,13 @@ Creates a new `WrappedAsset` to be used to create accounts and later receive tra | Index | Name | Type | signer | writeable | empty | derived | | ----- | -------- | ------------------- | ------ | --------- | ----- | ------- | -| 0 | sys | SystemProgram | | | ️ | | -| 1 | token_program | SplToken | | | ️ | | -| 2 | bridge | BridgeConfig | | | | | -| 3 | payer | Account | ✅ | | ️ | | -| 4 | wrapped_mint | WrappedAsset | | | ✅ | ✅ | -| 5 | wrapped_meta_account | WrappedAssetMeta | | ✅ | ✅ | ✅ | +| 0 | bridge_p | BridgeProgram | | | ️ | | +| 1 | sys | SystemProgram | | | ️ | | +| 2 | token_program | SplToken | | | ️ | | +| 3 | bridge | BridgeConfig | | | | | +| 4 | payer | Account | ✅ | | ️ | | +| 5 | wrapped_mint | WrappedAsset | | | ✅ | ✅ | +| 6 | wrapped_meta_account | WrappedAssetMeta | | ✅ | ✅ | ✅ | #### SubmitVAA @@ -97,12 +102,13 @@ All require: | Index | Name | Type | signer | writeable | empty | derived | | ----- | ------------ | ------------ | ------ | --------- | ----- | ------- | -| 0 | sys | SystemProgram | | | ️ | | -| 1 | clock | Sysvar | | | ️ | ✅ | -| 2 | bridge | BridgeConfig | | | | | -| 3 | guardian_set | GuardianSet | | | | | -| 4 | claim | ExecutedVAA | | ✅ | ✅ | ✅ | -| 5 | payer | Account | ✅ | | | | +| 0 | bridge_p | BridgeProgram | | | ️ | | +| 1 | sys | SystemProgram | | | ️ | | +| 2 | clock | Sysvar | | | ️ | ✅ | +| 3 | bridge | BridgeConfig | | | | | +| 4 | guardian_set | GuardianSet | | | | | +| 5 | claim | ExecutedVAA | | ✅ | ✅ | ✅ | +| 6 | payer | Account | ✅ | | | | followed by: @@ -110,30 +116,30 @@ followed by: | Index | Name | Type | signer | writeable | empty | derived | | ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- | -| 6 | guardian_set_new | GuardianSet | | ✅ | ✅ | ✅ | +| 7 | guardian_set_new | GuardianSet | | ✅ | ✅ | ✅ | ##### Transfer: Ethereum (native) -> Solana (wrapped) | Index | Name | Type | signer | writeable | empty | derived | | ----- | ------------ | ------------ | ------ | --------- | ----- | ------- | -| 6 | token_program | SplToken | | | ️ | | -| 7 | token | WrappedAsset | | | | ✅ | -| 8 | destination | TokenAccount | | ✅ | | | +| 7 | token_program | SplToken | | | ️ | | +| 8 | token | WrappedAsset | | | | ✅ | +| 9 | destination | TokenAccount | | ✅ | | | ##### Transfer: Ethereum (wrapped) -> Solana (native) | Index | Name | Type | signer | writeable | empty | derived | | ----- | ------------ | ------------ | ------ | --------- | ----- | ------- | -| 6 | token_program | SplToken | | | ️ | | -| 7 | token | Mint | | | | ✅ | -| 8 | destination | TokenAccount | | ✅ | opt | | -| 9 | custody_src | TokenAccount | | ✅ | | ✅ | +| 7 | token_program | SplToken | | | ️ | | +| 8 | token | Mint | | | | ✅ | +| 9 | destination | TokenAccount | | ✅ | opt | | +| 10 | custody_src | TokenAccount | | ✅ | | ✅ | ##### Transfer: Solana (any) -> Ethereum (any) | Index | Name | Type | signer | writeable | empty | derived | | ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- | -| 6 | out_proposal | TransferOutProposal | | ✅ | | ✅ | +| 7 | out_proposal | TransferOutProposal | | ✅ | | ✅ | ## Accounts diff --git a/solana/Cargo.lock b/solana/Cargo.lock index 7fc6b5284..9f2ece65f 100644 --- a/solana/Cargo.lock +++ b/solana/Cargo.lock @@ -2579,19 +2579,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "spl-token" -version = "1.0.8" -dependencies = [ - "cbindgen", - "num-derive 0.3.1", - "num-traits", - "rand", - "remove_dir_all", - "solana-sdk", - "thiserror", -] - [[package]] name = "spl-token" version = "1.0.8" diff --git a/solana/bridge/src/instruction.rs b/solana/bridge/src/instruction.rs index a12e2f72c..160077274 100644 --- a/solana/bridge/src/instruction.rs +++ b/solana/bridge/src/instruction.rs @@ -21,14 +21,11 @@ use crate::vaa::{VAABody, VAA}; /// chain id of this chain pub const CHAIN_ID_SOLANA: u8 = 1; -/// size of a VAA in bytes -const VAA_SIZE: usize = 200; - /// size of a foreign address in bytes const FOREIGN_ADDRESS_SIZE: usize = 32; /// length-prefixed serialized validator payment approval data -pub type VAAData = [u8; VAA_SIZE]; +pub type VAAData = Vec; /// X and Y point of P for guardians pub type GuardianKey = [u8; 64]; /// address on a foreign chain @@ -129,9 +126,8 @@ impl BridgeInstruction { TransferOut(*payload) } 2 => { - let payload: &VAAData = unpack(input)?; - - PostVAA(*payload) + let payload: VAAData = input[1..].to_vec(); + PostVAA(payload) } 5 => { let payload: &AssetMeta = unpack(input)?; @@ -307,22 +303,10 @@ pub fn transfer_out( pub fn post_vaa( program_id: &Pubkey, payer: &Pubkey, - v: &[u8], + v: VAAData, ) -> Result { - // VAA must be <= VAA_SIZE-1 to allow for the length prefix - if v.len() > VAA_SIZE - 1 { - return Err(VAATooLong.into()); - } - // Convert data to length-prefixed on-chain format - let mut vaa_data: Vec = vec![]; - vaa_data.push(v.len() as u8); - vaa_data.append(&mut v.to_vec()); - vaa_data.resize(200, 0); - - let mut vaa_chain: [u8; 200] = [0; 200]; - vaa_chain.copy_from_slice(vaa_data.as_slice()); - - let data = BridgeInstruction::PostVAA(vaa_chain).serialize()?; + let mut data = v.clone(); + data.insert(0, 2); // Parse VAA let vaa = VAA::deserialize(&v[..])?; @@ -350,6 +334,7 @@ pub fn post_vaa( } VAABody::Transfer(t) => { if t.source_chain == CHAIN_ID_SOLANA { + println!("kot"); // Solana (any) -> Ethereum (any) let transfer_key = Bridge::derive_transfer_id( program_id, @@ -363,6 +348,7 @@ pub fn post_vaa( )?; accounts.push(AccountMeta::new(transfer_key, false)) } else if t.asset.chain == CHAIN_ID_SOLANA { + println!("kat"); // Foreign (wrapped) -> Solana (native) let mint_key = Pubkey::new(&t.asset.address); let custody_key = Bridge::derive_custody_id(program_id, &bridge_key, &mint_key)?; @@ -371,6 +357,7 @@ pub fn post_vaa( accounts.push(AccountMeta::new(Pubkey::new(&t.target_address), false)); accounts.push(AccountMeta::new(custody_key, false)); } else { + println!("kit"); // Foreign (native) -> Solana (wrapped) let wrapped_key = Bridge::derive_wrapped_asset_id( program_id, diff --git a/solana/bridge/src/processor.rs b/solana/bridge/src/processor.rs index aade2aa31..fec30916e 100644 --- a/solana/bridge/src/processor.rs +++ b/solana/bridge/src/processor.rs @@ -51,9 +51,7 @@ impl Bridge { } PostVAA(vaa_body) => { info!("Instruction: PostVAA"); - let len = vaa_body[0] as usize; - let vaa_data = &vaa_body[..len]; - let vaa = VAA::deserialize(vaa_data)?; + let vaa = VAA::deserialize(&vaa_body)?; let hash = vaa.body_hash()?; @@ -147,7 +145,6 @@ impl Bridge { let transfer_info = next_account_info(account_info_iter)?; let mint_info = next_account_info(account_info_iter)?; let payer_info = next_account_info(account_info_iter)?; - let authority_info = next_account_info(account_info_iter)?; let sender = Bridge::token_account_deserialize(sender_account_info)?; let bridge = Bridge::bridge_deserialize(bridge_info)?; @@ -175,10 +172,10 @@ impl Bridge { t.asset.address, t.chain_id, t.target, - sender.owner.to_bytes(), + sender_account_info.key.to_bytes(), t.nonce, ); - Bridge::check_and_create_account::( + Bridge::check_and_create_account::<[u8; TRANSFER_OUT_PROPOSAL_SIZE]>( program_id, accounts, transfer_info.key, @@ -188,11 +185,7 @@ impl Bridge { )?; // Load transfer account - let mut transfer_data = transfer_info.data.borrow_mut(); - let transfer: &mut TransferOutProposal = Bridge::unpack_unchecked(&mut transfer_data)?; - if transfer.is_initialized { - return Err(Error::AlreadyExists.into()); - } + let mut transfer: TransferOutProposal = TransferOutProposal::default(); info!("burning"); // Burn tokens @@ -200,7 +193,6 @@ impl Bridge { program_id, accounts, &bridge.config.token_program, - authority_info.key, sender_account_info.key, t.amount, )?; @@ -213,6 +205,11 @@ impl Bridge { transfer.amount = t.amount; transfer.to_chain_id = t.chain_id; transfer.asset = t.asset; + let mut transfer_data = Self::transfer_out_proposal_serialize(&transfer)?; + transfer_info + .data + .borrow_mut() + .swap_with_slice(transfer_data.as_mut_slice()); Ok(()) } @@ -307,7 +304,7 @@ impl Bridge { sender_account_info.key.to_bytes(), t.nonce, ); - Bridge::check_and_create_account::( + Bridge::check_and_create_account::<[u8; TRANSFER_OUT_PROPOSAL_SIZE]>( program_id, accounts, transfer_info.key, @@ -317,11 +314,7 @@ impl Bridge { )?; // Load transfer account - let mut transfer_data = transfer_info.data.borrow_mut(); - let transfer: &mut TransferOutProposal = Bridge::unpack_unchecked(&mut transfer_data)?; - if transfer.is_initialized { - return Err(Error::AlreadyExists.into()); - } + let mut transfer = TransferOutProposal::default(); // Check that custody account was derived correctly let expected_custody_id = @@ -376,6 +369,12 @@ impl Bridge { address: mint_info.key.to_bytes(), }; + let mut transfer_data = Self::transfer_out_proposal_serialize(&transfer)?; + transfer_info + .data + .borrow_mut() + .swap_with_slice(transfer_data.as_mut_slice()); + Ok(()) } @@ -585,7 +584,6 @@ impl Bridge { program_id, accounts, &bridge.config.token_program, - bridge_info.key, custody_info.key, destination_info.key, b.amount, @@ -610,7 +608,6 @@ impl Bridge { &bridge.config.token_program, mint_info.key, destination_info.key, - bridge_info.key, b.amount, )?; } @@ -653,6 +650,12 @@ impl Bridge { proposal.vaa = vaa_data; proposal.vaa_time = vaa.timestamp; + let mut transfer_data = Self::transfer_out_proposal_serialize(&proposal)?; + proposal_info + .data + .borrow_mut() + .swap_with_slice(transfer_data.as_mut_slice()); + Ok(()) } } @@ -676,14 +679,7 @@ pub fn invoke_signed<'a>( for account_info in account_infos.iter() { if meta.pubkey == *account_info.key { let mut new_account_info = account_info.clone(); - for seeds in signers_seeds.iter() { - let signer = - solana_sdk::program::create_program_address(seeds, &WORMHOLE_PROGRAM_ID) - .unwrap(); - if *account_info.key == signer { - new_account_info.is_signer = true; - } - } + for seeds in signers_seeds.iter() {} new_account_infos.push(new_account_info); } } @@ -706,19 +702,14 @@ impl Bridge { program_id: &Pubkey, accounts: &[AccountInfo], token_program_id: &Pubkey, - authority: &Pubkey, token_account: &Pubkey, amount: U256, ) -> Result<(), ProgramError> { - let all_signers: Vec<&Pubkey> = accounts - .iter() - .filter_map(|item| if item.is_signer { Some(item.key) } else { None }) - .collect(); let ix = spl_token::instruction::burn( token_program_id, token_account, - authority, - all_signers.as_slice(), + &Self::derive_bridge_id(program_id)?, + &[], amount.as_u64(), )?; Self::invoke_as_bridge(program_id, &ix, accounts) @@ -731,14 +722,13 @@ impl Bridge { token_program_id: &Pubkey, mint: &Pubkey, destination: &Pubkey, - bridge: &Pubkey, amount: U256, ) -> Result<(), ProgramError> { let ix = spl_token::instruction::mint_to( token_program_id, mint, destination, - bridge, + &Self::derive_bridge_id(program_id)?, &[], amount.as_u64(), )?; @@ -771,7 +761,6 @@ impl Bridge { program_id: &Pubkey, accounts: &[AccountInfo], token_program_id: &Pubkey, - bridge: &Pubkey, source: &Pubkey, destination: &Pubkey, amount: U256, @@ -780,7 +769,7 @@ impl Bridge { token_program_id, source, destination, - bridge, + &Self::derive_bridge_id(program_id)?, &[], amount.as_u64(), )?; diff --git a/solana/bridge/src/state.rs b/solana/bridge/src/state.rs index d1f1558bc..910ebcaa3 100644 --- a/solana/bridge/src/state.rs +++ b/solana/bridge/src/state.rs @@ -1,7 +1,10 @@ //! Bridge transition types +use std::io::{Cursor, Read, Write}; use std::mem::size_of; +use std::ops::Deref; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use primitive_types::U256; use solana_sdk::hash::Hasher; use solana_sdk::pubkey::{PubkeyError, MAX_SEED_LEN}; @@ -46,8 +49,7 @@ impl IsInitialized for GuardianSet { } /// proposal to transfer tokens to a foreign chain -#[repr(C)] -#[derive(Clone, Copy)] +#[derive(Default)] pub struct TransferOutProposal { /// amount to transfer pub amount: U256, @@ -62,7 +64,7 @@ pub struct TransferOutProposal { /// nonce of the transfer pub nonce: u32, /// vaa to unlock the tokens on the foreign chain - pub vaa: VAAData, + pub vaa: Vec, /// time the vaa was submitted pub vaa_time: u32, @@ -76,6 +78,7 @@ impl IsInitialized for TransferOutProposal { } } +pub const TRANSFER_OUT_PROPOSAL_SIZE: usize = 1139; impl TransferOutProposal { pub fn matches_vaa(&self, b: &BodyTransfer) -> bool { return b.amount == self.amount @@ -203,8 +206,55 @@ impl Bridge { pub fn transfer_out_proposal_deserialize( info: &AccountInfo, ) -> Result { - Ok(*Bridge::unpack(&mut info.data.borrow_mut()) - .map_err(|_| Error::ExpectedTransferOutProposal)?) + let raw_data = info.data.borrow(); + let mut rdr = Cursor::new(raw_data.as_ref()); + let mut proposal = TransferOutProposal::default(); + proposal.is_initialized = rdr.read_u8()? == 1; + + let mut amount_data = [0u8; 32]; + rdr.read_exact(&mut amount_data)?; + let amount = U256::from_big_endian(&amount_data[..]); + proposal.amount = amount; + + proposal.nonce = rdr.read_u32::()?; + proposal.vaa_time = rdr.read_u32::()?; + proposal.to_chain_id = rdr.read_u8()?; + rdr.read_exact(&mut proposal.source_address)?; + rdr.read_exact(&mut proposal.foreign_address)?; + proposal.asset.chain = rdr.read_u8()?; + rdr.read_exact(&mut proposal.asset.address)?; + + rdr.read_to_end(&mut proposal.vaa)?; + + return Ok(proposal); + } + + /// Deserializes a `TransferOutProposal`. + pub fn transfer_out_proposal_serialize(data: &TransferOutProposal) -> Result, Error> { + let mut out_data = Vec::new(); + out_data.write_u8({ + if data.is_initialized { + 1 + } else { + 0 + } + })?; + let mut amount_data = [0u8; 32]; + data.amount.to_big_endian(&mut amount_data); + out_data.write(&amount_data)?; + + out_data.write_u32::(data.nonce)?; + out_data.write_u32::(data.vaa_time)?; + out_data.write_u8(data.to_chain_id)?; + out_data.write(&data.source_address)?; + out_data.write(&data.foreign_address)?; + out_data.write_u8(data.asset.chain)?; + out_data.write(&data.asset.address)?; + let mut vaa_res = data.vaa.clone(); + vaa_res.resize(1000, 0u8); + out_data.write(&vaa_res)?; + + return Ok(out_data); } /// Unpacks a state from a bytes buffer while assuring that the state is initialized. diff --git a/solana/cli/src/main.rs b/solana/cli/src/main.rs index 60767d97f..d71b68e15 100644 --- a/solana/cli/src/main.rs +++ b/solana/cli/src/main.rs @@ -46,7 +46,11 @@ struct Config { type Error = Box; type CommmandResult = Result, Error>; -fn command_deploy_bridge(config: &Config, bridge: &Pubkey) -> CommmandResult { +fn command_deploy_bridge( + config: &Config, + bridge: &Pubkey, + initial_guardian: Vec<[u8; 20]>, +) -> CommmandResult { println!("Deploying bridge program"); let minimum_balance_for_rent_exemption = config @@ -56,7 +60,7 @@ fn command_deploy_bridge(config: &Config, bridge: &Pubkey) -> CommmandResult { let ix = initialize( bridge, &config.owner.pubkey(), - vec![[0u8; 20]], + initial_guardian, &BridgeConfig { vaa_expiration_time: 200000000, token_program: spl_token::id(), @@ -104,7 +108,7 @@ fn command_lock_tokens( chain: wrapped_meta.chain, } } - Err(_) => AssetMeta { + Err(e) => AssetMeta { address: token.to_bytes(), chain: CHAIN_ID_SOLANA, }, @@ -152,6 +156,30 @@ fn command_lock_tokens( Ok(Some(transaction)) } +fn command_create_wrapped_asset( + config: &Config, + bridge: &Pubkey, + meta: AssetMeta, +) -> CommmandResult { + println!("Creating wrapped asset"); + + let minimum_balance_for_rent_exemption = config + .rpc_client + .get_minimum_balance_for_rent_exemption(size_of::())?; + + let ix = create_wrapped(bridge, &config.owner.pubkey(), meta)?; + + let mut transaction = Transaction::new_with_payer(&[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, bridge: &Pubkey, vaa: &[u8]) -> CommmandResult { println!("Submitting VAA"); @@ -159,7 +187,7 @@ fn command_submit_vaa(config: &Config, bridge: &Pubkey, vaa: &[u8]) -> CommmandR .rpc_client .get_minimum_balance_for_rent_exemption(size_of::())?; - let ix = post_vaa(bridge, &config.owner.pubkey(), vaa)?; + let ix = post_vaa(bridge, &config.owner.pubkey(), vaa.to_vec())?; let mut transaction = Transaction::new_with_payer(&[ix], Some(&config.fee_payer.pubkey())); @@ -828,6 +856,16 @@ fn main() { .help( "Specify the bridge program public key" ), + ) + .arg( + Arg::with_name("guardian") + .validator(is_hex) + .value_name("GUADIAN_ADDRESS") + .takes_value(true) + .index(2) + .required(true) + .help("Address of the initial guardian"), + )) .subcommand( SubCommand::with_name("lock") @@ -915,6 +953,74 @@ fn main() { .help("The vaa to be posted"), ) ) + .subcommand( + SubCommand::with_name("create-wrapped") + .about("Create new wrapped asset and token account") + .arg( + Arg::with_name("bridge") + .long("bridge") + .value_name("BRIDGE_KEY") + .validator(is_pubkey_or_keypair) + .takes_value(true) + .index(1) + .required(true) + .help( + "Specify the bridge program public key" + ), + ) + .arg( + Arg::with_name("chain") + .validator(is_u8) + .value_name("CHAIN") + .takes_value(true) + .index(2) + .required(true) + .help("Chain ID of the asset"), + ) + .arg( + Arg::with_name("token") + .validator(is_hex) + .value_name("TOKEN_ADDRESS") + .takes_value(true) + .index(3) + .required(true) + .help("Token address of the asset"), + ) + ) + .subcommand( + SubCommand::with_name("wrapped-address") + .about("Derive wrapped asset address") + .arg( + Arg::with_name("bridge") + .long("bridge") + .value_name("BRIDGE_KEY") + .validator(is_pubkey_or_keypair) + .takes_value(true) + .index(1) + .required(true) + .help( + "Specify the bridge program public key" + ), + ) + .arg( + Arg::with_name("chain") + .validator(is_u8) + .value_name("CHAIN") + .takes_value(true) + .index(2) + .required(true) + .help("Chain ID of the asset"), + ) + .arg( + Arg::with_name("token") + .validator(is_hex) + .value_name("TOKEN_ADDRESS") + .takes_value(true) + .index(3) + .required(true) + .help("Token address of the asset"), + ) + ) .get_matches(); let config = { @@ -999,7 +1105,12 @@ fn main() { } ("create-bridge", Some(arg_matches)) => { let bridge = pubkey_of(arg_matches, "bridge").unwrap(); - command_deploy_bridge(&config, &bridge) + let initial_guardian: String = value_of(arg_matches, "guardian").unwrap(); + let initial_data = hex::decode(initial_guardian).unwrap(); + + let mut guardian = [0u8; 20]; + guardian.copy_from_slice(&initial_data); + command_deploy_bridge(&config, &bridge, vec![guardian]) } ("lock", Some(arg_matches)) => { let bridge = pubkey_of(arg_matches, "bridge").unwrap(); @@ -1018,6 +1129,39 @@ fn main() { let vaa = hex::decode(vaa_string).unwrap(); command_submit_vaa(&config, &bridge, vaa.as_slice()) } + ("create-wrapped", Some(arg_matches)) => { + let bridge = pubkey_of(arg_matches, "bridge").unwrap(); + let chain = value_t_or_exit!(arg_matches, "chain", u8); + let addr_string: String = value_of(arg_matches, "token").unwrap(); + let addr_data = hex::decode(addr_string).unwrap(); + + let mut token_addr = [0u8; 32]; + token_addr.copy_from_slice(addr_data.as_slice()); + + command_create_wrapped_asset( + &config, + &bridge, + AssetMeta { + chain, + address: token_addr, + }, + ) + } + ("wrapped-address", Some(arg_matches)) => { + let bridge = pubkey_of(arg_matches, "bridge").unwrap(); + let chain = value_t_or_exit!(arg_matches, "chain", u8); + let addr_string: String = value_of(arg_matches, "token").unwrap(); + let addr_data = hex::decode(addr_string).unwrap(); + + let mut token_addr = [0u8; 32]; + token_addr.copy_from_slice(addr_data.as_slice()); + + let bridge_key = Bridge::derive_bridge_id(&bridge).unwrap(); + let wrapped_key = + Bridge::derive_wrapped_asset_id(&bridge, &bridge_key, chain, token_addr).unwrap(); + println!("Wrapped address: {}", wrapped_key); + return; + } _ => unreachable!(), } .and_then(|transaction| {