Skip to content

Commit

Permalink
Create change output when inputs containing non-outgoing runes are se…
Browse files Browse the repository at this point in the history
…lected (#4028)
  • Loading branch information
casey authored Oct 26, 2024
1 parent d97cf03 commit 821c6ed
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 21 deletions.
22 changes: 22 additions & 0 deletions crates/mockcore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,28 @@ impl Handle {
.clone()
}

#[track_caller]
pub fn tx_index(&self, txid: Txid) -> (usize, usize) {
let state = self.state();

for (block_hash, block) in &state.blocks {
for (t, tx) in block.txdata.iter().enumerate() {
if tx.compute_txid() == txid {
let b = state
.hashes
.iter()
.enumerate()
.find(|(_b, hash)| *hash == block_hash)
.unwrap()
.0;
return (b, t);
}
}
}

panic!("unknown transaction");
}

pub fn mempool(&self) -> Vec<Transaction> {
self.state().mempool().to_vec()
}
Expand Down
24 changes: 22 additions & 2 deletions crates/mockcore/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,28 @@ impl State {
let mut total_value = 0;
let mut input = Vec::new();
for (height, tx, vout, witness) in template.inputs.iter() {
let tx = &self.blocks.get(&self.hashes[*height]).unwrap().txdata[*tx];
total_value += tx.output[*vout].value.to_sat();
let block_hash = self
.hashes
.get(*height)
.ok_or_else(|| format!("invalid block height {height}"))
.unwrap();

let block = self.blocks.get(block_hash).unwrap();

let tx = block
.txdata
.get(*tx)
.ok_or_else(|| format!("invalid transaction index {tx}"))
.unwrap();

let tx_out = tx
.output
.get(*vout)
.ok_or_else(|| format!("invalid output index {vout}"))
.unwrap();

total_value += tx_out.value.to_sat();

input.push(TxIn {
previous_output: OutPoint::new(tx.compute_txid(), *vout as u32),
script_sig: ScriptBuf::new(),
Expand Down
10 changes: 6 additions & 4 deletions src/subcommand/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,20 +195,22 @@ impl Send {
output,
balance
.into_iter()
.map(|(spaced_rune, pile)| (spaced_rune.rune, pile))
.map(|(spaced_rune, pile)| (spaced_rune.rune, pile.amount))
.collect(),
)
})
})
.collect::<Result<BTreeMap<OutPoint, BTreeMap<Rune, Pile>>>>()?;
.collect::<Result<BTreeMap<OutPoint, BTreeMap<Rune, u128>>>>()?;

let mut inputs = Vec::new();
let mut input_rune_balances: BTreeMap<Rune, u128> = BTreeMap::new();

for (output, runes) in balances {
if let Some(balance) = runes.get(&spaced_rune.rune) {
if balance.amount > 0 {
*input_rune_balances.entry(spaced_rune.rune).or_default() += balance.amount;
if *balance > 0 {
for (rune, balance) in runes {
*input_rune_balances.entry(rune).or_default() += balance;
}

inputs.push(output);
}
Expand Down
15 changes: 6 additions & 9 deletions tests/balances.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ fn with_runes() {
assert_eq!(
output,
Output {
runes: vec![
runes: [
(
SpacedRune::new(Rune(RUNE), 0),
vec![(
[(
OutPoint {
txid: a.output.reveal,
vout: 1
Expand All @@ -59,12 +59,11 @@ fn with_runes() {
symbol: Some('¢')
},
)]
.into_iter()
.collect()
.into()
),
(
SpacedRune::new(Rune(RUNE + 1), 0),
vec![(
[(
OutPoint {
txid: b.output.reveal,
vout: 1
Expand All @@ -75,12 +74,10 @@ fn with_runes() {
symbol: Some('¢')
},
)]
.into_iter()
.collect()
.into()
),
]
.into_iter()
.collect(),
.into()
}
);
}
156 changes: 150 additions & 6 deletions tests/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -816,9 +816,9 @@ fn sending_rune_with_change_works() {
pretty_assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: vec![(
runes: [(
SpacedRune::new(Rune(RUNE), 0),
vec![
[
(
OutPoint {
txid: output.txid,
Expand All @@ -842,11 +842,155 @@ fn sending_rune_with_change_works() {
},
)
]
.into_iter()
.collect()
.into()
)]
.into_iter()
.collect(),
.into()
}
);
}

#[test]
fn sending_rune_creates_change_output_for_non_outgoing_runes() {
let core = mockcore::builder().network(Network::Regtest).build();

let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);

create_wallet(&core, &ord);

let a = etch(&core, &ord, Rune(RUNE));
let b = etch(&core, &ord, Rune(RUNE + 1));

let (a_block, a_tx) = core.tx_index(a.output.reveal);
let (b_block, b_tx) = core.tx_index(b.output.reveal);

core.mine_blocks(1);

let address = CommandBuilder::new("--regtest wallet receive")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::wallet::receive::Output>()
.addresses
.into_iter()
.next()
.unwrap();

let merge = core.broadcast_tx(TransactionTemplate {
inputs: &[(a_block, a_tx, 1, default()), (b_block, b_tx, 1, default())],
recipient: Some(address.require_network(Network::Regtest).unwrap()),
..default()
});

core.mine_blocks(1);

let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();

pretty_assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: [
(
SpacedRune::new(Rune(RUNE), 0),
[(
OutPoint {
txid: merge,
vout: 0
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into()
),
(
SpacedRune::new(Rune(RUNE + 1), 0),
[(
OutPoint {
txid: merge,
vout: 0
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into()
),
]
.into()
}
);

let output = CommandBuilder::new(format!(
"--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1000:{}",
Rune(RUNE)
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();

core.mine_blocks(1);

let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();

pretty_assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: [
(
SpacedRune::new(Rune(RUNE), 0),
[(
OutPoint {
txid: output.txid,
vout: 2
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into()
),
(
SpacedRune::new(Rune(RUNE + 1), 0),
[(
OutPoint {
txid: output.txid,
vout: 1
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into()
)
]
.into()
}
);

pretty_assert_eq!(
CommandBuilder::new("--regtest --index-runes wallet balance")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Balance>(),
Balance {
cardinal: 84999960160,
ordinal: 20000,
runes: Some([(SpacedRune::new(Rune(RUNE + 1), 0), "1000".parse().unwrap())].into()),
runic: Some(10000),
total: 84999990160,
}
);
}
Expand Down

0 comments on commit 821c6ed

Please sign in to comment.