diff --git a/crates/mockcore/src/state.rs b/crates/mockcore/src/state.rs index 28270d775c..ff58957074 100644 --- a/crates/mockcore/src/state.rs +++ b/crates/mockcore/src/state.rs @@ -254,9 +254,11 @@ impl State { ); } - self.mempool.push(tx.clone()); + let txid = tx.txid(); - tx.txid() + self.mempool.push(tx); + + txid } pub(crate) fn mempool(&self) -> &[Transaction] { diff --git a/crates/ordinals/src/cenotaph.rs b/crates/ordinals/src/cenotaph.rs index d03eda4cd8..c6e3f620ae 100644 --- a/crates/ordinals/src/cenotaph.rs +++ b/crates/ordinals/src/cenotaph.rs @@ -3,32 +3,6 @@ use super::*; #[derive(Serialize, Eq, PartialEq, Deserialize, Debug, Default)] pub struct Cenotaph { pub etching: Option, - pub flaws: u32, + pub flaw: Option, pub mint: Option, } - -impl Cenotaph { - pub fn flaws(&self) -> Vec { - Flaw::ALL - .into_iter() - .filter(|flaw| self.flaws & flaw.flag() != 0) - .collect() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn flaws() { - assert_eq!( - Cenotaph { - flaws: Flaw::Opcode.flag() | Flaw::Varint.flag(), - ..default() - } - .flaws(), - vec![Flaw::Opcode, Flaw::Varint], - ); - } -} diff --git a/crates/ordinals/src/edict.rs b/crates/ordinals/src/edict.rs index 3bc6113884..2d8d35f752 100644 --- a/crates/ordinals/src/edict.rs +++ b/crates/ordinals/src/edict.rs @@ -13,6 +13,8 @@ impl Edict { return None; }; + // note that this allows `output == tx.output.len()`, which means to divide + // amount between all non-OP_RETURN outputs if output > u32::try_from(tx.output.len()).unwrap() { return None; } diff --git a/crates/ordinals/src/flaw.rs b/crates/ordinals/src/flaw.rs index 69a2be27f8..69c152bf0b 100644 --- a/crates/ordinals/src/flaw.rs +++ b/crates/ordinals/src/flaw.rs @@ -1,6 +1,7 @@ use super::*; -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] pub enum Flaw { EdictOutput, EdictRuneId, @@ -14,25 +15,6 @@ pub enum Flaw { Varint, } -impl Flaw { - pub const ALL: [Self; 10] = [ - Self::EdictOutput, - Self::EdictRuneId, - Self::InvalidScript, - Self::Opcode, - Self::SupplyOverflow, - Self::TrailingIntegers, - Self::TruncatedField, - Self::UnrecognizedEvenTag, - Self::UnrecognizedFlag, - Self::Varint, - ]; - - pub fn flag(self) -> u32 { - 1 << (self as u32) - } -} - impl Display for Flaw { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { @@ -49,9 +31,3 @@ impl Display for Flaw { } } } - -impl From for u32 { - fn from(cenotaph: Flaw) -> Self { - cenotaph.flag() - } -} diff --git a/crates/ordinals/src/pile.rs b/crates/ordinals/src/pile.rs index d93cba20ba..4bbba23b08 100644 --- a/crates/ordinals/src/pile.rs +++ b/crates/ordinals/src/pile.rs @@ -9,7 +9,7 @@ pub struct Pile { impl Display for Pile { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let cutoff = 10u128.pow(self.divisibility.into()); + let cutoff = 10u128.checked_pow(self.divisibility.into()).unwrap(); let whole = self.amount / cutoff; let mut fractional = self.amount % cutoff; diff --git a/crates/ordinals/src/rune.rs b/crates/ordinals/src/rune.rs index d3cd3c49d2..90e26865cf 100644 --- a/crates/ordinals/src/rune.rs +++ b/crates/ordinals/src/rune.rs @@ -143,7 +143,7 @@ impl FromStr for Rune { let mut x = 0u128; for (i, c) in s.chars().enumerate() { if i > 0 { - x += 1; + x = x.checked_add(1).ok_or(Error::Range)?; } x = x.checked_mul(26).ok_or(Error::Range)?; match c { @@ -224,7 +224,11 @@ mod tests { fn from_str_error() { assert_eq!( "BCGDENLQRQWDSLRUGSNLBTMFIJAW".parse::().unwrap_err(), - Error::Range + Error::Range, + ); + assert_eq!( + "BCGDENLQRQWDSLRUGSNLBTMFIJAVX".parse::().unwrap_err(), + Error::Range, ); assert_eq!("x".parse::().unwrap_err(), Error::Character('x')); } diff --git a/crates/ordinals/src/rune_id.rs b/crates/ordinals/src/rune_id.rs index 0c82a0047a..c487db4c58 100644 --- a/crates/ordinals/src/rune_id.rs +++ b/crates/ordinals/src/rune_id.rs @@ -98,6 +98,7 @@ mod tests { #[test] fn delta() { let mut expected = [ + RuneId { block: 3, tx: 1 }, RuneId { block: 4, tx: 2 }, RuneId { block: 1, tx: 2 }, RuneId { block: 1, tx: 1 }, @@ -114,6 +115,7 @@ mod tests { RuneId { block: 1, tx: 2 }, RuneId { block: 2, tx: 0 }, RuneId { block: 3, tx: 1 }, + RuneId { block: 3, tx: 1 }, RuneId { block: 4, tx: 2 }, ] ); @@ -125,7 +127,7 @@ mod tests { previous = id; } - assert_eq!(deltas, [(1, 1), (0, 1), (1, 0), (1, 1), (1, 2)]); + assert_eq!(deltas, [(1, 1), (0, 1), (1, 0), (1, 1), (0, 0), (1, 2)]); let mut previous = RuneId::default(); let mut actual = Vec::new(); diff --git a/crates/ordinals/src/runestone.rs b/crates/ordinals/src/runestone.rs index 8667b6059d..7007d8d8cc 100644 --- a/crates/ordinals/src/runestone.rs +++ b/crates/ordinals/src/runestone.rs @@ -27,22 +27,22 @@ impl Runestone { Some(Payload::Valid(payload)) => payload, Some(Payload::Invalid(flaw)) => { return Some(Artifact::Cenotaph(Cenotaph { - flaws: flaw.into(), + flaw: Some(flaw), ..default() })); } None => return None, }; - let Some(integers) = Runestone::integers(&payload) else { + let Ok(integers) = Runestone::integers(&payload) else { return Some(Artifact::Cenotaph(Cenotaph { - flaws: Flaw::Varint.into(), + flaw: Some(Flaw::Varint), ..default() })); }; let Message { - mut flaws, + mut flaw, edicts, mut fields, } = Message::from_integers(transaction, &integers); @@ -99,20 +99,20 @@ impl Runestone { .map(|etching| etching.supply().is_none()) .unwrap_or_default() { - flaws |= Flaw::SupplyOverflow.flag(); + flaw.get_or_insert(Flaw::SupplyOverflow); } if flags != 0 { - flaws |= Flaw::UnrecognizedFlag.flag(); + flaw.get_or_insert(Flaw::UnrecognizedFlag); } if fields.keys().any(|tag| tag % 2 == 0) { - flaws |= Flaw::UnrecognizedEvenTag.flag(); + flaw.get_or_insert(Flaw::UnrecognizedEvenTag); } - if flaws != 0 { + if let Some(flaw) = flaw { return Some(Artifact::Cenotaph(Cenotaph { - flaws, + flaw: Some(flaw), mint, etching: etching.and_then(|etching| etching.rune), })); @@ -233,7 +233,7 @@ impl Runestone { None } - fn integers(payload: &[u8]) -> Option> { + fn integers(payload: &[u8]) -> Result, varint::Error> { let mut integers = Vec::new(); let mut i = 0; @@ -243,7 +243,7 @@ impl Runestone { i += length; } - Some(integers) + Ok(integers) } } @@ -451,7 +451,7 @@ mod tests { }) .unwrap(), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::Opcode.into(), + flaw: Some(Flaw::Opcode), ..default() }), ); @@ -475,7 +475,7 @@ mod tests { }) .unwrap(), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::Opcode.into(), + flaw: Some(Flaw::Opcode), ..default() }), ); @@ -623,7 +623,7 @@ mod tests { 0 ]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedFlag.into(), + flaw: Some(Flaw::UnrecognizedFlag), ..default() }), ); @@ -636,7 +636,7 @@ mod tests { assert_eq!( decipher(integers), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.into(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); @@ -766,7 +766,7 @@ mod tests { }) .unwrap(), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::Varint.into(), + flaw: Some(Flaw::Varint), ..default() }), ); @@ -789,7 +789,7 @@ mod tests { 0, ]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.into(), + flaw: Some(Flaw::UnrecognizedEvenTag), etching: Some(Rune(4)), ..default() }), @@ -848,7 +848,7 @@ mod tests { assert_eq!( decipher(&[Tag::Cenotaph.into(), 0, Tag::Body.into(), 1, 1, 2, 0]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.flag(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); @@ -867,7 +867,7 @@ mod tests { 0 ]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedFlag.flag(), + flaw: Some(Flaw::UnrecognizedFlag), ..default() }), ); @@ -878,7 +878,7 @@ mod tests { assert_eq!( decipher(&[Tag::Body.into(), 0, 1, 2, 0]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::EdictRuneId.into(), + flaw: Some(Flaw::EdictRuneId), ..default() }), ); @@ -889,7 +889,7 @@ mod tests { assert_eq!( decipher(&[Tag::Body.into(), 1, 0, 0, 0, u64::MAX.into(), 0, 0, 0]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::EdictRuneId.into(), + flaw: Some(Flaw::EdictRuneId), ..default() }), ); @@ -897,7 +897,7 @@ mod tests { assert_eq!( decipher(&[Tag::Body.into(), 1, 1, 0, 0, 0, u64::MAX.into(), 0, 0]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::EdictRuneId.into(), + flaw: Some(Flaw::EdictRuneId), ..default() }), ); @@ -908,7 +908,7 @@ mod tests { assert_eq!( decipher(&[Tag::Body.into(), 1, 1, 2, 2]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::EdictOutput.into(), + flaw: Some(Flaw::EdictOutput), ..default() }), ); @@ -919,7 +919,7 @@ mod tests { assert_eq!( decipher(&[Tag::Flags.into(), 1, Tag::Flags.into()]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::TruncatedField.flag(), + flaw: Some(Flaw::TruncatedField), ..default() }), ); @@ -943,7 +943,7 @@ mod tests { }) } else { Artifact::Cenotaph(Cenotaph { - flaws: Flaw::TrailingIntegers.into(), + flaw: Some(Flaw::TrailingIntegers), ..default() }) } @@ -1139,7 +1139,7 @@ mod tests { assert_eq!( decipher(&[Tag::Rune.into(), 4]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.flag(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); @@ -1236,7 +1236,7 @@ mod tests { assert_eq!( decipher(&[Tag::Body.into(), 1, 1, 2, 0, u128::MAX, 1, 0, 0,]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::EdictRuneId.flag(), + flaw: Some(Flaw::EdictRuneId), ..default() }), ); @@ -1247,7 +1247,7 @@ mod tests { assert_eq!( decipher(&[Tag::Body.into(), 1, 1, 2, 0, 1, u128::MAX, 0, 0,]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::EdictRuneId.flag(), + flaw: Some(Flaw::EdictRuneId), ..default() }), ); @@ -1640,7 +1640,7 @@ mod tests { u128::from(u64::MAX) + 1, ]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.into(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); @@ -1830,7 +1830,7 @@ mod tests { assert_eq!( decipher(&[Tag::Body.into(), 1, 1, 1, u128::from(u32::MAX) + 1]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::EdictOutput.flag(), + flaw: Some(Flaw::EdictOutput), ..default() }), ); @@ -1841,7 +1841,7 @@ mod tests { assert_eq!( decipher(&[Tag::Mint.into(), 1]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.flag(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); @@ -1852,7 +1852,7 @@ mod tests { assert_eq!( decipher(&[Tag::Mint.into(), 0, Tag::Mint.into(), 1]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.flag(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); @@ -1863,7 +1863,7 @@ mod tests { assert_eq!( decipher(&[Tag::OffsetEnd.into(), u128::MAX]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.flag(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); @@ -1874,14 +1874,14 @@ mod tests { assert_eq!( decipher(&[Tag::Pointer.into(), 1]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.flag(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); assert_eq!( decipher(&[Tag::Pointer.into(), u128::MAX]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.flag(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); @@ -1945,7 +1945,7 @@ mod tests { assert_eq!( decipher(&[Tag::OffsetEnd.into(), u128::MAX]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::UnrecognizedEvenTag.flag(), + flaw: Some(Flaw::UnrecognizedEvenTag), ..default() }), ); @@ -1986,7 +1986,7 @@ mod tests { u128::MAX ]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::SupplyOverflow.into(), + flaw: Some(Flaw::SupplyOverflow), ..default() }), ); @@ -2001,7 +2001,7 @@ mod tests { u128::MAX / 2 + 1 ]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::SupplyOverflow.into(), + flaw: Some(Flaw::SupplyOverflow), ..default() }), ); @@ -2018,7 +2018,7 @@ mod tests { u128::MAX ]), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::SupplyOverflow.into(), + flaw: Some(Flaw::SupplyOverflow), ..default() }), ); @@ -2099,7 +2099,7 @@ mod tests { }) .unwrap(), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::InvalidScript.into(), + flaw: Some(Flaw::InvalidScript), ..default() }), ); @@ -2177,7 +2177,7 @@ mod tests { }) .unwrap(), Artifact::Cenotaph(Cenotaph { - flaws: Flaw::Opcode.into(), + flaw: Some(Flaw::Opcode), ..default() }), ); diff --git a/crates/ordinals/src/runestone/message.rs b/crates/ordinals/src/runestone/message.rs index b833169fbc..4eaf59e477 100644 --- a/crates/ordinals/src/runestone/message.rs +++ b/crates/ordinals/src/runestone/message.rs @@ -1,7 +1,7 @@ use super::*; pub(super) struct Message { - pub(super) flaws: u32, + pub(super) flaw: Option, pub(super) edicts: Vec, pub(super) fields: HashMap>, } @@ -10,7 +10,7 @@ impl Message { pub(super) fn from_integers(tx: &Transaction, payload: &[u128]) -> Self { let mut edicts = Vec::new(); let mut fields = HashMap::>::new(); - let mut flaws = 0; + let mut flaw = None; for i in (0..payload.len()).step_by(2) { let tag = payload[i]; @@ -19,17 +19,17 @@ impl Message { let mut id = RuneId::default(); for chunk in payload[i + 1..].chunks(4) { if chunk.len() != 4 { - flaws |= Flaw::TrailingIntegers.flag(); + flaw.get_or_insert(Flaw::TrailingIntegers); break; } let Some(next) = id.next(chunk[0], chunk[1]) else { - flaws |= Flaw::EdictRuneId.flag(); + flaw.get_or_insert(Flaw::EdictRuneId); break; }; let Some(edict) = Edict::from_integers(tx, next, chunk[2], chunk[3]) else { - flaws |= Flaw::EdictOutput.flag(); + flaw.get_or_insert(Flaw::EdictOutput); break; }; @@ -40,7 +40,7 @@ impl Message { } let Some(&value) = payload.get(i + 1) else { - flaws |= Flaw::TruncatedField.flag(); + flaw.get_or_insert(Flaw::TruncatedField); break; }; @@ -48,7 +48,7 @@ impl Message { } Self { - flaws, + flaw, edicts, fields, } diff --git a/crates/ordinals/src/varint.rs b/crates/ordinals/src/varint.rs index cef2086a5e..f6a226946a 100644 --- a/crates/ordinals/src/varint.rs +++ b/crates/ordinals/src/varint.rs @@ -8,11 +8,7 @@ pub fn encode_to_vec(mut n: u128, v: &mut Vec) { v.push(n.to_le_bytes()[0]); } -pub fn decode(buffer: &[u8]) -> Option<(u128, usize)> { - try_decode(buffer).ok() -} - -fn try_decode(buffer: &[u8]) -> Result<(u128, usize), Error> { +pub fn decode(buffer: &[u8]) -> Result<(u128, usize), Error> { let mut n = 0u128; for (i, &byte) in buffer.iter().enumerate() { @@ -43,7 +39,7 @@ pub fn encode(n: u128) -> Vec { } #[derive(PartialEq, Debug)] -enum Error { +pub enum Error { Overlong, Overflow, Unterminated, @@ -69,7 +65,7 @@ mod tests { fn zero_round_trips_successfully() { let n = 0; let encoded = encode(n); - let (decoded, length) = try_decode(&encoded).unwrap(); + let (decoded, length) = decode(&encoded).unwrap(); assert_eq!(decoded, n); assert_eq!(length, encoded.len()); } @@ -78,7 +74,7 @@ mod tests { fn u128_max_round_trips_successfully() { let n = u128::MAX; let encoded = encode(n); - let (decoded, length) = try_decode(&encoded).unwrap(); + let (decoded, length) = decode(&encoded).unwrap(); assert_eq!(decoded, n); assert_eq!(length, encoded.len()); } @@ -88,7 +84,7 @@ mod tests { for i in 0..128 { let n = 1 << i; let encoded = encode(n); - let (decoded, length) = try_decode(&encoded).unwrap(); + let (decoded, length) = decode(&encoded).unwrap(); assert_eq!(decoded, n); assert_eq!(length, encoded.len()); } @@ -101,7 +97,7 @@ mod tests { for i in 0..129 { n = n << 1 | (i % 2); let encoded = encode(n); - let (decoded, length) = try_decode(&encoded).unwrap(); + let (decoded, length) = decode(&encoded).unwrap(); assert_eq!(decoded, n); assert_eq!(length, encoded.len()); } @@ -118,49 +114,49 @@ mod tests { 128, 0, ]; - assert_eq!(try_decode(&VALID), Ok((0, 19))); - assert_eq!(try_decode(&INVALID), Err(Error::Overlong)); + assert_eq!(decode(&VALID), Ok((0, 19))); + assert_eq!(decode(&INVALID), Err(Error::Overlong)); } #[test] fn varints_may_not_overflow_u128() { assert_eq!( - try_decode(&[ + decode(&[ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 64, ]), Err(Error::Overflow) ); assert_eq!( - try_decode(&[ + decode(&[ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 32, ]), Err(Error::Overflow) ); assert_eq!( - try_decode(&[ + 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(&[ + 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(&[ + 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(&[ + decode(&[ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 2, ]), @@ -170,6 +166,6 @@ mod tests { #[test] fn varints_must_be_terminated() { - assert_eq!(try_decode(&[128]), Err(Error::Unterminated)); + assert_eq!(decode(&[128]), Err(Error::Unterminated)); } } diff --git a/src/index.rs b/src/index.rs index b996307ca5..93282a68d3 100644 --- a/src/index.rs +++ b/src/index.rs @@ -955,19 +955,19 @@ impl Index { varint::encode_to_vec(balance, buffer); } - pub(crate) fn decode_rune_balance(buffer: &[u8]) -> Option<((RuneId, u128), usize)> { + pub(crate) fn decode_rune_balance(buffer: &[u8]) -> Result<((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()?, + block: block.try_into()?, + tx: tx.try_into()?, }; let (balance, balance_len) = varint::decode(&buffer[len..])?; len += balance_len; - Some(((id, balance), len)) + Ok(((id, balance), len)) } pub(crate) fn get_rune_balances_for_outpoint( diff --git a/src/index/updater/rune_updater.rs b/src/index/updater/rune_updater.rs index 882ace5c84..f185a897c2 100644 --- a/src/index/updater/rune_updater.rs +++ b/src/index/updater/rune_updater.rs @@ -89,22 +89,24 @@ impl<'a, 'tx, 'client> RuneUpdater<'a, 'tx, 'client> { }) .collect::>(); - if amount == 0 { - // if amount is zero, divide balance between eligible outputs - let amount = *balance / destinations.len() as u128; - let remainder = usize::try_from(*balance % destinations.len() as u128).unwrap(); - - for (i, output) in destinations.iter().enumerate() { - allocate( - balance, - if i < remainder { amount + 1 } else { amount }, - *output, - ); - } - } else { - // if amount is non-zero, distribute amount to eligible outputs - for output in destinations { - allocate(balance, amount.min(*balance), output); + if !destinations.is_empty() { + if amount == 0 { + // if amount is zero, divide balance between eligible outputs + let amount = *balance / destinations.len() as u128; + let remainder = usize::try_from(*balance % destinations.len() as u128).unwrap(); + + for (i, output) in destinations.iter().enumerate() { + allocate( + balance, + if i < remainder { amount + 1 } else { amount }, + *output, + ); + } + } else { + // if amount is non-zero, distribute amount to eligible outputs + for output in destinations { + allocate(balance, amount.min(*balance), output); + } } } } else { diff --git a/src/runes.rs b/src/runes.rs index e3b8caed91..3a247aa12a 100644 --- a/src/runes.rs +++ b/src/runes.rs @@ -5536,6 +5536,85 @@ mod tests { context.assert_runes([], []); } + #[test] + fn edict_with_amount_zero_and_no_destinations_is_ignored() { + let context = Context::builder().arg("--index-runes").build(); + + let (txid0, id) = context.etch( + Runestone { + etching: Some(Etching { + rune: Some(Rune(RUNE)), + premine: Some(u128::MAX), + ..default() + }), + ..default() + }, + 1, + ); + + context.assert_runes( + [( + id, + RuneEntry { + block: id.block, + etching: txid0, + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, + premine: u128::MAX, + timestamp: id.block, + ..default() + }, + )], + [( + OutPoint { + txid: txid0, + vout: 0, + }, + vec![(id, u128::MAX)], + )], + ); + + context.core.broadcast_tx(TransactionTemplate { + inputs: &[(id.block.try_into().unwrap(), 1, 0, Witness::new())], + op_return: Some( + Runestone { + edicts: vec![Edict { + id, + amount: 0, + output: 1, + }], + ..default() + } + .encipher(), + ), + outputs: 0, + ..default() + }); + + context.mine_blocks(1); + + context.assert_runes( + [( + id, + RuneEntry { + block: id.block, + etching: txid0, + spaced_rune: SpacedRune { + rune: Rune(RUNE), + spacers: 0, + }, + premine: u128::MAX, + burned: u128::MAX, + timestamp: id.block, + ..default() + }, + )], + [], + ); + } + #[test] fn genesis_rune() { assert_eq!(