Skip to content

Commit

Permalink
Fix send runes (ordinals#3484)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored May 2, 2024
1 parent ea9f597 commit 3cd4635
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 78 deletions.
4 changes: 2 additions & 2 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ pub struct Output {
pub address: Option<Address<NetworkUnchecked>>,
pub indexed: bool,
pub inscriptions: Vec<InscriptionId>,
pub runes: Vec<(SpacedRune, Pile)>,
pub runes: BTreeMap<SpacedRune, Pile>,
pub sat_ranges: Option<Vec<(u64, u64)>>,
pub script_pubkey: String,
pub spent: bool,
Expand All @@ -141,7 +141,7 @@ impl Output {
outpoint: OutPoint,
tx_out: TxOut,
indexed: bool,
runes: Vec<(SpacedRune, Pile)>,
runes: BTreeMap<SpacedRune, Pile>,
sat_ranges: Option<Vec<(u64, u64)>>,
spent: bool,
) -> Self {
Expand Down
10 changes: 5 additions & 5 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -973,35 +973,35 @@ impl Index {
pub(crate) fn get_rune_balances_for_outpoint(
&self,
outpoint: OutPoint,
) -> Result<Vec<(SpacedRune, Pile)>> {
) -> Result<BTreeMap<SpacedRune, Pile>> {
let rtx = self.database.begin_read()?;

let outpoint_to_balances = rtx.open_table(OUTPOINT_TO_RUNE_BALANCES)?;

let id_to_rune_entries = rtx.open_table(RUNE_ID_TO_RUNE_ENTRY)?;

let Some(balances) = outpoint_to_balances.get(&outpoint.store())? else {
return Ok(Vec::new());
return Ok(BTreeMap::new());
};

let balances_buffer = balances.value();

let mut balances = Vec::new();
let mut balances = BTreeMap::new();
let mut i = 0;
while i < balances_buffer.len() {
let ((id, amount), length) = Index::decode_rune_balance(&balances_buffer[i..]).unwrap();
i += length;

let entry = RuneEntry::load(id_to_rune_entries.get(id.store())?.unwrap().value());

balances.push((
balances.insert(
entry.spaced_rune,
Pile {
amount,
divisibility: entry.divisibility,
symbol: entry.symbol,
},
));
);
}

Ok(balances)
Expand Down
4 changes: 3 additions & 1 deletion src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3225,7 +3225,9 @@ mod tests {
divisibility: 1,
symbol: None,
}
)],
)]
.into_iter()
.collect(),
spent: false,
}
);
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet/balance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub(crate) fn run(wallet: Wallet) -> SubcommandResult {
let mut runic = 0;

for (output, txout) in unspent_outputs {
let rune_balances = wallet.get_runes_balances_for_output(output)?;
let rune_balances = wallet.get_runes_balances_in_output(output)?;

let is_ordinal = inscription_outputs.contains(output);
let is_runic = !rune_balances.is_empty();
Expand Down
108 changes: 71 additions & 37 deletions src/subcommand/wallet/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ impl Send {
address,
rune,
decimal,
self.postage.unwrap_or(TARGET_POSTAGE),
self.fee_rate,
)?,
Outgoing::InscriptionId(id) => Self::create_unsigned_send_satpoint_transaction(
Expand Down Expand Up @@ -209,17 +210,14 @@ impl Send {
destination: Address,
spaced_rune: SpacedRune,
decimal: Decimal,
postage: Amount,
fee_rate: FeeRate,
) -> Result<Transaction> {
ensure!(
wallet.has_rune_index(),
"sending runes with `ord send` requires index created with `--index-runes` flag",
);

let inscriptions = wallet.inscriptions();
let runic_outputs = wallet.get_runic_outputs()?;
let bitcoin_client = wallet.bitcoin_client();

wallet.lock_non_cardinal_outputs()?;

let (id, entry, _parent) = wallet
Expand All @@ -228,37 +226,64 @@ impl Send {

let amount = decimal.to_integer(entry.divisibility)?;

let inscribed_outputs = inscriptions
let inscribed_outputs = wallet
.inscriptions()
.keys()
.map(|satpoint| satpoint.outpoint)
.collect::<HashSet<OutPoint>>();

let mut input_runes = 0;
let mut input = Vec::new();
let balances = wallet
.get_runic_outputs()?
.into_iter()
.filter(|output| !inscribed_outputs.contains(output))
.map(|output| {
wallet.get_runes_balances_in_output(&output).map(|balance| {
(
output,
balance
.into_iter()
.map(|(spaced_rune, pile)| (spaced_rune.rune, pile))
.collect(),
)
})
})
.collect::<Result<BTreeMap<OutPoint, BTreeMap<Rune, Pile>>>>()?;

for output in runic_outputs {
if inscribed_outputs.contains(&output) {
continue;
}
let mut inputs = Vec::new();
let mut input_rune_balances: BTreeMap<Rune, u128> = BTreeMap::new();

let balance = wallet.get_rune_balance_in_output(&output, entry.spaced_rune.rune)?;
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 {
input_runes += balance;
input.push(output);
inputs.push(output);
}
}

if input_runes >= amount {
if input_rune_balances
.get(&spaced_rune.rune)
.cloned()
.unwrap_or_default()
>= amount
{
break;
}
}

let input_rune_balance = input_rune_balances
.get(&spaced_rune.rune)
.cloned()
.unwrap_or_default();

let needs_runes_change_output = input_rune_balance > amount || input_rune_balances.len() > 1;

ensure! {
input_runes >= amount,
input_rune_balance >= amount,
"insufficient `{}` balance, only {} in wallet",
spaced_rune,
Pile {
amount: input_runes,
amount: input_rune_balance,
divisibility: entry.divisibility,
symbol: entry.symbol
},
Expand All @@ -276,7 +301,7 @@ impl Send {
let unfunded_transaction = Transaction {
version: 2,
lock_time: LockTime::ZERO,
input: input
input: inputs
.into_iter()
.map(|previous_output| TxIn {
previous_output,
Expand All @@ -285,31 +310,40 @@ impl Send {
witness: Witness::new(),
})
.collect(),
output: vec![
TxOut {
script_pubkey: runestone.encipher(),
value: 0,
},
TxOut {
script_pubkey: wallet.get_change_address()?.script_pubkey(),
value: TARGET_POSTAGE.to_sat(),
},
TxOut {
output: if needs_runes_change_output {
vec![
TxOut {
script_pubkey: runestone.encipher(),
value: 0,
},
TxOut {
script_pubkey: wallet.get_change_address()?.script_pubkey(),
value: postage.to_sat(),
},
TxOut {
script_pubkey: destination.script_pubkey(),
value: postage.to_sat(),
},
]
} else {
vec![TxOut {
script_pubkey: destination.script_pubkey(),
value: TARGET_POSTAGE.to_sat(),
},
],
value: postage.to_sat(),
}]
},
};

let unsigned_transaction =
fund_raw_transaction(bitcoin_client, fee_rate, &unfunded_transaction)?;
fund_raw_transaction(wallet.bitcoin_client(), fee_rate, &unfunded_transaction)?;

let unsigned_transaction = consensus::encode::deserialize(&unsigned_transaction)?;

assert_eq!(
Runestone::decipher(&unsigned_transaction),
Some(Artifact::Runestone(runestone)),
);
if needs_runes_change_output {
assert_eq!(
Runestone::decipher(&unsigned_transaction),
Some(Artifact::Runestone(runestone)),
);
}

Ok(unsigned_transaction)
}
Expand Down
16 changes: 9 additions & 7 deletions src/templates/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub(crate) struct OutputHtml {
pub(crate) inscriptions: Vec<InscriptionId>,
pub(crate) outpoint: OutPoint,
pub(crate) output: TxOut,
pub(crate) runes: Vec<(SpacedRune, Pile)>,
pub(crate) runes: BTreeMap<SpacedRune, Pile>,
pub(crate) sat_ranges: Option<Vec<(u64, u64)>>,
pub(crate) spent: bool,
}
Expand All @@ -32,7 +32,7 @@ mod tests {
inscriptions: Vec::new(),
outpoint: outpoint(1),
output: TxOut { value: 3, script_pubkey: ScriptBuf::new_p2pkh(&PubkeyHash::all_zeros()), },
runes: Vec::new(),
runes: BTreeMap::new(),
sat_ranges: Some(vec![(0, 1), (1, 3)]),
spent: false,
},
Expand Down Expand Up @@ -66,7 +66,7 @@ mod tests {
value: 1,
script_pubkey: script::Builder::new().push_int(0).into_script(),
},
runes: Vec::new(),
runes: BTreeMap::new(),
sat_ranges: None,
spent: true,
},
Expand All @@ -91,7 +91,7 @@ mod tests {
inscriptions: Vec::new(),
outpoint: outpoint(1),
output: TxOut { value: 3, script_pubkey: ScriptBuf::new_p2pkh(&PubkeyHash::all_zeros()), },
runes: Vec::new(),
runes: BTreeMap::new(),
sat_ranges: Some(vec![(0, 1), (1, 3)]),
spent: true,
},
Expand Down Expand Up @@ -122,7 +122,7 @@ mod tests {
inscriptions: Vec::new(),
outpoint: outpoint(1),
output: TxOut { value: 3, script_pubkey: ScriptBuf::new_p2pkh(&PubkeyHash::all_zeros()), },
runes: Vec::new(),
runes: BTreeMap::new(),
sat_ranges: None,
spent: false,
}
Expand Down Expand Up @@ -152,7 +152,7 @@ mod tests {
value: 3,
script_pubkey: ScriptBuf::new_p2pkh(&PubkeyHash::all_zeros()),
},
runes: Vec::new(),
runes: BTreeMap::new(),
sat_ranges: None,
spent: false,
},
Expand Down Expand Up @@ -191,7 +191,9 @@ mod tests {
divisibility: 1,
symbol: None,
}
)],
)]
.into_iter()
.collect(),
sat_ranges: None,
spent: false,
},
Expand Down
20 changes: 2 additions & 18 deletions src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,10 @@ impl Wallet {
Ok(runic_outputs)
}

