templar_vault_kernel/
effects.rs

1extern crate alloc;
2
3use alloc::vec::Vec;
4
5use crate::types::Address;
6#[cfg(all(feature = "borsh", not(feature = "soroban")))]
7use borsh::{BorshDeserialize, BorshSerialize};
8#[cfg(all(feature = "serde", not(feature = "soroban")))]
9use serde::{Deserialize, Serialize};
10
11// KernelEffect: gate all serde behind not(soroban) — never serialized in Soroban.
12#[cfg_attr(
13    all(feature = "borsh", not(feature = "soroban")),
14    derive(BorshSerialize, BorshDeserialize)
15)]
16#[cfg_attr(
17    all(feature = "postcard", not(feature = "serde"), not(feature = "soroban")),
18    derive(serde::Serialize, serde::Deserialize)
19)]
20#[cfg_attr(
21    all(feature = "serde", not(feature = "soroban")),
22    derive(Serialize, Deserialize)
23)]
24#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
25#[derive(Clone, PartialEq, Eq)]
26pub enum KernelEffect {
27    /// Mint new share tokens to an owner.
28    MintShares { owner: Address, shares: u128 },
29    /// Burn share tokens from an owner.
30    BurnShares { owner: Address, shares: u128 },
31    BurnSharesFrom {
32        spender: Address,
33        owner: Address,
34        shares: u128,
35    },
36    /// Transfer shares between addresses.
37    TransferShares {
38        from: Address,
39        to: Address,
40        shares: u128,
41    },
42    /// Transfer underlying assets to a recipient.
43    TransferAssets { to: Address, amount: u128 },
44    /// Transfer underlying assets between two addresses.
45    TransferAssetsFrom {
46        from: Address,
47        to: Address,
48        amount: u128,
49    },
50    /// Make an external cross-contract call (NEAR only).
51    #[cfg(feature = "near")]
52    ExternalCall {
53        target: Address,
54        selector: u32,
55        args: Vec<u8>,
56        attached_value: u128,
57        callback: Option<KernelCallback>,
58    },
59    /// Charge storage costs to a payer (NEAR only).
60    #[cfg(feature = "near")]
61    ChargeStorage { payer: Address, bytes: u64 },
62    /// Emit an event for indexers and clients.
63    EmitEvent { event: KernelEvent },
64}
65
66#[cfg_attr(
67    all(feature = "borsh", not(feature = "soroban")),
68    derive(BorshSerialize, BorshDeserialize)
69)]
70#[cfg_attr(
71    all(feature = "postcard", not(feature = "serde"), not(feature = "soroban")),
72    derive(serde::Serialize, serde::Deserialize)
73)]
74#[cfg_attr(
75    all(feature = "serde", not(feature = "soroban")),
76    derive(Serialize, Deserialize)
77)]
78#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
79#[derive(Clone, PartialEq, Eq)]
80pub enum KernelCallback {
81    /// Callback for allocation step completion.
82    AllocationStep,
83    /// Callback for withdrawal step completion.
84    WithdrawalStep,
85    /// Callback for refresh step completion.
86    RefreshStep,
87    /// Callback for payout transfer completion.
88    PayoutTransfer,
89}
90
91#[cfg_attr(
92    all(feature = "borsh", not(feature = "soroban")),
93    derive(BorshSerialize, BorshDeserialize)
94)]
95#[cfg_attr(
96    all(feature = "postcard", not(feature = "serde"), not(feature = "soroban")),
97    derive(serde::Serialize, serde::Deserialize)
98)]
99#[cfg_attr(
100    all(feature = "serde", not(feature = "soroban")),
101    derive(Serialize, Deserialize)
102)]
103#[cfg_attr(all(feature = "serde", feature = "soroban"), derive(serde::Serialize))]
104#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
105#[derive(Clone, PartialEq, Eq)]
106pub enum WithdrawalSkipReason {
107    ZeroExpectedAssets,
108    Restricted,
109}
110
111#[cfg_attr(
112    all(feature = "borsh", not(feature = "soroban")),
113    derive(BorshSerialize, BorshDeserialize)
114)]
115#[cfg_attr(
116    all(feature = "postcard", not(feature = "serde"), not(feature = "soroban")),
117    derive(serde::Serialize, serde::Deserialize)
118)]
119#[cfg_attr(
120    all(feature = "serde", not(feature = "soroban")),
121    derive(Serialize, Deserialize)
122)]
123#[cfg_attr(all(feature = "serde", feature = "soroban"), derive(serde::Serialize))]
124#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
125#[derive(Clone, PartialEq, Eq)]
126pub enum KernelEvent {
127    /// Allocation operation started.
128    AllocationStarted {
129        op_id: u64,
130        total: u128,
131        plan_len: u32,
132    },
133    /// Allocation step failed and allocation aborted.
134    AllocationStepFailed {
135        op_id: u64,
136        index: u32,
137        remaining: u128,
138        /// Amount successfully allocated before failure (original total - remaining).
139        /// Caller uses this to restore idle_assets correctly.
140        total_allocated: u128,
141    },
142    /// Allocation completed (either returns to Idle or proceeds to withdrawal).
143    AllocationCompleted { op_id: u64, has_withdrawal: bool },
144    /// Withdrawal operation started.
145    WithdrawalStarted {
146        op_id: u64,
147        amount: u128,
148        escrow_shares: u128,
149        owner: Address,
150        receiver: Address,
151    },
152    /// Withdrawal collected enough to proceed to payout.
153    WithdrawalCollected {
154        op_id: u64,
155        burn_shares: u128,
156        collected: u128,
157    },
158    /// Withdrawal stopped and escrow refunded.
159    WithdrawalStopped { op_id: u64, escrow_shares: u128 },
160    /// Withdrawal skipped and escrow refunded without entering withdrawal execution.
161    WithdrawalSkipped {
162        id: u64,
163        owner: Address,
164        receiver: Address,
165        escrow_shares: u128,
166        expected_assets: u128,
167        reason: WithdrawalSkipReason,
168    },
169    /// Refresh operation started.
170    RefreshStarted { op_id: u64, plan_len: u32 },
171    /// Refresh operation completed.
172    RefreshCompleted { op_id: u64 },
173    /// Payout completed (success or failure).
174    PayoutCompleted {
175        op_id: u64,
176        success: bool,
177        burn_shares: u128,
178        refund_shares: u128,
179        amount: u128,
180    },
181    /// Deposit processed and shares minted.
182    DepositProcessed {
183        owner: Address,
184        receiver: Address,
185        assets_in: u128,
186        shares_out: u128,
187    },
188    AtomicWithdrawProcessed {
189        owner: Address,
190        receiver: Address,
191        shares_burned: u128,
192        assets_out: u128,
193    },
194    /// Withdrawal requested and enqueued.
195    WithdrawalRequested {
196        id: u64,
197        owner: Address,
198        receiver: Address,
199        shares: u128,
200        expected_assets: u128,
201    },
202    /// External assets synchronized for an operation.
203    ExternalAssetsSynced {
204        op_id: u64,
205        new_external_assets: u128,
206        total_assets: u128,
207    },
208    /// Fees refreshed for the vault.
209    FeesRefreshed { now_ns: u64, total_assets: u128 },
210    /// Pause state updated.
211    PauseUpdated { paused: bool },
212    /// Emergency reset forced the vault back to Idle.
213    EmergencyResetCompleted { op_id: u64, from_state: u32 },
214}
215
216impl From<KernelEvent> for KernelEffect {
217    fn from(event: KernelEvent) -> Self {
218        Self::EmitEvent { event }
219    }
220}
221
222impl KernelEffect {
223    /// Collect all addresses that must be resolved before this effect can be applied.
224    pub fn required_addresses(&self) -> Vec<Address> {
225        match self {
226            KernelEffect::MintShares { owner, .. } => alloc::vec![*owner],
227            KernelEffect::BurnShares { owner, .. } => alloc::vec![*owner],
228            KernelEffect::BurnSharesFrom { spender, owner, .. } => alloc::vec![*spender, *owner],
229            KernelEffect::TransferShares { from, to, .. } => alloc::vec![*from, *to],
230            KernelEffect::TransferAssets { to, .. } => alloc::vec![*to],
231            KernelEffect::TransferAssetsFrom { from, to, .. } => alloc::vec![*from, *to],
232            #[cfg(feature = "near")]
233            KernelEffect::ExternalCall { target, .. } => alloc::vec![*target],
234            #[cfg(feature = "near")]
235            KernelEffect::ChargeStorage { payer, .. } => alloc::vec![*payer],
236            KernelEffect::EmitEvent { event } => event.required_addresses(),
237        }
238    }
239}
240
241impl KernelEvent {
242    /// Collect all addresses referenced by this event.
243    pub fn required_addresses(&self) -> Vec<Address> {
244        match self {
245            KernelEvent::WithdrawalStarted {
246                owner, receiver, ..
247            } => alloc::vec![*owner, *receiver],
248            KernelEvent::DepositProcessed {
249                owner, receiver, ..
250            } => alloc::vec![*owner, *receiver],
251            KernelEvent::AtomicWithdrawProcessed {
252                owner, receiver, ..
253            } => alloc::vec![*owner, *receiver],
254            KernelEvent::WithdrawalRequested {
255                owner, receiver, ..
256            } => alloc::vec![*owner, *receiver],
257            KernelEvent::WithdrawalSkipped {
258                owner, receiver, ..
259            } => alloc::vec![*owner, *receiver],
260            _ => alloc::vec![],
261        }
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::{KernelEffect, KernelEvent};
268
269    fn address(byte: u8) -> [u8; 32] {
270        [byte; 32]
271    }
272
273    #[test]
274    fn effect_required_addresses_cover_transfer_shapes() {
275        let owner = crate::Address(address(1));
276        let from = crate::Address(address(2));
277        let to = crate::Address(address(3));
278
279        assert_eq!(
280            KernelEffect::MintShares { owner, shares: 10 }.required_addresses(),
281            alloc::vec![owner]
282        );
283        assert_eq!(
284            KernelEffect::TransferShares {
285                shares: 5,
286                from,
287                to
288            }
289            .required_addresses(),
290            alloc::vec![from, to]
291        );
292        assert_eq!(
293            KernelEffect::TransferAssetsFrom {
294                from,
295                to,
296                amount: 7
297            }
298            .required_addresses(),
299            alloc::vec![from, to]
300        );
301        assert_eq!(
302            KernelEffect::BurnSharesFrom {
303                spender: from,
304                owner,
305                shares: 9,
306            }
307            .required_addresses(),
308            alloc::vec![from, owner]
309        );
310    }
311
312    #[test]
313    fn event_required_addresses_cover_account_events() {
314        let owner = crate::Address(address(4));
315        let receiver = crate::Address(address(5));
316
317        assert_eq!(
318            KernelEvent::WithdrawalStarted {
319                op_id: 1,
320                amount: 20,
321                escrow_shares: 30,
322                owner,
323                receiver,
324            }
325            .required_addresses(),
326            alloc::vec![owner, receiver]
327        );
328        assert_eq!(
329            KernelEvent::AtomicWithdrawProcessed {
330                owner,
331                receiver,
332                shares_burned: 10,
333                assets_out: 5,
334            }
335            .required_addresses(),
336            alloc::vec![owner, receiver]
337        );
338        assert!(KernelEvent::RefreshCompleted { op_id: 9 }
339            .required_addresses()
340            .is_empty());
341    }
342
343    #[test]
344    fn event_converts_into_emit_effect() {
345        let event = KernelEvent::PauseUpdated { paused: true };
346        assert_eq!(
347            KernelEffect::from(event.clone()),
348            KernelEffect::EmitEvent { event }
349        );
350    }
351}