dexter_multi_staking/
utils.rs

1use cosmwasm_std::Uint128;
2use dexter::multi_staking::{Config, TokenLock};
3
4use crate::query::query_instant_unlock_fee_tiers;
5
6/// Find the difference between two lock vectors.
7/// This must take into account that same looking lock can coexist, for example, there can be 2 locks for unlocking
8/// 100 tokens at block 100 both.
9/// In this case, the difference calculation must only remove one occurances of the lock if one is present in the locks_to_be_unlocked vector.
10/// Locks are by default stored by unlock time in ascending order by design, but we can sort it once more to be sure.
11/// Return both locks to keep and valid locks to be unlocked since the locks_to_be_unlocked vector can actually contain invalid locks.
12pub fn find_lock_difference(
13    all_locks: Vec<TokenLock>,
14    locks_to_be_unlocked: Vec<TokenLock>,
15) -> (Vec<TokenLock>, Vec<TokenLock>) {
16    let mut all_locks = all_locks;
17    // sort by unlock time
18    all_locks.sort_by(|a, b| a.unlock_time.cmp(&b.unlock_time));
19
20    let mut locks_to_be_unlocked = locks_to_be_unlocked;
21
22    // sort by unlock time
23    locks_to_be_unlocked.sort_by(|a, b| a.unlock_time.cmp(&b.unlock_time));
24
25    let mut difference = vec![];
26
27    let mut i = 0;
28    let mut j = 0;
29
30    let mut valid_locks_to_be_unlocked = vec![];
31
32    while i < all_locks.len() && j < locks_to_be_unlocked.len() {
33        if all_locks[i] == locks_to_be_unlocked[j] {
34            valid_locks_to_be_unlocked.push(locks_to_be_unlocked[j].clone());
35            i += 1;
36            j += 1;
37        } else if all_locks[i].unlock_time < locks_to_be_unlocked[j].unlock_time {
38            difference.push(all_locks[i].clone());
39            i += 1;
40        } else {
41            j += 1;
42        }
43    }
44
45    while i < all_locks.len() {
46        difference.push(all_locks[i].clone());
47        i += 1;
48    }
49
50    return (difference, valid_locks_to_be_unlocked);
51}
52
53/// Calculate the instant unlock fee for a given token lock.
54/// The fee is calculated as a percentage of the locked amount.
55/// It is linearly interpolated between the start and end time of the lock at tier_interval granularity.
56pub fn calculate_unlock_fee(
57    token_lock: &TokenLock,
58    current_block_time: u64,
59    config: &Config,
60) -> (u64, Uint128) {
61    let lock_end_time = token_lock.unlock_time;
62
63    if current_block_time >= lock_end_time {
64        return (0, Uint128::zero());
65    }
66
67    // This is the bounds of the fee calculation linear interpolation at tier_interval granularity.
68    let min_fee_bp = config.instant_unbond_min_fee_bp;
69    let max_fee_bp = config.instant_unbond_fee_bp;
70
71    let tiers = query_instant_unlock_fee_tiers(
72        config.fee_tier_interval,
73        config.unlock_period,
74        min_fee_bp,
75        max_fee_bp,
76    );
77
78    // find applicable tier based on second left to unlock
79    let seconds_left_to_unlock = lock_end_time - current_block_time;
80
81    let mut fee_bp = max_fee_bp;
82    for tier in tiers {
83        // the tier is applicable if the seconds fall in tiers range, end non-inclusive
84        if seconds_left_to_unlock >= tier.seconds_till_unlock_start
85            && seconds_left_to_unlock < tier.seconds_till_unlock_end
86        {
87            fee_bp = tier.unlock_fee_bp;
88            break;
89        }
90    }
91
92    let fee = token_lock.amount.multiply_ratio(fee_bp, 10000u64);
93    (fee_bp, fee)
94}