From f133bea5f0eccab3821bc73a34f5ce578656a64b Mon Sep 17 00:00:00 2001 From: baichuan3 Date: Fri, 8 Sep 2023 18:41:46 +0800 Subject: [PATCH 1/5] refactor account balance for support balance API --- crates/rooch-framework/doc/coin.md | 32 ++ crates/rooch-framework/sources/coin.move | 11 + .../rooch-open-rpc-spec/schemas/openrpc.json | 101 +++++ 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 | 19 +- .../src/jsonrpc_types/rooch_types.rs | 381 +++++++++++----- crates/rooch-rpc-client/src/lib.rs | 19 +- 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 | 155 +++++++ crates/rooch-rpc-server/src/service/mod.rs | 184 +------- .../src/service/rpc_service.rs | 199 +++++++++ crates/rooch-types/src/account.rs | 33 +- crates/rooch-types/src/framework/coin.rs | 414 ++++++++++++++---- .../src/commands/account/commands/balance.rs | 226 +++++----- crates/testsuite/features/cmd.feature | 6 + .../moveos-types/src/function_return_value.rs | 15 + moveos/moveos-types/src/module_binding.rs | 2 +- 21 files changed, 1387 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/crates/rooch-framework/doc/coin.md b/crates/rooch-framework/doc/coin.md index 2a1aeb8caf..b2d81bcddf 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,37 @@ Return coin store handle for addr + + + + +## Function `coin_info_handle` + +Return coin info handle + + +
public fun coin_info_handle(ctx: &storage_context::StorageContext): option::Option<object_id::ObjectID>
+
+ + + +
+Implementation + + +
public fun coin_info_handle(ctx: &StorageContext): Option<ObjectID> {
+    if (account_storage::global_exists<CoinInfos>(ctx, @rooch_framework))
+        {
+            let coin_infos = account_storage::global_borrow<CoinInfos>(ctx, @rooch_framework);
+            option::some(*type_table::handle(&coin_infos.coin_infos))
+        } else {
+        option::none<ObjectID>()
+    }
+}
+
+ + +
diff --git a/crates/rooch-framework/sources/coin.move b/crates/rooch-framework/sources/coin.move index ce22c3b816..910269497e 100644 --- a/crates/rooch-framework/sources/coin.move +++ b/crates/rooch-framework/sources/coin.move @@ -239,6 +239,17 @@ module rooch_framework::coin { } } + /// Return coin info handle + public fun coin_info_handle(ctx: &StorageContext): Option { + if (account_storage::global_exists(ctx, @rooch_framework)) + { + let coin_infos = account_storage::global_borrow(ctx, @rooch_framework); + option::some(*type_table::handle(&coin_infos.coin_infos)) + } else { + option::none() + } + } + // // 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/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..0f2058ecd4 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,13 @@ // 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}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AccountInfoView { pub sequence_number: u64, pub balances: Vec>, @@ -50,17 +51,21 @@ 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 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 +74,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..755ecaf37d 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,19 +135,19 @@ impl CoinView { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AnnotatedCoinView { - type_: StructTagView, + struct_type: StructTagView, value: CoinView, } impl AnnotatedCoinView { - pub fn new(type_: StructTagView, value: CoinView) -> Self { - AnnotatedCoinView { type_, value } + pub fn new(struct_type: StructTagView, value: CoinView) -> Self { + AnnotatedCoinView { struct_type, value } } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct CompoundCoinStoreView { coin: AnnotatedCoinView, frozen: bool, @@ -156,111 +159,283 @@ impl CompoundCoinStoreView { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AnnotatedCoinStoreView { - type_: StructTagView, + struct_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 { + struct_type: coin_store.value.coin.struct_type.into(), + value: coin, + }; + let compose_coin_store = CompoundCoinStoreView { + coin: annotated_coin, + frozen: coin_store.value.frozen, + }; + AnnotatedCoinStoreView { + struct_type: coin_store.struct_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.struct_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.struct_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, - }; +// impl AnnotatedCoinStoreView { +// pub fn new(struct_type: StructTagView, value: CompoundCoinStoreView) -> Self { +// AnnotatedCoinStoreView { struct_type, value } +// } +// +// /// 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" +// ); +// +// 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") +// { +// (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, +// }; +// +// let annotated_coin_store_view = AnnotatedCoinStoreView { +// type_: annotated_coin_store_type, +// value: compose_coin_store, +// }; +// +// Ok(annotated_coin_store_view) +// } +// _ => bail!("CoinValue value field should be value"), +// } +// } +// +// pub fn get_coin_type_(&self) -> StructTagView { +// self.value.coin.type_.clone() +// } +// +// pub fn get_coin_value(&self) -> StrView { +// self.value.coin.value.value +// } +// } - let annotated_coin_store_view = AnnotatedCoinStoreView { - type_: annotated_coin_store_type, - value: compose_coin_store, - }; +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct CoinInfoView { + name: String, + symbol: String, + decimals: u8, + supply: StrView, +} - Ok(annotated_coin_store_view) - } - _ => bail!("CoinValue value field should be value"), +// impl CoinInfoView { +// pub fn new(name: String, symbol: String, decimals: u8, supply: StrView) -> Self { +// CoinInfoView { +// name, +// symbol, +// decimals, +// supply, +// } +// } +// } + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct AnnotatedCoinInfoView { + struct_type: StructTagView, + value: CoinInfoView, +} + +// pub struct CoinInfoView { +// name: String, +// symbol: String, +// decimals: u8, +// supply: StrView, +// } + +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 { + struct_type: annotated_coin_info.struct_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.struct_type.into(), coin_info) } } + +// impl AnnotatedCoinInfoView { +// pub fn new(struct_type: StructTagView, value: CoinInfoView) -> Self { +// AnnotatedCoinInfoView { struct_type, value } +// } +// +// /// Create a new AnnotatedCoinInfoView 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 struct_type = annotated_struct_view.type_; +// let mut fields = annotated_struct_view.value.into_iter(); +// +// let name = match fields.next().expect("CoinInfo should have name field") { +// (field_name, AnnotatedMoveValueView::Bytes(filed_value)) => { +// debug_assert!( +// field_name.as_str() == "name", +// "CoinInfo field name should be name" +// ); +// String::from_utf8(filed_value.0)? +// } +// _ => bail!("CoinInfo name field should be String"), +// }; +// let symbol = match fields.next().expect("CoinInfo should have symbol field") { +// (field_name, AnnotatedMoveValueView::Bytes(filed_value)) => { +// debug_assert!( +// field_name.as_str() == "symbol", +// "CoinInfo field symbol should be symbol" +// ); +// String::from_utf8(filed_value.0)? +// } +// _ => bail!("CoinInfo symbol field should be String"), +// }; +// let decimals = match fields.next().expect("CoinInfo should have decimals field") { +// (field_name, AnnotatedMoveValueView::U8(filed_value)) => { +// debug_assert!( +// field_name.as_str() == "decimals", +// "CoinInfo field decimals should be decimals" +// ); +// filed_value +// } +// _ => bail!("CoinInfo decimals field should be u8"), +// }; +// let supply = match fields.next().expect("CoinInfo should have supply field") { +// (field_name, AnnotatedMoveValueView::U256(filed_value)) => { +// debug_assert!( +// field_name.as_str() == "supply", +// "CoinInfo field supply should be supply" +// ); +// filed_value +// } +// _ => bail!("CoinInfo supply field should be U256"), +// }; +// +// let coin_info_view = CoinInfoView { +// name, +// symbol, +// decimals, +// supply, +// }; +// +// let annotated_coin_info_view = AnnotatedCoinInfoView { +// type_, +// value: coin_info_view, +// }; +// +// Ok(annotated_coin_info_view) +// } +// _ => bail!("CoinInfo value field should be struct"), +// } +// } +// +// pub fn get_struct_type(&self) -> StructTagView { +// self.struct_type.clone() +// } +// +// pub fn get_decimals(&self) -> u8 { +// self.value.decimals +// } +// } 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/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..7d04a2eb16 --- /dev/null +++ b/crates/rooch-rpc-server/src/service/aggregate_service.rs @@ -0,0 +1,155 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use crate::service::rpc_service::RpcService; +use anyhow::Result; +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::MoveFunctionCaller; +use moveos_types::move_types::get_first_ty_as_struct_tag; +use moveos_types::state_resolver::resource_tag_to_key; +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; + +/// 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 call = FunctionCall::new( + // CoinModule::function_id(CoinModule::COIN_STORE_HANDLE_FUNCTION_NAME), + // vec![], + // vec![account_addr.to_vec()], + // ); + // 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 + // })?; + + let coin_module = self.rpc_service.as_module_binding::(); + + // contruct coin info map for handle decimals + let coin_info_handle = coin_module + .coin_info_handle()? + .expect("Get coin info handle should succ"); + + //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() { + coin_info_table.insert(coin_info.struct_type, coin_info.value); + } + + let coin_store_handle = coin_module + .coin_store_handle(account_addr)? + .expect("Get coin store handle should succ"); + + // let mut account_info = AccountInfo::new(0u64, vec![]); + 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_struct_type()) + .expect("Coin type expected get_first_ty_as_struct_tag succ"); + // let inner_coin_info = coin_info_table + // .get(&coin_type) + // .expect("Get coin info by coin type expected return value"); + 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, ref mut balance_info) in &mut result { + // 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) + } +} 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..56f76e2f49 --- /dev/null +++ b/crates/rooch-rpc-server/src/service/rpc_service.rs @@ -0,0 +1,199 @@ +// 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, FunctionResult}; +use moveos_types::module_binding::MoveFunctionCaller; +use moveos_types::state::{AnnotatedState, State}; +use moveos_types::transaction::{FunctionCall, TransactionExecutionInfo}; +use moveos_types::tx_context::TxContext; +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) + } +} + +impl MoveFunctionCaller for RpcService { + fn call_function( + &self, + _ctx: &TxContext, + function_call: FunctionCall, + ) -> Result { + let function_result = + futures::executor::block_on(self.execute_view_function(function_call))?; + function_result.try_into() + } +} 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..74c3586404 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,78 @@ 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_STORE_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"); + let result = MoveOption::::from_bytes(&value.value) + .expect("Coin info handle expected Option"); + result.into() + })?; + 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,20 +100,20 @@ impl Coin { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AnnotatedCoin { - type_: StructTag, - value: Coin, + pub struct_type: StructTag, + pub value: Coin, } impl AnnotatedCoin { - pub fn new(type_: StructTag, value: Coin) -> Self { - AnnotatedCoin { type_, value } + pub fn new(struct_type: StructTag, value: Coin) -> Self { + AnnotatedCoin { struct_type, value } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CompoundCoinStore { - coin: AnnotatedCoin, - frozen: bool, + pub coin: AnnotatedCoin, + pub frozen: bool, } impl CompoundCoinStore { @@ -56,75 +124,163 @@ impl CompoundCoinStore { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AnnotatedCoinStore { - type_: StructTag, - value: CompoundCoinStore, + pub struct_type: StructTag, + pub value: CompoundCoinStore, } impl AnnotatedCoinStore { - pub fn new(type_: StructTag, value: CompoundCoinStore) -> Self { - AnnotatedCoinStore { type_, value } + pub fn new(struct_type: StructTag, value: CompoundCoinStore) -> Self { + AnnotatedCoinStore { struct_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 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_struct_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") + // { + // (field_name, AnnotatedMoveValue::Bytes(inner_filed_value)) => { + // debug_assert!( + // field_name.as_str() == "value", + // "CoinValue value field name should be value" + // ); + // U256::from_bytes(inner_filed_value.as_slice()) + // } + // _ => bail!("CoinValue value field should be value"), + // }?; + // + // let coin = Coin { value: coin_value }; + // AnnotatedCoin { + // struct_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 frozen field should be bool"), + // }; + // let compose_coin_store = CompoundCoinStore { + // coin: annotated_coin, + // frozen, + // }; + // + // let annotated_coin_store = AnnotatedCoinStore { + // struct_type: annotated_coin_store_type, + // value: compose_coin_store, + // }; + // + // Ok(annotated_coin_store) + // } + + /// Create a new AnnotatedCoinStore from a AnnotatedMoveValue + pub fn new_from_annotated_move_value(annotated_move_value: AnnotatedMoveValue) -> Result { + // pub fn new_from_annotated_move_value( + // annotated_move_value: AnnotatedMoveValueView, + // ) -> 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(filed_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_struct_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") + { + (field_name, AnnotatedMoveValue::Bytes(inner_filed_value)) => { + debug_assert!( + field_name.as_str() == "value", + "CoinValue value field name should be value" + ); + U256::from_bytes(inner_filed_value.as_slice())? + } + (field_name, AnnotatedMoveValue::U64(inner_filed_value)) => { + debug_assert!( + field_name.as_str() == "value", + "CoinValue value field name should be value" + ); + U256::from(inner_filed_value) + } + (field_name, AnnotatedMoveValue::U128(inner_filed_value)) => { + debug_assert!( + field_name.as_str() == "value", + "CoinValue value field name should be value" + ); + U256::from(inner_filed_value) + } + (field_name, AnnotatedMoveValue::U256(inner_filed_value)) => { + debug_assert!( + field_name.as_str() == "value", + "CoinValue value field name should be value" + ); + inner_filed_value + } + _ => bail!("CoinValue value field should be value"), + }; + let coin = Coin { value: coin_value }; + AnnotatedCoin { + struct_type: coin_struct_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(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 = CompoundCoinStore { + coin: annotated_coin, + frozen, + }; + + let annotated_coin_store = AnnotatedCoinStore { + struct_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 { - self.value.coin.type_.clone() + pub fn get_coin_struct_type(&self) -> StructTag { + self.value.coin.struct_type.clone() } pub fn get_coin_value(&self) -> U256 { @@ -132,43 +288,107 @@ 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, } -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 { + pub struct_type: StructTag, + pub value: CoinInfo, +} - fn new(caller: &'a impl MoveFunctionCaller) -> Self - where - Self: Sized, - { - Self { caller } +impl AnnotatedCoinInfo { + pub fn new(struct_type: StructTag, value: CoinInfo) -> Self { + AnnotatedCoinInfo { struct_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 struct_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::Bytes(filed_value)) => { + debug_assert!( + field_name.as_str() == "name", + "CoinInfo field name should be name" + ); + String::from_utf8(filed_value)? + } + _ => bail!("CoinInfo name field should be String"), + }; + let symbol = match fields.next().expect("CoinInfo should have symbol field") { + (field_name, AnnotatedMoveValue::Bytes(filed_value)) => { + debug_assert!( + field_name.as_str() == "symbol", + "CoinInfo field symbol should be symbol" + ); + String::from_utf8(filed_value)? + } + _ => bail!("CoinInfo symbol field should be String"), + }; + let decimals = match fields.next().expect("CoinInfo should have decimals field") { + (field_name, AnnotatedMoveValue::U8(filed_value)) => { + debug_assert!( + field_name.as_str() == "decimals", + "CoinInfo field decimals should be decimals" + ); + filed_value + } + _ => bail!("CoinInfo decimals field should be u8"), + }; + let supply = match fields.next().expect("CoinInfo should have supply field") { + (field_name, AnnotatedMoveValue::U256(filed_value)) => { + debug_assert!( + field_name.as_str() == "supply", + "CoinInfo field supply should be supply" + ); + filed_value + } + _ => bail!("CoinInfo supply field should be U256"), + }; + + let coin_info = CoinInfo { + name, + symbol, + decimals, + supply, + }; + + let annotated_coin_info = AnnotatedCoinInfo { + struct_type, + value: coin_info, + }; + + Ok(annotated_coin_info) + } + _ => bail!("CoinInfo value field should be struct"), + } + } + + pub fn get_struct_type(&self) -> StructTag { + self.struct_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..d3363b70b5 100644 --- a/crates/rooch/src/commands/account/commands/balance.rs +++ b/crates/rooch/src/commands/account/commands/balance.rs @@ -4,24 +4,9 @@ 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 #[derive(Debug, Parser)] @@ -44,104 +29,129 @@ 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()) - }; - + // let address_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?; + + // 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 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() + // .flatten() + // .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 { + // //TODO If the coin store list exceeds MAX_RESULT_LIMIT_USIZE, consider supporting traverse or pagination + // 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!( + "{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 From e18622e6e0778893df6b712cd089ffae4fc27ecc Mon Sep 17 00:00:00 2001 From: baichuan3 Date: Sun, 10 Sep 2023 12:02:59 +0800 Subject: [PATCH 2/5] try to solve rpc server hang cause by futures::executor::block_on when impl MoveFunctionCall --- Cargo.toml | 2 +- .../src/service/aggregate_service.rs | 179 ++++++++++++++++-- .../src/service/rpc_service.rs | 26 ++- 3 files changed, 176 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 67022cc8bc..553f192432 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/rooch-rpc-server/src/service/aggregate_service.rs b/crates/rooch-rpc-server/src/service/aggregate_service.rs index 7d04a2eb16..87c74a9cd3 100644 --- a/crates/rooch-rpc-server/src/service/aggregate_service.rs +++ b/crates/rooch-rpc-server/src/service/aggregate_service.rs @@ -6,9 +6,15 @@ use anyhow::Result; 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::MoveFunctionCaller; +use moveos_types::function_return_value::FunctionResult; +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::MoveState; 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; @@ -50,13 +56,42 @@ impl AggregateService { // .expect("Expected Option"); // result // })?; - - let coin_module = self.rpc_service.as_module_binding::(); + // let coin_module = self.as_module_binding::(); // contruct coin info map for handle decimals - let coin_info_handle = coin_module - .coin_info_handle()? - .expect("Get coin info handle should succ"); + // let coin_info_handle = + // tokio::task::spawn_blocking(move || coin_module.coin_info_handle().ok()?) + // .await + // .map_err(|e| RoochError::AccountBalanceError(e.to_string()))?; + // let self_service = coin_module.coin_info_handle().clone(); + // let coin_info_handle = tokio::task::spawn_blocking( + // coin_module.coin_info_handle()? + // ); + // let coin_info_handle = coin_info_handle.expect("Get coin info handle should succ"); + + // // contruct coin info map for handle decimals + // let coin_info_handle = coin_module + // .coin_info_handle()? + // .expect("Get coin info handle should succ"); + + let coin_info_handle_call = FunctionCall::new( + CoinModule::function_id(CoinModule::COIN_INFO_HANDLE_FUNCTION_NAME), + vec![], + vec![], + ); + let ctx = TxContext::new_readonly_ctx(account_addr); + let coin_info_handle: Option = self + .call_function(&ctx, coin_info_handle_call)? + .into_result() + .map(|values| { + let value = values + .get(0) + .expect("Coin info handle expected return value"); + let result = MoveOption::::from_bytes(&value.value) + .expect("Coin info handle Expected Option"); + result.into() + })?; + let coin_info_handle = coin_info_handle.expect("Get coin info handle should succ"); //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(); @@ -81,9 +116,27 @@ impl AggregateService { coin_info_table.insert(coin_info.struct_type, coin_info.value); } - let coin_store_handle = coin_module - .coin_store_handle(account_addr)? - .expect("Get coin store handle should succ"); + let coin_store_handle_call = FunctionCall::new( + CoinModule::function_id(CoinModule::COIN_STORE_HANDLE_FUNCTION_NAME), + vec![], + vec![account_addr.to_vec()], + ); + // let ctx = TxContext::new_readonly_ctx(account_addr); + let coin_store_handle: Option = self + .call_function(&ctx, coin_store_handle_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() + })?; + let coin_store_handle = coin_store_handle.expect("Get coin store handle should succ"); + // let coin_store_handle = coin_module + // .coin_store_handle(account_addr)? + // .expect("Get coin store handle should succ"); // let mut account_info = AccountInfo::new(0u64, vec![]); let mut result = vec![]; @@ -142,14 +195,108 @@ impl AggregateService { result.append(&mut data); }; - // for (_key, ref mut balance_info) in &mut result { - // 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; - // } + 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) } } + +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 + fn call_function( + &self, + _ctx: &TxContext, + function_call: FunctionCall, + ) -> Result { + // let function_result = futures::executor::block_on(async move { + // let function_result = futures::executor::block_on(async move { + // self.rpc_service.execute_view_function(function_call).await + // })?; + + let function_result = + futures::executor::block_on(self.rpc_service.execute_view_function(function_call))?; + + // let rt = tokio::runtime::Builder::new_current_thread() + // .enable_all() + // .build()?; + // let function_result = rt.block_on(self.rpc_service.execute_view_function(function_call))?; + + // let function_result = tokio::task::spawn_blocking(move || self.rpc_service.execute_view_function(function_call)); + + // let handle = Handle::current(); + + // let function_result = handle.block_on(self.rpc_service.execute_view_function(function_call))?; + // let handle = tokio::runtime::Handle::current(); + // let function_result = std::thread::scope(|s| { + // s.spawn(|| { + // handle.block_on(self.rpc_service.execute_view_function(function_call)) + // }) + // .join() + // })?.unwrap(); + // .unwrap(); + + // let function_result = Handle::block_on ( async move { + // self.rpc_service.execute_view_function(function_call).await + // }); + // let function_result = block_in_place ( async move { + // self.rpc_service.execute_view_function(function_call).await + // })?; + + // let handle = tokio::runtime::Handle::current(); + // let rpc_service = self.rpc_service.clone().; + // let function_result = futures::executor::block_on(async { + // handle + // .spawn(async { rpc_service.execute_view_function(function_call).await }) + // .await + // .expect("Task spawned in Tokio executor panicked") + // })?; + + // Warning! Possible deadlocks can occur if we try to wait for a future + // let rpc_service = self.rpc_service.clone(); + // let function_result = futures::executor::block_on( + // tokio::task::spawn( + // rpc_service.execute_view_function(function_call) + // ) + // // .unwrap() + // )??; + + // // Warning! Possible deadlocks can occur if we try to wait for a future + // let function_result = futures::executor::block_on(async move { + // // tokio::task::spawn( + // RUNTIME.spawn( + // async move { rpc_service.execute_view_function(function_call).await }, + // ) + // .await? + // })?; + + // let task = tokio::task::spawn(self.rpc_service.execute_view_function(function_call)); + // let function_result = futures::executor::block_on(tokio::task::spawn( + // self.rpc_service.execute_view_function(function_call) + // ))?; + + // let function_result = + // futures::executor::block_on( self.rpc_service.execute_view_function(function_call))?; + + // let function_result = + // tokio::task::block_in_place(|| { + // self.rpc_service.execute_view_function(function_call) + // }).; + + // let result = tokio::task::spawn_blocking(invoke_juniper).await.map_err(|e| e.to_string())?; + // println!("result={:?}", result); + // Ok(json!({})) + + // let result = tokio::task::spawn_blocking(async || { + // self.rpc_service.execute_view_function(function_call).await + // }).unwrap()?; + + function_result.try_into() + } +} diff --git a/crates/rooch-rpc-server/src/service/rpc_service.rs b/crates/rooch-rpc-server/src/service/rpc_service.rs index 56f76e2f49..e0245cf7ca 100644 --- a/crates/rooch-rpc-server/src/service/rpc_service.rs +++ b/crates/rooch-rpc-server/src/service/rpc_service.rs @@ -7,11 +7,9 @@ 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, FunctionResult}; -use moveos_types::module_binding::MoveFunctionCaller; +use moveos_types::function_return_value::AnnotatedFunctionResult; use moveos_types::state::{AnnotatedState, State}; use moveos_types::transaction::{FunctionCall, TransactionExecutionInfo}; -use moveos_types::tx_context::TxContext; use rooch_executor::proxy::ExecutorProxy; use rooch_proposer::proxy::ProposerProxy; use rooch_rpc_api::jsonrpc_types::ExecuteTransactionResponse; @@ -186,14 +184,14 @@ impl RpcService { } } -impl MoveFunctionCaller for RpcService { - fn call_function( - &self, - _ctx: &TxContext, - function_call: FunctionCall, - ) -> Result { - let function_result = - futures::executor::block_on(self.execute_view_function(function_call))?; - function_result.try_into() - } -} +// impl MoveFunctionCaller for RpcService { +// fn call_function( +// &self, +// _ctx: &TxContext, +// function_call: FunctionCall, +// ) -> Result { +// let function_result = +// futures::executor::block_on(self.execute_view_function(function_call))?; +// function_result.try_into() +// } +// } From 11a2c88d97e23d65d2d6c898d46cdbc529b4e24f Mon Sep 17 00:00:00 2001 From: baichuan3 Date: Mon, 11 Sep 2023 22:25:32 +0800 Subject: [PATCH 3/5] fixed async call_function hung causeby futures::executor::block_on --- Cargo.lock | 14 ++ Cargo.toml | 3 +- crates/rooch-rpc-api/Cargo.toml | 1 + .../src/jsonrpc_types/account_view.rs | 9 + .../src/jsonrpc_types/rooch_types.rs | 203 ------------------ crates/rooch-rpc-server/Cargo.toml | 2 + .../src/service/aggregate_service.rs | 188 +++------------- .../src/service/rpc_service.rs | 12 -- crates/rooch-types/src/framework/coin.rs | 189 +++++++--------- .../src/commands/account/commands/balance.rs | 91 +------- moveos/moveos-types/src/object.rs | 8 +- 11 files changed, 142 insertions(+), 578 deletions(-) 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 553f192432..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" @@ -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-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/jsonrpc_types/account_view.rs b/crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs index 0f2058ecd4..c26e7bc0f7 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs @@ -6,6 +6,7 @@ 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, JsonSchema)] pub struct AccountInfoView { @@ -59,6 +60,14 @@ pub struct BalanceInfoView { pub decimals: u8, } +impl BalanceInfoView { + //TODO implements big decimal calculation + pub fn get_balance_show(&self) -> String { + let balance = U256::div(self.balance.0, U256::from(10u32.pow(self.decimals as u32))); + format!("{:.}", balance.to_string()) + } +} + impl From for BalanceInfoView { fn from(balance_info: BalanceInfo) -> Self { BalanceInfoView { 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 755ecaf37d..68010907a1 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs @@ -195,109 +195,6 @@ impl From for AnnotatedCoinStore { } } -// impl AnnotatedCoinStoreView { -// pub fn new(struct_type: StructTagView, value: CompoundCoinStoreView) -> Self { -// AnnotatedCoinStoreView { struct_type, value } -// } -// -// /// 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" -// ); -// -// 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") -// { -// (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, -// }; -// -// let annotated_coin_store_view = AnnotatedCoinStoreView { -// type_: annotated_coin_store_type, -// value: compose_coin_store, -// }; -// -// Ok(annotated_coin_store_view) -// } -// _ => bail!("CoinValue value field should be value"), -// } -// } -// -// pub fn get_coin_type_(&self) -> StructTagView { -// self.value.coin.type_.clone() -// } -// -// pub fn get_coin_value(&self) -> StrView { -// self.value.coin.value.value -// } -// } - #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct CoinInfoView { name: String, @@ -306,30 +203,12 @@ pub struct CoinInfoView { supply: StrView, } -// impl CoinInfoView { -// pub fn new(name: String, symbol: String, decimals: u8, supply: StrView) -> Self { -// CoinInfoView { -// name, -// symbol, -// decimals, -// supply, -// } -// } -// } - #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AnnotatedCoinInfoView { struct_type: StructTagView, value: CoinInfoView, } -// pub struct CoinInfoView { -// name: String, -// symbol: String, -// decimals: u8, -// supply: StrView, -// } - impl From for AnnotatedCoinInfoView { fn from(annotated_coin_info: AnnotatedCoinInfo) -> Self { let coin_info = CoinInfoView { @@ -357,85 +236,3 @@ impl From for AnnotatedCoinInfo { AnnotatedCoinInfo::new(annotated_coin_info.struct_type.into(), coin_info) } } - -// impl AnnotatedCoinInfoView { -// pub fn new(struct_type: StructTagView, value: CoinInfoView) -> Self { -// AnnotatedCoinInfoView { struct_type, value } -// } -// -// /// Create a new AnnotatedCoinInfoView 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 struct_type = annotated_struct_view.type_; -// let mut fields = annotated_struct_view.value.into_iter(); -// -// let name = match fields.next().expect("CoinInfo should have name field") { -// (field_name, AnnotatedMoveValueView::Bytes(filed_value)) => { -// debug_assert!( -// field_name.as_str() == "name", -// "CoinInfo field name should be name" -// ); -// String::from_utf8(filed_value.0)? -// } -// _ => bail!("CoinInfo name field should be String"), -// }; -// let symbol = match fields.next().expect("CoinInfo should have symbol field") { -// (field_name, AnnotatedMoveValueView::Bytes(filed_value)) => { -// debug_assert!( -// field_name.as_str() == "symbol", -// "CoinInfo field symbol should be symbol" -// ); -// String::from_utf8(filed_value.0)? -// } -// _ => bail!("CoinInfo symbol field should be String"), -// }; -// let decimals = match fields.next().expect("CoinInfo should have decimals field") { -// (field_name, AnnotatedMoveValueView::U8(filed_value)) => { -// debug_assert!( -// field_name.as_str() == "decimals", -// "CoinInfo field decimals should be decimals" -// ); -// filed_value -// } -// _ => bail!("CoinInfo decimals field should be u8"), -// }; -// let supply = match fields.next().expect("CoinInfo should have supply field") { -// (field_name, AnnotatedMoveValueView::U256(filed_value)) => { -// debug_assert!( -// field_name.as_str() == "supply", -// "CoinInfo field supply should be supply" -// ); -// filed_value -// } -// _ => bail!("CoinInfo supply field should be U256"), -// }; -// -// let coin_info_view = CoinInfoView { -// name, -// symbol, -// decimals, -// supply, -// }; -// -// let annotated_coin_info_view = AnnotatedCoinInfoView { -// type_, -// value: coin_info_view, -// }; -// -// Ok(annotated_coin_info_view) -// } -// _ => bail!("CoinInfo value field should be struct"), -// } -// } -// -// pub fn get_struct_type(&self) -> StructTagView { -// self.struct_type.clone() -// } -// -// pub fn get_decimals(&self) -> u8 { -// self.value.decimals -// } -// } 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/service/aggregate_service.rs b/crates/rooch-rpc-server/src/service/aggregate_service.rs index 87c74a9cd3..f23be7f102 100644 --- a/crates/rooch-rpc-server/src/service/aggregate_service.rs +++ b/crates/rooch-rpc-server/src/service/aggregate_service.rs @@ -3,15 +3,13 @@ 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::{ModuleBinding, MoveFunctionCaller}; -use moveos_types::move_option::MoveOption; +use moveos_types::module_binding::MoveFunctionCaller; use moveos_types::move_types::get_first_ty_as_struct_tag; -use moveos_types::object::ObjectID; -use moveos_types::state::MoveState; use moveos_types::state_resolver::resource_tag_to_key; use moveos_types::transaction::FunctionCall; use moveos_types::tx_context::TxContext; @@ -21,6 +19,7 @@ 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)] @@ -42,57 +41,12 @@ impl AggregateService { cursor: Option>, limit: usize, ) -> Result>, BalanceInfo)>>> { - // let call = FunctionCall::new( - // CoinModule::function_id(CoinModule::COIN_STORE_HANDLE_FUNCTION_NAME), - // vec![], - // vec![account_addr.to_vec()], - // ); - // 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 - // })?; - // let coin_module = self.as_module_binding::(); + let coin_module = self.as_module_binding::(); + let coin_info_handle = coin_module + .coin_info_handle()? + .expect("Get coin info handle should succ"); // contruct coin info map for handle decimals - // let coin_info_handle = - // tokio::task::spawn_blocking(move || coin_module.coin_info_handle().ok()?) - // .await - // .map_err(|e| RoochError::AccountBalanceError(e.to_string()))?; - // let self_service = coin_module.coin_info_handle().clone(); - // let coin_info_handle = tokio::task::spawn_blocking( - // coin_module.coin_info_handle()? - // ); - // let coin_info_handle = coin_info_handle.expect("Get coin info handle should succ"); - - // // contruct coin info map for handle decimals - // let coin_info_handle = coin_module - // .coin_info_handle()? - // .expect("Get coin info handle should succ"); - - let coin_info_handle_call = FunctionCall::new( - CoinModule::function_id(CoinModule::COIN_INFO_HANDLE_FUNCTION_NAME), - vec![], - vec![], - ); - let ctx = TxContext::new_readonly_ctx(account_addr); - let coin_info_handle: Option = self - .call_function(&ctx, coin_info_handle_call)? - .into_result() - .map(|values| { - let value = values - .get(0) - .expect("Coin info handle expected return value"); - let result = MoveOption::::from_bytes(&value.value) - .expect("Coin info handle Expected Option"); - result.into() - })?; - let coin_info_handle = coin_info_handle.expect("Get coin info handle should succ"); - //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 @@ -113,32 +67,15 @@ impl AggregateService { }) .collect::>(); for coin_info in coin_info_data.into_iter().flatten() { - coin_info_table.insert(coin_info.struct_type, coin_info.value); + let coin_type = get_first_ty_as_struct_tag(coin_info.struct_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_call = FunctionCall::new( - CoinModule::function_id(CoinModule::COIN_STORE_HANDLE_FUNCTION_NAME), - vec![], - vec![account_addr.to_vec()], - ); - // let ctx = TxContext::new_readonly_ctx(account_addr); - let coin_store_handle: Option = self - .call_function(&ctx, coin_store_handle_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() - })?; - let coin_store_handle = coin_store_handle.expect("Get coin store handle should succ"); - // let coin_store_handle = coin_module - // .coin_store_handle(account_addr)? - // .expect("Get coin store handle should succ"); + let coin_store_handle = coin_module + .coin_store_handle(account_addr)? + .expect("Get coin store handle should succ"); - // let mut account_info = AccountInfo::new(0u64, vec![]); let mut result = vec![]; if let Some(coin_type) = coin_type { let coin_store_type = format!( @@ -182,9 +119,6 @@ impl AggregateService { let coin_type = get_first_ty_as_struct_tag(coin_store.get_coin_struct_type()) .expect("Coin type expected get_first_ty_as_struct_tag succ"); - // let inner_coin_info = coin_info_table - // .get(&coin_type) - // .expect("Get coin info by coin type expected return value"); let balance_info = BalanceInfo::new_with_default(coin_type, coin_store.get_coin_value()); (Some(key), balance_info) @@ -207,95 +141,27 @@ impl AggregateService { } } +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 + // 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 function_result = futures::executor::block_on(async move { - // let function_result = futures::executor::block_on(async move { - // self.rpc_service.execute_view_function(function_call).await - // })?; - - let function_result = - futures::executor::block_on(self.rpc_service.execute_view_function(function_call))?; - - // let rt = tokio::runtime::Builder::new_current_thread() - // .enable_all() - // .build()?; - // let function_result = rt.block_on(self.rpc_service.execute_view_function(function_call))?; - - // let function_result = tokio::task::spawn_blocking(move || self.rpc_service.execute_view_function(function_call)); - - // let handle = Handle::current(); - - // let function_result = handle.block_on(self.rpc_service.execute_view_function(function_call))?; - // let handle = tokio::runtime::Handle::current(); - // let function_result = std::thread::scope(|s| { - // s.spawn(|| { - // handle.block_on(self.rpc_service.execute_view_function(function_call)) - // }) - // .join() - // })?.unwrap(); - // .unwrap(); - - // let function_result = Handle::block_on ( async move { - // self.rpc_service.execute_view_function(function_call).await - // }); - // let function_result = block_in_place ( async move { - // self.rpc_service.execute_view_function(function_call).await - // })?; - - // let handle = tokio::runtime::Handle::current(); - // let rpc_service = self.rpc_service.clone().; - // let function_result = futures::executor::block_on(async { - // handle - // .spawn(async { rpc_service.execute_view_function(function_call).await }) - // .await - // .expect("Task spawned in Tokio executor panicked") - // })?; - - // Warning! Possible deadlocks can occur if we try to wait for a future - // let rpc_service = self.rpc_service.clone(); - // let function_result = futures::executor::block_on( - // tokio::task::spawn( - // rpc_service.execute_view_function(function_call) - // ) - // // .unwrap() - // )??; - - // // Warning! Possible deadlocks can occur if we try to wait for a future - // let function_result = futures::executor::block_on(async move { - // // tokio::task::spawn( - // RUNTIME.spawn( - // async move { rpc_service.execute_view_function(function_call).await }, - // ) - // .await? - // })?; - - // let task = tokio::task::spawn(self.rpc_service.execute_view_function(function_call)); - // let function_result = futures::executor::block_on(tokio::task::spawn( - // self.rpc_service.execute_view_function(function_call) - // ))?; - - // let function_result = - // futures::executor::block_on( self.rpc_service.execute_view_function(function_call))?; - - // let function_result = - // tokio::task::block_in_place(|| { - // self.rpc_service.execute_view_function(function_call) - // }).; - - // let result = tokio::task::spawn_blocking(invoke_juniper).await.map_err(|e| e.to_string())?; - // println!("result={:?}", result); - // Ok(json!({})) - - // let result = tokio::task::spawn_blocking(async || { - // self.rpc_service.execute_view_function(function_call).await - // }).unwrap()?; + 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 }), + )? + .unwrap(); function_result.try_into() } diff --git a/crates/rooch-rpc-server/src/service/rpc_service.rs b/crates/rooch-rpc-server/src/service/rpc_service.rs index e0245cf7ca..de2274585b 100644 --- a/crates/rooch-rpc-server/src/service/rpc_service.rs +++ b/crates/rooch-rpc-server/src/service/rpc_service.rs @@ -183,15 +183,3 @@ impl RpcService { Ok(resp) } } - -// impl MoveFunctionCaller for RpcService { -// fn call_function( -// &self, -// _ctx: &TxContext, -// function_call: FunctionCall, -// ) -> Result { -// let function_result = -// futures::executor::block_on(self.execute_view_function(function_call))?; -// function_result.try_into() -// } -// } diff --git a/crates/rooch-types/src/framework/coin.rs b/crates/rooch-types/src/framework/coin.rs index 74c3586404..7f71c60d8f 100644 --- a/crates/rooch-types/src/framework/coin.rs +++ b/crates/rooch-types/src/framework/coin.rs @@ -54,7 +54,7 @@ impl<'a> CoinModule<'a> { pub fn coin_info_handle(&self) -> Result> { let ctx = TxContext::zero(); let call = FunctionCall::new( - Self::function_id(Self::COIN_STORE_HANDLE_FUNCTION_NAME), + Self::function_id(Self::COIN_INFO_HANDLE_FUNCTION_NAME), vec![], vec![], ); @@ -133,115 +133,32 @@ impl AnnotatedCoinStore { AnnotatedCoinStore { struct_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_struct_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") - // { - // (field_name, AnnotatedMoveValue::Bytes(inner_filed_value)) => { - // debug_assert!( - // field_name.as_str() == "value", - // "CoinValue value field name should be value" - // ); - // U256::from_bytes(inner_filed_value.as_slice()) - // } - // _ => bail!("CoinValue value field should be value"), - // }?; - // - // let coin = Coin { value: coin_value }; - // AnnotatedCoin { - // struct_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 frozen field should be bool"), - // }; - // let compose_coin_store = CompoundCoinStore { - // coin: annotated_coin, - // frozen, - // }; - // - // let annotated_coin_store = AnnotatedCoinStore { - // struct_type: annotated_coin_store_type, - // value: compose_coin_store, - // }; - // - // Ok(annotated_coin_store) - // } - /// Create a new AnnotatedCoinStore from a AnnotatedMoveValue pub fn new_from_annotated_move_value(annotated_move_value: AnnotatedMoveValue) -> Result { - // pub fn new_from_annotated_move_value( - // annotated_move_value: AnnotatedMoveValueView, - // ) -> 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::Struct(filed_value)) => { + (field_name, AnnotatedMoveValue::Struct(field_value)) => { debug_assert!( field_name.as_str() == "coin", "CoinStore coin field name should be coin" ); + let coin_struct_type = field_value.type_; - let coin_struct_type = filed_value.type_; - - let mut inner_fields = filed_value.value.into_iter(); + 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::Bytes(inner_filed_value)) => { - debug_assert!( - field_name.as_str() == "value", - "CoinValue value field name should be value" - ); - U256::from_bytes(inner_filed_value.as_slice())? - } - (field_name, AnnotatedMoveValue::U64(inner_filed_value)) => { - debug_assert!( - field_name.as_str() == "value", - "CoinValue value field name should be value" - ); - U256::from(inner_filed_value) - } - (field_name, AnnotatedMoveValue::U128(inner_filed_value)) => { - debug_assert!( - field_name.as_str() == "value", - "CoinValue value field name should be value" - ); - U256::from(inner_filed_value) - } - (field_name, AnnotatedMoveValue::U256(inner_filed_value)) => { + (field_name, AnnotatedMoveValue::U256(inner_field_value)) => { debug_assert!( field_name.as_str() == "value", "CoinValue value field name should be value" ); - inner_filed_value + inner_field_value } _ => bail!("CoinValue value field should be value"), }; @@ -254,12 +171,12 @@ impl AnnotatedCoinStore { _ => bail!("CoinStore coin field should be struct"), }; let frozen = match fields.next().expect("CoinStore should have frozen field") { - (field_name, AnnotatedMoveValue::Bool(filed_value)) => { + (field_name, AnnotatedMoveValue::Bool(field_value)) => { debug_assert!( field_name.as_str() == "frozen", "CoinStore field name should be frozen" ); - filed_value + field_value } _ => bail!("CoinStore frozen field should be bool"), }; @@ -295,6 +212,46 @@ pub struct CoinInfo { 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 CoinInfo { pub fn new(name: String, symbol: String, decimals: u8, supply: U256) -> Self { @@ -326,42 +283,60 @@ impl AnnotatedCoinInfo { let mut fields = annotated_struct.value.into_iter(); let name = match fields.next().expect("CoinInfo should have name field") { - (field_name, AnnotatedMoveValue::Bytes(filed_value)) => { - debug_assert!( - field_name.as_str() == "name", - "CoinInfo field name should be name" - ); - String::from_utf8(filed_value)? + (_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::Bytes(filed_value)) => { - debug_assert!( - field_name.as_str() == "symbol", - "CoinInfo field symbol should be symbol" - ); - String::from_utf8(filed_value)? + (_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(filed_value)) => { + (field_name, AnnotatedMoveValue::U8(field_value)) => { debug_assert!( field_name.as_str() == "decimals", "CoinInfo field decimals should be decimals" ); - filed_value + field_value } _ => bail!("CoinInfo decimals field should be u8"), }; let supply = match fields.next().expect("CoinInfo should have supply field") { - (field_name, AnnotatedMoveValue::U256(filed_value)) => { + (field_name, AnnotatedMoveValue::U256(field_value)) => { debug_assert!( field_name.as_str() == "supply", "CoinInfo field supply should be supply" ); - filed_value + field_value } _ => bail!("CoinInfo supply field should be U256"), }; diff --git a/crates/rooch/src/commands/account/commands/balance.rs b/crates/rooch/src/commands/account/commands/balance.rs index d3363b70b5..fb0b746591 100644 --- a/crates/rooch/src/commands/account/commands/balance.rs +++ b/crates/rooch/src/commands/account/commands/balance.rs @@ -8,7 +8,7 @@ use rooch_rpc_api::api::MAX_RESULT_LIMIT_USIZE; 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")] @@ -28,13 +28,6 @@ pub struct BalanceCommand { impl CommandAction<()> for BalanceCommand { async fn execute(self) -> RoochResult<()> { let context = self.context_options.build().await?; - - // let address_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 address_addr = self .address .map_or( @@ -57,88 +50,6 @@ impl CommandAction<()> for BalanceCommand { ) .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 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() - // .flatten() - // .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 { - // //TODO If the coin store list exceeds MAX_RESULT_LIMIT_USIZE, consider supporting traverse or pagination - // 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!( "{0: ^102} | {1: ^16} | {2: ^32} | {3: ^6}", "Coin Type", "Symbol", "Balance", "Decimals" 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"), }; From c32ec92d46a54fa0509956a098bd5f5b4e89f014 Mon Sep 17 00:00:00 2001 From: baichuan3 Date: Mon, 11 Sep 2023 22:40:36 +0800 Subject: [PATCH 4/5] fixup --- crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs | 4 ++-- crates/rooch-rpc-server/src/service/aggregate_service.rs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) 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 c26e7bc0f7..0aa5c5fec0 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/account_view.rs @@ -61,10 +61,10 @@ pub struct BalanceInfoView { } impl BalanceInfoView { - //TODO implements big decimal calculation + //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))); - format!("{:.}", balance.to_string()) + balance.to_string() } } diff --git a/crates/rooch-rpc-server/src/service/aggregate_service.rs b/crates/rooch-rpc-server/src/service/aggregate_service.rs index f23be7f102..4284498271 100644 --- a/crates/rooch-rpc-server/src/service/aggregate_service.rs +++ b/crates/rooch-rpc-server/src/service/aggregate_service.rs @@ -44,7 +44,7 @@ impl AggregateService { let coin_module = self.as_module_binding::(); let coin_info_handle = coin_module .coin_info_handle()? - .expect("Get coin info handle should succ"); + .expect("Get coin info handle should succ."); // 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 @@ -160,8 +160,7 @@ impl MoveFunctionCaller for AggregateService { 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 }), - )? - .unwrap(); + )??; function_result.try_into() } From 72bed2feeb8c984a7be2211cf64af23ad4f8615a Mon Sep 17 00:00:00 2001 From: baichuan3 Date: Tue, 12 Sep 2023 10:27:01 +0800 Subject: [PATCH 5/5] unify type_ and handle error for tokio worker process --- crates/rooch-framework/doc/coin.md | 14 ++---- crates/rooch-framework/sources/coin.move | 12 ++--- .../src/jsonrpc_types/rooch_types.rs | 25 +++++----- .../src/service/aggregate_service.rs | 19 ++++---- crates/rooch-types/src/framework/coin.rs | 46 ++++++++++--------- 5 files changed, 56 insertions(+), 60 deletions(-) diff --git a/crates/rooch-framework/doc/coin.md b/crates/rooch-framework/doc/coin.md index b2d81bcddf..873743af39 100644 --- a/crates/rooch-framework/doc/coin.md +++ b/crates/rooch-framework/doc/coin.md @@ -863,7 +863,7 @@ Return coin store handle for addr Return coin info handle -
public fun coin_info_handle(ctx: &storage_context::StorageContext): option::Option<object_id::ObjectID>
+
public fun coin_info_handle(ctx: &storage_context::StorageContext): object_id::ObjectID
 
