Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove eth-connector fee logic #882

Merged
merged 4 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions engine-tests-connector/src/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = 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?;
Expand Down
14 changes: 3 additions & 11 deletions engine-tests/src/utils/standalone/mocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down Expand Up @@ -114,19 +112,13 @@ pub fn mint_evm_account<I: IO + Copy, E: Env>(
};

#[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<I: IO + Copy, E: Env, M: ModExpAlgorithm>(
mut io: I,
engine: &Engine<I, E, M>,
aurora_account_id: &AccountId,
address: Address,
balance: Wei,
) {
fn deposit<I: IO + Copy>(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(),
Expand Down Expand Up @@ -165,7 +157,7 @@ fn deposit<I: IO + Copy, E: Env, M: ModExpAlgorithm>(
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"))]
Expand Down
92 changes: 76 additions & 16 deletions engine/src/contract_methods/connector/deposit_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@ pub type EventParams = Vec<EventParam>;
#[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<FtTransferFee>,
aleksuss marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(BorshSerialize, BorshDeserialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Eq))]
pub struct FtTransferFee {
pub relayer: AccountId,
pub amount: Fee,
}

impl FtTransferMessageData {
Expand All @@ -29,6 +36,27 @@ impl FtTransferMessageData {
pub fn parse_on_transfer_message(
message: &str,
) -> Result<Self, errors::ParseOnTransferMessageError> {
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(':')
Expand Down Expand Up @@ -57,30 +85,39 @@ 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`
Expand All @@ -103,10 +140,13 @@ 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: Some(FtTransferFee {
birchmd marked this conversation as resolved.
Show resolved Hide resolved
relayer: relayer_account_id.clone(),
amount: fee,
}),
})
}
}
Expand Down Expand Up @@ -318,6 +358,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";
Expand Down Expand Up @@ -348,6 +398,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";
Expand Down
22 changes: 4 additions & 18 deletions engine/src/contract_methods/connector/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -166,7 +164,7 @@ pub fn ft_on_transfer<I: IO + Copy, E: Env, H: PromiseHandler>(
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(
Expand Down Expand Up @@ -415,29 +413,17 @@ impl<I: IO + Copy> EthConnectorContract<I> {
}

/// `ft_on_transfer` callback function.
pub fn ft_on_transfer<E: Env, M: ModExpAlgorithm>(
pub fn ft_on_transfer(
&mut self,
engine: &Engine<I, E, M>,
args: &NEP141FtOnTransferArgs,
) -> Result<(), errors::FtTransferCallError> {
sdk::log!("Call ft_on_transfer");
// Parse message with specific rules
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(())
Expand Down
27 changes: 3 additions & 24 deletions engine/src/contract_methods/connector/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -227,7 +227,7 @@ pub fn ft_on_transfer<I: IO + Copy, E: Env, H: PromiseHandler>(
.map_err(Into::<ParseArgsError>::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(
Expand Down Expand Up @@ -904,10 +904,6 @@ impl<I: IO + Copy> EthConnectorContract<I> {
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.
Expand Down Expand Up @@ -1009,32 +1005,15 @@ impl<I: IO + Copy> EthConnectorContract<I> {
}

/// `ft_on_transfer` callback function.
pub fn ft_on_transfer<E: Env, M: ModExpAlgorithm>(
pub fn ft_on_transfer(
&mut self,
engine: &Engine<I, E, M>,
args: &NEP141FtOnTransferArgs,
) -> Result<(), errors::FtTransferCallError> {
sdk::log!("Call ft_on_transfer");
// Parse message with specific rules
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();
Expand Down
Loading