Skip to main content

klend_interface/
discriminators.rs

1use sha2::{Digest, Sha256};
2
3/// Compute the 8-byte Anchor discriminator for a given instruction name.
4/// Uses the convention: `sha256("global:<snake_case_name>")[..8]`
5pub fn compute_discriminator(name: &str) -> [u8; 8] {
6    let mut hasher = Sha256::new();
7    hasher.update(format!("global:{name}"));
8    let hash = hasher.finalize();
9    let mut disc = [0u8; 8];
10    disc.copy_from_slice(&hash[..8]);
11    disc
12}
13
14macro_rules! disc {
15    ($name:ident, $ix:expr) => {
16        pub static $name: [u8; 8] = {
17            const PREIMAGE: &[u8] = concat!("global:", $ix).as_bytes();
18            sha256_first8(PREIMAGE)
19        };
20    };
21}
22
23const SHA256_K: [u32; 64] = [
24    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
25    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
26    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
27    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
28    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
29    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
30    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
31    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
32];
33
34/// Compress a single 64-byte block into the state, returning the new state.
35/// Uses value semantics to avoid `&mut` (not available in const fn on Rust 1.74).
36const fn compress_block(h: [u32; 8], block: [u8; 64]) -> [u32; 8] {
37    let mut w = [0u32; 64];
38    let mut i = 0;
39    while i < 16 {
40        w[i] = ((block[i * 4] as u32) << 24)
41            | ((block[i * 4 + 1] as u32) << 16)
42            | ((block[i * 4 + 2] as u32) << 8)
43            | (block[i * 4 + 3] as u32);
44        i += 1;
45    }
46    i = 16;
47    while i < 64 {
48        let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
49        let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
50        w[i] = w[i - 16]
51            .wrapping_add(s0)
52            .wrapping_add(w[i - 7])
53            .wrapping_add(s1);
54        i += 1;
55    }
56
57    let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh) =
58        (h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]);
59
60    i = 0;
61    while i < 64 {
62        let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
63        let ch = (e & f) ^ ((!e) & g);
64        let temp1 = hh
65            .wrapping_add(s1)
66            .wrapping_add(ch)
67            .wrapping_add(SHA256_K[i])
68            .wrapping_add(w[i]);
69        let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
70        let maj = (a & b) ^ (a & c) ^ (b & c);
71        let temp2 = s0.wrapping_add(maj);
72
73        hh = g;
74        g = f;
75        f = e;
76        e = d.wrapping_add(temp1);
77        d = c;
78        c = b;
79        b = a;
80        a = temp1.wrapping_add(temp2);
81        i += 1;
82    }
83
84    [
85        h[0].wrapping_add(a),
86        h[1].wrapping_add(b),
87        h[2].wrapping_add(c),
88        h[3].wrapping_add(d),
89        h[4].wrapping_add(e),
90        h[5].wrapping_add(f),
91        h[6].wrapping_add(g),
92        h[7].wrapping_add(hh),
93    ]
94}
95
96/// Const-compatible SHA-256 returning only the first 8 bytes.
97/// Supports messages up to 119 bytes (two 64-byte blocks).
98pub const fn sha256_first8(msg: &[u8]) -> [u8; 8] {
99    let h: [u32; 8] = [
100        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
101        0x5be0cd19,
102    ];
103
104    let bit_len = (msg.len() as u64) * 8;
105    let padded_len = if msg.len() + 9 <= 64 { 64 } else { 128 };
106
107    let mut padded = [0u8; 128];
108    let mut i = 0;
109    while i < msg.len() {
110        padded[i] = msg[i];
111        i += 1;
112    }
113    padded[msg.len()] = 0x80;
114    padded[padded_len - 8] = (bit_len >> 56) as u8;
115    padded[padded_len - 7] = (bit_len >> 48) as u8;
116    padded[padded_len - 6] = (bit_len >> 40) as u8;
117    padded[padded_len - 5] = (bit_len >> 32) as u8;
118    padded[padded_len - 4] = (bit_len >> 24) as u8;
119    padded[padded_len - 3] = (bit_len >> 16) as u8;
120    padded[padded_len - 2] = (bit_len >> 8) as u8;
121    padded[padded_len - 1] = bit_len as u8;
122
123    // First block
124    let mut block0 = [0u8; 64];
125    i = 0;
126    while i < 64 {
127        block0[i] = padded[i];
128        i += 1;
129    }
130    let h = compress_block(h, block0);
131
132    // Second block (if needed)
133    let h = if padded_len > 64 {
134        let mut block1 = [0u8; 64];
135        i = 0;
136        while i < 64 {
137            block1[i] = padded[64 + i];
138            i += 1;
139        }
140        compress_block(h, block1)
141    } else {
142        h
143    };
144
145    [
146        (h[0] >> 24) as u8,
147        (h[0] >> 16) as u8,
148        (h[0] >> 8) as u8,
149        h[0] as u8,
150        (h[1] >> 24) as u8,
151        (h[1] >> 16) as u8,
152        (h[1] >> 8) as u8,
153        h[1] as u8,
154    ]
155}
156
157// Refresh
158disc!(REFRESH_RESERVE, "refresh_reserve");
159disc!(REFRESH_RESERVES_BATCH, "refresh_reserves_batch");
160disc!(REFRESH_OBLIGATION, "refresh_obligation");
161
162// Deposit
163disc!(DEPOSIT_RESERVE_LIQUIDITY, "deposit_reserve_liquidity");
164disc!(
165    DEPOSIT_OBLIGATION_COLLATERAL_V2,
166    "deposit_obligation_collateral_v2"
167);
168disc!(
169    DEPOSIT_RESERVE_LIQUIDITY_AND_OBLIGATION_COLLATERAL_V2,
170    "deposit_reserve_liquidity_and_obligation_collateral_v2"
171);
172
173// Withdraw
174disc!(REDEEM_RESERVE_COLLATERAL, "redeem_reserve_collateral");
175disc!(
176    WITHDRAW_OBLIGATION_COLLATERAL_V2,
177    "withdraw_obligation_collateral_v2"
178);
179disc!(
180    WITHDRAW_OBLIGATION_COLLATERAL_AND_REDEEM_RESERVE_COLLATERAL_V2,
181    "withdraw_obligation_collateral_and_redeem_reserve_collateral_v2"
182);
183
184// Borrow
185disc!(
186    BORROW_OBLIGATION_LIQUIDITY_V2,
187    "borrow_obligation_liquidity_v2"
188);
189
190// Repay
191disc!(
192    REPAY_OBLIGATION_LIQUIDITY_V2,
193    "repay_obligation_liquidity_v2"
194);
195disc!(
196    REPAY_AND_WITHDRAW_AND_REDEEM,
197    "repay_and_withdraw_and_redeem"
198);
199
200// Compound
201disc!(DEPOSIT_AND_WITHDRAW, "deposit_and_withdraw");
202
203// Liquidate
204disc!(
205    LIQUIDATE_OBLIGATION_AND_REDEEM_RESERVE_COLLATERAL_V2,
206    "liquidate_obligation_and_redeem_reserve_collateral_v2"
207);
208
209// Flash
210disc!(
211    FLASH_BORROW_RESERVE_LIQUIDITY,
212    "flash_borrow_reserve_liquidity"
213);
214disc!(
215    FLASH_REPAY_RESERVE_LIQUIDITY,
216    "flash_repay_reserve_liquidity"
217);
218
219// Obligation lifecycle
220disc!(INIT_OBLIGATION, "init_obligation");
221disc!(
222    INIT_OBLIGATION_FARMS_FOR_RESERVE,
223    "init_obligation_farms_for_reserve"
224);
225disc!(
226    REFRESH_OBLIGATION_FARMS_FOR_RESERVE,
227    "refresh_obligation_farms_for_reserve"
228);
229disc!(REQUEST_ELEVATION_GROUP, "request_elevation_group");
230
231// Orders
232disc!(SET_OBLIGATION_ORDER, "set_obligation_order");
233disc!(SET_BORROW_ORDER, "set_borrow_order");
234disc!(FILL_BORROW_ORDER, "fill_borrow_order");
235
236// Referrer
237disc!(INIT_REFERRER_TOKEN_STATE, "init_referrer_token_state");
238disc!(INIT_USER_METADATA, "init_user_metadata");
239disc!(WITHDRAW_REFERRER_FEES, "withdraw_referrer_fees");
240disc!(
241    INIT_REFERRER_STATE_AND_SHORT_URL,
242    "init_referrer_state_and_short_url"
243);
244disc!(
245    DELETE_REFERRER_STATE_AND_SHORT_URL,
246    "delete_referrer_state_and_short_url"
247);
248
249// Withdraw queue
250disc!(ENQUEUE_TO_WITHDRAW, "enqueue_to_withdraw");
251disc!(WITHDRAW_QUEUED_LIQUIDITY, "withdraw_queued_liquidity");
252disc!(
253    RECOVER_INVALID_TICKET_COLLATERAL,
254    "recover_invalid_ticket_collateral"
255);
256disc!(CANCEL_WITHDRAW_TICKET, "cancel_withdraw_ticket");
257
258// Rollover / obligation config
259disc!(ROLLOVER_FIXED_TERM_BORROW, "rollover_fixed_term_borrow");
260disc!(UPDATE_OBLIGATION_CONFIG, "update_obligation_config");
261
262// Admin
263disc!(CLONE_RESERVE_CONFIG, "clone_reserve_config");
264
265/// Known Klend instruction types, identified by their 8-byte discriminator.
266#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
267#[non_exhaustive]
268pub enum KlendInstruction {
269    RefreshReserve,
270    RefreshReservesBatch,
271    RefreshObligation,
272    DepositReserveLiquidity,
273    DepositObligationCollateralV2,
274    DepositReserveLiquidityAndObligationCollateralV2,
275    RedeemReserveCollateral,
276    WithdrawObligationCollateralV2,
277    WithdrawObligationCollateralAndRedeemReserveCollateralV2,
278    BorrowObligationLiquidityV2,
279    RepayObligationLiquidityV2,
280    RepayAndWithdrawAndRedeem,
281    DepositAndWithdraw,
282    LiquidateObligationAndRedeemReserveCollateralV2,
283    FlashBorrowReserveLiquidity,
284    FlashRepayReserveLiquidity,
285    InitObligation,
286    InitObligationFarmsForReserve,
287    RefreshObligationFarmsForReserve,
288    RequestElevationGroup,
289    SetObligationOrder,
290    SetBorrowOrder,
291    FillBorrowOrder,
292    InitReferrerTokenState,
293    InitUserMetadata,
294    WithdrawReferrerFees,
295    InitReferrerStateAndShortUrl,
296    DeleteReferrerStateAndShortUrl,
297    EnqueueToWithdraw,
298    WithdrawQueuedLiquidity,
299    RecoverInvalidTicketCollateral,
300    CancelWithdrawTicket,
301    RolloverFixedTermBorrow,
302    UpdateObligationConfig,
303    CloneReserveConfig,
304}
305
306impl core::fmt::Display for KlendInstruction {
307    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
308        write!(f, "{self:?}")
309    }
310}
311
312/// Identify a Klend instruction from its raw instruction data.
313///
314/// Returns `Some(variant)` if the first 8 bytes match a known discriminator,
315/// `None` otherwise.
316pub fn identify_instruction(data: &[u8]) -> Option<KlendInstruction> {
317    if data.len() < 8 {
318        return None;
319    }
320    let mut disc = [0u8; 8];
321    disc.copy_from_slice(&data[..8]);
322
323    match disc {
324        d if d == REFRESH_RESERVE => Some(KlendInstruction::RefreshReserve),
325        d if d == REFRESH_RESERVES_BATCH => Some(KlendInstruction::RefreshReservesBatch),
326        d if d == REFRESH_OBLIGATION => Some(KlendInstruction::RefreshObligation),
327        d if d == DEPOSIT_RESERVE_LIQUIDITY => Some(KlendInstruction::DepositReserveLiquidity),
328        d if d == DEPOSIT_OBLIGATION_COLLATERAL_V2 => {
329            Some(KlendInstruction::DepositObligationCollateralV2)
330        }
331        d if d == DEPOSIT_RESERVE_LIQUIDITY_AND_OBLIGATION_COLLATERAL_V2 => {
332            Some(KlendInstruction::DepositReserveLiquidityAndObligationCollateralV2)
333        }
334        d if d == REDEEM_RESERVE_COLLATERAL => Some(KlendInstruction::RedeemReserveCollateral),
335        d if d == WITHDRAW_OBLIGATION_COLLATERAL_V2 => {
336            Some(KlendInstruction::WithdrawObligationCollateralV2)
337        }
338        d if d == WITHDRAW_OBLIGATION_COLLATERAL_AND_REDEEM_RESERVE_COLLATERAL_V2 => {
339            Some(KlendInstruction::WithdrawObligationCollateralAndRedeemReserveCollateralV2)
340        }
341        d if d == BORROW_OBLIGATION_LIQUIDITY_V2 => {
342            Some(KlendInstruction::BorrowObligationLiquidityV2)
343        }
344        d if d == REPAY_OBLIGATION_LIQUIDITY_V2 => {
345            Some(KlendInstruction::RepayObligationLiquidityV2)
346        }
347        d if d == REPAY_AND_WITHDRAW_AND_REDEEM => {
348            Some(KlendInstruction::RepayAndWithdrawAndRedeem)
349        }
350        d if d == DEPOSIT_AND_WITHDRAW => Some(KlendInstruction::DepositAndWithdraw),
351        d if d == LIQUIDATE_OBLIGATION_AND_REDEEM_RESERVE_COLLATERAL_V2 => {
352            Some(KlendInstruction::LiquidateObligationAndRedeemReserveCollateralV2)
353        }
354        d if d == FLASH_BORROW_RESERVE_LIQUIDITY => {
355            Some(KlendInstruction::FlashBorrowReserveLiquidity)
356        }
357        d if d == FLASH_REPAY_RESERVE_LIQUIDITY => {
358            Some(KlendInstruction::FlashRepayReserveLiquidity)
359        }
360        d if d == INIT_OBLIGATION => Some(KlendInstruction::InitObligation),
361        d if d == INIT_OBLIGATION_FARMS_FOR_RESERVE => {
362            Some(KlendInstruction::InitObligationFarmsForReserve)
363        }
364        d if d == REFRESH_OBLIGATION_FARMS_FOR_RESERVE => {
365            Some(KlendInstruction::RefreshObligationFarmsForReserve)
366        }
367        d if d == REQUEST_ELEVATION_GROUP => Some(KlendInstruction::RequestElevationGroup),
368        d if d == SET_OBLIGATION_ORDER => Some(KlendInstruction::SetObligationOrder),
369        d if d == SET_BORROW_ORDER => Some(KlendInstruction::SetBorrowOrder),
370        d if d == FILL_BORROW_ORDER => Some(KlendInstruction::FillBorrowOrder),
371        d if d == INIT_REFERRER_TOKEN_STATE => Some(KlendInstruction::InitReferrerTokenState),
372        d if d == INIT_USER_METADATA => Some(KlendInstruction::InitUserMetadata),
373        d if d == WITHDRAW_REFERRER_FEES => Some(KlendInstruction::WithdrawReferrerFees),
374        d if d == INIT_REFERRER_STATE_AND_SHORT_URL => {
375            Some(KlendInstruction::InitReferrerStateAndShortUrl)
376        }
377        d if d == DELETE_REFERRER_STATE_AND_SHORT_URL => {
378            Some(KlendInstruction::DeleteReferrerStateAndShortUrl)
379        }
380        d if d == ENQUEUE_TO_WITHDRAW => Some(KlendInstruction::EnqueueToWithdraw),
381        d if d == WITHDRAW_QUEUED_LIQUIDITY => Some(KlendInstruction::WithdrawQueuedLiquidity),
382        d if d == RECOVER_INVALID_TICKET_COLLATERAL => {
383            Some(KlendInstruction::RecoverInvalidTicketCollateral)
384        }
385        d if d == CANCEL_WITHDRAW_TICKET => Some(KlendInstruction::CancelWithdrawTicket),
386        d if d == ROLLOVER_FIXED_TERM_BORROW => Some(KlendInstruction::RolloverFixedTermBorrow),
387        d if d == UPDATE_OBLIGATION_CONFIG => Some(KlendInstruction::UpdateObligationConfig),
388        d if d == CLONE_RESERVE_CONFIG => Some(KlendInstruction::CloneReserveConfig),
389        _ => None,
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396
397    macro_rules! check_disc {
398        ($name:expr, $constant:ident) => {
399            assert_eq!(
400                compute_discriminator($name),
401                $constant,
402                "Discriminator mismatch for {}",
403                $name
404            );
405        };
406    }
407
408    #[test]
409    fn verify_all_discriminators() {
410        check_disc!("refresh_reserve", REFRESH_RESERVE);
411        check_disc!("refresh_reserves_batch", REFRESH_RESERVES_BATCH);
412        check_disc!("refresh_obligation", REFRESH_OBLIGATION);
413        check_disc!("deposit_reserve_liquidity", DEPOSIT_RESERVE_LIQUIDITY);
414        check_disc!(
415            "deposit_obligation_collateral_v2",
416            DEPOSIT_OBLIGATION_COLLATERAL_V2
417        );
418        check_disc!(
419            "deposit_reserve_liquidity_and_obligation_collateral_v2",
420            DEPOSIT_RESERVE_LIQUIDITY_AND_OBLIGATION_COLLATERAL_V2
421        );
422        check_disc!("redeem_reserve_collateral", REDEEM_RESERVE_COLLATERAL);
423        check_disc!(
424            "withdraw_obligation_collateral_v2",
425            WITHDRAW_OBLIGATION_COLLATERAL_V2
426        );
427        check_disc!(
428            "withdraw_obligation_collateral_and_redeem_reserve_collateral_v2",
429            WITHDRAW_OBLIGATION_COLLATERAL_AND_REDEEM_RESERVE_COLLATERAL_V2
430        );
431        check_disc!(
432            "borrow_obligation_liquidity_v2",
433            BORROW_OBLIGATION_LIQUIDITY_V2
434        );
435        check_disc!(
436            "repay_obligation_liquidity_v2",
437            REPAY_OBLIGATION_LIQUIDITY_V2
438        );
439        check_disc!(
440            "repay_and_withdraw_and_redeem",
441            REPAY_AND_WITHDRAW_AND_REDEEM
442        );
443        check_disc!("deposit_and_withdraw", DEPOSIT_AND_WITHDRAW);
444        check_disc!(
445            "liquidate_obligation_and_redeem_reserve_collateral_v2",
446            LIQUIDATE_OBLIGATION_AND_REDEEM_RESERVE_COLLATERAL_V2
447        );
448        check_disc!(
449            "flash_borrow_reserve_liquidity",
450            FLASH_BORROW_RESERVE_LIQUIDITY
451        );
452        check_disc!(
453            "flash_repay_reserve_liquidity",
454            FLASH_REPAY_RESERVE_LIQUIDITY
455        );
456        check_disc!("init_obligation", INIT_OBLIGATION);
457        check_disc!(
458            "init_obligation_farms_for_reserve",
459            INIT_OBLIGATION_FARMS_FOR_RESERVE
460        );
461        check_disc!(
462            "refresh_obligation_farms_for_reserve",
463            REFRESH_OBLIGATION_FARMS_FOR_RESERVE
464        );
465        check_disc!("request_elevation_group", REQUEST_ELEVATION_GROUP);
466        check_disc!("set_obligation_order", SET_OBLIGATION_ORDER);
467        check_disc!("set_borrow_order", SET_BORROW_ORDER);
468        check_disc!("fill_borrow_order", FILL_BORROW_ORDER);
469        check_disc!("init_referrer_token_state", INIT_REFERRER_TOKEN_STATE);
470        check_disc!("init_user_metadata", INIT_USER_METADATA);
471        check_disc!("withdraw_referrer_fees", WITHDRAW_REFERRER_FEES);
472        check_disc!(
473            "init_referrer_state_and_short_url",
474            INIT_REFERRER_STATE_AND_SHORT_URL
475        );
476        check_disc!(
477            "delete_referrer_state_and_short_url",
478            DELETE_REFERRER_STATE_AND_SHORT_URL
479        );
480        check_disc!("enqueue_to_withdraw", ENQUEUE_TO_WITHDRAW);
481        check_disc!("withdraw_queued_liquidity", WITHDRAW_QUEUED_LIQUIDITY);
482        check_disc!(
483            "recover_invalid_ticket_collateral",
484            RECOVER_INVALID_TICKET_COLLATERAL
485        );
486        check_disc!("cancel_withdraw_ticket", CANCEL_WITHDRAW_TICKET);
487        check_disc!("rollover_fixed_term_borrow", ROLLOVER_FIXED_TERM_BORROW);
488        check_disc!("update_obligation_config", UPDATE_OBLIGATION_CONFIG);
489        check_disc!("clone_reserve_config", CLONE_RESERVE_CONFIG);
490    }
491}