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, -}