@@ -872,14 +872,10 @@ Return coin info handle Implementation -
public fun coin_info_handle(ctx: &StorageContext): Option<ObjectID> {
-    if (account_storage::global_exists<CoinInfos>(ctx, @rooch_framework))
-        {
-            let coin_infos = account_storage::global_borrow<CoinInfos>(ctx, @rooch_framework);
-            option::some(*type_table::handle(&coin_infos.coin_infos))
-        } else {
-        option::none<ObjectID>()
-    }
+
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 910269497e..1f26e328b4 100644 --- a/crates/rooch-framework/sources/coin.move +++ b/crates/rooch-framework/sources/coin.move @@ -240,14 +240,10 @@ module rooch_framework::coin { } /// Return coin info handle - public fun coin_info_handle(ctx: &StorageContext): Option { - if (account_storage::global_exists(ctx, @rooch_framework)) - { - let coin_infos = account_storage::global_borrow(ctx, @rooch_framework); - option::some(*type_table::handle(&coin_infos.coin_infos)) - } else { - option::none() - } + 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) } // 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 68010907a1..04f8a6fdd1 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs @@ -137,13 +137,14 @@ impl CoinView { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AnnotatedCoinView { - struct_type: StructTagView, + #[serde(rename = "type")] + type_: StructTagView, value: CoinView, } impl AnnotatedCoinView { - pub fn new(struct_type: StructTagView, value: CoinView) -> Self { - AnnotatedCoinView { struct_type, value } + pub fn new(type_: StructTagView, value: CoinView) -> Self { + AnnotatedCoinView { type_, value } } } @@ -161,7 +162,8 @@ impl CompoundCoinStoreView { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AnnotatedCoinStoreView { - struct_type: StructTagView, + #[serde(rename = "type")] + type_: StructTagView, value: CompoundCoinStoreView, } @@ -171,7 +173,7 @@ impl From for AnnotatedCoinStoreView { value: StrView(coin_store.value.coin.value.value), }; let annotated_coin = AnnotatedCoinView { - struct_type: coin_store.value.coin.struct_type.into(), + type_: coin_store.value.coin.type_.into(), value: coin, }; let compose_coin_store = CompoundCoinStoreView { @@ -179,7 +181,7 @@ impl From for AnnotatedCoinStoreView { frozen: coin_store.value.frozen, }; AnnotatedCoinStoreView { - struct_type: coin_store.struct_type.into(), + type_: coin_store.type_.into(), value: compose_coin_store, } } @@ -188,10 +190,10 @@ impl From for AnnotatedCoinStoreView { 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.struct_type.into(), coin); + let annotated_coin = AnnotatedCoin::new(coin_store.value.coin.type_.into(), coin); let compose_coin_store = CompoundCoinStore::new(annotated_coin, coin_store.value.frozen); - AnnotatedCoinStore::new(coin_store.struct_type.into(), compose_coin_store) + AnnotatedCoinStore::new(coin_store.type_.into(), compose_coin_store) } } @@ -205,7 +207,8 @@ pub struct CoinInfoView { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct AnnotatedCoinInfoView { - struct_type: StructTagView, + #[serde(rename = "type")] + type_: StructTagView, value: CoinInfoView, } @@ -218,7 +221,7 @@ impl From for AnnotatedCoinInfoView { supply: StrView(annotated_coin_info.value.supply), }; AnnotatedCoinInfoView { - struct_type: annotated_coin_info.struct_type.into(), + type_: annotated_coin_info.type_.into(), value: coin_info, } } @@ -233,6 +236,6 @@ impl From for AnnotatedCoinInfo { annotated_coin_info.value.supply.0, ); - AnnotatedCoinInfo::new(annotated_coin_info.struct_type.into(), coin_info) + AnnotatedCoinInfo::new(annotated_coin_info.type_.into(), coin_info) } } diff --git a/crates/rooch-rpc-server/src/service/aggregate_service.rs b/crates/rooch-rpc-server/src/service/aggregate_service.rs index 4284498271..a1df71a5ee 100644 --- a/crates/rooch-rpc-server/src/service/aggregate_service.rs +++ b/crates/rooch-rpc-server/src/service/aggregate_service.rs @@ -42,9 +42,7 @@ impl AggregateService { limit: usize, ) -> Result>, BalanceInfo)>>> { let coin_module = self.as_module_binding::(); - let coin_info_handle = coin_module - .coin_info_handle()? - .expect("Get coin info handle should succ."); + 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 @@ -67,14 +65,16 @@ impl AggregateService { }) .collect::>(); for coin_info in coin_info_data.into_iter().flatten() { - let coin_type = get_first_ty_as_struct_tag(coin_info.struct_type) + 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)? - .expect("Get coin store handle should succ"); + 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 { @@ -116,9 +116,8 @@ impl AggregateService { 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_struct_type()) - .expect("Coin type expected get_first_ty_as_struct_tag succ"); + 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) diff --git a/crates/rooch-types/src/framework/coin.rs b/crates/rooch-types/src/framework/coin.rs index 7f71c60d8f..138c2bc356 100644 --- a/crates/rooch-types/src/framework/coin.rs +++ b/crates/rooch-types/src/framework/coin.rs @@ -51,7 +51,7 @@ impl<'a> CoinModule<'a> { Ok(result) } - pub fn coin_info_handle(&self) -> 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), @@ -66,9 +66,8 @@ impl<'a> CoinModule<'a> { let value = values .get(0) .expect("Coin info handle expected return value"); - let result = MoveOption::::from_bytes(&value.value) - .expect("Coin info handle expected Option"); - result.into() + bcs::from_bytes::(&value.value) + .expect("Coin info handle expected Option") })?; Ok(result) } @@ -100,13 +99,14 @@ impl Coin { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AnnotatedCoin { - pub struct_type: StructTag, + #[serde(rename = "type")] + pub type_: StructTag, pub value: Coin, } impl AnnotatedCoin { - pub fn new(struct_type: StructTag, value: Coin) -> Self { - AnnotatedCoin { struct_type, value } + pub fn new(type_: StructTag, value: Coin) -> Self { + AnnotatedCoin { type_, value } } } @@ -124,13 +124,14 @@ impl CompoundCoinStore { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AnnotatedCoinStore { - pub struct_type: StructTag, + #[serde(rename = "type")] + pub type_: StructTag, pub value: CompoundCoinStore, } impl AnnotatedCoinStore { - pub fn new(struct_type: StructTag, value: CompoundCoinStore) -> Self { - AnnotatedCoinStore { struct_type, value } + pub fn new(type_: StructTag, value: CompoundCoinStore) -> Self { + AnnotatedCoinStore { type_, value } } /// Create a new AnnotatedCoinStore from a AnnotatedMoveValue @@ -146,7 +147,7 @@ impl AnnotatedCoinStore { field_name.as_str() == "coin", "CoinStore coin field name should be coin" ); - let coin_struct_type = field_value.type_; + let coin_type = field_value.type_; let mut inner_fields = field_value.value.into_iter(); let coin_value = match inner_fields @@ -164,7 +165,7 @@ impl AnnotatedCoinStore { }; let coin = Coin { value: coin_value }; AnnotatedCoin { - struct_type: coin_struct_type, + type_: coin_type, value: coin, } } @@ -186,7 +187,7 @@ impl AnnotatedCoinStore { }; let annotated_coin_store = AnnotatedCoinStore { - struct_type: annotated_coin_store_type, + type_: annotated_coin_store_type, value: compose_coin_store, }; @@ -196,8 +197,8 @@ impl AnnotatedCoinStore { } } - pub fn get_coin_struct_type(&self) -> StructTag { - self.value.coin.struct_type.clone() + pub fn get_coin_type(&self) -> StructTag { + self.value.coin.type_.clone() } pub fn get_coin_value(&self) -> U256 { @@ -266,20 +267,21 @@ impl CoinInfo { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AnnotatedCoinInfo { - pub struct_type: StructTag, + #[serde(rename = "type")] + pub type_: StructTag, pub value: CoinInfo, } impl AnnotatedCoinInfo { - pub fn new(struct_type: StructTag, value: CoinInfo) -> Self { - AnnotatedCoinInfo { struct_type, value } + 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 struct_type = annotated_struct.type_; + let type_ = annotated_struct.type_; let mut fields = annotated_struct.value.into_iter(); let name = match fields.next().expect("CoinInfo should have name field") { @@ -349,7 +351,7 @@ impl AnnotatedCoinInfo { }; let annotated_coin_info = AnnotatedCoinInfo { - struct_type, + type_, value: coin_info, }; @@ -359,8 +361,8 @@ impl AnnotatedCoinInfo { } } - pub fn get_struct_type(&self) -> StructTag { - self.struct_type.clone() + pub fn get_type(&self) -> StructTag { + self.type_.clone() } pub fn get_decimals(&self) -> u8 {