Skip to content

Commit

Permalink
add balance ops
Browse files Browse the repository at this point in the history
  • Loading branch information
kariy committed Apr 4, 2024
1 parent 2c9d4a3 commit f50ccb9
Show file tree
Hide file tree
Showing 10 changed files with 546 additions and 12 deletions.
383 changes: 379 additions & 4 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ rika-ops = { path = "crates/ops" }
chrono = "0.4.23"
clap = { version = "4.1.8", features = [ "derive", "env" ] }
clap_complete = "4.1.0"
colored_json = "4.1.0"
eyre = "0.6.8"
hex = "0.4.3"
reqwest = { version = "0.12.0", features = [ "json", "rustls-tls" ], default-features = false }
Expand Down
1 change: 1 addition & 0 deletions bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fn main() -> Result<()> {

fn execute(command: Commands) -> Result<()> {
match command {
Commands::Balance(args) => ops::balance::get(args)?,
Commands::Tx(args) => ops::transaction::get(args)?,
Commands::TxCount(args) => ops::transaction::count(args)?,
Commands::TxStatus(args) => ops::transaction::status(args)?,
Expand Down
2 changes: 1 addition & 1 deletion crates/args/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ version.workspace = true
chrono.workspace = true
clap.workspace = true
clap_complete.workspace = true
colored_json = "4.1.0"
colored_json.workspace = true
comfy-table = "6.1.4"
dunce = "1.0.3"
eyre.workspace = true
Expand Down
17 changes: 11 additions & 6 deletions crates/args/src/commands/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,27 @@ use crate::parser::{BlockIdParser, TokenAddressParser};
pub struct BalanceArgs {
#[arg(value_name = "ADDRESS")]
#[arg(help = "The address whose balance you want to query.")]
address: FieldElement,
pub address: FieldElement,

#[arg(help = "The token you want to query the balance of.")]
#[arg(value_parser(TokenAddressParser))]
token: FieldElement,
#[arg(value_parser = TokenAddressParser)]
#[arg(default_value_t = TokenAddressParser::ETH)]
pub token: FieldElement,

#[arg(long)]
#[arg(help = "Return the balance as a raw integer value in hexadecimal form.")]
pub raw: bool,

#[arg(next_line_help = true)]
#[arg(short, long = "block")]
#[arg(default_value = "pending")]
#[arg(value_parser(BlockIdParser))]
#[arg(value_parser = BlockIdParser)]
#[arg(
help = "The hash of the requested block, or number (height) of the requested block, or a block tag (e.g. latest, pending)."
)]
block_id: BlockId,
pub block_id: BlockId,

#[command(flatten)]
#[command(next_help_heading = "Starknet options")]
starknet: StarknetOptions,
pub starknet: StarknetOptions,
}
3 changes: 3 additions & 0 deletions crates/ops/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
alloy-primitives = "0.7.0"
bigdecimal = "0.4.3"
colored_json.workspace = true
eyre.workspace = true
reqwest = { workspace = true, features = [ "rustls-tls" ] }
rika-args.workspace = true
Expand Down
137 changes: 137 additions & 0 deletions crates/ops/src/balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use std::future::join;

use alloy_primitives::U256;
use eyre::{eyre, ContextCompat, Report, Result};
use rika_args::commands::balance::BalanceArgs;
use starknet::{
core::{
types::{BlockId, FieldElement, FunctionCall, StarknetError},
utils::parse_cairo_short_string,
},
macros::selector,
providers::{Provider, ProviderError},
};

use crate::utils;

pub fn get(args: BalanceArgs) -> Result<()> {
let BalanceArgs {
address,
token,
raw,
block_id,
starknet,
} = args;

let provider = starknet.provider();
let (metadata, balance) = utils::block_on(join!(
get_token_metadata(&provider, block_id, token),
get_balance(&provider, block_id, token, address)
));

let balance = balance?;
let (symbol, decimals) = metadata?;

if raw {
println!("{:#x}", balance);
} else {
println!("{}", format_balance(balance, &symbol, decimals));
}

Ok(())
}

async fn get_balance<P>(
provider: P,
block_id: BlockId,
contract_address: FieldElement,
address: FieldElement,
) -> Result<U256>
where
P: Provider,
{
fn handle_error(err: ProviderError) -> Report {
match err {
ProviderError::StarknetError(StarknetError::ContractNotFound) => {
eyre!("token contract not found")
}
e => eyre!(e),
}
}

let call = FunctionCall {
contract_address,
calldata: vec![address],
entry_point_selector: selector!("balanceOf"),
};

let retdata = provider.call(call, block_id).await.map_err(handle_error)?;
// the convention is to return a u256, which means there are two felts
let low = retdata.first().context("missing low value")?;
let high = retdata.last().context("missing high value")?;

utils::to_u256(*low, *high)
}

async fn get_token_metadata<P>(
provider: P,
block_id: BlockId,
contract_address: FieldElement,
) -> Result<(String, u8)>
where
P: Provider + Sync,
{
async fn get_decimals(
provider: impl Provider,
block_id: BlockId,
contract_address: FieldElement,
) -> Result<u8> {
let request = FunctionCall {
contract_address,
calldata: Vec::new(),
entry_point_selector: selector!("decimals"),
};

let result = provider.call(request, block_id).await?;
let decimals = result.first().context("missing decimals in call retdata")?;
Ok((*decimals).try_into()?)
}

async fn get_symbol(
provider: impl Provider,
block_id: BlockId,
contract_address: FieldElement,
) -> Result<String> {
let request = FunctionCall {
contract_address,
calldata: Vec::new(),
entry_point_selector: selector!("symbol"),
};

let result = provider.call(request, block_id).await?;
let symbol = result.first().context("missing symbol in call retdata")?;
Ok(parse_cairo_short_string(symbol)?)
}

let (symbol, decimals) = join!(
get_symbol(&provider, block_id, contract_address),
get_decimals(&provider, block_id, contract_address)
)
.await;

Ok((symbol?, decimals?))
}

fn format_balance(balance: U256, symbol: &str, decimals: u8) -> String {
use bigdecimal::{
num_bigint::{BigInt, Sign},
BigDecimal,
};

let decimal = BigDecimal::new(
BigInt::from_bytes_be(Sign::Plus, &balance.to_be_bytes::<{ U256::BYTES }>()),
decimals as i64,
);

format!("{decimal} {symbol}")
}
2 changes: 2 additions & 0 deletions crates/ops/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![feature(future_join)]

Check failure on line 1 in crates/ops/src/lib.rs

View workflow job for this annotation

GitHub Actions / Cargo test

`#![feature]` may not be used on the stable release channel
#![cfg_attr(not(test), warn(unused_crate_dependencies))]

pub mod balance;
pub mod rpc;
pub mod transaction;
mod utils;
2 changes: 1 addition & 1 deletion crates/ops/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub fn send(args: RpcArgs) -> Result<()> {

let payload = build_payload(&method, params);
let res = utils::block_on(send_request::<Value>(url, payload))?;
println!("{}", serde_json::to_string_pretty(&res)?);
println!("{}", colored_json::to_colored_json_auto(&res)?);

Ok(())
}
Expand Down
10 changes: 10 additions & 0 deletions crates/ops/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::future::Future;

use alloy_primitives::U256;
use eyre::{Context, Result};
use starknet::core::types::FieldElement;

/// Blocks on a future, returning the output.
pub fn block_on<F, T>(future: F) -> T
where
Expand All @@ -12,3 +16,9 @@ where
.expect("failed to build async runtime")
.block_on(future)
}

pub fn to_u256(low: FieldElement, high: FieldElement) -> Result<U256> {
let low: u128 = low.try_into().context("parsing low")?;
let high: u128 = high.try_into().context("parsing high")?;
Ok(U256::from(high) << 128 | U256::from(low))
}

0 comments on commit f50ccb9

Please sign in to comment.