From 9121870ee7c422e94c670d433af48f783e6c7b96 Mon Sep 17 00:00:00 2001 From: onchainguy <1436535+onchainguy-btc@users.noreply.github.com> Date: Tue, 2 Jul 2024 00:04:35 +0200 Subject: [PATCH] Add charm to burned inscriptions (#3836) --- crates/mockcore/src/lib.rs | 2 + crates/mockcore/src/state.rs | 2 +- crates/ordinals/src/charm.rs | 11 +- src/index/updater/inscription_updater.rs | 35 +++++- src/subcommand/server.rs | 150 +++++++++++++++++++++++ 5 files changed, 192 insertions(+), 8 deletions(-) diff --git a/crates/mockcore/src/lib.rs b/crates/mockcore/src/lib.rs index 8e9e6ea83a..219bd79593 100644 --- a/crates/mockcore/src/lib.rs +++ b/crates/mockcore/src/lib.rs @@ -133,6 +133,7 @@ pub struct TransactionTemplate<'a> { pub inputs: &'a [(usize, usize, usize, Witness)], pub op_return: Option, pub op_return_index: Option, + pub op_return_value: Option, pub output_values: &'a [u64], pub outputs: usize, pub p2tr: bool, @@ -180,6 +181,7 @@ impl<'a> Default for TransactionTemplate<'a> { inputs: &[], op_return: None, op_return_index: None, + op_return_value: None, output_values: &[], outputs: 1, p2tr: false, diff --git a/crates/mockcore/src/state.rs b/crates/mockcore/src/state.rs index ff58957074..be3a9dce5e 100644 --- a/crates/mockcore/src/state.rs +++ b/crates/mockcore/src/state.rs @@ -248,7 +248,7 @@ impl State { tx.output.insert( template.op_return_index.unwrap_or(tx.output.len()), TxOut { - value: 0, + value: template.op_return_value.unwrap_or_default(), script_pubkey, }, ); diff --git a/crates/ordinals/src/charm.rs b/crates/ordinals/src/charm.rs index 0331be9306..53e4aae770 100644 --- a/crates/ordinals/src/charm.rs +++ b/crates/ordinals/src/charm.rs @@ -14,10 +14,11 @@ pub enum Charm { Uncommon = 9, Vindicated = 10, Mythic = 11, + Burned = 12, } impl Charm { - pub const ALL: [Self; 12] = [ + pub const ALL: [Self; 13] = [ Self::Coin, Self::Uncommon, Self::Rare, @@ -30,6 +31,7 @@ impl Charm { Self::Unbound, Self::Lost, Self::Vindicated, + Self::Burned, ]; fn flag(self) -> u16 { @@ -50,18 +52,19 @@ impl Charm { pub fn icon(self) -> &'static str { match self { + Self::Burned => "đŸ”Ĩ", Self::Coin => "đŸĒ™", Self::Cursed => "👹", Self::Epic => "đŸĒģ", Self::Legendary => "🌝", Self::Lost => "🤔", Self::Mythic => "🎃", - Self::Nineball => "9ī¸âƒŖ", + Self::Nineball => "\u{39}\u{fe0f}\u{20e3}", Self::Rare => "đŸ§ŋ", Self::Reinscription => "â™ģī¸", Self::Unbound => "🔓", Self::Uncommon => "🌱", - Self::Vindicated => "❤ī¸â€đŸ”Ĩ", + Self::Vindicated => "\u{2764}\u{fe0f}\u{200d}\u{1f525}", } } @@ -79,6 +82,7 @@ impl Display for Charm { f, "{}", match self { + Self::Burned => "burned", Self::Coin => "coin", Self::Cursed => "cursed", Self::Epic => "epic", @@ -101,6 +105,7 @@ impl FromStr for Charm { fn from_str(s: &str) -> Result { Ok(match s { + "burned" => Self::Burned, "coin" => Self::Coin, "cursed" => Self::Cursed, "epic" => Self::Epic, diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index ad55ef1ae2..8668052c24 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -306,7 +306,11 @@ impl<'a, 'tx> InscriptionUpdater<'a, 'tx> { offset: flotsam.offset - output_value, }; - new_locations.push((new_satpoint, inscriptions.next().unwrap())); + new_locations.push(( + new_satpoint, + inscriptions.next().unwrap(), + txout.script_pubkey.is_op_return(), + )); } range_to_vout.insert((output_value, end), vout.try_into().unwrap()); @@ -322,7 +326,7 @@ impl<'a, 'tx> InscriptionUpdater<'a, 'tx> { ); } - for (new_satpoint, mut flotsam) in new_locations.into_iter() { + for (new_satpoint, mut flotsam, op_return) in new_locations.into_iter() { let new_satpoint = match flotsam.origin { Origin::New { pointer: Some(pointer), @@ -344,7 +348,7 @@ impl<'a, 'tx> InscriptionUpdater<'a, 'tx> { _ => new_satpoint, }; - self.update_inscription_location(input_sat_ranges, flotsam, new_satpoint)?; + self.update_inscription_location(input_sat_ranges, flotsam, new_satpoint, op_return)?; } if is_coinbase { @@ -353,7 +357,7 @@ impl<'a, 'tx> InscriptionUpdater<'a, 'tx> { outpoint: OutPoint::null(), offset: self.lost_sats + flotsam.offset - output_value, }; - self.update_inscription_location(input_sat_ranges, flotsam, new_satpoint)?; + self.update_inscription_location(input_sat_ranges, flotsam, new_satpoint, false)?; } self.lost_sats += self.reward - output_value; Ok(()) @@ -391,6 +395,7 @@ impl<'a, 'tx> InscriptionUpdater<'a, 'tx> { input_sat_ranges: Option<&VecDeque<(u64, u64)>>, flotsam: Flotsam, new_satpoint: SatPoint, + op_return: bool, ) -> Result { let inscription_id = flotsam.inscription_id; let (unbound, sequence_number) = match flotsam.origin { @@ -405,6 +410,24 @@ impl<'a, 'tx> InscriptionUpdater<'a, 'tx> { .unwrap() .value(); + if op_return { + let entry = InscriptionEntry::load( + self + .sequence_number_to_entry + .get(&sequence_number)? + .unwrap() + .value(), + ); + + let mut charms = entry.charms; + Charm::Burned.set(&mut charms); + + self.sequence_number_to_entry.insert( + sequence_number, + &InscriptionEntry { charms, ..entry }.store(), + )?; + } + if let Some(sender) = self.event_sender { sender.blocking_send(Event::InscriptionTransferred { block_height: self.height, @@ -464,6 +487,10 @@ impl<'a, 'tx> InscriptionUpdater<'a, 'tx> { charms |= sat.charms(); } + if op_return { + Charm::Burned.set(&mut charms); + } + if new_satpoint.outpoint == OutPoint::null() { Charm::Lost.set(&mut charms); } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index ddccfaf666..ac47d929a8 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -6978,4 +6978,154 @@ next let server = TestServer::builder().build(); server.assert_response("/update", StatusCode::NOT_FOUND, ""); } + + #[test] + fn burned_charm() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + + server.mine_blocks(1); + + let inscription = Inscription { + content_type: Some("text/html".into()), + body: Some("foo".into()), + ..default() + }; + + let txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription.to_witness())], + outputs: 0, + op_return_index: Some(0), + op_return_value: Some(50 * COIN_VALUE), + op_return: Some( + script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .into_script(), + ), + ..default() + }); + + server.mine_blocks(1); + + let id = InscriptionId { txid, index: 0 }; + + pretty_assert_eq!( + server.get_json::(format!("/r/inscription/{id}")), + api::InscriptionRecursive { + charms: vec![Charm::Burned], + content_type: Some("text/html".into()), + content_length: Some(3), + delegate: None, + fee: 0, + height: 2, + id, + number: 0, + output: OutPoint { txid, vout: 0 }, + sat: None, + satpoint: SatPoint { + outpoint: OutPoint { txid, vout: 0 }, + offset: 0 + }, + timestamp: 2, + value: Some(50 * COIN_VALUE), + } + ); + } + + #[test] + fn burned_charm_on_transfer() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + + server.mine_blocks(1); + + let inscription = Inscription { + content_type: Some("text/html".into()), + body: Some("foo".into()), + ..default() + }; + + let create_txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription.to_witness())], + outputs: 1, + ..default() + }); + + server.mine_blocks(1); + + let id = InscriptionId { + txid: create_txid, + index: 0, + }; + + pretty_assert_eq!( + server.get_json::(format!("/r/inscription/{id}")), + api::InscriptionRecursive { + charms: vec![], + content_type: Some("text/html".into()), + content_length: Some(3), + delegate: None, + fee: 0, + height: 2, + id, + number: 0, + output: OutPoint { + txid: create_txid, + vout: 0 + }, + sat: None, + satpoint: SatPoint { + outpoint: OutPoint { + txid: create_txid, + vout: 0 + }, + offset: 0 + }, + timestamp: 2, + value: Some(50 * COIN_VALUE), + } + ); + + let transfer_txid = server.core.broadcast_tx(TransactionTemplate { + inputs: &[(2, 1, 0, Default::default())], + fee: 0, + outputs: 0, + op_return_index: Some(0), + op_return_value: Some(50 * COIN_VALUE), + op_return: Some( + script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .into_script(), + ), + ..default() + }); + + server.mine_blocks(1); + + pretty_assert_eq!( + server.get_json::(format!("/r/inscription/{id}")), + api::InscriptionRecursive { + charms: vec![Charm::Burned], + content_type: Some("text/html".into()), + content_length: Some(3), + delegate: None, + fee: 0, + height: 2, + id, + number: 0, + output: OutPoint { + txid: transfer_txid, + vout: 0 + }, + sat: None, + satpoint: SatPoint { + outpoint: OutPoint { + txid: transfer_txid, + vout: 0 + }, + offset: 0 + }, + timestamp: 2, + value: Some(50 * COIN_VALUE), + } + ); + } }