templar_vault_kernel/types/
mod.rs

1//! Chain-agnostic types for the vault kernel.
2//!
3//! These types are designed to be portable across NEAR and Soroban.
4
5#[cfg(feature = "schemars")]
6use alloc::borrow::ToOwned;
7#[cfg(feature = "borsh-schema")]
8use alloc::string::ToString;
9
10use derive_more::{Display, From, Into};
11
12/// Timestamp in nanoseconds.
13#[repr(transparent)]
14#[templar_vault_macros::vault_derive(borsh, borsh_schema, serde, postcard, schemars)]
15#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into, Display)]
16#[display("{_0}")]
17pub struct TimestampNs(pub u64);
18
19#[repr(transparent)]
20#[templar_vault_macros::vault_derive(borsh, borsh_schema, serde, postcard, schemars)]
21#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into, Display)]
22#[display("{_0}")]
23pub struct DurationNs(pub u64);
24
25impl TimestampNs {
26    pub const ZERO: Self = Self(0);
27
28    /// Create a timestamp from raw nanoseconds.
29    pub const fn from_nanos(nanos: u64) -> Self {
30        Self(nanos)
31    }
32
33    /// Return the raw nanosecond value.
34    pub const fn as_u64(self) -> u64 {
35        self.0
36    }
37
38    /// Saturating addition with another timestamp-like nanosecond delta.
39    pub const fn saturating_add(self, rhs: Self) -> Self {
40        Self(self.0.saturating_add(rhs.0))
41    }
42
43    /// Saturating addition with a raw nanosecond delta.
44    pub const fn saturating_add_u64(self, rhs: u64) -> Self {
45        Self(self.0.saturating_add(rhs))
46    }
47
48    /// Saturating subtraction with another timestamp-like nanosecond value.
49    pub const fn saturating_sub(self, rhs: Self) -> Self {
50        Self(self.0.saturating_sub(rhs.0))
51    }
52
53    pub const fn saturating_add_duration(self, rhs: DurationNs) -> Self {
54        Self(self.0.saturating_add(rhs.0))
55    }
56}
57
58impl DurationNs {
59    pub const ZERO: Self = Self(0);
60
61    pub const fn from_nanos(nanos: u64) -> Self {
62        Self(nanos)
63    }
64
65    /// Return the raw nanosecond value.
66    pub const fn as_u64(self) -> u64 {
67        self.0
68    }
69}
70
71/// Expected index in a queue or plan.
72#[repr(transparent)]
73#[templar_vault_macros::vault_derive(borsh, borsh_schema, serde, postcard, schemars)]
74#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into, Display)]
75#[display("{_0}")]
76pub struct ExpectedIdx(pub u32);
77
78/// Actual index reached during processing.
79#[repr(transparent)]
80#[templar_vault_macros::vault_derive(borsh, borsh_schema, serde, postcard, schemars)]
81#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into, Display)]
82#[display("{_0}")]
83pub struct ActualIdx(pub u32);
84
85/// Canonical address bytes.
86/// Executors map chain-native account identifiers to this form (sha256 hash).
87#[repr(transparent)]
88#[templar_vault_macros::vault_derive(borsh, borsh_schema, schemars)]
89#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into)]
90pub struct Address(pub [u8; 32]);
91
92impl Address {
93    /// Create an address from raw bytes.
94    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
95        Self(bytes)
96    }
97
98    /// Return the raw bytes for this address.
99    pub const fn as_bytes(&self) -> &[u8; 32] {
100        &self.0
101    }
102}
103
104impl AsRef<[u8; 32]> for Address {
105    fn as_ref(&self) -> &[u8; 32] {
106        &self.0
107    }
108}
109
110impl AsRef<[u8]> for Address {
111    fn as_ref(&self) -> &[u8] {
112        &self.0
113    }
114}
115
116#[cfg(all(feature = "serde", not(feature = "postcard")))]
117mod address_serde_impl {
118    use super::*;
119    use serde::de;
120    use serde::{Deserialize, Deserializer, Serialize, Serializer};
121
122    impl Serialize for Address {
123        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
124        where
125            S: Serializer,
126        {
127            serializer.serialize_bytes(&self.0)
128        }
129    }
130
131    impl<'de> Deserialize<'de> for Address {
132        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
133        where
134            D: Deserializer<'de>,
135        {
136            struct AddressVisitor;
137
138            impl<'de> de::Visitor<'de> for AddressVisitor {
139                type Value = Address;
140
141                fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
142                    formatter.write_str("exactly 32 bytes for Address")
143                }
144
145                fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
146                where
147                    E: de::Error,
148                {
149                    let bytes: [u8; 32] = v
150                        .try_into()
151                        .map_err(|_| E::custom("expected exactly 32 bytes for Address"))?;
152                    Ok(Address(bytes))
153                }
154
155                fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
156                where
157                    A: de::SeqAccess<'de>,
158                {
159                    let mut bytes = [0u8; 32];
160                    for byte in &mut bytes {
161                        *byte = seq.next_element()?.ok_or_else(|| {
162                            de::Error::custom("expected exactly 32 bytes for Address")
163                        })?;
164                    }
165
166                    if seq.next_element::<u8>()?.is_some() {
167                        return Err(de::Error::custom("expected exactly 32 bytes for Address"));
168                    }
169
170                    Ok(Address(bytes))
171                }
172            }
173
174            deserializer.deserialize_bytes(AddressVisitor)
175        }
176    }
177}
178
179#[cfg(feature = "postcard")]
180mod address_postcard_serde_impl {
181    use super::*;
182    #[cfg(not(feature = "soroban"))]
183    use serde::de;
184    use serde::{Deserialize, Deserializer, Serialize, Serializer};
185
186    impl Serialize for Address {
187        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
188        where
189            S: Serializer,
190        {
191            #[cfg(feature = "soroban")]
192            {
193                self.0.serialize(serializer)
194            }
195
196            #[cfg(not(feature = "soroban"))]
197            {
198                serializer.serialize_bytes(&self.0)
199            }
200        }
201    }
202
203    impl<'de> Deserialize<'de> for Address {
204        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
205        where
206            D: Deserializer<'de>,
207        {
208            #[cfg(feature = "soroban")]
209            {
210                <[u8; 32]>::deserialize(deserializer).map(Address)
211            }
212
213            #[cfg(not(feature = "soroban"))]
214            {
215                struct AddressVisitor;
216
217                impl<'de> de::Visitor<'de> for AddressVisitor {
218                    type Value = Address;
219
220                    fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
221                        formatter.write_str("exactly 32 bytes for Address")
222                    }
223
224                    fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
225                    where
226                        E: de::Error,
227                    {
228                        let bytes: [u8; 32] = v
229                            .try_into()
230                            .map_err(|_| E::custom("expected exactly 32 bytes for Address"))?;
231                        Ok(Address(bytes))
232                    }
233
234                    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
235                    where
236                        A: de::SeqAccess<'de>,
237                    {
238                        let mut bytes = [0u8; 32];
239                        for byte in &mut bytes {
240                            *byte = seq.next_element()?.ok_or_else(|| {
241                                de::Error::custom("expected exactly 32 bytes for Address")
242                            })?;
243                        }
244
245                        if seq.next_element::<u8>()?.is_some() {
246                            return Err(de::Error::custom("expected exactly 32 bytes for Address"));
247                        }
248
249                        Ok(Address(bytes))
250                    }
251                }
252
253                deserializer.deserialize_bytes(AddressVisitor)
254            }
255        }
256    }
257}
258
259/// Asset identifier as a fixed 32-byte hash.
260/// Executors map chain-native asset identifiers (e.g., NEAR account id)
261/// to this form (sha256 hash) and maintain the mapping.
262#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
263#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, From, Into)]
264pub struct AssetId(pub [u8; 32]);
265
266impl AssetId {
267    /// Create an AssetId from raw bytes.
268    pub const fn from_bytes(bytes: [u8; 32]) -> Self {
269        Self(bytes)
270    }
271
272    /// Return the raw bytes for this AssetId.
273    pub const fn as_bytes(&self) -> &[u8; 32] {
274        &self.0
275    }
276}
277
278impl AsRef<[u8; 32]> for AssetId {
279    fn as_ref(&self) -> &[u8; 32] {
280        &self.0
281    }
282}
283
284impl AsRef<[u8]> for AssetId {
285    fn as_ref(&self) -> &[u8] {
286        &self.0
287    }
288}
289
290/// Settlement result for escrowed shares.
291#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
292#[derive(Clone, Copy, PartialEq, Eq)]
293pub struct EscrowSettlement {
294    /// Shares to burn (successfully redeemed).
295    pub to_burn: u128,
296    /// Shares to refund (excess or on failure).
297    pub refund: u128,
298}
299
300impl EscrowSettlement {
301    /// Create a settlement from escrowed shares and intended burned shares.
302    ///
303    /// Burned shares are clamped to `escrow_shares`, and the remainder is refunded.
304    pub fn from_escrow_and_burn(escrow_shares: u128, burn_shares: u128) -> Self {
305        let to_burn = burn_shares.min(escrow_shares);
306        let refund = escrow_shares.saturating_sub(to_burn);
307        Self { to_burn, refund }
308    }
309
310    /// Create a settlement that burns all shares.
311    pub fn burn_all(shares: u128) -> Self {
312        Self {
313            to_burn: shares,
314            refund: 0,
315        }
316    }
317
318    /// Create a settlement that refunds all shares.
319    pub fn refund_all(shares: u128) -> Self {
320        Self {
321            to_burn: 0,
322            refund: shares,
323        }
324    }
325
326    /// Create a settlement with partial burn.
327    pub fn partial(to_burn: u128, refund: u128) -> Self {
328        Self { to_burn, refund }
329    }
330}
331
332/// Kernel version identifier.
333#[templar_vault_macros::vault_derive(borsh, serde, postcard)]
334#[derive(Clone, Copy, PartialEq, Eq, From, Into)]
335pub struct KernelVersion(pub u32);
336
337#[cfg(test)]
338mod tests;