Skip to content

Commit

Permalink
[rpc] Implement eth_getBalance (#867)
Browse files Browse the repository at this point in the history
* [rpc] Implement eth_getBalance
  • Loading branch information
jolestar authored Sep 26, 2023
1 parent c815906 commit 6753281
Show file tree
Hide file tree
Showing 15 changed files with 175 additions and 21 deletions.
2 changes: 1 addition & 1 deletion crates/rooch-framework/doc/gas_coin.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ This module defines Rooch Gas Coin.



<pre><code><b>struct</b> <a href="gas_coin.md#0x3_gas_coin_GasCoin">GasCoin</a> <b>has</b> key
<pre><code><b>struct</b> <a href="gas_coin.md#0x3_gas_coin_GasCoin">GasCoin</a> <b>has</b> store, key
</code></pre>


Expand Down
4 changes: 3 additions & 1 deletion crates/rooch-framework/sources/gas_coin.move
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ module rooch_framework::gas_coin {
friend rooch_framework::genesis;
friend rooch_framework::transaction_validator;

struct GasCoin has key {}
//TODO should we allow user to transfer gas coin?
//If not, we can remove `store` ability from GasCoin.
struct GasCoin has key, store {}

public fun balance(ctx: &StorageContext, addr: address): u256 {
coin::balance<GasCoin>(ctx, addr)
Expand Down
13 changes: 12 additions & 1 deletion crates/rooch-rpc-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// SPDX-License-Identifier: Apache-2.0

use anyhow::Result;
use jsonrpsee::http_client::{HttpClient, HttpClientBuilder};
use jsonrpsee::{
core::client::ClientT,
http_client::{HttpClient, HttpClientBuilder},
};
use moveos_types::{
access_path::AccessPath,
function_return_value::FunctionResult,
Expand Down Expand Up @@ -206,6 +209,14 @@ impl Client {
.get_balances(account_addr, coin_type, cursor, limit)
.await?)
}

pub async fn request(
&self,
method: &str,
params: Vec<serde_json::Value>,
) -> Result<serde_json::Value> {
Ok(self.rpc.http.request(method, params).await?)
}
}

impl MoveFunctionCaller for Client {
Expand Down
7 changes: 5 additions & 2 deletions crates/rooch-rpc-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,11 @@ pub async fn run_start_server(opt: &RoochOpt) -> Result<ServerHandle> {
))?;
rpc_module_builder.register_module(WalletServer::new(rpc_service.clone()))?;

rpc_module_builder
.register_module(EthServer::new(chain_id_opt.chain_id(), rpc_service.clone()))?;
rpc_module_builder.register_module(EthServer::new(
chain_id_opt.chain_id(),
rpc_service.clone(),
aggregate_service.clone(),
))?;

// let rpc_api = build_rpc_api(rpc_api);
let methods_names = rpc_module_builder.module.method_names().collect::<Vec<_>>();
Expand Down
37 changes: 30 additions & 7 deletions crates/rooch-rpc-server/src/server/eth_server.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use crate::service::rpc_service::RpcService;
use crate::service::{aggregate_service::AggregateService, rpc_service::RpcService};
use ethers::types::{
transaction::eip2930::AccessList, Address, Block, BlockNumber, Bloom, Bytes, OtherFields,
Transaction, TransactionReceipt, Withdrawal, H160, U256, U64,
Expand All @@ -10,7 +10,7 @@ use jsonrpsee::{
core::{async_trait, Error as JsonRpcError, RpcResult},
RpcModule,
};
use moveos_types::{access_path::AccessPath, state::MoveStructType};
use moveos_types::{access_path::AccessPath, gas_config::GasConfig, state::MoveStructType};
use rand::Rng;
use rooch_rpc_api::api::{
eth_api::{EthAPIServer, TransactionType},
Expand All @@ -23,6 +23,7 @@ use rooch_rpc_api::jsonrpc_types::{
use rooch_types::{
account::Account,
address::{EthereumAddress, MultiChainAddress},
framework::gas_coin::GasCoin,
transaction::{AbstractTransaction, TypedTransaction},
H256,
};
Expand All @@ -35,13 +36,19 @@ use tracing::info;
pub struct EthServer {
chain_id: ChainID,
rpc_service: RpcService,
aggregate_service: AggregateService,
}

impl EthServer {
pub fn new(chain_id: ChainID, rpc_service: RpcService) -> Self {
pub fn new(
chain_id: ChainID,
rpc_service: RpcService,
aggregate_service: AggregateService,
) -> Self {
Self {
chain_id,
rpc_service,
aggregate_service,
}
}
}
Expand Down Expand Up @@ -177,16 +184,31 @@ impl EthAPIServer for EthServer {
Ok(block)
}

async fn get_balance(&self, _address: H160, _num: Option<BlockNumber>) -> RpcResult<U256> {
Ok(U256::from(100) * U256::from(10_u64.pow(18)))
async fn get_balance(&self, eth_address: H160, _num: Option<BlockNumber>) -> RpcResult<U256> {
let account_address = self
.rpc_service
.resolve_address(MultiChainAddress::from(EthereumAddress(eth_address)))
.await?;
let balance = self
.aggregate_service
.get_balances(account_address, Some(GasCoin::struct_tag()), None, 0)
.await?
.pop()
.ok_or_else(|| JsonRpcError::Custom("Balance result must not empty".to_owned()))?
.map(|(_cursor, balance_info)| {
U256::from_little_endian(&balance_info.balance.to_le_bytes())
})
.unwrap_or(U256::zero());
Ok(balance)
}

async fn estimate_gas(
&self,
_request: CallRequest,
_num: Option<BlockNumber>,
) -> RpcResult<U256> {
Ok(U256::from(10_000_000))
//TODO call dry run to estimate gas
Ok(U256::from(GasConfig::DEFAULT_MAX_GAS_AMOUNT))
}

async fn fee_history(
Expand Down Expand Up @@ -245,7 +267,8 @@ impl EthAPIServer for EthServer {
}

async fn gas_price(&self) -> RpcResult<U256> {
Ok(U256::from(20 * (10_u64.pow(9))))
//TODO read the get_gas_factor from contract.
Ok(U256::from(1))
}

async fn transaction_count(&self, address: H160, _num: Option<BlockNumber>) -> RpcResult<U256> {
Expand Down
3 changes: 3 additions & 0 deletions crates/rooch-rpc-server/src/service/aggregate_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ impl AggregateService {
result.push(Some(v))
}
};
} else {
// If the account do not exist, return None
result.push(None)
}

for (_key, balance_info) in result.iter_mut().flatten() {
Expand Down
15 changes: 15 additions & 0 deletions crates/rooch-types/src/framework/gas_coin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use move_core_types::{ident_str, identifier::IdentStr};
use moveos_types::state::MoveStructType;

pub const MODULE_NAME: &IdentStr = ident_str!("gas_coin");

#[derive(Debug, Clone)]
pub struct GasCoin;

impl MoveStructType for GasCoin {
const MODULE_NAME: &'static IdentStr = MODULE_NAME;
const STRUCT_NAME: &'static IdentStr = ident_str!("GasCoin");
}
1 change: 1 addition & 0 deletions crates/rooch-types/src/framework/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod empty;
pub mod ethereum_address;
pub mod ethereum_light_client;
pub mod ethereum_validator;
pub mod gas_coin;
pub mod genesis;
pub mod native_validator;
pub mod session_key;
Expand Down
1 change: 1 addition & 0 deletions crates/rooch/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod init;
pub mod move_cli;
pub mod object;
pub mod resource;
pub mod rpc;
pub mod server;
pub mod session_key;
pub mod state;
Expand Down
4 changes: 4 additions & 0 deletions crates/rooch/src/commands/rpc/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

pub mod request;
41 changes: 41 additions & 0 deletions crates/rooch/src/commands/rpc/commands/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use crate::cli_types::{CommandAction, WalletContextOptions};
use async_trait::async_trait;
use clap::Parser;
use rooch_types::error::RoochResult;

/// Send a RPC request
#[derive(Debug, Parser)]
pub struct RequestCommand {
/// The RPC method name
/// --method rooch_getAnnotatedStates
#[clap(long)]
pub method: String,

/// The RPC method params, json value.
/// --params '"/resource/0x3/0x3::timestamp::CurrentTimeMicroseconds"'
/// or
/// --params '["/resource/0x3/0x3::timestamp::CurrentTimeMicroseconds"]'
#[clap(long)]
pub params: Option<serde_json::Value>,

#[clap(flatten)]
pub(crate) context_options: WalletContextOptions,
}

#[async_trait]
impl CommandAction<serde_json::Value> for RequestCommand {
async fn execute(self) -> RoochResult<serde_json::Value> {
let client = self.context_options.build().await?.get_client().await?;
let params = match self.params {
Some(serde_json::Value::Array(array)) => array,
Some(value) => {
vec![value]
}
None => vec![],
};
Ok(client.request(self.method.as_str(), params).await?)
}
}
31 changes: 31 additions & 0 deletions crates/rooch/src/commands/rpc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) RoochNetwork
// SPDX-License-Identifier: Apache-2.0

use crate::cli_types::CommandAction;
use async_trait::async_trait;
use clap::Parser;
use commands::request::RequestCommand;
use rooch_types::error::RoochResult;

pub mod commands;

#[derive(Parser)]
pub struct Rpc {
#[clap(subcommand)]
cmd: RpcCommand,
}

#[async_trait]
impl CommandAction<String> for Rpc {
async fn execute(self) -> RoochResult<String> {
match self.cmd {
RpcCommand::Request(request) => request.execute_serialized().await,
}
}
}

#[derive(clap::Subcommand)]
#[clap(name = "server")]
pub enum RpcCommand {
Request(RequestCommand),
}
6 changes: 4 additions & 2 deletions crates/rooch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::commands::event::EventCommand;
use cli_types::CommandAction;
use commands::{
abi::ABI, account::Account, dashboard::Dashboard, env::Env, init::Init, move_cli::MoveCli,
object::ObjectCommand, resource::ResourceCommand, server::Server, session_key::SessionKey,
state::StateCommand, transaction::Transaction,
object::ObjectCommand, resource::ResourceCommand, rpc::Rpc, server::Server,
session_key::SessionKey, state::StateCommand, transaction::Transaction,
};
use rooch_types::error::RoochResult;

Expand Down Expand Up @@ -39,6 +39,7 @@ pub enum Command {
ABI(ABI),
Env(Env),
SessionKey(SessionKey),
Rpc(Rpc),
}

pub async fn run_cli(opt: RoochCli) -> RoochResult<String> {
Expand All @@ -56,5 +57,6 @@ pub async fn run_cli(opt: RoochCli) -> RoochResult<String> {
Command::ABI(abi) => abi.execute().await,
Command::Env(env) => env.execute().await,
Command::SessionKey(session_key) => session_key.execute().await,
Command::Rpc(rpc) => rpc.execute().await,
}
}
10 changes: 9 additions & 1 deletion crates/testsuite/features/cmd.feature
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,12 @@ Feature: Rooch CLI integration tests
#TODO change the argument `0x3` address to a user account
Then cmd: "move run --function 0x3::coin::transfer_entry --type-args {default}::fixed_supply_coin::FSC --args address:0x3 --args 1u256 --sender-account {default}"

Then stop the server
Then stop the server

@serial
Scenario: rpc test
Given a server for rpc
Then cmd: "rpc request --method eth_getBalance --params \"0x1111111111111111111111111111111111111111\""
Then assert: "{{$.rpc[-1]}} == 0x0"

Then stop the server
21 changes: 15 additions & 6 deletions crates/testsuite/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ async fn assert_output(world: &mut World, args: String) {
assert!(world.tpl_ctx.is_some(), "tpl_ctx is none");
let args = eval_command_args(world.tpl_ctx.as_ref().unwrap(), args);
let parameters = split_string_with_quotes(&args).expect("Invalid commands");

info!("parameters: {:?}", parameters);
for chunk in parameters.chunks(3) {
let first = chunk.get(0).cloned();
let op = chunk.get(1).cloned();
Expand Down Expand Up @@ -130,10 +130,10 @@ async fn assert_output(world: &mut World, args: String) {
}

fn eval_command_args(ctx: &TemplateContext, args: String) -> String {
// info!("args: {}", args);
let args = args.replace("\\\"", "\"");
//info!("args: {}", args);
//let args = args.replace("\\\"", "\"");
let eval_args = jpst::format_str!(&args, ctx);
// info!("eval args:{}", eval_args);
//info!("eval args:{}", eval_args);
eval_args
}

Expand All @@ -144,12 +144,21 @@ fn split_string_with_quotes(s: &str) -> Result<Vec<String>> {
let mut chars = s.chars().peekable();
let mut current = String::new();
let mut in_quotes = false;
let mut in_escape = false;

while let Some(c) = chars.next() {
match c {
'\\' => {
in_escape = true;
}
'"' => {
in_quotes = !in_quotes;
// Skip the quote
if in_escape {
current.push(c);
in_escape = false;
} else {
// Skip the quote
in_quotes = !in_quotes;
}
}
' ' if !in_quotes => {
if !current.is_empty() {
Expand Down

0 comments on commit 6753281

Please sign in to comment.