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

fix(nft): add token_id field to the tx history primary key, fix balance #2209

Merged
merged 23 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
51eb543
update multi_index in wasm NftTransferHistoryTable and PRIMARY KEY in…
laruh Sep 3, 2024
2bdee4f
fix erc1155_balance, check that "balanceOf" ERC11155 returns the exac…
laruh Sep 3, 2024
416b23c
Merge remote-tracking branch 'origin/dev' into fix-nft-tx-history-pri…
laruh Sep 12, 2024
a493f3f
require amount: Option<BigUint> in WithdrawErc1155
laruh Sep 12, 2024
48a7f5b
cover sql nft tx history migration
laruh Sep 12, 2024
1da9cb8
Merge remote-tracking branch 'origin/dev' into fix-nft-tx-history-pri…
laruh Sep 13, 2024
5cae2d4
make migration function name clearer
laruh Sep 13, 2024
9cebf62
wasm migration wip
laruh Sep 15, 2024
df164d4
copy_store_data_sync for NftTransferHistoryTable migration
laruh Sep 15, 2024
041f99c
change version check and add logs
laruh Sep 15, 2024
e8be556
use "else if old_version == 1 && new_version == 2" check
laruh Sep 16, 2024
c42e941
Merge remote-tracking branch 'origin/dev' into fix-nft-tx-history-pri…
laruh Sep 30, 2024
12bdb27
WIP: update on_upgrade_needed for wasm nft tables
laruh Oct 10, 2024
7511d94
Merge remote-tracking branch 'origin/dev' into fix-nft-tx-history-pri…
laruh Oct 11, 2024
14bef0d
update old/new versions handle, remove unused code
laruh Oct 11, 2024
a3ba820
avoid duplication when we crate tx history sql str, migrate_tx_histor…
laruh Oct 13, 2024
5c14c09
update comment in migrate_tx_history_table_to_schema_v2
laruh Oct 13, 2024
38ac974
cover schema_table deletion in RPC clear_nft_db
laruh Oct 13, 2024
43d7ba2
fix sql migration bug
laruh Oct 14, 2024
a5486e1
Merge remote-tracking branch 'origin/dev' into fix-nft-tx-history-pri…
laruh Oct 26, 2024
ef3dd1e
review: replace BigDecimal by BigUint in NFT withdraw
laruh Oct 26, 2024
7636464
review: use progressive upgrade pattern for IDB and sql schemas
laruh Oct 27, 2024
f44bc38
review: remove break to allow straightforward version-by-version upg…
laruh Nov 6, 2024
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
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
Loading