diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index d8121a4921..4ffaeb88f7 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -56,6 +56,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.72" @@ -332,17 +380,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -537,16 +574,16 @@ dependencies = [ [[package]] name = "boilerplate" -version = "0.2.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e7294fa817b2deee8867b77bc4d378523240b5dddecdd9f11fd08187503048" +checksum = "1906889b1f805a715eac02b2dea416e25c5cfa00f099530fa9d137a3cff93113" dependencies = [ - "darling", + "darling 0.20.3", "mime", "new_mime_guess", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.27", ] [[package]] @@ -618,44 +655,78 @@ dependencies = [ "winapi", ] +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" -version = "3.2.25" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ - "atty", - "bitflags 1.3.2", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "indexmap 1.9.3", - "once_cell", "strsim", - "termcolor", - "textwrap", ] [[package]] name = "clap_derive" -version = "3.2.25" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.27", ] [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "concurrent-queue" @@ -778,8 +849,18 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core 0.20.3", + "darling_macro 0.20.3", ] [[package]] @@ -796,17 +877,42 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.27", +] + [[package]] name = "darling_macro" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ - "darling_core", + "darling_core 0.14.4", "quote", "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core 0.20.3", + "quote", + "syn 2.0.27", +] + [[package]] name = "data-encoding" version = "2.4.0" @@ -853,7 +959,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" dependencies = [ - "darling", + "darling 0.14.4", "proc-macro2", "quote", "syn 1.0.109", @@ -1242,6 +1348,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.12.3" @@ -1285,15 +1397,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.2" @@ -1501,7 +1604,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "libc", "windows-sys 0.48.0", ] @@ -1512,7 +1615,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "rustix 0.38.4", "windows-sys 0.48.0", ] @@ -1680,9 +1783,9 @@ dependencies = [ [[package]] name = "mp4" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509348cba250e7b852a875100a2ddce7a36ee3abf881a681c756670c1774264d" +checksum = "c9ef834d5ed55e494a2ae350220314dc4aacd1c43a9498b00e320e0ea352a5c3" dependencies = [ "byteorder", "bytes", @@ -1788,7 +1891,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "libc", ] @@ -1830,9 +1933,10 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ord" -version = "0.8.1" +version = "0.9.0" dependencies = [ "anyhow", + "async-trait", "axum", "axum-server", "base64 0.21.2", @@ -1841,6 +1945,7 @@ dependencies = [ "bitcoin", "boilerplate", "chrono", + "ciborium", "clap", "ctrlc", "derive_more", @@ -1879,9 +1984,9 @@ dependencies = [ [[package]] name = "ord-bitcoincore-rpc" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece3c1158d3391127ddd7615868792a602745cf4f9fbea259c4449c9ed89814a" +checksum = "3d57a4297d466506cde088e020b33819f9a496d50272b14d7890e6fde5595a3e" dependencies = [ "bitcoin-private", "jsonrpc", @@ -1893,9 +1998,9 @@ dependencies = [ [[package]] name = "ord-bitcoincore-rpc-json" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f46eb509cc95759461d3cb483ba51b2adbea0e31747cb1e3f388e71e13a321" +checksum = "5bb35088f918c775bc27fa79e372d034892b216fb7900aeedd6e06654879ad33" dependencies = [ "bitcoin", "bitcoin-private", @@ -1903,12 +2008,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "os_str_bytes" -version = "6.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" - [[package]] name = "parking" version = "2.1.0" @@ -1990,30 +2089,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.66" @@ -2266,9 +2341,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "6.8.1" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" +checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -2277,9 +2352,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "6.8.1" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" +checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29" dependencies = [ "proc-macro2", "quote", @@ -2290,9 +2365,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "7.8.1" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" +checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada" dependencies = [ "sha2", "walkdir", @@ -2511,6 +2586,7 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ + "indexmap 2.0.0", "itoa", "ryu", "serde", @@ -2749,12 +2825,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thiserror" version = "1.0.44" @@ -3041,6 +3111,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "value-bag" version = "1.4.1" diff --git a/src/index.rs b/src/index.rs index 4d7d96ad5c..5901c9af9a 100644 --- a/src/index.rs +++ b/src/index.rs @@ -674,8 +674,53 @@ impl Index { } } + pub(crate) fn get_rune_balances_for_outpoint( + &self, + outpoint: OutPoint, + ) -> Result> { + if !self.has_rune_index()? { + return Ok(Vec::new()); + } + + let rtx = &self.database.begin_read()?; + + let outpoint_to_balances = rtx.open_table(OUTPOINT_TO_RUNE_BALANCES)?; + + let id_to_rune_entries = rtx.open_table(RUNE_ID_TO_RUNE_ENTRY)?; + + let Some(balances) = outpoint_to_balances.get(&outpoint.store())? else { + return Ok(Vec::new()); + }; + + let balances_buffer = balances.value(); + + let mut balances = Vec::new(); + let mut i = 0; + while i < balances_buffer.len() { + let (id, length) = runes::varint::decode(&balances_buffer[i..]).unwrap(); + i += length; + let (amount, length) = runes::varint::decode(&balances_buffer[i..]).unwrap(); + i += length; + + let id = RuneId::try_from(id).unwrap(); + + let entry = RuneEntry::load(id_to_rune_entries.get(id.store())?.unwrap().value()); + + balances.push(( + entry.rune, + Pile { + amount, + divisibility: entry.divisibility, + symbol: entry.symbol, + }, + )); + } + + Ok(balances) + } + #[cfg(test)] - pub(crate) fn rune_balances(&self) -> Vec<(OutPoint, Vec<(RuneId, u128)>)> { + pub(crate) fn get_rune_balances(&self) -> Vec<(OutPoint, Vec<(RuneId, u128)>)> { let mut result = Vec::new(); for entry in self diff --git a/src/index/entry.rs b/src/index/entry.rs index b9c853ec96..abc63de3fd 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -27,17 +27,17 @@ pub(crate) struct RuneEntry { pub(crate) burned: u128, pub(crate) divisibility: u8, pub(crate) etching: Txid, - pub(crate) rarity: Rarity, pub(crate) rune: Rune, pub(crate) supply: u128, + pub(crate) symbol: Option, } -pub(super) type RuneEntryValue = (u128, u8, (u128, u128), u8, u128, u128); +pub(super) type RuneEntryValue = (u128, u8, (u128, u128), u128, u128, u32); impl Entry for RuneEntry { type Value = RuneEntryValue; - fn load((burned, divisibility, etching, rarity, rune, supply): RuneEntryValue) -> Self { + fn load((burned, divisibility, etching, rune, supply, symbol): RuneEntryValue) -> Self { Self { burned, divisibility, @@ -51,9 +51,9 @@ impl Entry for RuneEntry { high[14], high[15], ]) }, - rarity: Rarity::try_from(rarity).unwrap(), rune: Rune(rune), supply, + symbol: char::from_u32(symbol), } } @@ -74,9 +74,12 @@ impl Entry for RuneEntry { ]), ) }, - self.rarity.into(), self.rune.0, self.supply, + match self.symbol { + Some(symbol) => symbol.into(), + None => u32::max_value(), + }, ) } } @@ -401,9 +404,44 @@ mod tests { 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, ]), - rarity: Rarity::Epic, - rune: Rune(4), - supply: 5, + rune: Rune(3), + supply: 4, + symbol: Some('a'), + }; + + assert_eq!( + rune_entry.store(), + ( + 1, + 2, + ( + 0x0F0E0D0C0B0A09080706050403020100, + 0x1F1E1D1C1B1A19181716151413121110 + ), + 3, + 4, + u32::from('a'), + ) + ); + + assert_eq!( + RuneEntry::load(( + 1, + 2, + ( + 0x0F0E0D0C0B0A09080706050403020100, + 0x1F1E1D1C1B1A19181716151413121110 + ), + 3, + 4, + u32::from('a'), + )), + rune_entry + ); + + let rune_entry = RuneEntry { + symbol: None, + ..rune_entry }; assert_eq!( @@ -417,7 +455,7 @@ mod tests { ), 3, 4, - 5, + u32::max_value(), ) ); @@ -431,7 +469,7 @@ mod tests { ), 3, 4, - 5, + u32::max_value(), )), rune_entry ); diff --git a/src/index/updater/rune_updater.rs b/src/index/updater/rune_updater.rs index f0921bbead..a4e4814a91 100644 --- a/src/index/updater/rune_updater.rs +++ b/src/index/updater/rune_updater.rs @@ -8,16 +8,15 @@ struct Allocation { divisibility: u8, id: u128, rune: Rune, + symbol: Option, } pub(super) struct RuneUpdater<'a, 'db, 'tx> { - count: usize, height: u64, id_to_entry: &'a mut Table<'db, 'tx, RuneIdValue, RuneEntryValue>, minimum: Rune, outpoint_to_balances: &'a mut Table<'db, 'tx, &'static OutPointValue, &'static [u8]>, rune_to_id: &'a mut Table<'db, 'tx, u128, RuneIdValue>, - rarity: Rarity, } impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { @@ -28,8 +27,6 @@ impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { rune_to_id: &'a mut Table<'db, 'tx, u128, RuneIdValue>, ) -> Self { Self { - count: 0, - rarity: Height(height).starting_sat().rarity(), height, id_to_entry, minimum: Rune::minimum_at_height(Height(height)), @@ -84,6 +81,7 @@ impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { divisibility: etching.divisibility, id: u128::from(self.height) << 16 | u128::from(index), rune: etching.rune, + symbol: etching.symbol, }), Err(_) => None, } @@ -132,6 +130,7 @@ impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { divisibility, id, rune, + symbol, }) = allocation { // Calculate the allocated supply @@ -147,18 +146,13 @@ impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { burned: 0, divisibility, etching: txid, - rarity: if self.count == 0 { - self.rarity - } else { - Rarity::Common - }, rune, supply, + symbol, } .store(), )?; } - self.count += 1; } } diff --git a/src/lib.rs b/src/lib.rs index 984c9c0182..7ccc0b30f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ use { options::Options, outgoing::Outgoing, representation::Representation, - runes::{Pile, RuneId}, + runes::{Pile, Rune, RuneId}, subcommand::{Subcommand, SubcommandResult}, tally::Tally, }, diff --git a/src/runes.rs b/src/runes.rs index 71de9b5e66..9c9d67a08b 100644 --- a/src/runes.rs +++ b/src/runes.rs @@ -47,7 +47,7 @@ mod tests { .arg("--index-runes-pre-alpha-i-agree-to-get-rekt") .build(); assert_eq!(context.index.runes().unwrap().unwrap(), []); - assert_eq!(context.index.rune_balances(), []); + assert_eq!(context.index.get_rune_balances(), []); } #[test] @@ -73,7 +73,7 @@ mod tests { context.mine_blocks(1); assert_eq!(context.index.runes().unwrap().unwrap(), []); - assert_eq!(context.index.rune_balances(), []); + assert_eq!(context.index.get_rune_balances(), []); } #[test] @@ -92,6 +92,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -102,7 +103,7 @@ mod tests { context.mine_blocks(1); assert_eq!(context.index.runes().unwrap().unwrap(), []); - assert_eq!(context.index.rune_balances(), []); + assert_eq!(context.index.get_rune_balances(), []); } #[test] @@ -125,6 +126,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -147,15 +149,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); } @@ -181,6 +183,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(u128::from(Sat::SUPPLY - 150 * COIN_VALUE - 1)), + symbol: None, }), } .encipher(), @@ -192,7 +195,7 @@ mod tests { assert_eq!(context.index.runes().unwrap().unwrap(), []); - assert_eq!(context.index.rune_balances(), []); + assert_eq!(context.index.get_rune_balances(), []); } { @@ -214,6 +217,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(u128::from(Sat::SUPPLY - 150 * COIN_VALUE)), + symbol: None, }), } .encipher(), @@ -238,13 +242,13 @@ mod tests { etching: txid, rune: Rune(u128::from(Sat::SUPPLY - 150 * COIN_VALUE)), supply: u128::max_value(), - rarity: Rarity::Uncommon, + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); } @@ -270,6 +274,7 @@ mod tests { etching: Some(Etching { divisibility: 1, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -294,13 +299,13 @@ mod tests { etching: txid, divisibility: 1, supply: u128::max_value(), - rarity: Rarity::Uncommon, + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); } @@ -332,6 +337,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -354,15 +360,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); } @@ -394,6 +400,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -416,15 +423,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); } @@ -449,6 +456,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -471,15 +479,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: 100, + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, 100)])] ); } @@ -511,6 +519,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -533,15 +542,15 @@ mod tests { burned: 100, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: 200, + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [ (OutPoint { txid, vout: 0 }, vec![(id, 100)]), (OutPoint { txid, vout: 1 }, vec![(id, 100)]) @@ -576,6 +585,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -598,15 +608,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: 100, + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, 100)]),] ); } @@ -631,6 +641,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -653,15 +664,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -697,15 +708,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid1, @@ -736,6 +747,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -758,15 +770,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -798,15 +810,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid1, @@ -838,6 +850,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -860,15 +873,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -894,15 +907,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid1, @@ -933,6 +946,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -955,15 +969,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); @@ -979,6 +993,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -996,15 +1011,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); } @@ -1029,6 +1044,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -1051,15 +1067,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -1081,6 +1097,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE + 1), + symbol: None, }), } .encipher(), @@ -1104,9 +1121,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -1115,16 +1132,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Uncommon, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ) ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [ ( OutPoint { @@ -1159,9 +1176,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -1170,16 +1187,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Uncommon, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ) ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid2, @@ -1210,6 +1227,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -1232,15 +1250,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -1262,6 +1280,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE + 1), + symbol: None, }), } .encipher(), @@ -1285,9 +1304,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -1296,16 +1315,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Uncommon, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ) ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [ ( OutPoint { @@ -1340,9 +1359,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -1351,16 +1370,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Uncommon, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ) ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid2, @@ -1405,9 +1424,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -1416,16 +1435,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Uncommon, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ) ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [ ( OutPoint { @@ -1468,6 +1487,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -1490,15 +1510,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -1520,6 +1540,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE + 1), + symbol: None, }), } .encipher(), @@ -1543,9 +1564,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -1554,16 +1575,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Uncommon, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ) ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [ ( OutPoint { @@ -1616,9 +1637,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -1627,16 +1648,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Uncommon, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ) ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid2, @@ -1668,6 +1689,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -1690,15 +1712,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -1729,15 +1751,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 1 }, vec![(id, u128::max_value())])] ); } @@ -1762,6 +1784,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -1786,6 +1809,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE + 1), + symbol: None, }), } .encipher(), @@ -1809,9 +1833,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -1820,16 +1844,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Common, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ), ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [ ( OutPoint { @@ -1869,6 +1893,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -1891,15 +1916,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -1942,15 +1967,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid1, @@ -1981,6 +2006,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -2003,15 +2029,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -2033,6 +2059,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE + 1), + symbol: None, }), } .encipher(), @@ -2056,9 +2083,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -2067,16 +2094,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Uncommon, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ) ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [ ( OutPoint { @@ -2129,9 +2156,9 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } ), ( @@ -2140,16 +2167,16 @@ mod tests { burned: 0, divisibility: 0, etching: txid1, - rarity: Rarity::Uncommon, rune: Rune(RUNE + 1), supply: u128::max_value(), + symbol: None, } ) ] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [ ( OutPoint { @@ -2189,6 +2216,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -2211,15 +2239,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value() / 2, + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid0, @@ -2255,15 +2283,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid0, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value() / 2, + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [( OutPoint { txid: txid1, @@ -2294,6 +2322,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -2316,15 +2345,15 @@ mod tests { burned: u128::max_value(), divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 1 }, vec![(id, u128::max_value())])] ); } @@ -2350,6 +2379,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -2372,15 +2402,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); } @@ -2413,6 +2443,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(RUNE), + symbol: None, }), } .encipher(), @@ -2435,15 +2466,71 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(RUNE), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - context.index.rune_balances(), + context.index.get_rune_balances(), + [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] + ); + } + + #[test] + fn etching_may_specify_symbol() { + let context = Context::builder() + .arg("--index-runes-pre-alpha-i-agree-to-get-rekt") + .build(); + + context.mine_blocks(1); + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, Witness::new())], + op_return: Some( + Runestone { + edicts: vec![Edict { + id: 0, + amount: u128::max_value(), + output: 0, + }], + etching: Some(Etching { + divisibility: 0, + rune: Rune(RUNE), + symbol: Some('$'), + }), + } + .encipher(), + ), + ..Default::default() + }); + + context.mine_blocks(1); + + let id = RuneId { + height: 2, + index: 1, + }; + + assert_eq!( + context.index.runes().unwrap().unwrap(), + [( + id, + RuneEntry { + burned: 0, + divisibility: 0, + etching: txid, + rune: Rune(RUNE), + supply: u128::max_value(), + symbol: Some('$'), + } + )] + ); + + assert_eq!( + context.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); } diff --git a/src/runes/etching.rs b/src/runes/etching.rs index 5967aac2b5..88befa9dd5 100644 --- a/src/runes/etching.rs +++ b/src/runes/etching.rs @@ -4,4 +4,5 @@ use super::*; pub struct Etching { pub(crate) divisibility: u8, pub(crate) rune: Rune, + pub(crate) symbol: Option, } diff --git a/src/runes/pile.rs b/src/runes/pile.rs index c83c03c749..8300a0b271 100644 --- a/src/runes/pile.rs +++ b/src/runes/pile.rs @@ -3,10 +3,15 @@ use super::*; pub(crate) struct Pile { pub(crate) amount: u128, pub(crate) divisibility: u8, + pub(crate) symbol: Option, } impl Display for Pile { fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if let Some(symbol) = self.symbol { + write!(f, "{symbol}")?; + } + let cutoff = 10u128.pow(self.divisibility.into()); let whole = self.amount / cutoff; @@ -35,7 +40,8 @@ mod tests { assert_eq!( Pile { amount: 0, - divisibility: 0 + divisibility: 0, + symbol: None, } .to_string(), "0" @@ -43,7 +49,8 @@ mod tests { assert_eq!( Pile { amount: 25, - divisibility: 0 + divisibility: 0, + symbol: None, } .to_string(), "25" @@ -52,6 +59,7 @@ mod tests { Pile { amount: 0, divisibility: 1, + symbol: None, } .to_string(), "0" @@ -60,6 +68,7 @@ mod tests { Pile { amount: 1, divisibility: 1, + symbol: None, } .to_string(), "0.1" @@ -68,6 +77,7 @@ mod tests { Pile { amount: 1, divisibility: 2, + symbol: None, } .to_string(), "0.01" @@ -76,6 +86,7 @@ mod tests { Pile { amount: 10, divisibility: 2, + symbol: None, } .to_string(), "0.1" @@ -84,6 +95,7 @@ mod tests { Pile { amount: 1100, divisibility: 3, + symbol: None, } .to_string(), "1.1" @@ -92,6 +104,7 @@ mod tests { Pile { amount: 100, divisibility: 2, + symbol: None, } .to_string(), "1" @@ -100,6 +113,7 @@ mod tests { Pile { amount: 101, divisibility: 2, + symbol: None, } .to_string(), "1.01" @@ -108,6 +122,7 @@ mod tests { Pile { amount: u128::max_value(), divisibility: 18, + symbol: None, } .to_string(), "340282366920938463463.374607431768211455" @@ -116,9 +131,19 @@ mod tests { Pile { amount: u128::max_value(), divisibility: MAX_DIVISIBILITY, + symbol: None, } .to_string(), "3.40282366920938463463374607431768211455" ); + assert_eq!( + Pile { + amount: 0, + divisibility: 0, + symbol: Some('$'), + } + .to_string(), + "$0" + ); } } diff --git a/src/runes/rune.rs b/src/runes/rune.rs index 9f4065946e..e47046b7b6 100644 --- a/src/runes/rune.rs +++ b/src/runes/rune.rs @@ -1,7 +1,7 @@ use super::*; -#[derive(Default, Serialize, Debug, PartialEq, Copy, Clone, PartialOrd)] -pub(crate) struct Rune(pub(crate) u128); +#[derive(Default, Debug, PartialEq, Copy, Clone, PartialOrd, Ord, Eq)] +pub struct Rune(pub u128); impl Rune { pub(crate) fn minimum_at_height(height: Height) -> Self { @@ -11,6 +11,24 @@ impl Rune { } } +impl Serialize for Rune { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.collect_str(self) + } +} + +impl<'de> Deserialize<'de> for Rune { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(DeserializeFromStr::deserialize(deserializer)?.0) + } +} + impl Display for Rune { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let mut n = self.0; @@ -124,4 +142,12 @@ mod tests { Sat(100 * COIN_VALUE - 1).name().to_uppercase() ); } + + #[test] + fn serde() { + let rune = Rune(0); + let json = "\"A\""; + assert_eq!(serde_json::to_string(&rune).unwrap(), json); + assert_eq!(serde_json::from_str::(json).unwrap(), rune); + } } diff --git a/src/runes/runestone.rs b/src/runes/runestone.rs index 6640c91dd9..192497a8e8 100644 --- a/src/runes/runestone.rs +++ b/src/runes/runestone.rs @@ -38,6 +38,7 @@ impl Runestone { etching = Some(Etching { divisibility: 0, rune: Rune(rune), + symbol: None, }) } [rune, parameters] => { @@ -46,6 +47,14 @@ impl Runestone { .unwrap() .min(MAX_DIVISIBILITY), rune: Rune(rune), + symbol: { + let symbol = u32::try_from(parameters >> 6 & 0xFFFFFFFF).unwrap(); + if symbol > 0 { + char::from_u32(symbol) + } else { + None + } + }, }) } _ => unreachable!(), @@ -73,8 +82,11 @@ impl Runestone { if let Some(etching) = self.etching { varint::encode_to_vec(etching.rune.0, &mut payload); - if etching.divisibility != 0 { - varint::encode_to_vec(etching.divisibility.into(), &mut payload); + let parameters = + u128::from(etching.symbol.unwrap_or_default()) << 6 | u128::from(etching.divisibility); + + if parameters != 0 { + varint::encode_to_vec(parameters, &mut payload); } } @@ -428,6 +440,7 @@ mod tests { etching: Some(Etching { rune: Rune(4), divisibility: 0, + symbol: None, }), })) ); @@ -462,6 +475,7 @@ mod tests { etching: Some(Etching { rune: Rune(4), divisibility: 5, + symbol: None, }), })) ); @@ -496,13 +510,14 @@ mod tests { etching: Some(Etching { rune: Rune(4), divisibility: MAX_DIVISIBILITY, + symbol: None, }), })) ); } #[test] - fn divisibility_is_taken_from_lower_six_bits_of_parameter() { + fn divisibility_is_taken_from_bits_five_to_zero() { let payload = payload(&[1, 2, 3, 4, 0b110_0000]); let payload: &PushBytes = payload.as_slice().try_into().unwrap(); @@ -530,6 +545,42 @@ mod tests { etching: Some(Etching { rune: Rune(4), divisibility: 0b10_0000, + symbol: Some(1.into()), + }), + })) + ); + } + + #[test] + fn symbol_is_taken_from_bits_thirty_seven_to_six() { + let payload = payload(&[1, 2, 3, 4, u128::from('a') << 6]); + + let payload: &PushBytes = payload.as_slice().try_into().unwrap(); + + assert_eq!( + Runestone::decipher(&Transaction { + input: Vec::new(), + output: vec![TxOut { + script_pubkey: script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_slice(b"RUNE_TEST") + .push_slice(payload) + .into_script(), + value: 0 + }], + lock_time: locktime::absolute::LockTime::ZERO, + version: 0, + }), + Ok(Some(Runestone { + edicts: vec![Edict { + id: 1, + amount: 2, + output: 3, + }], + etching: Some(Etching { + rune: Rune(4), + divisibility: 0, + symbol: Some('a'), }), })) ); @@ -640,6 +691,7 @@ mod tests { etching: Some(Etching { rune: Rune(4), divisibility: 5, + symbol: None, }) })) ); @@ -739,6 +791,7 @@ mod tests { Some(Etching { divisibility: 0, rune: Rune(0), + symbol: None, }), 3, ); @@ -748,15 +801,27 @@ mod tests { Some(Etching { divisibility: MAX_DIVISIBILITY, rune: Rune(0), + symbol: None, }), 4, ); + case( + Vec::new(), + Some(Etching { + divisibility: MAX_DIVISIBILITY, + rune: Rune(0), + symbol: Some('$'), + }), + 5, + ); + case( Vec::new(), Some(Etching { divisibility: 0, rune: Rune(u128::max_value()), + symbol: None, }), 21, ); @@ -774,6 +839,7 @@ mod tests { Some(Etching { divisibility: MAX_DIVISIBILITY, rune: Rune(u128::max_value()), + symbol: None, }), 43, ); diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 86b888c4a4..0821998241 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -511,6 +511,8 @@ impl Server { let inscriptions = index.get_inscriptions_on_output(outpoint)?; + let runes = index.get_rune_balances_for_outpoint(outpoint)?; + Ok(if accept_json.0 { Json(OutputJson::new( outpoint, @@ -518,6 +520,10 @@ impl Server { page_config.chain, output, inscriptions, + runes + .into_iter() + .map(|(rune, pile)| (rune, pile.amount)) + .collect(), )) .into_response() } else { @@ -527,6 +533,7 @@ impl Server { list, chain: page_config.chain, output, + runes, } .page(page_config, index.has_sat_index()?) .into_response() @@ -1305,6 +1312,7 @@ mod tests { "--chain", "regtest", "--index-runes-pre-alpha-i-agree-to-get-rekt", + "--enable-json-api", ], &[], ) @@ -3143,6 +3151,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune: Rune(u128::from(21_000_000 * COIN_VALUE)), + symbol: None, }), } .encipher(), @@ -3165,15 +3174,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune: Rune(u128::from(21_000_000 * COIN_VALUE)), supply: u128::max_value(), + symbol: None, } )] ); assert_eq!( - server.index.rune_balances(), + server.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); @@ -3210,6 +3219,7 @@ mod tests { etching: Some(Etching { divisibility: 0, rune, + symbol: Some('$'), }), } .encipher(), @@ -3232,15 +3242,15 @@ mod tests { burned: 0, divisibility: 0, etching: txid, - rarity: Rarity::Uncommon, rune, supply: u128::max_value(), + symbol: Some('$'), } )] ); assert_eq!( - server.index.rune_balances(), + server.index.get_rune_balances(), [(OutPoint { txid, vout: 0 }, vec![(id, u128::max_value())])] ); @@ -3248,19 +3258,19 @@ mod tests { format!("/rune/{rune}"), StatusCode::OK, format!( - ".*Rune NVTDIJZYIPU.* + r".*Rune NVTDIJZYIPU.*

Rune NVTDIJZYIPU

id
2/1
supply
-
340282366920938463463374607431768211455
+
\$340282366920938463463374607431768211455
burned
-
0
+
\$0
divisibility
0
-
rarity
-
uncommon
+
symbol
+
\$
etching
{txid}
inscription
@@ -3322,4 +3332,104 @@ mod tests { ), ) } + + #[test] + fn runes_are_displayed_on_output_page() { + let server = TestServer::new_with_regtest_with_index_runes(); + + server.mine_blocks(1); + + let rune = Rune(u128::from(21_000_000 * COIN_VALUE)); + + server.assert_response_regex(format!("/rune/{rune}"), StatusCode::NOT_FOUND, ".*"); + + let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, Default::default())], + op_return: Some( + Runestone { + edicts: vec![Edict { + id: 0, + amount: u128::max_value(), + output: 0, + }], + etching: Some(Etching { + divisibility: 1, + rune, + symbol: None, + }), + } + .encipher(), + ), + ..Default::default() + }); + + server.mine_blocks(1); + + let id = RuneId { + height: 2, + index: 1, + }; + + assert_eq!( + server.index.runes().unwrap().unwrap(), + [( + id, + RuneEntry { + burned: 0, + divisibility: 1, + etching: txid, + rune, + supply: u128::max_value(), + symbol: None, + } + )] + ); + + let output = OutPoint { txid, vout: 0 }; + + assert_eq!( + server.index.get_rune_balances(), + [(output, vec![(id, u128::max_value())])] + ); + + server.assert_response_regex( + format!("/output/{output}"), + StatusCode::OK, + format!( + ".*Output {output}.*

