From 2792551eb7de7325c182006a1da9590fa06591d6 Mon Sep 17 00:00:00 2001 From: Christopher Berner Date: Fri, 22 Mar 2024 12:47:13 -0700 Subject: [PATCH 1/4] Update redb to 2.0.0 (#3341) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/index.rs | 4 ++-- src/index/rtx.rs | 4 ++-- src/index/updater.rs | 2 +- src/index/updater/inscription_updater.rs | 28 +++++++++++------------- src/index/updater/rune_updater.rs | 20 ++++++++--------- 7 files changed, 31 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e19b23f73..833b62d737 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2599,9 +2599,9 @@ dependencies = [ [[package]] name = "redb" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72623e6275cd430215b741f41ebda34db93a13ebde253f908b70871c46afc5ba" +checksum = "a1100a056c5dcdd4e5513d5333385223b26ef1bf92f31eb38f407e8c20549256" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 62ebd27696..3f15d37982 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ miniscript = "10.0.0" mp4 = "0.14.0" ord-bitcoincore-rpc = "0.17.2" ordinals = { version = "0.0.4", path = "crates/ordinals" } -redb = "1.5.0" +redb = "2.0.0" regex = "1.6.0" reqwest = { version = "0.11.23", features = ["blocking", "json"] } rss = "2.0.1" diff --git a/src/index.rs b/src/index.rs index 093404a75a..11dc9c6fa3 100644 --- a/src/index.rs +++ b/src/index.rs @@ -26,8 +26,8 @@ use { log::log_enabled, redb::{ Database, DatabaseError, MultimapTable, MultimapTableDefinition, MultimapTableHandle, - ReadOnlyTable, ReadableMultimapTable, ReadableTable, RepairSession, StorageError, Table, - TableDefinition, TableHandle, TableStats, WriteTransaction, + ReadOnlyTable, ReadableMultimapTable, ReadableTable, ReadableTableMetadata, RepairSession, + StorageError, Table, TableDefinition, TableHandle, TableStats, WriteTransaction, }, std::{ collections::HashMap, diff --git a/src/index/rtx.rs b/src/index/rtx.rs index fb02a06453..776494ba5e 100644 --- a/src/index/rtx.rs +++ b/src/index/rtx.rs @@ -1,8 +1,8 @@ use super::*; -pub(crate) struct Rtx<'a>(pub(crate) redb::ReadTransaction<'a>); +pub(crate) struct Rtx(pub(crate) redb::ReadTransaction); -impl Rtx<'_> { +impl Rtx { pub(crate) fn block_height(&self) -> Result> { Ok( self diff --git a/src/index/updater.rs b/src/index/updater.rs index 9e620b170b..1ce3834cbe 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -41,7 +41,7 @@ pub(crate) struct Updater<'index> { } impl<'index> Updater<'index> { - pub(crate) fn update_index<'a>(&'a mut self, mut wtx: WriteTransaction<'a>) -> Result { + pub(crate) fn update_index(&mut self, mut wtx: WriteTransaction) -> Result { let start = Instant::now(); let starting_height = u32::try_from(self.index.client.get_block_count()?).unwrap() + 1; let starting_index_height = self.height; diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 4dd5be7e82..52277a0ae7 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -37,39 +37,37 @@ enum Origin { }, } -pub(super) struct InscriptionUpdater<'a, 'db, 'tx> { +pub(super) struct InscriptionUpdater<'a, 'tx> { pub(super) blessed_inscription_count: u64, pub(super) chain: Chain, - pub(super) content_type_to_count: &'a mut Table<'db, 'tx, Option<&'static [u8]>, u64>, + pub(super) content_type_to_count: &'a mut Table<'tx, Option<&'static [u8]>, u64>, pub(super) cursed_inscription_count: u64, pub(super) event_sender: Option<&'a Sender>, pub(super) flotsam: Vec, pub(super) height: u32, pub(super) home_inscription_count: u64, - pub(super) home_inscriptions: &'a mut Table<'db, 'tx, u32, InscriptionIdValue>, - pub(super) id_to_sequence_number: &'a mut Table<'db, 'tx, InscriptionIdValue, u32>, + pub(super) home_inscriptions: &'a mut Table<'tx, u32, InscriptionIdValue>, + pub(super) id_to_sequence_number: &'a mut Table<'tx, InscriptionIdValue, u32>, pub(super) index_transactions: bool, - pub(super) inscription_number_to_sequence_number: &'a mut Table<'db, 'tx, i32, u32>, + pub(super) inscription_number_to_sequence_number: &'a mut Table<'tx, i32, u32>, pub(super) lost_sats: u64, pub(super) next_sequence_number: u32, - pub(super) outpoint_to_value: &'a mut Table<'db, 'tx, &'static OutPointValue, u64>, + pub(super) outpoint_to_value: &'a mut Table<'tx, &'static OutPointValue, u64>, pub(super) reward: u64, pub(super) transaction_buffer: Vec, - pub(super) transaction_id_to_transaction: - &'a mut Table<'db, 'tx, &'static TxidValue, &'static [u8]>, - pub(super) sat_to_sequence_number: &'a mut MultimapTable<'db, 'tx, u64, u32>, - pub(super) satpoint_to_sequence_number: - &'a mut MultimapTable<'db, 'tx, &'static SatPointValue, u32>, - pub(super) sequence_number_to_children: &'a mut MultimapTable<'db, 'tx, u32, u32>, - pub(super) sequence_number_to_entry: &'a mut Table<'db, 'tx, u32, InscriptionEntryValue>, - pub(super) sequence_number_to_satpoint: &'a mut Table<'db, 'tx, u32, &'static SatPointValue>, + pub(super) transaction_id_to_transaction: &'a mut Table<'tx, &'static TxidValue, &'static [u8]>, + pub(super) sat_to_sequence_number: &'a mut MultimapTable<'tx, u64, u32>, + pub(super) satpoint_to_sequence_number: &'a mut MultimapTable<'tx, &'static SatPointValue, u32>, + pub(super) sequence_number_to_children: &'a mut MultimapTable<'tx, u32, u32>, + pub(super) sequence_number_to_entry: &'a mut Table<'tx, u32, InscriptionEntryValue>, + pub(super) sequence_number_to_satpoint: &'a mut Table<'tx, u32, &'static SatPointValue>, pub(super) timestamp: u32, pub(super) unbound_inscriptions: u64, pub(super) value_cache: &'a mut HashMap, pub(super) value_receiver: &'a mut Receiver, } -impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { +impl<'a, 'tx> InscriptionUpdater<'a, 'tx> { pub(super) fn index_inscriptions( &mut self, tx: &Transaction, diff --git a/src/index/updater/rune_updater.rs b/src/index/updater/rune_updater.rs index 6963fb7193..89c1061b0f 100644 --- a/src/index/updater/rune_updater.rs +++ b/src/index/updater/rune_updater.rs @@ -25,23 +25,23 @@ pub(crate) struct RuneUpdate { pub(crate) supply: u128, } -pub(super) struct RuneUpdater<'a, 'db, 'tx> { +pub(super) struct RuneUpdater<'a, 'tx> { pub(super) height: u32, - pub(super) id_to_entry: &'a mut Table<'db, 'tx, RuneIdValue, RuneEntryValue>, - pub(super) inscription_id_to_sequence_number: &'a Table<'db, 'tx, InscriptionIdValue, u32>, + pub(super) id_to_entry: &'a mut Table<'tx, RuneIdValue, RuneEntryValue>, + pub(super) inscription_id_to_sequence_number: &'a Table<'tx, InscriptionIdValue, u32>, pub(super) minimum: Rune, - pub(super) outpoint_to_balances: &'a mut Table<'db, 'tx, &'static OutPointValue, &'static [u8]>, - pub(super) outpoint_to_output: &'a mut Table<'db, 'tx, &'static OutPointValue, OutputValue>, - pub(super) rune_to_id: &'a mut Table<'db, 'tx, u128, RuneIdValue>, + pub(super) outpoint_to_balances: &'a mut Table<'tx, &'static OutPointValue, &'static [u8]>, + pub(super) outpoint_to_output: &'a mut Table<'tx, &'static OutPointValue, OutputValue>, + pub(super) rune_to_id: &'a mut Table<'tx, u128, RuneIdValue>, pub(super) runes: u64, - pub(super) sequence_number_to_rune_id: &'a mut Table<'db, 'tx, u32, RuneIdValue>, - pub(super) statistic_to_count: &'a mut Table<'db, 'tx, u64, u64>, + pub(super) sequence_number_to_rune_id: &'a mut Table<'tx, u32, RuneIdValue>, + pub(super) statistic_to_count: &'a mut Table<'tx, u64, u64>, pub(super) block_time: u32, - pub(super) transaction_id_to_rune: &'a mut Table<'db, 'tx, &'static TxidValue, u128>, + pub(super) transaction_id_to_rune: &'a mut Table<'tx, &'static TxidValue, u128>, pub(super) updates: HashMap, } -impl<'a, 'db, 'tx> RuneUpdater<'a, 'db, 'tx> { +impl<'a, 'tx> RuneUpdater<'a, 'tx> { pub(super) fn index_runes( &mut self, tx_index: usize, From 9f0790a1f241984ba8574ecb9c413d108bb8868e Mon Sep 17 00:00:00 2001 From: emilcondrea Date: Fri, 22 Mar 2024 21:55:26 +0200 Subject: [PATCH 2/4] Derive Deserialize for Runestone (#3339) --- src/runes/edict.rs | 2 +- src/runes/etching.rs | 2 +- src/runes/mint.rs | 2 +- src/runes/runestone.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/runes/edict.rs b/src/runes/edict.rs index 7bd46de443..53722a53bc 100644 --- a/src/runes/edict.rs +++ b/src/runes/edict.rs @@ -1,6 +1,6 @@ use super::*; -#[derive(Default, Serialize, Debug, PartialEq, Copy, Clone)] +#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] pub struct Edict { pub id: RuneId, pub amount: u128, diff --git a/src/runes/etching.rs b/src/runes/etching.rs index 1f051148d8..3507e72b78 100644 --- a/src/runes/etching.rs +++ b/src/runes/etching.rs @@ -1,6 +1,6 @@ use super::*; -#[derive(Default, Serialize, Debug, PartialEq, Copy, Clone)] +#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] pub struct Etching { pub divisibility: u8, pub mint: Option, diff --git a/src/runes/mint.rs b/src/runes/mint.rs index 1081a20e36..718db0287c 100644 --- a/src/runes/mint.rs +++ b/src/runes/mint.rs @@ -1,6 +1,6 @@ use super::*; -#[derive(Default, Serialize, Debug, PartialEq, Copy, Clone)] +#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Copy, Clone)] pub struct Mint { pub deadline: Option, // unix timestamp pub limit: Option, // claim amount diff --git a/src/runes/runestone.rs b/src/runes/runestone.rs index 95a7b2b21a..72e0b1003e 100644 --- a/src/runes/runestone.rs +++ b/src/runes/runestone.rs @@ -2,7 +2,7 @@ use super::*; const MAX_SPACERS: u32 = 0b00000111_11111111_11111111_11111111; -#[derive(Default, Serialize, Debug, PartialEq)] +#[derive(Default, Serialize, Deserialize, Debug, PartialEq)] pub struct Runestone { pub cenotaph: bool, pub claim: Option, From 5e0cfc79af1aa68b17de90c7ef65a3d66cbf8ebb Mon Sep 17 00:00:00 2001 From: raph Date: Fri, 22 Mar 2024 21:09:51 +0100 Subject: [PATCH 3/4] Fix redundant locking (#3342) --- crates/test-bitcoincore-rpc/src/server.rs | 2 +- src/subcommand/wallet/mint.rs | 10 +-- src/subcommand/wallet/send.rs | 40 +---------- src/wallet.rs | 29 ++++++++ tests/wallet/mint.rs | 87 +++++++++++++++++++++++ 5 files changed, 120 insertions(+), 48 deletions(-) diff --git a/crates/test-bitcoincore-rpc/src/server.rs b/crates/test-bitcoincore-rpc/src/server.rs index c1bbbd34fe..a14ee1c438 100644 --- a/crates/test-bitcoincore-rpc/src/server.rs +++ b/crates/test-bitcoincore-rpc/src/server.rs @@ -827,7 +827,7 @@ impl Api for Server { txid: output.txid, }; assert!(state.utxos.contains_key(&output)); - state.locked.insert(output); + assert!(state.locked.insert(output)); } Ok(true) diff --git a/src/subcommand/wallet/mint.rs b/src/subcommand/wallet/mint.rs index 38f80ab7d3..3c22c6579a 100644 --- a/src/subcommand/wallet/mint.rs +++ b/src/subcommand/wallet/mint.rs @@ -72,15 +72,7 @@ impl Mint { ], }; - let inscriptions = wallet - .inscriptions() - .keys() - .map(|satpoint| satpoint.outpoint) - .collect::>(); - - if !bitcoin_client.lock_unspent(&inscriptions)? { - bail!("failed to lock UTXOs"); - } + wallet.lock_non_cardinal_outputs()?; let unsigned_transaction = fund_raw_transaction(bitcoin_client, self.fee_rate, &unfunded_transaction)?; diff --git a/src/subcommand/wallet/send.rs b/src/subcommand/wallet/send.rs index 4ffe847651..6c5d40b926 100644 --- a/src/subcommand/wallet/send.rs +++ b/src/subcommand/wallet/send.rs @@ -127,43 +127,13 @@ impl Send { }))) } - fn lock_non_cardinal_outputs( - bitcoin_client: &Client, - inscriptions: &BTreeMap>, - runic_outputs: &BTreeSet, - unspent_outputs: &BTreeMap, - ) -> Result { - let all_inscription_outputs = inscriptions - .keys() - .map(|satpoint| satpoint.outpoint) - .collect::>(); - - let locked_outputs = unspent_outputs - .keys() - .filter(|utxo| all_inscription_outputs.contains(utxo)) - .chain(runic_outputs.iter()) - .cloned() - .collect::>(); - - if !bitcoin_client.lock_unspent(&locked_outputs)? { - bail!("failed to lock UTXOs"); - } - - Ok(()) - } - fn create_unsigned_send_amount_transaction( wallet: &Wallet, destination: Address, amount: Amount, fee_rate: FeeRate, ) -> Result { - Self::lock_non_cardinal_outputs( - wallet.bitcoin_client(), - wallet.inscriptions(), - &wallet.get_runic_outputs()?, - wallet.utxos(), - )?; + wallet.lock_non_cardinal_outputs()?; let unfunded_transaction = Transaction { version: 2, @@ -243,17 +213,11 @@ impl Send { "sending runes with `ord send` requires index created with `--index-runes` flag", ); - let unspent_outputs = wallet.utxos(); let inscriptions = wallet.inscriptions(); let runic_outputs = wallet.get_runic_outputs()?; let bitcoin_client = wallet.bitcoin_client(); - Self::lock_non_cardinal_outputs( - bitcoin_client, - inscriptions, - &runic_outputs, - unspent_outputs, - )?; + wallet.lock_non_cardinal_outputs()?; let (id, entry, _parent) = wallet .get_rune(spaced_rune.rune)? diff --git a/src/wallet.rs b/src/wallet.rs index 1791df2c2f..556e195513 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -337,6 +337,35 @@ impl Wallet { &self.locked_utxos } + pub(crate) fn lock_non_cardinal_outputs(&self) -> Result { + let inscriptions = self + .inscriptions() + .keys() + .map(|satpoint| satpoint.outpoint) + .collect::>(); + + let locked = self + .locked_utxos() + .keys() + .cloned() + .collect::>(); + + let outputs = self + .utxos() + .keys() + .filter(|utxo| inscriptions.contains(utxo)) + .chain(self.get_runic_outputs()?.iter()) + .cloned() + .filter(|utxo| !locked.contains(utxo)) + .collect::>(); + + if !self.bitcoin_client().lock_unspent(&outputs)? { + bail!("failed to lock UTXOs"); + } + + Ok(()) + } + pub(crate) fn inscriptions(&self) -> &BTreeMap> { &self.inscriptions } diff --git a/tests/wallet/mint.rs b/tests/wallet/mint.rs index bb47b8b073..7fd9408a05 100644 --- a/tests/wallet/mint.rs +++ b/tests/wallet/mint.rs @@ -221,3 +221,90 @@ fn minting_rune_with_no_rune_index_fails() { .expected_stderr("error: `ord wallet etch` requires index created with `--index-runes` flag\n") .run_and_extract_stdout(); } + +#[test] +fn minting_rune_and_then_sending_works() { + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() + .network(Network::Regtest) + .build(); + + let ord_rpc_server = + TestServer::spawn_with_server_args(&bitcoin_rpc_server, &["--index-runes", "--regtest"], &[]); + + bitcoin_rpc_server.mine_blocks(1); + + create_wallet(&bitcoin_rpc_server, &ord_rpc_server); + + batch( + &bitcoin_rpc_server, + &ord_rpc_server, + Batchfile { + etch: Some(Etch { + divisibility: 0, + rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, + premine: "111".parse().unwrap(), + symbol: '¢', + mint: Some(ord::wallet::inscribe::BatchMint { + term: Some(10), + limit: "21".parse().unwrap(), + deadline: None, + }), + }), + inscriptions: vec![BatchEntry { + file: "inscription.jpeg".into(), + ..Default::default() + }], + ..Default::default() + }, + ); + + let balance = CommandBuilder::new("--chain regtest --index-runes wallet balance") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + assert_eq!( + *balance.runes.unwrap().first_key_value().unwrap().1, + 111_u128 + ); + + let output = CommandBuilder::new(format!( + "--chain regtest --index-runes wallet mint --fee-rate 1 --rune {}", + Rune(RUNE) + )) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + bitcoin_rpc_server.mine_blocks(1); + + let balance = CommandBuilder::new("--chain regtest --index-runes wallet balance") + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); + + assert_eq!( + *balance.runes.unwrap().first_key_value().unwrap().1, + 132_u128 + ); + + pretty_assert_eq!( + output.pile, + Pile { + amount: 21, + divisibility: 0, + symbol: Some('¢'), + } + ); + + CommandBuilder::new(format!( + "--regtest --index-runes wallet send bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 5{} --fee-rate 1", + Rune(RUNE) + )) + .bitcoin_rpc_server(&bitcoin_rpc_server) + .ord_rpc_server(&ord_rpc_server) + .run_and_deserialize_output::(); +} From 5c5042df8eed70403c942a5145b75ab4e00ba353 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 22 Mar 2024 14:26:37 -0700 Subject: [PATCH 4/4] Initial runes review (#3331) --- src/index.rs | 28 +- src/index/entry.rs | 47 +- src/index/updater.rs | 2 +- src/index/updater/rune_updater.rs | 70 ++- src/into_usize.rs | 19 + src/lib.rs | 2 + src/runes.rs | 743 +++++++++++++++++++----------- src/runes/edict.rs | 12 +- src/runes/pile.rs | 28 +- src/runes/rune_id.rs | 119 +++-- src/runes/runestone.rs | 551 ++++++++++++++-------- src/runes/tag.rs | 80 +++- src/runes/varint.rs | 180 ++++---- src/subcommand/runes.rs | 13 +- src/subcommand/server.rs | 27 +- src/subcommand/wallet/send.rs | 2 +- src/templates/output.rs | 2 +- src/templates/rune.rs | 38 +- src/templates/rune_balances.rs | 4 +- src/templates/runes.rs | 6 +- src/test.rs | 2 +- src/wallet/inscribe/batch.rs | 2 +- templates/rune.html | 2 +- templates/runes.html | 2 +- tests/json_api.rs | 24 +- tests/lib.rs | 2 +- tests/runes.rs | 18 +- tests/wallet/send.rs | 4 +- 28 files changed, 1262 insertions(+), 767 deletions(-) create mode 100644 src/into_usize.rs diff --git a/src/index.rs b/src/index.rs index 11dc9c6fa3..67167e83be 100644 --- a/src/index.rs +++ b/src/index.rs @@ -48,7 +48,7 @@ mod updater; #[cfg(test)] pub(crate) mod testing; -const SCHEMA_VERSION: u64 = 21; +const SCHEMA_VERSION: u64 = 22; macro_rules! define_table { ($name:ident, $key:ty, $value:ty) => { @@ -834,7 +834,7 @@ impl Index { .begin_read()? .open_table(RUNE_ID_TO_RUNE_ENTRY)? .get(&id.store())? - .map(|entry| RuneEntry::load(entry.value()).rune), + .map(|entry| RuneEntry::load(entry.value()).spaced_rune.rune), ) } @@ -909,17 +909,13 @@ impl Index { let mut balances = Vec::new(); let mut i = 0; while i < balances_buffer.len() { - let (id, length) = runes::varint::decode(&balances_buffer[i..]); + let ((id, amount), length) = RuneId::decode_balance(&balances_buffer[i..]).unwrap(); i += length; - let (amount, length) = runes::varint::decode(&balances_buffer[i..]); - 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.spaced_rune(), + entry.spaced_rune, Pile { amount, divisibility: entry.divisibility, @@ -954,8 +950,8 @@ impl Index { for (rune_id, balances) in rune_balances_by_id { let RuneEntry { - rune, divisibility, + spaced_rune, symbol, .. } = RuneEntry::load( @@ -966,7 +962,7 @@ impl Index { ); rune_balances.insert( - rune, + spaced_rune.rune, balances .into_iter() .map(|(outpoint, amount)| { @@ -1002,11 +998,9 @@ impl Index { let mut balances = Vec::new(); let mut i = 0; while i < balances_buffer.len() { - let (id, length) = runes::varint::decode(&balances_buffer[i..]); - i += length; - let (balance, length) = runes::varint::decode(&balances_buffer[i..]); + let ((id, balance), length) = RuneId::decode_balance(&balances_buffer[i..]).unwrap(); i += length; - balances.push((RuneId::try_from(id)?, balance)); + balances.push((id, balance)); } result.push((outpoint, balances)); @@ -1220,7 +1214,7 @@ impl Index { let rune_id_to_rune_entry = rtx.open_table(RUNE_ID_TO_RUNE_ENTRY)?; let entry = rune_id_to_rune_entry.get(&id.value())?.unwrap(); - Ok(Some(RuneEntry::load(entry.value()).spaced_rune())) + Ok(Some(RuneEntry::load(entry.value()).spaced_rune)) } pub(crate) fn get_inscription_ids_by_sat(&self, sat: Sat) -> Result> { @@ -1588,7 +1582,7 @@ impl Index { return Ok(false); } - if usize::try_from(outpoint.vout).unwrap() >= info.vout.len() { + if outpoint.vout.into_usize() >= info.vout.len() { return Ok(false); } @@ -1869,7 +1863,7 @@ impl Index { { let rune_id_to_rune_entry = rtx.open_table(RUNE_ID_TO_RUNE_ENTRY)?; let entry = rune_id_to_rune_entry.get(&rune_id.value())?.unwrap(); - Some(RuneEntry::load(entry.value()).spaced_rune()) + Some(RuneEntry::load(entry.value()).spaced_rune) } else { None }; diff --git a/src/index/entry.rs b/src/index/entry.rs index 33a84dce57..e19f751c6e 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -57,8 +57,7 @@ pub struct RuneEntry { pub mints: u64, pub number: u64, pub premine: u128, - pub rune: Rune, - pub spacers: u32, + pub spaced_rune: SpacedRune, pub supply: u128, pub symbol: Option, pub timestamp: u32, @@ -67,18 +66,18 @@ pub struct RuneEntry { impl RuneEntry { pub fn mintable(&self, block_height: Height, block_time: u32) -> Result { let Some(mint) = self.mint else { - return Err(MintError::Unmintable(self.rune)); + return Err(MintError::Unmintable(self.spaced_rune.rune)); }; if let Some(end) = mint.end { if block_height.0 >= end { - return Err(MintError::End((self.rune, end))); + return Err(MintError::End((self.spaced_rune.rune, end))); } } if let Some(deadline) = mint.deadline { if block_time >= deadline { - return Err(MintError::Deadline((self.rune, deadline))); + return Err(MintError::Deadline((self.spaced_rune.rune, deadline))); } } @@ -94,8 +93,7 @@ pub(super) type RuneEntryValue = ( u64, // mints u64, // number u128, // premine - u128, // rune - u32, // spacers + (u128, u32), // spaced rune u128, // supply Option, // symbol u32, // timestamp @@ -114,15 +112,6 @@ type MintEntryValue = ( Option, // limit ); -impl RuneEntry { - pub(crate) fn spaced_rune(&self) -> SpacedRune { - SpacedRune { - rune: self.rune, - spacers: self.spacers, - } - } -} - impl Default for RuneEntry { fn default() -> Self { Self { @@ -133,8 +122,7 @@ impl Default for RuneEntry { mints: 0, number: 0, premine: 0, - rune: Rune(0), - spacers: 0, + spaced_rune: SpacedRune::default(), supply: 0, symbol: None, timestamp: 0, @@ -154,8 +142,7 @@ impl Entry for RuneEntry { mints, number, premine, - rune, - spacers, + (rune, spacers), supply, symbol, timestamp, @@ -182,8 +169,10 @@ impl Entry for RuneEntry { mints, number, premine, - rune: Rune(rune), - spacers, + spaced_rune: SpacedRune { + rune: Rune(rune), + spacers, + }, supply, symbol, timestamp, @@ -217,8 +206,7 @@ impl Entry for RuneEntry { self.mints, self.number, self.premine, - self.rune.0, - self.spacers, + (self.spaced_rune.rune.0, self.spaced_rune.spacers), self.supply, self.symbol, self.timestamp, @@ -226,7 +214,7 @@ impl Entry for RuneEntry { } } -pub(super) type RuneIdValue = (u32, u16); +pub(super) type RuneIdValue = (u32, u32); impl Entry for RuneId { type Value = RuneIdValue; @@ -531,8 +519,10 @@ mod tests { mints: 11, number: 6, premine: 12, - rune: Rune(7), - spacers: 8, + spaced_rune: SpacedRune { + rune: Rune(7), + spacers: 8, + }, supply: 9, symbol: Some('a'), timestamp: 10, @@ -549,8 +539,7 @@ mod tests { 11, 6, 12, - 7, - 8, + (7, 8), 9, Some('a'), 10, diff --git a/src/index/updater.rs b/src/index/updater.rs index 1ce3834cbe..4f5c0f8598 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -608,7 +608,7 @@ impl<'index> Updater<'index> { }; for (i, (tx, txid)) in block.txdata.iter().enumerate() { - rune_updater.index_runes(i, tx, *txid)?; + rune_updater.index_runes(u32::try_from(i).unwrap(), tx, *txid)?; } for (rune_id, update) in rune_updater.updates { diff --git a/src/index/updater/rune_updater.rs b/src/index/updater/rune_updater.rs index 89c1061b0f..af2ac0dc8c 100644 --- a/src/index/updater/rune_updater.rs +++ b/src/index/updater/rune_updater.rs @@ -1,6 +1,6 @@ use { super::*, - crate::runes::{varint, Edict, Runestone}, + crate::runes::{Edict, Runestone}, }; struct Claim { @@ -13,8 +13,7 @@ struct Etched { divisibility: u8, id: RuneId, mint: Option, - rune: Rune, - spacers: u32, + spaced_rune: SpacedRune, symbol: Option, } @@ -42,12 +41,7 @@ pub(super) struct RuneUpdater<'a, 'tx> { } impl<'a, 'tx> RuneUpdater<'a, 'tx> { - pub(super) fn index_runes( - &mut self, - tx_index: usize, - tx: &Transaction, - txid: Txid, - ) -> Result<()> { + pub(super) fn index_runes(&mut self, tx_index: u32, tx: &Transaction, txid: Txid) -> Result<()> { let runestone = Runestone::from_transaction(tx); let mut unallocated = self.unallocated(tx)?; @@ -57,11 +51,9 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { .map(|runestone| runestone.cenotaph) .unwrap_or_default(); - let default_output = runestone.as_ref().and_then(|runestone| { - runestone - .default_output - .and_then(|default| usize::try_from(default).ok()) - }); + let default_output = runestone + .as_ref() + .and_then(|runestone| runestone.default_output); let mut allocated: Vec> = vec![HashMap::new(); tx.output.len()]; @@ -83,12 +75,9 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { if !cenotaph { for Edict { id, amount, output } in runestone.edicts { - let Ok(output) = usize::try_from(output) else { - continue; - }; - // edicts with output values greater than the number of outputs // should never be produced by the edict parser + let output = usize::try_from(output).unwrap(); assert!(output <= tx.output.len()); let (balance, id) = if id == RuneId::default() { @@ -173,6 +162,7 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { // OP_RETURN output if there is no default, or if the default output is // too large if let Some(vout) = default_output + .map(|vout| vout.into_usize()) .filter(|vout| *vout < allocated.len()) .or_else(|| { tx.output @@ -219,8 +209,7 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { balances.sort(); for (id, balance) in balances { - varint::encode_to_vec(id.into(), &mut buffer); - varint::encode_to_vec(balance, &mut buffer); + id.encode_balance(balance, &mut buffer); } self.outpoint_to_balances.insert( @@ -274,13 +263,14 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { divisibility, id, mint, - rune, - spacers, + spaced_rune, symbol, } = etched; - self.rune_to_id.insert(rune.0, id.store())?; - self.transaction_id_to_rune.insert(&txid.store(), rune.0)?; + self.rune_to_id.insert(spaced_rune.rune.0, id.store())?; + self + .transaction_id_to_rune + .insert(&txid.store(), spaced_rune.rune.0)?; let number = self.runes; self.runes += 1; @@ -297,12 +287,11 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { burned: 0, divisibility, etching: txid, - mints: 0, mint: mint.and_then(|mint| (!burn).then_some(mint)), + mints: 0, number, premine, - rune, - spacers, + spaced_rune, supply: premine, symbol, timestamp: self.block_time, @@ -326,7 +315,7 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { fn etched( &mut self, - tx_index: usize, + tx_index: u32, tx: &Transaction, runestone: &Runestone, ) -> Result> { @@ -357,23 +346,17 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { Rune::reserved(reserved_runes.into()) }; - // Nota bene: Because it would require constructing a block - // with 2**16 + 1 transactions, there is no test that checks that - // an eching in a transaction with an out-of-bounds index is - // ignored. - let Ok(index) = u16::try_from(tx_index) else { - return Ok(None); - }; - Ok(Some(Etched { balance: u128::MAX, divisibility: etching.divisibility, id: RuneId { block: self.height, - tx: index, + tx: tx_index, + }, + spaced_rune: SpacedRune { + rune, + spacers: etching.spacers, }, - rune, - spacers: etching.spacers, symbol: etching.symbol, mint: etching.mint.map(|mint| MintEntry { deadline: mint.deadline, @@ -401,6 +384,9 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { let commitment = rune.commitment(); for input in &tx.input { + // extracting a tapscript does not indicate that the input being spent + // was actually a taproot output. this is checked below, when we load the + // output's entry from the database let Some(tapscript) = input.witness.tapscript() else { continue; }; @@ -447,11 +433,9 @@ impl<'a, 'tx> RuneUpdater<'a, 'tx> { let buffer = guard.value(); let mut i = 0; while i < buffer.len() { - let (id, len) = varint::decode(&buffer[i..]); - i += len; - let (balance, len) = varint::decode(&buffer[i..]); + let ((id, balance), len) = RuneId::decode_balance(&buffer[i..]).unwrap(); i += len; - *unallocated.entry(id.try_into().unwrap()).or_default() += balance; + *unallocated.entry(id).or_default() += balance; } } } diff --git a/src/into_usize.rs b/src/into_usize.rs new file mode 100644 index 0000000000..5d773b9515 --- /dev/null +++ b/src/into_usize.rs @@ -0,0 +1,19 @@ +pub(crate) trait IntoUsize { + fn into_usize(self) -> usize; +} + +impl IntoUsize for u32 { + fn into_usize(self) -> usize { + self.try_into().unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn into_usize() { + u32::MAX.into_usize(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 6068dcd9db..ad4903775b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ use { media::{self, ImageRendering, Media}, teleburn, Charm, ParsedEnvelope, }, + into_usize::IntoUsize, representation::Representation, runes::Etching, settings::Settings, @@ -113,6 +114,7 @@ mod decimal; mod fee_rate; pub mod index; mod inscriptions; +mod into_usize; mod object; pub mod options; pub mod outgoing; diff --git a/src/runes.rs b/src/runes.rs index 8c94d867ea..9e5cb56974 100644 --- a/src/runes.rs +++ b/src/runes.rs @@ -118,7 +118,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, timestamp: id.block, ..Default::default() }, @@ -152,7 +155,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -219,7 +225,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(minimum), + spaced_rune: SpacedRune { + rune: Rune(minimum), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -279,7 +288,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RESERVED - 1), + spaced_rune: SpacedRune { + rune: Rune(RESERVED - 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -327,7 +339,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RESERVED), + spaced_rune: SpacedRune { + rune: Rune(RESERVED), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: 2, @@ -375,7 +390,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RESERVED), + spaced_rune: SpacedRune { + rune: Rune(RESERVED), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: 2, @@ -386,7 +404,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RESERVED + 1), + spaced_rune: SpacedRune { + rune: Rune(RESERVED + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: 4, @@ -441,7 +462,10 @@ mod tests { [( id, RuneEntry { - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, etching: txid, divisibility: 1, premine: u128::MAX, @@ -486,7 +510,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -529,7 +556,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, symbol: None, @@ -568,7 +598,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 100, supply: 100, timestamp: id.block, @@ -612,7 +645,10 @@ mod tests { RuneEntry { burned: 100, etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 200, supply: 200, timestamp: id.block, @@ -655,7 +691,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 0, timestamp: id.block, @@ -691,7 +730,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -730,7 +772,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -774,7 +819,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, timestamp: id.block, ..Default::default() }, @@ -822,8 +870,10 @@ mod tests { mints: 0, number: 0, premine: 0, - rune: Rune(RUNE), - spacers: 1, + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 1, + }, supply: 0, symbol: Some('$'), timestamp: id.block, @@ -866,7 +916,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RESERVED), + spaced_rune: SpacedRune { + rune: Rune(RESERVED), + spacers: 0, + }, timestamp: 2, ..Default::default() }, @@ -900,7 +953,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -936,7 +992,10 @@ mod tests { RuneEntry { burned: u128::MAX, etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -972,7 +1031,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1001,7 +1063,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1043,7 +1108,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1073,7 +1141,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1110,7 +1181,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1146,7 +1220,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1163,84 +1240,6 @@ mod tests { ); } - #[test] - fn unallocated_runes_are_assigned_to_first_non_op_return_output_if_default_is_too_large() { - let context = Context::builder().arg("--index-runes").build(); - - let (txid0, id) = context.etch( - Runestone { - edicts: vec![Edict { - id: RuneId::default(), - amount: u128::MAX, - output: 0, - }], - etching: Some(Etching { - rune: Some(Rune(RUNE)), - ..Default::default() - }), - ..Default::default() - }, - 1, - ); - - context.assert_runes( - [( - id, - RuneEntry { - etching: txid0, - rune: Rune(RUNE), - premine: u128::MAX, - supply: u128::MAX, - timestamp: id.block, - ..Default::default() - }, - )], - [( - OutPoint { - txid: txid0, - vout: 0, - }, - vec![(id, u128::MAX)], - )], - ); - - let txid1 = context.rpc_server.broadcast_tx(TransactionTemplate { - inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())], - outputs: 2, - op_return: Some( - Runestone { - default_output: Some(3), - ..Default::default() - } - .encipher(), - ), - ..Default::default() - }); - - context.mine_blocks(1); - - context.assert_runes( - [( - id, - RuneEntry { - etching: txid0, - rune: Rune(RUNE), - premine: u128::MAX, - supply: u128::MAX, - timestamp: id.block, - ..Default::default() - }, - )], - [( - OutPoint { - txid: txid1, - vout: 0, - }, - vec![(id, u128::MAX)], - )], - ); - } - #[test] fn unallocated_runes_are_burned_if_default_output_is_op_return() { let context = Context::builder().arg("--index-runes").build(); @@ -1266,7 +1265,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1302,7 +1304,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, burned: u128::MAX, @@ -1340,7 +1345,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1369,7 +1377,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1411,7 +1422,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1444,7 +1458,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -1480,7 +1497,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1518,7 +1538,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1529,7 +1552,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -1572,7 +1598,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1583,7 +1612,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -1627,7 +1659,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1665,7 +1700,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1676,7 +1714,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -1719,7 +1760,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1730,7 +1774,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -1780,7 +1827,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1791,7 +1841,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -1844,7 +1897,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1882,7 +1938,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1893,7 +1952,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -1954,7 +2016,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -1965,7 +2030,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -2010,7 +2078,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2044,7 +2115,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2097,7 +2171,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -2108,7 +2185,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -2163,7 +2243,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2209,7 +2292,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2251,7 +2337,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -2289,7 +2378,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -2300,7 +2392,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -2358,7 +2453,10 @@ mod tests { id0, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id0.block, @@ -2369,7 +2467,10 @@ mod tests { id1, RuneEntry { etching: txid1, - rune: Rune(RUNE + 1), + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id1.block, @@ -2422,7 +2523,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX / 2, supply: u128::MAX / 2, timestamp: id.block, @@ -2461,7 +2565,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX / 2, supply: u128::MAX / 2, timestamp: id.block, @@ -2504,7 +2611,10 @@ mod tests { RuneEntry { burned: u128::MAX, etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2540,7 +2650,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2583,7 +2696,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2619,7 +2735,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2667,7 +2786,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2727,7 +2849,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2768,7 +2893,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 4000, supply: 4000, timestamp: id.block, @@ -2816,7 +2944,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2863,7 +2994,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2907,7 +3041,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2947,7 +3084,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2998,7 +3138,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3045,7 +3188,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3096,7 +3242,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3143,7 +3292,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3194,7 +3346,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3234,7 +3389,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3285,7 +3443,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3332,7 +3493,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3383,7 +3547,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3430,7 +3597,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3496,7 +3666,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, symbol: Some('$'), @@ -3533,7 +3706,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3569,7 +3745,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3609,7 +3788,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3657,7 +3839,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, timestamp: id.block, mints: 0, mint: Some(MintEntry { @@ -3694,7 +3879,10 @@ mod tests { ..Default::default() }), mints: 1, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 1000, timestamp: id.block, @@ -3735,7 +3923,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, timestamp: id.block, premine: 0, supply: 0, @@ -3779,7 +3970,10 @@ mod tests { ..Default::default() }), mints: 1, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 1000, timestamp: id.block, @@ -3825,7 +4019,10 @@ mod tests { ..Default::default() }), mints: 2, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 2000, timestamp: id.block, @@ -3882,7 +4079,10 @@ mod tests { ..Default::default() }), mints: 3, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 3000, timestamp: id.block, @@ -3933,7 +4133,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: Some(1000), end: Some(id.block + 2), @@ -3970,7 +4173,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: Some(1000), end: Some(id.block + 2), @@ -4016,7 +4222,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 1000, timestamp: id.block, @@ -4069,7 +4278,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: Some(1000), end: Some(id.block), @@ -4104,7 +4316,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, timestamp: id.block, mint: Some(MintEntry { limit: Some(1000), @@ -4147,7 +4362,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, timestamp: 9, mint: Some(MintEntry { deadline: Some(12), @@ -4178,7 +4396,10 @@ mod tests { [( id, RuneEntry { - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 1000, timestamp: 9, @@ -4220,7 +4441,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 1000, timestamp: 9, @@ -4270,7 +4494,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, timestamp: id.block, mint: Some(MintEntry { deadline: Some(11), @@ -4301,7 +4528,10 @@ mod tests { [( id, RuneEntry { - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 1000, timestamp: id.block, @@ -4343,7 +4573,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, premine: 0, supply: 1000, timestamp: id.block, @@ -4391,7 +4624,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, timestamp: id.block, mint: Some(MintEntry { deadline: Some(id.block + 2), @@ -4427,7 +4663,10 @@ mod tests { [( id, RuneEntry { - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, supply: 1000, timestamp: id.block, mints: 1, @@ -4473,7 +4712,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, supply: 1000, timestamp: id.block, mint: Some(MintEntry { @@ -4519,7 +4761,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: Some(1000), ..Default::default() @@ -4556,7 +4801,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, supply: 1000, timestamp: id.block, mint: Some(MintEntry { @@ -4615,7 +4863,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: Some(1000), ..Default::default() @@ -4630,89 +4881,6 @@ mod tests { ); } - #[test] - fn limit_over_max_is_clamped() { - let context = Context::builder().arg("--index-runes").build(); - - let (txid0, id) = context.etch( - Runestone { - etching: Some(Etching { - rune: Some(Rune(RUNE)), - mint: Some(Mint { - limit: Some(MAX_LIMIT + 1), - ..Default::default() - }), - ..Default::default() - }), - ..Default::default() - }, - 1, - ); - - context.assert_runes( - [( - id, - RuneEntry { - etching: txid0, - rune: Rune(RUNE), - timestamp: id.block, - mint: Some(MintEntry { - limit: Some(MAX_LIMIT), - deadline: None, - end: None, - }), - ..Default::default() - }, - )], - [], - ); - - let txid1 = context.rpc_server.broadcast_tx(TransactionTemplate { - inputs: &[(2, 0, 0, Witness::new())], - op_return: Some( - Runestone { - edicts: vec![Edict { - id, - amount: MAX_LIMIT + 1, - output: 0, - }], - claim: Some(id), - ..Default::default() - } - .encipher(), - ), - ..Default::default() - }); - - context.mine_blocks(1); - - context.assert_runes( - [( - id, - RuneEntry { - etching: txid0, - rune: Rune(RUNE), - timestamp: id.block, - mints: 1, - supply: MAX_LIMIT, - mint: Some(MintEntry { - limit: Some(MAX_LIMIT), - deadline: None, - end: None, - }), - ..Default::default() - }, - )], - [( - OutPoint { - txid: txid1, - vout: 0, - }, - vec![(id, MAX_LIMIT)], - )], - ); - } - #[test] fn omitted_limit_defaults_to_max_limit() { let context = Context::builder().arg("--index-runes").build(); @@ -4737,7 +4905,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: None, end: Some(id.block + 1), @@ -4780,7 +4951,10 @@ mod tests { rune_id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: Some(1000), ..Default::default() @@ -4845,7 +5019,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: Some(1000), ..Default::default() @@ -4890,7 +5067,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: Some(1000), ..Default::default() @@ -4938,7 +5118,10 @@ mod tests { id, RuneEntry { etching: txid0, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, mint: Some(MintEntry { limit: Some(1000), ..Default::default() @@ -4963,7 +5146,7 @@ mod tests { fn commits_are_not_valid_in_non_taproot_witnesses() { let context = Context::builder().arg("--index-runes").build(); - let block_count = usize::try_from(context.index.block_count().unwrap()).unwrap(); + let block_count = context.index.block_count().unwrap().into_usize(); context.mine_blocks(1); @@ -5023,7 +5206,7 @@ mod tests { fn immature_commits_are_not_valid() { let context = Context::builder().arg("--index-runes").build(); - let block_count = usize::try_from(context.index.block_count().unwrap()).unwrap(); + let block_count = context.index.block_count().unwrap().into_usize(); context.mine_blocks(1); @@ -5083,7 +5266,7 @@ mod tests { fn etchings_are_not_valid_without_commitment() { let context = Context::builder().arg("--index-runes").build(); - let block_count = usize::try_from(context.index.block_count().unwrap()).unwrap(); + let block_count = context.index.block_count().unwrap().into_usize(); context.mine_blocks(1); diff --git a/src/runes/edict.rs b/src/runes/edict.rs index 53722a53bc..acd4f2eeae 100644 --- a/src/runes/edict.rs +++ b/src/runes/edict.rs @@ -4,23 +4,21 @@ use super::*; pub struct Edict { pub id: RuneId, pub amount: u128, - pub output: u128, + pub output: u32, } impl Edict { pub(crate) fn from_integers( tx: &Transaction, - id: u128, + id: RuneId, amount: u128, output: u128, ) -> Option { - let id = RuneId::try_from(id).ok()?; - - if id.block == 0 && id.tx > 0 { + let Ok(output) = u32::try_from(output) else { return None; - } + }; - if output > u128::try_from(tx.output.len()).ok()? { + if output > u32::try_from(tx.output.len()).unwrap() { return None; } diff --git a/src/runes/pile.rs b/src/runes/pile.rs index fbf429bec5..cf5d7b90f3 100644 --- a/src/runes/pile.rs +++ b/src/runes/pile.rs @@ -26,9 +26,7 @@ impl Display for Pile { write!(f, "{whole}.{fractional:0>width$}")?; } - if let Some(symbol) = self.symbol { - write!(f, "\u{00A0}{symbol}")?; - } + write!(f, "\u{A0}{}", self.symbol.unwrap_or('¤'))?; Ok(()) } @@ -47,7 +45,7 @@ mod tests { symbol: None, } .to_string(), - "0" + "0\u{A0}¤" ); assert_eq!( Pile { @@ -56,7 +54,7 @@ mod tests { symbol: None, } .to_string(), - "25" + "25\u{A0}¤" ); assert_eq!( Pile { @@ -65,7 +63,7 @@ mod tests { symbol: None, } .to_string(), - "0" + "0\u{A0}¤" ); assert_eq!( Pile { @@ -74,7 +72,7 @@ mod tests { symbol: None, } .to_string(), - "0.1" + "0.1\u{A0}¤" ); assert_eq!( Pile { @@ -83,7 +81,7 @@ mod tests { symbol: None, } .to_string(), - "0.01" + "0.01\u{A0}¤" ); assert_eq!( Pile { @@ -92,7 +90,7 @@ mod tests { symbol: None, } .to_string(), - "0.1" + "0.1\u{A0}¤" ); assert_eq!( Pile { @@ -101,7 +99,7 @@ mod tests { symbol: None, } .to_string(), - "1.1" + "1.1\u{A0}¤" ); assert_eq!( Pile { @@ -110,7 +108,7 @@ mod tests { symbol: None, } .to_string(), - "1" + "1\u{A0}¤" ); assert_eq!( Pile { @@ -119,7 +117,7 @@ mod tests { symbol: None, } .to_string(), - "1.01" + "1.01\u{A0}¤" ); assert_eq!( Pile { @@ -128,7 +126,7 @@ mod tests { symbol: None, } .to_string(), - "340282366920938463463.374607431768211455" + "340282366920938463463.374607431768211455\u{A0}¤" ); assert_eq!( Pile { @@ -137,7 +135,7 @@ mod tests { symbol: None, } .to_string(), - "3.40282366920938463463374607431768211455" + "3.40282366920938463463374607431768211455\u{A0}¤" ); assert_eq!( Pile { @@ -146,7 +144,7 @@ mod tests { symbol: Some('$'), } .to_string(), - "0\u{00A0}$" + "0\u{A0}$" ); } } diff --git a/src/runes/rune_id.rs b/src/runes/rune_id.rs index 114c292a41..266804bf83 100644 --- a/src/runes/rune_id.rs +++ b/src/runes/rune_id.rs @@ -1,25 +1,64 @@ -use {super::*, std::num::TryFromIntError}; +use super::*; #[derive(Debug, PartialEq, Copy, Clone, Hash, Eq, Ord, PartialOrd, Default)] pub struct RuneId { pub block: u32, - pub tx: u16, + pub tx: u32, } -impl TryFrom for RuneId { - type Error = TryFromIntError; +impl RuneId { + pub(crate) fn new(block: u32, tx: u32) -> Option { + let id = RuneId { block, tx }; - fn try_from(n: u128) -> Result { - Ok(Self { - block: u32::try_from(n >> 16)?, - tx: u16::try_from(n & 0xFFFF).unwrap(), - }) + if id.block == 0 && id.tx > 0 { + return None; + } + + Some(id) + } + + pub(crate) fn delta(self, next: RuneId) -> Option<(u128, u128)> { + let block = next.block.checked_sub(self.block)?; + + let tx = if block == 0 { + next.tx.checked_sub(self.tx)? + } else { + next.tx + }; + + Some((block.into(), tx.into())) + } + + pub(crate) fn next(self: RuneId, block: u128, tx: u128) -> Option { + RuneId::new( + self.block.checked_add(block.try_into().ok()?)?, + if block == 0 { + self.tx.checked_add(tx.try_into().ok()?)? + } else { + tx.try_into().ok()? + }, + ) + } + + pub(crate) fn encode_balance(self, balance: u128, buffer: &mut Vec) { + varint::encode_to_vec(self.block.into(), buffer); + varint::encode_to_vec(self.tx.into(), buffer); + varint::encode_to_vec(balance, buffer); } -} -impl From for u128 { - fn from(id: RuneId) -> Self { - u128::from(id.block) << 16 | u128::from(id.tx) + pub(crate) fn decode_balance(buffer: &[u8]) -> Option<((RuneId, u128), usize)> { + let mut len = 0; + let (block, block_len) = varint::decode(&buffer[len..])?; + len += block_len; + let (tx, tx_len) = varint::decode(&buffer[len..])?; + len += tx_len; + let id = RuneId { + block: block.try_into().ok()?, + tx: tx.try_into().ok()?, + }; + let (balance, balance_len) = varint::decode(&buffer[len..])?; + len += balance_len; + Some(((id, balance), len)) } } @@ -67,11 +106,46 @@ mod tests { use super::*; #[test] - fn rune_id_to_128() { + fn delta() { + let mut expected = [ + RuneId { block: 4, tx: 2 }, + RuneId { block: 1, tx: 2 }, + RuneId { block: 1, tx: 1 }, + RuneId { block: 3, tx: 1 }, + RuneId { block: 2, tx: 0 }, + ]; + + expected.sort(); + assert_eq!( - 0b11_0000_0000_0000_0001u128, - RuneId { block: 3, tx: 1 }.into() + expected, + [ + RuneId { block: 1, tx: 1 }, + RuneId { block: 1, tx: 2 }, + RuneId { block: 2, tx: 0 }, + RuneId { block: 3, tx: 1 }, + RuneId { block: 4, tx: 2 }, + ] ); + + let mut previous = RuneId::default(); + let mut deltas = Vec::new(); + for id in expected { + deltas.push(previous.delta(id).unwrap()); + previous = id; + } + + assert_eq!(deltas, [(1, 1), (0, 1), (1, 0), (1, 1), (1, 2)]); + + let mut previous = RuneId::default(); + let mut actual = Vec::new(); + for (block, tx) in deltas { + let next = previous.next(block, tx).unwrap(); + actual.push(next); + previous = next; + } + + assert_eq!(actual, expected); } #[test] @@ -89,19 +163,6 @@ mod tests { assert_eq!("1:2".parse::().unwrap(), RuneId { block: 1, tx: 2 }); } - #[test] - fn try_from() { - assert_eq!( - RuneId::try_from(0x060504030201).unwrap(), - RuneId { - block: 0x06050403, - tx: 0x0201 - } - ); - - assert!(RuneId::try_from(0x07060504030201).is_err()); - } - #[test] fn serde() { let rune_id = RuneId { block: 1, tx: 2 }; diff --git a/src/runes/runestone.rs b/src/runes/runestone.rs index 72e0b1003e..48590094ae 100644 --- a/src/runes/runestone.rs +++ b/src/runes/runestone.rs @@ -14,37 +14,53 @@ pub struct Runestone { struct Message { cenotaph: bool, edicts: Vec, - fields: HashMap, + fields: HashMap>, +} + +enum Payload { + Valid(Vec), + Invalid, } impl Message { fn from_integers(tx: &Transaction, payload: &[u128]) -> Self { let mut edicts = Vec::new(); - let mut fields = HashMap::new(); + let mut fields = HashMap::>::new(); let mut cenotaph = false; for i in (0..payload.len()).step_by(2) { let tag = payload[i]; if Tag::Body == tag { - let mut id = 0u128; - for chunk in payload[i + 1..].chunks_exact(3) { - id = id.saturating_add(chunk[0]); - - if let Some(edict) = Edict::from_integers(tx, id, chunk[1], chunk[2]) { - edicts.push(edict); - } else { + let mut id = RuneId::default(); + for chunk in payload[i + 1..].chunks(4) { + if chunk.len() != 4 { cenotaph = true; + break; } + + let Some(next) = id.next(chunk[0], chunk[1]) else { + cenotaph = true; + break; + }; + + let Some(edict) = Edict::from_integers(tx, next, chunk[2], chunk[3]) else { + cenotaph = true; + break; + }; + + id = next; + edicts.push(edict); } break; } let Some(&value) = payload.get(i + 1) else { + cenotaph = true; break; }; - fields.entry(tag).or_insert(value); + fields.entry(tag).or_default().push_back(value); } Self { @@ -61,11 +77,23 @@ impl Runestone { } fn decipher(transaction: &Transaction) -> Result, script::Error> { - let Some(payload) = Runestone::payload(transaction)? else { - return Ok(None); + let payload = match Runestone::payload(transaction)? { + Some(Payload::Valid(payload)) => payload, + Some(Payload::Invalid) => { + return Ok(Some(Self { + cenotaph: true, + ..Default::default() + })) + } + None => return Ok(None), }; - let integers = Runestone::integers(&payload); + let Some(integers) = Runestone::integers(&payload) else { + return Ok(Some(Self { + cenotaph: true, + ..Default::default() + })); + }; let Message { cenotaph, @@ -73,44 +101,44 @@ impl Runestone { mut fields, } = Message::from_integers(transaction, &integers); - let claim = Tag::Claim.take(&mut fields); + let claim = Tag::Claim.take(&mut fields, |[block, tx]| { + RuneId::new(block.try_into().ok()?, tx.try_into().ok()?) + }); - let deadline = Tag::Deadline - .take(&mut fields) - .and_then(|deadline| u32::try_from(deadline).ok()); + let deadline = Tag::Deadline.take(&mut fields, |[deadline]| u32::try_from(deadline).ok()); - let default_output = Tag::DefaultOutput - .take(&mut fields) - .and_then(|default| u32::try_from(default).ok()); + let default_output = Tag::DefaultOutput.take(&mut fields, |[default_output]| { + let default_output = u32::try_from(default_output).ok()?; + (default_output.into_usize() < transaction.output.len()).then_some(default_output) + }); let divisibility = Tag::Divisibility - .take(&mut fields) - .and_then(|divisibility| u8::try_from(divisibility).ok()) - .and_then(|divisibility| (divisibility <= MAX_DIVISIBILITY).then_some(divisibility)) + .take(&mut fields, |[divisibility]| { + let divisibility = u8::try_from(divisibility).ok()?; + (divisibility <= MAX_DIVISIBILITY).then_some(divisibility) + }) .unwrap_or_default(); - let limit = Tag::Limit - .take(&mut fields) - .map(|limit| limit.min(MAX_LIMIT)); + let limit = Tag::Limit.take(&mut fields, |[limit]| (limit <= MAX_LIMIT).then_some(limit)); - let rune = Tag::Rune.take(&mut fields).map(Rune); + let rune = Tag::Rune.take(&mut fields, |[rune]| Some(Rune(rune))); let spacers = Tag::Spacers - .take(&mut fields) - .and_then(|spacers| u32::try_from(spacers).ok()) - .and_then(|spacers| (spacers <= MAX_SPACERS).then_some(spacers)) + .take(&mut fields, |[spacers]| { + let spacers = u32::try_from(spacers).ok()?; + (spacers <= MAX_SPACERS).then_some(spacers) + }) .unwrap_or_default(); - let symbol = Tag::Symbol - .take(&mut fields) - .and_then(|symbol| u32::try_from(symbol).ok()) - .and_then(char::from_u32); + let symbol = Tag::Symbol.take(&mut fields, |[symbol]| { + char::from_u32(u32::try_from(symbol).ok()?) + }); - let term = Tag::Term - .take(&mut fields) - .and_then(|term| u32::try_from(term).ok()); + let term = Tag::Term.take(&mut fields, |[term]| u32::try_from(term).ok()); - let mut flags = Tag::Flags.take(&mut fields).unwrap_or_default(); + let mut flags = Tag::Flags + .take(&mut fields, |[flags]| Some(flags)) + .unwrap_or_default(); let etch = Flag::Etch.take(&mut flags); @@ -134,7 +162,7 @@ impl Runestone { Ok(Some(Self { cenotaph: cenotaph || flags != 0 || fields.keys().any(|tag| tag % 2 == 0), - claim: claim.and_then(|claim| claim.try_into().ok()), + claim, default_output, edicts, etching, @@ -152,49 +180,49 @@ impl Runestone { Flag::Mint.set(&mut flags); } - Tag::Flags.encode(flags, &mut payload); + Tag::Flags.encode([flags], &mut payload); if let Some(rune) = etching.rune { - Tag::Rune.encode(rune.0, &mut payload); + Tag::Rune.encode([rune.0], &mut payload); } if etching.divisibility != 0 { - Tag::Divisibility.encode(etching.divisibility.into(), &mut payload); + Tag::Divisibility.encode([etching.divisibility.into()], &mut payload); } if etching.spacers != 0 { - Tag::Spacers.encode(etching.spacers.into(), &mut payload); + Tag::Spacers.encode([etching.spacers.into()], &mut payload); } if let Some(symbol) = etching.symbol { - Tag::Symbol.encode(symbol.into(), &mut payload); + Tag::Symbol.encode([symbol.into()], &mut payload); } if let Some(mint) = etching.mint { if let Some(deadline) = mint.deadline { - Tag::Deadline.encode(deadline.into(), &mut payload); + Tag::Deadline.encode([deadline.into()], &mut payload); } if let Some(limit) = mint.limit { - Tag::Limit.encode(limit, &mut payload); + Tag::Limit.encode([limit], &mut payload); } if let Some(term) = mint.term { - Tag::Term.encode(term.into(), &mut payload); + Tag::Term.encode([term.into()], &mut payload); } } } - if let Some(claim) = self.claim { - Tag::Claim.encode(claim.into(), &mut payload); + if let Some(RuneId { block, tx }) = self.claim { + Tag::Claim.encode([block.into(), tx.into()], &mut payload); } if let Some(default_output) = self.default_output { - Tag::DefaultOutput.encode(default_output.into(), &mut payload); + Tag::DefaultOutput.encode([default_output.into()], &mut payload); } if self.cenotaph { - Tag::Cenotaph.encode(0, &mut payload); + Tag::Cenotaph.encode([0], &mut payload); } if !self.edicts.is_empty() { @@ -203,13 +231,14 @@ impl Runestone { let mut edicts = self.edicts.clone(); edicts.sort_by_key(|edict| edict.id); - let mut id = 0; + let mut previous = RuneId::default(); for edict in edicts { - let next = u128::from(edict.id); - varint::encode_to_vec(next - id, &mut payload); + let (block, tx) = previous.delta(edict.id).unwrap(); + varint::encode_to_vec(block, &mut payload); + varint::encode_to_vec(tx, &mut payload); varint::encode_to_vec(edict.amount, &mut payload); - varint::encode_to_vec(edict.output, &mut payload); - id = next; + varint::encode_to_vec(edict.output.into(), &mut payload); + previous = edict.id; } } @@ -225,43 +254,49 @@ impl Runestone { builder.into_script() } - fn payload(transaction: &Transaction) -> Result>, script::Error> { + fn payload(transaction: &Transaction) -> Result, script::Error> { + // search transaction outputs for payload for output in &transaction.output { let mut instructions = output.script_pubkey.instructions(); + // payload starts with OP_RETURN if instructions.next().transpose()? != Some(Instruction::Op(opcodes::all::OP_RETURN)) { continue; } + // followed by the protocol identifier if instructions.next().transpose()? != Some(Instruction::Op(MAGIC_NUMBER)) { continue; } + // construct the payload by concatinating remaining data pushes let mut payload = Vec::new(); for result in instructions { if let Instruction::PushBytes(push) = result? { payload.extend_from_slice(push.as_bytes()); + } else { + return Ok(Some(Payload::Invalid)); } } - return Ok(Some(payload)); + return Ok(Some(Payload::Valid(payload))); } Ok(None) } - fn integers(payload: &[u8]) -> Vec { + fn integers(payload: &[u8]) -> Option> { let mut integers = Vec::new(); let mut i = 0; while i < payload.len() { - let (integer, length) = varint::decode(&payload[i..]); + let (integer, length) = varint::decode(&payload[i..])?; integers.push(integer); i += length; } - integers + Some(integers) } } @@ -438,39 +473,44 @@ mod tests { } #[test] - fn non_push_opcodes_in_runestone_are_ignored() { + fn outputs_with_non_pushdata_opcodes_are_cenotaph() { assert_eq!( Runestone::decipher(&Transaction { input: Vec::new(), - output: vec![TxOut { - script_pubkey: script::Builder::new() - .push_opcode(opcodes::all::OP_RETURN) - .push_opcode(MAGIC_NUMBER) - .push_opcode(opcodes::all::OP_VERIFY) - .push_slice([0]) - .push_slice::<&script::PushBytes>( - varint::encode(rune_id(1).into()) - .as_slice() - .try_into() - .unwrap() - ) - .push_slice([2, 0]) - .into_script(), - value: 0, - }], + output: vec![ + TxOut { + script_pubkey: script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_opcode(MAGIC_NUMBER) + .push_opcode(opcodes::all::OP_VERIFY) + .push_slice([0]) + .push_slice::<&script::PushBytes>(varint::encode(1).as_slice().try_into().unwrap()) + .push_slice::<&script::PushBytes>(varint::encode(1).as_slice().try_into().unwrap()) + .push_slice([2, 0]) + .into_script(), + value: 0, + }, + TxOut { + script_pubkey: script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_opcode(MAGIC_NUMBER) + .push_slice([0]) + .push_slice::<&script::PushBytes>(varint::encode(1).as_slice().try_into().unwrap()) + .push_slice::<&script::PushBytes>(varint::encode(2).as_slice().try_into().unwrap()) + .push_slice([3, 0]) + .into_script(), + value: 0, + }, + ], lock_time: LockTime::ZERO, version: 2, }) .unwrap() .unwrap(), Runestone { - edicts: vec![Edict { - id: rune_id(1), - amount: 2, - output: 0, - }], + cenotaph: true, ..Default::default() - }, + } ); } @@ -531,7 +571,7 @@ mod tests { #[test] fn deciphering_non_empty_runestone_is_successful() { assert_eq!( - decipher(&[Tag::Body.into(), rune_id(1).into(), 2, 0]), + decipher(&[Tag::Body.into(), 1, 1, 2, 0]), Runestone { edicts: vec![Edict { id: rune_id(1), @@ -550,7 +590,8 @@ mod tests { Tag::Flags.into(), Flag::Etch.mask(), Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0 ]), @@ -568,14 +609,15 @@ mod tests { #[test] fn decipher_etching_with_rune() { - assert_eq!( + pretty_assert_eq!( decipher(&[ Tag::Flags.into(), Flag::Etch.mask(), Tag::Rune.into(), 4, Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0 ]), @@ -596,14 +638,15 @@ mod tests { #[test] fn etch_flag_is_required_to_etch_rune_even_if_mint_is_set() { - assert_eq!( + pretty_assert_eq!( decipher(&[ Tag::Flags.into(), Flag::Mint.mask(), Tag::Term.into(), 4, Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0 ]), @@ -620,14 +663,15 @@ mod tests { #[test] fn decipher_etching_with_term() { - assert_eq!( + pretty_assert_eq!( decipher(&[ Tag::Flags.into(), Flag::Etch.mask() | Flag::Mint.mask(), Tag::Term.into(), 4, Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0 ]), @@ -658,7 +702,8 @@ mod tests { Tag::Limit.into(), 4, Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0 ]), @@ -681,8 +726,33 @@ mod tests { } #[test] - fn duplicate_tags_are_ignored() { - assert_eq!( + fn invalid_varint_produces_cenotaph() { + pretty_assert_eq!( + Runestone::decipher(&Transaction { + input: Vec::new(), + output: vec![TxOut { + script_pubkey: script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_opcode(MAGIC_NUMBER) + .push_slice([128]) + .into_script(), + value: 0, + }], + lock_time: LockTime::ZERO, + version: 2, + }) + .unwrap() + .unwrap(), + Runestone { + cenotaph: true, + ..Default::default() + } + ); + } + + #[test] + fn duplicate_even_tags_produce_cenotaph() { + pretty_assert_eq!( decipher(&[ Tag::Flags.into(), Flag::Etch.mask(), @@ -691,7 +761,8 @@ mod tests { Tag::Rune.into(), 5, Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0, ]), @@ -705,22 +776,48 @@ mod tests { rune: Some(Rune(4)), ..Default::default() }), + cenotaph: true, ..Default::default() } ); } #[test] - fn unrecognized_odd_tag_is_ignored() { - assert_eq!( + fn duplicate_odd_tags_are_ignored() { + pretty_assert_eq!( decipher(&[ - Tag::Nop.into(), - 100, + Tag::Flags.into(), + Flag::Etch.mask(), + Tag::Divisibility.into(), + 4, + Tag::Divisibility.into(), + 5, Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, - 0 + 0, ]), + Runestone { + edicts: vec![Edict { + id: rune_id(1), + amount: 2, + output: 0, + }], + etching: Some(Etching { + rune: None, + divisibility: 4, + ..Default::default() + }), + ..Default::default() + } + ); + } + + #[test] + fn unrecognized_odd_tag_is_ignored() { + assert_eq!( + decipher(&[Tag::Nop.into(), 100, Tag::Body.into(), 1, 1, 2, 0]), Runestone { edicts: vec![Edict { id: rune_id(1), @@ -735,14 +832,7 @@ mod tests { #[test] fn runestone_with_unrecognized_even_tag_is_cenotaph() { assert_eq!( - decipher(&[ - Tag::Cenotaph.into(), - 0, - Tag::Body.into(), - rune_id(1).into(), - 2, - 0 - ]), + decipher(&[Tag::Cenotaph.into(), 0, Tag::Body.into(), 1, 1, 2, 0]), Runestone { edicts: vec![Edict { id: rune_id(1), @@ -762,7 +852,8 @@ mod tests { Tag::Flags.into(), Flag::Cenotaph.mask(), Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0 ]), @@ -781,7 +872,7 @@ mod tests { #[test] fn runestone_with_edict_id_with_zero_block_and_nonzero_tx_is_cenotaph() { pretty_assert_eq!( - decipher(&[Tag::Body.into(), RuneId { block: 0, tx: 1 }.into(), 2, 0]), + decipher(&[Tag::Body.into(), 0, 1, 2, 0]), Runestone { edicts: Vec::new(), cenotaph: true, @@ -793,7 +884,7 @@ mod tests { #[test] fn runestone_with_output_over_max_is_cenotaph() { pretty_assert_eq!( - decipher(&[Tag::Body.into(), 1, 2, 2]), + decipher(&[Tag::Body.into(), 1, 1, 2, 2]), Runestone { edicts: Vec::new(), cenotaph: true, @@ -803,44 +894,37 @@ mod tests { } #[test] - fn tag_with_no_value_is_ignored() { + fn tag_with_no_value_is_cenotaph() { assert_eq!( decipher(&[Tag::Flags.into(), 1, Tag::Flags.into()]), Runestone { etching: Some(Etching::default()), + cenotaph: true, ..Default::default() }, ); } #[test] - fn additional_integers_in_body_are_ignored() { - pretty_assert_eq!( - decipher(&[ - Tag::Flags.into(), - Flag::Etch.mask(), - Tag::Rune.into(), - 4, - Tag::Body.into(), - rune_id(1).into(), - 2, - 0, - 4, - 5 - ]), - Runestone { - edicts: vec![Edict { - id: rune_id(1), - amount: 2, - output: 0, - }], - etching: Some(Etching { - rune: Some(Rune(4)), + fn trailing_integers_in_body_is_cenotaph() { + let mut integers = vec![Tag::Body.into(), 1, 1, 2, 0]; + + for i in 0..4 { + pretty_assert_eq!( + decipher(&integers), + Runestone { + cenotaph: i > 0, + edicts: vec![Edict { + id: rune_id(1), + amount: 2, + output: 0, + }], ..Default::default() - }), - ..Default::default() - }, - ); + } + ); + + integers.push(0); + } } #[test] @@ -854,7 +938,8 @@ mod tests { Tag::Divisibility.into(), 5, Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0, ]), @@ -885,7 +970,8 @@ mod tests { Tag::Divisibility.into(), (MAX_DIVISIBILITY + 1).into(), Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0, ]), @@ -913,7 +999,8 @@ mod tests { Tag::Symbol.into(), u128::from(u32::from(char::MAX) + 1), Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0, ]), @@ -931,7 +1018,7 @@ mod tests { #[test] fn decipher_etching_with_symbol() { - assert_eq!( + pretty_assert_eq!( decipher(&[ Tag::Flags.into(), Flag::Etch.mask(), @@ -940,7 +1027,8 @@ mod tests { Tag::Symbol.into(), 'a'.into(), Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0, ]), @@ -981,7 +1069,8 @@ mod tests { Tag::Limit.into(), 3, Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0, ]), @@ -1022,7 +1111,8 @@ mod tests { Tag::Limit.into(), 3, Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0, ]), @@ -1050,7 +1140,8 @@ mod tests { Tag::Symbol.into(), 'a'.into(), Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0, ]), @@ -1073,14 +1164,15 @@ mod tests { #[test] fn tag_values_are_not_parsed_as_tags() { - assert_eq!( + pretty_assert_eq!( decipher(&[ Tag::Flags.into(), Flag::Etch.mask(), Tag::Divisibility.into(), Tag::Body.into(), Tag::Body.into(), - rune_id(1).into(), + 1, + 1, 2, 0, ]), @@ -1099,7 +1191,7 @@ mod tests { #[test] fn runestone_may_contain_multiple_edicts() { pretty_assert_eq!( - decipher(&[Tag::Body.into(), rune_id(1).into(), 2, 0, 3, 5, 0]), + decipher(&[Tag::Body.into(), 1, 1, 2, 0, 0, 3, 5, 0]), Runestone { edicts: vec![ Edict { @@ -1119,9 +1211,25 @@ mod tests { } #[test] - fn runestones_with_invalid_rune_ids_are_burn() { + fn runestones_with_invalid_rune_id_blocks_are_cenotaph() { pretty_assert_eq!( - decipher(&[Tag::Body.into(), rune_id(1).into(), 2, 0, u128::MAX, 5, 6]), + decipher(&[Tag::Body.into(), 1, 1, 2, 0, u128::MAX, 1, 0, 0,]), + Runestone { + edicts: vec![Edict { + id: rune_id(1), + amount: 2, + output: 0, + }], + cenotaph: true, + ..Default::default() + }, + ); + } + + #[test] + fn runestones_with_invalid_rune_id_txs_are_cenotaph() { + pretty_assert_eq!( + decipher(&[Tag::Body.into(), 1, 1, 2, 0, 1, u128::MAX, 0, 0,]), Runestone { edicts: vec![Edict { id: rune_id(1), @@ -1168,12 +1276,8 @@ mod tests { .try_into() .unwrap() ) - .push_slice::<&script::PushBytes>( - varint::encode(rune_id(1).into()) - .as_slice() - .try_into() - .unwrap() - ) + .push_slice::<&script::PushBytes>(varint::encode(1).as_slice().try_into().unwrap()) + .push_slice::<&script::PushBytes>(varint::encode(1).as_slice().try_into().unwrap()) .push_slice::<&PushBytes>(varint::encode(2).as_slice().try_into().unwrap()) .push_slice::<&PushBytes>(varint::encode(0).as_slice().try_into().unwrap()) .into_script(), @@ -1199,7 +1303,7 @@ mod tests { #[test] fn runestone_may_be_in_second_output() { - let payload = payload(&[0, rune_id(1).into(), 2, 0]); + let payload = payload(&[0, 1, 1, 2, 0]); let payload: &PushBytes = payload.as_slice().try_into().unwrap(); @@ -1236,7 +1340,7 @@ mod tests { #[test] fn runestone_may_be_after_non_matching_op_return() { - let payload = payload(&[0, rune_id(1).into(), 2, 0]); + let payload = payload(&[0, 1, 1, 2, 0]); let payload: &PushBytes = payload.as_slice().try_into().unwrap(); @@ -1347,7 +1451,7 @@ mod tests { rune: Some(Rune(u128::MAX)), ..Default::default() }), - 31, + 32, ); case( @@ -1361,7 +1465,7 @@ mod tests { rune: Some(Rune(u128::MAX)), ..Default::default() }), - 49, + 50, ); case( @@ -1369,12 +1473,12 @@ mod tests { amount: 0, id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }], None, - 12, + 14, ); case( @@ -1382,12 +1486,12 @@ mod tests { amount: u128::MAX, id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }], None, - 30, + 32, ); case( @@ -1396,7 +1500,7 @@ mod tests { amount: u128::MAX, id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }, @@ -1404,13 +1508,13 @@ mod tests { amount: u128::MAX, id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }, ], None, - 51, + 54, ); case( @@ -1419,7 +1523,7 @@ mod tests { amount: u128::MAX, id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }, @@ -1427,7 +1531,7 @@ mod tests { amount: u128::MAX, id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }, @@ -1435,13 +1539,13 @@ mod tests { amount: u128::MAX, id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }, ], None, - 72, + 76, ); case( @@ -1450,14 +1554,14 @@ mod tests { amount: u64::MAX.into(), id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }; 4 ], None, - 57, + 62, ); case( @@ -1466,14 +1570,14 @@ mod tests { amount: u64::MAX.into(), id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }; 5 ], None, - 69, + 75, ); case( @@ -1482,14 +1586,14 @@ mod tests { amount: u64::MAX.into(), id: RuneId { block: 0, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }; 5 ], None, - 66, + 73, ); case( @@ -1498,19 +1602,19 @@ mod tests { amount: 1_000_000_000_000_000_000, id: RuneId { block: 1_000_000, - tx: u16::MAX, + tx: u32::MAX, }, output: 0, }; 5 ], None, - 64, + 70, ); } #[test] - fn etching_with_term_greater_than_maximum_is_ignored() { + fn etching_with_term_greater_than_maximum_is_still_an_etching() { assert_eq!( decipher(&[ Tag::Flags.into(), @@ -1520,6 +1624,7 @@ mod tests { ]), Runestone { etching: Some(Etching::default()), + cenotaph: true, ..Default::default() }, ); @@ -1541,9 +1646,11 @@ mod tests { version: 2, }; - let payload = Runestone::payload(&transaction).unwrap().unwrap(); + let Payload::Valid(payload) = Runestone::payload(&transaction).unwrap().unwrap() else { + panic!("invalid payload") + }; - pretty_assert_eq!(Runestone::integers(&payload), expected); + pretty_assert_eq!(Runestone::integers(&payload).unwrap(), expected); let runestone = { let mut edicts = runestone.edicts; @@ -1587,9 +1694,9 @@ mod tests { output: 1, }, ], - default_output: Some(11), + default_output: Some(0), cenotaph: true, - claim: Some(RuneId::try_from(12).unwrap()), + claim: Some(rune_id(12)), }, &[ Tag::Flags.into(), @@ -1609,15 +1716,19 @@ mod tests { Tag::Term.into(), 5, Tag::Claim.into(), + 1, + Tag::Claim.into(), 12, Tag::DefaultOutput.into(), - 11, + 0, Tag::Cenotaph.into(), 0, Tag::Body.into(), - rune_id(6).into(), + 1, + 6, 5, 1, + 0, 3, 8, 0, @@ -1672,7 +1783,7 @@ mod tests { amount: 0, output: 0 }; - 173 + 129 ], ..Default::default() } @@ -1687,7 +1798,7 @@ mod tests { amount: 0, output: 0 }; - 174 + 130 ], ..Default::default() } @@ -1710,4 +1821,62 @@ mod tests { assert_eq!(MAX_SPACERS, rune.parse::().unwrap().spacers); } + + #[test] + fn edict_output_greater_than_32_max_produces_cenotaph() { + assert!(decipher(&[Tag::Body.into(), 1, 1, 1, u128::from(u32::MAX) + 1]).cenotaph); + } + + #[test] + fn partial_claim_produces_cenotaph() { + assert!(decipher(&[Tag::Claim.into(), 1]).cenotaph); + } + + #[test] + fn invalid_claim_produces_cenotaph() { + assert!(decipher(&[Tag::Claim.into(), 0, Tag::Claim.into(), 1]).cenotaph); + } + + #[test] + fn invalid_deadline_produces_cenotaph() { + assert!(decipher(&[Tag::Deadline.into(), u128::MAX]).cenotaph); + } + + #[test] + fn invalid_default_output_produces_cenotaph() { + assert!(decipher(&[Tag::DefaultOutput.into(), 1]).cenotaph); + assert!(decipher(&[Tag::DefaultOutput.into(), u128::MAX]).cenotaph); + } + + #[test] + fn invalid_divisibility_does_not_produce_cenotaph() { + assert!(!decipher(&[Tag::Divisibility.into(), u128::MAX]).cenotaph); + } + + #[test] + fn invalid_limit_produces_cenotaph() { + assert!(decipher(&[Tag::Limit.into(), u128::MAX]).cenotaph); + assert!(decipher(&[Tag::Limit.into(), u128::from(u64::MAX) + 1]).cenotaph); + } + + #[test] + fn min_and_max_runes_are_not_cenotaphs() { + assert!(!decipher(&[Tag::Rune.into(), 0]).cenotaph); + assert!(!decipher(&[Tag::Rune.into(), u128::MAX]).cenotaph); + } + + #[test] + fn invalid_spacers_does_not_produce_cenotaph() { + assert!(!decipher(&[Tag::Spacers.into(), u128::MAX]).cenotaph); + } + + #[test] + fn invalid_symbol_does_not_produce_cenotaph() { + assert!(!decipher(&[Tag::Symbol.into(), u128::MAX]).cenotaph); + } + + #[test] + fn invalid_term_produces_cenotaph() { + assert!(decipher(&[Tag::Term.into(), u128::MAX]).cenotaph); + } } diff --git a/src/runes/tag.rs b/src/runes/tag.rs index e8ef0c3510..cd22fea51e 100644 --- a/src/runes/tag.rs +++ b/src/runes/tag.rs @@ -21,13 +21,35 @@ pub(super) enum Tag { } impl Tag { - pub(super) fn take(self, fields: &mut HashMap) -> Option { - fields.remove(&self.into()) + pub(super) fn take( + self, + fields: &mut HashMap>, + with: impl Fn([u128; N]) -> Option, + ) -> Option { + let field = fields.get_mut(&self.into())?; + + let mut values: [u128; N] = [0; N]; + + for (i, v) in values.iter_mut().enumerate() { + *v = *field.get(i)?; + } + + let value = with(values)?; + + field.drain(0..N); + + if field.is_empty() { + fields.remove(&self.into()).unwrap(); + } + + Some(value) } - pub(super) fn encode(self, value: u128, payload: &mut Vec) { - varint::encode_to_vec(self.into(), payload); - varint::encode_to_vec(value, payload); + pub(super) fn encode(self, values: [u128; N], payload: &mut Vec) { + for value in values { + varint::encode_to_vec(self.into(), payload); + varint::encode_to_vec(value, payload); + } } } @@ -61,36 +83,70 @@ mod tests { #[test] fn take() { - let mut fields = vec![(2, 3)].into_iter().collect::>(); + let mut fields = vec![(2, vec![3].into_iter().collect())] + .into_iter() + .collect::>>(); + + assert_eq!(Tag::Flags.take(&mut fields, |[_]| None::), None); + + assert!(!fields.is_empty()); - assert_eq!(Tag::Flags.take(&mut fields), Some(3)); + assert_eq!(Tag::Flags.take(&mut fields, |[flags]| Some(flags)), Some(3)); assert!(fields.is_empty()); - assert_eq!(Tag::Flags.take(&mut fields), None); + assert_eq!(Tag::Flags.take(&mut fields, |[flags]| Some(flags)), None); + } + + #[test] + fn take_leaves_unconsumed_values() { + let mut fields = vec![(2, vec![1, 2, 3].into_iter().collect())] + .into_iter() + .collect::>>(); + + assert_eq!(fields[&2].len(), 3); + + assert_eq!(Tag::Flags.take(&mut fields, |[_]| None::), None); + + assert_eq!(fields[&2].len(), 3); + + assert_eq!( + Tag::Flags.take(&mut fields, |[a, b]| Some((a, b))), + Some((1, 2)) + ); + + assert_eq!(fields[&2].len(), 1); + + assert_eq!(Tag::Flags.take(&mut fields, |[a]| Some(a)), Some(3)); + + assert_eq!(fields.get(&2), None); } #[test] fn encode() { let mut payload = Vec::new(); - Tag::Flags.encode(3, &mut payload); + Tag::Flags.encode([3], &mut payload); assert_eq!(payload, [2, 3]); - Tag::Rune.encode(5, &mut payload); + Tag::Rune.encode([5], &mut payload); assert_eq!(payload, [2, 3, 4, 5]); + + Tag::Rune.encode([5, 6], &mut payload); + + assert_eq!(payload, [2, 3, 4, 5, 4, 5, 4, 6]); } #[test] fn burn_and_nop_are_one_byte() { let mut payload = Vec::new(); - Tag::Cenotaph.encode(0, &mut payload); + Tag::Cenotaph.encode([0], &mut payload); assert_eq!(payload.len(), 2); let mut payload = Vec::new(); - Tag::Nop.encode(0, &mut payload); + Tag::Nop.encode([0], &mut payload); assert_eq!(payload.len(), 2); } } diff --git a/src/runes/varint.rs b/src/runes/varint.rs index 438673a836..3c2d8cad9c 100644 --- a/src/runes/varint.rs +++ b/src/runes/varint.rs @@ -1,56 +1,71 @@ -#[cfg(test)] -pub fn encode(n: u128) -> Vec { - let mut v = Vec::new(); - encode_to_vec(n, &mut v); - v -} - pub fn encode_to_vec(mut n: u128, v: &mut Vec) { - let mut out = [0; 19]; - let mut i = 18; - - out[i] = n.to_le_bytes()[0] & 0b0111_1111; - - while n > 0b0111_1111 { - n = n / 128 - 1; - i -= 1; - out[i] = n.to_le_bytes()[0] | 0b1000_0000; + while n >> 7 > 0 { + v.push(n.to_le_bytes()[0] | 0b1000_0000); + n >>= 7; } + v.push(n.to_le_bytes()[0]); +} - v.extend_from_slice(&out[i..]); +pub fn decode(buffer: &[u8]) -> Option<(u128, usize)> { + try_decode(buffer).ok() } -pub fn decode(buffer: &[u8]) -> (u128, usize) { - let mut n = 0; - let mut i = 0; +#[derive(PartialEq, Debug)] +enum Error { + Overlong, + Overflow, + Unterminated, +} - loop { - let b = match buffer.get(i) { - Some(b) => u128::from(*b), - None => return (n, i), - }; +fn try_decode(buffer: &[u8]) -> Result<(u128, usize), Error> { + let mut n = 0u128; - n = n.saturating_mul(128); + for (i, &byte) in buffer.iter().enumerate() { + if i > 18 { + return Err(Error::Overlong); + } + + let value = u128::from(byte) & 0b0111_1111; - if b < 128 { - return (n.saturating_add(b), i + 1); + if i == 18 && value & 0b0111_1100 != 0 { + return Err(Error::Overflow); } - n = n.saturating_add(b - 127); + n |= value << (7 * i); - i += 1; + if byte & 0b1000_0000 == 0 { + return Ok((n, i + 1)); + } } + + Err(Error::Unterminated) +} + +#[cfg(test)] +pub fn encode(n: u128) -> Vec { + let mut v = Vec::new(); + encode_to_vec(n, &mut v); + v } #[cfg(test)] mod tests { use super::*; + #[test] + fn zero_round_trips_successfully() { + let n = 0; + let encoded = encode(n); + let (decoded, length) = try_decode(&encoded).unwrap(); + assert_eq!(decoded, n); + assert_eq!(length, encoded.len()); + } + #[test] fn u128_max_round_trips_successfully() { let n = u128::MAX; let encoded = encode(n); - let (decoded, length) = decode(&encoded); + let (decoded, length) = try_decode(&encoded).unwrap(); assert_eq!(decoded, n); assert_eq!(length, encoded.len()); } @@ -60,7 +75,7 @@ mod tests { for i in 0..128 { let n = 1 << i; let encoded = encode(n); - let (decoded, length) = decode(&encoded); + let (decoded, length) = try_decode(&encoded).unwrap(); assert_eq!(decoded, n); assert_eq!(length, encoded.len()); } @@ -73,72 +88,75 @@ mod tests { for i in 0..129 { n = n << 1 | (i % 2); let encoded = encode(n); - let (decoded, length) = decode(&encoded); + let (decoded, length) = try_decode(&encoded).unwrap(); assert_eq!(decoded, n); assert_eq!(length, encoded.len()); } } #[test] - fn large_varints_saturate_to_maximum() { - assert_eq!( - decode(&[ - 130, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 255, - 0, - ]), - (u128::MAX, 19) - ); + fn varints_may_not_be_longer_than_19_bytes() { + const VALID: [u8; 19] = [ + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 0, + ]; + + const INVALID: [u8; 20] = [ + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 0, + ]; + + assert_eq!(try_decode(&VALID), Ok((0, 19))); + assert_eq!(try_decode(&INVALID), Err(Error::Overlong)); } #[test] - fn truncated_varints_with_large_final_byte_saturate_to_maximum() { + fn varints_may_not_overflow_u128() { assert_eq!( - decode(&[ - 130, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 255, - 255, + try_decode(&[ + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 64, ]), - (u128::MAX, 19) + Err(Error::Overflow) ); - } - - #[test] - fn varints_with_large_final_byte_saturate_to_maximum() { assert_eq!( - decode(&[ - 130, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 255, - 127, + try_decode(&[ + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 32, ]), - (u128::MAX, 19) + Err(Error::Overflow) + ); + assert_eq!( + try_decode(&[ + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 16, + ]), + Err(Error::Overflow) + ); + assert_eq!( + try_decode(&[ + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 8, + ]), + Err(Error::Overflow) + ); + assert_eq!( + try_decode(&[ + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 4, + ]), + Err(Error::Overflow) + ); + assert_eq!( + try_decode(&[ + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 2, + ]), + Ok((2u128.pow(127), 19)) ); } #[test] - fn taproot_annex_format_bip_test_vectors_round_trip_successfully() { - const TEST_VECTORS: &[(u128, &[u8])] = &[ - (0, &[0x00]), - (1, &[0x01]), - (127, &[0x7F]), - (128, &[0x80, 0x00]), - (255, &[0x80, 0x7F]), - (256, &[0x81, 0x00]), - (16383, &[0xFE, 0x7F]), - (16384, &[0xFF, 0x00]), - (16511, &[0xFF, 0x7F]), - (65535, &[0x82, 0xFE, 0x7F]), - (1 << 32, &[0x8E, 0xFE, 0xFE, 0xFF, 0x00]), - ]; - - for (n, encoding) in TEST_VECTORS { - let actual = encode(*n); - assert_eq!(actual, *encoding); - let (actual, length) = decode(encoding); - assert_eq!(actual, *n); - assert_eq!(length, encoding.len()); - } - } - - #[test] - fn varints_may_be_truncated() { - assert_eq!(decode(&[128]), (1, 1)); + fn varints_must_be_terminated() { + assert_eq!(try_decode(&[128]), Err(Error::Unterminated)); } } diff --git a/src/subcommand/runes.rs b/src/subcommand/runes.rs index ed48753193..a162f471d7 100644 --- a/src/subcommand/runes.rs +++ b/src/subcommand/runes.rs @@ -16,12 +16,11 @@ pub struct RuneInfo { pub mints: u64, pub number: u64, pub premine: u128, - pub rune: Rune, - pub spacers: u32, + pub rune: SpacedRune, pub supply: u128, pub symbol: Option, pub timestamp: DateTime, - pub tx: u16, + pub tx: u32, } pub(crate) fn run(settings: Settings) -> SubcommandResult { @@ -49,15 +48,14 @@ pub(crate) fn run(settings: Settings) -> SubcommandResult { mints, number, premine, - rune, - spacers, + spaced_rune, supply, symbol, timestamp, }, )| { ( - rune, + spaced_rune.rune, RuneInfo { block: id.block, burned, @@ -68,8 +66,7 @@ pub(crate) fn run(settings: Settings) -> SubcommandResult { mints, number, premine, - rune, - spacers, + rune: spaced_rune, supply, symbol, timestamp: crate::timestamp(timestamp), diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index c5963bf0e1..4881cbb918 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -2705,7 +2705,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0 + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -2776,7 +2779,7 @@ mod tests { id, RuneEntry { etching: txid, - rune, + spaced_rune: SpacedRune { rune, spacers: 0 }, premine: u128::MAX, supply: u128::MAX, symbol: Some('%'), @@ -2812,11 +2815,11 @@ mod tests {
mint
no
supply
-
340282366920938463463374607431768211455\u{00A0}%
+
340282366920938463463374607431768211455\u{A0}%
premine
-
340282366920938463463374607431768211455\u{00A0}%
+
340282366920938463463374607431768211455\u{A0}%
burned
-
0\u{00A0}%
+
0\u{A0}%
divisibility
0
symbol
@@ -2889,12 +2892,11 @@ mod tests { id, RuneEntry { etching: txid, - rune, + spaced_rune: SpacedRune { rune, spacers: 1 }, premine: u128::MAX, supply: u128::MAX, symbol: Some('%'), timestamp: id.block, - spacers: 1, ..Default::default() } )] @@ -2937,7 +2939,7 @@ mod tests { StatusCode::OK, ".* A•AAAAAAAAAAAA - 340282366920938463463374607431768211455\u{00A0}% + 340282366920938463463374607431768211455\u{A0}% .*", ); } @@ -2980,7 +2982,10 @@ mod tests { id, RuneEntry { etching: txid, - rune: Rune(RUNE), + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0 + }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3042,7 +3047,7 @@ mod tests { RuneEntry { divisibility: 1, etching: txid, - rune, + spaced_rune: SpacedRune { rune, spacers: 0 }, premine: u128::MAX, supply: u128::MAX, timestamp: id.block, @@ -3072,7 +3077,7 @@ mod tests { AAAAAAAAAAAAA - 34028236692093846346337460743176821145.5 + 34028236692093846346337460743176821145.5\u{A0}¤ diff --git a/src/subcommand/wallet/send.rs b/src/subcommand/wallet/send.rs index 6c5d40b926..ca930d8e87 100644 --- a/src/subcommand/wallet/send.rs +++ b/src/subcommand/wallet/send.rs @@ -238,7 +238,7 @@ impl Send { continue; } - let balance = wallet.get_rune_balance_in_output(&output, entry.rune)?; + let balance = wallet.get_rune_balance_in_output(&output, entry.spaced_rune.rune)?; if balance > 0 { input_runes += balance; diff --git a/src/templates/output.rs b/src/templates/output.rs index a929d4b83d..ef13b6e2b8 100644 --- a/src/templates/output.rs +++ b/src/templates/output.rs @@ -207,7 +207,7 @@ mod tests { A•A - 1.1 + 1.1\u{A0}¤ diff --git a/src/templates/rune.rs b/src/templates/rune.rs index 91e62a0c14..56d92a3b44 100644 --- a/src/templates/rune.rs +++ b/src/templates/rune.rs @@ -10,7 +10,7 @@ pub struct RuneHtml { impl PageContent for RuneHtml { fn title(&self) -> String { - format!("Rune {}", self.entry.spaced_rune()) + format!("Rune {}", self.entry.spaced_rune) } } @@ -34,8 +34,10 @@ mod tests { }), number: 25, premine: 123456789, - rune: Rune(u128::MAX), - spacers: 1, + spaced_rune: SpacedRune { + rune: Rune(u128::MAX), + spacers: 1 + }, supply: 123456789123456789, symbol: Some('%'), timestamp: 0, @@ -76,11 +78,11 @@ mod tests {
supply
-
123456789.123456789\u{00A0}%
+
123456789.123456789\u{A0}%
premine
-
0.123456789\u{00A0}%
+
0.123456789\u{A0}%
burned
-
123456789.123456789\u{00A0}%
+
123456789.123456789\u{A0}%
divisibility
9
symbol
@@ -106,8 +108,10 @@ mod tests { mints: 0, number: 25, premine: 0, - rune: Rune(u128::MAX), - spacers: 1, + spaced_rune: SpacedRune { + rune: Rune(u128::MAX), + spacers: 1 + }, supply: 123456789123456789, symbol: Some('%'), timestamp: 0, @@ -131,11 +135,11 @@ mod tests {
mint
no
supply
-
123456789.123456789\u{00A0}%
+
123456789.123456789\u{A0}%
premine
-
0\u{00A0}%
+
0\u{A0}%
burned
-
123456789.123456789\u{00A0}%
+
123456789.123456789\u{A0}%
divisibility
9
symbol
@@ -163,8 +167,10 @@ mod tests { mints: 0, premine: 0, number: 25, - rune: Rune(u128::MAX), - spacers: 1, + spaced_rune: SpacedRune { + rune: Rune(u128::MAX), + spacers: 1 + }, supply: 123456789123456789, symbol: Some('%'), timestamp: 0, @@ -201,11 +207,11 @@ mod tests {
supply
-
123456789.123456789\u{00A0}%
+
123456789.123456789\u{A0}%
premine
-
0\u{00A0}%
+
0\u{A0}%
burned
-
123456789.123456789\u{00A0}%
+
123456789.123456789\u{A0}%
divisibility
9
symbol
diff --git a/src/templates/rune_balances.rs b/src/templates/rune_balances.rs index 343537e39c..5b390a6bc9 100644 --- a/src/templates/rune_balances.rs +++ b/src/templates/rune_balances.rs @@ -73,7 +73,7 @@ mod tests { 1{64}:1 - 1000\u{00A0}\\$ + 1000\u{A0}\\$ @@ -88,7 +88,7 @@ mod tests { 2{64}:2 - 1234567\\.8\u{00A0}¢ + 1234567\\.8\u{A0}¢ diff --git a/src/templates/runes.rs b/src/templates/runes.rs index f6e6591d88..337522db9e 100644 --- a/src/templates/runes.rs +++ b/src/templates/runes.rs @@ -22,8 +22,10 @@ mod tests { entries: vec![( RuneId { block: 0, tx: 0 }, RuneEntry { - rune: Rune(26), - spacers: 1, + spaced_rune: SpacedRune { + rune: Rune(26), + spacers: 1 + }, ..Default::default() } )], diff --git a/src/test.rs b/src/test.rs index 0f82d97f70..c234d9f39e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -131,7 +131,7 @@ pub(crate) fn inscription_id(n: u32) -> InscriptionId { format!("{}i{n}", hex.repeat(64)).parse().unwrap() } -pub(crate) fn rune_id(tx: u16) -> RuneId { +pub(crate) fn rune_id(tx: u32) -> RuneId { RuneId { block: 1, tx } } diff --git a/src/wallet/inscribe/batch.rs b/src/wallet/inscribe/batch.rs index 29c775c587..d2f1bc7d33 100644 --- a/src/wallet/inscribe/batch.rs +++ b/src/wallet/inscribe/batch.rs @@ -446,7 +446,7 @@ impl Batch { edicts.push(Edict { id: RuneId::default(), amount: premine, - output: output.into(), + output, }); vout = Some(output); diff --git a/templates/rune.html b/templates/rune.html index 38532497f8..d990691030 100644 --- a/templates/rune.html +++ b/templates/rune.html @@ -1,4 +1,4 @@ -

{{ self.entry.spaced_rune() }}

+

{{ self.entry.spaced_rune }}

%% if let Some(parent) = self.parent { {{Iframe::main(parent)}} %% } diff --git a/templates/runes.html b/templates/runes.html index c3d582fe6b..d2852d94b7 100644 --- a/templates/runes.html +++ b/templates/runes.html @@ -1,6 +1,6 @@

Runes

diff --git a/tests/json_api.rs b/tests/json_api.rs index e4d7d61637..a12c08c664 100644 --- a/tests/json_api.rs +++ b/tests/json_api.rs @@ -550,8 +550,10 @@ fn get_runes() { mints: 0, number: 0, premine: 1000, - rune: Rune(RUNE), - spacers: 0, + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0 + }, supply: 1000, symbol: Some('¢'), timestamp: 11, @@ -585,8 +587,10 @@ fn get_runes() { mints: 0, number: 0, premine: 1000, - rune: Rune(RUNE), - spacers: 0, + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0 + }, supply: 1000, symbol: Some('¢'), timestamp: 11, @@ -602,8 +606,10 @@ fn get_runes() { mints: 0, number: 1, premine: 1000, - rune: Rune(RUNE + 1), - spacers: 0, + spaced_rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0 + }, supply: 1000, symbol: Some('¢'), timestamp: 19, @@ -619,8 +625,10 @@ fn get_runes() { mints: 0, number: 2, premine: 1000, - rune: Rune(RUNE + 2), - spacers: 0, + spaced_rune: SpacedRune { + rune: Rune(RUNE + 2), + spacers: 0 + }, supply: 1000, symbol: Some('¢'), timestamp: 27, diff --git a/tests/lib.rs b/tests/lib.rs index f98827d12f..fb0c7a96b2 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -360,7 +360,7 @@ fn batch( {location} - {premine}\u{00A0}{symbol} + {premine}\u{A0}{symbol} diff --git a/tests/runes.rs b/tests/runes.rs index cfd179ba4c..d0d074fbf0 100644 --- a/tests/runes.rs +++ b/tests/runes.rs @@ -62,8 +62,10 @@ fn one_rune() { mints: 0, number: 0, premine: 1000, - rune: Rune(RUNE), - spacers: 0, + rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0 + }, supply: 1000, symbol: Some('¢'), timestamp: ord::timestamp(8), @@ -108,8 +110,10 @@ fn two_runes() { mints: 0, number: 0, premine: 1000, - rune: Rune(RUNE), - spacers: 0, + rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0 + }, supply: 1000, symbol: Some('¢'), timestamp: ord::timestamp(8), @@ -128,8 +132,10 @@ fn two_runes() { mints: 0, number: 1, premine: 1000, - rune: Rune(RUNE + 1), - spacers: 0, + rune: SpacedRune { + rune: Rune(RUNE + 1), + spacers: 0 + }, supply: 1000, symbol: Some('¢'), timestamp: ord::timestamp(16), diff --git a/tests/wallet/send.rs b/tests/wallet/send.rs index 3e54507e29..4b6dd95b7f 100644 --- a/tests/wallet/send.rs +++ b/tests/wallet/send.rs @@ -692,7 +692,7 @@ fn sending_rune_with_insufficient_balance_is_an_error() { .bitcoin_rpc_server(&bitcoin_rpc_server) .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) - .expected_stderr("error: insufficient `AAAAAAAAAAAAA` balance, only 1000\u{00A0}¢ in wallet\n") + .expected_stderr("error: insufficient `AAAAAAAAAAAAA` balance, only 1000\u{A0}¢ in wallet\n") .run_and_extract_stdout(); } @@ -1072,7 +1072,7 @@ fn error_messages_use_spaced_runes() { .bitcoin_rpc_server(&bitcoin_rpc_server) .ord_rpc_server(&ord_rpc_server) .expected_exit_code(1) - .expected_stderr("error: insufficient `A•AAAAAAAAAAAA` balance, only 1000\u{00A0}¢ in wallet\n") + .expected_stderr("error: insufficient `A•AAAAAAAAAAAA` balance, only 1000\u{A0}¢ in wallet\n") .run_and_extract_stdout(); CommandBuilder::new("--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1F•OO")