Skip to content

Commit

Permalink
fix(nft): add token_id field to the tx history primary key, fix balan…
Browse files Browse the repository at this point in the history
…ce (#2209)

This commit also fixes `withdarw_erc1155` decimal issue.
  • Loading branch information
laruh authored Nov 6, 2024
1 parent 5328bc3 commit a1b02c1
Show file tree
Hide file tree
Showing 15 changed files with 416 additions and 113 deletions.
26 changes: 14 additions & 12 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ mod eip1559_gas_fee;
pub(crate) use eip1559_gas_fee::FeePerGasEstimated;
use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator, GasApiConfig, GasApiProvider,
InfuraGasApiCaller};

pub(crate) mod eth_swap_v2;

/// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol
Expand Down Expand Up @@ -912,20 +913,20 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit
get_valid_nft_addr_to_withdraw(coin, &withdraw_type.to, &withdraw_type.token_address)?;

let token_id_str = &withdraw_type.token_id.to_string();
let wallet_amount = eth_coin.erc1155_balance(token_addr, token_id_str).await?;
let wallet_erc1155_amount = eth_coin.erc1155_balance(token_addr, token_id_str).await?;

let amount_dec = if withdraw_type.max {
wallet_amount.clone()
let amount_uint = if withdraw_type.max {
wallet_erc1155_amount.clone()
} else {
withdraw_type.amount.unwrap_or_else(|| 1.into())
withdraw_type.amount.unwrap_or_else(|| BigUint::from(1u32))
};

if amount_dec > wallet_amount {
if amount_uint > wallet_erc1155_amount {
return MmError::err(WithdrawError::NotEnoughNftsAmount {
token_address: withdraw_type.token_address,
token_id: withdraw_type.token_id.to_string(),
available: wallet_amount,
required: amount_dec,
available: wallet_erc1155_amount,
required: amount_uint,
});
}

Expand All @@ -936,7 +937,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit
let token_id_u256 =
U256::from_dec_str(token_id_str).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))?;
let amount_u256 =
U256::from_dec_str(&amount_dec.to_string()).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))?;
U256::from_dec_str(&amount_uint.to_string()).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))?;
let data = function.encode_input(&[
Token::Address(my_address),
Token::Address(to_addr),
Expand Down Expand Up @@ -995,7 +996,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit
contract_type: ContractType::Erc1155,
token_address: withdraw_type.token_address,
token_id: withdraw_type.token_id,
amount: amount_dec,
amount: amount_uint,
fee_details: Some(fee_details.into()),
coin: eth_coin.ticker.clone(),
block_height: 0,
Expand Down Expand Up @@ -1086,7 +1087,7 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd
contract_type: ContractType::Erc721,
token_address: withdraw_type.token_address,
token_id: withdraw_type.token_id,
amount: 1.into(),
amount: BigUint::from(1u8),
fee_details: Some(fee_details.into()),
coin: eth_coin.ticker.clone(),
block_height: 0,
Expand Down Expand Up @@ -4412,7 +4413,7 @@ impl EthCoin {
self.get_token_balance_for_address(my_address, token_address).await
}

async fn erc1155_balance(&self, token_addr: Address, token_id: &str) -> MmResult<BigDecimal, BalanceError> {
async fn erc1155_balance(&self, token_addr: Address, token_id: &str) -> MmResult<BigUint, BalanceError> {
let wallet_amount_uint = match self.coin_type {
EthCoinType::Eth | EthCoinType::Nft { .. } => {
let function = ERC1155_CONTRACT.function("balanceOf")?;
Expand All @@ -4438,7 +4439,8 @@ impl EthCoin {
))
},
};
let wallet_amount = u256_to_big_decimal(wallet_amount_uint, self.decimals)?;
// The "balanceOf" function in ERC1155 standard returns the exact count of tokens held by address without any decimals or scaling factors
let wallet_amount = wallet_amount_uint.to_string().parse::<BigUint>()?;
Ok(wallet_amount)
}

Expand Down
8 changes: 4 additions & 4 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ use mm2_core::mm_ctx::{from_ctx, MmArc};
use mm2_err_handle::prelude::*;
use mm2_metrics::MetricsWeak;
use mm2_number::{bigdecimal::{BigDecimal, ParseBigDecimalError, Zero},
MmNumber};
BigUint, MmNumber, ParseBigIntError};
use mm2_rpc::data::legacy::{EnabledCoin, GetEnabledResponse, Mm2RpcResult};
use parking_lot::Mutex as PaMutex;
use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json};
Expand Down Expand Up @@ -2642,7 +2642,7 @@ pub enum BalanceError {
UnexpectedDerivationMethod(UnexpectedDerivationMethod),
#[display(fmt = "Wallet storage error: {}", _0)]
WalletStorageError(String),
#[from_stringify("Bip32Error", "NumConversError")]
#[from_stringify("Bip32Error", "NumConversError", "ParseBigIntError")]
#[display(fmt = "Internal: {}", _0)]
Internal(String),
}
Expand Down Expand Up @@ -2994,8 +2994,8 @@ pub enum WithdrawError {
NotEnoughNftsAmount {
token_address: String,
token_id: String,
available: BigDecimal,
required: BigDecimal,
available: BigUint,
required: BigUint,
},
#[display(fmt = "DB error {}", _0)]
DbError(String),
Expand Down
7 changes: 7 additions & 0 deletions mm2src/coins/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use crate::nft::nft_errors::{ClearNftDbError, MetaFromUrlError, ProtectFromSpamE
use crate::nft::nft_structs::{build_nft_with_empty_meta, BuildNftFields, ClearNftDbReq, NftCommon, NftCtx, NftInfo,
NftTransferCommon, PhishingDomainReq, PhishingDomainRes, RefreshMetadataReq,
SpamContractReq, SpamContractRes, TransferMeta, TransferStatus, UriMeta};
#[cfg(not(target_arch = "wasm32"))]
use crate::nft::storage::NftMigrationOps;
use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps};
use common::log::error;
use common::parse_rfc3339_to_timestamp;
Expand Down Expand Up @@ -155,6 +157,9 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult<Nft
for chain in req.chains.iter() {
if !NftTransferHistoryStorageOps::is_initialized(&storage, chain).await? {
NftTransferHistoryStorageOps::init(&storage, chain).await?;
} else {
#[cfg(not(target_arch = "wasm32"))]
NftMigrationOps::migrate_tx_history_if_needed(&storage, chain).await?;
}
}
let mut transfer_history_list = storage
Expand Down Expand Up @@ -224,6 +229,8 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft
let transfer_history_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain).await?;