Output {output}

.* +
runes
+
+ + + + + + + + + +
runebalance
NVTDIJZYIPU34028236692093846346337460743176821145.5
+
+.*" + ), + ); + + assert_eq!( + server.get_json::(format!("/output/{output}")), + OutputJson { + value: 5000000000, + script_pubkey: String::new(), + address: None, + transaction: txid.to_string(), + sat_ranges: None, + inscriptions: Vec::new(), + runes: vec![( + Rune(2100000000000000), + 340282366920938463463374607431768211455 + )] + .into_iter() + .collect(), + } + ); + } } diff --git a/src/templates/output.rs b/src/templates/output.rs index fac57cc1d7..08efac186b 100644 --- a/src/templates/output.rs +++ b/src/templates/output.rs @@ -7,6 +7,7 @@ pub(crate) struct OutputHtml { pub(crate) chain: Chain, pub(crate) output: TxOut, pub(crate) inscriptions: Vec, + pub(crate) runes: Vec<(Rune, Pile)>, } #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -17,6 +18,7 @@ pub struct OutputJson { pub transaction: String, pub sat_ranges: Option>, pub inscriptions: Vec, + pub runes: BTreeMap, } impl OutputJson { @@ -26,9 +28,11 @@ impl OutputJson { chain: Chain, output: TxOut, inscriptions: Vec, + runes: BTreeMap, ) -> Self { Self { value: output.value, + runes: runes.into_iter().collect(), script_pubkey: output.script_pubkey.to_asm_string(), address: chain .address_from_script(&output.script_pubkey) @@ -69,6 +73,7 @@ mod tests { value: 3, script_pubkey: ScriptBuf::new_p2pkh(&PubkeyHash::all_zeros()), }, + runes: Vec::new(), }, "

