diff --git a/rust/processor/migrations/2024-04-18-173631_fungible_token/down.sql b/rust/processor/migrations/2024-04-18-173631_fungible_token/down.sql new file mode 100644 index 000000000..a97ee07d9 --- /dev/null +++ b/rust/processor/migrations/2024-04-18-173631_fungible_token/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE fungible_asset_metadata +DROP COLUMN supply_v2, +DROP COLUMN maximum_v2; \ No newline at end of file diff --git a/rust/processor/migrations/2024-04-18-173631_fungible_token/up.sql b/rust/processor/migrations/2024-04-18-173631_fungible_token/up.sql new file mode 100644 index 000000000..186d5fa15 --- /dev/null +++ b/rust/processor/migrations/2024-04-18-173631_fungible_token/up.sql @@ -0,0 +1,12 @@ +-- Your SQL goes here +ALTER TABLE fungible_asset_metadata +ADD COLUMN supply_v2 NUMERIC, +ADD COLUMN maximum_v2 NUMERIC; + +ALTER TABLE current_token_datas_v2 +ALTER COLUMN supply DROP NOT NULL, +ALTER COLUMN decimals DROP NOT NULL; + +ALTER TABLE token_datas_v2 +ALTER COLUMN supply DROP NOT NULL, +ALTER COLUMN decimals DROP NOT NULL; \ No newline at end of file diff --git a/rust/processor/src/config.rs b/rust/processor/src/config.rs index 63c59e94f..92a696f68 100644 --- a/rust/processor/src/config.rs +++ b/rust/processor/src/config.rs @@ -114,6 +114,9 @@ pub struct IndexerGrpcHttp2Config { /// Indexer GRPC http2 ping timeout in seconds. Defaults to 10. indexer_grpc_http2_ping_timeout_in_secs: u64, + + /// Seconds before timeout for grpc connection. + indexer_grpc_connection_timeout_secs: u64, } impl IndexerGrpcHttp2Config { @@ -124,6 +127,10 @@ impl IndexerGrpcHttp2Config { pub fn grpc_http2_ping_timeout_in_secs(&self) -> Duration { Duration::from_secs(self.indexer_grpc_http2_ping_timeout_in_secs) } + + pub fn grpc_connection_timeout_secs(&self) -> Duration { + Duration::from_secs(self.indexer_grpc_connection_timeout_secs) + } } impl Default for IndexerGrpcHttp2Config { @@ -131,6 +138,7 @@ impl Default for IndexerGrpcHttp2Config { Self { indexer_grpc_http2_ping_interval_in_secs: 30, indexer_grpc_http2_ping_timeout_in_secs: 10, + indexer_grpc_connection_timeout_secs: 5, } } } diff --git a/rust/processor/src/grpc_stream.rs b/rust/processor/src/grpc_stream.rs index 9f593e8b3..39c0c0942 100644 --- a/rust/processor/src/grpc_stream.rs +++ b/rust/processor/src/grpc_stream.rs @@ -74,6 +74,7 @@ pub async fn get_stream( indexer_grpc_data_service_address: Url, indexer_grpc_http2_ping_interval: Duration, indexer_grpc_http2_ping_timeout: Duration, + indexer_grpc_reconnection_timeout_secs: Duration, starting_version: u64, ending_version: Option, auth_token: String, @@ -121,7 +122,7 @@ pub async fn get_stream( let mut connect_retries = 0; let connect_res = loop { let res = timeout( - Duration::from_secs(5), + indexer_grpc_reconnection_timeout_secs, RawDataClient::connect(channel.clone()), ) .await; @@ -177,17 +178,65 @@ pub async fn get_stream( num_of_transactions = ?count, "[Parser] Setting up GRPC stream", ); - let request = grpc_request_builder(starting_version, count, auth_token, processor_name); - rpc_client - .get_transactions(request) - .await - .expect("[Parser] Failed to get grpc response. Is the server running?") + + // TODO: move this to a config file + // Retry this connection a few times before giving up + let mut connect_retries = 0; + let stream_res = loop { + let timeout_res = timeout(indexer_grpc_reconnection_timeout_secs, async { + let request = grpc_request_builder( + starting_version, + count, + auth_token.clone(), + processor_name.clone(), + ); + rpc_client.get_transactions(request).await + }) + .await; + match timeout_res { + Ok(client) => break Ok(client), + Err(e) => { + error!( + processor_name = processor_name, + service_type = crate::worker::PROCESSOR_SERVICE_TYPE, + stream_address = indexer_grpc_data_service_address.to_string(), + start_version = starting_version, + end_version = ending_version, + retries = connect_retries, + error = ?e, + "[Parser] Timeout making grpc request. Retrying...", + ); + connect_retries += 1; + if connect_retries >= RECONNECTION_MAX_RETRIES { + break Err(e); + } + }, + } + } + .expect("[Parser] Timed out making grpc request after max retries."); + + match stream_res { + Ok(stream) => stream, + Err(e) => { + error!( + processor_name = processor_name, + service_type = crate::worker::PROCESSOR_SERVICE_TYPE, + stream_address = indexer_grpc_data_service_address.to_string(), + start_version = starting_version, + ending_version = ending_version, + error = ?e, + "[Parser] Failed to get grpc response. Is the server running?" + ); + panic!("[Parser] Failed to get grpc response. Is the server running?"); + }, + } } pub async fn get_chain_id( indexer_grpc_data_service_address: Url, indexer_grpc_http2_ping_interval: Duration, indexer_grpc_http2_ping_timeout: Duration, + indexer_grpc_reconnection_timeout_secs: Duration, auth_token: String, processor_name: String, ) -> u64 { @@ -201,6 +250,7 @@ pub async fn get_chain_id( indexer_grpc_data_service_address.clone(), indexer_grpc_http2_ping_interval, indexer_grpc_http2_ping_timeout, + indexer_grpc_reconnection_timeout_secs, 1, Some(2), auth_token.clone(), @@ -257,6 +307,7 @@ pub async fn create_fetcher_loop( indexer_grpc_data_service_address: Url, indexer_grpc_http2_ping_interval: Duration, indexer_grpc_http2_ping_timeout: Duration, + indexer_grpc_reconnection_timeout_secs: Duration, starting_version: u64, request_ending_version: Option, auth_token: String, @@ -277,6 +328,7 @@ pub async fn create_fetcher_loop( indexer_grpc_data_service_address.clone(), indexer_grpc_http2_ping_interval, indexer_grpc_http2_ping_timeout, + indexer_grpc_reconnection_timeout_secs, starting_version, request_ending_version, auth_token.clone(), @@ -587,6 +639,7 @@ pub async fn create_fetcher_loop( indexer_grpc_data_service_address.clone(), indexer_grpc_http2_ping_interval, indexer_grpc_http2_ping_timeout, + indexer_grpc_reconnection_timeout_secs, next_version_to_fetch, request_ending_version, auth_token.clone(), diff --git a/rust/processor/src/models/fungible_asset_models/v2_fungible_asset_activities.rs b/rust/processor/src/models/fungible_asset_models/v2_fungible_asset_activities.rs index acf2a6b9d..06802ba1e 100644 --- a/rust/processor/src/models/fungible_asset_models/v2_fungible_asset_activities.rs +++ b/rust/processor/src/models/fungible_asset_models/v2_fungible_asset_activities.rs @@ -5,10 +5,7 @@ #![allow(clippy::extra_unused_lifetimes)] #![allow(clippy::unused_unit)] -use super::{ - v2_fungible_asset_utils::{FeeStatement, FungibleAssetEvent}, - v2_fungible_metadata::FungibleAssetMetadataModel, -}; +use super::v2_fungible_asset_utils::{FeeStatement, FungibleAssetEvent}; use crate::{ models::{ coin_models::{ @@ -19,7 +16,7 @@ use crate::{ token_v2_models::v2_token_utils::TokenStandard, }, schema::fungible_asset_activities, - utils::{database::PgPoolConnection, util::standardize_address}, + utils::util::standardize_address, }; use ahash::AHashMap; use anyhow::Context; @@ -70,7 +67,6 @@ impl FungibleAssetActivity { event_index: i64, entry_function_id_str: &Option, object_aggregated_data_mapping: &ObjectAggregatedDataMapping, - conn: &mut PgPoolConnection<'_>, ) -> anyhow::Result> { let event_type = event.type_str.clone(); if let Some(fa_event) = @@ -84,17 +80,6 @@ impl FungibleAssetActivity { let object_core = &object_metadata.object.object_core; let fungible_asset = object_metadata.fungible_asset_store.as_ref().unwrap(); let asset_type = fungible_asset.metadata.get_reference_address(); - // If it's a fungible token, return early - if !FungibleAssetMetadataModel::is_address_fungible_asset( - conn, - &asset_type, - object_aggregated_data_mapping, - txn_version, - ) - .await - { - return Ok(None); - } let (is_frozen, amount) = match fa_event { FungibleAssetEvent::WithdrawEvent(inner) => (None, Some(inner.amount.clone())), diff --git a/rust/processor/src/models/fungible_asset_models/v2_fungible_asset_balances.rs b/rust/processor/src/models/fungible_asset_models/v2_fungible_asset_balances.rs index 744518d4a..dce861672 100644 --- a/rust/processor/src/models/fungible_asset_models/v2_fungible_asset_balances.rs +++ b/rust/processor/src/models/fungible_asset_models/v2_fungible_asset_balances.rs @@ -7,7 +7,6 @@ use super::{ v2_fungible_asset_activities::EventToCoinType, v2_fungible_asset_utils::FungibleAssetStore, - v2_fungible_metadata::FungibleAssetMetadataModel, }; use crate::{ models::{ @@ -16,7 +15,7 @@ use crate::{ token_v2_models::v2_token_utils::TokenStandard, }, schema::{current_fungible_asset_balances, fungible_asset_balances}, - utils::{database::PgPoolConnection, util::standardize_address}, + utils::util::standardize_address, }; use ahash::AHashMap; use aptos_protos::transaction::v1::WriteResource; @@ -71,7 +70,6 @@ impl FungibleAssetBalance { txn_version: i64, txn_timestamp: chrono::NaiveDateTime, object_metadatas: &ObjectAggregatedDataMapping, - conn: &mut PgPoolConnection<'_>, ) -> anyhow::Result> { if let Some(inner) = &FungibleAssetStore::from_write_resource(write_resource, txn_version)? { @@ -81,17 +79,6 @@ impl FungibleAssetBalance { let object = &object_data.object.object_core; let owner_address = object.get_owner_address(); let asset_type = inner.metadata.get_reference_address(); - // If it's a fungible token, return early - if !FungibleAssetMetadataModel::is_address_fungible_asset( - conn, - &asset_type, - object_metadatas, - txn_version, - ) - .await - { - return Ok(None); - } let is_primary = Self::is_primary(&owner_address, &asset_type, &storage_id); let coin_balance = Self { diff --git a/rust/processor/src/models/fungible_asset_models/v2_fungible_metadata.rs b/rust/processor/src/models/fungible_asset_models/v2_fungible_metadata.rs index 1332ffa88..1acb60f76 100644 --- a/rust/processor/src/models/fungible_asset_models/v2_fungible_metadata.rs +++ b/rust/processor/src/models/fungible_asset_models/v2_fungible_metadata.rs @@ -13,12 +13,12 @@ use crate::{ token_v2_models::v2_token_utils::TokenStandard, }, schema::fungible_asset_metadata, - utils::{database::PgPoolConnection, util::standardize_address}, + utils::util::standardize_address, }; use ahash::AHashMap; use aptos_protos::transaction::v1::WriteResource; +use bigdecimal::BigDecimal; use diesel::prelude::*; -use diesel_async::RunQueryDsl; use field_count::FieldCount; use serde::{Deserialize, Serialize}; @@ -44,26 +44,8 @@ pub struct FungibleAssetMetadataModel { pub supply_aggregator_table_key_v1: Option, pub token_standard: String, pub is_token_v2: Option, -} - -#[derive(Debug, Deserialize, Identifiable, Queryable, Serialize)] -#[diesel(primary_key(asset_type))] -#[diesel(table_name = fungible_asset_metadata)] -pub struct FungibleAssetMetadataQuery { - pub asset_type: String, - pub creator_address: String, - pub name: String, - pub symbol: String, - pub decimals: i32, - pub icon_uri: Option, - pub project_uri: Option, - pub last_transaction_version: i64, - pub last_transaction_timestamp: chrono::NaiveDateTime, - pub supply_aggregator_table_handle_v1: Option, - pub supply_aggregator_table_key_v1: Option, - pub token_standard: String, - pub inserted_at: chrono::NaiveDateTime, - pub is_token_v2: Option, + pub supply_v2: Option, + pub maximum_v2: Option, } impl FungibleAssetMetadataModel { @@ -81,7 +63,16 @@ impl FungibleAssetMetadataModel { let asset_type = standardize_address(&write_resource.address.to_string()); if let Some(object_metadata) = object_metadatas.get(&asset_type) { let object = &object_metadata.object.object_core; - let is_token_v2 = object_metadata.token.is_some(); + let fungible_asset_supply = object_metadata.fungible_asset_supply.as_ref(); + let (maximum_v2, supply_v2) = + if let Some(fungible_asset_supply) = fungible_asset_supply { + ( + fungible_asset_supply.get_maximum(), + Some(fungible_asset_supply.current.clone()), + ) + } else { + (None, None) + }; return Ok(Some(Self { asset_type: asset_type.clone(), @@ -96,7 +87,9 @@ impl FungibleAssetMetadataModel { supply_aggregator_table_handle_v1: None, supply_aggregator_table_key_v1: None, token_standard: TokenStandard::V2.to_string(), - is_token_v2: Some(is_token_v2), + is_token_v2: None, + supply_v2, + maximum_v2, })); } } @@ -135,7 +128,9 @@ impl FungibleAssetMetadataModel { supply_aggregator_table_handle_v1: supply_aggregator_table_handle, supply_aggregator_table_key_v1: supply_aggregator_table_key, token_standard: TokenStandard::V1.to_string(), - is_token_v2: Some(false), + is_token_v2: None, + supply_v2: None, + maximum_v2: None, })) } else { Ok(None) @@ -144,54 +139,4 @@ impl FungibleAssetMetadataModel { _ => Ok(None), } } - - /// A fungible asset can also be a token. We will make a best effort guess at whether this is a fungible token. - /// 1. If metadata is present without token object, then it's not a token - /// 2. If metadata is not present, we will do a lookup in the db. - pub async fn is_address_fungible_asset( - conn: &mut PgPoolConnection<'_>, - asset_type: &str, - object_aggregated_data_mapping: &ObjectAggregatedDataMapping, - txn_version: i64, - ) -> bool { - // 1. If metadata is present without token object, then it's not a token - if let Some(object_data) = object_aggregated_data_mapping.get(asset_type) { - if object_data.fungible_asset_metadata.is_some() { - return object_data.token.is_none(); - } - } - // 2. If metadata is not present, we will do a lookup in the db. - match FungibleAssetMetadataQuery::get_by_asset_type(conn, asset_type).await { - Ok(metadata) => { - if let Some(is_token_v2) = metadata.is_token_v2 { - return !is_token_v2; - } - - // If is_token_v2 is null, then the metadata is a v1 coin info, and it's not a token - true - }, - Err(_) => { - tracing::error!( - transaction_version = txn_version, - lookup_key = asset_type, - "Missing fungible_asset_metadata for asset_type: {}. You probably should backfill db.", - asset_type, - ); - // Default - true - }, - } - } -} - -impl FungibleAssetMetadataQuery { - pub async fn get_by_asset_type( - conn: &mut PgPoolConnection<'_>, - asset_type: &str, - ) -> diesel::QueryResult { - fungible_asset_metadata::table - .filter(fungible_asset_metadata::asset_type.eq(asset_type)) - .first::(conn) - .await - } } diff --git a/rust/processor/src/models/object_models/v2_object_utils.rs b/rust/processor/src/models/object_models/v2_object_utils.rs index e2a397d53..95935f833 100644 --- a/rust/processor/src/models/object_models/v2_object_utils.rs +++ b/rust/processor/src/models/object_models/v2_object_utils.rs @@ -52,6 +52,32 @@ pub struct ObjectAggregatedData { pub token_identifier: Option, } +impl Default for ObjectAggregatedData { + fn default() -> Self { + Self { + object: ObjectWithMetadata { + object_core: ObjectCore { + allow_ungated_transfer: false, + guid_creation_num: BigDecimal::default(), + owner: String::default(), + }, + state_key_hash: String::default(), + }, + transfer_events: Vec::new(), + fungible_asset_metadata: None, + fungible_asset_supply: None, + fungible_asset_store: None, + aptos_collection: None, + fixed_supply: None, + property_map: None, + token: None, + unlimited_supply: None, + concurrent_supply: None, + token_identifier: None, + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ObjectCore { pub allow_ungated_transfer: bool, diff --git a/rust/processor/src/models/token_v2_models/v2_token_activities.rs b/rust/processor/src/models/token_v2_models/v2_token_activities.rs index a74674b6d..824bec15a 100644 --- a/rust/processor/src/models/token_v2_models/v2_token_activities.rs +++ b/rust/processor/src/models/token_v2_models/v2_token_activities.rs @@ -5,18 +5,14 @@ #![allow(clippy::extra_unused_lifetimes)] #![allow(clippy::unused_unit)] -use super::{ - v2_token_datas::TokenDataV2, - v2_token_utils::{TokenStandard, V2TokenEvent}, -}; +use super::v2_token_utils::{TokenStandard, V2TokenEvent}; use crate::{ models::{ - fungible_asset_models::v2_fungible_asset_utils::FungibleAssetEvent, object_models::v2_object_utils::ObjectAggregatedDataMapping, token_models::token_utils::{TokenDataIdType, TokenEvent}, }, schema::token_activities_v2, - utils::{database::PgPoolConnection, util::standardize_address}, + utils::util::standardize_address, }; use aptos_protos::transaction::v1::Event; use bigdecimal::{BigDecimal, One, Zero}; @@ -64,88 +60,6 @@ struct TokenActivityHelperV2 { } impl TokenActivityV2 { - /// We'll go from 0x1::fungible_asset::withdraw/deposit events. - /// We're guaranteed to find a 0x1::fungible_asset::FungibleStore which has a pointer to the - /// fungible asset metadata which could be a token. We'll either find that token in token_v2_metadata - /// or by looking up the postgres table. - /// TODO: Create artificial events for mint and burn. There are no mint and burn events so we'll have to - /// add all the deposits/withdrawals and if it's positive/negative it's a mint/burn. - pub async fn get_ft_v2_from_parsed_event( - event: &Event, - txn_version: i64, - txn_timestamp: chrono::NaiveDateTime, - event_index: i64, - entry_function_id_str: &Option, - object_metadatas: &ObjectAggregatedDataMapping, - conn: &mut PgPoolConnection<'_>, - ) -> anyhow::Result> { - let event_type = event.type_str.clone(); - if let Some(fa_event) = - &FungibleAssetEvent::from_event(event_type.as_str(), &event.data, txn_version)? - { - let event_account_address = - standardize_address(&event.key.as_ref().unwrap().account_address); - - // The event account address will also help us find fungible store which tells us where to find - // the metadata - if let Some(object_data) = object_metadatas.get(&event_account_address) { - let object_core = &object_data.object.object_core; - let fungible_asset = object_data.fungible_asset_store.as_ref().unwrap(); - let token_data_id = fungible_asset.metadata.get_reference_address(); - // Exit early if it's not a token - if !TokenDataV2::is_address_token( - conn, - &token_data_id, - object_metadatas, - txn_version, - ) - .await - { - return Ok(None); - } - - let token_activity_helper = match fa_event { - FungibleAssetEvent::WithdrawEvent(inner) => TokenActivityHelperV2 { - from_address: Some(object_core.get_owner_address()), - to_address: None, - token_amount: inner.amount.clone(), - before_value: None, - after_value: None, - event_type: event_type.clone(), - }, - FungibleAssetEvent::DepositEvent(inner) => TokenActivityHelperV2 { - from_address: None, - to_address: Some(object_core.get_owner_address()), - token_amount: inner.amount.clone(), - before_value: None, - after_value: None, - event_type: event_type.clone(), - }, - _ => return Ok(None), - }; - - return Ok(Some(Self { - transaction_version: txn_version, - event_index, - event_account_address, - token_data_id: token_data_id.clone(), - property_version_v1: BigDecimal::zero(), - type_: token_activity_helper.event_type, - from_address: token_activity_helper.from_address, - to_address: token_activity_helper.to_address, - token_amount: token_activity_helper.token_amount, - before_value: token_activity_helper.before_value, - after_value: token_activity_helper.after_value, - entry_function_id_str: entry_function_id_str.clone(), - token_standard: TokenStandard::V2.to_string(), - is_fungible_v2: Some(true), - transaction_timestamp: txn_timestamp, - })); - } - } - Ok(None) - } - pub async fn get_nft_v2_from_parsed_event( event: &Event, txn_version: i64, @@ -236,7 +150,7 @@ impl TokenActivityV2 { after_value: token_activity_helper.after_value, entry_function_id_str: entry_function_id_str.clone(), token_standard: TokenStandard::V2.to_string(), - is_fungible_v2: Some(false), + is_fungible_v2: None, transaction_timestamp: txn_timestamp, })); } else { @@ -264,7 +178,7 @@ impl TokenActivityV2 { after_value: None, entry_function_id_str: entry_function_id_str.clone(), token_standard: TokenStandard::V2.to_string(), - is_fungible_v2: Some(false), + is_fungible_v2: None, transaction_timestamp: txn_timestamp, })); } diff --git a/rust/processor/src/models/token_v2_models/v2_token_datas.rs b/rust/processor/src/models/token_v2_models/v2_token_datas.rs index 10ed2a944..7259dfa0c 100644 --- a/rust/processor/src/models/token_v2_models/v2_token_datas.rs +++ b/rust/processor/src/models/token_v2_models/v2_token_datas.rs @@ -12,12 +12,11 @@ use crate::{ token_models::token_utils::TokenWriteSet, }, schema::{current_token_datas_v2, token_datas_v2}, - utils::{database::PgPoolConnection, util::standardize_address}, + utils::util::standardize_address, }; use aptos_protos::transaction::v1::{WriteResource, WriteTableItem}; -use bigdecimal::{BigDecimal, Zero}; +use bigdecimal::BigDecimal; use diesel::prelude::*; -use diesel_async::RunQueryDsl; use field_count::FieldCount; use serde::{Deserialize, Serialize}; @@ -34,7 +33,7 @@ pub struct TokenDataV2 { pub collection_id: String, pub token_name: String, pub maximum: Option, - pub supply: BigDecimal, + pub supply: Option, pub largest_property_version_v1: Option, pub token_uri: String, pub token_properties: serde_json::Value, @@ -42,7 +41,8 @@ pub struct TokenDataV2 { pub token_standard: String, pub is_fungible_v2: Option, pub transaction_timestamp: chrono::NaiveDateTime, - pub decimals: i64, + // Deperecated, but still here for backwards compatibility + pub decimals: Option, } #[derive(Clone, Debug, Deserialize, FieldCount, Identifiable, Insertable, Serialize)] @@ -53,7 +53,7 @@ pub struct CurrentTokenDataV2 { pub collection_id: String, pub token_name: String, pub maximum: Option, - pub supply: BigDecimal, + pub supply: Option, pub largest_property_version_v1: Option, pub token_uri: String, pub token_properties: serde_json::Value, @@ -62,28 +62,8 @@ pub struct CurrentTokenDataV2 { pub is_fungible_v2: Option, pub last_transaction_version: i64, pub last_transaction_timestamp: chrono::NaiveDateTime, - pub decimals: i64, -} - -#[derive(Debug, Deserialize, Identifiable, Queryable, Serialize)] -#[diesel(primary_key(token_data_id))] -#[diesel(table_name = current_token_datas_v2)] -pub struct CurrentTokenDataV2Query { - pub token_data_id: String, - pub collection_id: String, - pub token_name: String, - pub maximum: Option, - pub supply: BigDecimal, - pub largest_property_version_v1: Option, - pub token_uri: String, - pub description: String, - pub token_properties: serde_json::Value, - pub token_standard: String, - pub is_fungible_v2: Option, - pub last_transaction_version: i64, - pub last_transaction_timestamp: chrono::NaiveDateTime, - pub inserted_at: chrono::NaiveDateTime, - pub decimals: i64, + // Deperecated, but still here for backwards compatibility + pub decimals: Option, } impl TokenDataV2 { @@ -99,21 +79,15 @@ impl TokenDataV2 { if let Some(inner) = &TokenV2::from_write_resource(write_resource, txn_version)? { let token_data_id = standardize_address(&write_resource.address.to_string()); let mut token_name = inner.get_name_trunc(); - // Get maximum, supply, and is fungible from fungible asset if this is a fungible token - let (mut maximum, mut supply, mut decimals, mut is_fungible_v2) = - (None, BigDecimal::zero(), 0, Some(false)); + let is_fungible_v2; // Get token properties from 0x4::property_map::PropertyMap let mut token_properties = serde_json::Value::Null; if let Some(object_metadata) = object_metadatas.get(&token_data_id) { let fungible_asset_metadata = object_metadata.fungible_asset_metadata.as_ref(); - let fungible_asset_supply = object_metadata.fungible_asset_supply.as_ref(); - if let Some(metadata) = fungible_asset_metadata { - if let Some(fa_supply) = fungible_asset_supply { - maximum = fa_supply.get_maximum(); - supply = fa_supply.current.clone(); - decimals = metadata.decimals as i64; - is_fungible_v2 = Some(true); - } + if fungible_asset_metadata.is_some() { + is_fungible_v2 = Some(true); + } else { + is_fungible_v2 = Some(false); } token_properties = object_metadata .property_map @@ -139,8 +113,8 @@ impl TokenDataV2 { token_data_id: token_data_id.clone(), collection_id: collection_id.clone(), token_name: token_name.clone(), - maximum: maximum.clone(), - supply: supply.clone(), + maximum: None, + supply: None, largest_property_version_v1: None, token_uri: token_uri.clone(), token_properties: token_properties.clone(), @@ -148,14 +122,14 @@ impl TokenDataV2 { token_standard: TokenStandard::V2.to_string(), is_fungible_v2, transaction_timestamp: txn_timestamp, - decimals, + decimals: None, }, CurrentTokenDataV2 { token_data_id, collection_id, token_name, - maximum, - supply, + maximum: None, + supply: None, largest_property_version_v1: None, token_uri, token_properties, @@ -164,7 +138,7 @@ impl TokenDataV2 { is_fungible_v2, last_transaction_version: txn_version, last_transaction_timestamp: txn_timestamp, - decimals, + decimals: None, }, ))) } else { @@ -212,7 +186,7 @@ impl TokenDataV2 { collection_id: collection_id.clone(), token_name: token_name.clone(), maximum: Some(token_data.maximum.clone()), - supply: token_data.supply.clone(), + supply: Some(token_data.supply.clone()), largest_property_version_v1: Some( token_data.largest_property_version.clone(), ), @@ -222,14 +196,14 @@ impl TokenDataV2 { token_standard: TokenStandard::V1.to_string(), is_fungible_v2: None, transaction_timestamp: txn_timestamp, - decimals: 0, + decimals: None, }, CurrentTokenDataV2 { token_data_id, collection_id, token_name, maximum: Some(token_data.maximum), - supply: token_data.supply, + supply: Some(token_data.supply), largest_property_version_v1: Some(token_data.largest_property_version), token_uri, token_properties: token_data.default_properties, @@ -238,7 +212,7 @@ impl TokenDataV2 { is_fungible_v2: None, last_transaction_version: txn_version, last_transaction_timestamp: txn_timestamp, - decimals: 0, + decimals: None, }, ))); } else { @@ -252,49 +226,4 @@ impl TokenDataV2 { } Ok(None) } - - /// A fungible asset can also be a token. We will make a best effort guess at whether this is a fungible token. - /// 1. If metadata is present with a token object, then is a token - /// 2. If metadata is not present, we will do a lookup in the db. - pub async fn is_address_token( - conn: &mut PgPoolConnection<'_>, - token_data_id: &str, - object_aggregated_data_mapping: &ObjectAggregatedDataMapping, - txn_version: i64, - ) -> bool { - if let Some(object_data) = object_aggregated_data_mapping.get(token_data_id) { - return object_data.token.is_some(); - } - match CurrentTokenDataV2Query::get_exists(conn, token_data_id).await { - Ok(is_token) => is_token, - Err(e) => { - // TODO: Standardize this error handling - panic!("Version: {}, error {:?}", txn_version, e) - }, - } - } -} - -impl CurrentTokenDataV2Query { - /// TODO: change this to diesel exists. Also this only checks once so may miss some data if coming from another thread - pub async fn get_exists( - conn: &mut PgPoolConnection<'_>, - token_data_id: &str, - ) -> anyhow::Result { - match current_token_datas_v2::table - .filter(current_token_datas_v2::token_data_id.eq(token_data_id)) - .first::(conn) - .await - .optional() - { - Ok(result) => { - if result.is_some() { - Ok(true) - } else { - Ok(false) - } - }, - Err(e) => anyhow::bail!("Error checking if token_data_id exists: {:?}", e), - } - } } diff --git a/rust/processor/src/models/token_v2_models/v2_token_ownerships.rs b/rust/processor/src/models/token_v2_models/v2_token_ownerships.rs index 453772511..51dc797f9 100644 --- a/rust/processor/src/models/token_v2_models/v2_token_ownerships.rs +++ b/rust/processor/src/models/token_v2_models/v2_token_ownerships.rs @@ -11,8 +11,6 @@ use super::{ }; use crate::{ models::{ - default_models::move_resources::MoveResource, - fungible_asset_models::v2_fungible_asset_utils::V2FungibleAssetResource, object_models::v2_object_utils::{ObjectAggregatedDataMapping, ObjectWithMetadata}, token_models::{token_utils::TokenWriteSet, tokens::TableHandleToOwner}, token_v2_models::v2_token_utils::DEFAULT_OWNER_ADDRESS, @@ -115,10 +113,6 @@ impl TokenOwnershipV2 { Vec, AHashMap, )> { - // We should be indexing v1 token or v2 fungible token here - if token_data.is_fungible_v2 != Some(false) { - return Ok((vec![], AHashMap::new())); - } let mut ownerships = vec![]; let mut current_ownerships = AHashMap::new(); @@ -143,7 +137,7 @@ impl TokenOwnershipV2 { token_properties_mutated_v1: None, is_soulbound_v2: Some(is_soulbound), token_standard: TokenStandard::V2.to_string(), - is_fungible_v2: token_data.is_fungible_v2, + is_fungible_v2: None, transaction_timestamp: token_data.transaction_timestamp, non_transferrable_by_owner: Some(is_soulbound), }); @@ -164,7 +158,7 @@ impl TokenOwnershipV2 { token_properties_mutated_v1: None, is_soulbound_v2: Some(is_soulbound), token_standard: TokenStandard::V2.to_string(), - is_fungible_v2: token_data.is_fungible_v2, + is_fungible_v2: None, last_transaction_version: token_data.transaction_version, last_transaction_timestamp: token_data.transaction_timestamp, non_transferrable_by_owner: Some(is_soulbound), @@ -192,7 +186,7 @@ impl TokenOwnershipV2 { token_properties_mutated_v1: None, is_soulbound_v2: Some(is_soulbound), token_standard: TokenStandard::V2.to_string(), - is_fungible_v2: token_data.is_fungible_v2, + is_fungible_v2: None, transaction_timestamp: token_data.transaction_timestamp, non_transferrable_by_owner: Some(is_soulbound), }); @@ -215,7 +209,7 @@ impl TokenOwnershipV2 { token_properties_mutated_v1: None, is_soulbound_v2: Some(is_soulbound), token_standard: TokenStandard::V2.to_string(), - is_fungible_v2: token_data.is_fungible_v2, + is_fungible_v2: None, last_transaction_version: token_data.transaction_version, last_transaction_timestamp: token_data.transaction_timestamp, non_transferrable_by_owner: Some(is_soulbound), @@ -412,90 +406,6 @@ impl TokenOwnershipV2 { Ok(None) } - // Getting this from 0x1::fungible_asset::FungibleStore - pub async fn get_ft_v2_from_write_resource( - write_resource: &WriteResource, - txn_version: i64, - write_set_change_index: i64, - txn_timestamp: chrono::NaiveDateTime, - object_metadatas: &ObjectAggregatedDataMapping, - conn: &mut PgPoolConnection<'_>, - ) -> anyhow::Result> { - let type_str = MoveResource::get_outer_type_from_resource(write_resource); - if !V2FungibleAssetResource::is_resource_supported(type_str.as_str()) { - return Ok(None); - } - let resource = MoveResource::from_write_resource( - write_resource, - 0, // Placeholder, this isn't used anyway - txn_version, - 0, // Placeholder, this isn't used anyway - ); - - if let V2FungibleAssetResource::FungibleAssetStore(inner) = - V2FungibleAssetResource::from_resource( - &type_str, - resource.data.as_ref().unwrap(), - txn_version, - )? - { - if let Some(object_data) = object_metadatas.get(&resource.address) { - let object_core = &object_data.object.object_core; - let token_data_id = inner.metadata.get_reference_address(); - // Exit early if it's not a token - if !TokenDataV2::is_address_token( - conn, - &token_data_id, - object_metadatas, - txn_version, - ) - .await - { - return Ok(None); - } - let storage_id = resource.address.clone(); - let is_soulbound = inner.frozen; - let amount = inner.balance; - let owner_address = object_core.get_owner_address(); - - return Ok(Some(( - Self { - transaction_version: txn_version, - write_set_change_index, - token_data_id: token_data_id.clone(), - property_version_v1: BigDecimal::zero(), - owner_address: Some(owner_address.clone()), - storage_id: storage_id.clone(), - amount: amount.clone(), - table_type_v1: None, - token_properties_mutated_v1: None, - is_soulbound_v2: Some(is_soulbound), - token_standard: TokenStandard::V2.to_string(), - is_fungible_v2: Some(true), - transaction_timestamp: txn_timestamp, - non_transferrable_by_owner: Some(is_soulbound), - }, - CurrentTokenOwnershipV2 { - token_data_id, - property_version_v1: BigDecimal::zero(), - owner_address, - storage_id, - amount, - table_type_v1: None, - token_properties_mutated_v1: None, - is_soulbound_v2: Some(is_soulbound), - token_standard: TokenStandard::V2.to_string(), - is_fungible_v2: Some(true), - last_transaction_version: txn_version, - last_transaction_timestamp: txn_timestamp, - non_transferrable_by_owner: Some(is_soulbound), - }, - ))); - } - } - Ok(None) - } - /// We want to track tokens in any offer/claims and tokenstore pub fn get_v1_from_write_table_item( table_item: &WriteTableItem, diff --git a/rust/processor/src/processors/fungible_asset_processor.rs b/rust/processor/src/processors/fungible_asset_processor.rs index af5683749..c5f303b17 100644 --- a/rust/processor/src/processors/fungible_asset_processor.rs +++ b/rust/processor/src/processors/fungible_asset_processor.rs @@ -9,18 +9,19 @@ use crate::{ v2_fungible_asset_balances::{ CurrentFungibleAssetBalance, CurrentFungibleAssetMapping, FungibleAssetBalance, }, - v2_fungible_asset_utils::{FeeStatement, FungibleAssetMetadata, FungibleAssetStore}, + v2_fungible_asset_utils::{ + FeeStatement, FungibleAssetMetadata, FungibleAssetStore, FungibleAssetSupply, + }, v2_fungible_metadata::{FungibleAssetMetadataMapping, FungibleAssetMetadataModel}, }, object_models::v2_object_utils::{ ObjectAggregatedData, ObjectAggregatedDataMapping, ObjectWithMetadata, }, - token_v2_models::v2_token_utils::TokenV2, }, schema, utils::{ counters::PROCESSOR_UNKNOWN_TYPE_COUNT, - database::{execute_in_chunks, get_config_table_chunk_size, PgDbPool, PgPoolConnection}, + database::{execute_in_chunks, get_config_table_chunk_size, PgDbPool}, util::{get_entry_function_from_user_request, standardize_address}, }, }; @@ -171,6 +172,8 @@ fn insert_fungible_asset_metadata_query( token_standard.eq(excluded(token_standard)), inserted_at.eq(excluded(inserted_at)), is_token_v2.eq(excluded(is_token_v2)), + supply_v2.eq(excluded(supply_v2)), + maximum_v2.eq(excluded(maximum_v2)), ) ), Some(" WHERE fungible_asset_metadata.last_transaction_version <= excluded.last_transaction_version "), @@ -189,11 +192,7 @@ fn insert_fungible_asset_balances_query( diesel::insert_into(schema::fungible_asset_balances::table) .values(items_to_insert) .on_conflict((transaction_version, write_set_change_index)) - .do_update() - .set(( - is_frozen.eq(excluded(is_frozen)), - inserted_at.eq(excluded(inserted_at)), - )), + .do_nothing(), None, ) } @@ -244,13 +243,12 @@ impl ProcessorTrait for FungibleAssetProcessor { let processing_start = std::time::Instant::now(); let last_transaction_timestamp = transactions.last().unwrap().timestamp.clone(); - let mut conn = self.get_conn().await; let ( fungible_asset_activities, fungible_asset_metadata, fungible_asset_balances, current_fungible_asset_balances, - ) = parse_v2_coin(&transactions, &mut conn).await; + ) = parse_v2_coin(&transactions).await; let processing_duration_in_secs = processing_start.elapsed().as_secs_f64(); let db_insertion_start = std::time::Instant::now(); @@ -297,7 +295,6 @@ impl ProcessorTrait for FungibleAssetProcessor { /// V2 coin is called fungible assets and this flow includes all data from V1 in coin_processor async fn parse_v2_coin( transactions: &[Transaction], - conn: &mut PgPoolConnection<'_>, ) -> ( Vec, Vec, @@ -367,18 +364,7 @@ async fn parse_v2_coin( standardize_address(&wr.address.to_string()), ObjectAggregatedData { object, - fungible_asset_metadata: None, - fungible_asset_store: None, - token: None, - // The following structs are unused in this processor - aptos_collection: None, - fixed_supply: None, - unlimited_supply: None, - concurrent_supply: None, - property_map: None, - transfer_events: vec![], - fungible_asset_supply: None, - token_identifier: None, + ..ObjectAggregatedData::default() }, ); } @@ -417,10 +403,11 @@ async fn parse_v2_coin( { aggregated_data.fungible_asset_store = Some(fungible_asset_store); } - if let Some(token) = - TokenV2::from_write_resource(write_resource, txn_version).unwrap() + if let Some(fungible_asset_supply) = + FungibleAssetSupply::from_write_resource(write_resource, txn_version) + .unwrap() { - aggregated_data.token = Some(token); + aggregated_data.fungible_asset_supply = Some(fungible_asset_supply); } } } @@ -473,7 +460,6 @@ async fn parse_v2_coin( index as i64, &entry_function_id_str, &fungible_asset_object_helper, - conn, ) .await .unwrap_or_else(|e| { @@ -529,7 +515,6 @@ async fn parse_v2_coin( txn_version, txn_timestamp, &fungible_asset_object_helper, - conn, ) .await .unwrap_or_else(|e| { diff --git a/rust/processor/src/processors/token_v2_processor.rs b/rust/processor/src/processors/token_v2_processor.rs index f53d134ed..e806e08c2 100644 --- a/rust/processor/src/processors/token_v2_processor.rs +++ b/rust/processor/src/processors/token_v2_processor.rs @@ -4,9 +4,7 @@ use super::{ProcessingResult, ProcessorName, ProcessorTrait}; use crate::{ models::{ - fungible_asset_models::v2_fungible_asset_utils::{ - FungibleAssetMetadata, FungibleAssetStore, FungibleAssetSupply, - }, + fungible_asset_models::v2_fungible_asset_utils::FungibleAssetMetadata, object_models::v2_object_utils::{ ObjectAggregatedData, ObjectAggregatedDataMapping, ObjectWithMetadata, }, @@ -244,7 +242,14 @@ fn insert_token_datas_v2_query( diesel::insert_into(schema::token_datas_v2::table) .values(items_to_insert) .on_conflict((transaction_version, write_set_change_index)) - .do_nothing(), + .do_update() + .set(( + maximum.eq(excluded(maximum)), + supply.eq(excluded(supply)), + is_fungible_v2.eq(excluded(is_fungible_v2)), + inserted_at.eq(excluded(inserted_at)), + decimals.eq(excluded(decimals)), + )), None, ) } @@ -261,7 +266,11 @@ fn insert_token_ownerships_v2_query( diesel::insert_into(schema::token_ownerships_v2::table) .values(items_to_insert) .on_conflict((transaction_version, write_set_change_index)) - .do_nothing(), + .do_update() + .set(( + is_fungible_v2.eq(excluded(is_fungible_v2)), + inserted_at.eq(excluded(inserted_at)), + )), None, ) } @@ -296,7 +305,7 @@ fn insert_current_collections_v2_query( inserted_at.eq(excluded(inserted_at)), )), Some(" WHERE current_collections_v2.last_transaction_version <= excluded.last_transaction_version "), - ) + ) } fn insert_current_token_datas_v2_query( @@ -378,6 +387,7 @@ fn insert_current_deleted_token_ownerships_v2_query( amount.eq(excluded(amount)), last_transaction_version.eq(excluded(last_transaction_version)), last_transaction_timestamp.eq(excluded(last_transaction_timestamp)), + is_fungible_v2.eq(excluded(is_fungible_v2)), inserted_at.eq(excluded(inserted_at)), )), Some(" WHERE current_token_ownerships_v2.last_transaction_version <= excluded.last_transaction_version "), @@ -398,7 +408,7 @@ fn insert_token_activities_v2_query( .on_conflict((transaction_version, event_index)) .do_update() .set(( - entry_function_id_str.eq(excluded(entry_function_id_str)), + is_fungible_v2.eq(excluded(is_fungible_v2)), inserted_at.eq(excluded(inserted_at)), )), None, @@ -604,18 +614,8 @@ async fn parse_v2_token( token_v2_metadata_helper.insert( standardize_address(&wr.address.to_string()), ObjectAggregatedData { - aptos_collection: None, - fixed_supply: None, object, - unlimited_supply: None, - concurrent_supply: None, - property_map: None, - transfer_events: vec![], - token: None, - fungible_asset_metadata: None, - fungible_asset_supply: None, - fungible_asset_store: None, - token_identifier: None, + ..ObjectAggregatedData::default() }, ); } @@ -661,16 +661,6 @@ async fn parse_v2_token( { aggregated_data.fungible_asset_metadata = Some(fungible_asset_metadata); } - if let Some(fungible_asset_supply) = - FungibleAssetSupply::from_write_resource(wr, txn_version).unwrap() - { - aggregated_data.fungible_asset_supply = Some(fungible_asset_supply); - } - if let Some(fungible_asset_store) = - FungibleAssetStore::from_write_resource(wr, txn_version).unwrap() - { - aggregated_data.fungible_asset_store = Some(fungible_asset_store); - } if let Some(token_identifier) = TokenIdentifiers::from_write_resource(wr, txn_version).unwrap() { @@ -737,21 +727,6 @@ async fn parse_v2_token( { token_activities_v2.push(event); } - // handling all the token v2 events - if let Some(event) = TokenActivityV2::get_ft_v2_from_parsed_event( - event, - txn_version, - txn_timestamp, - index as i64, - &entry_function_id_str, - &token_v2_metadata_helper, - conn, - ) - .await - .unwrap() - { - token_activities_v2.push(event); - } } for (index, wsc) in transaction_info.changes.iter().enumerate() { @@ -954,31 +929,6 @@ async fn parse_v2_token( ); } - // Add fungible token handling - if let Some((ft_ownership, current_ft_ownership)) = - TokenOwnershipV2::get_ft_v2_from_write_resource( - resource, - txn_version, - wsc_index, - txn_timestamp, - &token_v2_metadata_helper, - conn, - ) - .await - .unwrap() - { - token_ownerships_v2.push(ft_ownership); - current_token_ownerships_v2.insert( - ( - current_ft_ownership.token_data_id.clone(), - current_ft_ownership.property_version_v1.clone(), - current_ft_ownership.owner_address.clone(), - current_ft_ownership.storage_id.clone(), - ), - current_ft_ownership, - ); - } - // Track token properties if let Some(token_metadata) = CurrentTokenV2Metadata::from_write_resource( resource, diff --git a/rust/processor/src/schema.rs b/rust/processor/src/schema.rs index 6cd70a659..d8990e367 100644 --- a/rust/processor/src/schema.rs +++ b/rust/processor/src/schema.rs @@ -533,7 +533,7 @@ diesel::table! { #[max_length = 128] token_name -> Varchar, maximum -> Nullable, - supply -> Numeric, + supply -> Nullable, largest_property_version_v1 -> Nullable, #[max_length = 512] token_uri -> Varchar, @@ -545,7 +545,7 @@ diesel::table! { last_transaction_version -> Int8, last_transaction_timestamp -> Timestamp, inserted_at -> Timestamp, - decimals -> Int8, + decimals -> Nullable, } } @@ -800,6 +800,8 @@ diesel::table! { token_standard -> Varchar, inserted_at -> Timestamp, is_token_v2 -> Nullable, + supply_v2 -> Nullable, + maximum_v2 -> Nullable, } } @@ -1070,7 +1072,7 @@ diesel::table! { #[max_length = 128] token_name -> Varchar, maximum -> Nullable, - supply -> Numeric, + supply -> Nullable, largest_property_version_v1 -> Nullable, #[max_length = 512] token_uri -> Varchar, @@ -1081,7 +1083,7 @@ diesel::table! { is_fungible_v2 -> Nullable, transaction_timestamp -> Timestamp, inserted_at -> Timestamp, - decimals -> Int8, + decimals -> Nullable, } } diff --git a/rust/processor/src/worker.rs b/rust/processor/src/worker.rs index 07c14eb30..8acbd4e96 100644 --- a/rust/processor/src/worker.rs +++ b/rust/processor/src/worker.rs @@ -58,6 +58,7 @@ pub struct Worker { } impl Worker { + #[allow(clippy::too_many_arguments)] pub async fn new( processor_config: ProcessorConfig, postgres_connection_string: String, @@ -165,6 +166,7 @@ impl Worker { self.indexer_grpc_data_service_address.clone(), self.grpc_http2_config.grpc_http2_ping_interval_in_secs(), self.grpc_http2_config.grpc_http2_ping_timeout_in_secs(), + self.grpc_http2_config.grpc_connection_timeout_secs(), self.auth_token.clone(), processor_name.to_string(), ) @@ -181,6 +183,8 @@ impl Worker { self.grpc_http2_config.grpc_http2_ping_interval_in_secs(); let indexer_grpc_http2_ping_timeout = self.grpc_http2_config.grpc_http2_ping_timeout_in_secs(); + let indexer_grpc_reconnection_timeout_secs = + self.grpc_http2_config.grpc_connection_timeout_secs(); let pb_channel_txn_chunk_size = self.pb_channel_txn_chunk_size; // Create a transaction fetcher thread that will continuously fetch transactions from the GRPC stream @@ -204,6 +208,7 @@ impl Worker { indexer_grpc_data_service_address.clone(), indexer_grpc_http2_ping_interval, indexer_grpc_http2_ping_timeout, + indexer_grpc_reconnection_timeout_secs, starting_version, request_ending_version, auth_token.clone(), diff --git a/test_move_script/Move.toml b/test_move_script/Move.toml index 213daa04e..81175f294 100644 --- a/test_move_script/Move.toml +++ b/test_move_script/Move.toml @@ -2,6 +2,9 @@ name = 'run_script' version = '1.0.0' +[addresses] +test_addr = "_" + [dependencies.AptosFramework] git = 'https://github.com/aptos-labs/aptos-core.git' rev = 'mainnet' @@ -20,4 +23,4 @@ subdir = 'aptos-move/framework/move-stdlib' [dependencies.AptosToken] git = 'https://github.com/aptos-labs/aptos-core.git' rev = 'mainnet' -subdir = 'aptos-move/framework/aptos-token' \ No newline at end of file +subdir = 'aptos-move/framework/aptos-token' diff --git a/test_move_script/README.md b/test_move_script/README.md index 9df477b00..136fdf56c 100644 --- a/test_move_script/README.md +++ b/test_move_script/README.md @@ -1,14 +1,18 @@ ## Purpose + Sometimes we might need to create move transactions that simulate edge cases. For example, we noticed that mint + burn the same token in a single transaction creates problems, and to repro we'd have to submit a blockchain transaction. This is an example of how to create a script. Checkout how scripts work in: https://stackoverflow.com/questions/74627977/how-do-i-execute-a-move-script-with-the-aptos-cli. -This script attempts to get the signer of the resource account and deploy code to the resource account from the admin account. +This script attempts to get the signer of the resource account and deploy code to the resource account from the admin account. ## How to run this code? -`aptos move compile && aptos move run-script --compiled-script-path build/run_script/bytecode_scripts/main.mv --profile blah` - * If you haven't created a profile before run `aptos init --profile blah` + +`aptos move compile && aptos move publish --named-addresses test_addr=default && aptos move run-script --compiled-script-path build/run_script/bytecode_scripts/main.mv --profile blah` + +- If you haven't created a profile before run `aptos init --profile blah` ## Notes - * This is meant to be a template. You should build your own script. - * In `Move.toml`, you can change the revision for the frameworks to test new code, from `main` for example. \ No newline at end of file + +- This is meant to be a template. You should build your own script. +- In `Move.toml`, you can change the revision for the frameworks to test new code, from `main` for example. diff --git a/test_move_script/sources/modules/managed_fungible_asset.move b/test_move_script/sources/modules/managed_fungible_asset.move new file mode 100644 index 000000000..ef9fac8bd --- /dev/null +++ b/test_move_script/sources/modules/managed_fungible_asset.move @@ -0,0 +1,419 @@ +/// This module provides a managed fungible asset that allows the owner of the metadata object to +/// mint, transfer and burn fungible assets. +/// +/// The functionalities offered by this module are: +/// 1. Mint fungible assets to fungible stores as the owner of metadata object. +/// 2. Transfer fungible assets as the owner of metadata object ignoring `frozen` field between fungible stores. +/// 3. Burn fungible assets from fungible stores as the owner of metadata object. +/// 4. Withdraw the merged fungible assets from fungible stores as the owner of metadata object. +/// 5. Deposit fungible assets to fungible stores. +module test_addr::managed_fungible_asset { + use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleStore, FungibleAsset}; + use aptos_framework::object::{Self, Object, ConstructorRef}; + use aptos_framework::primary_fungible_store; + use std::error; + use std::signer; + use std::string::String; + use std::option; + + /// Only fungible asset metadata owner can make changes. + const ERR_NOT_OWNER: u64 = 1; + /// The length of ref_flags is not 3. + const ERR_INVALID_REF_FLAGS_LENGTH: u64 = 2; + /// The lengths of two vector do not equal. + const ERR_VECTORS_LENGTH_MISMATCH: u64 = 3; + /// MintRef error. + const ERR_MINT_REF: u64 = 4; + /// TransferRef error. + const ERR_TRANSFER_REF: u64 = 5; + /// BurnRef error. + const ERR_BURN_REF: u64 = 6; + + #[resource_group_member(group = aptos_framework::object::ObjectGroup)] + /// Hold refs to control the minting, transfer and burning of fungible assets. + struct ManagingRefs has key { + mint_ref: Option, + transfer_ref: Option, + burn_ref: Option, + } + + /// Initialize metadata object and store the refs specified by `ref_flags`. + public fun initialize( + constructor_ref: &ConstructorRef, + maximum_supply: u128, + name: String, + symbol: String, + decimals: u8, + icon_uri: String, + project_uri: String, + ref_flags: vector, + ) { + assert!(vector::length(&ref_flags) == 3, error::invalid_argument(ERR_INVALID_REF_FLAGS_LENGTH)); + let supply = if (maximum_supply != 0) { + option::some(maximum_supply) + } else { + option::none() + }; + primary_fungible_store::create_primary_store_enabled_fungible_asset( + constructor_ref, + supply, + name, + symbol, + decimals, + icon_uri, + project_uri, + ); + + // Optionally create mint/burn/transfer refs to allow creator to manage the fungible asset. + let mint_ref = if (*vector::borrow(&ref_flags, 0)) { + option::some(fungible_asset::generate_mint_ref(constructor_ref)) + } else { + option::none() + }; + let transfer_ref = if (*vector::borrow(&ref_flags, 1)) { + option::some(fungible_asset::generate_transfer_ref(constructor_ref)) + } else { + option::none() + }; + let burn_ref = if (*vector::borrow(&ref_flags, 2)) { + option::some(fungible_asset::generate_burn_ref(constructor_ref)) + } else { + option::none() + }; + let metadata_object_signer = object::generate_signer(constructor_ref); + move_to( + &metadata_object_signer, + ManagingRefs { mint_ref, transfer_ref, burn_ref } + ) + } + + /// Mint as the owner of metadata object to the primary fungible stores of the accounts with amounts of FAs. + public entry fun mint_to_primary_stores( + admin: &signer, + asset: Object, + to: vector
, + amounts: vector + ) acquires ManagingRefs { + let receiver_primary_stores = vector::map( + to, + |addr| primary_fungible_store::ensure_primary_store_exists(addr, asset) + ); + mint(admin, asset, receiver_primary_stores, amounts); + } + + + /// Mint as the owner of metadata object to multiple fungible stores with amounts of FAs. + public entry fun mint( + admin: &signer, + asset: Object, + stores: vector>, + amounts: vector, + ) acquires ManagingRefs { + let length = vector::length(&stores); + assert!(length == vector::length(&amounts), error::invalid_argument(ERR_VECTORS_LENGTH_MISMATCH)); + let mint_ref = authorized_borrow_mint_ref(admin, asset); + let i = 0; + while (i < length) { + fungible_asset::mint_to(mint_ref, *vector::borrow(&stores, i), *vector::borrow(&amounts, i)); + i = i + 1; + } + } + + /// Transfer as the owner of metadata object ignoring `frozen` field from primary stores to primary stores of + /// accounts. + public entry fun transfer_between_primary_stores( + admin: &signer, + asset: Object, + from: vector
, + to: vector
, + amounts: vector + ) acquires ManagingRefs { + let sender_primary_stores = vector::map( + from, + |addr| primary_fungible_store::primary_store(addr, asset) + ); + let receiver_primary_stores = vector::map( + to, + |addr| primary_fungible_store::ensure_primary_store_exists(addr, asset) + ); + transfer(admin, asset, sender_primary_stores, receiver_primary_stores, amounts); + } + + /// Transfer as the owner of metadata object ignoring `frozen` field between fungible stores. + public entry fun transfer( + admin: &signer, + asset: Object, + sender_stores: vector>, + receiver_stores: vector>, + amounts: vector, + ) acquires ManagingRefs { + let length = vector::length(&sender_stores); + assert!(length == vector::length(&receiver_stores), error::invalid_argument(ERR_VECTORS_LENGTH_MISMATCH)); + assert!(length == vector::length(&amounts), error::invalid_argument(ERR_VECTORS_LENGTH_MISMATCH)); + let transfer_ref = authorized_borrow_transfer_ref(admin, asset); + let i = 0; + while (i < length) { + fungible_asset::transfer_with_ref( + transfer_ref, + *vector::borrow(&sender_stores, i), + *vector::borrow(&receiver_stores, i), + *vector::borrow(&amounts, i) + ); + i = i + 1; + } + } + + /// Burn fungible assets as the owner of metadata object from the primary stores of accounts. + public entry fun burn_from_primary_stores( + admin: &signer, + asset: Object, + from: vector
, + amounts: vector + ) acquires ManagingRefs { + let primary_stores = vector::map( + from, + |addr| primary_fungible_store::primary_store(addr, asset) + ); + burn(admin, asset, primary_stores, amounts); + } + + /// Burn fungible assets as the owner of metadata object from fungible stores. + public entry fun burn( + admin: &signer, + asset: Object, + stores: vector>, + amounts: vector + ) acquires ManagingRefs { + let length = vector::length(&stores); + assert!(length == vector::length(&amounts), error::invalid_argument(ERR_VECTORS_LENGTH_MISMATCH)); + let burn_ref = authorized_borrow_burn_ref(admin, asset); + let i = 0; + while (i < length) { + fungible_asset::burn_from(burn_ref, *vector::borrow(&stores, i), *vector::borrow(&amounts, i)); + i = i + 1; + }; + } + + + /// Freeze/unfreeze the primary stores of accounts so they cannot transfer or receive fungible assets. + public entry fun set_primary_stores_frozen_status( + admin: &signer, + asset: Object, + accounts: vector
, + frozen: bool + ) acquires ManagingRefs { + let primary_stores = vector::map(accounts, |acct| { + primary_fungible_store::ensure_primary_store_exists(acct, asset) + }); + set_frozen_status(admin, asset, primary_stores, frozen); + } + + /// Freeze/unfreeze the fungible stores so they cannot transfer or receive fungible assets. + public entry fun set_frozen_status( + admin: &signer, + asset: Object, + stores: vector>, + frozen: bool + ) acquires ManagingRefs { + let transfer_ref = authorized_borrow_transfer_ref(admin, asset); + vector::for_each(stores, |store| { + fungible_asset::set_frozen_flag(transfer_ref, store, frozen); + }); + } + + /// Withdraw as the owner of metadata object ignoring `frozen` field from primary fungible stores of accounts. + public fun withdraw_from_primary_stores( + admin: &signer, + asset: Object, + from: vector
, + amounts: vector + ): FungibleAsset acquires ManagingRefs { + let primary_stores = vector::map( + from, + |addr| primary_fungible_store::primary_store(addr, asset) + ); + withdraw(admin, asset, primary_stores, amounts) + } + + /// Withdraw as the owner of metadata object ignoring `frozen` field from fungible stores. + /// return a fungible asset `fa` where `fa.amount = sum(amounts)`. + public fun withdraw( + admin: &signer, + asset: Object, + stores: vector>, + amounts: vector + ): FungibleAsset acquires ManagingRefs { + let length = vector::length(&stores); + assert!(length == vector::length(&amounts), error::invalid_argument(ERR_VECTORS_LENGTH_MISMATCH)); + let transfer_ref = authorized_borrow_transfer_ref(admin, asset); + let i = 0; + let sum = fungible_asset::zero(asset); + while (i < length) { + let fa = fungible_asset::withdraw_with_ref( + transfer_ref, + *vector::borrow(&stores, i), + *vector::borrow(&amounts, i) + ); + fungible_asset::merge(&mut sum, fa); + i = i + 1; + }; + sum + } + + /// Deposit as the owner of metadata object ignoring `frozen` field to primary fungible stores of accounts from a + /// single source of fungible asset. + public fun deposit_to_primary_stores( + admin: &signer, + fa: &mut FungibleAsset, + from: vector
, + amounts: vector, + ) acquires ManagingRefs { + let primary_stores = vector::map( + from, + |addr| primary_fungible_store::ensure_primary_store_exists(addr, fungible_asset::asset_metadata(fa)) + ); + deposit(admin, fa, primary_stores, amounts); + } + + /// Deposit as the owner of metadata object ignoring `frozen` field from fungible stores. The amount left in `fa` + /// is `fa.amount - sum(amounts)`. + public fun deposit( + admin: &signer, + fa: &mut FungibleAsset, + stores: vector>, + amounts: vector + ) acquires ManagingRefs { + let length = vector::length(&stores); + assert!(length == vector::length(&amounts), error::invalid_argument(ERR_VECTORS_LENGTH_MISMATCH)); + let transfer_ref = authorized_borrow_transfer_ref(admin, fungible_asset::asset_metadata(fa)); + let i = 0; + while (i < length) { + let split_fa = fungible_asset::extract(fa, *vector::borrow(&amounts, i)); + fungible_asset::deposit_with_ref( + transfer_ref, + *vector::borrow(&stores, i), + split_fa, + ); + i = i + 1; + }; + } + + /// Borrow the immutable reference of the refs of `metadata`. + /// This validates that the signer is the metadata object's owner. + inline fun authorized_borrow_refs( + owner: &signer, + asset: Object, + ): &ManagingRefs acquires ManagingRefs { + assert!(object::is_owner(asset, signer::address_of(owner)), error::permission_denied(ERR_NOT_OWNER)); + borrow_global(object::object_address(&asset)) + } + + /// Check the existence and borrow `MintRef`. + inline fun authorized_borrow_mint_ref( + owner: &signer, + asset: Object, + ): &MintRef acquires ManagingRefs { + let refs = authorized_borrow_refs(owner, asset); + assert!(option::is_some(&refs.mint_ref), error::not_found(ERR_MINT_REF)); + option::borrow(&refs.mint_ref) + } + + /// Check the existence and borrow `TransferRef`. + inline fun authorized_borrow_transfer_ref( + owner: &signer, + asset: Object, + ): &TransferRef acquires ManagingRefs { + let refs = authorized_borrow_refs(owner, asset); + assert!(option::is_some(&refs.transfer_ref), error::not_found(ERR_TRANSFER_REF)); + option::borrow(&refs.transfer_ref) + } + + /// Check the existence and borrow `BurnRef`. + inline fun authorized_borrow_burn_ref( + owner: &signer, + asset: Object, + ): &BurnRef acquires ManagingRefs { + let refs = authorized_borrow_refs(owner, asset); + assert!(option::is_some(&refs.mint_ref), error::not_found(ERR_BURN_REF)); + option::borrow(&refs.burn_ref) + } + + #[test_only] + use aptos_framework::object::object_from_constructor_ref; + #[test_only] + use std::string::utf8; + use std::vector; + use std::option::Option; + + #[test_only] + fun create_test_mfa(creator: &signer): Object { + let constructor_ref = &object::create_named_object(creator, b"APT"); + initialize( + constructor_ref, + 0, + utf8(b"Aptos Token"), /* name */ + utf8(b"APT"), /* symbol */ + 8, /* decimals */ + utf8(b"http://example.com/favicon.ico"), /* icon */ + utf8(b"http://example.com"), /* project */ + vector[true, true, true] + ); + object_from_constructor_ref(constructor_ref) + } + + #[test(creator = @example_addr)] + fun test_basic_flow( + creator: &signer, + ) acquires ManagingRefs { + let metadata = create_test_mfa(creator); + let creator_address = signer::address_of(creator); + let aaron_address = @0xface; + + mint_to_primary_stores(creator, metadata, vector[creator_address, aaron_address], vector[100, 50]); + assert!(primary_fungible_store::balance(creator_address, metadata) == 100, 1); + assert!(primary_fungible_store::balance(aaron_address, metadata) == 50, 2); + + set_primary_stores_frozen_status(creator, metadata, vector[creator_address, aaron_address], true); + assert!(primary_fungible_store::is_frozen(creator_address, metadata), 3); + assert!(primary_fungible_store::is_frozen(aaron_address, metadata), 4); + + transfer_between_primary_stores( + creator, + metadata, + vector[creator_address, aaron_address], + vector[aaron_address, creator_address], + vector[10, 5] + ); + assert!(primary_fungible_store::balance(creator_address, metadata) == 95, 5); + assert!(primary_fungible_store::balance(aaron_address, metadata) == 55, 6); + + set_primary_stores_frozen_status(creator, metadata, vector[creator_address, aaron_address], false); + assert!(!primary_fungible_store::is_frozen(creator_address, metadata), 7); + assert!(!primary_fungible_store::is_frozen(aaron_address, metadata), 8); + + let fa = withdraw_from_primary_stores( + creator, + metadata, + vector[creator_address, aaron_address], + vector[25, 15] + ); + assert!(fungible_asset::amount(&fa) == 40, 9); + deposit_to_primary_stores(creator, &mut fa, vector[creator_address, aaron_address], vector[30, 10]); + fungible_asset::destroy_zero(fa); + + burn_from_primary_stores(creator, metadata, vector[creator_address, aaron_address], vector[100, 50]); + assert!(primary_fungible_store::balance(creator_address, metadata) == 0, 10); + assert!(primary_fungible_store::balance(aaron_address, metadata) == 0, 11); + } + + #[test(creator = @example_addr, aaron = @0xface)] + #[expected_failure(abort_code = 0x50001, location = Self)] + fun test_permission_denied( + creator: &signer, + aaron: &signer + ) acquires ManagingRefs { + let metadata = create_test_mfa(creator); + let creator_address = signer::address_of(creator); + mint_to_primary_stores(aaron, metadata, vector[creator_address], vector[100]); + } +} \ No newline at end of file diff --git a/test_move_script/sources/run_script.move b/test_move_script/sources/run_script.move index c4e566f30..0b8b585c3 100644 --- a/test_move_script/sources/run_script.move +++ b/test_move_script/sources/run_script.move @@ -13,30 +13,30 @@ script { const S: vector = b"TEST2"; - fun main(deployer: &signer) { - collection::create_unlimited_collection( - deployer, - utf8(S), - utf8(S), - option::none(), - utf8(S), - ); - let constructor_ref = token::create_named_token( - deployer, - utf8(S), - utf8(S), - utf8(S), - option::none(), - utf8(S), - ); - let transfer_ref = object::generate_transfer_ref(&constructor_ref); - let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref); - object::transfer_with_ref(linear_transfer_ref, @0xcafe); - let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref); - object::transfer_with_ref(linear_transfer_ref, @0xcafe2); - // Disabling transfer ref after transferring - object::disable_ungated_transfer(&transfer_ref); - let burn_ref = token::generate_burn_ref(&constructor_ref); - token::burn(burn_ref); - } -} \ No newline at end of file + fun main(deployer: &signer) { + collection::create_unlimited_collection( + deployer, + utf8(S), + utf8(S), + option::none(), + utf8(S), + ); + let constructor_ref = token::create_named_token( + deployer, + utf8(S), + utf8(S), + utf8(S), + option::none(), + utf8(S), + ); + let transfer_ref = object::generate_transfer_ref(&constructor_ref); + let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref); + object::transfer_with_ref(linear_transfer_ref, @0xcafe); + let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref); + object::transfer_with_ref(linear_transfer_ref, @0xcafe2); + // Disabling transfer ref after transferring + object::disable_ungated_transfer(&transfer_ref); + let burn_ref = token::generate_burn_ref(&constructor_ref); + token::burn(burn_ref); + } +} diff --git a/test_move_script/sources/scripts/burn_fungible_token.move b/test_move_script/sources/scripts/burn_fungible_token.move new file mode 100644 index 000000000..63efa0b75 --- /dev/null +++ b/test_move_script/sources/scripts/burn_fungible_token.move @@ -0,0 +1,23 @@ +script { + use std::signer; + use std::string::utf8; + + use aptos_framework::fungible_asset::{Metadata}; + use aptos_framework::object::{Self}; + use aptos_token_objects::token; + use test_addr::managed_fungible_asset::{Self}; + + const FT: vector = b"FT2"; + + fun burn_ft(deployer: &signer) { + let deployer_address = signer::address_of(deployer); + let token_address = token::create_token_address(&deployer_address, &utf8(FT), &utf8(FT)); + let metadata = object::address_to_object(token_address); + managed_fungible_asset::burn_from_primary_stores( + deployer, + metadata, + vector[deployer_address], + vector[100], + ); + } +} \ No newline at end of file diff --git a/test_move_script/sources/scripts/fungible_token.move b/test_move_script/sources/scripts/fungible_token.move new file mode 100644 index 000000000..6eae04136 --- /dev/null +++ b/test_move_script/sources/scripts/fungible_token.move @@ -0,0 +1,52 @@ +script { + use std::signer; + use std::string::utf8; + use std::option; + + use aptos_framework::fungible_asset::{Metadata}; + use aptos_framework::object::object_from_constructor_ref; + use aptos_token_objects::collection; + use aptos_token_objects::token; + use test_addr::managed_fungible_asset::{Self}; + + const FT: vector = b"FT2"; + + fun create_ft(deployer: &signer) { + // Create token part + collection::create_unlimited_collection( + deployer, + utf8(FT), + utf8(FT), + option::none(), + utf8(FT), + ); + let constructor_ref = &token::create_named_token( + deployer, + utf8(FT), + utf8(FT), + utf8(FT), + option::none(), + utf8(FT), + ); + + // Create fungible asset part + managed_fungible_asset::initialize( + constructor_ref, + 0, /* maximum_supply */ + utf8(FT), + utf8(FT), + 8, /* decimals */ + utf8(b"http://example.com/favicon.ico"), /* icon */ + utf8(b"http://example.com"), /* project */ + vector[true, true, true], /* ref_flags */ + ); + let metadata = object_from_constructor_ref(constructor_ref); + let deployer_addr = signer::address_of(deployer); + managed_fungible_asset::mint_to_primary_stores( + deployer, + metadata, + vector[deployer_addr, @0xcafe], + vector[200000000, 100000000], + ); + } +} \ No newline at end of file diff --git a/test_move_script/sources/scripts/transfer_fungible_token.move b/test_move_script/sources/scripts/transfer_fungible_token.move new file mode 100644 index 000000000..7e1c508a4 --- /dev/null +++ b/test_move_script/sources/scripts/transfer_fungible_token.move @@ -0,0 +1,24 @@ +script { + use std::signer; + use std::string::utf8; + + use aptos_framework::fungible_asset::{Metadata}; + use aptos_framework::object::{Self}; + use aptos_token_objects::token; + use test_addr::managed_fungible_asset::{Self}; + + const FT: vector = b"FT2"; + + fun transfer_ft(deployer: &signer) { + let deployer_address = signer::address_of(deployer); + let token_address = token::create_token_address(&deployer_address, &utf8(FT), &utf8(FT)); + let metadata = object::address_to_object(token_address); + managed_fungible_asset::transfer_between_primary_stores( + deployer, + metadata, + vector[deployer_address], + vector[@0xdead], + vector[100000000], + ); + } +} \ No newline at end of file