let from_block = if transfer_history_initialized {
#[cfg(not(target_arch = "wasm32"))]
NftMigrationOps::migrate_tx_history_if_needed(&storage, chain).await?;
let last_transfer_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, chain).await?;
last_transfer_block.map(|b| b + 1)
} else {
Expand Down
23 changes: 20 additions & 3 deletions mm2src/coins/nft/nft_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ use crate::nft::nft_errors::{LockDBError, ParseChainTypeError, ParseContractType
use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps};
use crate::{TransactionType, TxFeeDetails, WithdrawFee};

#[cfg(not(target_arch = "wasm32"))]
use crate::nft::storage::NftMigrationOps;

cfg_native! {
use db_common::async_sql_conn::AsyncConnection;
use futures::lock::Mutex as AsyncMutex;
Expand Down Expand Up @@ -438,7 +441,8 @@ pub struct WithdrawErc1155 {
#[serde(deserialize_with = "deserialize_token_id")]
pub(crate) token_id: BigUint,
/// Optional amount of the token to withdraw. Defaults to 1 if not specified.
pub(crate) amount: Option<BigDecimal>,
#[serde(deserialize_with = "deserialize_opt_biguint")]
pub(crate) amount: Option<BigUint>,
/// If set to `true`, withdraws the maximum amount available. Overrides the `amount` field.
#[serde(default)]
pub(crate) max: bool,
Expand Down Expand Up @@ -489,7 +493,7 @@ pub struct TransactionNftDetails {
pub(crate) token_address: String,
#[serde(serialize_with = "serialize_token_id")]
pub(crate) token_id: BigUint,
pub(crate) amount: BigDecimal,
pub(crate) amount: BigUint,
pub(crate) fee_details: Option<TxFeeDetails>,
/// The coin transaction belongs to
pub(crate) coin: String,
Expand Down Expand Up @@ -753,7 +757,7 @@ impl NftCtx {
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn lock_db(
&self,
) -> MmResult<impl NftListStorageOps + NftTransferHistoryStorageOps + '_, LockDBError> {
) -> MmResult<impl NftListStorageOps + NftTransferHistoryStorageOps + NftMigrationOps + '_, LockDBError> {
Ok(self.nft_cache_db.lock().await)
}

Expand Down Expand Up @@ -806,6 +810,19 @@ where
BigUint::from_str(&s).map_err(serde::de::Error::custom)
}

/// Custom deserialization function for optional BigUint.
fn deserialize_opt_biguint<'de, D>(deserializer: D) -> Result<Option<BigUint>, D::Error>
where
D: Deserializer<'de>,
{
let opt: Option<String> = Option::deserialize(deserializer)?;
if let Some(s) = opt {
BigUint::from_str(&s).map(Some).map_err(serde::de::Error::custom)
} else {
Ok(None)
}
}

/// Request parameters for clearing NFT data from the database.
#[derive(Debug, Deserialize)]
pub struct ClearNftDbReq {
Expand Down
7 changes: 6 additions & 1 deletion mm2src/coins/nft/nft_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,12 @@ cross_test!(test_add_get_transfers, {
.clone();
assert_eq!(transfer1.block_number, 28056721);
let transfer2 = storage
.get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX)
.get_transfer_by_tx_hash_log_index_token_id(
&chain,
TX_HASH.to_string(),
LOG_INDEX,
BigUint::from_str("214300047253").unwrap(),
)
.await
.unwrap()
.unwrap();
Expand Down
8 changes: 4 additions & 4 deletions mm2src/coins/nft/storage/db_test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ pub(crate) fn nft_transfer_history() -> Vec<NftTransferHistory> {
transaction_index: Some(198),
log_index: 495,
value: Default::default(),
transaction_type: Some("Single".to_string()),
transaction_type: Some("Batch".to_string()),
token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(),
from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(),
to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
Expand All @@ -284,15 +284,15 @@ pub(crate) fn nft_transfer_history() -> Vec<NftTransferHistory> {
confirmations: 0,
};

// Same as transfer1 but with different log_index, meaning that transfer1 and transfer2 are part of one batch/multi token transaction
// Same as transfer1 (identical tx hash and log index) but with different token_id, meaning that transfer1 and transfer2 are part of one batch/multi token transaction
let transfer2 = NftTransferHistory {
common: NftTransferCommon {
block_hash: Some("0x3d68b78391fb3cf8570df27036214f7e9a5a6a45d309197936f51d826041bfe7".to_string()),
transaction_hash: "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe".to_string(),
transaction_index: Some(198),
log_index: 496,
log_index: 495,
value: Default::default(),
transaction_type: Some("Single".to_string()),
transaction_type: Some("Batch".to_string()),
token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(),
from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(),
to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
Expand Down
10 changes: 9 additions & 1 deletion mm2src/coins/nft/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,12 @@ pub trait NftTransferHistoryStorageOps {
token_id: BigUint,
) -> MmResult<Vec<NftTransferHistory>, Self::Error>;

async fn get_transfer_by_tx_hash_and_log_index(
async fn get_transfer_by_tx_hash_log_index_token_id(
&self,
chain: &Chain,
transaction_hash: String,
log_index: u32,
token_id: BigUint,
) -> MmResult<Option<NftTransferHistory>, Self::Error>;

/// Updates the metadata for NFT transfers identified by their token address and ID.
Expand Down Expand Up @@ -243,3 +244,10 @@ pub(crate) struct TransferDetailsJson {
pub(crate) to_address: Address,
pub(crate) fee_details: Option<EthTxFeeDetails>,
}

#[async_trait]
pub trait NftMigrationOps {
type Error: NftStorageError;

async fn migrate_tx_history_if_needed(&self, chain: &Chain) -> MmResult<(), Self::Error>;
}
Loading

0 comments on commit a1b02c1

Please sign in to comment.