Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cast): add flag equivalents of parseUnits, formatUnits #9165

Merged
merged 9 commits into from
Oct 24, 2024
32 changes: 32 additions & 0 deletions crates/cast/bin/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,38 @@ pub enum CastSubcommand {
unit: String,
},

/// Convert a number from decimal to smallest unit with arbitrary decimals.
///
/// Examples:
/// - 1.0 6 (for USDC, result: 1000000)
/// - 2.5 12 (for 12 decimals token, result: 2500000000000)
/// - 1.23 3 (for 3 decimals token, result: 1230)
#[command(visible_aliases = &["--parse-units", "pun"])]
ParseUnits {
/// The value to convert.
value: Option<String>,

/// The unit to convert to.
#[arg(default_value = "18")]
unit: u8,
},

/// Format a number from smallest unit to decimal with arbitrary decimals.
///
/// Examples:
/// - 1000000 6 (for USDC, result: 1.0)
/// - 2500000000000 12 (for 12 decimals, result: 2.5)
/// - 1230 3 (for 3 decimals, result: 1.23)
#[command(visible_aliases = &["--format-units", "fun"])]
FormatUnits {
/// The value to format.
value: Option<String>,

/// The unit to format to.
#[arg(default_value = "18")]
unit: u8,
},

/// Convert an ETH amount to wei.
///
/// Consider using --to-unit.
Expand Down
8 changes: 8 additions & 0 deletions crates/cast/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ async fn main_args(args: CastArgs) -> Result<()> {
let value = stdin::unwrap_line(value)?;
println!("{}", SimpleCast::to_unit(&value, &unit)?);
}
CastSubcommand::ParseUnits { value, unit } => {
let value = stdin::unwrap_line(value)?;
println!("{}", SimpleCast::parse_units(&value, unit)?);
}
CastSubcommand::FormatUnits { value, unit } => {
let value = stdin::unwrap_line(value)?;
println!("{}", SimpleCast::format_units(&value, unit)?);
}
CastSubcommand::FromWei { value, unit } => {
let value = stdin::unwrap_line(value)?;
println!("{}", SimpleCast::from_wei(&value, &unit)?);
Expand Down
51 changes: 48 additions & 3 deletions crates/cast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1351,17 +1351,62 @@ impl SimpleCast {
.wrap_err("Could not convert to uint")?
.0;
let unit = unit.parse().wrap_err("could not parse units")?;
let mut formatted = ParseUnits::U256(value).format_units(unit);
Ok(Self::format_unit_as_string(value, unit))
}

/// Convert a number into a uint with arbitrary decimals.
///
/// # Example
///
/// ```
/// use cast::SimpleCast as Cast;
///
/// # fn main() -> eyre::Result<()> {
/// assert_eq!(Cast::parse_units("1.0", 6)?, "1000000"); // USDC (6 decimals)
/// assert_eq!(Cast::parse_units("2.5", 6)?, "2500000");
/// assert_eq!(Cast::parse_units("1.0", 12)?, "1000000000000"); // 12 decimals
/// assert_eq!(Cast::parse_units("1.23", 3)?, "1230"); // 3 decimals
/// # Ok(())
/// # }
/// ```
pub fn parse_units(value: &str, unit: u8) -> Result<String> {
let unit = Unit::new(unit).ok_or_else(|| eyre::eyre!("invalid unit"))?;

Ok(ParseUnits::parse_units(value, unit)?.to_string())
}

/// Format a number from smallest unit to decimal with arbitrary decimals.
///
/// # Example
///
/// ```
/// use cast::SimpleCast as Cast;
///
/// # fn main() -> eyre::Result<()> {
/// assert_eq!(Cast::format_units("1000000", 6)?, "1"); // USDC (6 decimals)
/// assert_eq!(Cast::format_units("2500000", 6)?, "2.500000");
/// assert_eq!(Cast::format_units("1000000000000", 12)?, "1"); // 12 decimals
/// assert_eq!(Cast::format_units("1230", 3)?, "1.230"); // 3 decimals
/// # Ok(())
/// # }
/// ```
pub fn format_units(value: &str, unit: u8) -> Result<String> {
let value = NumberWithBase::parse_int(value, None)?.number();
let unit = Unit::new(unit).ok_or_else(|| eyre::eyre!("invalid unit"))?;
Ok(Self::format_unit_as_string(value, unit))
}

// Helper function to format units as a string
fn format_unit_as_string(value: U256, unit: Unit) -> String {
let mut formatted = ParseUnits::U256(value).format_units(unit);
// Trim empty fractional part.
if let Some(dot) = formatted.find('.') {
let fractional = &formatted[dot + 1..];
if fractional.chars().all(|c: char| c == '0') {
formatted = formatted[..dot].to_string();
}
}

Ok(formatted)
formatted
}

/// Converts wei into an eth amount
Expand Down
36 changes: 36 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1387,3 +1387,39 @@ casttest!(hash_message, |_prj, cmd| {

"#]]);
});

casttest!(parse_units, |_prj, cmd| {
cmd.args(["parse-units", "1.5", "6"]).assert_success().stdout_eq(str![[r#"
1500000

"#]]);

cmd.cast_fuse().args(["pun", "1.23", "18"]).assert_success().stdout_eq(str![[r#"
1230000000000000000

"#]]);

cmd.cast_fuse().args(["--parse-units", "1.23", "3"]).assert_success().stdout_eq(str![[r#"
1230

"#]]);
});

casttest!(format_units, |_prj, cmd| {
cmd.args(["format-units", "1000000", "6"]).assert_success().stdout_eq(str![[r#"
1

"#]]);

cmd.cast_fuse().args(["--format-units", "2500000", "6"]).assert_success().stdout_eq(str![[
r#"
2.500000

"#
]]);

cmd.cast_fuse().args(["fun", "1230", "3"]).assert_success().stdout_eq(str![[r#"
1.230

"#]]);
});
Loading