Skip to content

Commit

Permalink
Don't unintentionally send rare ordinals (ordinals#683)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Oct 20, 2022
1 parent b8a4b43 commit ef7285a
Showing 1 changed file with 102 additions and 25 deletions.
127 changes: 102 additions & 25 deletions src/subcommand/wallet/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ use {
pub(crate) enum Error {
NotInWallet(Ordinal),
NotEnoughCardinalUtxos,
RareOrdinalLostToRecipient(Ordinal),
RareOrdinalLostToFee(Ordinal),
}

impl fmt::Display for Error {
Expand All @@ -46,6 +48,13 @@ impl fmt::Display for Error {
f,
"Wallet does not contain enough cardinal UTXOs. Please add additional funds to wallet."
),
Error::RareOrdinalLostToRecipient(ordinal) => write!(
f,
"Transaction would lose rare ordinal {ordinal} to recipient"
),
Error::RareOrdinalLostToFee(ordinal) => {
write!(f, "Transaction would lose rare ordinal {ordinal} to fee")
}
}
}
}
Expand Down Expand Up @@ -77,16 +86,14 @@ impl TransactionBuilder {
recipient: Address,
change: Vec<Address>,
) -> Result<Transaction> {
Ok(
Self::new(ranges, ordinal, recipient, change)
.select_ordinal()?
.align_ordinal()
.pad_alignment_output()?
.add_postage()?
.strip_excess_postage()
.deduct_fee()
.build(),
)
Self::new(ranges, ordinal, recipient, change)
.select_ordinal()?
.align_ordinal()
.pad_alignment_output()?
.add_postage()?
.strip_excess_postage()
.deduct_fee()
.build()
}

fn new(
Expand Down Expand Up @@ -275,7 +282,7 @@ impl TransactionBuilder {
Self::TARGET_FEE_RATE * self.estimate_vsize().try_into().unwrap()
}

fn build(self) -> Transaction {
fn build(self) -> Result<Transaction> {
let ordinal = self.ordinal.n();
let recipient = self.recipient.script_pubkey();
let transaction = Transaction {
Expand Down Expand Up @@ -429,7 +436,39 @@ impl TransactionBuilder {
);
}

transaction
let mut offset = 0;
let mut rare_ordinals = Vec::<(Ordinal, u64)>::new();
for input in &transaction.input {
for (start, end) in &self.ranges[&input.previous_output] {
if Ordinal(*start).rarity() > Rarity::Common {
rare_ordinals.push((Ordinal(*start), offset));
}
offset += end - start;
}
}
let total_input_amount = offset;

let mut offset = 0;
let mut recipient_range = (0, 0);
for output in &transaction.output {
if output.script_pubkey == self.recipient.script_pubkey() {
recipient_range = (offset, offset + output.value);
break;
}
offset += output.value;
}

for (rare_ordinal, offset) in &rare_ordinals {
if rare_ordinal != &self.ordinal {
if offset >= &recipient_range.0 && offset < &recipient_range.1 {
return Err(Error::RareOrdinalLostToRecipient(*rare_ordinal));
} else if offset >= &(total_input_amount - fee.to_sat()) {
return Err(Error::RareOrdinalLostToFee(*rare_ordinal));
}
}
}

Ok(transaction)
}

fn calculate_ordinal_offset(&self) -> u64 {
Expand Down Expand Up @@ -578,7 +617,7 @@ mod tests {

pretty_assert_eq!(
tx_builder.build(),
Transaction {
Ok(Transaction {
version: 1,
lock_time: PackedLockTime::ZERO,
input: vec![tx_in(outpoint(1)), tx_in(outpoint(2)), tx_in(outpoint(3))],
Expand All @@ -587,7 +626,7 @@ mod tests {
tx_out(5_000, change(0)),
tx_out(1_360, change(1))
],
}
})
)
}

Expand Down Expand Up @@ -721,7 +760,8 @@ mod tests {
recipient(),
vec![change(0), change(1)],
)
.build();
.build()
.unwrap();
}

#[test]
Expand All @@ -733,7 +773,8 @@ mod tests {
recipient(),
vec![change(0), change(1)],
)
.build();
.build()
.unwrap();
}

#[test]
Expand All @@ -752,7 +793,7 @@ mod tests {
.parse()
.unwrap();

builder.build();
builder.build().unwrap();
}

#[test]
Expand All @@ -769,7 +810,7 @@ mod tests {

builder.outputs[0].1 = Amount::from_sat(0);

builder.build();
builder.build().unwrap();
}

#[test]
Expand Down Expand Up @@ -808,7 +849,8 @@ mod tests {
)
.select_ordinal()
.unwrap()
.build();
.build()
.unwrap();
}

#[test]
Expand Down Expand Up @@ -875,7 +917,7 @@ mod tests {

builder.change_addresses = BTreeSet::new();

builder.build();
builder.build().unwrap();
}

#[test]
Expand All @@ -896,7 +938,8 @@ mod tests {
.unwrap()
.strip_excess_postage()
.deduct_fee()
.build();
.build()
.unwrap();
}

#[test]
Expand All @@ -914,7 +957,8 @@ mod tests {
.unwrap()
.strip_excess_postage()
.deduct_fee()
.build();
.build()
.unwrap();
}

#[test]
Expand All @@ -931,7 +975,8 @@ mod tests {
.select_ordinal()
.unwrap()
.strip_excess_postage()
.build();
.build()
.unwrap();
}

#[test]
Expand Down Expand Up @@ -980,7 +1025,8 @@ mod tests {
(change(1), Amount::from_sat(1_774)),
],
}
.build();
.build()
.unwrap();
}

#[test]
Expand All @@ -1005,6 +1051,37 @@ mod tests {
(change(0), Amount::from_sat(1_774)),
],
}
.build();
.build()
.unwrap();
}

#[test]
fn rare_ordinals_are_not_sent_to_recipient() {
let utxos = vec![(outpoint(1), vec![(15_000, 25_000), (0, 10_000)])];

pretty_assert_eq!(
TransactionBuilder::build_transaction(
utxos.into_iter().collect(),
Ordinal(24_000),
recipient(),
vec![change(0), change(1),],
),
Err(Error::RareOrdinalLostToRecipient(Ordinal(0)))
)
}

#[test]
fn rare_ordinals_are_not_sent_as_fee() {
let utxos = vec![(outpoint(1), vec![(15_000, 25_000), (0, 100)])];

pretty_assert_eq!(
TransactionBuilder::build_transaction(
utxos.into_iter().collect(),
Ordinal(24_000),
recipient(),
vec![change(0), change(1),],
),
Err(Error::RareOrdinalLostToFee(Ordinal(0)))
)
}
}

0 comments on commit ef7285a

Please sign in to comment.