Skip to content

Commit 54071ce

Browse files
committed
Merge branch 'leo-variable-min-w-amount' into 'master'
feat(ckBTC) Variable minimum retrieval amount. There is the risk that the median fee rate may become so large that the fee exceeds the minimum retrieve amount of 100,000 satoshi. This problem can be addressed by making the minimum retrieve amount dependent on the current median fee rate. As outlined here, a solution is to introduce a “buffer” so that the minimum retrieve amount is always considerably larger than the fee in practice: `min_retrieve_amount = ((22_100 + 221 * median_fee_rate + 305 + 2000) / 50_000) * 50_000 + 100_000` Closes FI-730. Closes FI-730 See merge request dfinity-lab/public/ic!12536
2 parents c95da98 + 69913c4 commit 54071ce

File tree

2 files changed

+80
-3
lines changed

2 files changed

+80
-3
lines changed

rs/bitcoin/ckbtc/minter/src/lib.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ async fn fetch_main_utxos(main_account: &Account, main_address: &BitcoinAddress)
121121
})
122122
}
123123

124+
/// Returns the minimum withdrawal amount based on the current median fee rate (in millisatoshi per byte).
125+
/// The returned amount is in satoshi.
126+
fn compute_min_withdrawal_amount(median_fee_rate_e3s: MillisatoshiPerByte) -> u64 {
127+
const PER_REQUEST_RBF_BOUND: u64 = 22_100;
128+
const PER_REQUEST_VSIZE_BOUND: u64 = 221;
129+
const PER_REQUEST_MINTER_FEE_BOUND: u64 = 305;
130+
const PER_REQUEST_KYT_FEE: u64 = 2_000;
131+
132+
let median_fee_rate = median_fee_rate_e3s / 1_000;
133+
((PER_REQUEST_RBF_BOUND
134+
+ PER_REQUEST_VSIZE_BOUND * median_fee_rate
135+
+ PER_REQUEST_MINTER_FEE_BOUND
136+
+ PER_REQUEST_KYT_FEE)
137+
/ 50_000)
138+
* 50_000
139+
+ 100_000
140+
}
141+
124142
/// Returns an estimate for transaction fees in millisatoshi per vbyte. Returns
125143
/// None if the bitcoin canister is unavailable or does not have enough data for
126144
/// an estimate yet.
@@ -136,7 +154,10 @@ pub async fn estimate_fee_per_vbyte() -> Option<MillisatoshiPerByte> {
136154
return Some(DEFAULT_FEE);
137155
}
138156
if fees.len() >= 100 {
139-
state::mutate_state(|s| s.last_fee_per_vbyte = fees.clone());
157+
state::mutate_state(|s| {
158+
s.last_fee_per_vbyte = fees.clone();
159+
s.retrieve_btc_min_amount = compute_min_withdrawal_amount(fees[50]);
160+
});
140161
Some(fees[50])
141162
} else {
142163
log!(

rs/bitcoin/ckbtc/minter/tests/tests.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use ic_ckbtc_minter::state::{Mode, RetrieveBtcStatus};
1212
use ic_ckbtc_minter::updates::get_btc_address::GetBtcAddressArgs;
1313
use ic_ckbtc_minter::updates::retrieve_btc::{RetrieveBtcArgs, RetrieveBtcError, RetrieveBtcOk};
1414
use ic_ckbtc_minter::updates::update_balance::{UpdateBalanceArgs, UpdateBalanceError, UtxoStatus};
15+
use ic_ckbtc_minter::MinterInfo;
1516
use ic_icrc1_ledger::{ArchiveOptions, InitArgs as LedgerInitArgs, LedgerArgument};
1617
use ic_state_machine_tests::{Cycles, StateMachine, StateMachineBuilder, WasmResult};
1718
use ic_test_utilities_load_wasm::load_wasm;
@@ -495,7 +496,7 @@ impl CkBtcSetup {
495496
env.execute_ingress(
496497
bitcoin_id,
497498
"set_fee_percentiles",
498-
Encode!(&(1..=100).collect::<Vec<u64>>()).unwrap(),
499+
Encode!(&(1..=100).map(|i| i * 100).collect::<Vec<u64>>()).unwrap(),
499500
)
500501
.expect("failed to set fee percentiles");
501502

@@ -520,6 +521,16 @@ impl CkBtcSetup {
520521
}
521522
}
522523

524+
pub fn set_fee_percentiles(&self, fees: &Vec<u64>) {
525+
self.env
526+
.execute_ingress(
527+
self.bitcoin_id,
528+
"set_fee_percentiles",
529+
Encode!(fees).unwrap(),
530+
)
531+
.expect("failed to set fee percentiles");
532+
}
533+
523534
pub fn push_utxo(&self, address: String, utxo: Utxo) {
524535
assert_reply(
525536
self.env
@@ -554,7 +565,19 @@ impl CkBtcSetup {
554565
.unwrap()
555566
}
556567

557-
pub fn estimate_withdrawal_fee(&self, amount: Option<u64>) -> WithdrawalFee {
568+
pub fn get_minter_info(&self) -> MinterInfo {
569+
Decode!(
570+
&assert_reply(
571+
self.env
572+
.execute_ingress(self.minter_id, "get_minter_info", Encode!().unwrap(),)
573+
.expect("failed to get minter info")
574+
),
575+
MinterInfo
576+
)
577+
.unwrap()
578+
}
579+
580+
pub fn refresh_fee_percentiles(&self) {
558581
Decode!(
559582
&assert_reply(
560583
self.env
@@ -569,7 +592,10 @@ impl CkBtcSetup {
569592
()
570593
)
571594
.unwrap();
595+
}
572596

597+
pub fn estimate_withdrawal_fee(&self, amount: Option<u64>) -> WithdrawalFee {
598+
self.refresh_fee_percentiles();
573599
Decode!(
574600
&assert_reply(
575601
self.env
@@ -843,3 +869,33 @@ fn test_transaction_finalization() {
843869

844870
assert_eq!(ckbtc.await_finalization(block_index, 10), txid);
845871
}
872+
873+
#[test]
874+
fn test_min_retrieval_amount() {
875+
let ckbtc = CkBtcSetup::new();
876+
877+
ckbtc.refresh_fee_percentiles();
878+
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount;
879+
assert_eq!(retrieve_btc_min_amount, 100_000);
880+
881+
// The numbers used in this test have been re-computed using a python script using integers.
882+
ckbtc.set_fee_percentiles(&vec![0; 100]);
883+
ckbtc.refresh_fee_percentiles();
884+
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount;
885+
assert_eq!(retrieve_btc_min_amount, 100_000);
886+
887+
ckbtc.set_fee_percentiles(&vec![116_000; 100]);
888+
ckbtc.refresh_fee_percentiles();
889+
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount;
890+
assert_eq!(retrieve_btc_min_amount, 150_000);
891+
892+
ckbtc.set_fee_percentiles(&vec![342_000; 100]);
893+
ckbtc.refresh_fee_percentiles();
894+
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount;
895+
assert_eq!(retrieve_btc_min_amount, 150_000);
896+
897+
ckbtc.set_fee_percentiles(&vec![343_000; 100]);
898+
ckbtc.refresh_fee_percentiles();
899+
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount;
900+
assert_eq!(retrieve_btc_min_amount, 200_000);
901+
}

0 commit comments

Comments
 (0)