Skip to main content

ave_contract_sdk/
lib.rs

1mod error;
2mod externf;
3use ave_common::ValueWrapper;
4use borsh::{BorshDeserialize, BorshSerialize};
5use error::Error;
6use serde::{Deserialize, Serialize};
7
8/// Maximum size in bytes for data read from host memory.
9/// Prevents excessive allocations from malformed or malicious host input.
10const MAX_DATA_SIZE: i32 = 10_000_000; // 10MB
11
12/// Contract execution context.
13#[derive(Serialize, Deserialize, Debug)]
14pub struct Context<Event> {
15    /// Event being applied to the current state.
16    pub event: Event,
17    /// Whether the event sender is the owner.
18    pub is_owner: bool,
19}
20
21/// Contract execution result.
22#[derive(Serialize, Deserialize, Debug)]
23pub struct ContractResult<State> {
24    /// Final state after executing the event.
25    pub state: State,
26    /// Whether the runtime should apply the state change.
27    pub success: bool,
28    /// Rejection reason when `success` is `false`.
29    pub error: String,
30}
31
32/// Contract initialization validation result.
33#[derive(Serialize, Deserialize, Debug, Default)]
34pub struct ContractInitCheck {
35    /// Whether the initial state is accepted.
36    pub success: bool,
37    /// Rejection reason when `success` is `false`.
38    pub error: String,
39}
40
41/// Internal execution result serialized back to the host with Borsh.
42#[derive(BorshSerialize)]
43struct ContractResultBorsh {
44    /// Final state wrapped for Borsh serialization.
45    pub final_state: ValueWrapper,
46    /// Whether execution succeeded.
47    pub success: bool,
48    /// Error message when execution failed.
49    pub error: String,
50}
51
52impl ContractResultBorsh {
53    /// Creates a failed result with a null final state.
54    pub fn error(error: &str) -> Self {
55        Self {
56            final_state: ValueWrapper(serde_json::Value::Null),
57            success: false,
58            error: error.to_owned(),
59        }
60    }
61}
62
63/// Internal init-check result serialized back to the host with Borsh.
64#[derive(BorshSerialize)]
65struct ContractInitCheckBorsh {
66    /// Whether the initial state is valid.
67    pub success: bool,
68    /// Error message when validation failed.
69    pub error: String,
70}
71
72impl ContractInitCheckBorsh {
73    /// Creates a failed init-check result.
74    pub fn error(error: &str) -> Self {
75        Self {
76            success: false,
77            error: error.to_owned(),
78        }
79    }
80
81    /// Creates a successful init-check result.
82    pub fn ok() -> Self {
83        Self {
84            success: true,
85            error: String::default(),
86        }
87    }
88}
89
90impl<State> ContractResult<State> {
91    /// Creates a new contract result with the given state.
92    ///
93    /// New results start as failed and must be marked successful by contract logic.
94    pub fn new(state: State) -> Self {
95        Self {
96            state,
97            success: false,
98            error: String::default(),
99        }
100    }
101}
102
103/// Validates the initial state of a contract before subject creation.
104///
105/// The runtime passes the proposed state through `state_ptr`. The callback decides
106/// whether that state is valid and writes the outcome into `ContractInitCheck`.
107///
108/// # Arguments
109///
110/// * `state_ptr` - Pointer to the proposed initial state in host memory.
111/// * `callback` - Validation function with signature `fn(&State, &mut ContractInitCheck)`.
112///
113/// # Returns
114///
115/// Pointer to a serialized `ContractInitCheckBorsh`.
116///
117/// # Example
118///
119/// ```ignore
120/// #[unsafe(no_mangle)]
121/// pub unsafe fn init_check_function(state_ptr: i32) -> u32 {
122///     sdk::check_init_data(state_ptr, |state: &MyState, result| {
123///         if state.value > 100 {
124///             result.success = false;
125///             result.error = "Value too high".to_string();
126///         } else {
127///             result.success = true;
128///         }
129///     })
130/// }
131/// ```
132pub fn check_init_data<State, F>(state_ptr: i32, callback: F) -> u32
133where
134    State: for<'a> Deserialize<'a> + Serialize + Clone,
135    F: Fn(&State, &mut ContractInitCheck),
136{
137    {
138        let error: String;
139        'process: {
140            let Ok(state_bytes) = get_from_context(state_ptr) else {
141                error = "Can not read State from host memory".to_owned();
142                break 'process;
143            };
144            let Ok(state_value) = deserialize(state_bytes) else {
145                error = "Can not deserialize State".to_owned();
146                break 'process;
147            };
148            let Ok(state) = serde_json::from_value::<State>(state_value.0) else {
149                error = "Can not convert State from value".to_owned();
150                break 'process;
151            };
152            let mut contract_result = ContractInitCheck::default();
153            callback(&state, &mut contract_result);
154
155            if !contract_result.success {
156                error = format!(
157                    "Error running init contract data: {}",
158                    contract_result.error
159                );
160                break 'process;
161            }
162
163            let Ok(result_ptr) = store(&ContractInitCheckBorsh::ok()) else {
164                error = "Can not return init contract result".to_owned();
165                break 'process;
166            };
167            return result_ptr;
168        }
169        // Attempt to return error via store, but if that fails too, return 0 pointer
170        // The host should handle 0 pointer as a fatal error
171        store(&ContractInitCheckBorsh::error(&error)).unwrap_or(0)
172    }
173}
174
175/// Executes a contract by processing an event and updating the subject's state.
176///
177/// This is the main entry point used by the WASM runtime. It reads the current
178/// state and the incoming event, builds a `Context<Event>`, runs the callback,
179/// and returns the serialized result.
180///
181/// # Arguments
182///
183/// * `state_ptr` - Pointer to the current state in host memory.
184/// * `init_state_ptr` - Pointer to the initial state used as a fallback when the current state cannot be deserialized.
185/// * `event_ptr` - Pointer to the incoming event in host memory.
186/// * `is_owner` - Ownership flag sent by the runtime. `1` means owner, any other value means non-owner.
187/// * `callback` - Contract logic with signature `fn(&Context<Event>, &mut ContractResult<State>)`.
188///
189/// # Returns
190///
191/// Pointer to a serialized `ContractResultBorsh`.
192///
193/// If `state_ptr` cannot be deserialized, the function falls back to `init_state_ptr`.
194///
195/// # Example
196///
197/// ```ignore
198/// #[unsafe(no_mangle)]
199/// pub unsafe fn main_function(
200///     state_ptr: i32,
201///     init_state_ptr: i32,
202///     event_ptr: i32,
203///     is_owner: i32,
204/// ) -> u32 {
205///     sdk::execute_contract(state_ptr, init_state_ptr, event_ptr, is_owner, |context, result| {
206///         match &context.event {
207///             Event::Update { value } => {
208///                 result.state.value = *value;
209///                 result.success = true;
210///             }
211///             Event::Delete => {
212///                 if context.is_owner {
213///                     result.state.deleted = true;
214///                     result.success = true;
215///                 } else {
216///                     result.success = false;
217///                     result.error = "Only owner can delete".to_string();
218///                 }
219///             }
220///         }
221///     })
222/// }
223/// ```
224pub fn execute_contract<F, State, Event>(
225    state_ptr: i32,
226    init_state_ptr: i32,
227    event_ptr: i32,
228    is_owner: i32,
229    callback: F,
230) -> u32
231where
232    State: for<'a> Deserialize<'a> + Serialize + Clone,
233    Event: for<'a> Deserialize<'a> + Serialize,
234    F: Fn(&Context<Event>, &mut ContractResult<State>),
235{
236    {
237        let error: String;
238        'process: {
239            let Ok(state_bytes) = get_from_context(state_ptr) else {
240                error = "Can not read State from host memory".to_owned();
241                break 'process;
242            };
243            let Ok(state_value) = deserialize(state_bytes) else {
244                error = "Can not deserialize State".to_owned();
245                break 'process;
246            };
247            let state = match serde_json::from_value::<State>(state_value.0) {
248                Ok(state) => state,
249                Err(_) => {
250                    let Ok(init_state_bytes) = get_from_context(init_state_ptr) else {
251                        error = "Can not read Init State from host memory".to_owned();
252                        break 'process;
253                    };
254                    let Ok(init_state) = deserialize(init_state_bytes) else {
255                        error = "Can not deserialize Init State".to_owned();
256                        break 'process;
257                    };
258
259                    let Ok(init_state) = serde_json::from_value::<State>(init_state.0) else {
260                        error = "Can not convert State from value".to_owned();
261                        break 'process;
262                    };
263
264                    init_state
265                }
266            };
267            let Ok(event_bytes) = get_from_context(event_ptr) else {
268                error = "Can not read Event from host memory".to_owned();
269                break 'process;
270            };
271            let Ok(event_value) = deserialize(event_bytes) else {
272                error = "Can not deserialize Event".to_owned();
273                break 'process;
274            };
275            let Ok(event) = serde_json::from_value::<Event>(event_value.0) else {
276                error = "Can not convert Event from value".to_owned();
277                break 'process;
278            };
279            let is_owner = is_owner == 1;
280            let context = Context { event, is_owner };
281            let mut contract_result = ContractResult::new(state);
282            callback(&context, &mut contract_result);
283            let Ok(state_value) = serde_json::to_value(&contract_result.state) else {
284                error = "Can not convert contract final state into Value".to_owned();
285                break 'process;
286            };
287            let result = ContractResultBorsh {
288                final_state: ValueWrapper(state_value),
289                success: contract_result.success,
290                error: format!("Error running contract event: {}", contract_result.error),
291            };
292            let Ok(result_ptr) = store(&result) else {
293                error = "Can not return contract result".to_owned();
294                break 'process;
295            };
296            return result_ptr;
297        };
298        // Attempt to return error via store, but if that fails too, return 0 pointer
299        // The host should handle 0 pointer as a fatal error
300        store(&ContractResultBorsh::error(&error)).unwrap_or(0)
301    }
302}
303
304/// Deserializes data from bytes using Borsh format.
305fn deserialize(bytes: Vec<u8>) -> Result<ValueWrapper, Error> {
306    BorshDeserialize::try_from_slice(&bytes).map_err(|e| Error::Deserialization(e.to_string()))
307}
308
309/// Serializes data into bytes using Borsh format.
310fn serialize<S: BorshSerialize>(data: S) -> Result<Vec<u8>, Error> {
311    borsh::to_vec(&data).map_err(|e| Error::Serialization(e.to_string()))
312}
313
314/// Reads data from WASM host memory at the given pointer.
315///
316/// The host provides a pointer and length through the external memory API.
317fn get_from_context(pointer: i32) -> Result<Vec<u8>, Error> {
318    unsafe {
319        let len = externf::pointer_len(pointer);
320
321        // Reject oversized host input before allocating.
322        if len > MAX_DATA_SIZE {
323            return Err(Error::MemoryLimitExceeded {
324                requested: len as usize,
325                max: MAX_DATA_SIZE as usize,
326            });
327        }
328
329        // Negative lengths are invalid host input.
330        if len < 0 {
331            return Err(Error::Deserialization(
332                "Invalid negative length from host".to_owned(),
333            ));
334        }
335
336        let mut data = Vec::with_capacity(len as usize);
337        for i in 0..len {
338            // Checked arithmetic avoids pointer overflow on malformed input.
339            let read_ptr = pointer.checked_add(i).ok_or_else(|| {
340                Error::IntegerOverflow(format!("Pointer arithmetic overflow: {} + {}", pointer, i))
341            })?;
342            data.push(externf::read_byte(read_ptr));
343        }
344        Ok(data)
345    }
346}
347
348/// Stores data in WASM memory to be read by the host.
349///
350/// Serializes `data`, allocates host-visible memory, and writes the bytes there.
351fn store<S>(data: &S) -> Result<u32, Error>
352where
353    S: BorshSerialize,
354{
355    let bytes = serialize(data).map_err(|e| Error::Serialization(e.to_string()))?;
356
357    // The host allocator expects a `u32` byte length.
358    let len = u32::try_from(bytes.len()).map_err(|_| {
359        Error::IntegerOverflow(format!(
360            "Serialized data too large: {} bytes exceeds u32::MAX",
361            bytes.len()
362        ))
363    })?;
364
365    unsafe {
366        let ptr = externf::alloc(len) as u32;
367        for (index, byte) in bytes.into_iter().enumerate() {
368            externf::write_byte(ptr, index as u32, byte);
369        }
370        Ok(ptr)
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use super::*;
377    use serde::{Deserialize, Serialize};
378
379    #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
380    struct TestState {
381        value: i32,
382        name: String,
383    }
384
385    #[derive(Serialize, Deserialize, Debug)]
386    enum TestEvent {
387        Increment,
388        Decrement,
389        SetValue(i32),
390        Rename(String),
391    }
392
393    #[test]
394    fn test_context_creation() {
395        let event = TestEvent::Increment;
396        let context = Context {
397            event,
398            is_owner: true,
399        };
400        assert!(context.is_owner);
401    }
402
403    #[test]
404    fn test_contract_result_new() {
405        let state = TestState {
406            value: 42,
407            name: "test".to_string(),
408        };
409        let result = ContractResult::new(state.clone());
410        assert_eq!(result.state.value, 42);
411        assert!(!result.success);
412        assert_eq!(result.error, "");
413    }
414
415    #[test]
416    fn test_contract_result_success() {
417        let state = TestState {
418            value: 10,
419            name: "Alice".to_string(),
420        };
421        let mut result = ContractResult::new(state);
422        result.state.value = 20;
423        result.success = true;
424
425        assert_eq!(result.state.value, 20);
426        assert!(result.success);
427        assert_eq!(result.error, "");
428    }
429
430    #[test]
431    fn test_contract_result_error() {
432        let state = TestState {
433            value: 5,
434            name: "Bob".to_string(),
435        };
436        let mut result = ContractResult::new(state);
437        result.success = false;
438        result.error = "Invalid operation".to_string();
439
440        assert!(!result.success);
441        assert_eq!(result.error, "Invalid operation");
442    }
443
444    #[test]
445    fn test_contract_init_check_default() {
446        let check = ContractInitCheck::default();
447        assert!(!check.success);
448        assert_eq!(check.error, "");
449    }
450
451    #[test]
452    fn test_contract_init_check_success() {
453        let mut check = ContractInitCheck::default();
454        check.success = true;
455        assert!(check.success);
456        assert_eq!(check.error, "");
457    }
458
459    #[test]
460    fn test_contract_init_check_error() {
461        let mut check = ContractInitCheck::default();
462        check.success = false;
463        check.error = "Invalid initial state".to_string();
464        assert!(!check.success);
465        assert_eq!(check.error, "Invalid initial state");
466    }
467
468    #[test]
469    fn test_contract_result_borsh_error() {
470        let result = ContractResultBorsh::error("test error");
471        assert!(!result.success);
472        assert_eq!(result.error, "test error");
473        assert_eq!(result.final_state.0, serde_json::Value::Null);
474    }
475
476    #[test]
477    fn test_contract_init_check_borsh_ok() {
478        let result = ContractInitCheckBorsh::ok();
479        assert!(result.success);
480        assert_eq!(result.error, "");
481    }
482
483    #[test]
484    fn test_contract_init_check_borsh_error() {
485        let result = ContractInitCheckBorsh::error("validation failed");
486        assert!(!result.success);
487        assert_eq!(result.error, "validation failed");
488    }
489
490    #[test]
491    fn test_serialize_deserialize_roundtrip() {
492        let state = TestState {
493            value: 100,
494            name: "test".to_string(),
495        };
496        let value = serde_json::to_value(&state).unwrap();
497        let wrapper = ValueWrapper(value);
498
499        let serialized = serialize(&wrapper).unwrap();
500        let deserialized = deserialize(serialized).unwrap();
501
502        let recovered_state: TestState = serde_json::from_value(deserialized.0).unwrap();
503        assert_eq!(recovered_state.value, 100);
504        assert_eq!(recovered_state.name, "test");
505    }
506
507    #[test]
508    fn test_serialize_contract_result_borsh() {
509        let state = TestState {
510            value: 42,
511            name: "Alice".to_string(),
512        };
513        let state_value = serde_json::to_value(&state).unwrap();
514        let result = ContractResultBorsh {
515            final_state: ValueWrapper(state_value),
516            success: true,
517            error: String::new(),
518        };
519
520        let serialized = serialize(&result);
521        assert!(serialized.is_ok());
522    }
523
524    #[test]
525    fn test_serialize_contract_init_check_borsh() {
526        let check = ContractInitCheckBorsh {
527            success: true,
528            error: String::new(),
529        };
530
531        let serialized = serialize(&check);
532        assert!(serialized.is_ok());
533    }
534
535    #[test]
536    fn test_deserialize_invalid_data() {
537        let invalid_bytes = vec![0xFF, 0xFF, 0xFF, 0xFF];
538        let result = deserialize(invalid_bytes);
539        assert!(result.is_err());
540    }
541
542    #[test]
543    fn test_context_is_owner_true() {
544        let event = TestEvent::SetValue(100);
545        let context = Context {
546            event,
547            is_owner: true,
548        };
549        assert!(context.is_owner);
550    }
551
552    #[test]
553    fn test_context_is_owner_false() {
554        let event = TestEvent::SetValue(100);
555        let context = Context {
556            event,
557            is_owner: false,
558        };
559        assert!(!context.is_owner);
560    }
561
562    #[test]
563    fn test_contract_result_state_modification() {
564        let initial_state = TestState {
565            value: 0,
566            name: "Initial".to_string(),
567        };
568        let mut result = ContractResult::new(initial_state);
569
570        result.state.value = 999;
571        result.state.name = "Modified".to_string();
572        result.success = true;
573
574        assert_eq!(result.state.value, 999);
575        assert_eq!(result.state.name, "Modified");
576        assert!(result.success);
577    }
578
579    #[test]
580    fn test_serialize_complex_nested_structure() {
581        let mut inner_map = serde_json::Map::new();
582        inner_map.insert("nested".to_string(), serde_json::json!({"deep": "value"}));
583
584        let complex_value = serde_json::json!({
585            "array": [1, 2, 3],
586            "object": inner_map,
587            "string": "test",
588            "number": 42,
589            "bool": true,
590            "null": null
591        });
592
593        let wrapper = ValueWrapper(complex_value);
594        let serialized = serialize(&wrapper).unwrap();
595        let deserialized = deserialize(serialized).unwrap();
596
597        assert_eq!(wrapper, deserialized);
598    }
599
600    #[test]
601    fn test_max_data_size_constant() {
602        assert_eq!(MAX_DATA_SIZE, 10_000_000);
603    }
604
605    #[test]
606    fn test_contract_result_json_serialization() {
607        let state = TestState {
608            value: 123,
609            name: "JsonTest".to_string(),
610        };
611        let result = ContractResult {
612            state,
613            success: true,
614            error: String::new(),
615        };
616
617        let json = serde_json::to_string(&result);
618        assert!(json.is_ok());
619
620        let json_str = json.unwrap();
621        assert!(json_str.contains("123"));
622        assert!(json_str.contains("JsonTest"));
623        assert!(json_str.contains("true"));
624    }
625
626    #[test]
627    fn test_context_json_serialization() {
628        let event = TestEvent::Increment;
629        let context = Context {
630            event,
631            is_owner: true,
632        };
633
634        let json = serde_json::to_string(&context);
635        assert!(json.is_ok());
636    }
637
638    #[test]
639    fn test_contract_init_check_json_serialization() {
640        let check = ContractInitCheck {
641            success: true,
642            error: String::new(),
643        };
644
645        let json = serde_json::to_string(&check);
646        assert!(json.is_ok());
647
648        let json_str = json.unwrap();
649        assert!(json_str.contains("true"));
650    }
651
652    #[test]
653    fn test_multiple_contract_results() {
654        let states = vec![
655            TestState {
656                value: 1,
657                name: "one".to_string(),
658            },
659            TestState {
660                value: 2,
661                name: "two".to_string(),
662            },
663            TestState {
664                value: 3,
665                name: "three".to_string(),
666            },
667        ];
668
669        let results: Vec<ContractResult<TestState>> =
670            states.into_iter().map(ContractResult::new).collect();
671
672        assert_eq!(results.len(), 3);
673        assert_eq!(results[0].state.value, 1);
674        assert_eq!(results[1].state.value, 2);
675        assert_eq!(results[2].state.value, 3);
676    }
677
678    #[test]
679    fn test_value_wrapper_public_access() {
680        let value = serde_json::json!({"test": "value"});
681        let wrapper = ValueWrapper(value.clone());
682        assert_eq!(wrapper.0, value);
683    }
684
685    #[test]
686    fn test_empty_error_string() {
687        let state = TestState {
688            value: 0,
689            name: String::new(),
690        };
691        let result = ContractResult::new(state);
692        assert_eq!(result.error.len(), 0);
693    }
694}