Output 1{64}:1

@@ -100,6 +105,7 @@ mod tests { value: 1, script_pubkey: script::Builder::new().push_int(0).into_script(), }, + runes: Vec::new(), }, "

Output 1{64}:1

@@ -126,6 +132,7 @@ mod tests { value: 3, script_pubkey: ScriptBuf::new_p2pkh(&PubkeyHash::all_zeros()), }, + runes: Vec::new(), } .to_string(), " @@ -153,6 +160,7 @@ mod tests { value: 3, script_pubkey: ScriptBuf::new_p2pkh(&PubkeyHash::all_zeros()), }, + runes: Vec::new(), }, "

Output 1{64}:1

@@ -167,4 +175,48 @@ mod tests { .unindent() ); } + + #[test] + fn with_runes() { + assert_regex_match!( + OutputHtml { + inscriptions: Vec::new(), + outpoint: outpoint(1), + list: None, + chain: Chain::Mainnet, + output: TxOut { + value: 3, + script_pubkey: ScriptBuf::new_p2pkh(&PubkeyHash::all_zeros()), + }, + runes: vec![( + Rune(0), + Pile { + amount: 11, + divisibility: 1, + symbol: None, + } + )], + }, + " +

Output 1{64}:1

+
+
runes
+
+ + + + + + + + + +
runebalance
A1.1
+
+ .* +
+ " + .unindent() + ); + } } diff --git a/src/templates/rune.rs b/src/templates/rune.rs index 47aba70a81..3efa16fdb3 100644 --- a/src/templates/rune.rs +++ b/src/templates/rune.rs @@ -24,10 +24,10 @@ mod tests { entry: RuneEntry { burned: 123456789123456789, divisibility: 9, - rarity: Rarity::Uncommon, rune: Rune(u128::max_value()), supply: 123456789123456789, etching: Txid::all_zeros(), + symbol: Some('$'), }, id: RuneId { height: 10, @@ -38,18 +38,18 @@ mod tests { index: 0, }), }, - "

