Skip to main content

cougr_core/
macros.rs

1/// Helper macro to serialize a single field to big-endian bytes.
2#[macro_export]
3#[doc(hidden)]
4macro_rules! __cougr_serialize_field {
5    ($bytes:ident, $env:ident, $value:expr, i32) => {
6        $bytes.append(&soroban_sdk::Bytes::from_array($env, &$value.to_be_bytes()));
7    };
8    ($bytes:ident, $env:ident, $value:expr, u32) => {
9        $bytes.append(&soroban_sdk::Bytes::from_array($env, &$value.to_be_bytes()));
10    };
11    ($bytes:ident, $env:ident, $value:expr, i64) => {
12        $bytes.append(&soroban_sdk::Bytes::from_slice($env, &$value.to_be_bytes()));
13    };
14    ($bytes:ident, $env:ident, $value:expr, u64) => {
15        $bytes.append(&soroban_sdk::Bytes::from_slice($env, &$value.to_be_bytes()));
16    };
17    ($bytes:ident, $env:ident, $value:expr, i128) => {
18        $bytes.append(&soroban_sdk::Bytes::from_slice($env, &$value.to_be_bytes()));
19    };
20    ($bytes:ident, $env:ident, $value:expr, u8) => {
21        $bytes.append(&soroban_sdk::Bytes::from_array($env, &[$value]));
22    };
23    ($bytes:ident, $env:ident, $value:expr, bool) => {
24        $bytes.append(&soroban_sdk::Bytes::from_array(
25            $env,
26            &[if $value { 1u8 } else { 0u8 }],
27        ));
28    };
29    ($bytes:ident, $env:ident, $value:expr, u128) => {
30        $bytes.append(&soroban_sdk::Bytes::from_slice($env, &$value.to_be_bytes()));
31    };
32    ($bytes:ident, $env:ident, $value:expr, bytes32) => {
33        $bytes.append(&soroban_sdk::Bytes::from_slice($env, &$value.to_array()));
34    };
35}
36
37/// Helper macro to get the byte size of a field type.
38#[macro_export]
39#[doc(hidden)]
40macro_rules! __cougr_field_size {
41    (i32) => {
42        4u32
43    };
44    (u32) => {
45        4u32
46    };
47    (i64) => {
48        8u32
49    };
50    (u64) => {
51        8u32
52    };
53    (i128) => {
54        16u32
55    };
56    (u8) => {
57        1u32
58    };
59    (bool) => {
60        1u32
61    };
62    (u128) => {
63        16u32
64    };
65    (bytes32) => {
66        32u32
67    };
68}
69
70/// Helper macro to deserialize a single field from big-endian bytes.
71#[macro_export]
72#[doc(hidden)]
73macro_rules! __cougr_deserialize_field {
74    ($env:ident, $data:ident, $offset:expr, i32) => {{
75        let val = i32::from_be_bytes([
76            $data.get($offset)?,
77            $data.get($offset + 1)?,
78            $data.get($offset + 2)?,
79            $data.get($offset + 3)?,
80        ]);
81        val
82    }};
83    ($env:ident, $data:ident, $offset:expr, u32) => {{
84        let val = u32::from_be_bytes([
85            $data.get($offset)?,
86            $data.get($offset + 1)?,
87            $data.get($offset + 2)?,
88            $data.get($offset + 3)?,
89        ]);
90        val
91    }};
92    ($env:ident, $data:ident, $offset:expr, i64) => {{
93        let val = i64::from_be_bytes([
94            $data.get($offset)?,
95            $data.get($offset + 1)?,
96            $data.get($offset + 2)?,
97            $data.get($offset + 3)?,
98            $data.get($offset + 4)?,
99            $data.get($offset + 5)?,
100            $data.get($offset + 6)?,
101            $data.get($offset + 7)?,
102        ]);
103        val
104    }};
105    ($env:ident, $data:ident, $offset:expr, u64) => {{
106        let val = u64::from_be_bytes([
107            $data.get($offset)?,
108            $data.get($offset + 1)?,
109            $data.get($offset + 2)?,
110            $data.get($offset + 3)?,
111            $data.get($offset + 4)?,
112            $data.get($offset + 5)?,
113            $data.get($offset + 6)?,
114            $data.get($offset + 7)?,
115        ]);
116        val
117    }};
118    ($env:ident, $data:ident, $offset:expr, i128) => {{
119        let val = i128::from_be_bytes([
120            $data.get($offset)?,
121            $data.get($offset + 1)?,
122            $data.get($offset + 2)?,
123            $data.get($offset + 3)?,
124            $data.get($offset + 4)?,
125            $data.get($offset + 5)?,
126            $data.get($offset + 6)?,
127            $data.get($offset + 7)?,
128            $data.get($offset + 8)?,
129            $data.get($offset + 9)?,
130            $data.get($offset + 10)?,
131            $data.get($offset + 11)?,
132            $data.get($offset + 12)?,
133            $data.get($offset + 13)?,
134            $data.get($offset + 14)?,
135            $data.get($offset + 15)?,
136        ]);
137        val
138    }};
139    ($env:ident, $data:ident, $offset:expr, u8) => {{
140        let val: u8 = $data.get($offset)?;
141        val
142    }};
143    ($env:ident, $data:ident, $offset:expr, bool) => {{
144        let val = $data.get($offset)? != 0;
145        val
146    }};
147    ($env:ident, $data:ident, $offset:expr, u128) => {{
148        let val = u128::from_be_bytes([
149            $data.get($offset)?,
150            $data.get($offset + 1)?,
151            $data.get($offset + 2)?,
152            $data.get($offset + 3)?,
153            $data.get($offset + 4)?,
154            $data.get($offset + 5)?,
155            $data.get($offset + 6)?,
156            $data.get($offset + 7)?,
157            $data.get($offset + 8)?,
158            $data.get($offset + 9)?,
159            $data.get($offset + 10)?,
160            $data.get($offset + 11)?,
161            $data.get($offset + 12)?,
162            $data.get($offset + 13)?,
163            $data.get($offset + 14)?,
164            $data.get($offset + 15)?,
165        ]);
166        val
167    }};
168    ($env:ident, $data:ident, $offset:expr, bytes32) => {{
169        let mut arr = [0u8; 32];
170        let mut i = 0u32;
171        while i < 32 {
172            arr[i as usize] = $data.get($offset + i)?;
173            i += 1;
174        }
175        soroban_sdk::BytesN::from_array($env, &arr)
176    }};
177}
178
179/// Implement `ComponentTrait` for a struct with fixed-size fields.
180///
181/// Generates serialization/deserialization using big-endian byte encoding.
182///
183/// # Supported field types
184/// `i32` (4 bytes), `u32` (4 bytes), `i64` (8 bytes), `u64` (8 bytes),
185/// `i128` (16 bytes), `u128` (16 bytes), `u8` (1 byte), `bool` (1 byte),
186/// `bytes32` (32 bytes — use for `BytesN<32>` fields)
187///
188/// # Note
189/// The symbol name must be at most 9 characters (Soroban `symbol_short!` limit).
190///
191/// # Example
192/// ```no_run
193/// use cougr_core::impl_component;
194/// use soroban_sdk::contracttype;
195///
196/// #[contracttype]
197/// #[derive(Clone, Debug)]
198/// pub struct Position {
199///     pub x: i32,
200///     pub y: i32,
201/// }
202///
203/// impl_component!(Position, "position", Table, { x: i32, y: i32 });
204/// ```
205#[macro_export]
206macro_rules! impl_component {
207    ($struct_name:ident, $symbol:expr, $storage:ident, { $( $field:ident : $ftype:tt ),* $(,)? }) => {
208        impl $crate::component::ComponentTrait for $struct_name {
209            fn component_type() -> soroban_sdk::Symbol {
210                soroban_sdk::symbol_short!($symbol)
211            }
212
213            fn serialize(&self, env: &soroban_sdk::Env) -> soroban_sdk::Bytes {
214                let mut bytes = soroban_sdk::Bytes::new(env);
215                $(
216                    $crate::__cougr_serialize_field!(bytes, env, self.$field, $ftype);
217                )*
218                bytes
219            }
220
221            fn deserialize(_env: &soroban_sdk::Env, data: &soroban_sdk::Bytes) -> Option<Self> {
222                let expected_len: u32 = 0 $( + $crate::__cougr_field_size!($ftype) )*;
223                if data.len() != expected_len {
224                    return None;
225                }
226                let mut _offset: u32 = 0;
227                $(
228                    let $field = $crate::__cougr_deserialize_field!(_env, data, _offset, $ftype);
229                    _offset += $crate::__cougr_field_size!($ftype);
230                )*
231                Some(Self { $( $field ),* })
232            }
233
234            fn default_storage() -> $crate::component::ComponentStorage {
235                $crate::component::ComponentStorage::$storage
236            }
237        }
238    };
239}
240
241/// Implement `ComponentTrait` for a marker (unit) struct.
242///
243/// Marker components have no data and serialize to a single byte `[1]`.
244///
245/// # Example
246/// ```no_run
247/// use cougr_core::impl_marker_component;
248///
249/// pub struct SnakeHead;
250///
251/// impl_marker_component!(SnakeHead, "snkhead", Sparse);
252/// ```
253#[macro_export]
254macro_rules! impl_marker_component {
255    ($struct_name:ident, $symbol:expr, $storage:ident) => {
256        impl $crate::component::ComponentTrait for $struct_name {
257            fn component_type() -> soroban_sdk::Symbol {
258                soroban_sdk::symbol_short!($symbol)
259            }
260
261            fn serialize(&self, env: &soroban_sdk::Env) -> soroban_sdk::Bytes {
262                soroban_sdk::Bytes::from_array(env, &[1u8])
263            }
264
265            fn deserialize(_env: &soroban_sdk::Env, data: &soroban_sdk::Bytes) -> Option<Self> {
266                if data.len() != 1 {
267                    return None;
268                }
269                Some(Self)
270            }
271
272            fn default_storage() -> $crate::component::ComponentStorage {
273                $crate::component::ComponentStorage::$storage
274            }
275        }
276    };
277}
278
279/// Implement `ResourceTrait` for a struct with fixed-size fields.
280///
281/// Generates serialization/deserialization using big-endian byte encoding.
282///
283/// # Example
284/// ```no_run
285/// use cougr_core::impl_resource;
286/// use soroban_sdk::contracttype;
287///
288/// #[contracttype]
289/// #[derive(Clone)]
290/// pub struct GameState {
291///     pub score: i32,
292///     pub level: i32,
293///     pub is_game_over: bool,
294/// }
295///
296/// impl_resource!(GameState, "gamestat", { score: i32, level: i32, is_game_over: bool });
297/// ```
298#[macro_export]
299macro_rules! impl_resource {
300    ($struct_name:ident, $symbol:expr, { $( $field:ident : $ftype:tt ),* $(,)? }) => {
301        impl $crate::resource::ResourceTrait for $struct_name {
302            fn resource_type() -> soroban_sdk::Symbol {
303                soroban_sdk::symbol_short!($symbol)
304            }
305
306            fn serialize(&self, env: &soroban_sdk::Env) -> soroban_sdk::Bytes {
307                let mut bytes = soroban_sdk::Bytes::new(env);
308                $(
309                    $crate::__cougr_serialize_field!(bytes, env, self.$field, $ftype);
310                )*
311                bytes
312            }
313
314            fn deserialize(_env: &soroban_sdk::Env, data: &soroban_sdk::Bytes) -> Option<Self> {
315                let expected_len: u32 = 0 $( + $crate::__cougr_field_size!($ftype) )*;
316                if data.len() != expected_len {
317                    return None;
318                }
319                let mut _offset: u32 = 0;
320                $(
321                    let $field = $crate::__cougr_deserialize_field!(_env, data, _offset, $ftype);
322                    _offset += $crate::__cougr_field_size!($ftype);
323                )*
324                Some(Self { $( $field ),* })
325            }
326        }
327    };
328}