diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs index e4c8dcb4cda5..6cbfd73f7988 100644 --- a/crates/cast/bin/args.rs +++ b/crates/cast/bin/args.rs @@ -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, + + /// 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, + + /// The unit to format to. + #[arg(default_value = "18")] + unit: u8, + }, + /// Convert an ETH amount to wei. /// /// Consider using --to-unit. diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index bdd316399a47..90367d106e8a 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -133,6 +133,14 @@ async fn main_args(args: CastArgs) -> Result<()> { let value = stdin::unwrap_line(value)?; sh_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)?; sh_println!("{}", SimpleCast::from_wei(&value, &unit)?)? diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index ea717b6805d0..d40a4a5954aa 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -1354,8 +1354,54 @@ 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 { + 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 { + 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..]; @@ -1363,8 +1409,7 @@ impl SimpleCast { formatted = formatted[..dot].to_string(); } } - - Ok(formatted) + formatted } /// Converts wei into an eth amount diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 9f32693f7c9f..c4af46564d86 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1461,3 +1461,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 + +"#]]); +});