Skip to content

Commit

Permalink
Proper balance formatting and parsing (#1231)
Browse files Browse the repository at this point in the history
  • Loading branch information
dvc94ch authored Oct 23, 2024
1 parent e063d1c commit 078d717
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 32 deletions.
8 changes: 2 additions & 6 deletions config/envs/development/config.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
config:
shard_size: 1
shard_threshold: 1
chronicle_timechain_funds: 1000000000000
chronicle_timechain_funds: 1.
metadata_variant: testnet
timechain_url: 'wss://rpc.development-gmpv2.analog.one'
timechain_url: 'wss://rpc.development.analog.one'
prices_path: 'prices.csv'
contracts:
evm:
Expand All @@ -25,8 +25,6 @@ networks:
route_gas_limit: 10000
route_base_fee: 10000
gmp_margin: 0.1
symbol: ETH
token_decimals: 12
1:
backend: "grpc"
blockchain: "rust"
Expand All @@ -41,8 +39,6 @@ networks:
route_gas_limit: 10000
route_base_fee: 10000
gmp_margin: 0.1
symbol: ASTR
token_decimals: 12
chronicles:
- https://chronicle-1.development.analog.one
- https://chronicle-2.development.analog.one
Expand Down
6 changes: 1 addition & 5 deletions config/envs/local/config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
config:
shard_size: 1
shard_threshold: 1
chronicle_timechain_funds: 1000000000000
chronicle_timechain_funds: 1.
metadata_variant: testnet
timechain_url: 'ws://validator:9944'
prices_path: 'prices.csv'
Expand All @@ -23,8 +23,6 @@ networks:
batch_gas_limit: 10000
gmp_margin: 0.0
shard_task_limit: 50
symbol: "ETH"
token_decimals: 18
route_gas_limit: 10000
# This is the base fee of evm chains. Connector have a method to fetch base fee:
# https://github.com/Analog-Labs/chain-connectors/blob/master/chains/ethereum/server/src/utils.rs#L296
Expand All @@ -43,8 +41,6 @@ networks:
batch_gas_limit: 10000
gmp_margin: 0.0
shard_task_limit: 50
symbol: "ASTR"
token_decimals: 18
route_gas_limit: 10000
route_base_fee: 1715000000000
chronicles:
Expand Down
3 changes: 3 additions & 0 deletions gmp/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ impl IChain for Connector {
fn address(&self) -> Address {
self.parse_address(&self.wallet.account().address).unwrap()
}
fn currency(&self) -> (u32, &str) {
(18, "ETH")
}
/// Uses a faucet to fund the account when possible.
async fn faucet(&self) -> Result<()> {
let balance = match self.network_id() {
Expand Down
3 changes: 3 additions & 0 deletions gmp/grpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ impl IChain for Connector {
fn parse_address(&self, address: &str) -> Result<Address> {
gmp_rust::parse_address(address)
}
fn currency(&self) -> (u32, &str) {
gmp_rust::currency()
}
/// Network identifier.
fn network_id(&self) -> NetworkId {
self.network
Expand Down
21 changes: 20 additions & 1 deletion gmp/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const BLOCK_TIME: u64 = 1;
const FINALIZATION_TIME: u64 = 2;
const FAUCET: u128 = 1_000_000_000;

const BLOCKS: TableDefinition<u64, u64> = TableDefinition::new("blocks");
const BALANCE: TableDefinition<Address, u128> = TableDefinition::new("balance");
const ADMIN: TableDefinition<Address, Address> = TableDefinition::new("admin");
const EVENTS: MultimapTableDefinition<(Address, u64), Bincode<GmpEvent>> =
Expand Down Expand Up @@ -70,6 +71,10 @@ pub fn parse_address(address: &str) -> Result<Address> {
Ok(addr)
}

pub fn currency() -> (u32, &'static str) {
(3, "TT")
}

fn block(genesis: SystemTime) -> u64 {
let elapsed = SystemTime::now().duration_since(genesis).unwrap();
elapsed.as_secs() / BLOCK_TIME
Expand Down Expand Up @@ -102,8 +107,18 @@ impl IConnectorBuilder for Connector {
(None, Path::new(&params.url).to_owned())
};
let db = Database::create(path)?;
let genesis = SystemTime::now(); //std::fs::metadata(&path)?.created()?;
let tx = db.begin_write()?;
let genesis = {
let mut blocks = tx.open_table(BLOCKS)?;
let timestamp = blocks.get(0)?.map(|t| t.value());
if let Some(timestamp) = timestamp {
SystemTime::UNIX_EPOCH + Duration::from_secs(timestamp)
} else {
let genesis = SystemTime::now();
blocks.insert(0, genesis.duration_since(SystemTime::UNIX_EPOCH)?.as_secs())?;
genesis
}
};
tx.open_table(BALANCE)?;
tx.open_table(ADMIN)?;
tx.open_table(ROUTES)?;
Expand Down Expand Up @@ -144,6 +159,10 @@ impl IChain for Connector {
self.address
}

fn currency(&self) -> (u32, &str) {
currency()
}

async fn faucet(&self) -> Result<()> {
let tx = self.db.begin_write()?;
{
Expand Down
92 changes: 92 additions & 0 deletions primitives/src/balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use anyhow::Result;

const SI_PREFIX: [(i8, char); 12] = [
(18, 'E'),
(15, 'P'),
(12, 'T'),
(9, 'G'),
(6, 'M'),
(3, 'k'),
(-3, 'm'),
(-6, 'u'),
(-9, 'n'),
(-12, 'p'),
(-15, 'f'),
(-18, 'a'),
];

pub struct BalanceFormatter<'a> {
decimals: u32,
symbol: &'a str,
}

impl<'a> BalanceFormatter<'a> {
pub fn new(decimals: u32, symbol: &'a str) -> Self {
Self { decimals, symbol }
}

pub fn format(&self, balance: u128) -> String {
if balance == 0 {
return format!("{} {}", balance, self.symbol);
};
let digits = balance.ilog10();
let rel_digits = digits as i64 - self.decimals as i64;
let unit = rel_digits / 3 * 3;
assert!((-18..=18).contains(&unit), "balance out of range {}", unit);
let unit = unit as i8;
let prefix = SI_PREFIX.iter().find(|(d, _)| *d == unit).map(|(_, p)| p);

let base = f64::powi(10., unit as i32 + self.decimals as i32);
let tokens = balance as f64 / base;
let tokens_str = format!("{tokens:.3}");
let tokens_str = tokens_str.trim_end_matches(|c| c == '0' || c == '.');
if let Some(prefix) = prefix {
format!("{tokens_str}{prefix} {}", self.symbol)
} else {
format!("{tokens_str} {}", self.symbol)
}
}

pub fn parse(&self, balance: &str) -> Result<u128> {
let balance = balance.trim().replace('_', "");
if !balance.contains('.') && !balance.ends_with(&self.symbol) {
return balance.parse().map_err(|_| anyhow::anyhow!("balance is not a valid u128"));
}
let balance = balance.strip_suffix(&self.symbol).unwrap_or(&balance).trim_end();
let (unit, balance) = SI_PREFIX
.iter()
.find(|(_, p)| balance.ends_with(*p))
.map(|(d, p)| (*d, balance.strip_suffix(*p).unwrap()))
.unwrap_or((0, balance));
let balance: f64 =
balance.parse().map_err(|_| anyhow::anyhow!("balance is not a valid f64"))?;
let base = f64::powi(10., unit as i32 + self.decimals as i32);
Ok((balance * base) as u128)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_formatter() -> Result<()> {
let fmt_cases = [
(0, "0 ANLG"),
(1, "1p ANLG"),
(1_000_000_000_000, "1 ANLG"),
(1_200_000, "1.2u ANLG"),
(1_200_000_000_000_000, "1.2k ANLG"),
];
let parse_cases = [("0", 0), ("1_200_000", 1_200_000), ("1.", 1_000_000_000_000)];
let fmt = BalanceFormatter::new(12, "ANLG");
for (n, s) in fmt_cases {
assert_eq!(fmt.format(n), s);
assert_eq!(fmt.parse(s).unwrap(), n);
}
for (s, n) in parse_cases {
assert_eq!(fmt.parse(s).unwrap(), n);
}
Ok(())
}
}
8 changes: 6 additions & 2 deletions primitives/src/gmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,13 +289,17 @@ pub trait IChain: Send + Sync + 'static {
fn format_address(&self, address: Address) -> String;
/// Parses an address from a string.
fn parse_address(&self, address: &str) -> Result<Address>;
/// Returns the currency decimals and symobl.
fn currency(&self) -> (u32, &str);
/// Formats a balance into a string.
fn format_balance(&self, balance: u128) -> String {
balance.to_string()
let (decimals, symbol) = self.currency();
crate::balance::BalanceFormatter::new(decimals, symbol).format(balance)
}
/// Parses a balance from a string.
fn parse_balance(&self, balance: &str) -> Result<u128> {
balance.parse().map_err(|_| anyhow::anyhow!("expected unsigned integer"))
let (decimals, symbol) = self.currency();
crate::balance::BalanceFormatter::new(decimals, symbol).parse(balance)
}
/// Network identifier.
fn network_id(&self) -> NetworkId;
Expand Down
2 changes: 2 additions & 0 deletions primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use sp_runtime::{
// Export scoped ...
#[cfg(feature = "std")]
pub mod admin;
#[cfg(feature = "std")]
pub mod balance;
pub mod currency;
pub mod dmail;
pub mod gmp;
Expand Down
23 changes: 17 additions & 6 deletions tc-cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ pub struct Config {

impl Config {
pub fn from_file(path: &Path) -> Result<Self> {
let config = std::fs::read_to_string(path).context("failed to read config file")?;
let config = std::fs::read_to_string(path)
.with_context(|| format!("failed to read config file {}", path.display()))?;
let yaml = serde_yaml::from_str(&config).context("failed to parse config file")?;
Ok(Self {
path: path.parent().unwrap().to_path_buf(),
Expand Down Expand Up @@ -79,7 +80,7 @@ pub struct GlobalConfig {
prices_path: PathBuf,
pub shard_size: u16,
pub shard_threshold: u16,
pub chronicle_timechain_funds: u128,
pub chronicle_timechain_funds: String,
pub metadata_variant: MetadataVariant,
pub timechain_url: String,
}
Expand All @@ -104,15 +105,25 @@ pub struct NetworkConfig {
pub blockchain: String,
pub network: String,
pub url: String,
pub gateway_funds: u128,
pub chronicle_target_funds: u128,
pub gateway_funds: String,
pub chronicle_target_funds: String,
pub batch_size: u32,
pub batch_offset: u32,
pub batch_gas_limit: u128,
pub gmp_margin: f64,
pub shard_task_limit: u32,
pub symbol: String,
pub token_decimals: u32,
pub route_gas_limit: u64,
pub route_base_fee: u128,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn make_sure_envs_parse() {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../config/envs");
Config::from_file(&root.join("local/config.yaml")).unwrap();
Config::from_file(&root.join("development/config.yaml")).unwrap();
}
}
8 changes: 4 additions & 4 deletions tc-cli/src/gas_price_calculator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ impl Tc {
.with_context(|| format!("failed to create {}", price_path.display()))?;
let mut wtr = Writer::from_writer(file);
wtr.write_record(["network_id", "symbol", "usd_price"])?;
for (network_id, network) in self.config.networks().iter() {
let symbol = network.symbol.clone();
for network_id in self.config.networks().keys() {
let symbol = self.currency(Some(*network_id))?.1;
let token_url = format!("{}{}", env.token_price_url, symbol);
let client = reqwest::Client::new();
let request = client.get(token_url).headers(header_map.clone()).build()?;
Expand Down Expand Up @@ -191,10 +191,10 @@ impl Tc {
) -> Result<Ratio<BigUint>> {
let src_config = self.config.network(src_network)?;
let src_margin: f64 = src_config.gmp_margin;
let src_decimals: u32 = src_config.token_decimals;
let src_decimals = self.currency(Some(src_network))?.0;

let dest_config = self.config.network(dest_network)?;
let dest_decimals: u32 = dest_config.token_decimals;
let dest_decimals = self.currency(Some(dest_network))?.0;
let dest_gas_fee = dest_config.route_base_fee;

let src_usd_price =
Expand Down
29 changes: 21 additions & 8 deletions tc-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ use std::sync::Arc;
use std::time::Duration;
use tc_subxt::SubxtClient;
use time_primitives::{
AccountId, Address, BatchId, BlockHash, BlockNumber, ChainName, ChainNetwork, ConnectorParams,
Gateway, GatewayMessage, GmpEvent, GmpMessage, IConnectorAdmin, MemberStatus, MessageId,
NetworkConfig, NetworkId, PublicKey, Route, ShardId, ShardStatus, TaskId, TssPublicKey,
balance::BalanceFormatter, AccountId, Address, BatchId, BlockHash, BlockNumber, ChainName,
ChainNetwork, ConnectorParams, Gateway, GatewayMessage, GmpEvent, GmpMessage, IConnectorAdmin,
MemberStatus, MessageId, NetworkConfig, NetworkId, PublicKey, Route, ShardId, ShardStatus,
TaskId, TssPublicKey,
};

mod config;
Expand Down Expand Up @@ -148,19 +149,27 @@ impl Tc {
}
}

pub fn currency(&self, network: Option<NetworkId>) -> Result<(u32, &str)> {
if let Some(network) = network {
Ok(self.connector(network)?.currency())
} else {
Ok((12, "ANLG"))
}
}

pub fn parse_balance(&self, network: Option<NetworkId>, balance: &str) -> Result<u128> {
if let Some(network) = network {
self.connector(network)?.parse_balance(balance)
} else {
Ok(balance.parse()?)
BalanceFormatter::new(12, "ANLG").parse(balance)
}
}

pub fn format_balance(&self, network: Option<NetworkId>, balance: u128) -> Result<String> {
if let Some(network) = network {
Ok(self.connector(network)?.format_balance(balance))
} else {
Ok(balance.to_string())
Ok(BalanceFormatter::new(12, "ANLG").format(balance))
}
}

Expand Down Expand Up @@ -712,7 +721,8 @@ impl Tc {
self.faucet(network).await?;
}
tracing::info!("funding gateway");
self.fund(Some(network), gateway, config.gateway_funds).await?;
let gateway_funds = self.parse_balance(Some(network), &config.gateway_funds)?;
self.fund(Some(network), gateway, gateway_funds).await?;
gateways.insert(network, gateway);
}
self.register_routes(gateways).await?;
Expand All @@ -721,14 +731,17 @@ impl Tc {
let chronicle = self.wait_for_chronicle(chronicle).await?;
tracing::info!("funding chronicle timechain account");
let status = self.chronicle_status(&chronicle.account).await?;
let mut funds = self.config.global().chronicle_timechain_funds;
let mut funds =
self.parse_balance(None, &self.config.global().chronicle_timechain_funds)?;
if status == ChronicleStatus::Unregistered {
funds += self.runtime.min_stake().await?;
}
self.fund(None, chronicle.account.clone().into(), funds).await?;
let config = self.config.network(chronicle.network)?;
tracing::info!("funding chronicle target account");
self.fund(Some(chronicle.network), chronicle.address, config.chronicle_target_funds)
let chronicle_target_funds =
self.parse_balance(Some(chronicle.network), &config.chronicle_target_funds)?;
self.fund(Some(chronicle.network), chronicle.address, chronicle_target_funds)
.await?;
accounts.push(chronicle.account);
}
Expand Down

0 comments on commit 078d717

Please sign in to comment.