From a8dff43090ea04a5cd97bc182ff47472d5df31bd Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:13:48 +0400 Subject: [PATCH 01/14] Implement simple fast transfer --- near/omni-bridge/src/lib.rs | 154 ++++++++++++++++++++++------- near/omni-types/src/lib.rs | 16 +++ near/omni-types/src/locker_args.rs | 8 +- 3 files changed, 140 insertions(+), 38 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index 709e56b6..dd3d93ae 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -17,15 +17,14 @@ use near_sdk::{ PanicOnDefault, Promise, PromiseError, PromiseOrValue, PromiseResult, }; use omni_types::locker_args::{ - BindTokenArgs, ClaimFeeArgs, DeployTokenArgs, FinTransferArgs, StorageDepositAction, + BindTokenArgs, ClaimFeeArgs, DeployTokenArgs, FastTransferArgs, FinTransferArgs, StorageDepositAction }; use omni_types::mpc_types::SignatureResponse; use omni_types::near_events::OmniBridgeEvent; use omni_types::prover_args::VerifyProofArgs; use omni_types::prover_result::ProverResult; use omni_types::{ - BasicMetadata, ChainKind, Fee, InitTransferMsg, MetadataPayload, Nonce, OmniAddress, - PayloadType, SignRequest, TransferId, TransferMessage, TransferMessagePayload, UpdateFee, + BasicMetadata, ChainKind, FastTransfer, Fee, InitTransferMsg, MetadataPayload, Nonce, OmniAddress, PayloadType, SignRequest, TransferId, TransferMessage, TransferMessagePayload, UpdateFee }; use storage::{TransferMessageStorage, TransferMessageStorageValue}; @@ -70,6 +69,7 @@ enum StorageKey { TokenDeployerAccounts, DeployedTokens, DestinationNonces, + FastTransfers, } #[derive(AccessControlRole, Deserialize, Serialize, Copy, Clone)] @@ -162,6 +162,7 @@ pub struct Contract { pub factories: LookupMap, pub pending_transfers: LookupMap, pub finalised_transfers: LookupSet, + pub fast_transfers: LookupMap, pub token_id_to_address: LookupMap<(ChainKind, AccountId), OmniAddress>, pub token_address_to_id: LookupMap, pub deployed_tokens: LookupSet, @@ -246,6 +247,7 @@ impl Contract { factories: LookupMap::new(StorageKey::Factories), pending_transfers: LookupMap::new(StorageKey::PendingTransfers), finalised_transfers: LookupSet::new(StorageKey::FinalisedTransfers), + fast_transfers: LookupMap::new(StorageKey::FastTransfers), token_id_to_address: LookupMap::new(StorageKey::TokenIdToAddress), token_address_to_id: LookupMap::new(StorageKey::TokenAddressToId), deployed_tokens: LookupSet::new(StorageKey::DeployedTokens), @@ -448,12 +450,71 @@ impl Contract { } #[payable] + pub fn fast_transfer(&mut self, #[serializer(borsh)] args: FastTransferArgs) -> Promise { + let mut attached_deposit = env::attached_deposit(); + + Self::check_or_pay_ft_storage(&args.storage_deposit_action, &mut attached_deposit) + .then( + Self::ext(env::current_account_id()) + .with_attached_deposit(attached_deposit) + .with_static_gas(VERIFY_PROOF_CALLBACK_GAS) + .fast_transfer_callback(args, env::predecessor_account_id()) + ) + } + + pub fn fast_transfer_callback( + &mut self, + #[serializer(borsh)] args: FastTransferArgs, + #[serializer(borsh)] relayer_id: AccountId, + ) -> Promise { + require!( + Self::check_storage_balance_result(1) + && args.storage_deposit_action.account_id == args.fast_transfer.recipient + && args.storage_deposit_action.token_id == args.fast_transfer.token, + "STORAGE_ERR: The transfer recipient is omitted" + ); + + let mut required_balance = self.add_fast_transfer(&args.fast_transfer, &relayer_id); + + let promise = if args.fast_transfer.token == self.wnear_account_id && args.fast_transfer.msg.is_empty() { + Promise::new(args.fast_transfer.recipient) + .transfer(NearToken::from_yoctonear(args.fast_transfer.amount.0)) + } else if args.fast_transfer.msg.is_empty() { + required_balance = required_balance.saturating_add(ONE_YOCTO); + + ext_token::ext(args.fast_transfer.token) + .with_static_gas(FT_TRANSFER_GAS) + .with_attached_deposit(ONE_YOCTO) + .ft_transfer(args.fast_transfer.recipient, args.fast_transfer.amount, None) + } else { + required_balance = required_balance.saturating_add(ONE_YOCTO); + + ext_token::ext(args.fast_transfer.token) + .with_static_gas(FT_TRANSFER_CALL_GAS) + .with_attached_deposit(ONE_YOCTO) + .ft_transfer_call( + args.fast_transfer.recipient, + args.fast_transfer.amount, + None, + args.fast_transfer.msg.clone(), + ) + }; + + self.update_storage_balance( + relayer_id, + required_balance, + env::attached_deposit(), + ); + + promise + } + pub fn fin_transfer(&mut self, #[serializer(borsh)] args: FinTransferArgs) -> Promise { require!( args.storage_deposit_actions.len() <= 3, "Invalid len of accounts for storage deposit" ); - let main_promise = ext_prover::ext(self.prover_account.clone()) + let mut main_promise = ext_prover::ext(self.prover_account.clone()) .with_static_gas(VERIFY_PROOF_GAS) .with_attached_deposit(NO_DEPOSIT) .verify_proof(VerifyProofArgs { @@ -462,12 +523,12 @@ impl Contract { }); let mut attached_deposit = env::attached_deposit(); - Self::check_or_pay_ft_storage( - main_promise, - &args.storage_deposit_actions, - &mut attached_deposit, - ) - .then( + + for action in &args.storage_deposit_actions { + main_promise = main_promise.and(Self::check_or_pay_ft_storage(action, &mut attached_deposit)); + } + + main_promise.then( Self::ext(env::current_account_id()) .with_attached_deposit(attached_deposit) .with_static_gas(VERIFY_PROOF_CALLBACK_GAS) @@ -887,6 +948,25 @@ impl Contract { let token = self.get_token_id(&transfer_message.token); + // If fast transfer happened, change recipient to the relayer that executed fast transfer + let fast_transfer = FastTransfer { + transfer_id: transfer_message.get_transfer_id(), + token: token.clone(), + amount: transfer_message.amount, + recipient: recipient.clone(), + msg: transfer_message.msg.clone(), + }; + let (recipient, is_fast_transfer) = match self.fast_transfers.get(&fast_transfer) { + Some(relayer) => { + require!( + predecessor_account_id == *relayer, + "ERR_FAST_TRANSFER_PERFORMED_BY_ANOTHER_RELAYER" + ); + (relayer, true) + }, + None => (recipient, false), + }; + require!( Self::check_storage_balance_result(1) && storage_deposit_actions[0].account_id == recipient @@ -917,7 +997,7 @@ impl Contract { amount_to_transfer, (!transfer_message.msg.is_empty()).then(|| transfer_message.msg.clone()), ) - } else if transfer_message.msg.is_empty() { + } else if transfer_message.msg.is_empty() || is_fast_transfer { transfer_promise .with_static_gas(FT_TRANSFER_GAS) .ft_transfer(recipient, amount_to_transfer, None) @@ -1032,34 +1112,24 @@ impl Contract { self.get_token_id(&native_token_address) } - fn check_or_pay_ft_storage( - mut main_promise: Promise, - storage_deposit_actions: &Vec, - attached_deposit: &mut NearToken, - ) -> Promise { - for action in storage_deposit_actions { - let promise = if let Some(storage_deposit_amount) = action.storage_deposit_amount { - let storage_deposit_amount = NearToken::from_yoctonear(storage_deposit_amount); - - *attached_deposit = attached_deposit - .checked_sub(storage_deposit_amount) - .sdk_expect("The attached deposit is less than required"); - - ext_token::ext(action.token_id.clone()) - .with_static_gas(STORAGE_DEPOSIT_GAS) - .with_attached_deposit(storage_deposit_amount) - .storage_deposit(&action.account_id, Some(true)) - } else { - ext_token::ext(action.token_id.clone()) - .with_static_gas(STORAGE_BALANCE_OF_GAS) - .with_attached_deposit(NO_DEPOSIT) - .storage_balance_of(&action.account_id) - }; + fn check_or_pay_ft_storage(action: &StorageDepositAction, attached_deposit: &mut NearToken) -> Promise { + if let Some(storage_deposit_amount) = action.storage_deposit_amount { + let storage_deposit_amount = NearToken::from_yoctonear(storage_deposit_amount); - main_promise = main_promise.and(promise); - } + *attached_deposit = attached_deposit + .checked_sub(storage_deposit_amount) + .sdk_expect("The attached deposit is less than required"); - main_promise + ext_token::ext(action.token_id.clone()) + .with_static_gas(STORAGE_DEPOSIT_GAS) + .with_attached_deposit(storage_deposit_amount) + .storage_deposit(&action.account_id, Some(true)) + } else { + ext_token::ext(action.token_id.clone()) + .with_static_gas(STORAGE_BALANCE_OF_GAS) + .with_attached_deposit(NO_DEPOSIT) + .storage_balance_of(&action.account_id) + } } fn check_storage_balance_result(result_idx: u64) -> bool { @@ -1141,6 +1211,16 @@ impl Contract { .saturating_mul((env::storage_usage().saturating_sub(storage_usage)).into()) } + fn add_fast_transfer(&mut self, transfer_id: &FastTransfer, relayer_id: &AccountId) -> NearToken { + let storage_usage = env::storage_usage(); + require!( + self.fast_transfers.insert(transfer_id, relayer_id).is_none(), + "Fast transfer is already performed" + ); + env::storage_byte_cost() + .saturating_mul((env::storage_usage().saturating_sub(storage_usage)).into()) + } + fn update_storage_balance( &mut self, account_id: AccountId, diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index 2f0a3f9c..b26602f5 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -485,3 +485,19 @@ pub struct BasicMetadata { pub symbol: String, pub decimals: u8, } + +#[derive( + BorshDeserialize, + BorshSerialize, + Debug, + Clone, + PartialEq, + Eq, +)] +pub struct FastTransfer { + pub transfer_id: TransferId, + pub token: AccountId, + pub amount: U128, + pub recipient: AccountId, + pub msg: String, +} \ No newline at end of file diff --git a/near/omni-types/src/locker_args.rs b/near/omni-types/src/locker_args.rs index 7ea3d8c4..89626b2d 100644 --- a/near/omni-types/src/locker_args.rs +++ b/near/omni-types/src/locker_args.rs @@ -1,4 +1,4 @@ -use crate::ChainKind; +use crate::{ChainKind, FastTransfer}; use near_sdk::{ borsh::{self, BorshDeserialize, BorshSerialize}, AccountId, @@ -35,3 +35,9 @@ pub struct DeployTokenArgs { pub chain_kind: ChainKind, pub prover_args: Vec, } + +#[derive(BorshDeserialize, BorshSerialize, Clone)] +pub struct FastTransferArgs { + pub fast_transfer: FastTransfer, + pub storage_deposit_action: StorageDepositAction, +} From 938da6c80e7be6e11fb6bfe29462e6d3a6e27c76 Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:30:52 +0400 Subject: [PATCH 02/14] run fmt --- near/omni-bridge/src/lib.rs | 56 +++++++++++++++++++++++-------------- near/omni-types/src/lib.rs | 11 ++------ 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index dd3d93ae..fc98f88f 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -17,14 +17,17 @@ use near_sdk::{ PanicOnDefault, Promise, PromiseError, PromiseOrValue, PromiseResult, }; use omni_types::locker_args::{ - BindTokenArgs, ClaimFeeArgs, DeployTokenArgs, FastTransferArgs, FinTransferArgs, StorageDepositAction + BindTokenArgs, ClaimFeeArgs, DeployTokenArgs, FastTransferArgs, FinTransferArgs, + StorageDepositAction, }; use omni_types::mpc_types::SignatureResponse; use omni_types::near_events::OmniBridgeEvent; use omni_types::prover_args::VerifyProofArgs; use omni_types::prover_result::ProverResult; use omni_types::{ - BasicMetadata, ChainKind, FastTransfer, Fee, InitTransferMsg, MetadataPayload, Nonce, OmniAddress, PayloadType, SignRequest, TransferId, TransferMessage, TransferMessagePayload, UpdateFee + BasicMetadata, ChainKind, FastTransfer, Fee, InitTransferMsg, MetadataPayload, Nonce, + OmniAddress, PayloadType, SignRequest, TransferId, TransferMessage, TransferMessagePayload, + UpdateFee, }; use storage::{TransferMessageStorage, TransferMessageStorageValue}; @@ -453,13 +456,12 @@ impl Contract { pub fn fast_transfer(&mut self, #[serializer(borsh)] args: FastTransferArgs) -> Promise { let mut attached_deposit = env::attached_deposit(); - Self::check_or_pay_ft_storage(&args.storage_deposit_action, &mut attached_deposit) - .then( - Self::ext(env::current_account_id()) - .with_attached_deposit(attached_deposit) - .with_static_gas(VERIFY_PROOF_CALLBACK_GAS) - .fast_transfer_callback(args, env::predecessor_account_id()) - ) + Self::check_or_pay_ft_storage(&args.storage_deposit_action, &mut attached_deposit).then( + Self::ext(env::current_account_id()) + .with_attached_deposit(attached_deposit) + .with_static_gas(VERIFY_PROOF_CALLBACK_GAS) + .fast_transfer_callback(args, env::predecessor_account_id()), + ) } pub fn fast_transfer_callback( @@ -476,7 +478,9 @@ impl Contract { let mut required_balance = self.add_fast_transfer(&args.fast_transfer, &relayer_id); - let promise = if args.fast_transfer.token == self.wnear_account_id && args.fast_transfer.msg.is_empty() { + let promise = if args.fast_transfer.token == self.wnear_account_id + && args.fast_transfer.msg.is_empty() + { Promise::new(args.fast_transfer.recipient) .transfer(NearToken::from_yoctonear(args.fast_transfer.amount.0)) } else if args.fast_transfer.msg.is_empty() { @@ -485,7 +489,11 @@ impl Contract { ext_token::ext(args.fast_transfer.token) .with_static_gas(FT_TRANSFER_GAS) .with_attached_deposit(ONE_YOCTO) - .ft_transfer(args.fast_transfer.recipient, args.fast_transfer.amount, None) + .ft_transfer( + args.fast_transfer.recipient, + args.fast_transfer.amount, + None, + ) } else { required_balance = required_balance.saturating_add(ONE_YOCTO); @@ -500,11 +508,7 @@ impl Contract { ) }; - self.update_storage_balance( - relayer_id, - required_balance, - env::attached_deposit(), - ); + self.update_storage_balance(relayer_id, required_balance, env::attached_deposit()); promise } @@ -525,7 +529,8 @@ impl Contract { let mut attached_deposit = env::attached_deposit(); for action in &args.storage_deposit_actions { - main_promise = main_promise.and(Self::check_or_pay_ft_storage(action, &mut attached_deposit)); + main_promise = + main_promise.and(Self::check_or_pay_ft_storage(action, &mut attached_deposit)); } main_promise.then( @@ -963,7 +968,7 @@ impl Contract { "ERR_FAST_TRANSFER_PERFORMED_BY_ANOTHER_RELAYER" ); (relayer, true) - }, + } None => (recipient, false), }; @@ -1112,7 +1117,10 @@ impl Contract { self.get_token_id(&native_token_address) } - fn check_or_pay_ft_storage(action: &StorageDepositAction, attached_deposit: &mut NearToken) -> Promise { + fn check_or_pay_ft_storage( + action: &StorageDepositAction, + attached_deposit: &mut NearToken, + ) -> Promise { if let Some(storage_deposit_amount) = action.storage_deposit_amount { let storage_deposit_amount = NearToken::from_yoctonear(storage_deposit_amount); @@ -1211,10 +1219,16 @@ impl Contract { .saturating_mul((env::storage_usage().saturating_sub(storage_usage)).into()) } - fn add_fast_transfer(&mut self, transfer_id: &FastTransfer, relayer_id: &AccountId) -> NearToken { + fn add_fast_transfer( + &mut self, + transfer_id: &FastTransfer, + relayer_id: &AccountId, + ) -> NearToken { let storage_usage = env::storage_usage(); require!( - self.fast_transfers.insert(transfer_id, relayer_id).is_none(), + self.fast_transfers + .insert(transfer_id, relayer_id) + .is_none(), "Fast transfer is already performed" ); env::storage_byte_cost() diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index b26602f5..ebbe516a 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -486,18 +486,11 @@ pub struct BasicMetadata { pub decimals: u8, } -#[derive( - BorshDeserialize, - BorshSerialize, - Debug, - Clone, - PartialEq, - Eq, -)] +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, PartialEq, Eq)] pub struct FastTransfer { pub transfer_id: TransferId, pub token: AccountId, pub amount: U128, pub recipient: AccountId, pub msg: String, -} \ No newline at end of file +} From 527e1488377739472bd2fac217fc403af541eca9 Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:10:07 +0400 Subject: [PATCH 03/14] Fast transfers implementation --- near/omni-bridge/src/lib.rs | 399 ++++++++++++++++++----------- near/omni-types/src/lib.rs | 28 +- near/omni-types/src/locker_args.rs | 8 +- 3 files changed, 274 insertions(+), 161 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index fc98f88f..4d218386 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -17,17 +17,16 @@ use near_sdk::{ PanicOnDefault, Promise, PromiseError, PromiseOrValue, PromiseResult, }; use omni_types::locker_args::{ - BindTokenArgs, ClaimFeeArgs, DeployTokenArgs, FastTransferArgs, FinTransferArgs, - StorageDepositAction, + BindTokenArgs, ClaimFeeArgs, DeployTokenArgs, FinTransferArgs, StorageDepositAction, }; use omni_types::mpc_types::SignatureResponse; use omni_types::near_events::OmniBridgeEvent; use omni_types::prover_args::VerifyProofArgs; use omni_types::prover_result::ProverResult; use omni_types::{ - BasicMetadata, ChainKind, FastTransfer, Fee, InitTransferMsg, MetadataPayload, Nonce, - OmniAddress, PayloadType, SignRequest, TransferId, TransferMessage, TransferMessagePayload, - UpdateFee, + BasicMetadata, BridgeOnTransferMsg, ChainKind, FastFinTransferMsg, FastTransfer, Fee, + InitTransferMsg, MetadataPayload, Nonce, OmniAddress, PayloadType, SignRequest, TransferId, + TransferMessage, TransferMessagePayload, UpdateFee, }; use storage::{TransferMessageStorage, TransferMessageStorageValue}; @@ -165,7 +164,7 @@ pub struct Contract { pub factories: LookupMap, pub pending_transfers: LookupMap, pub finalised_transfers: LookupSet, - pub fast_transfers: LookupMap, + pub fast_transfers: LookupMap<[u8; 32], AccountId>, pub token_id_to_address: LookupMap<(ChainKind, AccountId), OmniAddress>, pub token_address_to_id: LookupMap, pub deployed_tokens: LookupSet, @@ -187,53 +186,25 @@ impl FungibleTokenReceiver for Contract { amount: U128, msg: String, ) -> PromiseOrValue { - let parsed_msg: InitTransferMsg = serde_json::from_str(&msg).sdk_expect("ERR_PARSE_MSG"); - let token_id = env::predecessor_account_id(); - require!( - parsed_msg.recipient.get_chain() != ChainKind::Near, - "ERR_INVALID_RECIPIENT_CHAIN" - ); - - self.current_origin_nonce += 1; - let destination_nonce = self.get_next_destination_nonce(parsed_msg.recipient.get_chain()); - - let transfer_message = TransferMessage { - origin_nonce: self.current_origin_nonce, - token: OmniAddress::Near(token_id.clone()), - amount, - recipient: parsed_msg.recipient, - fee: Fee { - fee: parsed_msg.fee, - native_fee: parsed_msg.native_token_fee, - }, - sender: OmniAddress::Near(sender_id.clone()), - msg: String::new(), - destination_nonce, + let parsed_msg: BridgeOnTransferMsg = + serde_json::from_str(&msg).sdk_expect("ERR_PARSE_MSG"); + let promise_or_value = match parsed_msg { + BridgeOnTransferMsg::InitTransfer(init_transfer_msg) => { + PromiseOrValue::Value(self.init_transfer(sender_id, amount, init_transfer_msg)) + } + BridgeOnTransferMsg::FastFinTransfer(fast_fin_transfer_msg) => { + self.fast_fin_transfer(sender_id, amount, fast_fin_transfer_msg) + } }; - require!( - transfer_message.fee.fee < transfer_message.amount, - "ERR_INVALID_FEE" - ); - - let mut required_storage_balance = - self.add_transfer_message(transfer_message.clone(), sender_id.clone()); - required_storage_balance = required_storage_balance - .saturating_add(NearToken::from_yoctonear(parsed_msg.native_token_fee.0)); - - self.update_storage_balance( - sender_id, - required_storage_balance, - NearToken::from_yoctonear(0), - ); + let token_id = env::predecessor_account_id(); if self.deployed_tokens.contains(&token_id) { - ext_token::ext(token_id.clone()) + ext_token::ext(token_id) .with_static_gas(BURN_TOKEN_GAS) .burn(amount); } - env::log_str(&OmniBridgeEvent::InitTransferEvent { transfer_message }.to_log_string()); - PromiseOrValue::Value(U128(0)) + promise_or_value } } @@ -430,6 +401,57 @@ impl Contract { ) } + fn init_transfer( + &mut self, + sender_id: AccountId, + amount: U128, + init_transfer_msg: InitTransferMsg, + ) -> U128 { + let token_id = env::predecessor_account_id(); + require!( + init_transfer_msg.recipient.get_chain() != ChainKind::Near, + "ERR_INVALID_RECIPIENT_CHAIN" + ); + + self.current_origin_nonce += 1; + let destination_nonce = + self.get_next_destination_nonce(init_transfer_msg.recipient.get_chain()); + + let transfer_message = TransferMessage { + origin_nonce: self.current_origin_nonce, + token: OmniAddress::Near(token_id.clone()), + amount, + recipient: init_transfer_msg.recipient, + fee: Fee { + fee: init_transfer_msg.fee, + native_fee: init_transfer_msg.native_token_fee, + }, + sender: OmniAddress::Near(sender_id.clone()), + msg: String::new(), + destination_nonce, + }; + require!( + transfer_message.fee.fee < transfer_message.amount, + "ERR_INVALID_FEE" + ); + + let mut required_storage_balance = + self.add_transfer_message(transfer_message.clone(), sender_id.clone()); + required_storage_balance = required_storage_balance.saturating_add( + NearToken::from_yoctonear(init_transfer_msg.native_token_fee.0), + ); + + self.update_storage_balance( + sender_id, + required_storage_balance, + NearToken::from_yoctonear(0), + ); + + env::log_str(&OmniBridgeEvent::InitTransferEvent { transfer_message }.to_log_string()); + + U128(0) + } + #[private] pub fn sign_transfer_callback( &mut self, @@ -452,67 +474,6 @@ impl Contract { } } - #[payable] - pub fn fast_transfer(&mut self, #[serializer(borsh)] args: FastTransferArgs) -> Promise { - let mut attached_deposit = env::attached_deposit(); - - Self::check_or_pay_ft_storage(&args.storage_deposit_action, &mut attached_deposit).then( - Self::ext(env::current_account_id()) - .with_attached_deposit(attached_deposit) - .with_static_gas(VERIFY_PROOF_CALLBACK_GAS) - .fast_transfer_callback(args, env::predecessor_account_id()), - ) - } - - pub fn fast_transfer_callback( - &mut self, - #[serializer(borsh)] args: FastTransferArgs, - #[serializer(borsh)] relayer_id: AccountId, - ) -> Promise { - require!( - Self::check_storage_balance_result(1) - && args.storage_deposit_action.account_id == args.fast_transfer.recipient - && args.storage_deposit_action.token_id == args.fast_transfer.token, - "STORAGE_ERR: The transfer recipient is omitted" - ); - - let mut required_balance = self.add_fast_transfer(&args.fast_transfer, &relayer_id); - - let promise = if args.fast_transfer.token == self.wnear_account_id - && args.fast_transfer.msg.is_empty() - { - Promise::new(args.fast_transfer.recipient) - .transfer(NearToken::from_yoctonear(args.fast_transfer.amount.0)) - } else if args.fast_transfer.msg.is_empty() { - required_balance = required_balance.saturating_add(ONE_YOCTO); - - ext_token::ext(args.fast_transfer.token) - .with_static_gas(FT_TRANSFER_GAS) - .with_attached_deposit(ONE_YOCTO) - .ft_transfer( - args.fast_transfer.recipient, - args.fast_transfer.amount, - None, - ) - } else { - required_balance = required_balance.saturating_add(ONE_YOCTO); - - ext_token::ext(args.fast_transfer.token) - .with_static_gas(FT_TRANSFER_CALL_GAS) - .with_attached_deposit(ONE_YOCTO) - .ft_transfer_call( - args.fast_transfer.recipient, - args.fast_transfer.amount, - None, - args.fast_transfer.msg.clone(), - ) - }; - - self.update_storage_balance(relayer_id, required_balance, env::attached_deposit()); - - promise - } - pub fn fin_transfer(&mut self, #[serializer(borsh)] args: FinTransferArgs) -> Promise { require!( args.storage_deposit_actions.len() <= 3, @@ -583,11 +544,111 @@ impl Contract { ) .into() } else { - self.process_fin_transfer_to_other_cahin(predecessor_account_id, transfer_message); + self.process_fin_transfer_to_other_chain(predecessor_account_id, transfer_message); PromiseOrValue::Value(destination_nonce) } } + fn fast_fin_transfer( + &mut self, + sender_id: AccountId, + amount: U128, + fast_fin_transfer_msg: FastFinTransferMsg, + ) -> PromiseOrValue { + let token_id = env::predecessor_account_id(); + let mut finaliser_balance = self + .storage_balance_of(&sender_id) + .unwrap_or_else(|| env::panic_str("ERR_EMPTY_STORAGE_BALANCE")) + .available; + + let fast_transfer = FastTransfer { + token_id: token_id.clone(), + recipient: fast_fin_transfer_msg.recipient.clone(), + amount, + fee: fast_fin_transfer_msg.fee, + transfer_id: fast_fin_transfer_msg.transfer_id, + msg: fast_fin_transfer_msg.msg, + }; + + match fast_fin_transfer_msg.recipient { + OmniAddress::Near(recipient) => { + let deposit_action = StorageDepositAction { + account_id: recipient, + token_id, + storage_deposit_amount: fast_fin_transfer_msg.storage_deposit_amount.clone(), + }; + + PromiseOrValue::Promise( + Self::check_or_pay_ft_storage(&deposit_action, &mut finaliser_balance).then( + Self::ext(env::current_account_id()) + .with_attached_deposit(finaliser_balance) + .with_static_gas(VERIFY_PROOF_CALLBACK_GAS) + .fast_fin_transfer_to_near_callback(fast_transfer, sender_id), + ), + ) + } + _ => { + self.fast_fin_transfer_to_other_chain(fast_transfer, sender_id); + PromiseOrValue::Value(U128(0)) + } + } + } + + #[private] + pub fn fast_fin_transfer_to_near_callback( + &mut self, + #[serializer(borsh)] fast_transfer: FastTransfer, + #[serializer(borsh)] relayer_id: AccountId, + ) -> Promise { + require!( + Self::check_storage_balance_result(1), + "STORAGE_ERR: The transfer recipient is omitted" + ); + + let OmniAddress::Near(recipient) = fast_transfer.recipient.clone() else { + env::panic_str("ERR_INVALID_STATE") + }; + + let required_balance = self + .add_fast_transfer(&fast_transfer, &relayer_id) + .saturating_add(ONE_YOCTO); + self.update_storage_balance(relayer_id, required_balance, env::attached_deposit()); + + self.send_tokens( + fast_transfer.token_id, + recipient, + fast_transfer.amount, + fast_transfer.msg, + ) + } + + fn fast_fin_transfer_to_other_chain( + &mut self, + fast_transfer: FastTransfer, + relayer_id: AccountId, + ) { + let mut required_balance = self.add_fast_transfer(&fast_transfer, &relayer_id); + + let destination_nonce = + self.get_next_destination_nonce(fast_transfer.recipient.get_chain()); + let transfer_message = TransferMessage { + origin_nonce: fast_transfer.transfer_id.origin_nonce, + token: OmniAddress::Near(fast_transfer.token_id.clone()), + amount: fast_transfer.amount, + recipient: fast_transfer.recipient, + fee: fast_transfer.fee, + sender: OmniAddress::Near(relayer_id.clone()), + msg: fast_transfer.msg, + destination_nonce, + }; + + required_balance = self + .add_transfer_message(transfer_message, relayer_id.clone()) + .saturating_add(required_balance); + + self.update_storage_balance(relayer_id, required_balance, env::attached_deposit()); + } + #[payable] pub fn claim_fee(&mut self, #[serializer(borsh)] args: ClaimFeeArgs) -> Promise { ext_prover::ext(self.prover_account.clone()) @@ -956,12 +1017,13 @@ impl Contract { // If fast transfer happened, change recipient to the relayer that executed fast transfer let fast_transfer = FastTransfer { transfer_id: transfer_message.get_transfer_id(), - token: token.clone(), - amount: transfer_message.amount, - recipient: recipient.clone(), + token_id: token.clone(), + amount: U128(transfer_message.amount.0 - transfer_message.fee.fee.0), + fee: transfer_message.fee.clone(), + recipient: OmniAddress::Near(recipient.clone()), msg: transfer_message.msg.clone(), }; - let (recipient, is_fast_transfer) = match self.fast_transfers.get(&fast_transfer) { + let (recipient, is_fast_transfer) = match self.fast_transfers.get(&fast_transfer.hash()) { Some(relayer) => { require!( predecessor_account_id == *relayer, @@ -982,41 +1044,16 @@ impl Contract { let amount_to_transfer = U128(transfer_message.amount.0 - transfer_message.fee.fee.0); let is_deployed_token = self.deployed_tokens.contains(&token); - let mut promise = if token == self.wnear_account_id && transfer_message.msg.is_empty() { - // Unwrap wNEAR and transfer NEAR tokens - ext_wnear_token::ext(self.wnear_account_id.clone()) - .with_static_gas(WNEAR_WITHDRAW_GAS) - .with_attached_deposit(ONE_YOCTO) - .near_withdraw(amount_to_transfer) - .then( - Promise::new(recipient) - .transfer(NearToken::from_yoctonear(amount_to_transfer.0)), - ) - } else { - let transfer_promise = ext_token::ext(token.clone()).with_attached_deposit(ONE_YOCTO); - if is_deployed_token { - transfer_promise - .with_static_gas(MINT_TOKEN_GAS.saturating_add(FT_TRANSFER_CALL_GAS)) - .mint( - recipient, - amount_to_transfer, - (!transfer_message.msg.is_empty()).then(|| transfer_message.msg.clone()), - ) - } else if transfer_message.msg.is_empty() || is_fast_transfer { - transfer_promise - .with_static_gas(FT_TRANSFER_GAS) - .ft_transfer(recipient, amount_to_transfer, None) + let mut promise = self.send_tokens( + token.clone(), + recipient, + amount_to_transfer, + if is_fast_transfer { + String::new() } else { - transfer_promise - .with_static_gas(FT_TRANSFER_CALL_GAS) - .ft_transfer_call( - recipient, - amount_to_transfer, - None, - transfer_message.msg.clone(), - ) - } - }; + transfer_message.msg.clone() + }, + ); if transfer_message.fee.fee.0 > 0 { require!( @@ -1080,16 +1117,42 @@ impl Contract { promise } - fn process_fin_transfer_to_other_cahin( + fn process_fin_transfer_to_other_chain( &mut self, predecessor_account_id: AccountId, transfer_message: TransferMessage, ) { let mut required_balance = self.add_fin_transfer(&transfer_message.get_transfer_id()); + let token = self.get_token_id(&transfer_message.token); - required_balance = self - .add_transfer_message(transfer_message.clone(), predecessor_account_id.clone()) - .saturating_add(required_balance); + let fast_transfer = FastTransfer { + transfer_id: transfer_message.get_transfer_id(), + token_id: token.clone(), + amount: U128(transfer_message.amount.0 - transfer_message.fee.fee.0), + fee: transfer_message.fee.clone(), + recipient: transfer_message.recipient.clone(), + msg: transfer_message.msg.clone(), + }; + + let recipient = match self.fast_transfers.get(&fast_transfer.hash()) { + Some(relayer) => { + require!( + predecessor_account_id == *relayer, + "ERR_FAST_TRANSFER_PERFORMED_BY_ANOTHER_RELAYER" + ); + Some(relayer) + } + None => None, + }; + + // If fast transfer happened, send tokens to the relayer that executed fast transfer + if let Some(relayer) = recipient { + self.send_tokens(token, relayer, transfer_message.amount, String::new()); + } else { + required_balance = self + .add_transfer_message(transfer_message.clone(), predecessor_account_id.clone()) + .saturating_add(required_balance); + } self.update_storage_balance( predecessor_account_id, @@ -1100,6 +1163,40 @@ impl Contract { env::log_str(&OmniBridgeEvent::FinTransferEvent { transfer_message }.to_log_string()); } + fn send_tokens( + &self, + token: AccountId, + recipient: AccountId, + amount: U128, + msg: String, + ) -> Promise { + let is_deployed_token = self.deployed_tokens.contains(&token); + + if token == self.wnear_account_id && msg.is_empty() { + // Unwrap wNEAR and transfer NEAR tokens + ext_wnear_token::ext(self.wnear_account_id.clone()) + .with_static_gas(WNEAR_WITHDRAW_GAS) + .with_attached_deposit(ONE_YOCTO) + .near_withdraw(amount) + .then(Promise::new(recipient).transfer(NearToken::from_yoctonear(amount.0))) + } else { + let transfer_promise = ext_token::ext(token.clone()).with_attached_deposit(ONE_YOCTO); + if is_deployed_token { + transfer_promise + .with_static_gas(MINT_TOKEN_GAS.saturating_add(FT_TRANSFER_CALL_GAS)) + .mint(recipient, amount, (!msg.is_empty()).then(|| msg.clone())) + } else if msg.is_empty() { + transfer_promise + .with_static_gas(FT_TRANSFER_GAS) + .ft_transfer(recipient, amount, None) + } else { + transfer_promise + .with_static_gas(FT_TRANSFER_CALL_GAS) + .ft_transfer_call(recipient, amount, None, msg.clone()) + } + } + } + fn get_token_id(&self, address: &OmniAddress) -> AccountId { if let OmniAddress::Near(token_account_id) = address { token_account_id.clone() @@ -1227,7 +1324,7 @@ impl Contract { let storage_usage = env::storage_usage(); require!( self.fast_transfers - .insert(transfer_id, relayer_id) + .insert(&transfer_id.hash(), relayer_id) .is_none(), "Fast transfer is already performed" ); diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index ebbe516a..555a8b74 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -357,6 +357,21 @@ impl<'de> Deserialize<'de> for OmniAddress { } } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum BridgeOnTransferMsg { + InitTransfer(InitTransferMsg), + FastFinTransfer(FastFinTransferMsg), +} + +#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize, Debug, Clone)] +pub struct FastFinTransferMsg { + pub transfer_id: TransferId, + pub recipient: OmniAddress, + pub fee: Fee, + pub msg: String, + pub storage_deposit_amount: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct InitTransferMsg { pub recipient: OmniAddress, @@ -486,11 +501,18 @@ pub struct BasicMetadata { pub decimals: u8, } -#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, PartialEq, Eq)] +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone)] pub struct FastTransfer { pub transfer_id: TransferId, - pub token: AccountId, + pub token_id: AccountId, pub amount: U128, - pub recipient: AccountId, + pub fee: Fee, + pub recipient: OmniAddress, pub msg: String, } + +impl FastTransfer { + pub fn hash(&self) -> [u8; 32] { + utils::keccak256(&borsh::to_vec(self).unwrap()) + } +} diff --git a/near/omni-types/src/locker_args.rs b/near/omni-types/src/locker_args.rs index 89626b2d..7ea3d8c4 100644 --- a/near/omni-types/src/locker_args.rs +++ b/near/omni-types/src/locker_args.rs @@ -1,4 +1,4 @@ -use crate::{ChainKind, FastTransfer}; +use crate::ChainKind; use near_sdk::{ borsh::{self, BorshDeserialize, BorshSerialize}, AccountId, @@ -35,9 +35,3 @@ pub struct DeployTokenArgs { pub chain_kind: ChainKind, pub prover_args: Vec, } - -#[derive(BorshDeserialize, BorshSerialize, Clone)] -pub struct FastTransferArgs { - pub fast_transfer: FastTransfer, - pub storage_deposit_action: StorageDepositAction, -} From c1cd7f36734cc266014f9c68f2a155e13ba99cf3 Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:32:24 +0400 Subject: [PATCH 04/14] Storage deposit and fee fixes --- near/omni-bridge/src/lib.rs | 62 ++++++++++++++++++------------------- near/omni-types/src/lib.rs | 13 ++++++++ 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index 4d218386..ff8c53a5 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -556,15 +556,10 @@ impl Contract { fast_fin_transfer_msg: FastFinTransferMsg, ) -> PromiseOrValue { let token_id = env::predecessor_account_id(); - let mut finaliser_balance = self - .storage_balance_of(&sender_id) - .unwrap_or_else(|| env::panic_str("ERR_EMPTY_STORAGE_BALANCE")) - .available; - let fast_transfer = FastTransfer { token_id: token_id.clone(), recipient: fast_fin_transfer_msg.recipient.clone(), - amount, + amount: U128(amount.0 + fast_fin_transfer_msg.fee.fee.0), fee: fast_fin_transfer_msg.fee, transfer_id: fast_fin_transfer_msg.transfer_id, msg: fast_fin_transfer_msg.msg, @@ -572,16 +567,29 @@ impl Contract { match fast_fin_transfer_msg.recipient { OmniAddress::Near(recipient) => { + let storage_deposit_amount = fast_fin_transfer_msg + .storage_deposit_amount + .unwrap_or_default(); + if storage_deposit_amount > 0 { + self.update_storage_balance( + sender_id.clone(), + NearToken::from_yoctonear(storage_deposit_amount), + NearToken::from_yoctonear(0), + ); + } + let deposit_action = StorageDepositAction { account_id: recipient, token_id, - storage_deposit_amount: fast_fin_transfer_msg.storage_deposit_amount.clone(), + storage_deposit_amount: fast_fin_transfer_msg.storage_deposit_amount, }; - PromiseOrValue::Promise( - Self::check_or_pay_ft_storage(&deposit_action, &mut finaliser_balance).then( + Self::check_or_pay_ft_storage( + &deposit_action, + &mut NearToken::from_yoctonear(storage_deposit_amount), + ) + .then( Self::ext(env::current_account_id()) - .with_attached_deposit(finaliser_balance) .with_static_gas(VERIFY_PROOF_CALLBACK_GAS) .fast_fin_transfer_to_near_callback(fast_transfer, sender_id), ), @@ -612,12 +620,12 @@ impl Contract { let required_balance = self .add_fast_transfer(&fast_transfer, &relayer_id) .saturating_add(ONE_YOCTO); - self.update_storage_balance(relayer_id, required_balance, env::attached_deposit()); + self.update_storage_balance(relayer_id, required_balance, NearToken::from_yoctonear(0)); self.send_tokens( fast_transfer.token_id, recipient, - fast_transfer.amount, + U128(fast_transfer.amount.0 - fast_transfer.fee.fee.0), fast_transfer.msg, ) } @@ -1015,14 +1023,7 @@ impl Contract { let token = self.get_token_id(&transfer_message.token); // If fast transfer happened, change recipient to the relayer that executed fast transfer - let fast_transfer = FastTransfer { - transfer_id: transfer_message.get_transfer_id(), - token_id: token.clone(), - amount: U128(transfer_message.amount.0 - transfer_message.fee.fee.0), - fee: transfer_message.fee.clone(), - recipient: OmniAddress::Near(recipient.clone()), - msg: transfer_message.msg.clone(), - }; + let fast_transfer = FastTransfer::from_transfer(transfer_message.clone(), token.clone()); let (recipient, is_fast_transfer) = match self.fast_transfers.get(&fast_transfer.hash()) { Some(relayer) => { require!( @@ -1125,15 +1126,7 @@ impl Contract { let mut required_balance = self.add_fin_transfer(&transfer_message.get_transfer_id()); let token = self.get_token_id(&transfer_message.token); - let fast_transfer = FastTransfer { - transfer_id: transfer_message.get_transfer_id(), - token_id: token.clone(), - amount: U128(transfer_message.amount.0 - transfer_message.fee.fee.0), - fee: transfer_message.fee.clone(), - recipient: transfer_message.recipient.clone(), - msg: transfer_message.msg.clone(), - }; - + let fast_transfer = FastTransfer::from_transfer(transfer_message.clone(), token.clone()); let recipient = match self.fast_transfers.get(&fast_transfer.hash()) { Some(relayer) => { require!( @@ -1147,7 +1140,12 @@ impl Contract { // If fast transfer happened, send tokens to the relayer that executed fast transfer if let Some(relayer) = recipient { - self.send_tokens(token, relayer, transfer_message.amount, String::new()); + self.send_tokens( + token, + relayer, + U128(transfer_message.amount.0 - transfer_message.fee.fee.0), + String::new(), + ); } else { required_balance = self .add_transfer_message(transfer_message.clone(), predecessor_account_id.clone()) @@ -1318,13 +1316,13 @@ impl Contract { fn add_fast_transfer( &mut self, - transfer_id: &FastTransfer, + fast_transfer: &FastTransfer, relayer_id: &AccountId, ) -> NearToken { let storage_usage = env::storage_usage(); require!( self.fast_transfers - .insert(&transfer_id.hash(), relayer_id) + .insert(&fast_transfer.hash(), relayer_id) .is_none(), "Fast transfer is already performed" ); diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index 555a8b74..4034982a 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -516,3 +516,16 @@ impl FastTransfer { utils::keccak256(&borsh::to_vec(self).unwrap()) } } + +impl FastTransfer { + pub fn from_transfer(transfer: TransferMessage, token_id: AccountId) -> Self { + FastTransfer { + transfer_id: transfer.get_transfer_id(), + token_id, + amount: transfer.amount, + fee: transfer.fee, + recipient: transfer.recipient, + msg: transfer.msg, + } + } +} From 7aa8115d52d0c3a5f3c18c0954ddfeede6f6c83b Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:34:28 +0400 Subject: [PATCH 05/14] Minor code review fixes --- near/omni-bridge/src/lib.rs | 23 ++++++++++++----------- near/omni-types/src/lib.rs | 7 +++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index c38e8fb2..ee92dc4f 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -24,9 +24,9 @@ use omni_types::near_events::OmniBridgeEvent; use omni_types::prover_args::VerifyProofArgs; use omni_types::prover_result::ProverResult; use omni_types::{ - BasicMetadata, BridgeOnTransferMsg, ChainKind, FastFinTransferMsg, FastTransfer, Fee, - InitTransferMsg, MetadataPayload, Nonce, OmniAddress, PayloadType, SignRequest, TransferId, - TransferMessage, TransferMessagePayload, UpdateFee, + BasicMetadata, BridgeOnTransferMsg, ChainKind, FastFinTransferMsg, FastTransfer, + FastTransferId, Fee, InitTransferMsg, MetadataPayload, Nonce, OmniAddress, PayloadType, + SignRequest, TransferId, TransferMessage, TransferMessagePayload, UpdateFee, }; use storage::{TransferMessageStorage, TransferMessageStorageValue}; @@ -164,7 +164,7 @@ pub struct Contract { pub factories: LookupMap, pub pending_transfers: LookupMap, pub finalised_transfers: LookupSet, - pub fast_transfers: LookupMap<[u8; 32], AccountId>, + pub fast_transfers: LookupMap, pub token_id_to_address: LookupMap<(ChainKind, AccountId), OmniAddress>, pub token_address_to_id: LookupMap, pub deployed_tokens: LookupSet, @@ -186,14 +186,15 @@ impl FungibleTokenReceiver for Contract { amount: U128, msg: String, ) -> PromiseOrValue { + let token_id = env::predecessor_account_id(); let parsed_msg: BridgeOnTransferMsg = serde_json::from_str(&msg).sdk_expect("ERR_PARSE_MSG"); let promise_or_value = match parsed_msg { BridgeOnTransferMsg::InitTransfer(init_transfer_msg) => { - PromiseOrValue::Value(self.init_transfer(sender_id, amount, init_transfer_msg)) + PromiseOrValue::Value(self.init_transfer(sender_id, token_id, amount, init_transfer_msg)) } BridgeOnTransferMsg::FastFinTransfer(fast_fin_transfer_msg) => { - self.fast_fin_transfer(sender_id, amount, fast_fin_transfer_msg) + self.fast_fin_transfer(sender_id, token_id, amount, fast_fin_transfer_msg) } }; @@ -404,10 +405,10 @@ impl Contract { fn init_transfer( &mut self, sender_id: AccountId, + token_id: AccountId, amount: U128, init_transfer_msg: InitTransferMsg, ) -> U128 { - let token_id = env::predecessor_account_id(); require!( init_transfer_msg.recipient.get_chain() != ChainKind::Near, "ERR_INVALID_RECIPIENT_CHAIN" @@ -552,10 +553,10 @@ impl Contract { fn fast_fin_transfer( &mut self, sender_id: AccountId, + token_id: AccountId, amount: U128, fast_fin_transfer_msg: FastFinTransferMsg, ) -> PromiseOrValue { - let token_id = env::predecessor_account_id(); let fast_transfer = FastTransfer { token_id: token_id.clone(), recipient: fast_fin_transfer_msg.recipient.clone(), @@ -1034,7 +1035,7 @@ impl Contract { // If fast transfer happened, change recipient to the relayer that executed fast transfer let fast_transfer = FastTransfer::from_transfer(transfer_message.clone(), token.clone()); - let (recipient, is_fast_transfer) = match self.fast_transfers.get(&fast_transfer.hash()) { + let (recipient, is_fast_transfer) = match self.fast_transfers.get(&fast_transfer.id()) { Some(relayer) => { require!( predecessor_account_id == *relayer, @@ -1137,7 +1138,7 @@ impl Contract { let token = self.get_token_id(&transfer_message.token); let fast_transfer = FastTransfer::from_transfer(transfer_message.clone(), token.clone()); - let recipient = match self.fast_transfers.get(&fast_transfer.hash()) { + let recipient = match self.fast_transfers.get(&fast_transfer.id()) { Some(relayer) => { require!( predecessor_account_id == *relayer, @@ -1322,7 +1323,7 @@ impl Contract { let storage_usage = env::storage_usage(); require!( self.fast_transfers - .insert(&fast_transfer.hash(), relayer_id) + .insert(&fast_transfer.id(), relayer_id) .is_none(), "Fast transfer is already performed" ); diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index 4034982a..00bfe3e0 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -501,6 +501,9 @@ pub struct BasicMetadata { pub decimals: u8, } +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Eq, PartialEq)] +pub struct FastTransferId(pub [u8; 32]); + #[derive(BorshDeserialize, BorshSerialize, Debug, Clone)] pub struct FastTransfer { pub transfer_id: TransferId, @@ -512,8 +515,8 @@ pub struct FastTransfer { } impl FastTransfer { - pub fn hash(&self) -> [u8; 32] { - utils::keccak256(&borsh::to_vec(self).unwrap()) + pub fn id(&self) -> FastTransferId { + FastTransferId(utils::keccak256(&borsh::to_vec(self).unwrap())) } } From a86a62e70e7eac45d3d28b96eef180cc99a4eda1 Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:47:55 +0400 Subject: [PATCH 06/14] Fix fast transfer to other chain message --- near/omni-bridge/src/lib.rs | 13 +++++++++---- near/omni-bridge/src/storage.rs | 1 + near/omni-bridge/src/tests/lib_test.rs | 1 + near/omni-types/src/lib.rs | 1 + near/omni-types/src/tests/lib_test.rs | 3 +++ 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index ee92dc4f..a0a303a4 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -190,9 +190,9 @@ impl FungibleTokenReceiver for Contract { let parsed_msg: BridgeOnTransferMsg = serde_json::from_str(&msg).sdk_expect("ERR_PARSE_MSG"); let promise_or_value = match parsed_msg { - BridgeOnTransferMsg::InitTransfer(init_transfer_msg) => { - PromiseOrValue::Value(self.init_transfer(sender_id, token_id, amount, init_transfer_msg)) - } + BridgeOnTransferMsg::InitTransfer(init_transfer_msg) => PromiseOrValue::Value( + self.init_transfer(sender_id, token_id, amount, init_transfer_msg), + ), BridgeOnTransferMsg::FastFinTransfer(fast_fin_transfer_msg) => { self.fast_fin_transfer(sender_id, token_id, amount, fast_fin_transfer_msg) } @@ -430,6 +430,7 @@ impl Contract { sender: OmniAddress::Near(sender_id.clone()), msg: String::new(), destination_nonce, + parent_transfer_id: None, }; require!( transfer_message.fee.fee < transfer_message.amount, @@ -534,6 +535,7 @@ impl Contract { sender: init_transfer.sender, msg: init_transfer.msg, destination_nonce, + parent_transfer_id: None, }; if let OmniAddress::Near(recipient) = transfer_message.recipient.clone() { @@ -640,8 +642,10 @@ impl Contract { let destination_nonce = self.get_next_destination_nonce(fast_transfer.recipient.get_chain()); + self.current_origin_nonce += 1; + let transfer_message = TransferMessage { - origin_nonce: fast_transfer.transfer_id.origin_nonce, + origin_nonce: self.current_origin_nonce, token: OmniAddress::Near(fast_transfer.token_id.clone()), amount: fast_transfer.amount, recipient: fast_transfer.recipient, @@ -649,6 +653,7 @@ impl Contract { sender: OmniAddress::Near(relayer_id.clone()), msg: fast_transfer.msg, destination_nonce, + parent_transfer_id: Some(fast_transfer.transfer_id), }; required_balance = self diff --git a/near/omni-bridge/src/storage.rs b/near/omni-bridge/src/storage.rs index 636cf926..c51f30dc 100644 --- a/near/omni-bridge/src/storage.rs +++ b/near/omni-bridge/src/storage.rs @@ -148,6 +148,7 @@ impl Contract { sender: OmniAddress::Near(max_account_id.clone()), msg: String::new(), destination_nonce: 0, + parent_transfer_id: None, }, owner: max_account_id, })) diff --git a/near/omni-bridge/src/tests/lib_test.rs b/near/omni-bridge/src/tests/lib_test.rs index cf1adc44..7da27ba8 100644 --- a/near/omni-bridge/src/tests/lib_test.rs +++ b/near/omni-bridge/src/tests/lib_test.rs @@ -269,6 +269,7 @@ fn run_update_transfer_fee( sender: OmniAddress::Near(sender_id.clone().parse().unwrap()), msg: "".to_string(), destination_nonce: 1, + parent_transfer_id: None, }; contract.insert_raw_transfer( diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index 00bfe3e0..21aa7485 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -428,6 +428,7 @@ pub struct TransferMessage { pub sender: OmniAddress, pub msg: String, pub destination_nonce: Nonce, + pub parent_transfer_id: Option, } impl TransferMessage { diff --git a/near/omni-types/src/tests/lib_test.rs b/near/omni-types/src/tests/lib_test.rs index a43b287e..f53ba499 100644 --- a/near/omni-types/src/tests/lib_test.rs +++ b/near/omni-types/src/tests/lib_test.rs @@ -345,6 +345,7 @@ fn test_transfer_message_getters() { fee: Fee::default(), sender: OmniAddress::Eth(evm_addr.clone()), msg: "".to_string(), + parent_transfer_id: None, }, ChainKind::Eth, TransferId { @@ -363,6 +364,7 @@ fn test_transfer_message_getters() { fee: Fee::default(), sender: OmniAddress::Near("alice.near".parse().unwrap()), msg: "".to_string(), + parent_transfer_id: None, }, ChainKind::Near, TransferId { @@ -381,6 +383,7 @@ fn test_transfer_message_getters() { fee: Fee::default(), sender: OmniAddress::Sol("11111111111111111111111111111111".parse().unwrap()), msg: "".to_string(), + parent_transfer_id: None, }, ChainKind::Sol, TransferId { From 118a3332583d68ca30da8c9760532e2cb1fa0def Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:37:34 +0400 Subject: [PATCH 07/14] add integration tests --- near/omni-bridge/src/lib.rs | 12 +- near/omni-bridge/src/storage.rs | 19 +- near/omni-bridge/src/tests/lib_test.rs | 26 +- near/omni-tests/src/fast_transfer.rs | 574 +++++++++++++++++++++++++ near/omni-tests/src/helpers.rs | 12 + near/omni-tests/src/init_transfer.rs | 6 +- near/omni-tests/src/lib.rs | 1 + 7 files changed, 627 insertions(+), 23 deletions(-) create mode 100644 near/omni-tests/src/fast_transfer.rs diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index a0a303a4..1311fa35 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -164,7 +164,7 @@ pub struct Contract { pub factories: LookupMap, pub pending_transfers: LookupMap, pub finalised_transfers: LookupSet, - pub fast_transfers: LookupMap, + pub fast_transfers: LookupMap, // value is relayer address that performed the transfer pub token_id_to_address: LookupMap<(ChainKind, AccountId), OmniAddress>, pub token_address_to_id: LookupMap, pub deployed_tokens: LookupSet, @@ -191,14 +191,13 @@ impl FungibleTokenReceiver for Contract { serde_json::from_str(&msg).sdk_expect("ERR_PARSE_MSG"); let promise_or_value = match parsed_msg { BridgeOnTransferMsg::InitTransfer(init_transfer_msg) => PromiseOrValue::Value( - self.init_transfer(sender_id, token_id, amount, init_transfer_msg), + self.init_transfer(sender_id, token_id.clone(), amount, init_transfer_msg), ), BridgeOnTransferMsg::FastFinTransfer(fast_fin_transfer_msg) => { - self.fast_fin_transfer(sender_id, token_id, amount, fast_fin_transfer_msg) + self.fast_fin_transfer(sender_id, token_id.clone(), amount, fast_fin_transfer_msg) } }; - let token_id = env::predecessor_account_id(); if self.deployed_tokens.contains(&token_id) { ext_token::ext(token_id) .with_static_gas(BURN_TOKEN_GAS) @@ -476,6 +475,7 @@ impl Contract { } } + #[payable] pub fn fin_transfer(&mut self, #[serializer(borsh)] args: FinTransferArgs) -> Promise { require!( args.storage_deposit_actions.len() <= 3, @@ -612,7 +612,7 @@ impl Contract { #[serializer(borsh)] relayer_id: AccountId, ) -> Promise { require!( - Self::check_storage_balance_result(1), + Self::check_storage_balance_result(0), "STORAGE_ERR: The transfer recipient is omitted" ); @@ -660,7 +660,7 @@ impl Contract { .add_transfer_message(transfer_message, relayer_id.clone()) .saturating_add(required_balance); - self.update_storage_balance(relayer_id, required_balance, env::attached_deposit()); + self.update_storage_balance(relayer_id, required_balance, NearToken::from_near(0)); } #[payable] diff --git a/near/omni-bridge/src/storage.rs b/near/omni-bridge/src/storage.rs index c51f30dc..6a656926 100644 --- a/near/omni-bridge/src/storage.rs +++ b/near/omni-bridge/src/storage.rs @@ -160,7 +160,7 @@ impl Contract { } pub fn required_balance_for_fin_transfer(&self) -> NearToken { - let key_len = borsh::to_vec(&(ChainKind::Eth, 0_u128)) + let key_len = borsh::to_vec(&(ChainKind::Eth, 0_u64)) .sdk_expect("ERR_BORSH") .len() as u64; @@ -171,6 +171,23 @@ impl Contract { storage_cost.saturating_add(ft_transfers_cost) } + pub fn required_balance_for_fast_transfer(&self) -> NearToken { + let key_len = borsh::to_vec(&[0u8; 32]) + .sdk_expect("ERR_BORSH") + .len() as u64; + + let max_account_id: AccountId = "a".repeat(64).parse().sdk_expect("ERR_PARSE_ACCOUNT_ID"); + let value_len = borsh::to_vec(&max_account_id) + .sdk_expect("ERR_BORSH") + .len() as u64; + + let storage_cost = + env::storage_byte_cost().saturating_mul((Self::get_basic_storage() + key_len + value_len).into()); + let ft_transfers_cost = NearToken::from_yoctonear(1); + + storage_cost.saturating_add(ft_transfers_cost) + } + pub fn required_balance_for_bind_token(&self) -> NearToken { let max_token_id: AccountId = "a".repeat(64).parse().sdk_expect("ERR_PARSE_ACCOUNT_ID"); diff --git a/near/omni-bridge/src/tests/lib_test.rs b/near/omni-bridge/src/tests/lib_test.rs index 7da27ba8..f95acbc6 100644 --- a/near/omni-bridge/src/tests/lib_test.rs +++ b/near/omni-bridge/src/tests/lib_test.rs @@ -6,7 +6,7 @@ use near_sdk::test_utils::VMContextBuilder; use near_sdk::RuntimeFeesConfig; use near_sdk::{test_vm_config, testing_env}; use omni_types::prover_result::{InitTransferMessage, ProverResult}; -use omni_types::{EvmAddress, Nonce, TransferId}; +use omni_types::{BridgeOnTransferMsg, EvmAddress, Nonce, TransferId}; use std::str::FromStr; use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver; @@ -92,7 +92,7 @@ fn run_ft_on_transfer( token_id: String, amount: U128, attached_deposit: Option, - msg: InitTransferMsg, + msg: BridgeOnTransferMsg, ) -> PromiseOrValue { let sender_id = AccountId::try_from(sender_id).expect("Invalid sender ID"); let token_id = AccountId::try_from(token_id).expect("Invalid token ID"); @@ -125,7 +125,7 @@ fn test_initialize_contract() { } #[test] -fn test_ft_on_transfer_nonce_increment() { +fn test_init_transfer_nonce_increment() { let mut contract = get_default_contract(); run_ft_on_transfer( @@ -134,14 +134,14 @@ fn test_ft_on_transfer_nonce_increment() { DEFAULT_FT_CONTRACT_ACCOUNT.to_string(), U128(100), None, - get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0), + BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0)), ); assert_eq!(contract.current_origin_nonce, DEFAULT_NONCE + 1); } #[test] -fn test_ft_on_transfer_stored_transfer_message() { +fn test_init_transfer_stored_transfer_message() { let mut contract = get_default_contract(); let msg = get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0); @@ -151,7 +151,7 @@ fn test_ft_on_transfer_stored_transfer_message() { DEFAULT_FT_CONTRACT_ACCOUNT.to_string(), U128(DEFAULT_TRANSFER_AMOUNT), None, - msg.clone(), + BridgeOnTransferMsg::InitTransfer(msg.clone()), ); let stored_transfer = contract.get_transfer_message(TransferId { @@ -185,7 +185,7 @@ fn test_ft_on_transfer_stored_transfer_message() { } #[test] -fn test_ft_on_transfer_promise_result() { +fn test_init_transfer_promise_result() { let mut contract = get_default_contract(); let promise = run_ft_on_transfer( @@ -194,7 +194,7 @@ fn test_ft_on_transfer_promise_result() { DEFAULT_FT_CONTRACT_ACCOUNT.to_string(), U128(DEFAULT_TRANSFER_AMOUNT), None, - get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0), + BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0)), ); let remaining = match promise { @@ -206,7 +206,7 @@ fn test_ft_on_transfer_promise_result() { #[test] #[should_panic(expected = "ERR_INVALID_FEE")] -fn test_ft_on_transfer_invalid_fee() { +fn test_init_transfer_invalid_fee() { let mut contract = get_default_contract(); run_ft_on_transfer( &mut contract, @@ -214,16 +214,16 @@ fn test_ft_on_transfer_invalid_fee() { DEFAULT_FT_CONTRACT_ACCOUNT.to_string(), U128(DEFAULT_TRANSFER_AMOUNT), None, - get_init_transfer_msg( + BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg( DEFAULT_ETH_USER_ADDRESS.to_string(), DEFAULT_TRANSFER_AMOUNT + 1, 0, - ), + )), ); } #[test] -fn test_ft_on_transfer_balance_updated() { +fn test_init_transfer_balance_updated() { let mut contract = get_default_contract(); let min_storage_balance = contract.required_balance_for_account(); @@ -236,7 +236,7 @@ fn test_ft_on_transfer_balance_updated() { DEFAULT_FT_CONTRACT_ACCOUNT.to_string(), U128(DEFAULT_TRANSFER_AMOUNT), Some(total_balance), - get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0), + BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0)), ); let storage_balance = contract diff --git a/near/omni-tests/src/fast_transfer.rs b/near/omni-tests/src/fast_transfer.rs new file mode 100644 index 00000000..4c2ad83b --- /dev/null +++ b/near/omni-tests/src/fast_transfer.rs @@ -0,0 +1,574 @@ +#[cfg(test)] +mod tests { + use near_sdk::{ + borsh, + json_types::U128, + serde_json::{self, json}, + AccountId, + }; + use near_workspaces::{ + result::{ExecutionResult, Value}, + types::NearToken, + }; + use omni_types::{ + locker_args::{FinTransferArgs, StorageDepositAction}, + prover_result::{InitTransferMessage, ProverResult}, + BridgeOnTransferMsg, ChainKind, FastFinTransferMsg, Fee, OmniAddress, TransferId, + TransferMessage, + }; + + use crate::helpers::tests::{ + account_n, base_eoa_address, base_factory_address, eth_eoa_address, eth_factory_address, + eth_token_address, get_bind_token_args, relayer_account_id, LOCKER_PATH, MOCK_PROVER_PATH, + MOCK_TOKEN_PATH, NEP141_DEPOSIT, + }; + + struct TestEnv { + token_contract: near_workspaces::Contract, + bridge_contract: near_workspaces::Contract, + relayer_account: near_workspaces::Account, + } + + impl TestEnv { + async fn new(sender_balance_token: u128) -> anyhow::Result { + let worker = near_workspaces::sandbox().await?; + // Deploy and initialize FT token + let token_contract = worker.dev_deploy(&std::fs::read(MOCK_TOKEN_PATH)?).await?; + token_contract + .call("new_default_meta") + .args_json(json!({ + "owner_id": token_contract.id(), + "total_supply": U128(u128::MAX) + })) + .max_gas() + .transact() + .await? + .into_result()?; + + let prover_contract = worker.dev_deploy(&std::fs::read(MOCK_PROVER_PATH)?).await?; + // Deploy and initialize bridge + let bridge_contract = worker.dev_deploy(&std::fs::read(LOCKER_PATH)?).await?; + bridge_contract + .call("new") + .args_json(json!({ + "prover_account": prover_contract.id(), + "mpc_signer": "mpc.testnet", + "nonce": U128(0), + "wnear_account_id": "wnear.testnet", + })) + .max_gas() + .transact() + .await? + .into_result()?; + + // Register the bridge contract in the token contract + token_contract + .call("storage_deposit") + .args_json(json!({ + "account_id": bridge_contract.id(), + "registration_only": true, + })) + .deposit(NEP141_DEPOSIT) + .max_gas() + .transact() + .await? + .into_result()?; + + // Create relayer account. (Default account in sandbox has 100 NEAR) + let relayer_account = worker + .create_tla(relayer_account_id(), worker.dev_generate().await.1) + .await? + .unwrap(); + + // Register the relayer in the token contract + token_contract + .call("storage_deposit") + .args_json(json!({ + "account_id": relayer_account.id(), + "registration_only": true, + })) + .deposit(NEP141_DEPOSIT) + .max_gas() + .transact() + .await? + .into_result()?; + + // Transfer initial tokens to the relayer account + token_contract + .call("ft_transfer") + .args_json(json!({ + "receiver_id": relayer_account.id(), + "amount": U128(sender_balance_token), + "memo": None::, + })) + .deposit(NearToken::from_yoctonear(1)) + .max_gas() + .transact() + .await? + .into_result()?; + + // Transfer initial tokens to the bridge contract (locked) + token_contract + .call("ft_transfer") + .args_json(json!({ + "receiver_id": bridge_contract.id(), + "amount": U128(sender_balance_token), + })) + .deposit(NearToken::from_yoctonear(1)) + .max_gas() + .transact() + .await? + .into_result()?; + + // Add ETH factory address to the bridge contract + let eth_factory_address = eth_factory_address(); + bridge_contract + .call("add_factory") + .args_json(json!({ + "address": eth_factory_address, + })) + .max_gas() + .transact() + .await? + .into_result()?; + + // Bind the token to the bridge contract + let required_deposit_for_bind_token = bridge_contract + .view("required_balance_for_bind_token") + .await? + .json()?; + bridge_contract + .call("bind_token") + .args_borsh(get_bind_token_args( + &token_contract.id(), + ð_token_address(), + ð_factory_address, + )) + .deposit(required_deposit_for_bind_token) + .max_gas() + .transact() + .await? + .into_result()?; + + // Add base factory address to the bridge contract + let base_factory_address = base_factory_address(); + bridge_contract + .call("add_factory") + .args_json(json!({ + "address": base_factory_address, + })) + .max_gas() + .transact() + .await? + .into_result()?; + + Ok(Self { + token_contract, + bridge_contract, + relayer_account, + }) + } + } + + async fn get_balance_required_for_fast_transfer_to_near( + bridge_contract: &near_workspaces::Contract, + is_storage_deposit: bool, + ) -> anyhow::Result { + let required_balance_for_account: NearToken = bridge_contract + .view("required_balance_for_account") + .await? + .json()?; + + let required_balance_fast_transfer: NearToken = bridge_contract + .view("required_balance_for_fast_transfer") + .await? + .json()?; + + let mut required_balance = + required_balance_for_account.saturating_add(required_balance_fast_transfer); + if is_storage_deposit { + required_balance = required_balance.saturating_add(NEP141_DEPOSIT); + } + + Ok(required_balance) + } + + async fn get_balance_required_for_fast_transfer_to_other_chain( + bridge_contract: &near_workspaces::Contract, + ) -> anyhow::Result { + let required_balance_for_account: NearToken = bridge_contract + .view("required_balance_for_account") + .await? + .json()?; + + let required_balance_fast_transfer: NearToken = bridge_contract + .view("required_balance_for_fast_transfer") + .await? + .json()?; + + let required_balance_init_transfer: NearToken = bridge_contract + .view("required_balance_for_init_transfer") + .await? + .json()?; + + Ok(required_balance_for_account + .saturating_add(required_balance_fast_transfer) + .saturating_add(required_balance_init_transfer)) + } + + async fn do_fast_transfer( + env: &TestEnv, + transfer_amount: u128, + fast_transfer_msg: FastFinTransferMsg, + ) -> anyhow::Result> { + let storage_deposit_amount = match fast_transfer_msg.recipient { + OmniAddress::Near(_) => { + get_balance_required_for_fast_transfer_to_near(&env.bridge_contract, true).await? + } + _ => { + get_balance_required_for_fast_transfer_to_other_chain(&env.bridge_contract).await? + } + }; + + // Deposit to the storage + env.relayer_account + .call(env.bridge_contract.id(), "storage_deposit") + .args_json(json!({ + "account_id": env.relayer_account.id(), + })) + .deposit(storage_deposit_amount) + .max_gas() + .transact() + .await? + .into_result()?; + + // Initiate the fast transfer + let transfer_result = env + .relayer_account + .call(env.token_contract.id(), "ft_transfer_call") + .args_json(json!({ + "receiver_id": env.bridge_contract.id(), + "amount": U128(transfer_amount), + "memo": None::, + "msg": serde_json::to_string(&BridgeOnTransferMsg::FastFinTransfer(fast_transfer_msg))?, + })) + .deposit(NearToken::from_yoctonear(1)) + .max_gas() + .transact() + .await? + .into_result()?; + + Ok(transfer_result) + } + + async fn do_fin_transfer( + env: &TestEnv, + transfer_msg: InitTransferMessage, + ) -> anyhow::Result> { + let required_balance_for_fin_transfer: NearToken = env + .bridge_contract + .view("required_balance_for_fin_transfer") + .await? + .json()?; + + // let required_deposit_for_fin_transfer = NEP141_DEPOSIT + // .saturating_add(required_balance_for_fin_transfer); + + let storage_deposit_action = StorageDepositAction { + token_id: env.token_contract.id().clone(), + account_id: env.relayer_account.id().clone(), + storage_deposit_amount: None, + }; + + let result = env + .relayer_account + .call(env.bridge_contract.id(), "fin_transfer") + .args_borsh(FinTransferArgs { + chain_kind: omni_types::ChainKind::Eth, + storage_deposit_actions: vec![storage_deposit_action], + prover_args: borsh::to_vec(&ProverResult::InitTransfer(transfer_msg)).unwrap(), + }) + .deposit(required_balance_for_fin_transfer) + .max_gas() + .transact() + .await? + .into_result()?; + + Ok(result) + } + + async fn get_balance( + token_contract: &near_workspaces::Contract, + account_id: &AccountId, + ) -> anyhow::Result { + let balance: U128 = token_contract + .view("ft_balance_of") + .args_json(json!({ + "account_id": account_id, + })) + .await? + .json()?; + + Ok(balance) + } + + #[tokio::test] + async fn test_fast_transfer_to_near() -> anyhow::Result<()> { + let env = TestEnv::new(1_000_000).await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg); + + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; + + assert_eq!(0, result.failures().len()); + + let recipient_balance: U128 = get_balance(&env.token_contract, &account_n(1)).await?; + assert_eq!(transfer_amount, recipient_balance.0); + + Ok(()) + } + + #[tokio::test] + async fn test_fast_transfer_to_near_twice() -> anyhow::Result<()> { + let env = TestEnv::new(1_000_000).await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg); + + do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; + + assert_eq!(1, result.failures().len()); + + let failure = result.failures()[0].clone().into_result(); + assert!(failure.is_err_and(|err| { + format!("{:?}", err).contains("Fast transfer is already performed") + })); + + Ok(()) + } + + #[tokio::test] + async fn test_fast_transfer_to_near_finalisation() -> anyhow::Result<()> { + let env = TestEnv::new(1_000_000).await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); + + do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let recipient_balance_before = get_balance(&env.token_contract, &account_n(1)).await?; + + do_fin_transfer(&env, transfer_msg).await?; + + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let recipient_balance_after = get_balance(&env.token_contract, &account_n(1)).await?; + + assert_eq!( + transfer_amount, + relayer_balance_after.0 - relayer_balance_before.0 + ); + assert_eq!(recipient_balance_after, recipient_balance_before); + + Ok(()) + } + + #[tokio::test] + async fn test_fast_transfer_to_near_finalisation_twice() -> anyhow::Result<()> { + let env = TestEnv::new(1_000_000).await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); + + do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + + do_fin_transfer(&env, transfer_msg.clone()).await?; + let result = do_fin_transfer(&env, transfer_msg).await; + + assert!(result.is_err_and(|err| { + println!("err: {:?}", err); + format!("{:?}", err).contains("The transfer is already finalised") + })); + + Ok(()) + } + + #[tokio::test] + async fn test_fast_transfer_to_other_chain() -> anyhow::Result<()> { + let env = TestEnv::new(1_000_000).await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); + + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + + assert_eq!(0, result.failures().len()); + + //get_transfer_message + let transfer_message: TransferMessage = env + .bridge_contract + .view("get_transfer_message") + .args_json(json!({ + "transfer_id": TransferId { + origin_chain: ChainKind::Near, + origin_nonce: 1, + }, + })) + .await? + .json()?; + + assert_eq!( + OmniAddress::Near(env.token_contract.id().clone()), + transfer_message.token + ); + assert_eq!(transfer_amount, transfer_message.amount.0); + assert_eq!(fast_transfer_msg.recipient, transfer_message.recipient); + assert_eq!(fast_transfer_msg.fee, transfer_message.fee); + assert_eq!(fast_transfer_msg.msg, transfer_message.msg); + assert_eq!( + OmniAddress::Near(env.relayer_account.id().clone()), + transfer_message.sender + ); + + Ok(()) + } + + #[tokio::test] + async fn test_fast_transfer_to_other_chain_twice() -> anyhow::Result<()> { + let env = TestEnv::new(1_000_000).await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); + + do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; + + assert_eq!(1, result.failures().len()); + + let failure = result.failures()[0].clone().into_result(); + assert!(failure.is_err_and(|err| { + format!("{:?}", err).contains("Fast transfer is already performed") + })); + + Ok(()) + } + + #[tokio::test] + async fn test_fast_transfer_to_other_chain_finalisation() -> anyhow::Result<()> { + let env = TestEnv::new(1_000_000).await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); + + do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + + do_fin_transfer(&env, transfer_msg).await?; + + let transfer_message = env + .bridge_contract + .view("get_transfer_message") + .args_json(json!({ + "transfer_id": TransferId { + origin_chain: ChainKind::Base, + origin_nonce: 0, + }, + })) + .await; + + assert!(transfer_message + .is_err_and(|err| { format!("{:?}", err).contains("The transfer does not exist") })); + + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + + assert_eq!( + transfer_amount, + relayer_balance_after.0 - relayer_balance_before.0 + ); + + Ok(()) + } + + #[tokio::test] + async fn test_fast_transfer_to_other_chain_finalisation_twice() -> anyhow::Result<()> { + let env = TestEnv::new(1_000_000).await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); + + do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + + do_fin_transfer(&env, transfer_msg.clone()).await?; + let result = do_fin_transfer(&env, transfer_msg).await; + + assert!(result.is_err_and(|err| { + println!("err: {:?}", err); + format!("{:?}", err).contains("The transfer is already finalised") + })); + + Ok(()) + } + + fn get_transfer_msg_to_near(env: &TestEnv, amount: u128) -> InitTransferMessage { + InitTransferMessage { + origin_nonce: 0, + token: OmniAddress::Near(env.token_contract.id().clone()), + recipient: OmniAddress::Near(account_n(1)), + amount: U128(amount), + fee: Fee { + fee: U128(0), + native_fee: U128(0), + }, + sender: eth_eoa_address(), + msg: String::default(), + emitter_address: eth_factory_address(), + } + } + + fn get_transfer_msg_to_other_chain(env: &TestEnv, amount: u128) -> InitTransferMessage { + InitTransferMessage { + origin_nonce: 0, + token: OmniAddress::Near(env.token_contract.id().clone()), + recipient: eth_eoa_address(), + amount: U128(amount), + fee: Fee { + fee: U128(0), + native_fee: U128(0), + }, + sender: base_eoa_address(), + msg: String::default(), + emitter_address: base_factory_address(), + } + } + + fn get_fast_transfer_msg(transfer_msg: InitTransferMessage) -> FastFinTransferMsg { + FastFinTransferMsg { + transfer_id: TransferId { + origin_chain: transfer_msg.sender.get_chain(), + origin_nonce: transfer_msg.origin_nonce, + }, + recipient: transfer_msg.recipient.clone(), + fee: transfer_msg.fee, + msg: transfer_msg.msg, + storage_deposit_amount: match transfer_msg.recipient.get_chain() { + ChainKind::Near => Some(NEP141_DEPOSIT.as_yoctonear()), + _ => None, + }, + } + } +} diff --git a/near/omni-tests/src/helpers.rs b/near/omni-tests/src/helpers.rs index 6b33d05a..6c9152f8 100644 --- a/near/omni-tests/src/helpers.rs +++ b/near/omni-tests/src/helpers.rs @@ -52,12 +52,24 @@ pub mod tests { .unwrap() } + pub fn base_eoa_address() -> OmniAddress { + "base:0xc5ed912ca6db7b41de4ef3632fa0a5641e42bf09" + .parse() + .unwrap() + } + pub fn base_token_address() -> OmniAddress { "base:0x1234567890123456789012345678901234567890" .parse() .unwrap() } + pub fn base_factory_address() -> OmniAddress { + "base:0x252e87862A3A720287E7fd527cE6e8d0738427A2" + .parse() + .unwrap() + } + pub fn get_claim_fee_args_near( origin_chain: ChainKind, destination_chain: ChainKind, diff --git a/near/omni-tests/src/init_transfer.rs b/near/omni-tests/src/init_transfer.rs index 560c9ca9..0be1dfd9 100644 --- a/near/omni-tests/src/init_transfer.rs +++ b/near/omni-tests/src/init_transfer.rs @@ -12,8 +12,8 @@ mod tests { }; use near_workspaces::{result::ExecutionSuccess, types::NearToken, AccountId}; use omni_types::{ - near_events::OmniBridgeEvent, ChainKind, Fee, InitTransferMsg, OmniAddress, TransferId, - TransferMessage, UpdateFee, + near_events::OmniBridgeEvent, BridgeOnTransferMsg, ChainKind, Fee, InitTransferMsg, + OmniAddress, TransferId, TransferMessage, UpdateFee, }; const DEFAULT_NEAR_SANDBOX_BALANCE: NearToken = NearToken::from_near(100); @@ -203,7 +203,7 @@ mod tests { "receiver_id": env.locker_contract.id(), "amount": U128(transfer_amount), "memo": None::, - "msg": serde_json::to_string(&init_transfer_msg)?, + "msg": serde_json::to_string(&BridgeOnTransferMsg::InitTransfer(init_transfer_msg))?, })) .deposit(NearToken::from_yoctonear(1)) .max_gas() diff --git a/near/omni-tests/src/lib.rs b/near/omni-tests/src/lib.rs index 1717cf35..c69725c9 100644 --- a/near/omni-tests/src/lib.rs +++ b/near/omni-tests/src/lib.rs @@ -1,3 +1,4 @@ +mod fast_transfer; mod fin_transfer; mod helpers; mod init_transfer; From f046b2d5fc0efa9bb7df1ea1837dc05b93122919 Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:34:02 +0400 Subject: [PATCH 08/14] Fix token refund --- near/omni-bridge/src/lib.rs | 16 ++++++++++++ near/omni-tests/src/fast_transfer.rs | 38 +++++++++++++++++++++++++++- near/omni-types/src/lib.rs | 1 + 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index c66e994c..da6b6f06 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -56,6 +56,7 @@ const DEPLOY_TOKEN_GAS: Gas = Gas::from_tgas(50); const BURN_TOKEN_GAS: Gas = Gas::from_tgas(10); const MINT_TOKEN_GAS: Gas = Gas::from_tgas(5); const SET_METADATA_GAS: Gas = Gas::from_tgas(10); +const FT_RESOLVE_TRANSFER_GAS: Gas = Gas::from_tgas(3); const NO_DEPOSIT: NearToken = NearToken::from_near(0); const ONE_YOCTO: NearToken = NearToken::from_yoctonear(1); const SIGN_PATH: &str = "bridge-1"; @@ -630,6 +631,10 @@ impl Contract { recipient, U128(fast_transfer.amount.0 - fast_transfer.fee.fee.0), fast_transfer.msg, + ).then( + Self::ext(env::current_account_id()) + .with_static_gas(FT_RESOLVE_TRANSFER_GAS) + .ft_resolve_transfer(fast_transfer.amount) ) } @@ -1016,6 +1021,17 @@ impl Contract { pub fn get_current_destination_nonce(&self, chain_kind: ChainKind) -> Nonce { self.destination_nonces.get(&chain_kind).unwrap_or_default() } + + #[private] + pub fn ft_resolve_transfer( + &mut self, + amount: U128, + ) -> U128 { + match env::promise_result(0) { + PromiseResult::Successful(_) => U128(0), + PromiseResult::Failed => amount, + } + } } impl Contract { diff --git a/near/omni-tests/src/fast_transfer.rs b/near/omni-tests/src/fast_transfer.rs index 4c2ad83b..e8ac5427 100644 --- a/near/omni-tests/src/fast_transfer.rs +++ b/near/omni-tests/src/fast_transfer.rs @@ -320,12 +320,20 @@ mod tests { let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); let fast_transfer_msg = get_fast_transfer_msg(transfer_msg); + let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; assert_eq!(0, result.failures().len()); let recipient_balance: U128 = get_balance(&env.token_contract, &account_n(1)).await?; + let relayer_balance_after = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + assert_eq!(transfer_amount, recipient_balance.0); + assert_eq!(contract_balance_before, contract_balance_after); + assert_eq!(relayer_balance_before, U128(relayer_balance_after.0 + transfer_amount)); Ok(()) } @@ -339,8 +347,11 @@ mod tests { let fast_transfer_msg = get_fast_transfer_msg(transfer_msg); do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; - let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; + let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; assert_eq!(1, result.failures().len()); let failure = result.failures()[0].clone().into_result(); @@ -348,6 +359,12 @@ mod tests { format!("{:?}", err).contains("Fast transfer is already performed") })); + let relayer_balance_after = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(relayer_balance_before, relayer_balance_after); + assert_eq!(contract_balance_before, contract_balance_after); + Ok(()) } @@ -409,6 +426,9 @@ mod tests { let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); + let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; assert_eq!(0, result.failures().len()); @@ -439,6 +459,12 @@ mod tests { transfer_message.sender ); + let relayer_balance_after = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(contract_balance_before, U128(contract_balance_after.0 - transfer_amount)); + assert_eq!(relayer_balance_before, U128(relayer_balance_after.0 + transfer_amount)); + Ok(()) } @@ -451,6 +477,10 @@ mod tests { let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + + let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; assert_eq!(1, result.failures().len()); @@ -460,6 +490,12 @@ mod tests { format!("{:?}", err).contains("Fast transfer is already performed") })); + let relayer_balance_after = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(relayer_balance_before, relayer_balance_after); + assert_eq!(contract_balance_before, contract_balance_after); + Ok(()) } diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index 21aa7485..d7a79446 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -516,6 +516,7 @@ pub struct FastTransfer { } impl FastTransfer { + #[allow(clippy::missing_panics_doc)] pub fn id(&self) -> FastTransferId { FastTransferId(utils::keccak256(&borsh::to_vec(self).unwrap())) } From ba82ea7fda08835689096ef46878fb62f2c9a887 Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:36:07 +0400 Subject: [PATCH 09/14] cargo fmt --- near/omni-bridge/src/lib.rs | 10 ++-- near/omni-bridge/src/storage.rs | 12 ++--- near/omni-bridge/src/tests/lib_test.rs | 18 ++++++-- near/omni-tests/src/fast_transfer.rs | 63 ++++++++++++++++++-------- 4 files changed, 67 insertions(+), 36 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index da6b6f06..f4b14e1e 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -631,10 +631,11 @@ impl Contract { recipient, U128(fast_transfer.amount.0 - fast_transfer.fee.fee.0), fast_transfer.msg, - ).then( + ) + .then( Self::ext(env::current_account_id()) .with_static_gas(FT_RESOLVE_TRANSFER_GAS) - .ft_resolve_transfer(fast_transfer.amount) + .ft_resolve_transfer(fast_transfer.amount), ) } @@ -1023,10 +1024,7 @@ impl Contract { } #[private] - pub fn ft_resolve_transfer( - &mut self, - amount: U128, - ) -> U128 { + pub fn ft_resolve_transfer(&mut self, amount: U128) -> U128 { match env::promise_result(0) { PromiseResult::Successful(_) => U128(0), PromiseResult::Failed => amount, diff --git a/near/omni-bridge/src/storage.rs b/near/omni-bridge/src/storage.rs index 6a656926..f8a3bdab 100644 --- a/near/omni-bridge/src/storage.rs +++ b/near/omni-bridge/src/storage.rs @@ -172,17 +172,13 @@ impl Contract { } pub fn required_balance_for_fast_transfer(&self) -> NearToken { - let key_len = borsh::to_vec(&[0u8; 32]) - .sdk_expect("ERR_BORSH") - .len() as u64; + let key_len = borsh::to_vec(&[0u8; 32]).sdk_expect("ERR_BORSH").len() as u64; let max_account_id: AccountId = "a".repeat(64).parse().sdk_expect("ERR_PARSE_ACCOUNT_ID"); - let value_len = borsh::to_vec(&max_account_id) - .sdk_expect("ERR_BORSH") - .len() as u64; + let value_len = borsh::to_vec(&max_account_id).sdk_expect("ERR_BORSH").len() as u64; - let storage_cost = - env::storage_byte_cost().saturating_mul((Self::get_basic_storage() + key_len + value_len).into()); + let storage_cost = env::storage_byte_cost() + .saturating_mul((Self::get_basic_storage() + key_len + value_len).into()); let ft_transfers_cost = NearToken::from_yoctonear(1); storage_cost.saturating_add(ft_transfers_cost) diff --git a/near/omni-bridge/src/tests/lib_test.rs b/near/omni-bridge/src/tests/lib_test.rs index f95acbc6..9f9dbf61 100644 --- a/near/omni-bridge/src/tests/lib_test.rs +++ b/near/omni-bridge/src/tests/lib_test.rs @@ -134,7 +134,11 @@ fn test_init_transfer_nonce_increment() { DEFAULT_FT_CONTRACT_ACCOUNT.to_string(), U128(100), None, - BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0)), + BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg( + DEFAULT_ETH_USER_ADDRESS.to_string(), + 0, + 0, + )), ); assert_eq!(contract.current_origin_nonce, DEFAULT_NONCE + 1); @@ -194,7 +198,11 @@ fn test_init_transfer_promise_result() { DEFAULT_FT_CONTRACT_ACCOUNT.to_string(), U128(DEFAULT_TRANSFER_AMOUNT), None, - BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0)), + BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg( + DEFAULT_ETH_USER_ADDRESS.to_string(), + 0, + 0, + )), ); let remaining = match promise { @@ -236,7 +244,11 @@ fn test_init_transfer_balance_updated() { DEFAULT_FT_CONTRACT_ACCOUNT.to_string(), U128(DEFAULT_TRANSFER_AMOUNT), Some(total_balance), - BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg(DEFAULT_ETH_USER_ADDRESS.to_string(), 0, 0)), + BridgeOnTransferMsg::InitTransfer(get_init_transfer_msg( + DEFAULT_ETH_USER_ADDRESS.to_string(), + 0, + 0, + )), ); let storage_balance = contract diff --git a/near/omni-tests/src/fast_transfer.rs b/near/omni-tests/src/fast_transfer.rs index e8ac5427..20cd3a89 100644 --- a/near/omni-tests/src/fast_transfer.rs +++ b/near/omni-tests/src/fast_transfer.rs @@ -320,20 +320,27 @@ mod tests { let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); let fast_transfer_msg = get_fast_transfer_msg(transfer_msg); - let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; - let contract_balance_before = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; assert_eq!(0, result.failures().len()); let recipient_balance: U128 = get_balance(&env.token_contract, &account_n(1)).await?; - let relayer_balance_after = get_balance(&env.token_contract, env.relayer_account.id()).await?; - let contract_balance_after = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; assert_eq!(transfer_amount, recipient_balance.0); assert_eq!(contract_balance_before, contract_balance_after); - assert_eq!(relayer_balance_before, U128(relayer_balance_after.0 + transfer_amount)); + assert_eq!( + relayer_balance_before, + U128(relayer_balance_after.0 + transfer_amount) + ); Ok(()) } @@ -348,8 +355,10 @@ mod tests { do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; - let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; - let contract_balance_before = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; assert_eq!(1, result.failures().len()); @@ -359,8 +368,10 @@ mod tests { format!("{:?}", err).contains("Fast transfer is already performed") })); - let relayer_balance_after = get_balance(&env.token_contract, env.relayer_account.id()).await?; - let contract_balance_after = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; assert_eq!(relayer_balance_before, relayer_balance_after); assert_eq!(contract_balance_before, contract_balance_after); @@ -426,8 +437,10 @@ mod tests { let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); - let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; - let contract_balance_before = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; @@ -459,11 +472,19 @@ mod tests { transfer_message.sender ); - let relayer_balance_after = get_balance(&env.token_contract, env.relayer_account.id()).await?; - let contract_balance_after = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; - assert_eq!(contract_balance_before, U128(contract_balance_after.0 - transfer_amount)); - assert_eq!(relayer_balance_before, U128(relayer_balance_after.0 + transfer_amount)); + assert_eq!( + contract_balance_before, + U128(contract_balance_after.0 - transfer_amount) + ); + assert_eq!( + relayer_balance_before, + U128(relayer_balance_after.0 + transfer_amount) + ); Ok(()) } @@ -478,8 +499,10 @@ mod tests { do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; - let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; - let contract_balance_before = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; @@ -490,8 +513,10 @@ mod tests { format!("{:?}", err).contains("Fast transfer is already performed") })); - let relayer_balance_after = get_balance(&env.token_contract, env.relayer_account.id()).await?; - let contract_balance_after = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; assert_eq!(relayer_balance_before, relayer_balance_after); assert_eq!(contract_balance_before, contract_balance_after); From 1f38b0469619ad975744e128cc91dc404cde9b6f Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:00:25 +0400 Subject: [PATCH 10/14] Code review fixes --- near/omni-bridge/src/lib.rs | 44 ++++++++++++++++---------- near/omni-bridge/src/storage.rs | 1 - near/omni-bridge/src/tests/lib_test.rs | 1 - near/omni-types/src/lib.rs | 3 +- near/omni-types/src/near_events.rs | 6 +++- near/omni-types/src/tests/lib_test.rs | 3 -- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index f4b14e1e..5c74acf5 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -56,7 +56,8 @@ const DEPLOY_TOKEN_GAS: Gas = Gas::from_tgas(50); const BURN_TOKEN_GAS: Gas = Gas::from_tgas(10); const MINT_TOKEN_GAS: Gas = Gas::from_tgas(5); const SET_METADATA_GAS: Gas = Gas::from_tgas(10); -const FT_RESOLVE_TRANSFER_GAS: Gas = Gas::from_tgas(3); +const RESOLVE_TRANSFER_GAS: Gas = Gas::from_tgas(3); +const FAST_TRANSFER_CALLBACK_GAS: Gas = Gas::from_tgas(40); const NO_DEPOSIT: NearToken = NearToken::from_near(0); const ONE_YOCTO: NearToken = NearToken::from_yoctonear(1); const SIGN_PATH: &str = "bridge-1"; @@ -430,7 +431,6 @@ impl Contract { sender: OmniAddress::Near(sender_id.clone()), msg: String::new(), destination_nonce, - parent_transfer_id: None, }; require!( transfer_message.fee.fee < transfer_message.amount, @@ -536,7 +536,6 @@ impl Contract { sender: init_transfer.sender, msg: init_transfer.msg, destination_nonce, - parent_transfer_id: None, }; if let OmniAddress::Near(recipient) = transfer_message.recipient.clone() { @@ -594,7 +593,7 @@ impl Contract { ) .then( Self::ext(env::current_account_id()) - .with_static_gas(VERIFY_PROOF_CALLBACK_GAS) + .with_static_gas(FAST_TRANSFER_CALLBACK_GAS) .fast_fin_transfer_to_near_callback(fast_transfer, sender_id), ), ) @@ -626,6 +625,14 @@ impl Contract { .saturating_add(ONE_YOCTO); self.update_storage_balance(relayer_id, required_balance, NearToken::from_yoctonear(0)); + env::log_str( + &OmniBridgeEvent::FastTransferEvent { + fast_transfer: fast_transfer.clone(), + new_transfer_id: None, + } + .to_log_string(), + ); + self.send_tokens( fast_transfer.token_id, recipient, @@ -634,8 +641,8 @@ impl Contract { ) .then( Self::ext(env::current_account_id()) - .with_static_gas(FT_RESOLVE_TRANSFER_GAS) - .ft_resolve_transfer(fast_transfer.amount), + .with_static_gas(RESOLVE_TRANSFER_GAS) + .resolve_transfer(fast_transfer.amount), ) } @@ -653,19 +660,27 @@ impl Contract { let transfer_message = TransferMessage { origin_nonce: self.current_origin_nonce, token: OmniAddress::Near(fast_transfer.token_id.clone()), - amount: fast_transfer.amount, - recipient: fast_transfer.recipient, - fee: fast_transfer.fee, + amount: fast_transfer.amount.clone(), + recipient: fast_transfer.recipient.clone(), + fee: fast_transfer.fee.clone(), sender: OmniAddress::Near(relayer_id.clone()), - msg: fast_transfer.msg, + msg: fast_transfer.msg.clone(), destination_nonce, - parent_transfer_id: Some(fast_transfer.transfer_id), }; + let new_transfer_id = transfer_message.get_transfer_id(); required_balance = self .add_transfer_message(transfer_message, relayer_id.clone()) .saturating_add(required_balance); + env::log_str( + &OmniBridgeEvent::FastTransferEvent { + fast_transfer: fast_transfer.clone(), + new_transfer_id: Some(new_transfer_id), + } + .to_log_string(), + ); + self.update_storage_balance(relayer_id, required_balance, NearToken::from_near(0)); } @@ -1024,11 +1039,8 @@ impl Contract { } #[private] - pub fn ft_resolve_transfer(&mut self, amount: U128) -> U128 { - match env::promise_result(0) { - PromiseResult::Successful(_) => U128(0), - PromiseResult::Failed => amount, - } + pub fn resolve_transfer(&mut self, _amount: U128) -> U128 { + U128(0) } } diff --git a/near/omni-bridge/src/storage.rs b/near/omni-bridge/src/storage.rs index f8a3bdab..9da50499 100644 --- a/near/omni-bridge/src/storage.rs +++ b/near/omni-bridge/src/storage.rs @@ -148,7 +148,6 @@ impl Contract { sender: OmniAddress::Near(max_account_id.clone()), msg: String::new(), destination_nonce: 0, - parent_transfer_id: None, }, owner: max_account_id, })) diff --git a/near/omni-bridge/src/tests/lib_test.rs b/near/omni-bridge/src/tests/lib_test.rs index 9f9dbf61..5a11d039 100644 --- a/near/omni-bridge/src/tests/lib_test.rs +++ b/near/omni-bridge/src/tests/lib_test.rs @@ -281,7 +281,6 @@ fn run_update_transfer_fee( sender: OmniAddress::Near(sender_id.clone().parse().unwrap()), msg: "".to_string(), destination_nonce: 1, - parent_transfer_id: None, }; contract.insert_raw_transfer( diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index d7a79446..04245a90 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -428,7 +428,6 @@ pub struct TransferMessage { pub sender: OmniAddress, pub msg: String, pub destination_nonce: Nonce, - pub parent_transfer_id: Option, } impl TransferMessage { @@ -505,7 +504,7 @@ pub struct BasicMetadata { #[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Eq, PartialEq)] pub struct FastTransferId(pub [u8; 32]); -#[derive(BorshDeserialize, BorshSerialize, Debug, Clone)] +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] pub struct FastTransfer { pub transfer_id: TransferId, pub token_id: AccountId, diff --git a/near/omni-types/src/near_events.rs b/near/omni-types/src/near_events.rs index e3dd1025..88954c0d 100644 --- a/near/omni-types/src/near_events.rs +++ b/near/omni-types/src/near_events.rs @@ -2,7 +2,7 @@ use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::serde_json::json; use crate::mpc_types::SignatureResponse; -use crate::{MetadataPayload, TransferMessage, TransferMessagePayload}; +use crate::{FastTransfer, MetadataPayload, TransferId, TransferMessage, TransferMessagePayload}; #[derive(Deserialize, Serialize, Clone, Debug)] pub enum OmniBridgeEvent { @@ -26,6 +26,10 @@ pub enum OmniBridgeEvent { ClaimFeeEvent { transfer_message: TransferMessage, }, + FastTransferEvent { + fast_transfer: FastTransfer, + new_transfer_id: Option, + }, } impl OmniBridgeEvent { diff --git a/near/omni-types/src/tests/lib_test.rs b/near/omni-types/src/tests/lib_test.rs index f53ba499..a43b287e 100644 --- a/near/omni-types/src/tests/lib_test.rs +++ b/near/omni-types/src/tests/lib_test.rs @@ -345,7 +345,6 @@ fn test_transfer_message_getters() { fee: Fee::default(), sender: OmniAddress::Eth(evm_addr.clone()), msg: "".to_string(), - parent_transfer_id: None, }, ChainKind::Eth, TransferId { @@ -364,7 +363,6 @@ fn test_transfer_message_getters() { fee: Fee::default(), sender: OmniAddress::Near("alice.near".parse().unwrap()), msg: "".to_string(), - parent_transfer_id: None, }, ChainKind::Near, TransferId { @@ -383,7 +381,6 @@ fn test_transfer_message_getters() { fee: Fee::default(), sender: OmniAddress::Sol("11111111111111111111111111111111".parse().unwrap()), msg: "".to_string(), - parent_transfer_id: None, }, ChainKind::Sol, TransferId { From a3d9a72c9160d15ba1dd715cd1cde3cd3ce84817 Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:33:22 +0400 Subject: [PATCH 11/14] Fail during fast transfer if transfer already finalised --- near/omni-bridge/src/lib.rs | 4 +++ near/omni-tests/src/fast_transfer.rs | 42 +++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index 5c74acf5..8178e92e 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -651,6 +651,10 @@ impl Contract { fast_transfer: FastTransfer, relayer_id: AccountId, ) { + if self.is_transfer_finalised(fast_transfer.transfer_id) { + env::panic_str("ERR_TRANSFER_ALREADY_FINALISED"); + } + let mut required_balance = self.add_fast_transfer(&fast_transfer, &relayer_id); let destination_nonce = diff --git a/near/omni-tests/src/fast_transfer.rs b/near/omni-tests/src/fast_transfer.rs index 20cd3a89..6bb2d29e 100644 --- a/near/omni-tests/src/fast_transfer.rs +++ b/near/omni-tests/src/fast_transfer.rs @@ -271,8 +271,15 @@ mod tests { .await? .json()?; - // let required_deposit_for_fin_transfer = NEP141_DEPOSIT - // .saturating_add(required_balance_for_fin_transfer); + let required_balance_for_init_transfer: NearToken = env + .bridge_contract + .view("required_balance_for_init_transfer") + .await? + .json()?; + + let attached_deposit = required_balance_for_init_transfer + .saturating_add(required_balance_for_fin_transfer) + .saturating_add(NEP141_DEPOSIT); let storage_deposit_action = StorageDepositAction { token_id: env.token_contract.id().clone(), @@ -288,7 +295,7 @@ mod tests { storage_deposit_actions: vec![storage_deposit_action], prover_args: borsh::to_vec(&ProverResult::InitTransfer(transfer_msg)).unwrap(), }) - .deposit(required_balance_for_fin_transfer) + .deposit(attached_deposit) .max_gas() .transact() .await? @@ -578,13 +585,40 @@ mod tests { let result = do_fin_transfer(&env, transfer_msg).await; assert!(result.is_err_and(|err| { - println!("err: {:?}", err); format!("{:?}", err).contains("The transfer is already finalised") })); Ok(()) } + #[tokio::test] + async fn test_fast_transfer_to_other_chain_already_finalised() -> anyhow::Result<()> { + let env = TestEnv::new(1_000_000).await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); + + do_fin_transfer(&env, transfer_msg).await?; + + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; + + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + + assert_eq!(relayer_balance_before, relayer_balance_after); + + assert_eq!(1, result.failures().len()); + let failure = result.failures()[0].clone().into_result(); + assert!(failure + .is_err_and(|err| { format!("{:?}", err).contains("ERR_TRANSFER_ALREADY_FINALISED") })); + + Ok(()) + } + fn get_transfer_msg_to_near(env: &TestEnv, amount: u128) -> InitTransferMessage { InitTransferMessage { origin_nonce: 0, From 88a978119074c7f4a4fc8cc18dd1f640b16eccc1 Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Thu, 19 Dec 2024 21:21:05 +0400 Subject: [PATCH 12/14] Do not burn tokens during failed fast transfer --- near/omni-bridge/src/lib.rs | 42 ++- near/omni-tests/src/fast_transfer.rs | 478 +++++++++++++++++++++++---- 2 files changed, 442 insertions(+), 78 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index 8178e92e..af07aa63 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -57,7 +57,7 @@ const BURN_TOKEN_GAS: Gas = Gas::from_tgas(10); const MINT_TOKEN_GAS: Gas = Gas::from_tgas(5); const SET_METADATA_GAS: Gas = Gas::from_tgas(10); const RESOLVE_TRANSFER_GAS: Gas = Gas::from_tgas(3); -const FAST_TRANSFER_CALLBACK_GAS: Gas = Gas::from_tgas(40); +const FAST_TRANSFER_CALLBACK_GAS: Gas = Gas::from_tgas(5); const NO_DEPOSIT: NearToken = NearToken::from_near(0); const ONE_YOCTO: NearToken = NearToken::from_yoctonear(1); const SIGN_PATH: &str = "bridge-1"; @@ -200,13 +200,23 @@ impl FungibleTokenReceiver for Contract { } }; - if self.deployed_tokens.contains(&token_id) { - ext_token::ext(token_id) - .with_static_gas(BURN_TOKEN_GAS) - .burn(amount); + if !self.deployed_tokens.contains(&token_id) { + return promise_or_value; } - promise_or_value + match promise_or_value { + PromiseOrValue::Promise(promise) => PromiseOrValue::Promise( + promise.then( + Self::ext(env::current_account_id()) + .with_static_gas(BURN_TOKEN_GAS) + .burn_tokens(token_id, amount), + ), + ), + PromiseOrValue::Value(_) => { + self.burn_tokens(token_id, amount); + promise_or_value + } + } } } @@ -593,7 +603,9 @@ impl Contract { ) .then( Self::ext(env::current_account_id()) - .with_static_gas(FAST_TRANSFER_CALLBACK_GAS) + .with_static_gas( + FAST_TRANSFER_CALLBACK_GAS.saturating_add(FT_TRANSFER_CALL_GAS), + ) .fast_fin_transfer_to_near_callback(fast_transfer, sender_id), ), ) @@ -1046,6 +1058,22 @@ impl Contract { pub fn resolve_transfer(&mut self, _amount: U128) -> U128 { U128(0) } + + #[private] + pub fn burn_tokens(&self, token: AccountId, amount: U128) -> Promise { + if env::promise_results_count() == 0 { + return ext_token::ext(token) + .with_static_gas(BURN_TOKEN_GAS) + .burn(amount); + } + + match env::promise_result(0) { + PromiseResult::Failed => env::panic_str("ERR_FAST_TRANSFER_FAILED"), + PromiseResult::Successful(_) => ext_token::ext(token) + .with_static_gas(BURN_TOKEN_GAS) + .burn(amount), + } + } } impl Contract { diff --git a/near/omni-tests/src/fast_transfer.rs b/near/omni-tests/src/fast_transfer.rs index 6bb2d29e..cd683734 100644 --- a/near/omni-tests/src/fast_transfer.rs +++ b/near/omni-tests/src/fast_transfer.rs @@ -1,5 +1,7 @@ #[cfg(test)] mod tests { + use std::str::FromStr; + use near_sdk::{ borsh, json_types::U128, @@ -13,14 +15,14 @@ mod tests { use omni_types::{ locker_args::{FinTransferArgs, StorageDepositAction}, prover_result::{InitTransferMessage, ProverResult}, - BridgeOnTransferMsg, ChainKind, FastFinTransferMsg, Fee, OmniAddress, TransferId, - TransferMessage, + BasicMetadata, BridgeOnTransferMsg, ChainKind, FastFinTransferMsg, Fee, OmniAddress, + TransferId, TransferMessage, }; use crate::helpers::tests::{ account_n, base_eoa_address, base_factory_address, eth_eoa_address, eth_factory_address, eth_token_address, get_bind_token_args, relayer_account_id, LOCKER_PATH, MOCK_PROVER_PATH, - MOCK_TOKEN_PATH, NEP141_DEPOSIT, + MOCK_TOKEN_PATH, NEP141_DEPOSIT, TOKEN_DEPLOYER_PATH, }; struct TestEnv { @@ -30,20 +32,17 @@ mod tests { } impl TestEnv { - async fn new(sender_balance_token: u128) -> anyhow::Result { + async fn new_with_native_token() -> anyhow::Result { + Self::new(false).await + } + + async fn new_with_bridged_token() -> anyhow::Result { + Self::new(true).await + } + + async fn new(is_bridged_token: bool) -> anyhow::Result { + let sender_balance_token = 1_000_000; let worker = near_workspaces::sandbox().await?; - // Deploy and initialize FT token - let token_contract = worker.dev_deploy(&std::fs::read(MOCK_TOKEN_PATH)?).await?; - token_contract - .call("new_default_meta") - .args_json(json!({ - "owner_id": token_contract.id(), - "total_supply": U128(u128::MAX) - })) - .max_gas() - .transact() - .await? - .into_result()?; let prover_contract = worker.dev_deploy(&std::fs::read(MOCK_PROVER_PATH)?).await?; // Deploy and initialize bridge @@ -61,78 +60,222 @@ mod tests { .await? .into_result()?; - // Register the bridge contract in the token contract - token_contract - .call("storage_deposit") + // Add ETH factory address to the bridge contract + let eth_factory_address = eth_factory_address(); + bridge_contract + .call("add_factory") .args_json(json!({ - "account_id": bridge_contract.id(), - "registration_only": true, + "address": eth_factory_address, })) - .deposit(NEP141_DEPOSIT) .max_gas() .transact() .await? .into_result()?; - // Create relayer account. (Default account in sandbox has 100 NEAR) - let relayer_account = worker - .create_tla(relayer_account_id(), worker.dev_generate().await.1) + let base_factory_address = base_factory_address(); + bridge_contract + .call("add_factory") + .args_json(json!({ + "address": base_factory_address, + })) + .max_gas() + .transact() + .await? + .into_result()?; + + let token_deployer = worker + .create_tla_and_deploy( + account_n(1), + worker.dev_generate().await.1, + &std::fs::read(TOKEN_DEPLOYER_PATH)?, + ) .await? .unwrap(); - // Register the relayer in the token contract - token_contract - .call("storage_deposit") + token_deployer + .call("new") .args_json(json!({ - "account_id": relayer_account.id(), - "registration_only": true, + "controller": bridge_contract.id(), + "dao": AccountId::from_str("dao.near").unwrap(), })) - .deposit(NEP141_DEPOSIT) .max_gas() .transact() .await? .into_result()?; - // Transfer initial tokens to the relayer account - token_contract - .call("ft_transfer") + bridge_contract + .call("add_token_deployer") .args_json(json!({ - "receiver_id": relayer_account.id(), - "amount": U128(sender_balance_token), - "memo": None::, + "chain": eth_factory_address.get_chain(), + "account_id": token_deployer.id(), })) - .deposit(NearToken::from_yoctonear(1)) .max_gas() .transact() .await? .into_result()?; - // Transfer initial tokens to the bridge contract (locked) - token_contract - .call("ft_transfer") + // Create relayer account. (Default account in sandbox has 100 NEAR) + let relayer_account = worker + .create_tla(relayer_account_id(), worker.dev_generate().await.1) + .await? + .unwrap(); + + let token_contract = if is_bridged_token { + let token_contract = Self::deploy_bridged_token(&worker, &bridge_contract).await?; + + // Mint to relayer account + Self::fake_finalize_transfer( + &bridge_contract, + &token_contract, + &relayer_account, + eth_factory_address, + U128(sender_balance_token), + ) + .await?; + + // Register the bridge in the token contract + token_contract + .call("storage_deposit") + .args_json(json!({ + "account_id": bridge_contract.id(), + "registration_only": true, + })) + .deposit(NEP141_DEPOSIT) + .max_gas() + .transact() + .await? + .into_result()?; + + token_contract + } else { + let token_contract = + Self::deploy_native_token(worker, &bridge_contract, eth_factory_address) + .await?; + + // Register and send tokens to the relayer account + token_contract + .call("storage_deposit") + .args_json(json!({ + "account_id": relayer_account.id(), + "registration_only": true, + })) + .deposit(NEP141_DEPOSIT) + .max_gas() + .transact() + .await? + .into_result()?; + + token_contract + .call("ft_transfer") + .args_json(json!({ + "receiver_id": relayer_account.id(), + "amount": U128(sender_balance_token), + "memo": None::, + })) + .deposit(NearToken::from_yoctonear(1)) + .max_gas() + .transact() + .await? + .into_result()?; + + // Register and send tokens to the bridge contract + token_contract + .call("storage_deposit") + .args_json(json!({ + "account_id": bridge_contract.id(), + "registration_only": true, + })) + .deposit(NEP141_DEPOSIT) + .max_gas() + .transact() + .await? + .into_result()?; + + token_contract + .call("ft_transfer") + .args_json(json!({ + "receiver_id": bridge_contract.id(), + "amount": U128(sender_balance_token), + "memo": None::, + })) + .deposit(NearToken::from_yoctonear(1)) + .max_gas() + .transact() + .await? + .into_result()?; + + token_contract + }; + + Ok(Self { + token_contract, + bridge_contract, + relayer_account, + }) + } + + async fn deploy_bridged_token( + worker: &near_workspaces::Worker, + bridge_contract: &near_workspaces::Contract, + ) -> anyhow::Result { + let init_token_address = OmniAddress::new_zero(ChainKind::Eth).unwrap(); + let token_metadata = BasicMetadata { + name: "ETH from Ethereum".to_string(), + symbol: "ETH".to_string(), + decimals: 18, + }; + + let required_storage: NearToken = bridge_contract + .view("required_balance_for_deploy_token") + .await? + .json()?; + + bridge_contract + .call("deploy_native_token") .args_json(json!({ - "receiver_id": bridge_contract.id(), - "amount": U128(sender_balance_token), + "chain_kind": init_token_address.get_chain(), + "name": token_metadata.name, + "symbol": token_metadata.symbol, + "decimals": token_metadata.decimals, })) - .deposit(NearToken::from_yoctonear(1)) + .deposit(required_storage) .max_gas() .transact() .await? .into_result()?; - // Add ETH factory address to the bridge contract - let eth_factory_address = eth_factory_address(); - bridge_contract - .call("add_factory") + let token_account_id: AccountId = bridge_contract + .view("get_token_id") .args_json(json!({ - "address": eth_factory_address, + "address": init_token_address + })) + .await? + .json()?; + + let token_contract = worker + .import_contract(&token_account_id, worker) + .transact() + .await?; + + Ok(token_contract) + } + + async fn deploy_native_token( + worker: near_workspaces::Worker, + bridge_contract: &near_workspaces::Contract, + eth_factory_address: OmniAddress, + ) -> Result { + let token_contract = worker.dev_deploy(&std::fs::read(MOCK_TOKEN_PATH)?).await?; + token_contract + .call("new_default_meta") + .args_json(json!({ + "owner_id": token_contract.id(), + "total_supply": U128(u128::MAX) })) .max_gas() .transact() .await? .into_result()?; - - // Bind the token to the bridge contract let required_deposit_for_bind_token = bridge_contract .view("required_balance_for_bind_token") .await? @@ -149,24 +292,55 @@ mod tests { .transact() .await? .into_result()?; + Ok(token_contract) + } - // Add base factory address to the bridge contract - let base_factory_address = base_factory_address(); + async fn fake_finalize_transfer( + bridge_contract: &near_workspaces::Contract, + token_contract: &near_workspaces::Contract, + recipient: &near_workspaces::Account, + emitter_address: OmniAddress, + amount: U128, + ) -> anyhow::Result<()> { + let storage_deposit_actions = vec![StorageDepositAction { + token_id: token_contract.id().clone(), + account_id: recipient.id().clone(), + storage_deposit_amount: Some(NEP141_DEPOSIT.as_yoctonear()), + }]; + let required_balance_for_fin_transfer: NearToken = bridge_contract + .view("required_balance_for_fin_transfer") + .await? + .json()?; + let required_deposit_for_fin_transfer = + NEP141_DEPOSIT.saturating_add(required_balance_for_fin_transfer); + + // Simulate finalization of transfer through locker bridge_contract - .call("add_factory") - .args_json(json!({ - "address": base_factory_address, - })) + .call("fin_transfer") + .args_borsh(FinTransferArgs { + chain_kind: ChainKind::Near, + storage_deposit_actions, + prover_args: borsh::to_vec(&ProverResult::InitTransfer(InitTransferMessage { + origin_nonce: 1, + token: OmniAddress::Near(token_contract.id().clone()), + recipient: OmniAddress::Near(recipient.id().clone()), + amount, + fee: Fee { + fee: U128(0), + native_fee: U128(0), + }, + sender: eth_eoa_address(), + msg: String::default(), + emitter_address, + }))?, + }) + .deposit(required_deposit_for_fin_transfer) .max_gas() .transact() .await? .into_result()?; - Ok(Self { - token_contract, - bridge_contract, - relayer_account, - }) + Ok(()) } } @@ -321,7 +495,7 @@ mod tests { #[tokio::test] async fn test_fast_transfer_to_near() -> anyhow::Result<()> { - let env = TestEnv::new(1_000_000).await?; + let env = TestEnv::new_with_native_token().await?; let transfer_amount = 100; let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); @@ -352,9 +526,81 @@ mod tests { Ok(()) } + #[tokio::test] + async fn test_fast_transfer_to_near_bridged_token() -> anyhow::Result<()> { + let env = TestEnv::new_with_bridged_token().await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg); + + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(U128(0), contract_balance_before); + + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; + + assert_eq!(0, result.failures().len()); + + let recipient_balance: U128 = get_balance(&env.token_contract, &account_n(1)).await?; + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(transfer_amount, recipient_balance.0); + assert_eq!(U128(0), contract_balance_after); + assert_eq!( + relayer_balance_before, + U128(relayer_balance_after.0 + transfer_amount) + ); + + Ok(()) + } + + #[tokio::test] + async fn test_fast_transfer_to_near_bad_storage_deposit() -> anyhow::Result<()> { + let env = TestEnv::new_with_bridged_token().await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); + let mut fast_transfer_msg = get_fast_transfer_msg(transfer_msg); + fast_transfer_msg.storage_deposit_amount = + Some(NEP141_DEPOSIT.saturating_mul(100).as_yoctonear()); + + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(U128(0), contract_balance_before); + + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; + + assert_eq!(1, result.failures().len()); + let failure = result.failures()[0].clone().into_result(); + assert!(failure + .is_err_and(|err| { format!("{:?}", err).contains("Not enough storage deposited") })); + + let recipient_balance: U128 = get_balance(&env.token_contract, &account_n(1)).await?; + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(0, recipient_balance.0); + assert_eq!(U128(0), contract_balance_after); + assert_eq!(relayer_balance_before, relayer_balance_after); + + Ok(()) + } + #[tokio::test] async fn test_fast_transfer_to_near_twice() -> anyhow::Result<()> { - let env = TestEnv::new(1_000_000).await?; + let env = TestEnv::new_with_native_token().await?; let transfer_amount = 100; let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); @@ -362,10 +608,15 @@ mod tests { do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + let OmniAddress::Near(recipient) = fast_transfer_msg.recipient.clone() else { + panic!("Recipient is not a Near address"); + }; + let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; let contract_balance_before = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let recipient_balance_before = get_balance(&env.token_contract, &recipient).await?; let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; assert_eq!(1, result.failures().len()); @@ -379,16 +630,61 @@ mod tests { get_balance(&env.token_contract, env.relayer_account.id()).await?; let contract_balance_after = get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let recipient_balance_after = get_balance(&env.token_contract, &recipient).await?; assert_eq!(relayer_balance_before, relayer_balance_after); assert_eq!(contract_balance_before, contract_balance_after); + assert_eq!(recipient_balance_before, recipient_balance_after); + + Ok(()) + } + + #[tokio::test] + async fn test_fast_transfer_to_near_twice_bridged_token() -> anyhow::Result<()> { + let env = TestEnv::new_with_bridged_token().await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg); + + do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + + let OmniAddress::Near(recipient) = fast_transfer_msg.recipient.clone() else { + panic!("Recipient is not a Near address"); + }; + + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let recipient_balance_before = get_balance(&env.token_contract, &recipient).await?; + + assert_eq!(U128(0), contract_balance_before); + + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; + assert!(result.failures().len() > 0); + + let failure = result.failures()[0].clone().into_result(); + assert!(failure.is_err_and(|err| { + format!("{:?}", err).contains("Fast transfer is already performed") + })); + + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; + let recipient_balance_after = get_balance(&env.token_contract, &recipient).await?; + + assert_eq!(relayer_balance_before, relayer_balance_after); + assert_eq!(U128(0), contract_balance_after); + assert_eq!(recipient_balance_before, recipient_balance_after); Ok(()) } #[tokio::test] async fn test_fast_transfer_to_near_finalisation() -> anyhow::Result<()> { - let env = TestEnv::new(1_000_000).await?; + let env = TestEnv::new_with_native_token().await?; let transfer_amount = 100; let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); @@ -417,7 +713,7 @@ mod tests { #[tokio::test] async fn test_fast_transfer_to_near_finalisation_twice() -> anyhow::Result<()> { - let env = TestEnv::new(1_000_000).await?; + let env = TestEnv::new_with_native_token().await?; let transfer_amount = 100; let transfer_msg = get_transfer_msg_to_near(&env, transfer_amount); @@ -438,7 +734,7 @@ mod tests { #[tokio::test] async fn test_fast_transfer_to_other_chain() -> anyhow::Result<()> { - let env = TestEnv::new(1_000_000).await?; + let env = TestEnv::new_with_native_token().await?; let transfer_amount = 100; let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); @@ -496,9 +792,42 @@ mod tests { Ok(()) } + #[tokio::test] + async fn test_fast_transfer_to_other_chain_bridged_token() -> anyhow::Result<()> { + let env = TestEnv::new_with_bridged_token().await?; + + let transfer_amount = 100; + let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); + let fast_transfer_msg = get_fast_transfer_msg(transfer_msg.clone()); + + let relayer_balance_before = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(U128(0), contract_balance_before); + + let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg.clone()).await?; + + assert_eq!(0, result.failures().len()); + + let relayer_balance_after = + get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(U128(0), contract_balance_after); + assert_eq!( + relayer_balance_before, + U128(relayer_balance_after.0 + transfer_amount) + ); + + Ok(()) + } + #[tokio::test] async fn test_fast_transfer_to_other_chain_twice() -> anyhow::Result<()> { - let env = TestEnv::new(1_000_000).await?; + let env = TestEnv::new_with_native_token().await?; let transfer_amount = 100; let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); @@ -533,7 +862,7 @@ mod tests { #[tokio::test] async fn test_fast_transfer_to_other_chain_finalisation() -> anyhow::Result<()> { - let env = TestEnv::new(1_000_000).await?; + let env = TestEnv::new_with_native_token().await?; let transfer_amount = 100; let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); @@ -573,7 +902,7 @@ mod tests { #[tokio::test] async fn test_fast_transfer_to_other_chain_finalisation_twice() -> anyhow::Result<()> { - let env = TestEnv::new(1_000_000).await?; + let env = TestEnv::new_with_native_token().await?; let transfer_amount = 100; let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); @@ -593,7 +922,7 @@ mod tests { #[tokio::test] async fn test_fast_transfer_to_other_chain_already_finalised() -> anyhow::Result<()> { - let env = TestEnv::new(1_000_000).await?; + let env = TestEnv::new_with_bridged_token().await?; let transfer_amount = 100; let transfer_msg = get_transfer_msg_to_other_chain(&env, transfer_amount); @@ -603,13 +932,20 @@ mod tests { let relayer_balance_before = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_before = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; + + assert_eq!(U128(0), contract_balance_before); let result = do_fast_transfer(&env, transfer_amount, fast_transfer_msg).await?; let relayer_balance_after = get_balance(&env.token_contract, env.relayer_account.id()).await?; + let contract_balance_after = + get_balance(&env.token_contract, env.bridge_contract.id()).await?; assert_eq!(relayer_balance_before, relayer_balance_after); + assert_eq!(U128(0), contract_balance_after); assert_eq!(1, result.failures().len()); let failure = result.failures()[0].clone().into_result(); From cb763fc55547cb68019b0e41ff3c9423ea94f57e Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Wed, 29 Jan 2025 19:07:29 +0530 Subject: [PATCH 13/14] Mark fast transfer as completed --- near/omni-bridge/src/lib.rs | 67 ++++++++++++++-------------- near/omni-tests/src/fast_transfer.rs | 2 + near/omni-tests/src/helpers.rs | 6 --- near/omni-types/src/lib.rs | 6 +++ 4 files changed, 42 insertions(+), 39 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index 40b7de30..7f9ff37a 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -25,8 +25,8 @@ use omni_types::prover_args::VerifyProofArgs; use omni_types::prover_result::ProverResult; use omni_types::{ BasicMetadata, BridgeOnTransferMsg, ChainKind, FastFinTransferMsg, FastTransfer, - FastTransferId, Fee, InitTransferMsg, MetadataPayload, Nonce, OmniAddress, PayloadType, - SignRequest, TransferId, TransferMessage, TransferMessagePayload, UpdateFee, + FastTransferId, FastTransferStatus, Fee, InitTransferMsg, MetadataPayload, Nonce, OmniAddress, + PayloadType, SignRequest, TransferId, TransferMessage, TransferMessagePayload, UpdateFee, }; use storage::{Decimals, TransferMessageStorage, TransferMessageStorageValue, NEP141_DEPOSIT}; @@ -168,7 +168,7 @@ pub struct Contract { pub factories: LookupMap, pub pending_transfers: LookupMap, pub finalised_transfers: LookupSet, - pub fast_transfers: LookupMap, // value is relayer address that performed the transfer + pub fast_transfers: LookupMap, pub token_id_to_address: LookupMap<(ChainKind, AccountId), OmniAddress>, pub token_address_to_id: LookupMap, pub token_decimals: LookupMap, @@ -660,7 +660,7 @@ impl Contract { }; let required_balance = self - .add_fast_transfer(&fast_transfer, &relayer_id) + .add_fast_transfer(&fast_transfer, relayer_id.clone()) .saturating_add(ONE_YOCTO); self.update_storage_balance(relayer_id, required_balance, NearToken::from_yoctonear(0)); @@ -694,7 +694,7 @@ impl Contract { env::panic_str("ERR_TRANSFER_ALREADY_FINALISED"); } - let mut required_balance = self.add_fast_transfer(&fast_transfer, &relayer_id); + let mut required_balance = self.add_fast_transfer(&fast_transfer, relayer_id.clone()); let destination_nonce = self.get_next_destination_nonce(fast_transfer.recipient.get_chain()); @@ -1143,16 +1143,20 @@ impl Contract { // If fast transfer happened, change recipient to the relayer that executed fast transfer let fast_transfer = FastTransfer::from_transfer(transfer_message.clone(), token.clone()); let (recipient, is_fast_transfer) = match self.fast_transfers.get(&fast_transfer.id()) { - Some(relayer) => { + Some(status) => { require!( - predecessor_account_id == *relayer, + predecessor_account_id == *status.relayer, "ERR_FAST_TRANSFER_PERFORMED_BY_ANOTHER_RELAYER" ); - (relayer, true) + (status.relayer, true) } None => (recipient, false), }; + if is_fast_transfer { + self.complete_fast_transfer(&fast_transfer.id()); + } + let mut storage_deposit_action_index: usize = 0; require!( Self::check_storage_balance_result((storage_deposit_action_index + 1) as u64) @@ -1252,12 +1256,12 @@ impl Contract { let fast_transfer = FastTransfer::from_transfer(transfer_message.clone(), token.clone()); let recipient = match self.fast_transfers.get(&fast_transfer.id()) { - Some(relayer) => { + Some(status) => { require!( - predecessor_account_id == *relayer, + predecessor_account_id == *status.relayer, "ERR_FAST_TRANSFER_PERFORMED_BY_ANOTHER_RELAYER" ); - Some(relayer) + Some(status.relayer) } None => None, }; @@ -1266,10 +1270,11 @@ impl Contract { if let Some(relayer) = recipient { self.send_tokens( token, - relayer, + relayer.clone(), U128(transfer_message.amount.0 - transfer_message.fee.fee.0), String::new(), ); + self.complete_fast_transfer(&fast_transfer.id()); } else { required_balance = self .add_transfer_message(transfer_message.clone(), predecessor_account_id.clone()) @@ -1300,10 +1305,7 @@ impl Contract { .with_static_gas(WNEAR_WITHDRAW_GAS) .with_attached_deposit(ONE_YOCTO) .near_withdraw(amount) - .then( - Promise::new(recipient) - .transfer(NearToken::from_yoctonear(amount.0)), - ) + .then(Promise::new(recipient).transfer(NearToken::from_yoctonear(amount.0))) } else if is_deployed_token { let deposit = if msg.is_empty() { NO_DEPOSIT @@ -1313,11 +1315,7 @@ impl Contract { ext_token::ext(token.clone()) .with_attached_deposit(deposit) .with_static_gas(MINT_TOKEN_GAS.saturating_add(FT_TRANSFER_CALL_GAS)) - .mint( - recipient, - amount, - (!msg.is_empty()).then(|| msg.clone()), - ) + .mint(recipient, amount, (!msg.is_empty()).then(|| msg.clone())) } else if msg.is_empty() { ext_token::ext(token.clone()) .with_attached_deposit(ONE_YOCTO) @@ -1327,12 +1325,7 @@ impl Contract { ext_token::ext(token.clone()) .with_attached_deposit(ONE_YOCTO) .with_static_gas(FT_TRANSFER_CALL_GAS) - .ft_transfer_call( - recipient, - amount, - None, - msg.clone(), - ) + .ft_transfer_call(recipient, amount, None, msg.clone()) } } @@ -1438,15 +1431,17 @@ impl Contract { .saturating_mul((env::storage_usage().saturating_sub(storage_usage)).into()) } - fn add_fast_transfer( - &mut self, - fast_transfer: &FastTransfer, - relayer_id: &AccountId, - ) -> NearToken { + fn add_fast_transfer(&mut self, fast_transfer: &FastTransfer, relayer: AccountId) -> NearToken { let storage_usage = env::storage_usage(); require!( self.fast_transfers - .insert(&fast_transfer.id(), relayer_id) + .insert( + &fast_transfer.id(), + &FastTransferStatus { + relayer, + completed: false, + }, + ) .is_none(), "Fast transfer is already performed" ); @@ -1454,6 +1449,12 @@ impl Contract { .saturating_mul((env::storage_usage().saturating_sub(storage_usage)).into()) } + fn complete_fast_transfer(&mut self, fast_transfer_id: &FastTransferId) { + let mut fast_transfer = self.fast_transfers.get(fast_transfer_id).unwrap(); + fast_transfer.completed = true; + self.fast_transfers.insert(fast_transfer_id, &fast_transfer); + } + fn update_storage_balance( &mut self, account_id: AccountId, diff --git a/near/omni-tests/src/fast_transfer.rs b/near/omni-tests/src/fast_transfer.rs index cd683734..94ad22dc 100644 --- a/near/omni-tests/src/fast_transfer.rs +++ b/near/omni-tests/src/fast_transfer.rs @@ -286,6 +286,8 @@ mod tests { &token_contract.id(), ð_token_address(), ð_factory_address, + 18, + 18, )) .deposit(required_deposit_for_bind_token) .max_gas() diff --git a/near/omni-tests/src/helpers.rs b/near/omni-tests/src/helpers.rs index e3e7bb83..f4942f6a 100644 --- a/near/omni-tests/src/helpers.rs +++ b/near/omni-tests/src/helpers.rs @@ -80,12 +80,6 @@ pub mod tests { .unwrap() } - pub fn base_factory_address() -> OmniAddress { - "base:0x252e87862A3A720287E7fd527cE6e8d0738427A2" - .parse() - .unwrap() - } - pub fn get_claim_fee_args_near( origin_chain: ChainKind, destination_chain: ChainKind, diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index 0c03cb51..2a1af2a5 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -552,3 +552,9 @@ impl FastTransfer { } } } + +#[derive(BorshDeserialize, BorshSerialize, Debug, Clone)] +pub struct FastTransferStatus { + pub completed: bool, + pub relayer: AccountId, +} From 333be178f37933fafefc5302f264c1b39706f4ca Mon Sep 17 00:00:00 2001 From: kiseln <3428059+kiseln@users.noreply.github.com> Date: Wed, 29 Jan 2025 22:11:34 +0530 Subject: [PATCH 14/14] Implement restriction on claiming fee before fast transfer is finalised --- near/omni-bridge/src/lib.rs | 56 ++++++++++++++++++-------- near/omni-bridge/src/storage.rs | 13 +++++- near/omni-bridge/src/tests/lib_test.rs | 1 + near/omni-tests/src/fast_transfer.rs | 39 +++++++++--------- near/omni-types/src/lib.rs | 5 ++- near/omni-types/src/tests/lib_test.rs | 3 ++ 6 files changed, 78 insertions(+), 39 deletions(-) diff --git a/near/omni-bridge/src/lib.rs b/near/omni-bridge/src/lib.rs index 7f9ff37a..dae43bad 100644 --- a/near/omni-bridge/src/lib.rs +++ b/near/omni-bridge/src/lib.rs @@ -459,6 +459,7 @@ impl Contract { sender: OmniAddress::Near(sender_id.clone()), msg: String::new(), destination_nonce, + origin_transfer_id: None, }; require!( transfer_message.fee.fee < transfer_message.amount, @@ -573,6 +574,7 @@ impl Contract { sender: init_transfer.sender, msg: init_transfer.msg, destination_nonce, + origin_transfer_id: None, }; if let OmniAddress::Near(recipient) = transfer_message.recipient.clone() { @@ -709,6 +711,7 @@ impl Contract { sender: OmniAddress::Near(relayer_id.clone()), msg: fast_transfer.msg.clone(), destination_nonce, + origin_transfer_id: Some(fast_transfer.transfer_id), }; let new_transfer_id = transfer_message.get_transfer_id(); @@ -770,23 +773,23 @@ impl Contract { ); let message = self.remove_transfer_message(fin_transfer.transfer_id); - let token_address = self - .get_token_address( - message.get_destination_chain(), - self.get_token_id(&message.token), - ) - .unwrap_or_else(|| env::panic_str("ERR_FAILED_TO_GET_TOKEN_ADDRESS")); - let denormalized_amount = Self::denormalize_amount( - fin_transfer.amount.0, - self.token_decimals - .get(&token_address) - .sdk_expect("ERR_TOKEN_DECIMALS_NOT_FOUND"), - ); - let fee = message.amount.0 - denormalized_amount; + // Need to make sure fast transfer is finalised because it means transfer parameters are correct. Otherwise, fee can be set as anything. + if let Some(origin_transfer_id) = message.origin_transfer_id { + let mut fast_transfer = FastTransfer::from_transfer(message.clone(), self.get_token_id(&message.token)); + fast_transfer.transfer_id = origin_transfer_id; + require!( + self.is_fast_transfer_finalised(fast_transfer.id()), + "ERR_FAST_TRANSFER_NOT_FINALISED" + ); + } if message.fee.native_fee.0 != 0 { - if message.get_origin_chain() == ChainKind::Near { + let origin_chain = match message.origin_transfer_id { + Some(origin_transfer_id) => origin_transfer_id.origin_chain, + None => message.get_origin_chain(), + }; + if origin_chain == ChainKind::Near { Promise::new(fin_transfer.fee_recipient.clone()) .transfer(NearToken::from_yoctonear(message.fee.native_fee.0)); } else { @@ -803,11 +806,26 @@ impl Contract { let token = self.get_token_id(&message.token); env::log_str( &OmniBridgeEvent::ClaimFeeEvent { - transfer_message: message, + transfer_message: message.clone(), } .to_log_string(), ); + let token_address = self + .get_token_address( + message.get_destination_chain(), + token.clone(), + ) + .unwrap_or_else(|| env::panic_str("ERR_FAILED_TO_GET_TOKEN_ADDRESS")); + + let denormalized_amount = Self::denormalize_amount( + fin_transfer.amount.0, + self.token_decimals + .get(&token_address) + .sdk_expect("ERR_TOKEN_DECIMALS_NOT_FOUND"), + ); + let fee = message.amount.0 - denormalized_amount; + if fee > 0 { if self.deployed_tokens.contains(&token) { PromiseOrValue::Promise(ext_token::ext(token).with_static_gas(MINT_TOKEN_GAS).mint( @@ -1028,6 +1046,10 @@ impl Contract { self.finalised_transfers.contains(&transfer_id) } + pub fn is_fast_transfer_finalised(&self, fast_transfer_id: FastTransferId) -> bool { + self.fast_transfers.get(&fast_transfer_id).map(|status| status.finalised).unwrap_or(false) + } + #[access_control_any(roles(Role::DAO))] pub fn add_factory(&mut self, address: OmniAddress) { self.factories.insert(&(&address).into(), &address); @@ -1439,7 +1461,7 @@ impl Contract { &fast_transfer.id(), &FastTransferStatus { relayer, - completed: false, + finalised: false, }, ) .is_none(), @@ -1451,7 +1473,7 @@ impl Contract { fn complete_fast_transfer(&mut self, fast_transfer_id: &FastTransferId) { let mut fast_transfer = self.fast_transfers.get(fast_transfer_id).unwrap(); - fast_transfer.completed = true; + fast_transfer.finalised = true; self.fast_transfers.insert(fast_transfer_id, &fast_transfer); } diff --git a/near/omni-bridge/src/storage.rs b/near/omni-bridge/src/storage.rs index d2621fed..e1008183 100644 --- a/near/omni-bridge/src/storage.rs +++ b/near/omni-bridge/src/storage.rs @@ -1,7 +1,7 @@ use near_contract_standards::storage_management::{StorageBalance, StorageBalanceBounds}; use near_sdk::{assert_one_yocto, borsh}; use near_sdk::{env, near_bindgen, AccountId, NearToken}; -use omni_types::TransferId; +use omni_types::{FastTransferStatus, TransferId}; use crate::{ require, BorshDeserialize, BorshSerialize, ChainKind, Contract, ContractExt, Deserialize, Fee, @@ -155,6 +155,10 @@ impl Contract { sender: OmniAddress::Near(max_account_id.clone()), msg: String::new(), destination_nonce: 0, + origin_transfer_id: Some(TransferId { + origin_chain: ChainKind::Near, + origin_nonce: 0, + }), }, owner: max_account_id, })) @@ -181,7 +185,12 @@ impl Contract { let key_len = borsh::to_vec(&[0u8; 32]).sdk_expect("ERR_BORSH").len() as u64; let max_account_id: AccountId = "a".repeat(64).parse().sdk_expect("ERR_PARSE_ACCOUNT_ID"); - let value_len = borsh::to_vec(&max_account_id).sdk_expect("ERR_BORSH").len() as u64; + let value_len = borsh::to_vec(&FastTransferStatus { + relayer: max_account_id, + finalised: false, + }) + .sdk_expect("ERR_BORSH") + .len() as u64; let storage_cost = env::storage_byte_cost() .saturating_mul((Self::get_basic_storage() + key_len + value_len).into()); diff --git a/near/omni-bridge/src/tests/lib_test.rs b/near/omni-bridge/src/tests/lib_test.rs index 69aca8da..83b71e0e 100644 --- a/near/omni-bridge/src/tests/lib_test.rs +++ b/near/omni-bridge/src/tests/lib_test.rs @@ -282,6 +282,7 @@ fn run_update_transfer_fee( sender: OmniAddress::Near(sender_id.clone().parse().unwrap()), msg: "".to_string(), destination_nonce: 1, + origin_transfer_id: None, }; contract.insert_raw_transfer( diff --git a/near/omni-tests/src/fast_transfer.rs b/near/omni-tests/src/fast_transfer.rs index 94ad22dc..06621e68 100644 --- a/near/omni-tests/src/fast_transfer.rs +++ b/near/omni-tests/src/fast_transfer.rs @@ -20,13 +20,12 @@ mod tests { }; use crate::helpers::tests::{ - account_n, base_eoa_address, base_factory_address, eth_eoa_address, eth_factory_address, - eth_token_address, get_bind_token_args, relayer_account_id, LOCKER_PATH, MOCK_PROVER_PATH, - MOCK_TOKEN_PATH, NEP141_DEPOSIT, TOKEN_DEPLOYER_PATH, + account_n, base_eoa_address, base_factory_address, eth_eoa_address, eth_factory_address, eth_token_address, get_bind_token_args, relayer_account_id, LOCKER_PATH, MOCK_PROVER_PATH, MOCK_TOKEN_PATH, NEP141_DEPOSIT, TOKEN_DEPLOYER_PATH }; struct TestEnv { token_contract: near_workspaces::Contract, + eth_token_address: OmniAddress, bridge_contract: near_workspaces::Contract, relayer_account: near_workspaces::Account, } @@ -120,13 +119,14 @@ mod tests { .await? .unwrap(); - let token_contract = if is_bridged_token { - let token_contract = Self::deploy_bridged_token(&worker, &bridge_contract).await?; + let (token_contract, eth_token_address) = if is_bridged_token { + let (token_contract, eth_token_address) = Self::deploy_bridged_token(&worker, &bridge_contract).await?; // Mint to relayer account Self::fake_finalize_transfer( &bridge_contract, &token_contract, + eth_token_address.clone(), &relayer_account, eth_factory_address, U128(sender_balance_token), @@ -146,9 +146,9 @@ mod tests { .await? .into_result()?; - token_contract + (token_contract, eth_token_address) } else { - let token_contract = + let (token_contract, eth_token_address) = Self::deploy_native_token(worker, &bridge_contract, eth_factory_address) .await?; @@ -204,11 +204,12 @@ mod tests { .await? .into_result()?; - token_contract + (token_contract, eth_token_address) }; Ok(Self { token_contract, + eth_token_address, bridge_contract, relayer_account, }) @@ -217,7 +218,7 @@ mod tests { async fn deploy_bridged_token( worker: &near_workspaces::Worker, bridge_contract: &near_workspaces::Contract, - ) -> anyhow::Result { + ) -> anyhow::Result<(near_workspaces::Contract, OmniAddress)> { let init_token_address = OmniAddress::new_zero(ChainKind::Eth).unwrap(); let token_metadata = BasicMetadata { name: "ETH from Ethereum".to_string(), @@ -257,14 +258,14 @@ mod tests { .transact() .await?; - Ok(token_contract) + Ok((token_contract, init_token_address)) } async fn deploy_native_token( worker: near_workspaces::Worker, bridge_contract: &near_workspaces::Contract, eth_factory_address: OmniAddress, - ) -> Result { + ) -> Result<(near_workspaces::Contract, OmniAddress), anyhow::Error> { let token_contract = worker.dev_deploy(&std::fs::read(MOCK_TOKEN_PATH)?).await?; token_contract .call("new_default_meta") @@ -280,6 +281,7 @@ mod tests { .view("required_balance_for_bind_token") .await? .json()?; + bridge_contract .call("bind_token") .args_borsh(get_bind_token_args( @@ -294,12 +296,13 @@ mod tests { .transact() .await? .into_result()?; - Ok(token_contract) + Ok((token_contract, eth_token_address())) } async fn fake_finalize_transfer( bridge_contract: &near_workspaces::Contract, token_contract: &near_workspaces::Contract, + eth_token_address: OmniAddress, recipient: &near_workspaces::Account, emitter_address: OmniAddress, amount: U128, @@ -324,7 +327,7 @@ mod tests { storage_deposit_actions, prover_args: borsh::to_vec(&ProverResult::InitTransfer(InitTransferMessage { origin_nonce: 1, - token: OmniAddress::Near(token_contract.id().clone()), + token: eth_token_address, recipient: OmniAddress::Near(recipient.id().clone()), amount, fee: Fee { @@ -960,7 +963,7 @@ mod tests { fn get_transfer_msg_to_near(env: &TestEnv, amount: u128) -> InitTransferMessage { InitTransferMessage { origin_nonce: 0, - token: OmniAddress::Near(env.token_contract.id().clone()), + token: env.eth_token_address.clone(), recipient: OmniAddress::Near(account_n(1)), amount: U128(amount), fee: Fee { @@ -976,16 +979,16 @@ mod tests { fn get_transfer_msg_to_other_chain(env: &TestEnv, amount: u128) -> InitTransferMessage { InitTransferMessage { origin_nonce: 0, - token: OmniAddress::Near(env.token_contract.id().clone()), - recipient: eth_eoa_address(), + token: env.eth_token_address.clone(), + recipient: base_eoa_address(), amount: U128(amount), fee: Fee { fee: U128(0), native_fee: U128(0), }, - sender: base_eoa_address(), + sender: eth_eoa_address(), msg: String::default(), - emitter_address: base_factory_address(), + emitter_address: eth_factory_address(), } } diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index 2a1af2a5..ba0391e1 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -447,6 +447,7 @@ pub struct TransferMessage { pub sender: OmniAddress, pub msg: String, pub destination_nonce: Nonce, + pub origin_transfer_id: Option, } impl TransferMessage { @@ -520,7 +521,7 @@ pub struct BasicMetadata { pub decimals: u8, } -#[derive(BorshDeserialize, BorshSerialize, Debug, Clone, Eq, PartialEq)] +#[derive(BorshDeserialize, BorshSerialize, Serialize, Debug, Clone, Eq, PartialEq)] pub struct FastTransferId(pub [u8; 32]); #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] @@ -555,6 +556,6 @@ impl FastTransfer { #[derive(BorshDeserialize, BorshSerialize, Debug, Clone)] pub struct FastTransferStatus { - pub completed: bool, + pub finalised: bool, pub relayer: AccountId, } diff --git a/near/omni-types/src/tests/lib_test.rs b/near/omni-types/src/tests/lib_test.rs index 271e137a..d50ae668 100644 --- a/near/omni-types/src/tests/lib_test.rs +++ b/near/omni-types/src/tests/lib_test.rs @@ -345,6 +345,7 @@ fn test_transfer_message_getters() { fee: Fee::default(), sender: OmniAddress::Eth(evm_addr.clone()), msg: "".to_string(), + origin_transfer_id: None, }, ChainKind::Eth, TransferId { @@ -363,6 +364,7 @@ fn test_transfer_message_getters() { fee: Fee::default(), sender: OmniAddress::Near("alice.near".parse().unwrap()), msg: "".to_string(), + origin_transfer_id: None, }, ChainKind::Near, TransferId { @@ -381,6 +383,7 @@ fn test_transfer_message_getters() { fee: Fee::default(), sender: OmniAddress::Sol("11111111111111111111111111111111".parse().unwrap()), msg: "".to_string(), + origin_transfer_id: None, }, ChainKind::Sol, TransferId {