Skip to content

Commit 69913c4

Browse files
committed
feat(ckBTC) Variable minimum retrieval amount.
1 parent bc9f69d commit 69913c4

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)