1use core::marker::PhantomData;
14use soroban_sdk::{contracttype, Env, IntoVal, TryFromVal, Val};
15
16pub trait TransitionHandler<K, V>
18where
19 K: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>,
20 V: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>,
21{
22 fn on_guard(&self, env: &Env, state_machine: &StateMachine<K, V>);
25
26 fn on_effect(&self, env: &Env, state_machine: &StateMachine<K, V>);
29}
30
31pub struct StateMachine<'a, K, V>
35where
36 K: 'a + IntoVal<Env, Val> + TryFromVal<Env, Val>,
37 V: IntoVal<Env, Val> + TryFromVal<Env, Val>,
38{
39 region: &'a K,
40 storage_type: StorageType,
41 _data: PhantomData<*const V>,
42}
43
44impl<'a, K, V> StateMachine<'a, K, V>
45where
46 K: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>,
47 V: Clone + IntoVal<Env, Val> + TryFromVal<Env, Val>,
48{
49 pub fn new(region: &'a K, storage_type: StorageType) -> Self {
50 StateMachine {
51 region,
52 storage_type,
53 _data: PhantomData,
54 }
55 }
56
57 pub fn get_region(&self) -> &'a K {
58 self.region
59 }
60
61 pub fn get_storage_type(&self) -> &StorageType {
62 &self.storage_type
63 }
64
65 pub fn set_state(&self, env: &Env, value: &V) {
66 match self.storage_type {
67 StorageType::Instance => env
68 .storage()
69 .instance()
70 .set(&self.region.into_val(env), value),
71 StorageType::Persistent => env
72 .storage()
73 .persistent()
74 .set(&self.region.into_val(env), value),
75 StorageType::Temporary => env
76 .storage()
77 .temporary()
78 .set(&self.region.into_val(env), value),
79 }
80 }
81
82 pub fn get_state(&self, env: &Env) -> Option<V> {
83 match self.storage_type {
84 StorageType::Instance => env.storage().instance().get(&self.region.into_val(env)),
85 StorageType::Persistent => env.storage().persistent().get(&self.region.into_val(env)),
86 StorageType::Temporary => env.storage().temporary().get(&self.region.into_val(env)),
87 }
88 }
89
90 pub fn remove_state(&self, env: &Env) {
91 match self.storage_type {
92 StorageType::Instance => env.storage().instance().remove(&self.region.into_val(env)),
93 StorageType::Persistent => env.storage().persistent().remove(&self.region.into_val(env)),
94 StorageType::Temporary => env.storage().temporary().remove(&self.region.into_val(env)),
95 }
96 }
97}
98
99#[contracttype]
100#[derive(Debug, Clone, Eq, PartialEq)]
101pub enum StorageType {
102 Instance,
103 Persistent,
104 Temporary,
105}
106
107#[contracttype]
109#[derive(Debug, Clone, Eq, PartialEq)]
110pub enum StateMachineRegion {
111 Default,
112}
113
114#[macro_export]
119macro_rules! impl_state_machine {
120 ($instance:expr, $env:expr, $storage_type:expr, $state_enum:ident, $state_variant:ident) => {
121 let state_key = $state_enum::$state_variant;
122 let region_key = $crate::fsm::StateMachineRegion::Default;
123 $crate::impl_state_machine!(@internal $instance, $env, $storage_type, state_key, region_key, $state_enum, $crate::fsm::StateMachineRegion);
124 };
125 ($instance:expr, $env:expr, $storage_type:expr, $state_enum:ident, $state_variant:ident, (),
126 $region_enum:ident, $region_variant:ident, ()) => {
127 let state_key = $state_enum::$state_variant;
128 let region_key = $region_enum::$region_variant;
129 $crate::impl_state_machine!(@internal $instance, $env, $storage_type, state_key, region_key, $state_enum, $region_enum);
130 };
131 ($instance:expr, $env:expr, $storage_type:expr, $state_enum:ident, $state_variant:ident,
132 (), $region_enum:ident, $region_variant:ident, ($($region_tuple_value:expr),+)) => {
133 let state_key = $state_enum::$state_variant;
134 let region_key = $region_enum::$region_variant($($region_tuple_value),*);
135 $crate::impl_state_machine!(@internal $instance, $env, $storage_type, state_key, region_key, $state_enum, $region_enum);
136 };
137 ($instance:expr, $env:expr, $storage_type:expr, $state_enum:ident, $state_variant:ident, ($($state_tuple_value:expr),+)) => {
138 let state_key = $state_enum::$state_variant($($state_tuple_value),*);
139 let region_key = $crate::fsm::StateMachineRegion::Default;
140 $crate::impl_state_machine!(@internal $instance, $env, $storage_type, state_key, region_key, $state_enum, $crate::fsm::StateMachineRegion);
141 };
142 ($instance:expr, $env:expr, $storage_type:expr, $state_enum:ident, $state_variant:ident, ($($state_tuple_value:expr),+),
143 $region_enum:ident, $region_variant:ident, ()) => {
144 let state_key = $state_enum::$state_variant($($state_tuple_value),*);
145 let region_key = $region_enum::$region_variant;
146 $crate::impl_state_machine!(@internal $instance, $env, $storage_type, state_key, region_key, $state_enum, $region_enum);
147 };
148 ($instance:expr, $env:expr, $storage_type:expr, $state_enum:ident, $state_variant:ident,
149 ($($state_tuple_value:expr),+),$region_enum:ident, $region_variant:ident, ($($region_tuple_value:expr),+)) => {
150 let state_key = $state_enum::$state_variant($($state_tuple_value),*);
151 let region_key = $region_enum::$region_variant($($region_tuple_value),*);
152 $crate::impl_state_machine!(@internal $instance, $env, $storage_type, state_key, region_key, $state_enum, $region_enum);
153 };
154 (@internal $instance:expr, $env:expr, $storage_type:expr, $state_key:expr, $region_key:expr, $state_enum:ty, $region_enum:ty) => {
156 let sm = $crate::fsm::StateMachine::<$region_enum, $state_enum>::new(&$region_key, $storage_type);
157 $instance.on_guard($env, &sm);
158 assert_eq!(sm.get_state(&$env).unwrap(), $state_key);
159 $instance.on_effect($env, &sm);
160 };
161}