Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix send runes #3484

Merged
merged 20 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment ref.

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
Loading