diff --git a/src/lib.rs b/src/lib.rs index fb329abe83..e85782fbfb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,12 +23,9 @@ use { inscription::Inscription, inscription_id::InscriptionId, media::Media, - object::Object, options::Options, outgoing::Outgoing, - rarity::Rarity, representation::Representation, - sat::Sat, subcommand::Subcommand, tally::Tally, }, @@ -74,7 +71,7 @@ use { }; pub use crate::{ - fee_rate::FeeRate, sat_point::SatPoint, + fee_rate::FeeRate, object::Object, rarity::Rarity, sat::Sat, sat_point::SatPoint, subcommand::wallet::transaction_builder::TransactionBuilder, }; diff --git a/src/object.rs b/src/object.rs index a967caac98..aaa30c1743 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,7 +1,7 @@ use super::*; #[derive(Debug, PartialEq)] -pub(crate) enum Object { +pub enum Object { Address(Address), Hash([u8; 32]), InscriptionId(InscriptionId), @@ -50,6 +50,24 @@ impl Display for Object { } } +impl Serialize for Object { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for Object { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(DeserializeFromStr::deserialize(deserializer)?.0) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/sat.rs b/src/sat.rs index 9444a0ae22..dcc2afb2b5 100644 --- a/src/sat.rs +++ b/src/sat.rs @@ -2,7 +2,7 @@ use super::*; #[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Ord, PartialOrd, Deserialize, Serialize)] #[serde(transparent)] -pub struct Sat(pub(crate) u64); +pub struct Sat(pub u64); impl Sat { pub(crate) const LAST: Self = Self(Self::SUPPLY - 1); diff --git a/src/subcommand.rs b/src/subcommand.rs index c4b7b49af1..cc1848b252 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -1,16 +1,16 @@ use super::*; -mod epochs; -mod find; +pub mod epochs; +pub mod find; mod index; -mod info; -mod list; -mod parse; +pub mod info; +pub mod list; +pub mod parse; mod preview; mod server; -mod subsidy; -mod supply; -mod traits; +pub mod subsidy; +pub mod supply; +pub mod traits; pub mod wallet; fn print_json(output: impl Serialize) -> Result { diff --git a/src/subcommand/epochs.rs b/src/subcommand/epochs.rs index aeff9afa11..53293cb025 100644 --- a/src/subcommand/epochs.rs +++ b/src/subcommand/epochs.rs @@ -1,8 +1,17 @@ use super::*; +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Output { + pub starting_sats: Vec, +} + pub(crate) fn run() -> Result { + let mut starting_sats = Vec::new(); for sat in Epoch::STARTING_SATS { - println!("{}", sat); + starting_sats.push(sat); } + + print_json(Output { starting_sats })?; + Ok(()) } diff --git a/src/subcommand/find.rs b/src/subcommand/find.rs index e06132797c..fdf314cc48 100644 --- a/src/subcommand/find.rs +++ b/src/subcommand/find.rs @@ -6,6 +6,11 @@ pub(crate) struct Find { sat: Sat, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Output { + pub satpoint: SatPoint, +} + impl Find { pub(crate) fn run(self, options: Options) -> Result { let index = Index::open(&options)?; @@ -14,7 +19,7 @@ impl Find { match index.find(self.sat.0)? { Some(satpoint) => { - println!("{satpoint}"); + print_json(Output { satpoint })?; Ok(()) } None => Err(anyhow!("sat has not been mined as of index height")), diff --git a/src/subcommand/info.rs b/src/subcommand/info.rs index fa015510dd..aa96de00b4 100644 --- a/src/subcommand/info.rs +++ b/src/subcommand/info.rs @@ -6,6 +6,14 @@ pub(crate) struct Info { transactions: bool, } +#[derive(Serialize, Deserialize)] +pub struct TransactionsOutput { + pub start: u64, + pub end: u64, + pub count: u64, + pub elapsed: f64, +} + impl Info { pub(crate) fn run(self, options: Options) -> Result { let index = Index::open(&options)?; @@ -13,19 +21,18 @@ impl Info { let info = index.info()?; if self.transactions { - println!("start\tend\tcount\telapsed"); - + let mut output = Vec::new(); for window in info.transactions.windows(2) { let start = &window[0]; let end = &window[1]; - println!( - "{}\t{}\t{}\t{:.2}", - start.starting_block_count, - end.starting_block_count, - end.starting_block_count - start.starting_block_count, - (end.starting_timestamp - start.starting_timestamp) as f64 / 1000.0 / 60.0 - ); + output.push(TransactionsOutput { + start: start.starting_block_count, + end: end.starting_block_count, + count: end.starting_block_count - start.starting_block_count, + elapsed: (end.starting_timestamp - start.starting_timestamp) as f64 / 1000.0 / 60.0, + }); } + print_json(output)?; } else { print_json(info)?; } diff --git a/src/subcommand/list.rs b/src/subcommand/list.rs index fb3f493256..b1193638fe 100644 --- a/src/subcommand/list.rs +++ b/src/subcommand/list.rs @@ -6,6 +6,15 @@ pub(crate) struct List { outpoint: OutPoint, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Output { + pub output: OutPoint, + pub start: u64, + pub size: u64, + pub rarity: Rarity, + pub name: String, +} + impl List { pub(crate) fn run(self, options: Options) -> Result { let index = Index::open(&options)?; @@ -14,10 +23,19 @@ impl List { match index.list(self.outpoint)? { Some(crate::index::List::Unspent(ranges)) => { + let mut outputs = Vec::new(); for (output, start, size, rarity, name) in list(self.outpoint, ranges) { - println!("{output}\t{start}\t{size}\t{rarity}\t{name}"); + outputs.push(Output { + output, + start, + size, + rarity, + name, + }); } + print_json(outputs)?; + Ok(()) } Some(crate::index::List::Spent) => Err(anyhow!("output spent.")), diff --git a/src/subcommand/parse.rs b/src/subcommand/parse.rs index 2a6d378ff0..73c815d3de 100644 --- a/src/subcommand/parse.rs +++ b/src/subcommand/parse.rs @@ -6,9 +6,16 @@ pub(crate) struct Parse { object: Object, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Output { + pub object: Object, +} + impl Parse { pub(crate) fn run(self) -> Result { - println!("{}", self.object); + print_json(Output { + object: self.object, + })?; Ok(()) } } diff --git a/src/subcommand/subsidy.rs b/src/subcommand/subsidy.rs index 6eb2fe47b7..e293d6d7c6 100644 --- a/src/subcommand/subsidy.rs +++ b/src/subcommand/subsidy.rs @@ -6,6 +6,13 @@ pub(crate) struct Subsidy { height: Height, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Output { + pub first: u64, + pub subsidy: u64, + pub name: String, +} + impl Subsidy { pub(crate) fn run(self) -> Result { let first = self.height.starting_sat(); @@ -16,7 +23,11 @@ impl Subsidy { bail!("block {} has no subsidy", self.height); } - println!("{}\t{}\t{}", first, self.height.subsidy(), first.name()); + print_json(Output { + first: first.0, + subsidy, + name: first.name(), + })?; Ok(()) } diff --git a/src/subcommand/supply.rs b/src/subcommand/supply.rs index 801bc744d2..4a4b468cca 100644 --- a/src/subcommand/supply.rs +++ b/src/subcommand/supply.rs @@ -1,5 +1,13 @@ use super::*; +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Output { + pub supply: u64, + pub first: u64, + pub last: u64, + pub last_mined_in_block: u64, +} + pub(crate) fn run() -> Result { let mut last = 0; @@ -10,10 +18,12 @@ pub(crate) fn run() -> Result { last += 1; } - println!("supply: {}", Sat::SUPPLY); - println!("first: {}", 0); - println!("last: {}", Sat::SUPPLY - 1); - println!("last mined in block: {}", last); + print_json(Output { + supply: Sat::SUPPLY, + first: 0, + last: Sat::SUPPLY - 1, + last_mined_in_block: last, + })?; Ok(()) } diff --git a/src/subcommand/traits.rs b/src/subcommand/traits.rs index 455f01f41c..bfb4999be6 100644 --- a/src/subcommand/traits.rs +++ b/src/subcommand/traits.rs @@ -6,71 +6,35 @@ pub(crate) struct Traits { sat: Sat, } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct Output { + pub number: u64, + pub decimal: String, + pub degree: String, + pub name: String, + pub height: u64, + pub cycle: u64, + pub epoch: u64, + pub period: u64, + pub offset: u64, + pub rarity: Rarity, +} + impl Traits { pub(crate) fn run(self) -> Result { - print!("{}", self); - Ok(()) - } -} + print_json(Output { + number: self.sat.n(), + decimal: self.sat.decimal().to_string(), + degree: self.sat.degree().to_string(), + name: self.sat.name(), + height: self.sat.height().0, + cycle: self.sat.cycle(), + epoch: self.sat.epoch().0, + period: self.sat.period(), + offset: self.sat.third(), + rarity: self.sat.rarity(), + })?; -impl Display for Traits { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - writeln!(f, "number: {}", self.sat.n())?; - writeln!(f, "decimal: {}", self.sat.decimal())?; - writeln!(f, "degree: {}", self.sat.degree())?; - writeln!(f, "name: {}", self.sat.name())?; - writeln!(f, "height: {}", self.sat.height())?; - writeln!(f, "cycle: {}", self.sat.cycle())?; - writeln!(f, "epoch: {}", self.sat.epoch())?; - writeln!(f, "period: {}", self.sat.period())?; - writeln!(f, "offset: {}", self.sat.third())?; - writeln!(f, "rarity: {}", self.sat.rarity())?; Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn first() { - assert_eq!( - Traits { sat: Sat(0) }.to_string(), - "\ -number: 0 -decimal: 0.0 -degree: 0°0′0″0‴ -name: nvtdijuwxlp -height: 0 -cycle: 0 -epoch: 0 -period: 0 -offset: 0 -rarity: mythic -", - ); - } - - #[test] - fn last() { - assert_eq!( - Traits { - sat: Sat(2099999997689999) - } - .to_string(), - "\ -number: 2099999997689999 -decimal: 6929999.0 -degree: 5°209999′1007″0‴ -name: a -height: 6929999 -cycle: 5 -epoch: 32 -period: 3437 -offset: 0 -rarity: uncommon -", - ); - } -} diff --git a/tests/command_builder.rs b/tests/command_builder.rs index d983b07caa..b6bb0153c8 100644 --- a/tests/command_builder.rs +++ b/tests/command_builder.rs @@ -61,13 +61,6 @@ impl CommandBuilder { } } - pub(crate) fn expected_stdout(self, expected_stdout: impl AsRef) -> Self { - Self { - expected_stdout: Expected::String(expected_stdout.as_ref().to_owned()), - ..self - } - } - pub(crate) fn stdout_regex(self, expected_stdout: impl AsRef) -> Self { Self { expected_stdout: Expected::regex(expected_stdout.as_ref()), diff --git a/tests/epochs.rs b/tests/epochs.rs index aad9f5b927..d98c9d0ccf 100644 --- a/tests/epochs.rs +++ b/tests/epochs.rs @@ -1,46 +1,46 @@ -use super::*; +use {super::*, ord::subcommand::epochs::Output, ord::Sat}; #[test] fn empty() { - CommandBuilder::new("epochs") - .expected_stdout( - " - 0 - 1050000000000000 - 1575000000000000 - 1837500000000000 - 1968750000000000 - 2034375000000000 - 2067187500000000 - 2083593750000000 - 2091796875000000 - 2095898437500000 - 2097949218750000 - 2098974609270000 - 2099487304530000 - 2099743652160000 - 2099871825870000 - 2099935912620000 - 2099967955890000 - 2099983977420000 - 2099991988080000 - 2099995993410000 - 2099997995970000 - 2099998997250000 - 2099999497890000 - 2099999748210000 - 2099999873370000 - 2099999935950000 - 2099999967240000 - 2099999982780000 - 2099999990550000 - 2099999994330000 - 2099999996220000 - 2099999997060000 - 2099999997480000 - 2099999997690000 - " - .unindent(), - ) - .run(); + assert_eq!( + CommandBuilder::new("epochs").output::(), + Output { + starting_sats: vec![ + Sat(0), + Sat(1050000000000000), + Sat(1575000000000000), + Sat(1837500000000000), + Sat(1968750000000000), + Sat(2034375000000000), + Sat(2067187500000000), + Sat(2083593750000000), + Sat(2091796875000000), + Sat(2095898437500000), + Sat(2097949218750000), + Sat(2098974609270000), + Sat(2099487304530000), + Sat(2099743652160000), + Sat(2099871825870000), + Sat(2099935912620000), + Sat(2099967955890000), + Sat(2099983977420000), + Sat(2099991988080000), + Sat(2099995993410000), + Sat(2099997995970000), + Sat(2099998997250000), + Sat(2099999497890000), + Sat(2099999748210000), + Sat(2099999873370000), + Sat(2099999935950000), + Sat(2099999967240000), + Sat(2099999982780000), + Sat(2099999990550000), + Sat(2099999994330000), + Sat(2099999996220000), + Sat(2099999997060000), + Sat(2099999997480000), + Sat(2099999997690000) + ] + } + ); } diff --git a/tests/find.rs b/tests/find.rs index 04474560c0..ab8739761c 100644 --- a/tests/find.rs +++ b/tests/find.rs @@ -1,12 +1,18 @@ -use super::*; +use {super::*, ord::subcommand::find::Output}; #[test] fn find_command_returns_satpoint_for_sat() { let rpc_server = test_bitcoincore_rpc::spawn(); - CommandBuilder::new("--index-sats find 0") - .rpc_server(&rpc_server) - .expected_stdout("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0\n") - .run(); + assert_eq!( + CommandBuilder::new("--index-sats find 0") + .rpc_server(&rpc_server) + .output::(), + Output { + satpoint: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0" + .parse() + .unwrap() + } + ); } #[test] diff --git a/tests/info.rs b/tests/info.rs index 74b8b1e83f..f3b1518a65 100644 --- a/tests/info.rs +++ b/tests/info.rs @@ -1,4 +1,4 @@ -use super::*; +use {super::*, ord::subcommand::info::TransactionsOutput}; #[test] fn json_with_satoshi_index() { @@ -72,33 +72,37 @@ fn transactions() { let index_path = tempdir.path().join("index.redb"); - CommandBuilder::new(format!( + assert!(CommandBuilder::new(format!( "--index {} info --transactions", index_path.display() )) .rpc_server(&rpc_server) - .expected_stdout("start\tend\tcount\telapsed\n") - .run(); + .output::>() + .is_empty()); rpc_server.mine_blocks(10); - CommandBuilder::new(format!( + let output = CommandBuilder::new(format!( "--index {} info --transactions", index_path.display() )) .rpc_server(&rpc_server) - .stdout_regex("start\tend\tcount\telapsed\n0\t1\t1\t\\d+\\.\\d+\n") - .run(); + .output::>(); + + assert_eq!(output[0].start, 0); + assert_eq!(output[0].end, 1); + assert_eq!(output[0].count, 1); rpc_server.mine_blocks(10); - CommandBuilder::new(format!( + let output = CommandBuilder::new(format!( "--index {} info --transactions", index_path.display() )) .rpc_server(&rpc_server) - .expected_stdout("start\tend\tcount\telapsed\n") - .stdout_regex("start\tend\tcount\telapsed\n0\t1\t1\t\\d+\\.\\d+\n") - .stdout_regex("start\tend\tcount\telapsed\n0\t1\t1\t\\d+\\.\\d+\n1\t11\t10\t\\d+\\.\\d+\n") - .run(); + .output::>(); + + assert_eq!(output[1].start, 1); + assert_eq!(output[1].end, 11); + assert_eq!(output[1].count, 10); } diff --git a/tests/lib.rs b/tests/lib.rs index 8ee8659842..710e6b2a88 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -20,7 +20,6 @@ use { }, tempfile::TempDir, test_bitcoincore_rpc::Sent, - unindent::Unindent, }; macro_rules! assert_regex_match { diff --git a/tests/list.rs b/tests/list.rs index 646b99ad13..db8660d36d 100644 --- a/tests/list.rs +++ b/tests/list.rs @@ -1,14 +1,26 @@ -use super::*; +use {super::*, ord::subcommand::list::Output}; #[test] fn output_found() { let rpc_server = test_bitcoincore_rpc::spawn(); - CommandBuilder::new( + let output = CommandBuilder::new( "--index-sats list 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0", ) .rpc_server(&rpc_server) - .expected_stdout("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0\t0\t5000000000\tmythic\tnvtdijuwxlp\n") - .run(); + .output::>(); + + assert_eq!( + output, + vec![Output { + output: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0" + .parse() + .unwrap(), + start: 0, + size: 50 * COIN_VALUE, + rarity: "mythic".parse().unwrap(), + name: "nvtdijuwxlp".into(), + }] + ); } #[test] diff --git a/tests/parse.rs b/tests/parse.rs index 0eac9abed2..a4cb3ecaed 100644 --- a/tests/parse.rs +++ b/tests/parse.rs @@ -1,17 +1,26 @@ -use super::*; +use {super::*, ord::subcommand::parse::Output, ord::Object}; #[test] fn name() { - CommandBuilder::new("parse a") - .expected_stdout("2099999997689999\n") - .run(); + assert_eq!( + CommandBuilder::new("parse a").output::(), + Output { + object: Object::Integer(2099999997689999), + } + ); } #[test] fn hash() { - CommandBuilder::new("parse 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") - .expected_stdout("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n") - .run(); + assert_eq!( + CommandBuilder::new("parse 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .output::(), + Output { + object: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + .parse::() + .unwrap(), + } + ); } #[test] diff --git a/tests/subsidy.rs b/tests/subsidy.rs index f1a037e10b..b84b14333d 100644 --- a/tests/subsidy.rs +++ b/tests/subsidy.rs @@ -1,31 +1,51 @@ -use super::*; +use {super::*, ord::subcommand::subsidy::Output}; #[test] fn genesis() { - CommandBuilder::new("subsidy 0") - .expected_stdout("0\t5000000000\tnvtdijuwxlp\n") - .run(); + assert_eq!( + CommandBuilder::new("subsidy 0").output::(), + Output { + first: 0, + subsidy: 5000000000, + name: "nvtdijuwxlp".into(), + } + ); } #[test] fn second_block() { - CommandBuilder::new("subsidy 1") - .expected_stdout("5000000000\t5000000000\tnvtcsezkbth\n") - .run(); + assert_eq!( + CommandBuilder::new("subsidy 1").output::(), + Output { + first: 5000000000, + subsidy: 5000000000, + name: "nvtcsezkbth".into(), + } + ); } #[test] fn second_to_last_block_with_subsidy() { - CommandBuilder::new("subsidy 6929998") - .expected_stdout("2099999997689998\t1\tb\n") - .run(); + assert_eq!( + CommandBuilder::new("subsidy 6929998").output::(), + Output { + first: 2099999997689998, + subsidy: 1, + name: "b".into(), + } + ); } #[test] fn last_block_with_subsidy() { - CommandBuilder::new("subsidy 6929999") - .expected_stdout("2099999997689999\t1\ta\n") - .run(); + assert_eq!( + CommandBuilder::new("subsidy 6929999").output::(), + Output { + first: 2099999997689999, + subsidy: 1, + name: "a".into(), + } + ); } #[test] diff --git a/tests/supply.rs b/tests/supply.rs index 10c4d9679a..89f1a63d00 100644 --- a/tests/supply.rs +++ b/tests/supply.rs @@ -1,16 +1,14 @@ -use super::*; +use {super::*, ord::subcommand::supply::Output}; #[test] fn genesis() { - CommandBuilder::new("supply") - .expected_stdout( - " - supply: 2099999997690000 - first: 0 - last: 2099999997689999 - last mined in block: 6929999 - " - .unindent(), - ) - .run(); + assert_eq!( + CommandBuilder::new("supply").output::(), + Output { + supply: 2099999997690000, + first: 0, + last: 2099999997689999, + last_mined_in_block: 6929999 + } + ); } diff --git a/tests/traits.rs b/tests/traits.rs index a4a7f13f9b..5ac00d8074 100644 --- a/tests/traits.rs +++ b/tests/traits.rs @@ -1,21 +1,38 @@ -use super::*; +use {super::*, ord::subcommand::traits::Output, ord::Rarity}; #[test] fn traits_command_prints_sat_traits() { - CommandBuilder::new("traits 0") - .expected_stdout( - "\ -number: 0 -decimal: 0.0 -degree: 0°0′0″0‴ -name: nvtdijuwxlp -height: 0 -cycle: 0 -epoch: 0 -period: 0 -offset: 0 -rarity: mythic -", - ) - .run(); + assert_eq!( + CommandBuilder::new("traits 0").output::(), + Output { + number: 0, + decimal: "0.0".into(), + degree: "0°0′0″0‴".into(), + name: "nvtdijuwxlp".into(), + height: 0, + cycle: 0, + epoch: 0, + period: 0, + offset: 0, + rarity: Rarity::Mythic, + } + ); +} +#[test] +fn traits_command_for_last_sat() { + assert_eq!( + CommandBuilder::new("traits 2099999997689999").output::(), + Output { + number: 2099999997689999, + decimal: "6929999.0".into(), + degree: "5°209999′1007″0‴".into(), + name: "a".into(), + height: 6929999, + cycle: 5, + epoch: 32, + period: 3437, + offset: 0, + rarity: Rarity::Uncommon, + } + ); }