Rune BCGDENLQRQWDSLRUGSNLBTMFIJAV

+ r"

Rune BCGDENLQRQWDSLRUGSNLBTMFIJAV

id
10/9
supply
-
123456789.123456789
+
\$123456789.123456789
burned
-
123456789.123456789
+
\$123456789.123456789
divisibility
9
-
rarity
-
uncommon
+
symbol
+
\$
etching
0{64}
inscription
diff --git a/templates/output.html b/templates/output.html index e2b01448db..bb1918d9f6 100644 --- a/templates/output.html +++ b/templates/output.html @@ -7,6 +7,23 @@

Output {{self.outpoint}}

{{Iframe::thumbnail(*inscription)}} %% } +%% } +%% if !self.runes.is_empty() { +
runes
+
+ + + + + +%% for (rune, balance) in &self.runes { + + + + +%% } +
runebalance
{{ rune }}{{ balance }}
+
%% }
value
{{ self.output.value }}
script pubkey
{{ self.output.script_pubkey.to_asm_string() }}
diff --git a/templates/rune.html b/templates/rune.html index 0e232f1244..c6c974af71 100644 --- a/templates/rune.html +++ b/templates/rune.html @@ -3,13 +3,15 @@

Rune {{ self.entry.rune }}

id
{{ self.id }}
supply
-
{{ Pile{ amount: self.entry.supply, divisibility: self.entry.divisibility } }}
+
{{ Pile{ amount: self.entry.supply, divisibility: self.entry.divisibility, symbol: self.entry.symbol } }}
burned
-
{{ Pile{ amount: self.entry.burned, divisibility: self.entry.divisibility } }}
+
{{ Pile{ amount: self.entry.burned, divisibility: self.entry.divisibility, symbol: self.entry.symbol } }}
divisibility
{{ self.entry.divisibility }}
-
rarity
-
{{ self.entry.rarity }}
+%% if let Some(symbol) = self.entry.symbol { +
symbol
+
{{ symbol }}
+%% }
etching
{{ self.entry.etching }}
%% if let Some(inscription) = self.inscription { diff --git a/tests/json_api.rs b/tests/json_api.rs index 662ff733d1..b4bfc8a113 100644 --- a/tests/json_api.rs +++ b/tests/json_api.rs @@ -383,7 +383,8 @@ fn get_output() { InscriptionId { txid, index: 0 }, InscriptionId { txid, index: 1 }, InscriptionId { txid, index: 2 }, - ] + ], + runes: BTreeMap::new(), } ); } diff --git a/tests/lib.rs b/tests/lib.rs index 84b056ac21..15b997084a 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -22,6 +22,7 @@ use { reqwest::{StatusCode, Url}, serde::de::DeserializeOwned, std::{ + collections::BTreeMap, fs, io::Write, net::TcpListener, diff --git a/tests/wallet/send.rs b/tests/wallet/send.rs index 95b70ac74a..8ac346eff8 100644 --- a/tests/wallet/send.rs +++ b/tests/wallet/send.rs @@ -1,4 +1,4 @@ -use {super::*, ord::subcommand::wallet::send::Output}; +use {super::*, ord::subcommand::wallet::send::Output, std::collections::BTreeMap}; #[test] fn inscriptions_can_be_sent() { @@ -262,7 +262,8 @@ fn splitting_merged_inscriptions_is_possible() { txid: reveal_txid, index: 2 }, - ] + ], + runes: BTreeMap::new(), } );