Skip to content

Commit

Permalink
Add dry run to send, print Outgoing and PSBT (#3063)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored and casey committed Feb 2, 2024
1 parent 4ab4444 commit 44538e6
Show file tree
Hide file tree
Showing 14 changed files with 536 additions and 191 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

123 changes: 111 additions & 12 deletions src/decimal.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::*;

#[derive(Debug, PartialEq, Copy, Clone)]
pub(crate) struct Decimal {
pub struct Decimal {
value: u128,
scale: u8,
}
Expand All @@ -24,6 +24,30 @@ impl Decimal {
}
}

impl Display for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let magnitude = 10u128.pow(self.scale.into());

let integer = self.value / magnitude;
let mut fraction = self.value % magnitude;

write!(f, "{integer}")?;

if fraction > 0 {
let mut width = self.scale.into();

while fraction % 10 == 0 {
fraction /= 10;
width -= 1;
}

write!(f, ".{fraction:0>width$}", width = width)?;
}

Ok(())
}
}

impl FromStr for Decimal {
type Err = Error;

Expand All @@ -39,20 +63,15 @@ impl FromStr for Decimal {
integer.parse::<u128>()?
};

let decimal = if decimal.is_empty() {
0
let (decimal, scale) = if decimal.is_empty() {
(0, 0)
} else {
decimal.parse::<u128>()?
let trailing_zeros = decimal.chars().rev().take_while(|c| *c == '0').count();
let significant_digits = decimal.chars().count() - trailing_zeros;
let decimal = decimal.parse::<u128>()? / 10u128.pow(u32::try_from(trailing_zeros).unwrap());
(decimal, u8::try_from(significant_digits).unwrap())
};

let scale = s
.trim_end_matches('0')
.chars()
.skip_while(|c| *c != '.')
.skip(1)
.count()
.try_into()?;

Ok(Self {
value: integer * 10u128.pow(u32::from(scale)) + decimal,
scale,
Expand All @@ -66,6 +85,24 @@ impl FromStr for Decimal {
}
}

impl Serialize for Decimal {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}

impl<'de> Deserialize<'de> for Decimal {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(DeserializeFromStr::deserialize(deserializer)?.0)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -99,6 +136,7 @@ mod tests {
case("1.11", 111, 2);
case("1.", 1, 0);
case(".1", 1, 1);
case("1.10", 11, 1);
}

#[test]
Expand Down Expand Up @@ -149,4 +187,65 @@ mod tests {
case("123.456", 3, 123456);
case("123.456", 6, 123456000);
}

#[test]
fn to_string() {
#[track_caller]
fn case(decimal: Decimal, string: &str) {
assert_eq!(decimal.to_string(), string);
assert_eq!(decimal, string.parse::<Decimal>().unwrap());
}

case(Decimal { value: 1, scale: 0 }, "1");
case(Decimal { value: 1, scale: 1 }, "0.1");
case(
Decimal {
value: 101,
scale: 2,
},
"1.01",
);
case(
Decimal {
value: 1234,
scale: 6,
},
"0.001234",
);
case(
Decimal {
value: 12,
scale: 0,
},
"12",
);
case(
Decimal {
value: 12,
scale: 1,
},
"1.2",
);
case(
Decimal {
value: 12,
scale: 2,
},
"0.12",
);
case(
Decimal {
value: 123456,
scale: 3,
},
"123.456",
);
case(
Decimal {
value: 123456789,
scale: 6,
},
"123.456789",
);
}
}
3 changes: 1 addition & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use {
epoch::Epoch,
height::Height,
inscriptions::{media, teleburn, Charm, Media, ParsedEnvelope},
outgoing::Outgoing,
representation::Representation,
runes::{Etching, Pile, SpacedRune},
subcommand::{Subcommand, SubcommandResult},
Expand Down Expand Up @@ -127,7 +126,7 @@ pub mod index;
mod inscriptions;
mod object;
mod options;
mod outgoing;
pub mod outgoing;
pub mod rarity;
mod representation;
pub mod runes;
Expand Down
122 changes: 121 additions & 1 deletion src/outgoing.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
use super::*;

#[derive(Debug, PartialEq, Clone)]
pub(crate) enum Outgoing {
pub enum Outgoing {
Amount(Amount),
InscriptionId(InscriptionId),
SatPoint(SatPoint),
Rune { decimal: Decimal, rune: SpacedRune },
}

impl Display for Outgoing {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Amount(amount) => write!(f, "{}", amount.to_string().to_lowercase()),
Self::InscriptionId(inscription_id) => inscription_id.fmt(f),
Self::SatPoint(satpoint) => satpoint.fmt(f),
Self::Rune { decimal, rune } => write!(f, "{decimal} {rune}"),
}
}
}

impl FromStr for Outgoing {
type Err = Error;

Expand Down Expand Up @@ -69,6 +80,24 @@ impl FromStr for Outgoing {
}
}

impl Serialize for Outgoing {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}

impl<'de> Deserialize<'de> for Outgoing {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(DeserializeFromStr::deserialize(deserializer)?.0)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -153,4 +182,95 @@ mod tests {

assert!("0".parse::<Outgoing>().is_err());
}

#[test]
fn roundtrip() {
#[track_caller]
fn case(s: &str, outgoing: Outgoing) {
assert_eq!(s.parse::<Outgoing>().unwrap(), outgoing);
assert_eq!(s, outgoing.to_string());
}

case(
"0000000000000000000000000000000000000000000000000000000000000000i0",
Outgoing::InscriptionId(
"0000000000000000000000000000000000000000000000000000000000000000i0"
.parse()
.unwrap(),
),
);

case(
"0000000000000000000000000000000000000000000000000000000000000000:0:0",
Outgoing::SatPoint(
"0000000000000000000000000000000000000000000000000000000000000000:0:0"
.parse()
.unwrap(),
),
);

case("0 btc", Outgoing::Amount("0 btc".parse().unwrap()));
case("1.2 btc", Outgoing::Amount("1.2 btc".parse().unwrap()));

case(
"0 XY•Z",
Outgoing::Rune {
rune: "XY•Z".parse().unwrap(),
decimal: "0".parse().unwrap(),
},
);

case(
"1.1 XYZ",
Outgoing::Rune {
rune: "XYZ".parse().unwrap(),
decimal: "1.1".parse().unwrap(),
},
);
}

#[test]
fn serde() {
#[track_caller]
fn case(s: &str, j: &str, o: Outgoing) {
assert_eq!(s.parse::<Outgoing>().unwrap(), o);
assert_eq!(serde_json::to_string(&o).unwrap(), j);
assert_eq!(serde_json::from_str::<Outgoing>(j).unwrap(), o);
}

case(
"0000000000000000000000000000000000000000000000000000000000000000i0",
"\"0000000000000000000000000000000000000000000000000000000000000000i0\"",
Outgoing::InscriptionId(
"0000000000000000000000000000000000000000000000000000000000000000i0"
.parse()
.unwrap(),
),
);

case(
"0000000000000000000000000000000000000000000000000000000000000000:0:0",
"\"0000000000000000000000000000000000000000000000000000000000000000:0:0\"",
Outgoing::SatPoint(
"0000000000000000000000000000000000000000000000000000000000000000:0:0"
.parse()
.unwrap(),
),
);

case(
"3 btc",
"\"3 btc\"",
Outgoing::Amount(Amount::from_sat(3 * COIN_VALUE)),
);

case(
"6.66 HELL.MONEY",
"\"6.66 HELL•MONEY\"",
Outgoing::Rune {
rune: "HELL•MONEY".parse().unwrap(),
decimal: "6.66".parse().unwrap(),
},
);
}
}
Loading

0 comments on commit 44538e6

Please sign in to comment.