diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 3d84103f3f..e679947517 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -11,14 +11,14 @@ pub(crate) mod storage; use crate::{get_my_address, MyAddressReq, WithdrawError}; use nft_errors::{GetInfoFromUriError, GetNftInfoError, UpdateNftError}; use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftList, NftListReq, NftMetadataReq, - NftTransferHistory, NftTransfersReq, NftTxHistoryFromMoralis, NftsTransferHistoryList, + NftTransferHistory, NftTransferHistoryFromMoralis, NftTransfersReq, NftsTransferHistoryList, TransactionNftDetails, UpdateNftReq, WithdrawNftReq}; use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721}; use crate::nft::nft_errors::ProtectFromSpamError; -use crate::nft::nft_structs::{NftCommon, NftCtx, NftTransferCommon, RefreshMetadataReq, TransferStatus, TxMeta, +use crate::nft::nft_structs::{NftCommon, NftCtx, NftTransferCommon, RefreshMetadataReq, TransferMeta, TransferStatus, UriMeta}; -use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTxHistoryStorageOps}; +use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps}; use common::{parse_rfc3339_to_timestamp, APPLICATION_JSON}; use ethereum_types::Address; use http::header::ACCEPT; @@ -91,16 +91,16 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult MmResult<(), UpdateNft let storage = NftStorageBuilder::new(&ctx).build()?; for chain in req.chains.iter() { - let tx_history_initialized = NftTxHistoryStorageOps::is_initialized(&storage, chain).await?; + let transfer_history_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain).await?; - let from_block = if tx_history_initialized { - let last_tx_block = NftTxHistoryStorageOps::get_last_block_number(&storage, chain).await?; - last_tx_block.map(|b| b + 1) + let from_block = if transfer_history_initialized { + let last_transfer_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, chain).await?; + last_transfer_block.map(|b| b + 1) } else { - NftTxHistoryStorageOps::init(&storage, chain).await?; + NftTransferHistoryStorageOps::init(&storage, chain).await?; None }; let nft_transfers = get_moralis_nft_transfers(&ctx, chain, from_block, &req.url).await?; - storage.add_txs_to_history(chain, nft_transfers).await?; + storage.add_transfers_to_history(chain, nft_transfers).await?; let nft_block = match NftListStorageOps::get_last_block_number(&storage, chain).await { Ok(Some(block)) => block, Ok(None) => { // if there are no rows in NFT LIST table we can try to get all info from moralis. let nfts = cache_nfts_from_moralis(&ctx, &storage, chain, &req.url).await?; - update_meta_in_txs(&storage, chain, nfts).await?; - update_txs_with_empty_meta(&storage, chain, &req.url).await?; + update_meta_in_transfers(&storage, chain, nfts).await?; + update_transfers_with_empty_meta(&storage, chain, &req.url).await?; continue; }, Err(_) => { // if there is an error, then NFT LIST table doesnt exist, so we need to cache from mroalis. NftListStorageOps::init(&storage, chain).await?; let nft_list = get_moralis_nft_list(&ctx, chain, &req.url).await?; - let last_scanned_block = NftTxHistoryStorageOps::get_last_block_number(&storage, chain) + let last_scanned_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, chain) .await? .unwrap_or(0); storage .add_nfts_to_list(chain, nft_list.clone(), last_scanned_block) .await?; - update_meta_in_txs(&storage, chain, nft_list).await?; - update_txs_with_empty_meta(&storage, chain, &req.url).await?; + update_meta_in_transfers(&storage, chain, nft_list).await?; + update_transfers_with_empty_meta(&storage, chain, &req.url).await?; continue; }, }; @@ -166,7 +166,7 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft }); } update_nft_list(ctx.clone(), &storage, chain, scanned_block + 1, &req.url).await?; - update_txs_with_empty_meta(&storage, chain, &req.url).await?; + update_transfers_with_empty_meta(&storage, chain, &req.url).await?; } Ok(()) } @@ -204,8 +204,10 @@ pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResu storage .refresh_nft_metadata(&moralis_meta.chain, nft_db.clone()) .await?; - let tx_meta = TxMeta::from(nft_db.clone()); - storage.update_txs_meta_by_token_addr_id(&nft_db.chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft_db.clone()); + storage + .update_transfers_meta_by_token_addr_id(&nft_db.chain, transfer_meta) + .await?; Ok(()) } @@ -292,12 +294,13 @@ async fn get_moralis_nft_transfers( let response = send_request_to_uri(uri.as_str()).await?; if let Some(transfer_list) = response["result"].as_array() { for transfer in transfer_list { - let transfer_moralis: NftTxHistoryFromMoralis = serde_json::from_str(&transfer.to_string())?; + let transfer_moralis: NftTransferHistoryFromMoralis = serde_json::from_str(&transfer.to_string())?; let contract_type = match transfer_moralis.contract_type { Some(contract_type) => contract_type, None => continue, }; - let status = get_tx_status(&wallet_address, ð_addr_to_hex(&transfer_moralis.common.to_address)); + let status = + get_transfer_status(&wallet_address, ð_addr_to_hex(&transfer_moralis.common.to_address)); let block_timestamp = parse_rfc3339_to_timestamp(&transfer_moralis.block_timestamp)?; let transfer_history = NftTransferHistory { common: NftTransferCommon { @@ -477,8 +480,8 @@ async fn get_uri_meta(token_uri: Option<&str>, metadata: Option<&str>) -> UriMet uri_meta } -fn get_tx_status(my_wallet: &str, to_address: &str) -> TransferStatus { - // if my_wallet == from_address && my_wallet == to_address it is incoming tx, so we can check just to_address. +fn get_transfer_status(my_wallet: &str, to_address: &str) -> TransferStatus { + // if my_wallet == from_address && my_wallet == to_address it is incoming transfer, so we can check just to_address. if my_wallet.to_lowercase() == to_address.to_lowercase() { TransferStatus::Receive } else { @@ -488,96 +491,98 @@ fn get_tx_status(my_wallet: &str, to_address: &str) -> TransferStatus { /// `update_nft_list` function gets nft transfers from NFT HISTORY table, iterates through them /// and updates NFT LIST table info. -async fn update_nft_list( +async fn update_nft_list( ctx: MmArc, storage: &T, chain: &Chain, scan_from_block: u64, url: &Url, ) -> MmResult<(), UpdateNftError> { - let txs = storage.get_txs_from_block(chain, scan_from_block).await?; + let transfers = storage.get_transfers_from_block(chain, scan_from_block).await?; let req = MyAddressReq { coin: chain.to_ticker(), }; let my_address = get_my_address(ctx.clone(), req).await?.wallet_address.to_lowercase(); - for tx in txs.into_iter() { - handle_nft_tx(storage, chain, url, tx, &my_address).await?; + for transfer in transfers.into_iter() { + handle_nft_transfer(storage, chain, url, transfer, &my_address).await?; } Ok(()) } -async fn handle_nft_tx( +async fn handle_nft_transfer( storage: &T, chain: &Chain, url: &Url, - tx: NftTransferHistory, + transfer: NftTransferHistory, my_address: &str, ) -> MmResult<(), UpdateNftError> { - match (tx.status, tx.contract_type) { - (TransferStatus::Send, ContractType::Erc721) => handle_send_erc721(storage, chain, tx).await, + match (transfer.status, transfer.contract_type) { + (TransferStatus::Send, ContractType::Erc721) => handle_send_erc721(storage, chain, transfer).await, (TransferStatus::Receive, ContractType::Erc721) => { - handle_receive_erc721(storage, chain, tx, url, my_address).await + handle_receive_erc721(storage, chain, transfer, url, my_address).await }, - (TransferStatus::Send, ContractType::Erc1155) => handle_send_erc1155(storage, chain, tx).await, + (TransferStatus::Send, ContractType::Erc1155) => handle_send_erc1155(storage, chain, transfer).await, (TransferStatus::Receive, ContractType::Erc1155) => { - handle_receive_erc1155(storage, chain, tx, url, my_address).await + handle_receive_erc1155(storage, chain, transfer, url, my_address).await }, } } -async fn handle_send_erc721( +async fn handle_send_erc721( storage: &T, chain: &Chain, - tx: NftTransferHistory, + transfer: NftTransferHistory, ) -> MmResult<(), UpdateNftError> { let nft_db = storage .get_nft( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), ) .await? .ok_or_else(|| UpdateNftError::TokenNotFoundInWallet { - token_address: eth_addr_to_hex(&tx.common.token_address), - token_id: tx.common.token_id.to_string(), + token_address: eth_addr_to_hex(&transfer.common.token_address), + token_id: transfer.common.token_id.to_string(), })?; - let tx_meta = TxMeta::from(nft_db); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft_db); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; storage .remove_nft_from_list( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id, - tx.block_number, + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id, + transfer.block_number, ) .await?; Ok(()) } -async fn handle_receive_erc721( +async fn handle_receive_erc721( storage: &T, chain: &Chain, - tx: NftTransferHistory, + transfer: NftTransferHistory, url: &Url, my_address: &str, ) -> MmResult<(), UpdateNftError> { let nft = match storage .get_nft( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), ) .await? { Some(mut nft_db) => { // An error is raised if user tries to receive an identical ERC-721 token they already own // and if owner address != from address - if my_address != eth_addr_to_hex(&tx.common.from_address) { + if my_address != eth_addr_to_hex(&transfer.common.from_address) { return MmError::err(UpdateNftError::AttemptToReceiveAlreadyOwnedErc721 { - tx_hash: tx.common.transaction_hash, + tx_hash: transfer.common.transaction_hash, }); } - nft_db.block_number = tx.block_number; + nft_db.block_number = transfer.block_number; drop_mutability!(nft_db); storage .update_nft_amount_and_block_number(chain, nft_db.clone()) @@ -587,8 +592,8 @@ async fn handle_receive_erc721( // If token isn't in NFT LIST table then add nft to the table. None => { let mut nft = get_moralis_metadata( - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id, + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id, chain, url, ) @@ -597,86 +602,90 @@ async fn handle_receive_erc721( // than History by Wallet update nft.common.owner_of = Address::from_str(my_address).map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?; - nft.block_number = tx.block_number; + nft.block_number = transfer.block_number; drop_mutability!(nft); storage - .add_nfts_to_list(chain, vec![nft.clone()], tx.block_number) + .add_nfts_to_list(chain, vec![nft.clone()], transfer.block_number) .await?; nft }, }; - let tx_meta = TxMeta::from(nft); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; Ok(()) } -async fn handle_send_erc1155( +async fn handle_send_erc1155( storage: &T, chain: &Chain, - tx: NftTransferHistory, + transfer: NftTransferHistory, ) -> MmResult<(), UpdateNftError> { let mut nft_db = storage .get_nft( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), ) .await? .ok_or_else(|| UpdateNftError::TokenNotFoundInWallet { - token_address: eth_addr_to_hex(&tx.common.token_address), - token_id: tx.common.token_id.to_string(), + token_address: eth_addr_to_hex(&transfer.common.token_address), + token_id: transfer.common.token_id.to_string(), })?; - match nft_db.common.amount.cmp(&tx.common.amount) { + match nft_db.common.amount.cmp(&transfer.common.amount) { Ordering::Equal => { storage .remove_nft_from_list( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id, - tx.block_number, + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id, + transfer.block_number, ) .await?; }, Ordering::Greater => { - nft_db.common.amount -= tx.common.amount; + nft_db.common.amount -= transfer.common.amount; storage - .update_nft_amount(chain, nft_db.clone(), tx.block_number) + .update_nft_amount(chain, nft_db.clone(), transfer.block_number) .await?; }, Ordering::Less => { return MmError::err(UpdateNftError::InsufficientAmountInCache { amount_list: nft_db.common.amount.to_string(), - amount_history: tx.common.amount.to_string(), + amount_history: transfer.common.amount.to_string(), }); }, } - let tx_meta = TxMeta::from(nft_db); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft_db); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; Ok(()) } -async fn handle_receive_erc1155( +async fn handle_receive_erc1155( storage: &T, chain: &Chain, - tx: NftTransferHistory, + transfer: NftTransferHistory, url: &Url, my_address: &str, ) -> MmResult<(), UpdateNftError> { let nft = match storage .get_nft( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), ) .await? { Some(mut nft_db) => { // if owner address == from address, then owner sent tokens to themself, // which means that the amount will not change. - if my_address != eth_addr_to_hex(&tx.common.from_address) { - nft_db.common.amount += tx.common.amount; + if my_address != eth_addr_to_hex(&transfer.common.from_address) { + nft_db.common.amount += transfer.common.amount; } - nft_db.block_number = tx.block_number; + nft_db.block_number = transfer.block_number; drop_mutability!(nft_db); storage .update_nft_amount_and_block_number(chain, nft_db.clone()) @@ -686,8 +695,8 @@ async fn handle_receive_erc1155( // If token isn't in NFT LIST table then add nft to the table. None => { let moralis_meta = get_moralis_metadata( - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), chain, url, ) @@ -698,7 +707,7 @@ async fn handle_receive_erc1155( common: NftCommon { token_address: moralis_meta.common.token_address, token_id: moralis_meta.common.token_id, - amount: tx.common.amount, + amount: transfer.common.amount, owner_of: Address::from_str(my_address) .map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?, token_hash: moralis_meta.common.token_hash, @@ -713,16 +722,20 @@ async fn handle_receive_erc1155( }, chain: *chain, block_number_minted: moralis_meta.block_number_minted, - block_number: tx.block_number, + block_number: transfer.block_number, contract_type: moralis_meta.contract_type, uri_meta, }; - storage.add_nfts_to_list(chain, [nft.clone()], tx.block_number).await?; + storage + .add_nfts_to_list(chain, [nft.clone()], transfer.block_number) + .await?; nft }, }; - let tx_meta = TxMeta::from(nft); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; Ok(()) } @@ -751,14 +764,14 @@ pub(crate) async fn find_wallet_nft_amount( Ok(nft_meta.common.amount) } -async fn cache_nfts_from_moralis( +async fn cache_nfts_from_moralis( ctx: &MmArc, storage: &T, chain: &Chain, url: &Url, ) -> MmResult, UpdateNftError> { let nft_list = get_moralis_nft_list(ctx, chain, url).await?; - let last_scanned_block = NftTxHistoryStorageOps::get_last_block_number(storage, chain) + let last_scanned_block = NftTransferHistoryStorageOps::get_last_block_number(storage, chain) .await? .unwrap_or(0); storage @@ -767,28 +780,32 @@ async fn cache_nfts_from_moralis( Ok(nft_list) } -/// `update_meta_in_txs` function updates only txs related to current nfts in wallet. -async fn update_meta_in_txs(storage: &T, chain: &Chain, nfts: Vec) -> MmResult<(), UpdateNftError> +/// `update_meta_in_transfers` function updates only transfers related to current nfts in wallet. +async fn update_meta_in_transfers(storage: &T, chain: &Chain, nfts: Vec) -> MmResult<(), UpdateNftError> where - T: NftListStorageOps + NftTxHistoryStorageOps, + T: NftListStorageOps + NftTransferHistoryStorageOps, { for nft in nfts.into_iter() { - let tx_meta = TxMeta::from(nft); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; } Ok(()) } -/// `update_txs_with_empty_meta` function updates empty metadata in transfers. -async fn update_txs_with_empty_meta(storage: &T, chain: &Chain, url: &Url) -> MmResult<(), UpdateNftError> +/// `update_transfers_with_empty_meta` function updates empty metadata in transfers. +async fn update_transfers_with_empty_meta(storage: &T, chain: &Chain, url: &Url) -> MmResult<(), UpdateNftError> where - T: NftListStorageOps + NftTxHistoryStorageOps, + T: NftListStorageOps + NftTransferHistoryStorageOps, { - let nft_token_addr_id = storage.get_txs_with_empty_meta(chain).await?; + let nft_token_addr_id = storage.get_transfers_with_empty_meta(chain).await?; for addr_id_pair in nft_token_addr_id.into_iter() { let nft_meta = get_moralis_metadata(addr_id_pair.token_address, addr_id_pair.token_id, chain, url).await?; - let tx_meta = TxMeta::from(nft_meta); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft_meta); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; } Ok(()) } @@ -818,12 +835,12 @@ fn check_and_redact_if_spam(text: &mut Option) -> Result MmResult<(), ProtectFromSpamError> { - let collection_name_spam = check_and_redact_if_spam(&mut tx.collection_name)?; - let token_name_spam = check_and_redact_if_spam(&mut tx.token_name)?; +fn protect_from_history_spam(transfer: &mut NftTransferHistory) -> MmResult<(), ProtectFromSpamError> { + let collection_name_spam = check_and_redact_if_spam(&mut transfer.collection_name)?; + let token_name_spam = check_and_redact_if_spam(&mut transfer.token_name)?; if collection_name_spam || token_name_spam { - tx.common.possible_spam = true; + transfer.common.possible_spam = true; } Ok(()) } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index 3db093540c..9d7a07b89a 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -325,7 +325,7 @@ pub struct TransactionNftDetails { #[derive(Debug, Deserialize)] pub struct NftTransfersReq { pub(crate) chains: Vec, - pub(crate) filters: Option, + pub(crate) filters: Option, #[serde(default)] pub(crate) max: bool, #[serde(default = "ten")] @@ -368,14 +368,14 @@ impl fmt::Display for TransferStatus { } } -/// [`NftTransferCommon`] structure contains common fields from [`NftTransferHistory`] and [`NftTxHistoryFromMoralis`] +/// [`NftTransferCommon`] structure contains common fields from [`NftTransferHistory`] and [`NftTransferHistoryFromMoralis`] #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NftTransferCommon { pub(crate) block_hash: Option, /// Transaction hash in hexadecimal format pub(crate) transaction_hash: String, pub(crate) transaction_index: Option, - pub(crate) log_index: Option, + pub(crate) log_index: u32, pub(crate) value: Option, pub(crate) transaction_type: Option, pub(crate) token_address: Address, @@ -404,9 +404,9 @@ pub struct NftTransferHistory { pub(crate) status: TransferStatus, } -/// This structure is for deserializing moralis NFT transaction json to struct. +/// This structure is for deserializing moralis NFT transfer json to struct. #[derive(Debug, Deserialize)] -pub(crate) struct NftTxHistoryFromMoralis { +pub(crate) struct NftTransferHistoryFromMoralis { #[serde(flatten)] pub(crate) common: NftTransferCommon, pub(crate) block_number: SerdeStringWrap, @@ -422,7 +422,7 @@ pub struct NftsTransferHistoryList { } #[derive(Copy, Clone, Debug, Deserialize)] -pub struct NftTxHistoryFilters { +pub struct NftTransferHistoryFilters { #[serde(default)] pub receive: bool, #[serde(default)] @@ -444,7 +444,7 @@ pub struct NftTokenAddrId { } #[derive(Debug)] -pub struct TxMeta { +pub struct TransferMeta { pub(crate) token_address: String, pub(crate) token_id: BigDecimal, pub(crate) token_uri: Option, @@ -453,9 +453,9 @@ pub struct TxMeta { pub(crate) token_name: Option, } -impl From for TxMeta { +impl From for TransferMeta { fn from(nft_db: Nft) -> Self { - TxMeta { + TransferMeta { token_address: eth_addr_to_hex(&nft_db.common.token_address), token_id: nft_db.common.token_id, token_uri: nft_db.common.token_uri, diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs index 7beb9e5f12..02710e0ac4 100644 --- a/mm2src/coins/nft/nft_tests.rs +++ b/mm2src/coins/nft/nft_tests.rs @@ -6,7 +6,7 @@ const TEST_WALLET_ADDR_EVM: &str = "0x394d86994f954ed931b86791b62fe64f4c5dac37"; #[cfg(all(test, not(target_arch = "wasm32")))] mod native_tests { use crate::eth::eth_addr_to_hex; - use crate::nft::nft_structs::{NftFromMoralis, NftTxHistoryFromMoralis, UriMeta}; + use crate::nft::nft_structs::{NftFromMoralis, NftTransferHistoryFromMoralis, UriMeta}; use crate::nft::nft_tests::{NFT_HISTORY_URL_TEST, NFT_LIST_URL_TEST, NFT_METADATA_URL_TEST, TEST_WALLET_ADDR_EVM}; use crate::nft::storage::db_test_helpers::*; use crate::nft::{check_and_redact_if_spam, check_moralis_ipfs_bafy, check_nft_metadata_for_spam, @@ -70,11 +70,12 @@ mod native_tests { assert_eq!(TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&nft_moralis.common.owner_of)); } - let response_tx_history = block_on(send_request_to_uri(NFT_HISTORY_URL_TEST)).unwrap(); - let mut transfer_list = response_tx_history["result"].as_array().unwrap().clone(); + let response_transfer_history = block_on(send_request_to_uri(NFT_HISTORY_URL_TEST)).unwrap(); + let mut transfer_list = response_transfer_history["result"].as_array().unwrap().clone(); assert!(!transfer_list.is_empty()); - let first_tx = transfer_list.remove(transfer_list.len() - 1); - let transfer_moralis: NftTxHistoryFromMoralis = serde_json::from_str(&first_tx.to_string()).unwrap(); + let first_transfer = transfer_list.remove(transfer_list.len() - 1); + let transfer_moralis: NftTransferHistoryFromMoralis = + serde_json::from_str(&first_transfer.to_string()).unwrap(); assert_eq!( TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&transfer_moralis.common.to_address) @@ -107,25 +108,25 @@ mod native_tests { fn test_nft_amount() { block_on(test_nft_amount_impl()) } #[test] - fn test_add_get_txs() { block_on(test_add_get_txs_impl()) } + fn test_add_get_transfers() { block_on(test_add_get_transfers_impl()) } #[test] - fn test_last_tx_block() { block_on(test_last_tx_block_impl()) } + fn test_last_transfer_block() { block_on(test_last_transfer_block_impl()) } #[test] - fn test_tx_history() { block_on(test_tx_history_impl()) } + fn test_transfer_history() { block_on(test_transfer_history_impl()) } #[test] - fn test_tx_history_filters() { block_on(test_tx_history_filters_impl()) } + fn test_transfer_history_filters() { block_on(test_transfer_history_filters_impl()) } #[test] - fn test_get_update_tx_meta() { block_on(test_get_update_tx_meta_impl()) } + fn test_get_update_transfer_meta() { block_on(test_get_update_transfer_meta_impl()) } } #[cfg(target_arch = "wasm32")] mod wasm_tests { use crate::eth::eth_addr_to_hex; - use crate::nft::nft_structs::{NftFromMoralis, NftTxHistoryFromMoralis}; + use crate::nft::nft_structs::{NftFromMoralis, NftTransferHistoryFromMoralis}; use crate::nft::nft_tests::{NFT_HISTORY_URL_TEST, NFT_LIST_URL_TEST, NFT_METADATA_URL_TEST, TEST_WALLET_ADDR_EVM}; use crate::nft::send_request_to_uri; use crate::nft::storage::db_test_helpers::*; @@ -142,11 +143,12 @@ mod wasm_tests { assert_eq!(TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&nft_moralis.common.owner_of)); } - let response_tx_history = send_request_to_uri(NFT_HISTORY_URL_TEST).await.unwrap(); - let mut transfer_list = response_tx_history["result"].as_array().unwrap().clone(); + let response_transfer_history = send_request_to_uri(NFT_HISTORY_URL_TEST).await.unwrap(); + let mut transfer_list = response_transfer_history["result"].as_array().unwrap().clone(); assert!(!transfer_list.is_empty()); - let first_tx = transfer_list.remove(transfer_list.len() - 1); - let transfer_moralis: NftTxHistoryFromMoralis = serde_json::from_str(&first_tx.to_string()).unwrap(); + let first_transfer = transfer_list.remove(transfer_list.len() - 1); + let transfer_moralis: NftTransferHistoryFromMoralis = + serde_json::from_str(&first_transfer.to_string()).unwrap(); assert_eq!( TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&transfer_moralis.common.to_address) @@ -176,17 +178,17 @@ mod wasm_tests { async fn test_refresh_metadata() { test_refresh_metadata_impl().await } #[wasm_bindgen_test] - async fn test_add_get_txs() { test_add_get_txs_impl().await } + async fn test_add_get_transfers() { test_add_get_transfers_impl().await } #[wasm_bindgen_test] - async fn test_last_tx_block() { test_last_tx_block_impl().await } + async fn test_last_transfer_block() { test_last_transfer_block_impl().await } #[wasm_bindgen_test] - async fn test_tx_history() { test_tx_history_impl().await } + async fn test_transfer_history() { test_transfer_history_impl().await } #[wasm_bindgen_test] - async fn test_tx_history_filters() { test_tx_history_filters_impl().await } + async fn test_transfer_history_filters() { test_transfer_history_filters_impl().await } #[wasm_bindgen_test] - async fn test_get_update_tx_meta() { test_get_update_tx_meta_impl().await } + async fn test_get_update_transfer_meta() { test_get_update_transfer_meta_impl().await } } diff --git a/mm2src/coins/nft/storage/db_test_helpers.rs b/mm2src/coins/nft/storage/db_test_helpers.rs index 44e0ede9e1..7961d4c1ea 100644 --- a/mm2src/coins/nft/storage/db_test_helpers.rs +++ b/mm2src/coins/nft/storage/db_test_helpers.rs @@ -1,7 +1,7 @@ use crate::eth::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCommon, NftTransferCommon, NftTransferHistory, - NftTxHistoryFilters, TransferStatus, TxMeta, UriMeta}; -use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTxHistoryStorageOps, RemoveNftResult}; + NftTransferHistoryFilters, TransferMeta, TransferStatus, UriMeta}; +use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps, RemoveNftResult}; use ethereum_types::Address; use mm2_number::BigDecimal; use mm2_test_helpers::for_tests::mm_ctx_with_custom_db; @@ -17,6 +17,7 @@ cfg_wasm32! { const TOKEN_ADD: &str = "0xfd913a305d70a60aac4faac70c739563738e1f81"; const TOKEN_ID: &str = "214300044414"; const TX_HASH: &str = "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe"; +const LOG_INDEX: u32 = 495; pub(crate) fn nft() -> Nft { Nft { @@ -56,13 +57,13 @@ pub(crate) fn nft() -> Nft { } } -fn tx() -> NftTransferHistory { +fn transfer() -> NftTransferHistory { NftTransferHistory { common: NftTransferCommon { block_hash: Some("0x3d68b78391fb3cf8570df27036214f7e9a5a6a45d309197936f51d826041bfe7".to_string()), transaction_hash: "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe".to_string(), transaction_index: Some(198), - log_index: Some(495), + log_index: 495, value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), @@ -158,6 +159,44 @@ fn nft_list() -> Vec { }; let nft2 = Nft { + common: NftCommon { + token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), + token_id: BigDecimal::from_str("214300047253").unwrap(), + amount: BigDecimal::from_str("1").unwrap(), + owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), + token_hash: Some("c5d1cfd75a0535b0ec750c0156e6ddfe".to_string()), + collection_name: Some("Binance NFT Mystery Box-Back to Blockchain Future".to_string()), + symbol: Some("BMBBBF".to_string()), + token_uri: Some("https://public.nftstatic.com/static/nft/BSC/BMBBBF/214300047252".to_string()), + metadata: Some( + "{\"image\":\"https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png\"}" + .to_string(), + ), + last_token_uri_sync: Some("2023-02-16T16:35:52.392Z".to_string()), + last_metadata_sync: Some("2023-02-16T16:36:04.283Z".to_string()), + minter_address: Some("0xdbdeb0895f3681b87fb3654b5cf3e05546ba24a9".to_string()), + possible_spam: false, + }, + chain: Chain::Bsc, + + block_number_minted: Some(25721963), + block_number: 28056726, + contract_type: ContractType::Erc721, + uri_meta: UriMeta { + image_url: Some( + "https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png".to_string(), + ), + raw_image_url: None, + token_name: Some("Nebula Nodes".to_string()), + description: Some("Interchain nodes".to_string()), + attributes: None, + animation_url: None, + external_url: None, + image_details: None, + }, + }; + + let nft3 = Nft { common: NftCommon { token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), token_id: BigDecimal::from_str("214300044414").unwrap(), @@ -194,16 +233,16 @@ fn nft_list() -> Vec { image_details: None, }, }; - vec![nft, nft1, nft2] + vec![nft, nft1, nft2, nft3] } -fn nft_tx_history() -> Vec { - let tx = NftTransferHistory { +fn nft_transfer_history() -> Vec { + let transfer = NftTransferHistory { common: NftTransferCommon { block_hash: Some("0xcb41654fc5cf2bf5d7fd3f061693405c74d419def80993caded0551ecfaeaae5".to_string()), transaction_hash: "0x9c16b962f63eead1c5d2355cc9037dde178b14b53043c57eb40c27964d22ae6a".to_string(), transaction_index: Some(57), - log_index: Some(139), + log_index: 139, value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0x5c7d6712dfaf0cb079d48981781c8705e8417ca0").unwrap(), @@ -226,12 +265,12 @@ fn nft_tx_history() -> Vec { status: TransferStatus::Receive, }; - let tx1 = NftTransferHistory { + let transfer1 = NftTransferHistory { common: NftTransferCommon { block_hash: Some("0x3d68b78391fb3cf8570df27036214f7e9a5a6a45d309197936f51d826041bfe7".to_string()), transaction_hash: "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe".to_string(), transaction_index: Some(198), - log_index: Some(495), + log_index: 495, value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), @@ -256,12 +295,43 @@ fn nft_tx_history() -> Vec { status: TransferStatus::Receive, }; - let tx2 = NftTransferHistory { + // Same as transfer1 but with different log_index, 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, + value: Default::default(), + transaction_type: Some("Single".to_string()), + token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), + token_id: BigDecimal::from_str("214300047253").unwrap(), + from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), + to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), + amount: BigDecimal::from_str("1").unwrap(), + verified: Some(1), + operator: None, + possible_spam: false, + }, + chain: Chain::Bsc, + block_number: 28056726, + block_timestamp: 1683627432, + contract_type: ContractType::Erc721, + + token_uri: None, + collection_name: None, + image_url: None, + token_name: None, + + status: TransferStatus::Receive, + }; + + let transfer3 = NftTransferHistory { common: NftTransferCommon { block_hash: Some("0x326db41c5a4fd5f033676d95c590ced18936ef2ef6079e873b23af087fd966c6".to_string()), transaction_hash: "0x981bad702cc6e088f0e9b5e7287ff7a3487b8d269103cee3b9e5803141f63f91".to_string(), transaction_index: Some(83), - log_index: Some(201), + log_index: 201, value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), @@ -286,10 +356,10 @@ fn nft_tx_history() -> Vec { status: TransferStatus::Receive, }; - vec![tx, tx1, tx2] + vec![transfer, transfer1, transfer2, transfer3] } -async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTxHistoryStorageOps { +async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { let ctx = mm_ctx_with_custom_db(); let storage = NftStorageBuilder::new(&ctx).build().unwrap(); NftListStorageOps::init(&storage, chain).await.unwrap(); @@ -298,11 +368,13 @@ async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTxH storage } -async fn init_nft_history_storage(chain: &Chain) -> impl NftListStorageOps + NftTxHistoryStorageOps { +async fn init_nft_history_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { let ctx = mm_ctx_with_custom_db(); let storage = NftStorageBuilder::new(&ctx).build().unwrap(); - NftTxHistoryStorageOps::init(&storage, chain).await.unwrap(); - let is_initialized = NftTxHistoryStorageOps::is_initialized(&storage, chain).await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, chain).await.unwrap(); + let is_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain) + .await + .unwrap(); assert!(is_initialized); storage } @@ -344,14 +416,14 @@ pub(crate) async fn test_nft_list_impl() { storage.add_nfts_to_list(&chain, nft_list, 28056726).await.unwrap(); let nft_list = storage - .get_nft_list(vec![chain], false, 1, Some(NonZeroUsize::new(2).unwrap())) + .get_nft_list(vec![chain], false, 1, Some(NonZeroUsize::new(3).unwrap())) .await .unwrap(); assert_eq!(nft_list.nfts.len(), 1); let nft = nft_list.nfts.get(0).unwrap(); assert_eq!(nft.block_number, 28056721); - assert_eq!(nft_list.skipped, 1); - assert_eq!(nft_list.total, 3); + assert_eq!(nft_list.skipped, 2); + assert_eq!(nft_list.total, 4); } pub(crate) async fn test_remove_nft_impl() { @@ -372,7 +444,7 @@ pub(crate) async fn test_remove_nft_impl() { .unwrap() .nfts .len(); - assert_eq!(list_len, 2); + assert_eq!(list_len, 3); let last_scanned_block = storage.get_last_scanned_block(&chain).await.unwrap().unwrap(); assert_eq!(last_scanned_block, 28056800); } @@ -435,126 +507,126 @@ pub(crate) async fn test_refresh_metadata_impl() { assert_eq!(new_symbol.to_string(), nft_upd.common.symbol.unwrap()); } -pub(crate) async fn test_add_get_txs_impl() { +pub(crate) async fn test_add_get_transfers_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); - let tx1 = storage - .get_txs_by_token_addr_id(&chain, TOKEN_ADD.to_string(), token_id) + let transfer1 = storage + .get_transfers_by_token_addr_id(&chain, TOKEN_ADD.to_string(), token_id) .await .unwrap() .get(0) .unwrap() .clone(); - assert_eq!(tx1.block_number, 28056721); - let tx2 = storage - .get_tx_by_tx_hash(&chain, TX_HASH.to_string()) + assert_eq!(transfer1.block_number, 28056721); + let transfer2 = storage + .get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX) .await .unwrap() .unwrap(); - assert_eq!(tx2.block_number, 28056726); - let tx_from = storage.get_txs_from_block(&chain, 28056721).await.unwrap(); - assert_eq!(tx_from.len(), 2); + assert_eq!(transfer2.block_number, 28056726); + let transfer_from = storage.get_transfers_from_block(&chain, 28056721).await.unwrap(); + assert_eq!(transfer_from.len(), 3); } -pub(crate) async fn test_last_tx_block_impl() { +pub(crate) async fn test_last_transfer_block_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - let last_block = NftTxHistoryStorageOps::get_last_block_number(&storage, &chain) + let last_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, &chain) .await .unwrap() .unwrap(); assert_eq!(last_block, 28056726); } -pub(crate) async fn test_tx_history_impl() { +pub(crate) async fn test_transfer_history_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - let tx_history = storage - .get_tx_history(vec![chain], false, 1, Some(NonZeroUsize::new(2).unwrap()), None) + let transfer_history = storage + .get_transfer_history(vec![chain], false, 1, Some(NonZeroUsize::new(3).unwrap()), None) .await .unwrap(); - assert_eq!(tx_history.transfer_history.len(), 1); - let tx = tx_history.transfer_history.get(0).unwrap(); - assert_eq!(tx.block_number, 28056721); - assert_eq!(tx_history.skipped, 1); - assert_eq!(tx_history.total, 3); + assert_eq!(transfer_history.transfer_history.len(), 1); + let transfer = transfer_history.transfer_history.get(0).unwrap(); + assert_eq!(transfer.block_number, 28056721); + assert_eq!(transfer_history.skipped, 2); + assert_eq!(transfer_history.total, 4); } -pub(crate) async fn test_tx_history_filters_impl() { +pub(crate) async fn test_transfer_history_filters_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - let filters = NftTxHistoryFilters { + let filters = NftTransferHistoryFilters { receive: true, send: false, from_date: None, to_date: None, }; - let filters1 = NftTxHistoryFilters { + let filters1 = NftTransferHistoryFilters { receive: false, send: false, from_date: None, to_date: Some(1677166110), }; - let filters2 = NftTxHistoryFilters { + let filters2 = NftTransferHistoryFilters { receive: false, send: false, from_date: Some(1677166110), to_date: Some(1683627417), }; - let tx_history = storage - .get_tx_history(vec![chain], true, 1, None, Some(filters)) + let transfer_history = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters)) .await .unwrap(); - assert_eq!(tx_history.transfer_history.len(), 3); - let tx = tx_history.transfer_history.get(0).unwrap(); - assert_eq!(tx.block_number, 28056726); + assert_eq!(transfer_history.transfer_history.len(), 4); + let transfer = transfer_history.transfer_history.get(0).unwrap(); + assert_eq!(transfer.block_number, 28056726); - let tx_history1 = storage - .get_tx_history(vec![chain], true, 1, None, Some(filters1)) + let transfer_history1 = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters1)) .await .unwrap(); - assert_eq!(tx_history1.transfer_history.len(), 1); - let tx1 = tx_history1.transfer_history.get(0).unwrap(); - assert_eq!(tx1.block_number, 25919780); + assert_eq!(transfer_history1.transfer_history.len(), 1); + let transfer1 = transfer_history1.transfer_history.get(0).unwrap(); + assert_eq!(transfer1.block_number, 25919780); - let tx_history2 = storage - .get_tx_history(vec![chain], true, 1, None, Some(filters2)) + let transfer_history2 = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters2)) .await .unwrap(); - assert_eq!(tx_history2.transfer_history.len(), 2); - let tx_0 = tx_history2.transfer_history.get(0).unwrap(); - assert_eq!(tx_0.block_number, 28056721); - let tx_1 = tx_history2.transfer_history.get(1).unwrap(); - assert_eq!(tx_1.block_number, 25919780); + assert_eq!(transfer_history2.transfer_history.len(), 2); + let transfer_0 = transfer_history2.transfer_history.get(0).unwrap(); + assert_eq!(transfer_0.block_number, 28056721); + let transfer_1 = transfer_history2.transfer_history.get(1).unwrap(); + assert_eq!(transfer_1.block_number, 25919780); } -pub(crate) async fn test_get_update_tx_meta_impl() { +pub(crate) async fn test_get_update_transfer_meta_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - let vec_token_add_id = storage.get_txs_with_empty_meta(&chain).await.unwrap(); - assert_eq!(vec_token_add_id.len(), 2); + let vec_token_add_id = storage.get_transfers_with_empty_meta(&chain).await.unwrap(); + assert_eq!(vec_token_add_id.len(), 3); let token_add = "0x5c7d6712dfaf0cb079d48981781c8705e8417ca0".to_string(); - let tx_meta = TxMeta { + let transfer_meta = TransferMeta { token_address: token_add.clone(), token_id: Default::default(), token_uri: None, @@ -562,20 +634,26 @@ pub(crate) async fn test_get_update_tx_meta_impl() { image_url: None, token_name: Some("Tiki box".to_string()), }; - storage.update_txs_meta_by_token_addr_id(&chain, tx_meta).await.unwrap(); - let tx_upd = storage - .get_txs_by_token_addr_id(&chain, token_add, Default::default()) + storage + .update_transfers_meta_by_token_addr_id(&chain, transfer_meta) + .await + .unwrap(); + let transfer_upd = storage + .get_transfers_by_token_addr_id(&chain, token_add, Default::default()) .await .unwrap(); - let tx_upd = tx_upd.get(0).unwrap(); - assert_eq!(tx_upd.token_name, Some("Tiki box".to_string())); + let transfer_upd = transfer_upd.get(0).unwrap(); + assert_eq!(transfer_upd.token_name, Some("Tiki box".to_string())); - let tx_meta = tx(); - storage.update_tx_meta_by_hash(&chain, tx_meta).await.unwrap(); - let tx_by_hash = storage - .get_tx_by_tx_hash(&chain, TX_HASH.to_string()) + let transfer_meta = transfer(); + storage + .update_transfer_meta_by_hash_and_log_index(&chain, transfer_meta) + .await + .unwrap(); + let transfer_by_hash = storage + .get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX) .await .unwrap() .unwrap(); - assert_eq!(tx_by_hash.token_name, Some("Nebula Nodes".to_string())) + assert_eq!(transfer_by_hash.token_name, Some("Nebula Nodes".to_string())) } diff --git a/mm2src/coins/nft/storage/mod.rs b/mm2src/coins/nft/storage/mod.rs index d5f4319a2a..0a2e906ccc 100644 --- a/mm2src/coins/nft/storage/mod.rs +++ b/mm2src/coins/nft/storage/mod.rs @@ -1,5 +1,5 @@ -use crate::nft::nft_structs::{Chain, Nft, NftList, NftTokenAddrId, NftTransferHistory, NftTxHistoryFilters, - NftsTransferHistoryList, TxMeta}; +use crate::nft::nft_structs::{Chain, Nft, NftList, NftTokenAddrId, NftTransferHistory, NftTransferHistoryFilters, + NftsTransferHistoryList, TransferMeta}; use crate::WithdrawError; use async_trait::async_trait; use derive_more::Display; @@ -88,7 +88,7 @@ pub trait NftListStorageOps { } #[async_trait] -pub trait NftTxHistoryStorageOps { +pub trait NftTransferHistoryStorageOps { type Error: NftStorageError; /// Initializes tables in storage for the specified chain type. @@ -97,48 +97,57 @@ pub trait NftTxHistoryStorageOps { /// Whether tables are initialized for the specified chain. async fn is_initialized(&self, chain: &Chain) -> MmResult; - async fn get_tx_history( + async fn get_transfer_history( &self, chains: Vec, max: bool, limit: usize, page_number: Option, - filters: Option, + filters: Option, ) -> MmResult; - async fn add_txs_to_history(&self, chain: &Chain, txs: I) -> MmResult<(), Self::Error> + async fn add_transfers_to_history(&self, chain: &Chain, transfers: I) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send; async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error>; - /// `get_txs_from_block` function returns transfers sorted by + /// `get_transfers_from_block` function returns transfers sorted by /// block_number in ascending order. It is needed to update the NFT LIST table correctly. - async fn get_txs_from_block( + async fn get_transfers_from_block( &self, chain: &Chain, from_block: u64, ) -> MmResult, Self::Error>; - async fn get_txs_by_token_addr_id( + async fn get_transfers_by_token_addr_id( &self, chain: &Chain, token_address: String, token_id: BigDecimal, ) -> MmResult, Self::Error>; - async fn get_tx_by_tx_hash( + async fn get_transfer_by_tx_hash_and_log_index( &self, chain: &Chain, transaction_hash: String, + log_index: u32, ) -> MmResult, Self::Error>; - async fn update_tx_meta_by_hash(&self, chain: &Chain, tx: NftTransferHistory) -> MmResult<(), Self::Error>; + async fn update_transfer_meta_by_hash_and_log_index( + &self, + chain: &Chain, + transfer: NftTransferHistory, + ) -> MmResult<(), Self::Error>; - async fn update_txs_meta_by_token_addr_id(&self, chain: &Chain, tx_meta: TxMeta) -> MmResult<(), Self::Error>; + async fn update_transfers_meta_by_token_addr_id( + &self, + chain: &Chain, + transfer_meta: TransferMeta, + ) -> MmResult<(), Self::Error>; - async fn get_txs_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error>; + async fn get_transfers_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error>; } #[derive(Debug, Deserialize, Display, Serialize)] @@ -155,7 +164,7 @@ impl From for WithdrawError { } /// `NftStorageBuilder` is used to create an instance that implements the [`NftListStorageOps`] -/// and [`NftTxHistoryStorageOps`] traits.Also has guard to lock write operations. +/// and [`NftTransferHistoryStorageOps`] traits.Also has guard to lock write operations. pub struct NftStorageBuilder<'a> { ctx: &'a MmArc, } @@ -164,9 +173,9 @@ impl<'a> NftStorageBuilder<'a> { #[inline] pub fn new(ctx: &MmArc) -> NftStorageBuilder<'_> { NftStorageBuilder { ctx } } - /// `build` function is used to build nft storage which implements [`NftListStorageOps`] and [`NftTxHistoryStorageOps`] traits. + /// `build` function is used to build nft storage which implements [`NftListStorageOps`] and [`NftTransferHistoryStorageOps`] traits. #[inline] - pub fn build(&self) -> MmResult { + pub fn build(&self) -> MmResult { #[cfg(target_arch = "wasm32")] return wasm::wasm_storage::IndexedDbNftStorage::new(self.ctx); #[cfg(not(target_arch = "wasm32"))] diff --git a/mm2src/coins/nft/storage/sql_storage.rs b/mm2src/coins/nft/storage/sql_storage.rs index e37780111d..9179704467 100644 --- a/mm2src/coins/nft/storage/sql_storage.rs +++ b/mm2src/coins/nft/storage/sql_storage.rs @@ -1,8 +1,8 @@ use crate::nft::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ConvertChain, Nft, NftList, NftTokenAddrId, NftTransferHistory, - NftTxHistoryFilters, NftsTransferHistoryList, TxMeta}; + NftTransferHistoryFilters, NftsTransferHistoryList, TransferMeta}; use crate::nft::storage::{get_offset_limit, CreateNftStorageError, NftListStorageOps, NftStorageError, - NftTxHistoryStorageOps, RemoveNftResult}; + NftTransferHistoryStorageOps, RemoveNftResult}; use async_trait::async_trait; use common::async_blocking; use db_common::sql_build::{SqlCondition, SqlQuery}; @@ -22,7 +22,7 @@ use std::sync::{Arc, Mutex}; fn nft_list_table_name(chain: &Chain) -> String { chain.to_ticker() + "_nft_list" } -fn nft_tx_history_table_name(chain: &Chain) -> String { chain.to_ticker() + "_nft_tx_history" } +fn nft_transfer_history_table_name(chain: &Chain) -> String { chain.to_ticker() + "_nft_transfer_history" } fn scanned_nft_blocks_table_name() -> String { "scanned_nft_blocks".to_string() } @@ -45,12 +45,13 @@ fn create_nft_list_table_sql(chain: &Chain) -> MmResult { Ok(sql) } -fn create_tx_history_table_sql(chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); +fn create_transfer_history_table_sql(chain: &Chain) -> MmResult { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( - transaction_hash VARCHAR(256) PRIMARY KEY, + transaction_hash VARCHAR(256) NOT NULL, + log_index INTEGER NOT NULL, chain TEXT NOT NULL, block_number INTEGER NOT NULL, block_timestamp INTEGER NOT NULL, @@ -63,7 +64,8 @@ fn create_tx_history_table_sql(chain: &Chain) -> MmResult { collection_name TEXT, image_url TEXT, token_name TEXT, - details_json TEXT + details_json TEXT, + PRIMARY KEY (transaction_hash, log_index) );", table_name ); @@ -121,14 +123,14 @@ fn get_nft_list_builder_preimage(chains: Vec) -> MmResult, - filters: Option, + filters: Option, ) -> MmResult { let union_sql_strings = chains .into_iter() .map(|chain| { - let table_name = nft_tx_history_table_name(&chain); + let table_name = nft_transfer_history_table_name(&chain); validate_table_name(&table_name)?; let sql_builder = nft_history_table_builder_preimage(table_name.as_str(), filters)?; let sql_string = sql_builder @@ -148,7 +150,7 @@ fn get_nft_tx_builder_preimage( fn nft_history_table_builder_preimage( table_name: &str, - filters: Option, + filters: Option, ) -> Result { let mut sql_builder = SqlBuilder::select_from(table_name); if let Some(filters) = filters { @@ -201,7 +203,7 @@ fn nft_from_row(row: &Row<'_>) -> Result { json::from_str(&json_string).map_err(|e| SqlError::FromSqlConversionFailure(0, Type::Text, Box::new(e))) } -fn tx_history_from_row(row: &Row<'_>) -> Result { +fn transfer_history_from_row(row: &Row<'_>) -> Result { let json_string: String = row.get(0)?; json::from_str(&json_string).map_err(|e| SqlError::FromSqlConversionFailure(0, Type::Text, Box::new(e))) } @@ -231,16 +233,16 @@ fn insert_nft_in_list_sql(chain: &Chain) -> MmResult { Ok(sql) } -fn insert_tx_in_history_sql(chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); +fn insert_transfer_in_history_sql(chain: &Chain) -> MmResult { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; let sql = format!( "INSERT INTO {} ( - transaction_hash, chain, block_number, block_timestamp, contract_type, + transaction_hash, log_index, chain, block_number, block_timestamp, contract_type, token_address, token_id, status, amount, collection_name, image_url, token_name, details_json ) VALUES ( - ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13 + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14 );", table_name ); @@ -271,12 +273,12 @@ where Ok(sql) } -fn update_meta_by_tx_hash_sql(chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); +fn update_meta_by_tx_hash_and_log_index_sql(chain: &Chain) -> MmResult { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; let sql = format!( - "UPDATE {} SET token_uri = ?1, collection_name = ?2, image_url = ?3, token_name = ?4, details_json = ?5 WHERE transaction_hash = ?6;", + "UPDATE {} SET token_uri = ?1, collection_name = ?2, image_url = ?3, token_name = ?4, details_json = ?5 WHERE transaction_hash = ?6 AND log_index = ?7;", table_name ); Ok(sql) @@ -367,12 +369,12 @@ fn block_number_from_row(row: &Row<'_>) -> Result { row.get::<_, fn nft_amount_from_row(row: &Row<'_>) -> Result { row.get(0) } -fn get_txs_from_block_builder<'a>( +fn get_transfers_from_block_builder<'a>( conn: &'a Connection, chain: &'a Chain, from_block: u64, ) -> MmResult, SqlError> { - let table_name = nft_tx_history_table_name(chain); + let table_name = nft_transfer_history_table_name(chain); validate_table_name(table_name.as_str())?; let mut sql_builder = SqlQuery::select_from(conn, table_name.as_str())?; sql_builder @@ -384,8 +386,11 @@ fn get_txs_from_block_builder<'a>( Ok(sql_builder) } -fn get_txs_by_token_addr_id_statement<'a>(conn: &'a Connection, chain: &'a Chain) -> MmResult, SqlError> { - let table_name = nft_tx_history_table_name(chain); +fn get_transfers_by_token_addr_id_statement<'a>( + conn: &'a Connection, + chain: &'a Chain, +) -> MmResult, SqlError> { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(table_name.as_str())?; let sql_query = format!( "SELECT details_json FROM {} WHERE token_address = ? AND token_id = ?", @@ -395,8 +400,11 @@ fn get_txs_by_token_addr_id_statement<'a>(conn: &'a Connection, chain: &'a Chain Ok(stmt) } -fn get_txs_with_empty_meta_builder<'a>(conn: &'a Connection, chain: &'a Chain) -> MmResult, SqlError> { - let table_name = nft_tx_history_table_name(chain); +fn get_transfers_with_empty_meta_builder<'a>( + conn: &'a Connection, + chain: &'a Chain, +) -> MmResult, SqlError> { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(table_name.as_str())?; let mut sql_builder = SqlQuery::select_from(conn, table_name.as_str())?; sql_builder @@ -412,10 +420,13 @@ fn get_txs_with_empty_meta_builder<'a>(conn: &'a Connection, chain: &'a Chain) - Ok(sql_builder) } -fn get_tx_by_tx_hash_sql(chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); +fn get_transfer_by_tx_hash_and_log_index_sql(chain: &Chain) -> MmResult { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; - let sql = format!("SELECT details_json FROM {} WHERE transaction_hash=?1", table_name); + let sql = format!( + "SELECT details_json FROM {} WHERE transaction_hash=?1 AND log_index = ?2", + table_name + ); Ok(sql) } @@ -675,22 +686,22 @@ impl NftListStorageOps for SqliteNftStorage { } #[async_trait] -impl NftTxHistoryStorageOps for SqliteNftStorage { +impl NftTransferHistoryStorageOps for SqliteNftStorage { type Error = SqlError; async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error> { let selfi = self.clone(); - let sql_tx_history = create_tx_history_table_sql(chain)?; + let sql_transfer_history = create_transfer_history_table_sql(chain)?; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - conn.execute(&sql_tx_history, []).map(|_| ())?; + conn.execute(&sql_transfer_history, []).map(|_| ())?; Ok(()) }) .await } async fn is_initialized(&self, chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; let selfi = self.clone(); async_blocking(move || { @@ -701,18 +712,18 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { .await } - async fn get_tx_history( + async fn get_transfer_history( &self, chains: Vec, max: bool, limit: usize, page_number: Option, - filters: Option, + filters: Option, ) -> MmResult { let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let sql_builder = get_nft_tx_builder_preimage(chains, filters)?; + let sql_builder = get_nft_transfer_builder_preimage(chains, filters)?; let total_count_builder_sql = sql_builder .clone() .count("*") @@ -725,12 +736,12 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { let (offset, limit) = get_offset_limit(max, limit, page_number, count_total); let sql = finalize_nft_history_sql_builder(sql_builder, offset, limit)?; - let txs = conn + let transfers = conn .prepare(&sql)? - .query_map([], tx_history_from_row)? + .query_map([], transfer_history_from_row)? .collect::, _>>()?; let result = NftsTransferHistoryList { - transfer_history: txs, + transfer_history: transfers, skipped: offset, total: count_total, }; @@ -739,7 +750,7 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { .await } - async fn add_txs_to_history(&self, chain: &Chain, txs: I) -> MmResult<(), Self::Error> + async fn add_transfers_to_history(&self, chain: &Chain, transfers: I) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send, @@ -750,24 +761,25 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { let mut conn = selfi.0.lock().unwrap(); let sql_transaction = conn.transaction()?; - for tx in txs { - let tx_json = json::to_string(&tx).expect("serialization should not fail"); + for transfer in transfers { + let transfer_json = json::to_string(&transfer).expect("serialization should not fail"); let params = [ - Some(tx.common.transaction_hash), - Some(tx.chain.to_string()), - Some(tx.block_number.to_string()), - Some(tx.block_timestamp.to_string()), - Some(tx.contract_type.to_string()), - Some(eth_addr_to_hex(&tx.common.token_address)), - Some(tx.common.token_id.to_string()), - Some(tx.status.to_string()), - Some(tx.common.amount.to_string()), - tx.collection_name, - tx.image_url, - tx.token_name, - Some(tx_json), + Some(transfer.common.transaction_hash), + Some(transfer.common.log_index.to_string()), + Some(transfer.chain.to_string()), + Some(transfer.block_number.to_string()), + Some(transfer.block_timestamp.to_string()), + Some(transfer.contract_type.to_string()), + Some(eth_addr_to_hex(&transfer.common.token_address)), + Some(transfer.common.token_id.to_string()), + Some(transfer.status.to_string()), + Some(transfer.common.amount.to_string()), + transfer.collection_name, + transfer.image_url, + transfer.token_name, + Some(transfer_json), ]; - sql_transaction.execute(&insert_tx_in_history_sql(&chain)?, params)?; + sql_transaction.execute(&insert_transfer_in_history_sql(&chain)?, params)?; } sql_transaction.commit()?; Ok(()) @@ -776,7 +788,7 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { } async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { - let sql = select_last_block_number_sql(chain, nft_tx_history_table_name)?; + let sql = select_last_block_number_sql(chain, nft_transfer_history_table_name)?; let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); @@ -788,7 +800,7 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { .map_to_mm(|e| SqlError::FromSqlConversionFailure(2, Type::Integer, Box::new(e))) } - async fn get_txs_from_block( + async fn get_transfers_from_block( &self, chain: &Chain, from_block: u64, @@ -797,14 +809,14 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { let chain = *chain; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let sql_builder = get_txs_from_block_builder(&conn, &chain, from_block)?; - let txs = sql_builder.query(tx_history_from_row)?; - Ok(txs) + let sql_builder = get_transfers_from_block_builder(&conn, &chain, from_block)?; + let transfers = sql_builder.query(transfer_history_from_row)?; + Ok(transfers) }) .await } - async fn get_txs_by_token_addr_id( + async fn get_transfers_by_token_addr_id( &self, chain: &Chain, token_address: String, @@ -814,39 +826,51 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { let chain = *chain; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let mut stmt = get_txs_by_token_addr_id_statement(&conn, &chain)?; - let txs = stmt - .query_map([token_address, token_id.to_string()], tx_history_from_row)? + let mut stmt = get_transfers_by_token_addr_id_statement(&conn, &chain)?; + let transfers = stmt + .query_map([token_address, token_id.to_string()], transfer_history_from_row)? .collect::, _>>()?; - Ok(txs) + Ok(transfers) }) .await } - async fn get_tx_by_tx_hash( + async fn get_transfer_by_tx_hash_and_log_index( &self, chain: &Chain, transaction_hash: String, + log_index: u32, ) -> MmResult, Self::Error> { - let sql = get_tx_by_tx_hash_sql(chain)?; + let sql = get_transfer_by_tx_hash_and_log_index_sql(chain)?; let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); - query_single_row(&conn, &sql, [transaction_hash], tx_history_from_row).map_to_mm(SqlError::from) + query_single_row( + &conn, + &sql, + [transaction_hash, log_index.to_string()], + transfer_history_from_row, + ) + .map_to_mm(SqlError::from) }) .await } - async fn update_tx_meta_by_hash(&self, chain: &Chain, tx: NftTransferHistory) -> MmResult<(), Self::Error> { - let sql = update_meta_by_tx_hash_sql(chain)?; - let tx_json = json::to_string(&tx).expect("serialization should not fail"); + async fn update_transfer_meta_by_hash_and_log_index( + &self, + chain: &Chain, + transfer: NftTransferHistory, + ) -> MmResult<(), Self::Error> { + let sql = update_meta_by_tx_hash_and_log_index_sql(chain)?; + let transfer_json = json::to_string(&transfer).expect("serialization should not fail"); let params = [ - tx.token_uri, - tx.collection_name, - tx.image_url, - tx.token_name, - Some(tx_json), - Some(tx.common.transaction_hash), + transfer.token_uri, + transfer.collection_name, + transfer.image_url, + transfer.token_name, + Some(transfer_json), + Some(transfer.common.transaction_hash), + Some(transfer.common.log_index.to_string()), ]; let selfi = self.clone(); async_blocking(move || { @@ -859,28 +883,34 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { .await } - async fn update_txs_meta_by_token_addr_id(&self, chain: &Chain, tx_meta: TxMeta) -> MmResult<(), Self::Error> { + async fn update_transfers_meta_by_token_addr_id( + &self, + chain: &Chain, + transfer_meta: TransferMeta, + ) -> MmResult<(), Self::Error> { let selfi = self.clone(); - let txs = selfi - .get_txs_by_token_addr_id(chain, tx_meta.token_address, tx_meta.token_id) + let transfers = selfi + .get_transfers_by_token_addr_id(chain, transfer_meta.token_address, transfer_meta.token_id) .await?; - for mut tx in txs.into_iter() { - tx.token_uri = tx_meta.token_uri.clone(); - tx.collection_name = tx_meta.collection_name.clone(); - tx.image_url = tx_meta.image_url.clone(); - tx.token_name = tx_meta.token_name.clone(); - drop_mutability!(tx); - selfi.update_tx_meta_by_hash(chain, tx).await?; + for mut transfer in transfers.into_iter() { + transfer.token_uri = transfer_meta.token_uri.clone(); + transfer.collection_name = transfer_meta.collection_name.clone(); + transfer.image_url = transfer_meta.image_url.clone(); + transfer.token_name = transfer_meta.token_name.clone(); + drop_mutability!(transfer); + selfi + .update_transfer_meta_by_hash_and_log_index(chain, transfer) + .await?; } Ok(()) } - async fn get_txs_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { + async fn get_transfers_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { let selfi = self.clone(); let chain = *chain; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let sql_builder = get_txs_with_empty_meta_builder(&conn, &chain)?; + let sql_builder = get_transfers_with_empty_meta_builder(&conn, &chain)?; let token_addr_id_pair = sql_builder.query(token_address_id_from_row)?; Ok(token_addr_id_pair) }) diff --git a/mm2src/coins/nft/storage/wasm/nft_idb.rs b/mm2src/coins/nft/storage/wasm/nft_idb.rs index 287bcab30a..0d7758d61a 100644 --- a/mm2src/coins/nft/storage/wasm/nft_idb.rs +++ b/mm2src/coins/nft/storage/wasm/nft_idb.rs @@ -1,4 +1,4 @@ -use crate::nft::storage::wasm::wasm_storage::{LastScannedBlockTable, NftListTable, NftTxHistoryTable}; +use crate::nft::storage::wasm::wasm_storage::{LastScannedBlockTable, NftListTable, NftTransferHistoryTable}; use async_trait::async_trait; use mm2_db::indexed_db::InitDbResult; use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedDbBuilder}; @@ -19,7 +19,7 @@ impl DbInstance for NftCacheIDB { let inner = IndexedDbBuilder::new(db_id) .with_version(DB_VERSION) .with_table::() - .with_table::() + .with_table::() .with_table::() .build() .await?; diff --git a/mm2src/coins/nft/storage/wasm/wasm_storage.rs b/mm2src/coins/nft/storage/wasm/wasm_storage.rs index 6fb341c26e..058b6cdffd 100644 --- a/mm2src/coins/nft/storage/wasm/wasm_storage.rs +++ b/mm2src/coins/nft/storage/wasm/wasm_storage.rs @@ -1,10 +1,10 @@ use crate::eth::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCtx, NftList, NftTransferHistory, NftsTransferHistoryList, - TransferStatus, TxMeta}; + TransferMeta, TransferStatus}; use crate::nft::storage::wasm::nft_idb::{NftCacheIDB, NftCacheIDBLocked}; use crate::nft::storage::wasm::{WasmNftCacheError, WasmNftCacheResult}; use crate::nft::storage::{get_offset_limit, CreateNftStorageError, NftListStorageOps, NftTokenAddrId, - NftTxHistoryFilters, NftTxHistoryStorageOps, RemoveNftResult}; + NftTransferHistoryFilters, NftTransferHistoryStorageOps, RemoveNftResult}; use async_trait::async_trait; use common::is_initial_upgrade; use mm2_core::mm_ctx::MmArc; @@ -52,54 +52,54 @@ impl IndexedDbNftStorage { }) } - fn take_txs_according_to_paging_opts( - mut txs: Vec, + fn take_transfers_according_to_paging_opts( + mut transfers: Vec, max: bool, limit: usize, page_number: Option, ) -> WasmNftCacheResult { - let total_count = txs.len(); - txs.sort_by(|a, b| b.block_timestamp.cmp(&a.block_timestamp)); + let total_count = transfers.len(); + transfers.sort_by(|a, b| b.block_timestamp.cmp(&a.block_timestamp)); let (offset, limit) = get_offset_limit(max, limit, page_number, total_count); Ok(NftsTransferHistoryList { - transfer_history: txs.into_iter().skip(offset).take(limit).collect(), + transfer_history: transfers.into_iter().skip(offset).take(limit).collect(), skipped: offset, total: total_count, }) } - fn take_txs_according_to_filters( - txs: I, - filters: Option, + fn take_transfers_according_to_filters( + transfers: I, + filters: Option, ) -> WasmNftCacheResult> where - I: Iterator, + I: Iterator, { - let mut filtered_txs = Vec::new(); - for tx_table in txs { - let tx = tx_details_from_item(tx_table)?; + let mut filtered_transfers = Vec::new(); + for transfers_table in transfers { + let transfer = transfer_details_from_item(transfers_table)?; if let Some(filters) = &filters { - if filters.is_status_match(&tx) && filters.is_date_match(&tx) { - filtered_txs.push(tx); + if filters.is_status_match(&transfer) && filters.is_date_match(&transfer) { + filtered_transfers.push(transfer); } } else { - filtered_txs.push(tx); + filtered_transfers.push(transfer); } } - Ok(filtered_txs) + Ok(filtered_transfers) } } -impl NftTxHistoryFilters { - fn is_status_match(&self, tx: &NftTransferHistory) -> bool { +impl NftTransferHistoryFilters { + fn is_status_match(&self, transfer: &NftTransferHistory) -> bool { (!self.receive && !self.send) - || (self.receive && tx.status == TransferStatus::Receive) - || (self.send && tx.status == TransferStatus::Send) + || (self.receive && transfer.status == TransferStatus::Receive) + || (self.send && transfer.status == TransferStatus::Send) } - fn is_date_match(&self, tx: &NftTransferHistory) -> bool { - self.from_date.map_or(true, |from| tx.block_timestamp >= from) - && self.to_date.map_or(true, |to| tx.block_timestamp <= to) + fn is_date_match(&self, transfer: &NftTransferHistory) -> bool { + self.from_date.map_or(true, |from| transfer.block_timestamp >= from) + && self.to_date.map_or(true, |to| transfer.block_timestamp <= to) } } @@ -318,48 +318,48 @@ impl NftListStorageOps for IndexedDbNftStorage { } #[async_trait] -impl NftTxHistoryStorageOps for IndexedDbNftStorage { +impl NftTransferHistoryStorageOps for IndexedDbNftStorage { type Error = WasmNftCacheError; async fn init(&self, _chain: &Chain) -> MmResult<(), Self::Error> { Ok(()) } async fn is_initialized(&self, _chain: &Chain) -> MmResult { Ok(true) } - async fn get_tx_history( + async fn get_transfer_history( &self, chains: Vec, max: bool, limit: usize, page_number: Option, - filters: Option, + filters: Option, ) -> MmResult { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - let mut txs = Vec::new(); + let table = db_transaction.table::().await?; + let mut transfers = Vec::new(); for chain in chains { - let tx_tables = table + let transfer_tables = table .get_items("chain", chain.to_string()) .await? .into_iter() - .map(|(_item_id, tx)| tx); - let filtered = Self::take_txs_according_to_filters(tx_tables, filters)?; - txs.extend(filtered); + .map(|(_item_id, transfer)| transfer); + let filtered = Self::take_transfers_according_to_filters(transfer_tables, filters)?; + transfers.extend(filtered); } - Self::take_txs_according_to_paging_opts(txs, max, limit, page_number) + Self::take_transfers_according_to_paging_opts(transfers, max, limit, page_number) } - async fn add_txs_to_history(&self, _chain: &Chain, txs: I) -> MmResult<(), Self::Error> + async fn add_transfers_to_history(&self, _chain: &Chain, transfers: I) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send, { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - for tx in txs { - let tx_item = NftTxHistoryTable::from_tx_history(&tx)?; - table.add_item(&tx_item).await?; + let table = db_transaction.table::().await?; + for transfer in transfers { + let transfer_item = NftTransferHistoryTable::from_transfer_history(&transfer)?; + table.add_item(&transfer_item).await?; } Ok(()) } @@ -367,24 +367,24 @@ impl NftTxHistoryStorageOps for IndexedDbNftStorage { async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - get_last_block_from_table(chain, table, NftTxHistoryTable::CHAIN_BLOCK_NUMBER_INDEX).await + let table = db_transaction.table::().await?; + get_last_block_from_table(chain, table, NftTransferHistoryTable::CHAIN_BLOCK_NUMBER_INDEX).await } - async fn get_txs_from_block( + async fn get_transfers_from_block( &self, chain: &Chain, from_block: u64, ) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; + let table = db_transaction.table::().await?; let items = table .cursor_builder() .only("chain", chain.to_string()) .map_err(|e| WasmNftCacheError::GetLastNftBlockError(e.to_string()))? .bound("block_number", BeBigUint::from(from_block), BeBigUint::from(u64::MAX)) - .open_cursor(NftTxHistoryTable::CHAIN_BLOCK_NUMBER_INDEX) + .open_cursor(NftTransferHistoryTable::CHAIN_BLOCK_NUMBER_INDEX) .await .map_err(|e| WasmNftCacheError::GetLastNftBlockError(e.to_string()))? .collect() @@ -393,13 +393,13 @@ impl NftTxHistoryStorageOps for IndexedDbNftStorage { let mut res = Vec::new(); for (_item_id, item) in items.into_iter() { - let tx = tx_details_from_item(item)?; - res.push(tx); + let transfer = transfer_details_from_item(item)?; + res.push(transfer); } Ok(res) } - async fn get_txs_by_token_addr_id( + async fn get_transfers_by_token_addr_id( &self, chain: &Chain, token_address: String, @@ -407,9 +407,9 @@ impl NftTxHistoryStorageOps for IndexedDbNftStorage { ) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; + let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTxHistoryTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? .with_value(token_id.to_string())?; @@ -418,71 +418,83 @@ impl NftTxHistoryStorageOps for IndexedDbNftStorage { .get_items_by_multi_index(index_keys) .await? .into_iter() - .map(|(_item_id, item)| tx_details_from_item(item)) + .map(|(_item_id, item)| transfer_details_from_item(item)) .collect() } - async fn get_tx_by_tx_hash( + async fn get_transfer_by_tx_hash_and_log_index( &self, chain: &Chain, transaction_hash: String, + log_index: u32, ) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTxHistoryTable::CHAIN_TX_HASH_INDEX) + let table = db_transaction.table::().await?; + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) .with_value(chain.to_string())? - .with_value(&transaction_hash)?; + .with_value(&transaction_hash)? + .with_value(log_index)?; if let Some((_item_id, item)) = table.get_item_by_unique_multi_index(index_keys).await? { - Ok(Some(tx_details_from_item(item)?)) + Ok(Some(transfer_details_from_item(item)?)) } else { Ok(None) } } - async fn update_tx_meta_by_hash(&self, chain: &Chain, tx: NftTransferHistory) -> MmResult<(), Self::Error> { + async fn update_transfer_meta_by_hash_and_log_index( + &self, + chain: &Chain, + transfer: NftTransferHistory, + ) -> MmResult<(), Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; + let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTxHistoryTable::CHAIN_TX_HASH_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) .with_value(chain.to_string())? - .with_value(&tx.common.transaction_hash)?; + .with_value(&transfer.common.transaction_hash)? + .with_value(transfer.common.log_index)?; - let item = NftTxHistoryTable::from_tx_history(&tx)?; + let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; table.replace_item_by_unique_multi_index(index_keys, &item).await?; Ok(()) } - async fn update_txs_meta_by_token_addr_id(&self, chain: &Chain, tx_meta: TxMeta) -> MmResult<(), Self::Error> { - let txs: Vec = self - .get_txs_by_token_addr_id(chain, tx_meta.token_address, tx_meta.token_id) + async fn update_transfers_meta_by_token_addr_id( + &self, + chain: &Chain, + transfer_meta: TransferMeta, + ) -> MmResult<(), Self::Error> { + let transfers: Vec = self + .get_transfers_by_token_addr_id(chain, transfer_meta.token_address, transfer_meta.token_id) .await?; let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - for mut tx in txs { - tx.token_uri = tx_meta.token_uri.clone(); - tx.collection_name = tx_meta.collection_name.clone(); - tx.image_url = tx_meta.image_url.clone(); - tx.token_name = tx_meta.token_name.clone(); - drop_mutability!(tx); - - let index_keys = MultiIndex::new(NftTxHistoryTable::CHAIN_TX_HASH_INDEX) + let table = db_transaction.table::().await?; + for mut transfer in transfers { + transfer.token_uri = transfer_meta.token_uri.clone(); + transfer.collection_name = transfer_meta.collection_name.clone(); + transfer.image_url = transfer_meta.image_url.clone(); + transfer.token_name = transfer_meta.token_name.clone(); + drop_mutability!(transfer); + + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) .with_value(chain.to_string())? - .with_value(&tx.common.transaction_hash)?; + .with_value(&transfer.common.transaction_hash)? + .with_value(transfer.common.log_index)?; - let item = NftTxHistoryTable::from_tx_history(&tx)?; + let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; table.replace_item_by_unique_multi_index(index_keys, &item).await?; } Ok(()) } - async fn get_txs_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { + async fn get_transfers_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; + let table = db_transaction.table::().await?; let items = table .cursor_builder() .only("chain", chain.to_string()) @@ -553,7 +565,7 @@ impl BlockNumberTable for NftListTable { fn get_block_number(&self) -> &BeBigUint { &self.block_number } } -impl BlockNumberTable for NftTxHistoryTable { +impl BlockNumberTable for NftTransferHistoryTable { fn get_block_number(&self) -> &BeBigUint { &self.block_number } } @@ -607,8 +619,9 @@ impl TableSignature for NftListTable { } #[derive(Clone, Debug, Deserialize, Serialize)] -pub(crate) struct NftTxHistoryTable { +pub(crate) struct NftTransferHistoryTable { transaction_hash: String, + log_index: u32, chain: String, block_number: BeBigUint, block_timestamp: BeBigUint, @@ -624,36 +637,38 @@ pub(crate) struct NftTxHistoryTable { details_json: Json, } -impl NftTxHistoryTable { +impl NftTransferHistoryTable { const CHAIN_TOKEN_ADD_TOKEN_ID_INDEX: &str = "chain_token_add_token_id_index"; - const CHAIN_TX_HASH_INDEX: &str = "chain_tx_hash_index"; + const CHAIN_TX_HASH_LOG_INDEX_INDEX: &str = "chain_tx_hash_log_index_index"; const CHAIN_BLOCK_NUMBER_INDEX: &str = "chain_block_number_index"; - fn from_tx_history(tx: &NftTransferHistory) -> WasmNftCacheResult { - let details_json = json::to_value(tx).map_to_mm(|e| WasmNftCacheError::ErrorSerializing(e.to_string()))?; - Ok(NftTxHistoryTable { - transaction_hash: tx.common.transaction_hash.clone(), - chain: tx.chain.to_string(), - block_number: BeBigUint::from(tx.block_number), - block_timestamp: BeBigUint::from(tx.block_timestamp), - contract_type: tx.contract_type, - token_address: eth_addr_to_hex(&tx.common.token_address), - token_id: tx.common.token_id.to_string(), - status: tx.status, - amount: tx.common.amount.to_string(), - token_uri: tx.token_uri.clone(), - collection_name: tx.collection_name.clone(), - image_url: tx.image_url.clone(), - token_name: tx.token_name.clone(), + fn from_transfer_history(transfer: &NftTransferHistory) -> WasmNftCacheResult { + let details_json = + json::to_value(transfer).map_to_mm(|e| WasmNftCacheError::ErrorSerializing(e.to_string()))?; + Ok(NftTransferHistoryTable { + transaction_hash: transfer.common.transaction_hash.clone(), + log_index: transfer.common.log_index, + chain: transfer.chain.to_string(), + block_number: BeBigUint::from(transfer.block_number), + block_timestamp: BeBigUint::from(transfer.block_timestamp), + contract_type: transfer.contract_type, + token_address: eth_addr_to_hex(&transfer.common.token_address), + token_id: transfer.common.token_id.to_string(), + status: transfer.status, + amount: transfer.common.amount.to_string(), + token_uri: transfer.token_uri.clone(), + collection_name: transfer.collection_name.clone(), + image_url: transfer.image_url.clone(), + token_name: transfer.token_name.clone(), details_json, }) } } -impl TableSignature for NftTxHistoryTable { - fn table_name() -> &'static str { "nft_tx_history_cache_table" } +impl TableSignature for NftTransferHistoryTable { + fn table_name() -> &'static str { "nft_transfer_history_cache_table" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { if is_initial_upgrade(old_version, new_version) { @@ -663,7 +678,11 @@ impl TableSignature for NftTxHistoryTable { &["chain", "token_address", "token_id"], false, )?; - table.create_multi_index(Self::CHAIN_TX_HASH_INDEX, &["chain", "transaction_hash"], true)?; + table.create_multi_index( + Self::CHAIN_TX_HASH_LOG_INDEX_INDEX, + &["chain", "transaction_hash", "log_index"], + true, + )?; table.create_multi_index(Self::CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; table.create_index("block_number", false)?; table.create_index("chain", false)?; @@ -694,6 +713,6 @@ fn nft_details_from_item(item: NftListTable) -> WasmNftCacheResult { json::from_value(item.details_json).map_to_mm(|e| WasmNftCacheError::ErrorDeserializing(e.to_string())) } -fn tx_details_from_item(item: NftTxHistoryTable) -> WasmNftCacheResult { +fn transfer_details_from_item(item: NftTransferHistoryTable) -> WasmNftCacheResult { json::from_value(item.details_json).map_to_mm(|e| WasmNftCacheError::ErrorDeserializing(e.to_string())) }