From 1436e945ddeea547b6582246372b2f1bfec779b1 Mon Sep 17 00:00:00 2001 From: Bai Chuan Date: Tue, 12 Sep 2023 13:56:12 +0800 Subject: [PATCH] Refactor account balance for support balance API and add some test case (#774) * refactor account balance for support balance API * try to solve rpc server hang cause by futures::executor::block_on when impl MoveFunctionCall * fixed async call_function hung causeby futures::executor::block_on * unify type_ and handle error for tokio worker process --- Cargo.lock | 14 + Cargo.toml | 5 +- crates/rooch-framework/doc/coin.md | 28 ++ crates/rooch-framework/sources/coin.move | 7 + .../rooch-open-rpc-spec/schemas/openrpc.json | 101 +++++ crates/rooch-rpc-api/Cargo.toml | 1 + crates/rooch-rpc-api/src/api/mod.rs | 5 +- crates/rooch-rpc-api/src/api/rooch_api.rs | 19 +- .../src/jsonrpc_types/account_view.rs | 28 +- .../src/jsonrpc_types/rooch_types.rs | 173 ++++---- crates/rooch-rpc-client/src/lib.rs | 19 +- crates/rooch-rpc-server/Cargo.toml | 2 + crates/rooch-rpc-server/src/lib.rs | 9 +- .../rooch-rpc-server/src/server/eth_server.rs | 2 +- .../src/server/rooch_server.rs | 78 +++- .../src/server/wallet_server.rs | 2 +- .../src/service/aggregate_service.rs | 166 ++++++++ crates/rooch-rpc-server/src/service/mod.rs | 184 +-------- .../src/service/rpc_service.rs | 185 +++++++++ crates/rooch-types/src/account.rs | 33 +- crates/rooch-types/src/framework/coin.rs | 381 +++++++++++++----- .../src/commands/account/commands/balance.rs | 143 ++----- crates/testsuite/features/cmd.feature | 6 + .../moveos-types/src/function_return_value.rs | 15 + moveos/moveos-types/src/module_binding.rs | 2 +- moveos/moveos-types/src/object.rs | 8 +- 26 files changed, 1091 insertions(+), 525 deletions(-) create mode 100644 crates/rooch-rpc-server/src/service/aggregate_service.rs create mode 100644 crates/rooch-rpc-server/src/service/rpc_service.rs diff --git a/Cargo.lock b/Cargo.lock index 87de1048dc..6486d5fb6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -640,6 +640,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits 0.2.16", + "serde 1.0.188", +] + [[package]] name = "binascii" version = "0.1.4" @@ -7492,6 +7504,7 @@ dependencies = [ "anyhow", "async-trait", "bcs", + "bigdecimal", "chrono", "clap 3.2.25", "coerce", @@ -7566,6 +7579,7 @@ dependencies = [ "hex", "hyper", "jsonrpsee", + "lazy_static 1.4.0", "log", "move-binary-format", "move-bytecode-utils", diff --git a/Cargo.toml b/Cargo.toml index 67022cc8bc..2261f9ad42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,7 +121,7 @@ ethereum-types = "0.14.1" ethers = { version = "2.0.7", features = ["legacy"] } eyre = "0.6.8" fastcrypto = { git = "https://github.com/rooch-network/fastcrypto", rev = "aa5f9f308b6598779820db8b673050c10cfcc3c1" } -futures = "0.3" +futures = "0.3.28" hex = "0.4.3" rustc-hex = "1.0" rust-embed = "6.8.1" @@ -165,7 +165,7 @@ smallvec = "1.6.1" thiserror = "1.0.34" tiny-keccak = { version = "2", features = ["keccak", "sha3"] } tiny-bip39 = "1.0.0" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1.28.1", features = ["full"] } tonic = { version = "0.8", features = ["gzip"] } tracing = "0.1" tracing-subscriber = "0.3" @@ -202,6 +202,7 @@ bitcoin-bech32 = "0.13.0" bs58 = "0.5.0" dirs-next = "2.0.0" anstream = { version = "0.3" } +bigdecimal = { version = "0.3.0", features = ["serde"] } # Note: the BEGIN and END comments below are required for external tooling. Do not remove. # BEGIN MOVE DEPENDENCIES diff --git a/crates/rooch-framework/doc/coin.md b/crates/rooch-framework/doc/coin.md index 2a1aeb8caf..873743af39 100644 --- a/crates/rooch-framework/doc/coin.md +++ b/crates/rooch-framework/doc/coin.md @@ -28,6 +28,7 @@ This module provides the foundation for typesafe Coins. - [Function `supply`](#0x3_coin_supply) - [Function `is_same_coin`](#0x3_coin_is_same_coin) - [Function `coin_store_handle`](#0x3_coin_coin_store_handle) +- [Function `coin_info_handle`](#0x3_coin_coin_info_handle) - [Function `is_account_accept_coin`](#0x3_coin_is_account_accept_coin) - [Function `can_auto_accept_coin`](#0x3_coin_can_auto_accept_coin) - [Function `do_accept_coin`](#0x3_coin_do_accept_coin) @@ -853,6 +854,33 @@ Return coin store handle for addr + + + + +## Function `coin_info_handle` + +Return coin info handle + + +
public fun coin_info_handle(ctx: &storage_context::StorageContext): object_id::ObjectID
+
+ + + +
+Implementation + + +
public fun coin_info_handle(ctx: &StorageContext): ObjectID {
+    // coin info ensured via the Genesis transaction, so it should always exist
+    let coin_infos = account_storage::global_borrow<CoinInfos>(ctx, @rooch_framework);
+    *type_table::handle(&coin_infos.coin_infos)
+}
+
+ + +
diff --git a/crates/rooch-framework/sources/coin.move b/crates/rooch-framework/sources/coin.move index ce22c3b816..1f26e328b4 100644 --- a/crates/rooch-framework/sources/coin.move +++ b/crates/rooch-framework/sources/coin.move @@ -239,6 +239,13 @@ module rooch_framework::coin { } } + /// Return coin info handle + public fun coin_info_handle(ctx: &StorageContext): ObjectID { + // coin info ensured via the Genesis transaction, so it should always exist + let coin_infos = account_storage::global_borrow(ctx, @rooch_framework); + *type_table::handle(&coin_infos.coin_infos) + } + // // Helper functions // diff --git a/crates/rooch-open-rpc-spec/schemas/openrpc.json b/crates/rooch-open-rpc-spec/schemas/openrpc.json index 56f0b7e557..6504e6236d 100644 --- a/crates/rooch-open-rpc-spec/schemas/openrpc.json +++ b/crates/rooch-open-rpc-spec/schemas/openrpc.json @@ -85,6 +85,46 @@ } } }, + { + "name": "rooch_getBalances", + "description": "get account balances by AccountAddress", + "params": [ + { + "name": "account_addr", + "required": true, + "schema": { + "$ref": "#/components/schemas/move_core_types::account_address::AccountAddress" + } + }, + { + "name": "coin_type", + "schema": { + "$ref": "#/components/schemas/move_core_types::language_storage::StructTag" + } + }, + { + "name": "cursor", + "schema": { + "$ref": "#/components/schemas/alloc::vec::Vec" + } + }, + { + "name": "limit", + "schema": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + } + ], + "result": { + "name": "ListBalanceInfoPageView", + "required": true, + "schema": { + "$ref": "#/components/schemas/PageView_for_Nullable_BalanceInfoView_and_alloc::vec::Vec" + } + } + }, { "name": "rooch_getEvents", "description": "Get the events by event filter", @@ -563,6 +603,31 @@ } } }, + "BalanceInfoView": { + "type": "object", + "required": [ + "balance", + "coin_type", + "decimals", + "symbol" + ], + "properties": { + "balance": { + "$ref": "#/components/schemas/move_core_types::u256::U256" + }, + "coin_type": { + "$ref": "#/components/schemas/move_core_types::language_storage::StructTag" + }, + "decimals": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "symbol": { + "type": "string" + } + } + }, "EventFilterView": { "oneOf": [ { @@ -1156,6 +1221,42 @@ } } }, + "PageView_for_Nullable_BalanceInfoView_and_alloc::vec::Vec": { + "description": "`next_cursor` points to the last item in the page; Reading with `next_cursor` will start from the next item after `next_cursor` if `next_cursor` is `Some`, otherwise it will start from the first item.", + "type": "object", + "required": [ + "data", + "has_next_page" + ], + "properties": { + "data": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/BalanceInfoView" + }, + { + "type": "null" + } + ] + } + }, + "has_next_page": { + "type": "boolean" + }, + "next_cursor": { + "anyOf": [ + { + "$ref": "#/components/schemas/alloc::vec::Vec" + }, + { + "type": "null" + } + ] + } + } + }, "PageView_for_Nullable_StateView_and_alloc::vec::Vec": { "description": "`next_cursor` points to the last item in the page; Reading with `next_cursor` will start from the next item after `next_cursor` if `next_cursor` is `Some`, otherwise it will start from the first item.", "type": "object", diff --git a/crates/rooch-rpc-api/Cargo.toml b/crates/rooch-rpc-api/Cargo.toml index 0e52c64020..9d00feb4eb 100644 --- a/crates/rooch-rpc-api/Cargo.toml +++ b/crates/rooch-rpc-api/Cargo.toml @@ -34,6 +34,7 @@ schemars = { workspace = true } serde_with = { workspace = true } rand = { workspace = true } fastcrypto = { workspace = true } +bigdecimal = { workspace = true } move-core-types = { workspace = true } move-resource-viewer = { workspace = true } diff --git a/crates/rooch-rpc-api/src/api/mod.rs b/crates/rooch-rpc-api/src/api/mod.rs index a976711c9a..78f06baa41 100644 --- a/crates/rooch-rpc-api/src/api/mod.rs +++ b/crates/rooch-rpc-api/src/api/mod.rs @@ -7,7 +7,10 @@ pub mod eth_api; pub mod rooch_api; pub mod wallet_api; -pub const MAX_RESULT_LIMIT: u64 = 50; +pub const DEFAULT_RESULT_LIMIT: u64 = 50; +pub const DEFAULT_RESULT_LIMIT_USIZE: usize = DEFAULT_RESULT_LIMIT as usize; + +pub const MAX_RESULT_LIMIT: u64 = 200; pub const MAX_RESULT_LIMIT_USIZE: usize = MAX_RESULT_LIMIT as usize; // pub fn validate_limit(limit: Option, max: usize) -> Result { diff --git a/crates/rooch-rpc-api/src/api/rooch_api.rs b/crates/rooch-rpc-api/src/api/rooch_api.rs index 45e7ccc2d8..61e49f2a03 100644 --- a/crates/rooch-rpc-api/src/api/rooch_api.rs +++ b/crates/rooch-rpc-api/src/api/rooch_api.rs @@ -2,10 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::jsonrpc_types::{ - AccessPathView, AnnotatedEventView, AnnotatedFunctionResultView, AnnotatedStateView, - EventFilterView, EventPageView, ExecuteTransactionResponseView, FunctionCallView, H256View, - ListAnnotatedStatesPageView, ListStatesPageView, StateView, StrView, StructTagView, - TransactionExecutionInfoView, TransactionInfoPageView, TransactionView, + AccessPathView, AccountAddressView, AnnotatedEventView, AnnotatedFunctionResultView, + AnnotatedStateView, EventFilterView, EventPageView, ExecuteTransactionResponseView, + FunctionCallView, H256View, ListAnnotatedStatesPageView, ListBalanceInfoPageView, + ListStatesPageView, StateView, StrView, StructTagView, TransactionExecutionInfoView, + TransactionInfoPageView, TransactionView, }; use jsonrpsee::core::RpcResult; use jsonrpsee::proc_macros::rpc; @@ -104,4 +105,14 @@ pub trait RoochAPI { &self, tx_hashes: Vec, ) -> RpcResult>>; + + /// get account balances by AccountAddress + #[method(name = "getBalances")] + async fn get_balances( + &self, + account_addr: AccountAddressView, + coin_type: Option, + cursor: Option>>, + limit: Option, + ) -> RpcResult; } diff --git a/crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs b/crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs index fa31e87dda..0aa5c5fec0 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs @@ -1,12 +1,14 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use crate::jsonrpc_types::StructTagView; +use crate::jsonrpc_types::{StrView, StructTagView}; use move_core_types::u256::U256; use rooch_types::account::{AccountInfo, BalanceInfo}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::ops::Div; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AccountInfoView { pub sequence_number: u64, pub balances: Vec>, @@ -50,17 +52,29 @@ impl From for AccountInfo { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct BalanceInfoView { pub coin_type: StructTagView, - pub balance: U256, + pub symbol: String, + pub balance: StrView, + pub decimals: u8, +} + +impl BalanceInfoView { + //TODO implements big decimal calculation for Decimal point display + pub fn get_balance_show(&self) -> String { + let balance = U256::div(self.balance.0, U256::from(10u32.pow(self.decimals as u32))); + balance.to_string() + } } impl From for BalanceInfoView { fn from(balance_info: BalanceInfo) -> Self { BalanceInfoView { coin_type: balance_info.coin_type.into(), - balance: balance_info.balance, + symbol: balance_info.symbol, + balance: balance_info.balance.into(), + decimals: balance_info.decimals, } } } @@ -69,7 +83,9 @@ impl From for BalanceInfo { fn from(balance_info: BalanceInfoView) -> Self { BalanceInfo { coin_type: balance_info.coin_type.into(), - balance: balance_info.balance, + symbol: balance_info.symbol, + balance: balance_info.balance.into(), + decimals: balance_info.decimals, } } } diff --git a/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs b/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs index 229ef32a2b..04f8a6fdd1 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs @@ -1,20 +1,22 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 +use crate::jsonrpc_types::account_view::BalanceInfoView; use crate::jsonrpc_types::{ move_types::{MoveActionTypeView, MoveActionView}, - AnnotatedMoveStructView, AnnotatedMoveValueView, AnnotatedStateView, EventView, H256View, - StateView, StrView, StructTagView, TransactionExecutionInfoView, + AnnotatedMoveStructView, AnnotatedStateView, EventView, H256View, StateView, StrView, + StructTagView, TransactionExecutionInfoView, }; -use anyhow::bail; -use anyhow::Result; use move_core_types::u256::U256; use moveos_types::event::AnnotatedMoveOSEvent; -use moveos_types::state::MoveState; +use rooch_types::framework::coin::{ + AnnotatedCoin, AnnotatedCoinInfo, AnnotatedCoinStore, Coin, CoinInfo, CompoundCoinStore, +}; use rooch_types::transaction::{AbstractTransaction, TransactionType, TypedTransaction}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::serde_as; +use std::string::String; use super::AccountAddressView; @@ -22,6 +24,7 @@ pub type EventPageView = PageView, u64>; pub type TransactionInfoPageView = PageView, u128>; pub type ListStatesPageView = PageView, StrView>>; pub type ListAnnotatedStatesPageView = PageView, StrView>>; +pub type ListBalanceInfoPageView = PageView, StrView>>; /// `next_cursor` points to the last item in the page; /// Reading with `next_cursor` will start from the next item after `next_cursor` if @@ -121,7 +124,7 @@ impl From for AnnotatedEventView { // } #[serde_as] -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct CoinView { value: StrView, } @@ -132,8 +135,9 @@ impl CoinView { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AnnotatedCoinView { + #[serde(rename = "type")] type_: StructTagView, value: CoinView, } @@ -144,7 +148,7 @@ impl AnnotatedCoinView { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct CompoundCoinStoreView { coin: AnnotatedCoinView, frozen: bool, @@ -156,111 +160,82 @@ impl CompoundCoinStoreView { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AnnotatedCoinStoreView { + #[serde(rename = "type")] type_: StructTagView, value: CompoundCoinStoreView, } -impl AnnotatedCoinStoreView { - pub fn new(type_: StructTagView, value: CompoundCoinStoreView) -> Self { - AnnotatedCoinStoreView { type_, value } +impl From for AnnotatedCoinStoreView { + fn from(coin_store: AnnotatedCoinStore) -> Self { + let coin = CoinView { + value: StrView(coin_store.value.coin.value.value), + }; + let annotated_coin = AnnotatedCoinView { + type_: coin_store.value.coin.type_.into(), + value: coin, + }; + let compose_coin_store = CompoundCoinStoreView { + coin: annotated_coin, + frozen: coin_store.value.frozen, + }; + AnnotatedCoinStoreView { + type_: coin_store.type_.into(), + value: compose_coin_store, + } } +} - /// Create a new AnnotatedCoinStoreView from a AnnotatedMoveValueView - pub fn new_from_annotated_move_value_view( - annotated_move_value_view: AnnotatedMoveValueView, - ) -> Result { - match annotated_move_value_view { - AnnotatedMoveValueView::Struct(annotated_struct_view) => { - let annotated_coin_store_type = annotated_struct_view.type_; - let mut fields = annotated_struct_view.value.into_iter(); - let annotated_coin = match fields.next().expect("CoinStore should have coin field") - { - (field_name, AnnotatedMoveValueView::Struct(filed_value)) => { - debug_assert!( - field_name.as_str() == "coin", - "CoinStore coin field name should be coin" - ); +impl From for AnnotatedCoinStore { + fn from(coin_store: AnnotatedCoinStoreView) -> Self { + let coin = Coin::new(coin_store.value.coin.value.value.0); + let annotated_coin = AnnotatedCoin::new(coin_store.value.coin.type_.into(), coin); + let compose_coin_store = CompoundCoinStore::new(annotated_coin, coin_store.value.frozen); - let coin_type_ = filed_value.type_; + AnnotatedCoinStore::new(coin_store.type_.into(), compose_coin_store) + } +} - let mut inner_fields = filed_value.value.into_iter(); - let coin_value = match inner_fields - .next() - .expect("CoinValue should have value field") - { - (field_name, AnnotatedMoveValueView::Bytes(inner_filed_value)) => { - debug_assert!( - field_name.as_str() == "value", - "CoinValue value field name should be value" - ); - U256::from_bytes(inner_filed_value.0.as_slice())? - } - (field_name, AnnotatedMoveValueView::U64(inner_filed_value)) => { - debug_assert!( - field_name.as_str() == "value", - "CoinValue value field name should be value" - ); - U256::from(inner_filed_value.0) - } - (field_name, AnnotatedMoveValueView::U128(inner_filed_value)) => { - debug_assert!( - field_name.as_str() == "value", - "CoinValue value field name should be value" - ); - U256::from(inner_filed_value.0) - } - (field_name, AnnotatedMoveValueView::U256(inner_filed_value)) => { - debug_assert!( - field_name.as_str() == "value", - "CoinValue value field name should be value" - ); - inner_filed_value.0 - } - _ => bail!("CoinValue value field should be value"), - }; - let coin = CoinView { - value: StrView(coin_value), - }; - AnnotatedCoinView { - type_: coin_type_, - value: coin, - } - } - _ => bail!("CoinStore coin field should be struct"), - }; - let frozen = match fields.next().expect("CoinStore should have frozen field") { - (field_name, AnnotatedMoveValueView::Bool(filed_value)) => { - debug_assert!( - field_name.as_str() == "frozen", - "CoinStore field name should be frozen" - ); - filed_value - } - _ => bail!("CoinStore frozen field should be bool"), - }; - let compose_coin_store = CompoundCoinStoreView { - coin: annotated_coin, - frozen, - }; +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct CoinInfoView { + name: String, + symbol: String, + decimals: u8, + supply: StrView, +} - let annotated_coin_store_view = AnnotatedCoinStoreView { - type_: annotated_coin_store_type, - value: compose_coin_store, - }; +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct AnnotatedCoinInfoView { + #[serde(rename = "type")] + type_: StructTagView, + value: CoinInfoView, +} - Ok(annotated_coin_store_view) - } - _ => bail!("CoinValue value field should be value"), +impl From for AnnotatedCoinInfoView { + fn from(annotated_coin_info: AnnotatedCoinInfo) -> Self { + let coin_info = CoinInfoView { + name: annotated_coin_info.value.name, + symbol: annotated_coin_info.value.symbol, + decimals: annotated_coin_info.value.decimals, + supply: StrView(annotated_coin_info.value.supply), + }; + AnnotatedCoinInfoView { + type_: annotated_coin_info.type_.into(), + value: coin_info, } } +} - pub fn get_coin_type_(&self) -> StructTagView { - self.value.coin.type_.clone() - } +impl From for AnnotatedCoinInfo { + fn from(annotated_coin_info: AnnotatedCoinInfoView) -> Self { + let coin_info = CoinInfo::new( + annotated_coin_info.value.name, + annotated_coin_info.value.symbol, + annotated_coin_info.value.decimals, + annotated_coin_info.value.supply.0, + ); - pub fn get_coin_value(&self) -> StrView { - self.value.coin.value.value + AnnotatedCoinInfo::new(annotated_coin_info.type_.into(), coin_info) } } diff --git a/crates/rooch-rpc-client/src/lib.rs b/crates/rooch-rpc-client/src/lib.rs index 4fecf7efef..35dd026f27 100644 --- a/crates/rooch-rpc-client/src/lib.rs +++ b/crates/rooch-rpc-client/src/lib.rs @@ -12,8 +12,9 @@ use moveos_types::{ tx_context::TxContext, }; use rooch_rpc_api::jsonrpc_types::{ - AccessPathView, AnnotatedFunctionResultView, EventPageView, ListAnnotatedStatesPageView, - ListStatesPageView, StrView, StructTagView, + AccessPathView, AccountAddressView, AnnotatedFunctionResultView, EventPageView, + ListAnnotatedStatesPageView, ListBalanceInfoPageView, ListStatesPageView, StrView, + StructTagView, }; use rooch_rpc_api::{ api::rooch_api::RoochAPIClient, @@ -196,6 +197,20 @@ impl Client { .list_annotated_states(access_path, cursor, limit) .await?) } + + pub async fn get_balances( + &self, + account_addr: AccountAddressView, + coin_type: Option, + cursor: Option>>, + limit: Option, + ) -> Result { + Ok(self + .rpc + .http + .get_balances(account_addr, coin_type, cursor, limit) + .await?) + } } impl MoveFunctionCaller for Client { diff --git a/crates/rooch-rpc-server/Cargo.toml b/crates/rooch-rpc-server/Cargo.toml index 20d8bcc8f6..e416780b6e 100644 --- a/crates/rooch-rpc-server/Cargo.toml +++ b/crates/rooch-rpc-server/Cargo.toml @@ -40,6 +40,8 @@ rand = { workspace = true } fastcrypto = { workspace = true } hyper = { workspace = true } log = { workspace = true } +lazy_static = { workspace = true } + move-core-types = { workspace = true } move-resource-viewer = { workspace = true } diff --git a/crates/rooch-rpc-server/src/lib.rs b/crates/rooch-rpc-server/src/lib.rs index 5d2941d314..ce31e251a4 100644 --- a/crates/rooch-rpc-server/src/lib.rs +++ b/crates/rooch-rpc-server/src/lib.rs @@ -4,7 +4,8 @@ use crate::server::eth_server::EthServer; use crate::server::rooch_server::RoochServer; use crate::server::wallet_server::WalletServer; -use crate::service::RpcService; +use crate::service::aggregate_service::AggregateService; +use crate::service::rpc_service::RpcService; use anyhow::Result; use coerce::actor::scheduler::timer::Timer; use coerce::actor::{system::ActorSystem, IntoActor}; @@ -191,6 +192,7 @@ pub async fn run_start_server(opt: &RoochOpt) -> Result { ); let rpc_service = RpcService::new(executor_proxy, sequencer_proxy, proposer_proxy); + let aggregate_service = AggregateService::new(rpc_service.clone()); let acl = match env::var("ACCESS_CONTROL_ALLOW_ORIGIN") { Ok(value) => { @@ -221,7 +223,10 @@ pub async fn run_start_server(opt: &RoochOpt) -> Result { .await?; let mut rpc_module_builder = RpcModuleBuilder::new(); - rpc_module_builder.register_module(RoochServer::new(rpc_service.clone()))?; + rpc_module_builder.register_module(RoochServer::new( + rpc_service.clone(), + aggregate_service.clone(), + ))?; rpc_module_builder.register_module(WalletServer::new(rpc_service.clone()))?; rpc_module_builder.register_module(EthServer::new(chain_id, rpc_service.clone()))?; diff --git a/crates/rooch-rpc-server/src/server/eth_server.rs b/crates/rooch-rpc-server/src/server/eth_server.rs index e2a906b642..ff36674be0 100644 --- a/crates/rooch-rpc-server/src/server/eth_server.rs +++ b/crates/rooch-rpc-server/src/server/eth_server.rs @@ -1,7 +1,7 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use crate::service::RpcService; +use crate::service::rpc_service::RpcService; use ethers::types::{ transaction::eip2930::AccessList, Address, Block, BlockNumber, Bloom, Bytes, OtherFields, Transaction, TransactionReceipt, Withdrawal, H160, U256, U64, diff --git a/crates/rooch-rpc-server/src/server/rooch_server.rs b/crates/rooch-rpc-server/src/server/rooch_server.rs index 8fad0b9117..0a83c46648 100644 --- a/crates/rooch-rpc-server/src/server/rooch_server.rs +++ b/crates/rooch-rpc-server/src/server/rooch_server.rs @@ -1,21 +1,25 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use crate::service::RpcService; +use crate::service::aggregate_service::AggregateService; +use crate::service::rpc_service::RpcService; use jsonrpsee::{ core::{async_trait, RpcResult}, RpcModule, }; +use move_core_types::language_storage::StructTag; use moveos_types::h256::H256; +use rooch_rpc_api::api::{MAX_RESULT_LIMIT, MAX_RESULT_LIMIT_USIZE}; +use rooch_rpc_api::jsonrpc_types::account_view::BalanceInfoView; use rooch_rpc_api::jsonrpc_types::{ - AccessPathView, AnnotatedEventView, AnnotatedStateView, EventFilterView, EventPageView, - ExecuteTransactionResponseView, FunctionCallView, H256View, ListAnnotatedStatesPageView, - ListStatesPageView, StateView, StrView, StructTagView, TransactionExecutionInfoView, - TransactionInfoPageView, TransactionView, + AccessPathView, AccountAddressView, AnnotatedEventView, AnnotatedStateView, EventFilterView, + EventPageView, ExecuteTransactionResponseView, FunctionCallView, H256View, + ListAnnotatedStatesPageView, ListBalanceInfoPageView, ListStatesPageView, StateView, StrView, + StructTagView, TransactionExecutionInfoView, TransactionInfoPageView, TransactionView, }; -use rooch_rpc_api::{api::rooch_api::RoochAPIServer, api::MAX_RESULT_LIMIT}; +use rooch_rpc_api::{api::rooch_api::RoochAPIServer, api::DEFAULT_RESULT_LIMIT}; use rooch_rpc_api::{ - api::{RoochRpcModule, MAX_RESULT_LIMIT_USIZE}, + api::{RoochRpcModule, DEFAULT_RESULT_LIMIT_USIZE}, jsonrpc_types::AnnotatedFunctionResultView, }; use rooch_types::transaction::rooch::RoochTransaction; @@ -25,11 +29,15 @@ use tracing::info; pub struct RoochServer { rpc_service: RpcService, + aggregate_service: AggregateService, } impl RoochServer { - pub fn new(rpc_service: RpcService) -> Self { - Self { rpc_service } + pub fn new(rpc_service: RpcService, aggregate_service: AggregateService) -> Self { + Self { + rpc_service, + aggregate_service, + } } } @@ -100,7 +108,7 @@ impl RoochAPIServer for RoochServer { limit: Option, ) -> RpcResult { let limit_of = min( - limit.unwrap_or(MAX_RESULT_LIMIT_USIZE), + limit.unwrap_or(DEFAULT_RESULT_LIMIT_USIZE), MAX_RESULT_LIMIT_USIZE, ); let cursor_of = cursor.clone().map(|v| v.0); @@ -136,7 +144,7 @@ impl RoochAPIServer for RoochServer { limit: Option, ) -> RpcResult { let limit_of = min( - limit.unwrap_or(MAX_RESULT_LIMIT_USIZE), + limit.unwrap_or(DEFAULT_RESULT_LIMIT_USIZE), MAX_RESULT_LIMIT_USIZE, ); let cursor_of = cursor.clone().map(|v| v.0); @@ -172,7 +180,7 @@ impl RoochAPIServer for RoochServer { limit: Option, ) -> RpcResult { // NOTE: fetch one more object to check if there is next page - let limit_of = min(limit.unwrap_or(MAX_RESULT_LIMIT), MAX_RESULT_LIMIT); + let limit_of = min(limit.unwrap_or(DEFAULT_RESULT_LIMIT), MAX_RESULT_LIMIT); let mut data: Vec> = self .rpc_service .get_events_by_event_handle(event_handle_type.into(), cursor, limit_of + 1) @@ -238,7 +246,7 @@ impl RoochAPIServer for RoochServer { limit: Option, ) -> RpcResult { // NOTE: fetch one more object to check if there is next page - let limit_of = limit.unwrap_or(MAX_RESULT_LIMIT); + let limit_of = limit.unwrap_or(DEFAULT_RESULT_LIMIT); let mut tx_seq_mapping = self .rpc_service @@ -287,6 +295,50 @@ impl RoochAPIServer for RoochServer { Ok(result) } + + /// get account balances by AccountAddress + async fn get_balances( + &self, + account_addr: AccountAddressView, + coin_type: Option, + cursor: Option>>, + limit: Option, + ) -> RpcResult { + let limit_of = min( + limit.unwrap_or(DEFAULT_RESULT_LIMIT_USIZE), + MAX_RESULT_LIMIT_USIZE, + ); + let cursor_of = cursor.clone().map(|v| v.0); + let coin_type: Option = coin_type.map(|type_| type_.into()); + + let mut data = self + .aggregate_service + .get_balances(account_addr.into(), coin_type, cursor_of, limit_of + 1) + .await? + .into_iter() + .map(|item| item.map(|(key, balance_info)| (key, BalanceInfoView::from(balance_info)))) + .collect::>(); + + let has_next_page = data.len() > limit_of; + data.truncate(limit_of); + + let next_cursor = data + .last() + .cloned() + .flatten() + .map_or(cursor, |(key, _balance_info)| key.map(StrView)); + + let result = data + .into_iter() + .map(|item| item.map(|(_key, balance_info)| balance_info)) + .collect(); + + Ok(ListBalanceInfoPageView { + data: result, + next_cursor, + has_next_page, + }) + } } impl RoochRpcModule for RoochServer { diff --git a/crates/rooch-rpc-server/src/server/wallet_server.rs b/crates/rooch-rpc-server/src/server/wallet_server.rs index cdc5b8063f..68ff704b8a 100644 --- a/crates/rooch-rpc-server/src/server/wallet_server.rs +++ b/crates/rooch-rpc-server/src/server/wallet_server.rs @@ -1,7 +1,7 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use crate::service::RpcService; +use crate::service::rpc_service::RpcService; use ethers::types::Bytes; use jsonrpsee::core::{async_trait, RpcResult}; use jsonrpsee::RpcModule; diff --git a/crates/rooch-rpc-server/src/service/aggregate_service.rs b/crates/rooch-rpc-server/src/service/aggregate_service.rs new file mode 100644 index 0000000000..a1df71a5ee --- /dev/null +++ b/crates/rooch-rpc-server/src/service/aggregate_service.rs @@ -0,0 +1,166 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use crate::service::rpc_service::RpcService; +use anyhow::Result; +use lazy_static::lazy_static; +use move_core_types::account_address::AccountAddress; +use move_core_types::language_storage::StructTag; +use moveos_types::access_path::AccessPath; +use moveos_types::function_return_value::FunctionResult; +use moveos_types::module_binding::MoveFunctionCaller; +use moveos_types::move_types::get_first_ty_as_struct_tag; +use moveos_types::state_resolver::resource_tag_to_key; +use moveos_types::transaction::FunctionCall; +use moveos_types::tx_context::TxContext; +use rooch_rpc_api::api::MAX_RESULT_LIMIT_USIZE; +use rooch_types::account::BalanceInfo; +use rooch_types::addresses::ROOCH_FRAMEWORK_ADDRESS_LITERAL; +use rooch_types::framework::coin::{AnnotatedCoinInfo, AnnotatedCoinStore, CoinModule}; +use std::collections::HashMap; +use std::str::FromStr; +use tokio::runtime::Runtime; + +/// AggregateService is aggregate RPC service and MoveFunctionCaller. +#[derive(Clone)] +pub struct AggregateService { + rpc_service: RpcService, +} + +impl AggregateService { + pub fn new(rpc_service: RpcService) -> Self { + Self { rpc_service } + } +} + +impl AggregateService { + pub async fn get_balances( + &self, + account_addr: AccountAddress, + coin_type: Option, + cursor: Option>, + limit: usize, + ) -> Result>, BalanceInfo)>>> { + let coin_module = self.as_module_binding::(); + let coin_info_handle = coin_module.coin_info_handle()?; + + // contruct coin info map for handle decimals + //TODO Extract to signle module as cache to load all token info, as well as to avoid query every time + let mut coin_info_table = HashMap::new(); + let coin_info_states = self + .rpc_service + .list_annotated_states( + AccessPath::table_without_keys(coin_info_handle), + None, + MAX_RESULT_LIMIT_USIZE, + ) + .await?; + let coin_info_data = coin_info_states + .into_iter() + .map(|item| { + item.map(|(_key, state)| { + AnnotatedCoinInfo::new_from_annotated_move_value(state.move_value) + .expect("AnnotatedCoinInfo expected return value") + }) + }) + .collect::>(); + for coin_info in coin_info_data.into_iter().flatten() { + let coin_type = get_first_ty_as_struct_tag(coin_info.get_type()) + .expect("Coin type expected get_first_ty_as_struct_tag succ"); + coin_info_table.insert(coin_type, coin_info.value); + } + + let coin_store_handle = coin_module.coin_store_handle(account_addr)?; + let coin_store_handle = match coin_store_handle { + Some(v) => v, + None => anyhow::bail!("Coin store handle does not exist"), + }; + + let mut result = vec![]; + if let Some(coin_type) = coin_type { + let coin_store_type = format!( + "{}::coin::CoinStore<0x{}>", + ROOCH_FRAMEWORK_ADDRESS_LITERAL, + coin_type.to_canonical_string() + ); + let key = resource_tag_to_key(&StructTag::from_str(coin_store_type.as_str())?); + let keys = vec![key]; + let mut states = self + .rpc_service + .get_annotated_states(AccessPath::table(coin_store_handle, keys)) + .await?; + + let state = states.pop().flatten().expect("State expected return value"); + let annotated_coin_store = + AnnotatedCoinStore::new_from_annotated_move_value(state.move_value)?; + + let balance_info = + BalanceInfo::new_with_default(coin_type, annotated_coin_store.get_coin_value()); + result.push(Some((None, balance_info))) + } else { + //TODO If the coin store list exceeds MAX_RESULT_LIMIT_USIZE, consider supporting traverse or pagination + let states = self + .rpc_service + .list_annotated_states( + AccessPath::table_without_keys(coin_store_handle), + cursor, + limit, + ) + .await?; + + let mut data = states + .into_iter() + .map(|item| { + item.map(|(key, state)| { + let coin_store = + AnnotatedCoinStore::new_from_annotated_move_value(state.move_value) + .expect("AnnotatedCoinStore expected return value"); + + let coin_type = get_first_ty_as_struct_tag(coin_store.get_coin_type()) + .expect("Coin type expected get_first_ty_as_struct_tag succ"); + let balance_info = + BalanceInfo::new_with_default(coin_type, coin_store.get_coin_value()); + (Some(key), balance_info) + }) + }) + .collect::>(); + + result.append(&mut data); + }; + + for (_key, balance_info) in result.iter_mut().flatten() { + let coin_info = coin_info_table + .get(&balance_info.coin_type) + .expect("Get coin info by coin type expected return value"); + balance_info.symbol = coin_info.symbol.clone(); + balance_info.decimals = coin_info.decimals; + } + + Ok(result) + } +} + +lazy_static! { + static ref RUNTIME: Runtime = tokio::runtime::Builder::new_multi_thread() + .thread_name("rooch-aggregate-service") + .enable_all() + .build() + .unwrap(); +} + +impl MoveFunctionCaller for AggregateService { + // Use futures::executors::block_on to go from sync -> async + // Warning! Possible deadlocks can occur if we try to wait for a future without spawn + fn call_function( + &self, + _ctx: &TxContext, + function_call: FunctionCall, + ) -> Result { + let rpc_service = self.rpc_service.clone(); + let function_result = futures::executor::block_on( + RUNTIME.spawn(async move { rpc_service.execute_view_function(function_call).await }), + )??; + + function_result.try_into() + } +} diff --git a/crates/rooch-rpc-server/src/service/mod.rs b/crates/rooch-rpc-server/src/service/mod.rs index de2274585b..3c8acd0439 100644 --- a/crates/rooch-rpc-server/src/service/mod.rs +++ b/crates/rooch-rpc-server/src/service/mod.rs @@ -1,185 +1,5 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 -use anyhow::{bail, Result}; -use move_core_types::account_address::AccountAddress; -use move_core_types::language_storage::StructTag; -use moveos_types::access_path::AccessPath; -use moveos_types::event::AnnotatedMoveOSEvent; -use moveos_types::event_filter::EventFilter; -use moveos_types::function_return_value::AnnotatedFunctionResult; -use moveos_types::state::{AnnotatedState, State}; -use moveos_types::transaction::{FunctionCall, TransactionExecutionInfo}; -use rooch_executor::proxy::ExecutorProxy; -use rooch_proposer::proxy::ProposerProxy; -use rooch_rpc_api::jsonrpc_types::ExecuteTransactionResponse; -use rooch_sequencer::proxy::SequencerProxy; -use rooch_types::address::{MultiChainAddress, RoochAddress}; -use rooch_types::transaction::TransactionSequenceMapping; -use rooch_types::{transaction::TypedTransaction, H256}; - -/// RpcService is the implementation of the RPC service. -/// It is the glue between the RPC server(EthAPIServer,RoochApiServer) and the rooch's actors. -/// The RpcService encapsulates the logic of the functions, and the RPC server handle the response format. -#[derive(Clone)] -pub struct RpcService { - executor: ExecutorProxy, - sequencer: SequencerProxy, - proposer: ProposerProxy, -} - -impl RpcService { - pub fn new( - executor: ExecutorProxy, - sequencer: SequencerProxy, - proposer: ProposerProxy, - ) -> Self { - Self { - executor, - sequencer, - proposer, - } - } -} - -impl RpcService { - pub async fn quene_tx(&self, tx: TypedTransaction) -> Result<()> { - //TODO implement quene tx and do not wait to execute - let _ = self.execute_tx(tx).await?; - Ok(()) - } - - pub async fn execute_tx(&self, tx: TypedTransaction) -> Result { - //First, validate the transactin - let moveos_tx = self.executor.validate_transaction(tx.clone()).await?; - let sequence_info = self.sequencer.sequence_transaction(tx.clone()).await?; - // Then execute - let (output, execution_info) = self.executor.execute_transaction(moveos_tx).await?; - self.proposer - .propose_transaction(tx, execution_info.clone(), sequence_info.clone()) - .await?; - - Ok(ExecuteTransactionResponse { - sequence_info, - execution_info, - output, - }) - } - - pub async fn execute_view_function( - &self, - function_call: FunctionCall, - ) -> Result { - let resp = self.executor.execute_view_function(function_call).await?; - Ok(resp) - } - - pub async fn resolve_address(&self, mca: MultiChainAddress) -> Result { - self.executor.resolve_address(mca).await - } - - pub async fn get_states(&self, access_path: AccessPath) -> Result>> { - self.executor.get_states(access_path).await - } - - pub async fn get_annotated_states( - &self, - access_path: AccessPath, - ) -> Result>> { - self.executor.get_annotated_states(access_path).await - } - - pub async fn list_states( - &self, - access_path: AccessPath, - cursor: Option>, - limit: usize, - ) -> Result, State)>>> { - self.executor.list_states(access_path, cursor, limit).await - } - - pub async fn list_annotated_states( - &self, - access_path: AccessPath, - cursor: Option>, - limit: usize, - ) -> Result, AnnotatedState)>>> { - self.executor - .list_annotated_states(access_path, cursor, limit) - .await - } - - /// Sign a message with the private key of the given address. - pub async fn sign(&self, _address: RoochAddress, _message: Vec) -> Result> { - bail!("Not implemented") - //TODO implement sign - //Call WalletActor to sign? - //How to unlock the wallet? - //Define the sign message format for rooch, and does it need to be compatible with Ethereum? - } - - pub async fn accounts(&self) -> Result> { - bail!("Not implemented") - } - - pub async fn get_events_by_event_handle( - &self, - event_handle_type: StructTag, - cursor: Option, - limit: u64, - ) -> Result>> { - let resp = self - .executor - .get_events_by_event_handle(event_handle_type, cursor, limit) - .await?; - Ok(resp) - } - - pub async fn get_events( - &self, - filter: EventFilter, - ) -> Result>> { - let resp = self.executor.get_events(filter).await?; - Ok(resp) - } - - pub async fn get_transaction_by_hash(&self, hash: H256) -> Result> { - let resp = self.sequencer.get_transaction_by_hash(hash).await?; - Ok(resp) - } - - pub async fn get_transaction_by_index( - &self, - start: u64, - limit: u64, - ) -> Result> { - let resp = self - .sequencer - .get_transaction_by_index(start, limit) - .await?; - Ok(resp) - } - - pub async fn get_tx_seq_mapping_by_tx_order( - &self, - cursor: Option, - limit: u64, - ) -> Result> { - let resp = self - .executor - .get_tx_seq_mapping_by_tx_order(cursor, limit) - .await?; - Ok(resp) - } - - pub async fn get_transaction_infos_by_tx_hash( - &self, - tx_hashes: Vec, - ) -> Result>> { - let resp = self - .executor - .get_transaction_infos_by_tx_hash(tx_hashes) - .await?; - Ok(resp) - } -} +pub mod aggregate_service; +pub mod rpc_service; diff --git a/crates/rooch-rpc-server/src/service/rpc_service.rs b/crates/rooch-rpc-server/src/service/rpc_service.rs new file mode 100644 index 0000000000..de2274585b --- /dev/null +++ b/crates/rooch-rpc-server/src/service/rpc_service.rs @@ -0,0 +1,185 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::{bail, Result}; +use move_core_types::account_address::AccountAddress; +use move_core_types::language_storage::StructTag; +use moveos_types::access_path::AccessPath; +use moveos_types::event::AnnotatedMoveOSEvent; +use moveos_types::event_filter::EventFilter; +use moveos_types::function_return_value::AnnotatedFunctionResult; +use moveos_types::state::{AnnotatedState, State}; +use moveos_types::transaction::{FunctionCall, TransactionExecutionInfo}; +use rooch_executor::proxy::ExecutorProxy; +use rooch_proposer::proxy::ProposerProxy; +use rooch_rpc_api::jsonrpc_types::ExecuteTransactionResponse; +use rooch_sequencer::proxy::SequencerProxy; +use rooch_types::address::{MultiChainAddress, RoochAddress}; +use rooch_types::transaction::TransactionSequenceMapping; +use rooch_types::{transaction::TypedTransaction, H256}; + +/// RpcService is the implementation of the RPC service. +/// It is the glue between the RPC server(EthAPIServer,RoochApiServer) and the rooch's actors. +/// The RpcService encapsulates the logic of the functions, and the RPC server handle the response format. +#[derive(Clone)] +pub struct RpcService { + executor: ExecutorProxy, + sequencer: SequencerProxy, + proposer: ProposerProxy, +} + +impl RpcService { + pub fn new( + executor: ExecutorProxy, + sequencer: SequencerProxy, + proposer: ProposerProxy, + ) -> Self { + Self { + executor, + sequencer, + proposer, + } + } +} + +impl RpcService { + pub async fn quene_tx(&self, tx: TypedTransaction) -> Result<()> { + //TODO implement quene tx and do not wait to execute + let _ = self.execute_tx(tx).await?; + Ok(()) + } + + pub async fn execute_tx(&self, tx: TypedTransaction) -> Result { + //First, validate the transactin + let moveos_tx = self.executor.validate_transaction(tx.clone()).await?; + let sequence_info = self.sequencer.sequence_transaction(tx.clone()).await?; + // Then execute + let (output, execution_info) = self.executor.execute_transaction(moveos_tx).await?; + self.proposer + .propose_transaction(tx, execution_info.clone(), sequence_info.clone()) + .await?; + + Ok(ExecuteTransactionResponse { + sequence_info, + execution_info, + output, + }) + } + + pub async fn execute_view_function( + &self, + function_call: FunctionCall, + ) -> Result { + let resp = self.executor.execute_view_function(function_call).await?; + Ok(resp) + } + + pub async fn resolve_address(&self, mca: MultiChainAddress) -> Result { + self.executor.resolve_address(mca).await + } + + pub async fn get_states(&self, access_path: AccessPath) -> Result>> { + self.executor.get_states(access_path).await + } + + pub async fn get_annotated_states( + &self, + access_path: AccessPath, + ) -> Result>> { + self.executor.get_annotated_states(access_path).await + } + + pub async fn list_states( + &self, + access_path: AccessPath, + cursor: Option>, + limit: usize, + ) -> Result, State)>>> { + self.executor.list_states(access_path, cursor, limit).await + } + + pub async fn list_annotated_states( + &self, + access_path: AccessPath, + cursor: Option>, + limit: usize, + ) -> Result, AnnotatedState)>>> { + self.executor + .list_annotated_states(access_path, cursor, limit) + .await + } + + /// Sign a message with the private key of the given address. + pub async fn sign(&self, _address: RoochAddress, _message: Vec) -> Result> { + bail!("Not implemented") + //TODO implement sign + //Call WalletActor to sign? + //How to unlock the wallet? + //Define the sign message format for rooch, and does it need to be compatible with Ethereum? + } + + pub async fn accounts(&self) -> Result> { + bail!("Not implemented") + } + + pub async fn get_events_by_event_handle( + &self, + event_handle_type: StructTag, + cursor: Option, + limit: u64, + ) -> Result>> { + let resp = self + .executor + .get_events_by_event_handle(event_handle_type, cursor, limit) + .await?; + Ok(resp) + } + + pub async fn get_events( + &self, + filter: EventFilter, + ) -> Result>> { + let resp = self.executor.get_events(filter).await?; + Ok(resp) + } + + pub async fn get_transaction_by_hash(&self, hash: H256) -> Result> { + let resp = self.sequencer.get_transaction_by_hash(hash).await?; + Ok(resp) + } + + pub async fn get_transaction_by_index( + &self, + start: u64, + limit: u64, + ) -> Result> { + let resp = self + .sequencer + .get_transaction_by_index(start, limit) + .await?; + Ok(resp) + } + + pub async fn get_tx_seq_mapping_by_tx_order( + &self, + cursor: Option, + limit: u64, + ) -> Result> { + let resp = self + .executor + .get_tx_seq_mapping_by_tx_order(cursor, limit) + .await?; + Ok(resp) + } + + pub async fn get_transaction_infos_by_tx_hash( + &self, + tx_hashes: Vec, + ) -> Result>> { + let resp = self + .executor + .get_transaction_infos_by_tx_hash(tx_hashes) + .await?; + Ok(resp) + } +} diff --git a/crates/rooch-types/src/account.rs b/crates/rooch-types/src/account.rs index f9a3436766..792a39e451 100644 --- a/crates/rooch-types/src/account.rs +++ b/crates/rooch-types/src/account.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::addresses::ROOCH_FRAMEWORK_ADDRESS; +use crate::framework::coin; use move_core_types::language_storage::StructTag; use move_core_types::u256::U256; use move_core_types::{ @@ -90,18 +91,42 @@ impl AccountInfo { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BalanceInfo { pub coin_type: StructTag, + pub symbol: String, pub balance: U256, + pub decimals: u8, } impl BalanceInfo { - pub fn new(coin_type: StructTag, balance: U256) -> Self { - Self { coin_type, balance } + pub fn new(coin_type: StructTag, symbol: String, balance: U256, decimals: u8) -> Self { + Self { + coin_type, + symbol, + balance, + decimals, + } + } + + pub fn new_with_default(coin_type: StructTag, balance: U256) -> Self { + let default_symbol = coin_type.name.to_string(); + let default_decimals = coin::DEFAULT_DECIMALS; + Self { + coin_type, + symbol: default_symbol, + balance, + decimals: default_decimals, + } } pub fn random() -> Self { let coin_type = random_struct_tag(); - // let coin_type = StructTag::new(struct_tag.address, struct_tag.module, struct_tag.name); let balance = U256::zero(); - BalanceInfo { coin_type, balance } + let symbol = coin_type.name.to_string(); + + BalanceInfo { + coin_type, + symbol, + balance, + decimals: 9u8, + } } } diff --git a/crates/rooch-types/src/framework/coin.rs b/crates/rooch-types/src/framework/coin.rs index f74d03c31f..138c2bc356 100644 --- a/crates/rooch-types/src/framework/coin.rs +++ b/crates/rooch-types/src/framework/coin.rs @@ -6,7 +6,7 @@ use anyhow::{bail, Ok, Result}; use move_core_types::language_storage::StructTag; use move_core_types::u256::U256; use move_core_types::{account_address::AccountAddress, ident_str, identifier::IdentStr}; -use move_resource_viewer::{AnnotatedMoveStruct, AnnotatedMoveValue}; +use move_resource_viewer::AnnotatedMoveValue; use moveos_types::object::ObjectID; use moveos_types::state::MoveState; use moveos_types::{ @@ -18,10 +18,77 @@ use moveos_types::{ use serde::{Deserialize, Serialize}; use serde_with::serde_as; +pub const DEFAULT_DECIMALS: u8 = 9; + +/// Rust bindings for RoochFramework coin module +pub struct CoinModule<'a> { + caller: &'a dyn MoveFunctionCaller, +} + +impl<'a> CoinModule<'a> { + pub const COIN_STORE_HANDLE_FUNCTION_NAME: &'static IdentStr = ident_str!("coin_store_handle"); + pub const COIN_INFO_HANDLE_FUNCTION_NAME: &'static IdentStr = ident_str!("coin_info_handle"); + + pub fn coin_store_handle(&self, addr: AccountAddress) -> Result> { + let ctx = TxContext::zero(); + let call = FunctionCall::new( + Self::function_id(Self::COIN_STORE_HANDLE_FUNCTION_NAME), + vec![], + vec![addr.to_vec()], + ); + let result = self + .caller + .call_function(&ctx, call)? + .into_result() + .map(|values| { + let value = values + .get(0) + .expect("Coin store handle expected return value"); + let result = MoveOption::::from_bytes(&value.value) + .expect("Coin store handle expected Option"); + result.into() + })?; + Ok(result) + } + + pub fn coin_info_handle(&self) -> Result { + let ctx = TxContext::zero(); + let call = FunctionCall::new( + Self::function_id(Self::COIN_INFO_HANDLE_FUNCTION_NAME), + vec![], + vec![], + ); + let result = self + .caller + .call_function(&ctx, call)? + .into_result() + .map(|values| { + let value = values + .get(0) + .expect("Coin info handle expected return value"); + bcs::from_bytes::(&value.value) + .expect("Coin info handle expected Option") + })?; + Ok(result) + } +} + +impl<'a> ModuleBinding<'a> for CoinModule<'a> { + const MODULE_NAME: &'static IdentStr = ident_str!("coin"); + const MODULE_ADDRESS: AccountAddress = ROOCH_FRAMEWORK_ADDRESS; + + fn new(caller: &'a impl MoveFunctionCaller) -> Self + where + Self: Sized, + { + Self { caller } + } +} + #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Coin { - value: U256, + pub value: U256, } impl Coin { @@ -32,8 +99,9 @@ impl Coin { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AnnotatedCoin { - type_: StructTag, - value: Coin, + #[serde(rename = "type")] + pub type_: StructTag, + pub value: Coin, } impl AnnotatedCoin { @@ -44,8 +112,8 @@ impl AnnotatedCoin { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CompoundCoinStore { - coin: AnnotatedCoin, - frozen: bool, + pub coin: AnnotatedCoin, + pub frozen: bool, } impl CompoundCoinStore { @@ -56,8 +124,9 @@ impl CompoundCoinStore { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AnnotatedCoinStore { - type_: StructTag, - value: CompoundCoinStore, + #[serde(rename = "type")] + pub type_: StructTag, + pub value: CompoundCoinStore, } impl AnnotatedCoinStore { @@ -65,65 +134,70 @@ impl AnnotatedCoinStore { AnnotatedCoinStore { type_, value } } - /// Create a new AnnotatedCoinStore from a AnnotatedMoveStruct - pub fn new_from_annotated_struct(annotated_struct: AnnotatedMoveStruct) -> Result { - let annotated_coin_store_type = annotated_struct.type_; - let mut fields = annotated_struct.value.into_iter(); - let annotated_coin = match fields.next().expect("CoinStore should have coin field") { - (field_name, AnnotatedMoveValue::Struct(filed_value)) => { - debug_assert!( - field_name.as_str() == "coin", - "CoinStore coin field name should be coin" - ); - - let coin_type_ = filed_value.type_; - let mut inner_fields = filed_value.value.into_iter(); - let coin_value = match inner_fields - .next() - .expect("CoinValue should have value field") + /// Create a new AnnotatedCoinStore from a AnnotatedMoveValue + pub fn new_from_annotated_move_value(annotated_move_value: AnnotatedMoveValue) -> Result { + match annotated_move_value { + AnnotatedMoveValue::Struct(annotated_struct) => { + let annotated_coin_store_type = annotated_struct.type_; + let mut fields = annotated_struct.value.into_iter(); + let annotated_coin = match fields.next().expect("CoinStore should have coin field") { - (field_name, AnnotatedMoveValue::Bytes(inner_filed_value)) => { + (field_name, AnnotatedMoveValue::Struct(field_value)) => { debug_assert!( - field_name.as_str() == "value", - "CoinValue value field name should be value" + field_name.as_str() == "coin", + "CoinStore coin field name should be coin" ); - U256::from_bytes(inner_filed_value.as_slice()) + let coin_type = field_value.type_; + + let mut inner_fields = field_value.value.into_iter(); + let coin_value = match inner_fields + .next() + .expect("CoinValue should have value field") + { + (field_name, AnnotatedMoveValue::U256(inner_field_value)) => { + debug_assert!( + field_name.as_str() == "value", + "CoinValue value field name should be value" + ); + inner_field_value + } + _ => bail!("CoinValue value field should be value"), + }; + let coin = Coin { value: coin_value }; + AnnotatedCoin { + type_: coin_type, + value: coin, + } } - _ => bail!("CoinValue value field should be value"), - }?; - - let coin = Coin { value: coin_value }; - AnnotatedCoin { - type_: coin_type_, - value: coin, - } - } - _ => bail!("CoinStore coin field should be struct"), - }; - let frozen = match fields.next().expect("CoinStore should have frozen field") { - (field_name, AnnotatedMoveValue::Bool(filed_value)) => { - debug_assert!( - field_name.as_str() == "frozen", - "CoinStore field name should be frozen" - ); - filed_value + _ => bail!("CoinStore coin field should be struct"), + }; + let frozen = match fields.next().expect("CoinStore should have frozen field") { + (field_name, AnnotatedMoveValue::Bool(field_value)) => { + debug_assert!( + field_name.as_str() == "frozen", + "CoinStore field name should be frozen" + ); + field_value + } + _ => bail!("CoinStore frozen field should be bool"), + }; + let compose_coin_store = CompoundCoinStore { + coin: annotated_coin, + frozen, + }; + + let annotated_coin_store = AnnotatedCoinStore { + type_: annotated_coin_store_type, + value: compose_coin_store, + }; + + Ok(annotated_coin_store) } - _ => bail!("CoinStore frozen field should be bool"), - }; - let compose_coin_store = CompoundCoinStore { - coin: annotated_coin, - frozen, - }; - - let annotated_coin_store = AnnotatedCoinStore { - type_: annotated_coin_store_type, - value: compose_coin_store, - }; - - Ok(annotated_coin_store) + _ => bail!("CoinValue value field should be value"), + } } - pub fn get_coin_type_(&self) -> StructTag { + pub fn get_coin_type(&self) -> StructTag { self.value.coin.type_.clone() } @@ -132,43 +206,166 @@ impl AnnotatedCoinStore { } } -/// Rust bindings for RoochFramework coin module -pub struct CoinModule<'a> { - caller: &'a dyn MoveFunctionCaller, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CoinInfo { + pub name: String, + pub symbol: String, + pub decimals: u8, + pub supply: U256, } +// +// #[derive(Debug, Clone, Serialize, Deserialize)] +// pub struct CoinInfo { +// pub name: String, +// pub symbol: String, +// pub decimals: u8, +// pub supply: U256, +// pub coin_type: std::marker::PhantomData, +// } +// +// impl MoveStructType for CoinInfo +// where T: MoveStructType, { +// const ADDRESS: AccountAddress = ROOCH_FRAMEWORK_ADDRESS; +// const MODULE_NAME: &'static IdentStr = ident_str!("coin"); +// const STRUCT_NAME: &'static IdentStr = ident_str!("CoinInfo"); +// +// fn struct_tag() -> move_core_types::language_storage::StructTag { +// move_core_types::language_storage::StructTag { +// address: Self::ADDRESS, +// module: Self::MODULE_NAME.to_owned(), +// name: Self::STRUCT_NAME.to_owned(), +// type_params: vec![T::type_tag()], +// } +// } +// } +// +// impl MoveStructState for CoinInfo where T: MoveStructType, { +// fn struct_layout() -> move_core_types::value::MoveStructLayout { +// move_core_types::value::MoveStructLayout::new(vec![ +// move_core_types::value::MoveTypeLayout::Vector(Box::new( +// move_core_types::value::MoveTypeLayout::U8, +// )), +// move_core_types::value::MoveTypeLayout::Vector(Box::new( +// move_core_types::value::MoveTypeLayout::U8, +// )), +// move_core_types::value::MoveTypeLayout::U8, +// move_core_types::value::MoveTypeLayout::U256, +// ]) +// } +// } -impl<'a> CoinModule<'a> { - pub const COIN_STORE_HANDLE_FUNCTION_NAME: &'static IdentStr = ident_str!("coin_store_handle"); - - pub fn coin_store_handle(&self, addr: AccountAddress) -> Result> { - let ctx = TxContext::zero(); - let call = FunctionCall::new( - Self::function_id(Self::COIN_STORE_HANDLE_FUNCTION_NAME), - vec![], - vec![addr.to_vec()], - ); - let result = self - .caller - .call_function(&ctx, call)? - .into_result() - .map(|values| { - let value = values.get(0).expect("Expected return value"); - let result = MoveOption::::from_bytes(&value.value) - .expect("Expected Option"); - result.into() - })?; - Ok(result) +impl CoinInfo { + pub fn new(name: String, symbol: String, decimals: u8, supply: U256) -> Self { + CoinInfo { + name, + symbol, + decimals, + supply, + } } } -impl<'a> ModuleBinding<'a> for CoinModule<'a> { - const MODULE_NAME: &'static IdentStr = ident_str!("coin"); - const MODULE_ADDRESS: AccountAddress = ROOCH_FRAMEWORK_ADDRESS; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AnnotatedCoinInfo { + #[serde(rename = "type")] + pub type_: StructTag, + pub value: CoinInfo, +} - fn new(caller: &'a impl MoveFunctionCaller) -> Self - where - Self: Sized, - { - Self { caller } +impl AnnotatedCoinInfo { + pub fn new(type_: StructTag, value: CoinInfo) -> Self { + AnnotatedCoinInfo { type_, value } + } + + /// Create a new AnnotatedCoinInfo from a AnnotatedMoveValue + pub fn new_from_annotated_move_value(annotated_move_value: AnnotatedMoveValue) -> Result { + match annotated_move_value { + AnnotatedMoveValue::Struct(annotated_struct) => { + let type_ = annotated_struct.type_; + let mut fields = annotated_struct.value.into_iter(); + + let name = match fields.next().expect("CoinInfo should have name field") { + (_field_name, AnnotatedMoveValue::Struct(field_value)) => { + let mut inner_fields = field_value.value.into_iter(); + match inner_fields + .next() + .expect("CoinInfo name struct should have field") + { + (field_name, AnnotatedMoveValue::Bytes(field_value)) => { + debug_assert!( + field_name.as_str() == "bytes", + "CoinInfo inner field name should be bytes" + ); + String::from_utf8(field_value)? + } + _ => bail!("CoinInfo name field should be Bytes"), + } + } + _ => bail!("CoinInfo name field should be String"), + }; + let symbol = match fields.next().expect("CoinInfo should have symbol field") { + (_field_name, AnnotatedMoveValue::Struct(field_value)) => { + let mut inner_fields = field_value.value.into_iter(); + match inner_fields + .next() + .expect("CoinInfo symbol struct should have field") + { + (field_name, AnnotatedMoveValue::Bytes(field_value)) => { + debug_assert!( + field_name.as_str() == "bytes", + "CoinInfo field symbol should be symbol" + ); + String::from_utf8(field_value)? + } + _ => bail!("CoinInfo symbol field should be Bytes"), + } + } + _ => bail!("CoinInfo symbol field should be String"), + }; + let decimals = match fields.next().expect("CoinInfo should have decimals field") { + (field_name, AnnotatedMoveValue::U8(field_value)) => { + debug_assert!( + field_name.as_str() == "decimals", + "CoinInfo field decimals should be decimals" + ); + field_value + } + _ => bail!("CoinInfo decimals field should be u8"), + }; + let supply = match fields.next().expect("CoinInfo should have supply field") { + (field_name, AnnotatedMoveValue::U256(field_value)) => { + debug_assert!( + field_name.as_str() == "supply", + "CoinInfo field supply should be supply" + ); + field_value + } + _ => bail!("CoinInfo supply field should be U256"), + }; + + let coin_info = CoinInfo { + name, + symbol, + decimals, + supply, + }; + + let annotated_coin_info = AnnotatedCoinInfo { + type_, + value: coin_info, + }; + + Ok(annotated_coin_info) + } + _ => bail!("CoinInfo value field should be struct"), + } + } + + pub fn get_type(&self) -> StructTag { + self.type_.clone() + } + + pub fn get_decimals(&self) -> u8 { + self.value.decimals } } diff --git a/crates/rooch/src/commands/account/commands/balance.rs b/crates/rooch/src/commands/account/commands/balance.rs index 82ac99784f..fb0b746591 100644 --- a/crates/rooch/src/commands/account/commands/balance.rs +++ b/crates/rooch/src/commands/account/commands/balance.rs @@ -4,26 +4,11 @@ use crate::cli_types::{CommandAction, WalletContextOptions}; use clap::Parser; use move_core_types::account_address::AccountAddress; -use move_core_types::language_storage::StructTag; -use moveos_types::access_path::AccessPath; -use moveos_types::module_binding::{ModuleBinding, MoveFunctionCaller}; -use moveos_types::move_option::MoveOption; -use moveos_types::move_types::get_first_ty_as_struct_tag; -use moveos_types::object::ObjectID; -use moveos_types::state::MoveStructState; -use moveos_types::state_resolver::resource_tag_to_key; -use moveos_types::transaction::FunctionCall; -use moveos_types::tx_context::TxContext; use rooch_rpc_api::api::MAX_RESULT_LIMIT_USIZE; -use rooch_rpc_api::jsonrpc_types::account_view::AccountInfoView; -use rooch_rpc_api::jsonrpc_types::{AccountAddressView, AnnotatedCoinStoreView, StructTagView}; -use rooch_types::account::BalanceInfo; -use rooch_types::addresses::ROOCH_FRAMEWORK_ADDRESS_LITERAL; -use rooch_types::error::{RoochError, RoochResult}; -use rooch_types::framework::coin::CoinModule; -use std::str::FromStr; +use rooch_rpc_api::jsonrpc_types::{AccountAddressView, StructTagView}; +use rooch_types::error::RoochResult; -/// Show a account info, only the accounts managed by the current node are supported +/// Show account balance, only the accounts managed by the current node are supported #[derive(Debug, Parser)] pub struct BalanceCommand { #[clap(short = 'a', long = "address")] @@ -43,105 +28,41 @@ pub struct BalanceCommand { impl CommandAction<()> for BalanceCommand { async fn execute(self) -> RoochResult<()> { let context = self.context_options.build().await?; - - let addr_view: Option = if let Some(address) = self.address { - Some(address.into()) - } else { - context.config.active_address.map(|address| address.into()) - }; - - // Obtain account address - let addr = addr_view.expect("Account not found error"); + let address_addr = self + .address + .map_or( + context + .config + .active_address + .map(|active_address| AccountAddress::from(active_address).into()), + Some, + ) + .expect("Account not found error"); let client = context.get_client().await?; - let ctx = TxContext::new_readonly_ctx(addr); - let call = FunctionCall::new( - CoinModule::function_id(CoinModule::COIN_STORE_HANDLE_FUNCTION_NAME), - vec![], - vec![addr.to_vec()], + let data = client + .get_balances( + address_addr, + self.coin_type, + None, + Some(MAX_RESULT_LIMIT_USIZE), + ) + .await?; + + println!( + "{0: ^102} | {1: ^16} | {2: ^32} | {3: ^6}", + "Coin Type", "Symbol", "Balance", "Decimals" ); - let coin_store_handle_opt: Option = client - .call_function(&ctx, call)? - .into_result() - .map(|values| { - let value = values.get(0).expect("Expected return value"); - let result = MoveOption::::from_bytes(&value.value) - .expect("Expected Option"); - result.into() - })?; - // let coin_module = client.as_module_binding::(); - // let coin_store_handle_opt = coin_module.coin_store_handle(addr)?; - let coin_store_handle = coin_store_handle_opt - .unwrap_or_else(|| panic!("Failed to get coin store handle via {}", addr)); - - let mut result = AccountInfoView::new(0u64, vec![]); - if let Some(coin_type_opt) = self.coin_type { - let coin_store_type = format!( - "{}::coin::CoinStore<0x{}>", - ROOCH_FRAMEWORK_ADDRESS_LITERAL, - coin_type_opt.0.to_canonical_string() - ); - let key = resource_tag_to_key(&StructTag::from_str(coin_store_type.as_str())?); - let _hex_key = hex::encode(key.clone()); - let keys = vec![key]; - let mut states = client - .get_annotated_states(AccessPath::table(coin_store_handle, keys)) - .await - .map_err(|e| RoochError::AccountBalanceError(e.to_string()))?; - - let state = states - .pop() - .expect("States expected return value") - .expect("State expected return value"); - - let annotated_coin_store_view = - AnnotatedCoinStoreView::new_from_annotated_move_value_view(state.move_value)?; - - let coin_type = - get_first_ty_as_struct_tag(annotated_coin_store_view.get_coin_type_().into()) - .expect("Coin type expected get_first_ty_as_struct_tag succ"); - let balance_info = - BalanceInfo::new(coin_type, annotated_coin_store_view.get_coin_value().into()); - result.balances.push(Some(balance_info.into())) - } else { - let states = client - .list_annotated_states( - AccessPath::table_without_keys(coin_store_handle).into(), - None, - Some(MAX_RESULT_LIMIT_USIZE), - ) - .await - .map_err(|e| RoochError::AccountBalanceError(e.to_string()))?; - - let mut annotated_coin_store_views = states - .data - .into_iter() - .map(|state| { - let coin_store_view = - AnnotatedCoinStoreView::new_from_annotated_move_value_view( - state.expect("State expected return value").move_value, - ) - .expect("AnnotatedCoinStoreView expected return value"); - - let coin_type = - get_first_ty_as_struct_tag(coin_store_view.get_coin_type_().into()) - .expect("Coin type expected get_first_ty_as_struct_tag succ"); - let balance_info = - BalanceInfo::new(coin_type, coin_store_view.get_coin_value().into()); - Some(balance_info.into()) - }) - .collect(); - - result.balances.append(&mut annotated_coin_store_views); - }; + println!("{}", ["-"; 68].join("")); - println!("{0: ^102} | {1: ^32}", "Coin Type", "Balance"); - println!("{}", ["-"; 48].join("")); - for balance_info in result.balances.into_iter().flatten() { + for balance_info in data.data.into_iter().flatten() { println!( - "{0: ^102} | {1: ^32}", - balance_info.coin_type, balance_info.balance, + "{0: ^102} | {1: ^16} | {2: ^32} | {3: ^6}", + balance_info.coin_type, + balance_info.symbol, + balance_info.balance, + balance_info.decimals ); } diff --git a/crates/testsuite/features/cmd.feature b/crates/testsuite/features/cmd.feature index 83c3d1ab0c..96db8df946 100644 --- a/crates/testsuite/features/cmd.feature +++ b/crates/testsuite/features/cmd.feature @@ -33,6 +33,12 @@ Feature: Rooch CLI integration tests Then cmd: "move run --function {default}::event_test::emit_event --sender-account {default} --args 10u64" Then cmd: "event get-events-by-event-handle --event_handle_type {default}::event_test::WithdrawEvent --cursor 0 --limit 1" + # account balance + Then cmd: "move publish -p ../../examples/coins --sender-account {default} --named-addresses coins={default}" + Then cmd: "move run --function {default}::fixed_supply_coin::faucet --sender-account {default}" + Then cmd: "account balance" + Then cmd: "account balance --coin-type {default}::fixed_supply_coin::FSC" + Then stop the server @serial diff --git a/moveos/moveos-types/src/function_return_value.rs b/moveos/moveos-types/src/function_return_value.rs index 41a7c6fa54..f676011fd1 100644 --- a/moveos/moveos-types/src/function_return_value.rs +++ b/moveos/moveos-types/src/function_return_value.rs @@ -61,6 +61,21 @@ impl From>> for FunctionResult { } } +impl TryFrom for FunctionResult { + type Error = anyhow::Error; + + fn try_from(value: AnnotatedFunctionResult) -> Result { + Ok(Self { + vm_status: value.vm_status, + return_values: value.return_values.map(|v| { + v.into_iter() + .map(|v| v.value) + .collect::>() + }), + }) + } +} + #[derive(Debug, Clone)] pub struct DecodedFunctionResult { pub vm_status: VMStatus, diff --git a/moveos/moveos-types/src/module_binding.rs b/moveos/moveos-types/src/module_binding.rs index cbdb7892ea..bd4d877328 100644 --- a/moveos/moveos-types/src/module_binding.rs +++ b/moveos/moveos-types/src/module_binding.rs @@ -15,7 +15,7 @@ use move_core_types::{ value::MoveValue, }; -pub trait MoveFunctionCaller { +pub trait MoveFunctionCaller: Send + Sync { fn call_function(&self, ctx: &TxContext, call: FunctionCall) -> Result; fn as_module_binding<'a, M: ModuleBinding<'a>>(&'a self) -> M diff --git a/moveos/moveos-types/src/object.rs b/moveos/moveos-types/src/object.rs index e3d04f648d..7ebb631b7f 100644 --- a/moveos/moveos-types/src/object.rs +++ b/moveos/moveos-types/src/object.rs @@ -432,22 +432,22 @@ impl AnnotatedObject { let mut fields = object_struct.value.into_iter(); let object_id = ObjectID::try_from(fields.next().expect("Object should have id").1)?; let owner = match fields.next().expect("Object should have owner") { - (field_name, AnnotatedMoveValue::Address(filed_value)) => { + (field_name, AnnotatedMoveValue::Address(field_value)) => { debug_assert!( field_name.as_str() == "owner", "Object owner field name should be owner" ); - filed_value + field_value } _ => bail!("Object owner field should be address"), }; let value = match fields.next().expect("Object should have value") { - (field_name, AnnotatedMoveValue::Struct(filed_value)) => { + (field_name, AnnotatedMoveValue::Struct(field_value)) => { debug_assert!( field_name.as_str() == "value", "Object value field name should be value" ); - filed_value + field_value } _ => bail!("Object value field should be struct"), };