Skip to main content

amadeus_runtime/consensus/bic/
lockup_prime.rs

1use crate::consensus::bic::coin::{balance, to_flat};
2use crate::consensus::bic::epoch::TREASURY_DONATION_ADDRESS;
3use crate::consensus::bic::lockup::create_lock;
4use crate::consensus::consensus_apply::ApplyEnv;
5use crate::consensus::consensus_kv::{kv_delete, kv_get, kv_increment, kv_put};
6use crate::{Result, bcat};
7use amadeus_utils::misc::list_of_binaries_to_vecpak;
8
9/// Initialize PRIME token if it doesn't exist (called by call_lock)
10fn init_prime_if_needed(env: &mut ApplyEnv) -> Result<()> {
11    use crate::consensus::bic::coin::exists;
12    if !exists(env, b"PRIME")? {
13        kv_increment(env, b"coin:PRIME:totalSupply", 0)?;
14
15        // Admin public key
16        let v0: &[u8; 48] = &[
17            149, 216, 55, 255, 29, 8, 239, 251, 139, 112, 30, 29, 199, 57, 90, 67, 198, 220, 101, 18, 228, 100, 100,
18            241, 43, 213, 221, 230, 253, 58, 231, 1, 102, 166, 54, 66, 245, 148, 140, 44, 78, 56, 84, 12, 222, 205, 57,
19            210,
20        ];
21        let admin = vec![v0.to_vec()];
22        let term_admins = list_of_binaries_to_vecpak(admin);
23        kv_put(env, b"coin:PRIME:permission", &term_admins)?;
24
25        kv_put(env, b"coin:PRIME:mintable", b"true")?;
26        kv_put(env, b"coin:PRIME:pausable", b"true")?;
27        kv_put(env, b"coin:PRIME:soulbound", b"true")?;
28    }
29    Ok(())
30}
31
32/// Mint PRIME tokens to a receiver
33fn mint_prime(env: &mut ApplyEnv, amount: i128, receiver: &[u8]) -> Result<()> {
34    if amount <= 0 {
35        return Err("invalid_amount");
36    }
37    kv_increment(env, &bcat(&[b"account:", receiver, b":balance:PRIME"]), amount)?;
38    kv_increment(env, b"coin:PRIME:totalSupply", amount)?;
39    Ok(())
40}
41
42/// Lock AMA tokens to receive PRIME points upon unlock
43pub fn call_lock(env: &mut ApplyEnv, args: Vec<Vec<u8>>) -> Result<()> {
44    init_prime_if_needed(env)?;
45
46    if args.len() != 2 {
47        return Err("invalid_args");
48    }
49    let amount = args[0].as_slice();
50    let amount = std::str::from_utf8(&amount).ok().and_then(|s| s.parse::<i128>().ok()).ok_or("invalid_amount")?;
51    let tier = args[1].as_slice();
52
53    let (tier_epochs, multiplier) = match tier {
54        b"magic" => (0, 1),
55        b"magic2" => (1, 1),
56        b"7d" => (10, 13),
57        b"30d" => (45, 17),
58        b"90d" => (135, 27),
59        b"180d" => (270, 35),
60        b"365d" => (547, 54),
61        _ => return Err("invalid_tier"),
62    };
63
64    if amount <= to_flat(1) {
65        return Err("invalid_amount");
66    }
67    if amount > balance(env, env.caller_env.account_caller.as_slice(), b"AMA")? {
68        return Err("insufficient_funds");
69    }
70    kv_increment(env, &bcat(&[b"account:", &env.caller_env.account_caller, b":balance:AMA"]), -amount)?;
71
72    let vault_index = kv_increment(env, b"bic:lockup_prime:unique_index", 1)?;
73    let vault_value = bcat(&[
74        tier,
75        b"-",
76        multiplier.to_string().as_bytes(),
77        b"-",
78        (env.caller_env.entry_epoch.saturating_add(tier_epochs)).to_string().as_bytes(),
79        b"-",
80        amount.to_string().as_bytes(),
81    ]);
82    kv_put(
83        env,
84        &bcat(&[b"bic:lockup_prime:vault:", &env.caller_env.account_caller, b":", vault_index.to_string().as_bytes()]),
85        &vault_value,
86    )?;
87    Ok(())
88}
89
90/// Unlock a PRIME vault - early unlock incurs 25% penalty
91pub fn call_unlock(env: &mut ApplyEnv, args: Vec<Vec<u8>>) -> Result<()> {
92    if args.len() != 1 {
93        return Err("invalid_args");
94    }
95    let vault_index = args[0].as_slice();
96
97    let vault_key = bcat(&[b"bic:lockup_prime:vault:", &env.caller_env.account_caller, b":", vault_index]);
98
99    let vault = kv_get(env, &vault_key)?.ok_or("invalid_vault")?;
100
101    let vault_parts: Vec<Vec<u8>> = vault.split(|&b| b == b'-').map(|seg| seg.to_vec()).collect();
102    if vault_parts.len() < 4 {
103        return Err("invalid_vault_format");
104    }
105
106    // vault format: tier-multiplier-unlock_epoch-amount
107    let multiplier =
108        std::str::from_utf8(&vault_parts[1]).ok().and_then(|s| s.parse::<u64>().ok()).ok_or("invalid_multiplier")?;
109    let unlock_epoch =
110        std::str::from_utf8(&vault_parts[2]).ok().and_then(|s| s.parse::<u64>().ok()).ok_or("invalid_unlock_epoch")?;
111    let unlock_amount =
112        std::str::from_utf8(&vault_parts[3]).ok().and_then(|s| s.parse::<u64>().ok()).ok_or("invalid_unlock_amount")?;
113
114    if env.caller_env.entry_epoch < unlock_epoch {
115        // Early unlock: 25% penalty
116        let penalty = unlock_amount / 4;
117        let disbursement = unlock_amount - penalty;
118
119        kv_increment(
120            env,
121            &bcat(&[b"account:", TREASURY_DONATION_ADDRESS.as_slice(), b":balance:AMA"]),
122            penalty as i128,
123        )?;
124        // Lockup for 5 epochs
125        create_lock(
126            env,
127            env.caller_env.account_caller.to_vec().as_slice(),
128            b"AMA",
129            disbursement as i128,
130            env.caller_env.entry_epoch.saturating_add(5),
131        )?;
132    } else {
133        // Normal unlock: receive PRIME points and original AMA
134        let prime_points = unlock_amount * multiplier;
135        mint_prime(env, prime_points as i128, env.caller_env.account_caller.to_vec().as_slice())?;
136        kv_increment(
137            env,
138            &bcat(&[b"account:", &env.caller_env.account_caller, b":balance:AMA"]),
139            unlock_amount as i128,
140        )?;
141    }
142
143    kv_delete(env, &vault_key)?;
144    Ok(())
145}
146
147/// Daily check-in for PRIME vaults - earn 1% bonus daily with streak bonuses
148pub fn call_daily_checkin(env: &mut ApplyEnv, args: Vec<Vec<u8>>) -> Result<()> {
149    if args.len() != 1 {
150        return Err("invalid_args");
151    }
152    let vault_index = args[0].as_slice();
153
154    let vault_key = bcat(&[b"bic:lockup_prime:vault:", &env.caller_env.account_caller, b":", vault_index]);
155    let vault = kv_get(env, &vault_key)?.ok_or("invalid_vault")?;
156    let vault_parts: Vec<Vec<u8>> = vault.split(|&b| b == b'-').map(|seg| seg.to_vec()).collect();
157    if vault_parts.len() < 4 {
158        return Err("invalid_vault_format");
159    }
160
161    let unlock_amount =
162        std::str::from_utf8(&vault_parts[3]).ok().and_then(|s| s.parse::<u64>().ok()).ok_or("invalid_unlock_amount")?;
163
164    let next_checkin_key = bcat(&[b"bic:lockup_prime:next_checkin_epoch:", &env.caller_env.account_caller]);
165    let next_checkin_epoch: u64 = kv_get(env, &next_checkin_key)?
166        .map(|bytes| {
167            std::str::from_utf8(&bytes).ok().and_then(|s| s.parse::<u64>().ok()).unwrap_or(env.caller_env.entry_epoch)
168        })
169        .unwrap_or(env.caller_env.entry_epoch);
170
171    let delta = (env.caller_env.entry_epoch as i64) - (next_checkin_epoch as i64);
172    if delta == 0 || delta == 1 {
173        kv_put(env, &next_checkin_key, env.caller_env.entry_epoch.saturating_add(2).to_string().as_bytes())?;
174
175        let daily_bonus = unlock_amount / 100;
176        mint_prime(env, daily_bonus as i128, env.caller_env.account_caller.to_vec().as_slice())?;
177
178        let streak_key = bcat(&[b"bic:lockup_prime:daily_streak:", &env.caller_env.account_caller]);
179        let streak = kv_increment(env, &streak_key, 1)?;
180        if streak >= 30 {
181            kv_put(env, &streak_key, b"0")?;
182            let streak_bonus = daily_bonus * 30;
183            mint_prime(env, streak_bonus as i128, env.caller_env.account_caller.to_vec().as_slice())?;
184        }
185    } else if delta > 2 {
186        // Missed check-in, reset streak
187        kv_put(env, &next_checkin_key, env.caller_env.entry_epoch.saturating_add(2).to_string().as_bytes())?;
188        let streak_key = bcat(&[b"bic:lockup_prime:daily_streak:", &env.caller_env.account_caller]);
189        kv_put(env, &streak_key, b"0")?;
190    }
191    // else: already checked in for the day (2 epoch window), do nothing
192
193    Ok(())
194}