From 1b88b4ee0df12e2f3007d4f2285e45b654c73b20 Mon Sep 17 00:00:00 2001 From: Karim Date: Mon, 30 Sep 2024 20:24:08 +0100 Subject: [PATCH] feat: pay fee in native token (#45) * feat: pay fee in native token * Fix fee amount * EVM: fix fee logic * Don't pass amount to `sign_claim_native_fee` * Fix typo * Fix typo * Add fee validation * Improve `claim_fee_callback` --- .../contracts/BridgeTokenFactory.sol | 56 ++++- .../contracts/BridgeTokenFactoryWormhole.sol | 7 +- near/nep141-locker/src/lib.rs | 217 +++++++++++++++--- near/nep141-locker/src/storage.rs | 2 +- .../src/parsed_vaa.rs | 29 +-- near/omni-types/src/evm/events.rs | 8 +- near/omni-types/src/lib.rs | 43 +++- near/omni-types/src/locker_args.rs | 4 +- near/omni-types/src/near_events.rs | 6 +- 9 files changed, 307 insertions(+), 65 deletions(-) diff --git a/evm/bridge-token-factory/contracts/BridgeTokenFactory.sol b/evm/bridge-token-factory/contracts/BridgeTokenFactory.sol index eaa4b188..999b4248 100644 --- a/evm/bridge-token-factory/contracts/BridgeTokenFactory.sol +++ b/evm/bridge-token-factory/contracts/BridgeTokenFactory.sol @@ -39,6 +39,7 @@ contract BridgeTokenFactory is uint8 public omniBridgeChainId; mapping(uint128 => bool) public completedTransfers; + mapping(uint128 => bool) public claimedFee; uint128 public initTransferNonce; bytes32 public constant PAUSABLE_ADMIN_ROLE = keccak256("PAUSABLE_ADMIN_ROLE"); @@ -61,6 +62,12 @@ contract BridgeTokenFactory is uint8 decimals; } + struct ClaimFeePayload { + uint128[] nonces; + uint128 amount; + address recipient; + } + event InitTransfer( address indexed sender, address indexed tokenAddress, @@ -68,6 +75,7 @@ contract BridgeTokenFactory is string token, uint128 amount, uint128 fee, + uint128 nativeFee, string recipient ); @@ -98,6 +106,7 @@ contract BridgeTokenFactory is error InvalidSignature(); error NonceAlreadyUsed(uint256 nonce); + error InvalidFee(); function initialize( address tokenImplementationAddress_, @@ -238,19 +247,56 @@ contract BridgeTokenFactory is string calldata token, uint128 amount, uint128 fee, + uint128 nativeFee, string calldata recipient ) payable external whenNotPaused(PAUSED_INIT_TRANSFER) { initTransferNonce += 1; _checkWhitelistedToken(token, msg.sender); require(_isBridgeToken[_nearToEthToken[token]], "ERR_NOT_BRIDGE_TOKEN"); + if (fee >= amount) { + revert InvalidFee(); + } address tokenAddress = _nearToEthToken[token]; - BridgeToken(tokenAddress).burn(msg.sender, amount + fee); + BridgeToken(tokenAddress).burn(msg.sender, amount); + + uint256 extensionValue = msg.value - nativeFee; + initTransferExtension(initTransferNonce, token, amount, fee, nativeFee, recipient, msg.sender, extensionValue); + + emit InitTransfer(msg.sender, tokenAddress, initTransferNonce, token , amount, fee, nativeFee, recipient); + } + + function claimNativeFee(bytes calldata signatureData, ClaimFeePayload memory payload) external { + bytes memory borshEncodedNonces = Borsh.encodeUint32(uint32(payload.nonces.length)); + + for (uint i = 0; i < payload.nonces.length; ++i) { + uint128 nonce = payload.nonces[i]; + if (claimedFee[nonce]) { + revert NonceAlreadyUsed(nonce); + } - initTransferExtension(initTransferNonce, token, amount, fee, recipient, msg.sender); + claimedFee[nonce] = true; + borshEncodedNonces = bytes.concat( + borshEncodedNonces, + Borsh.encodeUint128(nonce) + ); + } + + bytes memory borshEncoded = bytes.concat( + borshEncodedNonces, + Borsh.encodeUint128(payload.amount), + bytes1(omniBridgeChainId), + Borsh.encodeAddress(payload.recipient) + ); + bytes32 hashed = keccak256(borshEncoded); + + if (ECDSA.recover(hashed, signatureData) != nearBridgeDerivedAddress) { + revert InvalidSignature(); + } - emit InitTransfer(msg.sender, tokenAddress, initTransferNonce, token , amount, fee, recipient); + (bool success,) = payload.recipient.call{value: payload.amount}(""); + require(success, "Failed to send Ether."); } function initTransferExtension( @@ -258,8 +304,10 @@ contract BridgeTokenFactory is string calldata token, uint128 amount, uint128 fee, + uint128 nativeFee, string calldata recipient, - address sender + address sender, + uint256 value ) internal virtual {} function pause(uint flags) external onlyRole(DEFAULT_ADMIN_ROLE) { diff --git a/evm/bridge-token-factory/contracts/BridgeTokenFactoryWormhole.sol b/evm/bridge-token-factory/contracts/BridgeTokenFactoryWormhole.sol index 0a54278a..ab7fa502 100644 --- a/evm/bridge-token-factory/contracts/BridgeTokenFactoryWormhole.sol +++ b/evm/bridge-token-factory/contracts/BridgeTokenFactoryWormhole.sol @@ -62,10 +62,12 @@ contract BridgeTokenFactoryWormhole is BridgeTokenFactory { string calldata token, uint128 amount, uint128 fee, + uint128 nativeFee, string calldata recipient, - address sender + address sender, + uint256 value ) internal override { - _wormhole.publishMessage{value: msg.value}( + _wormhole.publishMessage{value: value}( wormholeNonce, abi.encode( MessageType.InitTransfer, @@ -73,6 +75,7 @@ contract BridgeTokenFactoryWormhole is BridgeTokenFactory { token, amount, fee, + nativeFee, recipient, sender ), diff --git a/near/nep141-locker/src/lib.rs b/near/nep141-locker/src/lib.rs index 9a977e27..c68a3e0a 100644 --- a/near/nep141-locker/src/lib.rs +++ b/near/nep141-locker/src/lib.rs @@ -8,7 +8,7 @@ use near_contract_standards::fungible_token::metadata::FungibleTokenMetadata; use near_contract_standards::fungible_token::receiver::FungibleTokenReceiver; use near_contract_standards::storage_management::StorageBalance; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; -use near_sdk::collections::{LookupMap, LookupSet}; +use near_sdk::collections::LookupMap; use near_sdk::json_types::U128; use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::{ @@ -21,8 +21,9 @@ use omni_types::near_events::Nep141LockerEvent; use omni_types::prover_args::VerifyProofArgs; use omni_types::prover_result::ProverResult; use omni_types::{ - ChainKind, InitTransferMsg, MetadataPayload, NearRecipient, Nonce, OmniAddress, SignRequest, - TransferMessage, TransferMessagePayload, UpdateFee, + ChainKind, ClaimNativeFeePayload, Fee, InitTransferMsg, MetadataPayload, NativeFee, + NearRecipient, Nonce, OmniAddress, SignRequest, TransferId, TransferMessage, + TransferMessagePayload, UpdateFee, }; use storage::{TransferMessageStorage, TransferMessageStorageValue}; @@ -34,6 +35,7 @@ const LOG_METADATA_CALLBCAK_GAS: Gas = Gas::from_tgas(260); const MPC_SIGNING_GAS: Gas = Gas::from_tgas(250); const SIGN_TRANSFER_CALLBACK_GAS: Gas = Gas::from_tgas(5); const SIGN_LOG_METADATA_CALLBACK_GAS: Gas = Gas::from_tgas(5); +const SIGN_CLAIM_NATIVE_FEE_CALLBACK_GAS: Gas = Gas::from_tgas(5); const VERIFY_POOF_GAS: Gas = Gas::from_tgas(50); const CLAIM_FEE_CALLBACK_GAS: Gas = Gas::from_tgas(50); const BIND_TOKEN_CALLBACK_GAS: Gas = Gas::from_tgas(25); @@ -120,7 +122,7 @@ pub struct Contract { pub prover_account: AccountId, pub factories: LookupMap, pub pending_transfers: LookupMap, - pub finalised_transfers: LookupSet<(ChainKind, Nonce)>, + pub finalised_transfers: LookupMap>, pub tokens_to_address_mapping: LookupMap<(ChainKind, AccountId), OmniAddress>, pub mpc_signer: AccountId, pub current_nonce: Nonce, @@ -144,16 +146,25 @@ impl FungibleTokenReceiver for Contract { token: env::predecessor_account_id(), amount, recipient: parsed_msg.recipient, - fee: parsed_msg.fee, + fee: Fee { + fee: parsed_msg.fee, + native_fee: parsed_msg.native_token_fee, + }, sender: OmniAddress::Near(sender_id.to_string()), }; + require!( + transfer_message.fee.fee < transfer_message.amount, + "ERR_INVALID_FEE" + ); - // TODO: add native token as fee attachment - let required_storage_balance = self.add_transfer_message( + let mut required_storage_balance = self.add_transfer_message( self.current_nonce, 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, @@ -173,7 +184,7 @@ impl Contract { prover_account, factories: LookupMap::new(StorageKey::Factories), pending_transfers: LookupMap::new(StorageKey::PendingTransfers), - finalised_transfers: LookupSet::new(StorageKey::FinalisedTransfers), + finalised_transfers: LookupMap::new(StorageKey::FinalisedTransfers), tokens_to_address_mapping: LookupMap::new(StorageKey::TokensMapping), mpc_signer, current_nonce: nonce.0, @@ -248,6 +259,7 @@ impl Contract { } } + #[payable] pub fn update_transfer_fee(&mut self, nonce: U128, fee: UpdateFee) { match fee { UpdateFee::Fee(fee) => { @@ -259,6 +271,23 @@ impl Contract { "Only sender can update fee" ); + let current_fee = transfer.message.fee; + require!( + fee.fee >= current_fee.fee && fee.fee < transfer.message.amount, + "ERR_INVALID_FEE" + ); + + let diff_native_fee = current_fee + .native_fee + .0 + .checked_sub(fee.native_fee.0) + .sdk_expect("ERR_LOWER_FEE"); + + require!( + NearToken::from_yoctonear(diff_native_fee) == env::attached_deposit(), + "ERR_INVALID_ATTACHED_DEPOSIT" + ); + transfer.message.fee = fee; self.insert_raw_transfer(nonce.0, transfer.message.clone(), transfer.owner); @@ -273,22 +302,77 @@ impl Contract { } } + #[payable] + pub fn sign_claim_native_fee(&mut self, nonces: Vec, recipient: OmniAddress) -> Promise { + let chain_kind = recipient.get_chain(); + let mut amount: u128 = 0_u128; + for nonce in &nonces { + let native_fee = self + .finalised_transfers + .get(&(chain_kind, nonce.0)) + .flatten() + .sdk_expect("ERR_NATIVE_FEE_NOT_EXISIT"); + + require!(native_fee.recipient == recipient, "ERR_WRONG_RECIPIENT"); + amount += native_fee.amount.0; + } + + let claim_payload = ClaimNativeFeePayload { + nonces, + amount: U128(amount), + recipient, + }; + let payload = + near_sdk::env::keccak256_array(&borsh::to_vec(&claim_payload).sdk_expect("ERR_BORSH")); + + ext_signer::ext(self.mpc_signer.clone()) + .with_static_gas(MPC_SIGNING_GAS) + .with_attached_deposit(env::attached_deposit()) + .sign(SignRequest { + payload, + path: SIGN_PATH.to_owned(), + key_version: 0, + }) + .then( + Self::ext(env::current_account_id()) + .with_static_gas(SIGN_CLAIM_NATIVE_FEE_CALLBACK_GAS) + .sign_claim_native_fee_callback(claim_payload), + ) + } + + #[private] + pub fn sign_claim_native_fee_callback( + &mut self, + #[callback_result] call_result: Result, + #[serializer(borsh)] claim_payload: ClaimNativeFeePayload, + ) { + if let Ok(signature) = call_result { + env::log_str( + &Nep141LockerEvent::SignClaimNativeFeeEvent { + signature, + claim_payload, + } + .to_log_string(), + ); + } + } + #[payable] pub fn sign_transfer( &mut self, nonce: U128, fee_recipient: Option, - fee: Option, + fee: Option, ) -> Promise { let transfer_message = self.get_transfer_message(nonce); - if let Some(fee) = fee { - require!(transfer_message.fee == fee, "Invalid fee"); + if let Some(fee) = &fee { + require!(&transfer_message.fee == fee, "Invalid fee"); } let transfer_payload = TransferMessagePayload { nonce, token: transfer_message.token, - amount: U128(transfer_message.amount.0 - transfer_message.fee.0), + amount: U128(transfer_message.amount.0 - transfer_message.fee.fee.0), recipient: transfer_message.recipient, fee_recipient, }; @@ -315,11 +399,11 @@ impl Contract { &mut self, #[callback_result] call_result: Result, #[serializer(borsh)] message_payload: TransferMessagePayload, - #[serializer(borsh)] fee: U128, + #[serializer(borsh)] fee: Fee, ) { if let Ok(signature) = call_result { let nonce = message_payload.nonce; - if fee.0 == 0 { + if fee.is_zero() { self.remove_transfer_message(nonce.0); } @@ -357,7 +441,11 @@ impl Contract { Self::ext(env::current_account_id()) .with_attached_deposit(attached_deposit) .with_static_gas(CLAIM_FEE_CALLBACK_GAS) - .fin_transfer_callback(args.storage_deposit_args, env::predecessor_account_id()), + .fin_transfer_callback( + args.storage_deposit_args, + env::predecessor_account_id(), + args.native_fee_recipient, + ), ) } @@ -367,6 +455,7 @@ impl Contract { &mut self, #[serializer(borsh)] storage_deposit_args: StorageDepositArgs, #[serializer(borsh)] predecessor_account_id: AccountId, + #[serializer(borsh)] native_fee_recipient: OmniAddress, ) -> PromiseOrValue { let Ok(ProverResult::InitTransfer(init_transfer)) = Self::decode_prover_result(0) else { env::panic_str("Invalid proof message") @@ -379,12 +468,17 @@ impl Contract { ); let transfer_message = init_transfer.transfer; - let mut required_balance = self.add_fin_transfer( - transfer_message.get_origin_chain(), - transfer_message.origin_nonce.0, - ); + let mut required_balance; if let OmniAddress::Near(recipient) = &transfer_message.recipient { + required_balance = self.add_fin_transfer( + &transfer_message.get_transfer_id(), + &Some(NativeFee { + amount: transfer_message.fee.native_fee, + recipient: native_fee_recipient, + }), + ); + let recipient: NearRecipient = recipient.parse().sdk_expect("Failed to parse recipient"); @@ -398,7 +492,7 @@ impl Contract { "STORAGE_ERR: The transfer recipient is omitted" ); - let amount_to_transfer = U128(transfer_message.amount.0 - transfer_message.fee.0); + let amount_to_transfer = U128(transfer_message.amount.0 - transfer_message.fee.fee.0); let mut promise = match recipient.message { Some(message) => ext_token::ext(transfer_message.token.clone()) .with_static_gas(FT_TRANSFER_CALL_GAS) @@ -410,7 +504,7 @@ impl Contract { .ft_transfer(recipient.target, amount_to_transfer, None), }; - if transfer_message.fee.0 > 0 { + if transfer_message.fee.fee.0 > 0 { require!( Self::check_storage_balance_result(2) && storage_deposit_args.accounts[1].0 == predecessor_account_id, @@ -420,7 +514,11 @@ impl Contract { ext_token::ext(transfer_message.token.clone()) .with_static_gas(FT_TRANSFER_GAS) .with_attached_deposit(ONE_YOCTO) - .ft_transfer(predecessor_account_id.clone(), transfer_message.fee, None), + .ft_transfer( + predecessor_account_id.clone(), + transfer_message.fee.fee, + None, + ), ); } @@ -440,6 +538,7 @@ impl Contract { promise.into() } else { + required_balance = self.add_fin_transfer(&transfer_message.get_transfer_id(), &None); self.current_nonce += 1; required_balance = self .add_transfer_message( @@ -467,7 +566,8 @@ impl Contract { } } - pub fn claim_fee(&self, #[serializer(borsh)] args: ClaimFeeArgs) -> Promise { + #[payable] + pub fn claim_fee(&mut self, #[serializer(borsh)] args: ClaimFeeArgs) -> Promise { ext_prover::ext(self.prover_account.clone()) .with_static_gas(VERIFY_POOF_GAS) .with_attached_deposit(NO_DEPOSIT) @@ -479,13 +579,16 @@ impl Contract { Self::ext(env::current_account_id()) .with_attached_deposit(env::attached_deposit()) .with_static_gas(CLAIM_FEE_CALLBACK_GAS) - .claim_fee_callback(), + .claim_fee_callback(args.native_fee_recipient, env::predecessor_account_id()), ) } #[private] + #[payable] pub fn claim_fee_callback( &mut self, + #[serializer(borsh)] native_fee_recipient: OmniAddress, + #[serializer(borsh)] predecessor_account_id: AccountId, #[callback_result] #[serializer(borsh)] call_result: Result, @@ -493,16 +596,44 @@ impl Contract { let Ok(ProverResult::FinTransfer(fin_transfer)) = call_result else { env::panic_str("Invalid proof message") }; + require!( + fin_transfer.fee_recipient == predecessor_account_id, + "ERR_ONLY_FEE_RECIPIENT_CAN_CLAIM" + ); require!( self.factories .get(&fin_transfer.emitter_address.get_chain()) == Some(fin_transfer.emitter_address), - "Unknown factory" + "ERR_UNKNOWN_FACTORY" ); let message = self.remove_transfer_message(fin_transfer.nonce.0); let fee = message.amount.0 - fin_transfer.amount.0; + if message.fee.native_fee.0 != 0 { + if message.get_origin_chain() == ChainKind::Near { + let OmniAddress::Near(recipient) = native_fee_recipient else { + env::panic_str("ERR_WRONG_CHAIN_KIND") + }; + Promise::new(recipient.parse().sdk_expect("ERR_PARSE_FEE_RECIPIENT")) + .transfer(NearToken::from_yoctonear(message.fee.native_fee.0)); + } else { + let required_balance = self.update_fin_transfer( + &message.get_transfer_id(), + &Some(NativeFee { + amount: message.fee.native_fee, + recipient: native_fee_recipient, + }), + ); + + self.update_storage_balance( + predecessor_account_id, + required_balance, + env::attached_deposit(), + ); + } + } + ext_token::ext(message.token) .with_static_gas(LOG_METADATA_GAS) .ft_transfer(fin_transfer.fee_recipient, U128(fee), None) @@ -661,24 +792,40 @@ impl Contract { let refund = env::storage_byte_cost().saturating_mul((storage_usage - env::storage_usage()).into()); - let mut storage = self - .accounts_balances - .get(&transfer.owner) - .sdk_expect("ERR_ACCOUNT_NOT_REGISTERED"); - - storage.available = storage.available.saturating_add(refund); - self.accounts_balances.insert(&transfer.owner, &storage); + if let Some(mut storage) = self.accounts_balances.get(&transfer.owner) { + storage.available = storage.available.saturating_add(refund); + self.accounts_balances.insert(&transfer.owner, &storage); + } transfer.message } - fn add_fin_transfer(&mut self, chain_kind: ChainKind, nonce: u128) -> NearToken { + fn add_fin_transfer( + &mut self, + transfer_id: &TransferId, + native_token_fee: &Option, + ) -> NearToken { let storage_usage = env::storage_usage(); require!( - self.finalised_transfers.insert(&(chain_kind, nonce)), + self.finalised_transfers + .insert(transfer_id, native_token_fee) + .is_none(), "The transfer is already finalised" ); - env::storage_byte_cost().saturating_mul((env::storage_usage() - storage_usage).into()) + env::storage_byte_cost() + .saturating_mul((env::storage_usage().saturating_sub(storage_usage)).into()) + } + + fn update_fin_transfer( + &mut self, + transfer_id: &TransferId, + native_token_fee: &Option, + ) -> NearToken { + let storage_usage = env::storage_usage(); + self.finalised_transfers + .insert(transfer_id, native_token_fee); + env::storage_byte_cost() + .saturating_mul((env::storage_usage().saturating_sub(storage_usage)).into()) } fn update_storage_balance( diff --git a/near/nep141-locker/src/storage.rs b/near/nep141-locker/src/storage.rs index 8aa5a829..6415be5d 100644 --- a/near/nep141-locker/src/storage.rs +++ b/near/nep141-locker/src/storage.rs @@ -140,7 +140,7 @@ impl Contract { token: max_account_id.clone(), amount: U128(0), recipient, - fee: U128(0), + fee: Fee::default(), sender, }, owner: max_account_id, diff --git a/near/omni-prover/wormhole-omni-prover-proxy/src/parsed_vaa.rs b/near/omni-prover/wormhole-omni-prover-proxy/src/parsed_vaa.rs index b4b75782..75b8159e 100644 --- a/near/omni-prover/wormhole-omni-prover-proxy/src/parsed_vaa.rs +++ b/near/omni-prover/wormhole-omni-prover-proxy/src/parsed_vaa.rs @@ -6,7 +6,7 @@ use { near_sdk::{bs58, env}, omni_types::{ prover_result::{DeployTokenMessage, FinTransferMessage, InitTransferMessage}, - stringify, EvmAddress, OmniAddress, TransferMessage, H160, + stringify, EvmAddress, Fee, OmniAddress, TransferMessage, H160, }, }; @@ -116,6 +116,7 @@ sol! { string token; uint128 amount; uint128 fee; + uint128 nativeFee; string recipient; address sender; } @@ -144,15 +145,15 @@ impl TryInto for ParsedVAA { transfer: TransferMessage { token: transfer.token.parse().map_err(stringify)?, amount: transfer.amount.into(), - fee: transfer.fee.into(), + fee: Fee { + fee: transfer.fee.into(), + native_fee: transfer.nativeFee.into(), + }, recipient: transfer.recipient.parse().map_err(stringify)?, origin_nonce: transfer.nonce.into(), - sender: to_omni_address(self.emitter_chain, &transfer.sender.0.0), + sender: to_omni_address(self.emitter_chain, &transfer.sender.0 .0), }, - emitter_address: to_omni_address( - self.emitter_chain, - &self.emitter_address, - ), + emitter_address: to_omni_address(self.emitter_chain, &self.emitter_address), }) } } @@ -168,10 +169,7 @@ impl TryInto for ParsedVAA { nonce: transfer.nonce.into(), fee_recipient: transfer.recipient.parse().map_err(stringify)?, amount: transfer.amount.into(), - emitter_address: to_omni_address( - self.emitter_chain, - &self.emitter_address, - ), + emitter_address: to_omni_address(self.emitter_chain, &self.emitter_address), }) } } @@ -185,11 +183,8 @@ impl TryInto for ParsedVAA { Ok(DeployTokenMessage { token: transfer.token.parse().map_err(stringify)?, - token_address: to_omni_address(self.emitter_chain, &transfer.tokenAddress.0.0), - emitter_address: to_omni_address( - self.emitter_chain, - &self.emitter_address, - ), + token_address: to_omni_address(self.emitter_chain, &transfer.tokenAddress.0 .0), + emitter_address: to_omni_address(self.emitter_chain, &self.emitter_address), }) } } @@ -208,5 +203,5 @@ fn to_evm_address(address: &[u8]) -> EvmAddress { match address.try_into() { Ok(bytes) => H160(bytes), Err(_) => env::panic_str("Invalid EVM address"), - } + } } diff --git a/near/omni-types/src/evm/events.rs b/near/omni-types/src/evm/events.rs index ec52737c..dc39b7e9 100644 --- a/near/omni-types/src/evm/events.rs +++ b/near/omni-types/src/evm/events.rs @@ -4,7 +4,7 @@ use alloy_sol_types::{sol, SolEvent}; use crate::{ prover_result::{DeployTokenMessage, FinTransferMessage, InitTransferMessage}, - stringify, ChainKind, OmniAddress, TransferMessage, H160, + stringify, ChainKind, Fee, OmniAddress, TransferMessage, H160, }; const ERR_INVALIDE_SIGNATURE_HASH: &str = "ERR_INVALIDE_SIGNATURE_HASH"; @@ -17,6 +17,7 @@ sol! { string token, uint128 amount, uint128 fee, + uint128 nativeTokenFee, string recipient ); @@ -89,7 +90,10 @@ impl TryFromLog> for InitTransferMessage { token: event.data.token.parse().map_err(stringify)?, amount: near_sdk::json_types::U128(event.data.amount), recipient: event.data.recipient.parse().map_err(stringify)?, - fee: near_sdk::json_types::U128(event.data.fee), + fee: Fee { + fee: near_sdk::json_types::U128(event.data.fee), + native_fee: near_sdk::json_types::U128(event.data.nativeTokenFee), + }, sender: OmniAddress::from_evm_address(chain_kind, H160(event.data.sender.into()))?, }, }) diff --git a/near/omni-types/src/lib.rs b/near/omni-types/src/lib.rs index 7ec79a68..6382fb85 100644 --- a/near/omni-types/src/lib.rs +++ b/near/omni-types/src/lib.rs @@ -273,15 +273,43 @@ impl fmt::Display for NearRecipient { pub struct InitTransferMsg { pub recipient: OmniAddress, pub fee: U128, + pub native_token_fee: U128, } +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] +pub struct FeeRecipient { + pub recipient: AccountId, + pub native_fee_recipient: OmniAddress, +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] +pub struct NativeFee { + pub amount: U128, + pub recipient: OmniAddress, +} + +#[derive( + BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone, PartialEq, Default, +)] +pub struct Fee { + pub fee: U128, + pub native_fee: U128, +} + +impl Fee { + pub fn is_zero(&self) -> bool { + self.fee.0 == 0 && self.native_fee.0 == 0 + } +} + +pub type TransferId = (ChainKind, Nonce); #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] pub struct TransferMessage { pub origin_nonce: U128, pub token: AccountId, pub amount: U128, pub recipient: OmniAddress, - pub fee: U128, + pub fee: Fee, pub sender: OmniAddress, } @@ -289,6 +317,10 @@ impl TransferMessage { pub fn get_origin_chain(&self) -> ChainKind { self.sender.get_chain() } + + pub fn get_transfer_id(&self) -> TransferId { + (self.get_origin_chain(), self.origin_nonce.0) + } } #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] @@ -300,6 +332,13 @@ pub struct TransferMessagePayload { pub fee_recipient: Option, } +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] +pub struct ClaimNativeFeePayload { + pub nonces: Vec, + pub amount: U128, + pub recipient: OmniAddress, +} + #[derive(Deserialize, Serialize, Clone)] #[serde(crate = "near_sdk::serde")] pub struct SignRequest { @@ -310,7 +349,7 @@ pub struct SignRequest { #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug, Clone)] pub enum UpdateFee { - Fee(U128), + Fee(Fee), Proof(Vec), } diff --git a/near/omni-types/src/locker_args.rs b/near/omni-types/src/locker_args.rs index 6d3988d9..6c02a516 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, OmniAddress}; use near_sdk::{ borsh::{self, BorshDeserialize, BorshSerialize}, AccountId, @@ -13,6 +13,7 @@ pub struct StorageDepositArgs { #[derive(BorshDeserialize, BorshSerialize, Clone)] pub struct FinTransferArgs { pub chain_kind: ChainKind, + pub native_fee_recipient: OmniAddress, pub storage_deposit_args: StorageDepositArgs, pub prover_args: Vec, } @@ -21,6 +22,7 @@ pub struct FinTransferArgs { pub struct ClaimFeeArgs { pub chain_kind: ChainKind, pub prover_args: Vec, + pub native_fee_recipient: OmniAddress, } #[derive(BorshDeserialize, BorshSerialize, Clone)] diff --git a/near/omni-types/src/near_events.rs b/near/omni-types/src/near_events.rs index aaf45ae5..643a5355 100644 --- a/near/omni-types/src/near_events.rs +++ b/near/omni-types/src/near_events.rs @@ -3,7 +3,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::{ClaimNativeFeePayload, MetadataPayload, TransferMessage, TransferMessagePayload}; #[derive(Deserialize, Serialize, Clone, Debug)] pub enum Nep141LockerEvent { @@ -25,6 +25,10 @@ pub enum Nep141LockerEvent { signature: SignatureResponse, metadata_payload: MetadataPayload, }, + SignClaimNativeFeeEvent { + signature: SignatureResponse, + claim_payload: ClaimNativeFeePayload, + }, } impl Nep141LockerEvent {