diff --git a/engine-tests-connector/src/connector.rs b/engine-tests-connector/src/connector.rs index 7eb80cd9a..7411180f5 100644 --- a/engine-tests-connector/src/connector.rs +++ b/engine-tests-connector/src/connector.rs @@ -267,6 +267,64 @@ async fn test_ft_transfer_call_eth() -> anyhow::Result<()> { Ok(()) } +#[tokio::test] +async fn test_ft_transfer_call_without_fee() -> anyhow::Result<()> { + let contract = TestContract::new().await?; + contract.call_deposit_eth_to_near().await?; + + let user_acc = contract + .create_sub_account(DEPOSITED_RECIPIENT_NAME) + .await?; + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT, + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + 0, + ); + + let transfer_amount: U128 = 50.into(); + let message = RECIPIENT_ETH_ADDRESS; + let memo: Option = None; + let res = user_acc + .call(contract.engine_contract.id(), "ft_transfer_call") + .args_json(json!({ + "receiver_id": contract.engine_contract.id(), + "amount": transfer_amount, + "memo": memo, + "msg": message, + })) + .gas(DEFAULT_GAS) + .deposit(ONE_YOCTO) + .transact() + .await?; + assert!(res.is_success()); + + assert_eq!( + contract.get_eth_on_near_balance(user_acc.id()).await?.0, + DEPOSITED_AMOUNT - transfer_amount.0, + ); + assert_eq!( + contract + .get_eth_on_near_balance(contract.engine_contract.id()) + .await? + .0, + transfer_amount.0, + ); + assert_eq!( + contract + .get_eth_balance(&validate_eth_address(RECIPIENT_ETH_ADDRESS),) + .await?, + transfer_amount.0, + ); + assert_eq!(contract.total_supply().await?, DEPOSITED_AMOUNT); + Ok(()) +} + #[tokio::test] async fn test_ft_transfer_call_without_message() -> anyhow::Result<()> { let contract = TestContract::new().await?; @@ -438,8 +496,7 @@ async fn test_deposit_with_0x_prefix() -> anyhow::Result<()> { let message = [CONTRACT_ACC, ":", "0x", &recipient_address_encoded].concat(); let fee: Fee = Fee::new(NEP141Wei::new(0)); let token_message_data = - TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) - .unwrap(); + TokenMessageData::parse_event_message_and_prepare_token_message_data(&message).unwrap(); let deposit_event = DepositedEvent { eth_custodian_address, diff --git a/engine-tests/src/tests/erc20_connector.rs b/engine-tests/src/tests/erc20_connector.rs index 67547f51a..0baf800d8 100644 --- a/engine-tests/src/tests/erc20_connector.rs +++ b/engine-tests/src/tests/erc20_connector.rs @@ -1020,8 +1020,7 @@ pub mod workspace { let message = recipient_id.to_string(); let fee: Fee = Fee::new(NEP141Wei::new(0)); let token_message_data = - TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) - .unwrap(); + TokenMessageData::parse_event_message_and_prepare_token_message_data(&message).unwrap(); let deposit_event = DepositedEvent { eth_custodian_address, diff --git a/engine-tests/src/tests/standalone/sync.rs b/engine-tests/src/tests/standalone/sync.rs index 1a14a1633..7bc735164 100644 --- a/engine-tests/src/tests/standalone/sync.rs +++ b/engine-tests/src/tests/standalone/sync.rs @@ -472,8 +472,7 @@ fn mock_proof(recipient_address: Address, deposit_amount: Wei) -> aurora_engine: let fee = Fee::new(NEP141Wei::new(0)); let message = ["aurora", ":", recipient_address.encode().as_str()].concat(); let token_message_data: TokenMessageData = - TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) - .unwrap(); + TokenMessageData::parse_event_message_and_prepare_token_message_data(&message).unwrap(); let deposit_event = DepositedEvent { eth_custodian_address, diff --git a/engine-tests/src/utils/standalone/mocks/mod.rs b/engine-tests/src/utils/standalone/mocks/mod.rs index fd998fb39..1ee409089 100644 --- a/engine-tests/src/utils/standalone/mocks/mod.rs +++ b/engine-tests/src/utils/standalone/mocks/mod.rs @@ -3,8 +3,6 @@ use aurora_engine::engine; use aurora_engine::engine::Engine; #[cfg(not(feature = "ext-connector"))] use aurora_engine::parameters::InitCallArgs; -#[cfg(not(feature = "ext-connector"))] -use aurora_engine_modexp::ModExpAlgorithm; use aurora_engine_sdk::env::{Env, DEFAULT_PREPAID_GAS}; use aurora_engine_sdk::io::IO; #[cfg(not(feature = "ext-connector"))] @@ -114,19 +112,13 @@ pub fn mint_evm_account( }; #[cfg(not(feature = "ext-connector"))] - deposit(io, &engine, &env.current_account_id(), address, balance); + deposit(io, &env.current_account_id(), address, balance); engine.apply(std::iter::once(state_change), std::iter::empty(), false); } #[cfg(not(feature = "ext-connector"))] -fn deposit( - mut io: I, - engine: &Engine, - aurora_account_id: &AccountId, - address: Address, - balance: Wei, -) { +fn deposit(mut io: I, aurora_account_id: &AccountId, address: Address, balance: Wei) { const DEFAULT_GAS: u64 = 300_000_000_000_000; let deposit_args = aurora_engine_types::parameters::connector::FinishDepositCallArgs { new_owner_id: aurora_account_id.clone(), @@ -165,7 +157,7 @@ fn deposit( hex::encode(address.as_bytes()) ), }; - connector.ft_on_transfer(engine, &transfer_args).unwrap(); + connector.ft_on_transfer(&transfer_args).unwrap(); } #[cfg(not(feature = "ext-connector"))] diff --git a/engine/src/contract_methods/connector/deposit_event.rs b/engine/src/contract_methods/connector/deposit_event.rs index 324504657..2a77f00f3 100644 --- a/engine/src/contract_methods/connector/deposit_event.rs +++ b/engine/src/contract_methods/connector/deposit_event.rs @@ -18,9 +18,16 @@ pub type EventParams = Vec; #[derive(BorshSerialize, BorshDeserialize)] #[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Eq))] pub struct FtTransferMessageData { - pub relayer: AccountId, pub recipient: Address, - pub fee: Fee, + #[deprecated] + pub fee: Option, +} + +#[derive(BorshSerialize, BorshDeserialize)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Eq))] +pub struct FtTransferFee { + pub relayer: AccountId, + pub amount: Fee, } impl FtTransferMessageData { @@ -29,6 +36,27 @@ impl FtTransferMessageData { pub fn parse_on_transfer_message( message: &str, ) -> Result { + if message.len() == 40 { + // Parse message to determine recipient + let recipient = { + // Message format: + // Recipient of the transaction - 40 characters (Address in hex) + let mut address_bytes = [0; 20]; + hex::decode_to_slice(message, &mut address_bytes) + .map_err(|_| errors::ParseOnTransferMessageError::InvalidHexData)?; + Address::from_array(address_bytes) + }; + + #[allow(deprecated)] + return Ok(Self { + recipient, + fee: None, + }); + } + + // This logic is for backward compatibility to parse the message of the deprecated format. + // "{relayer_id}:0000000000000000000000000000000000000000000000000000000000000000{hex_address}" + // Split message by separator let (account, msg) = message .split_once(':') @@ -57,36 +85,43 @@ impl FtTransferMessageData { let fee_u128: u128 = U256::from_little_endian(&data[..32]) .try_into() .map_err(|_| errors::ParseOnTransferMessageError::OverflowNumber)?; - let fee: Fee = fee_u128.into(); + let fee_amount: Fee = fee_u128.into(); // Get recipient Eth address from message slice let recipient = Address::try_from_slice(&data[32..]).unwrap(); + #[allow(deprecated)] Ok(Self { - relayer: account_id, recipient, - fee, + fee: Some(FtTransferFee { + relayer: account_id, + amount: fee_amount, + }), }) } /// Encode to String with specific rules #[must_use] pub fn encode(&self) -> String { - // The first data section should contain fee data. - // Pay attention, that for compatibility reasons we used U256 type. - // It means 32 bytes for fee data and 20 bytes for address. - let mut data = [0; 52]; - U256::from(self.fee.as_u128()).to_little_endian(&mut data[..32]); - // Second data section should contain Eth address. - data[32..].copy_from_slice(self.recipient.as_bytes()); - // Add `:` separator between relayer_id and the data encoded in hex. - format!("{}:{}", self.relayer, hex::encode(data)) + #[allow(deprecated)] + self.fee.as_ref().map_or_else( + || hex::encode(self.recipient.as_bytes()), + |fee| { + // The first data section should contain fee data. + // Pay attention, that for compatibility reasons we used U256 type. + // It means 32 bytes for fee data and 20 bytes for address. + let mut data = [0; 52]; + U256::from(fee.amount.as_u128()).to_little_endian(&mut data[..32]); + // Second data section should contain Eth address. + data[32..].copy_from_slice(self.recipient.as_bytes()); + // Add `:` separator between relayer_id and the data encoded in hex. + format!("{}:{}", fee.relayer, hex::encode(data)) + }, + ) } /// Prepare message for `ft_transfer_call` -> `ft_on_transfer` pub fn prepare_message_for_on_transfer( - relayer_account_id: &AccountId, - fee: Fee, recipient: String, ) -> Result { let address = if recipient.len() == 42 { @@ -103,10 +138,10 @@ impl FtTransferMessageData { let recipient_address = Address::decode(&address) .map_err(errors::ParseEventMessageError::EthAddressValidationError)?; + #[allow(deprecated)] Ok(Self { - relayer: relayer_account_id.clone(), recipient: recipient_address, - fee, + fee: None, }) } } @@ -135,7 +170,6 @@ impl TokenMessageData { /// parsing for `ft_on_transfer` message parsing with correct and validated data. pub fn parse_event_message_and_prepare_token_message_data( message: &str, - fee: Fee, ) -> Result { let data: Vec<_> = message.split(':').collect(); // Data array can contain 1 or 2 elements @@ -150,11 +184,7 @@ impl TokenMessageData { Ok(Self::Near(account_id)) } else { let raw_message = data[1].into(); - let message = FtTransferMessageData::prepare_message_for_on_transfer( - &account_id, - fee, - raw_message, - )?; + let message = FtTransferMessageData::prepare_message_for_on_transfer(raw_message)?; Ok(Self::Eth { receiver_id: account_id, @@ -287,7 +317,6 @@ impl DepositedEvent { let token_message_data = TokenMessageData::parse_event_message_and_prepare_token_message_data( &event_message_data, - fee, )?; Ok(Self { @@ -318,6 +347,16 @@ mod tests { assert_eq!(expect_message, actual_message); } + #[test] + fn test_decoded_and_then_encoded_message_without_fee_does_not_change() { + let expect_message = "000000000000000000000000000000000000dead"; + let message_data = + FtTransferMessageData::parse_on_transfer_message(expect_message).unwrap(); + let actual_message = message_data.encode(); + + assert_eq!(expect_message, actual_message); + } + #[test] fn test_parsing_message_with_incorrect_amount_of_parts() { let message = "foo"; @@ -348,6 +387,16 @@ mod tests { assert_eq!(expected_error_message, actual_error_message); } + #[test] + fn test_parsing_message_without_fee_with_invalid_hex_data() { + let message = "g00000000000000000000000000000000000dead"; + let error = FtTransferMessageData::parse_on_transfer_message(message).unwrap_err(); + let expected_error_message = errors::ERR_INVALID_ON_TRANSFER_MESSAGE_HEX; + let actual_error_message = error.as_ref(); + + assert_eq!(expected_error_message, actual_error_message); + } + #[test] fn test_parsing_message_with_invalid_length_of_hex_data() { let message = "foo:dead"; @@ -371,13 +420,11 @@ mod tests { #[test] fn test_eth_token_message_data_decodes_recipient_correctly() { - let fee = Fee::new(NEP141Wei::new(0)); let address = Address::zero(); let message = format!("aurora:{}", address.encode()); let token_message_data = - TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) - .unwrap(); + TokenMessageData::parse_event_message_and_prepare_token_message_data(&message).unwrap(); let actual_recipient = token_message_data.recipient().to_string(); let expected_recipient = "aurora"; @@ -386,13 +433,11 @@ mod tests { #[test] fn test_eth_token_message_data_decodes_recipient_correctly_with_prefix() { - let fee = Fee::new(NEP141Wei::new(0)); let address = Address::zero(); let message = format!("aurora:0x{}", address.encode()); let token_message_data = - TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) - .unwrap(); + TokenMessageData::parse_event_message_and_prepare_token_message_data(&message).unwrap(); let actual_recipient = token_message_data.recipient().to_string(); let expected_recipient = "aurora"; @@ -401,12 +446,10 @@ mod tests { #[test] fn test_near_token_message_data_decodes_recipient_correctly() { - let fee = Fee::new(NEP141Wei::new(0)); let message = "aurora"; let token_message_data = - TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee) - .unwrap(); + TokenMessageData::parse_event_message_and_prepare_token_message_data(message).unwrap(); let actual_recipient = token_message_data.recipient().to_string(); let expected_recipient = "aurora"; @@ -415,11 +458,10 @@ mod tests { #[test] fn test_token_message_data_fails_with_too_many_parts() { - let fee = Fee::new(NEP141Wei::new(0)); let message = "aurora:foo:bar"; let parse_error = - TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee) + TokenMessageData::parse_event_message_and_prepare_token_message_data(message) .unwrap_err(); let actual_parse_error = parse_error.as_ref(); let expected_parse_error = errors::ERR_INVALID_EVENT_MESSAGE_FORMAT; @@ -429,11 +471,10 @@ mod tests { #[test] fn test_token_message_data_fails_with_invalid_account() { - let fee = Fee::new(NEP141Wei::new(0)); let message = "INVALID"; let parse_error = - TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee) + TokenMessageData::parse_event_message_and_prepare_token_message_data(message) .unwrap_err(); let actual_parse_error = parse_error.as_ref(); let expected_parse_error = errors::ERR_INVALID_ACCOUNT_ID; @@ -443,11 +484,10 @@ mod tests { #[test] fn test_eth_token_message_data_fails_with_invalid_address_length() { - let fee = Fee::new(NEP141Wei::new(0)); let message = "aurora:0xINVALID"; let parse_error = - TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee) + TokenMessageData::parse_event_message_and_prepare_token_message_data(message) .unwrap_err(); let actual_parse_error = std::str::from_utf8(parse_error.as_ref()).unwrap(); let expected_parse_error = AddressError::IncorrectLength.to_string(); @@ -457,11 +497,10 @@ mod tests { #[test] fn test_eth_token_message_data_fails_with_invalid_address() { - let fee = Fee::new(NEP141Wei::new(0)); let message = "aurora:0xINVALID_ADDRESS_WITH_CORRECT_LENGTH_HERE"; let parse_error = - TokenMessageData::parse_event_message_and_prepare_token_message_data(message, fee) + TokenMessageData::parse_event_message_and_prepare_token_message_data(message) .unwrap_err(); let actual_parse_error = std::str::from_utf8(parse_error.as_ref()).unwrap(); let expected_parse_error = AddressError::FailedDecodeHex.to_string(); @@ -477,8 +516,7 @@ mod tests { let fee = Fee::new(NEP141Wei::new(0)); let message = ["aurora", ":", recipient_address.encode().as_str()].concat(); let token_message_data: TokenMessageData = - TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) - .unwrap(); + TokenMessageData::parse_event_message_and_prepare_token_message_data(&message).unwrap(); let expected_deposited_event = DepositedEvent { eth_custodian_address, diff --git a/engine/src/contract_methods/connector/external.rs b/engine/src/contract_methods/connector/external.rs index 132f665b6..86a8b58d5 100644 --- a/engine/src/contract_methods/connector/external.rs +++ b/engine/src/contract_methods/connector/external.rs @@ -12,7 +12,6 @@ use crate::prelude::{ sdk, AccountId, Address, EthConnectorStorageId, NearGas, ToString, Vec, Yocto, }; use crate::state; -use aurora_engine_modexp::ModExpAlgorithm; use aurora_engine_sdk::env::{Env, DEFAULT_PREPAID_GAS}; use aurora_engine_sdk::io::{StorageIntermediate, IO}; use aurora_engine_sdk::promise::PromiseHandler; @@ -25,7 +24,6 @@ use aurora_engine_types::parameters::connector::{ use aurora_engine_types::parameters::engine::errors::ParseArgsError; use aurora_engine_types::parameters::engine::SubmitResult; use aurora_engine_types::parameters::{PromiseWithCallbackArgs, WithdrawCallArgs}; -use aurora_engine_types::types::ZERO_WEI; use function_name::named; /// NEAR Gas for calling `finish_deposit` promise. Used in the `deposit` logic. @@ -166,7 +164,7 @@ pub fn ft_on_transfer( let mut eth_connector = EthConnectorContract::init(io)?; let output = if predecessor_account_id == eth_connector.get_eth_connector_contract_account() { - eth_connector.ft_on_transfer(&engine, &args)?; + eth_connector.ft_on_transfer(&args)?; None } else { let result = engine.receive_erc20_tokens( @@ -415,9 +413,8 @@ impl EthConnectorContract { } /// `ft_on_transfer` callback function. - pub fn ft_on_transfer( + pub fn ft_on_transfer( &mut self, - engine: &Engine, args: &NEP141FtOnTransferArgs, ) -> Result<(), errors::FtTransferCallError> { sdk::log!("Call ft_on_transfer"); @@ -425,19 +422,8 @@ impl EthConnectorContract { let message_data = FtTransferMessageData::parse_on_transfer_message(&args.msg) .map_err(errors::FtTransferCallError::MessageParseFailed)?; let amount = Wei::new_u128(args.amount.as_u128()); - // Special case when predecessor_account_id is current_account_id - let fee = Wei::from(message_data.fee); - // Mint fee to relayer - let relayer = engine.get_relayer(message_data.relayer.as_bytes()); - - let mint_amount = if relayer.is_some() && fee > ZERO_WEI { - self.mint_eth_on_aurora(relayer.unwrap(), fee)?; - amount - fee - } else { - amount - }; - - self.mint_eth_on_aurora(message_data.recipient, mint_amount)?; + + self.mint_eth_on_aurora(message_data.recipient, amount)?; self.io.return_output(b"\"0\""); Ok(()) diff --git a/engine/src/contract_methods/connector/internal.rs b/engine/src/contract_methods/connector/internal.rs index c9431df8e..36d9ba93e 100644 --- a/engine/src/contract_methods/connector/internal.rs +++ b/engine/src/contract_methods/connector/internal.rs @@ -14,7 +14,7 @@ use crate::engine::Engine; use crate::hashchain::with_hashchain; use crate::prelude::{format, sdk, ToString, Vec}; use crate::state; -use aurora_engine_modexp::{AuroraModExp, ModExpAlgorithm}; +use aurora_engine_modexp::AuroraModExp; use aurora_engine_sdk::io::StorageIntermediate; use aurora_engine_sdk::promise::PromiseHandler; use aurora_engine_sdk::{env::Env, io::IO}; @@ -227,7 +227,7 @@ pub fn ft_on_transfer( .map_err(Into::::into)?; let output = if predecessor_account_id == current_account_id { - EthConnectorContract::init(io)?.ft_on_transfer(&engine, &args)?; + EthConnectorContract::init(io)?.ft_on_transfer(&args)?; None } else { let result = engine.receive_erc20_tokens( @@ -904,10 +904,6 @@ impl EthConnectorContract { if args.receiver_id == current_account_id { let message_data = FtTransferMessageData::parse_on_transfer_message(&args.msg) .map_err(errors::FtTransferCallError::MessageParseFailed)?; - // Check is transfer amount > fee - if message_data.fee.as_u128() >= args.amount.as_u128() { - return Err(errors::FtTransferCallError::InsufficientAmountForFee); - } // Additional check for overflowing before `ft_on_transfer` calling. // But skip checking for overflowing for the relayer. @@ -1009,9 +1005,8 @@ impl EthConnectorContract { } /// `ft_on_transfer` callback function. - pub fn ft_on_transfer( + pub fn ft_on_transfer( &mut self, - engine: &Engine, args: &NEP141FtOnTransferArgs, ) -> Result<(), errors::FtTransferCallError> { sdk::log!("Call ft_on_transfer"); @@ -1019,22 +1014,6 @@ impl EthConnectorContract { let message_data = FtTransferMessageData::parse_on_transfer_message(&args.msg) .map_err(errors::FtTransferCallError::MessageParseFailed)?; let amount = Wei::new_u128(args.amount.as_u128()); - // Special case when predecessor_account_id is current_account_id - let fee = Wei::from(message_data.fee); - // Mint fee to relayer - let relayer = engine.get_relayer(message_data.relayer.as_bytes()); - let (amount, relayer_fee) = relayer - .filter(|_| fee > aurora_engine_types::types::ZERO_WEI) - .map_or(Ok((amount, None)), |address| { - amount.checked_sub(fee).map_or( - Err(errors::FtTransferCallError::InsufficientAmountForFee), - |amount| Ok((amount, Some((address, fee)))), - ) - })?; - - if let Some((address, fee)) = relayer_fee { - self.mint_eth_on_aurora(address, fee)?; - } self.mint_eth_on_aurora(message_data.recipient, amount)?; self.save_ft_contract(); diff --git a/engine/src/contract_methods/connector/mod.rs b/engine/src/contract_methods/connector/mod.rs index c8ae381cc..c5cd3804a 100644 --- a/engine/src/contract_methods/connector/mod.rs +++ b/engine/src/contract_methods/connector/mod.rs @@ -609,8 +609,7 @@ mod tests { let fee = Fee::new(NEP141Wei::new(0)); let message = ["aurora", ":", recipient_address.encode().as_str()].concat(); let token_message_data: TokenMessageData = - TokenMessageData::parse_event_message_and_prepare_token_message_data(&message, fee) - .unwrap(); + TokenMessageData::parse_event_message_and_prepare_token_message_data(&message).unwrap(); let deposit_event = DepositedEvent { eth_custodian_address,