pub(crate) fn get_runes_balances_for_output(
pub(crate) fn get_runes_balances_in_output(
&self,
output: &OutPoint,
) -> Result<Vec<(SpacedRune, Pile)>> {
) -> Result<BTreeMap<SpacedRune, Pile>> {
Ok(
self
.output_info
Expand All @@ -231,22 +231,6 @@ impl Wallet {
)
}

pub(crate) fn get_rune_balance_in_output(&self, output: &OutPoint, rune: Rune) -> Result<u128> {
Ok(
self
.get_runes_balances_for_output(output)?
.iter()
.map(|(spaced_rune, pile)| {
if spaced_rune.rune == rune {
pile.amount
} else {
0
}
})
.sum(),
)
}

pub(crate) fn get_rune(
&self,
rune: Rune,
Expand Down
2 changes: 1 addition & 1 deletion tests/json_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ fn get_output() {
InscriptionId { txid, index: 2 },
],
indexed: true,
runes: Vec::new(),
runes: BTreeMap::new(),
sat_ranges: Some(vec![
(5000000000, 10000000000,),
(10000000000, 15000000000,),
Expand Down
1 change: 1 addition & 0 deletions tests/wallet/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ fn sending_rune_does_not_send_inscription() {
--chain regtest
--index-runes
wallet send
--postage 11111sat
--fee-rate 0
bcrt1pyrmadgg78e38ewfv0an8c6eppk2fttv5vnuvz04yza60qau5va0saknu8k
1000:{rune}
Expand Down
Loading

0 comments on commit 3cd4635

Please sign in to comment.