1use std::collections::HashMap;
4use std::fmt;
5
6use serde_json::Value;
7
8use crate::error::{Error, Result};
9use crate::schema::Schema;
10use crate::snapshot::{Bindings, BindingsExt, Snapshot};
11
12pub trait GuardEvaluator {
14 fn evaluate(
15 &self,
16 expr: &str,
17 bindings: &Bindings,
18 ) -> std::result::Result<bool, String>;
19
20 fn evaluate_constraint(
21 &self,
22 expr: &str,
23 tokens: &HashMap<String, i64>,
24 ) -> std::result::Result<bool, String>;
25}
26
27#[derive(Debug, Clone)]
29pub struct ConstraintViolation {
30 pub constraint_id: String,
31 pub constraint_expr: String,
32 pub snapshot: Snapshot,
33 pub err: Option<String>,
34}
35
36impl fmt::Display for ConstraintViolation {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 if let Some(err) = &self.err {
39 write!(f, "constraint {} error: {}", self.constraint_id, err)
40 } else {
41 write!(f, "constraint {} violated", self.constraint_id)
42 }
43 }
44}
45
46pub struct Runtime {
48 pub schema: Schema,
49 pub snapshot: Snapshot,
50 pub sequence: u64,
51 pub check_constraints: bool,
52 pub guard_evaluator: Option<Box<dyn GuardEvaluator>>,
53}
54
55impl Runtime {
56 pub fn new(schema: Schema) -> Self {
57 let snapshot = Snapshot::from_schema(&schema);
58 Self {
59 schema,
60 snapshot,
61 sequence: 0,
62 check_constraints: true,
63 guard_evaluator: None,
64 }
65 }
66
67 pub fn clone_runtime(&self) -> Self {
68 Self {
69 schema: self.schema.clone(),
70 snapshot: self.snapshot.clone(),
71 sequence: self.sequence,
72 check_constraints: self.check_constraints,
73 guard_evaluator: None, }
75 }
76
77 pub fn tokens(&self, state_id: &str) -> i64 {
78 self.snapshot.get_tokens(state_id)
79 }
80
81 pub fn set_tokens(&mut self, state_id: &str, count: i64) {
82 self.snapshot.set_tokens(state_id, count);
83 }
84
85 pub fn data(&self, state_id: &str) -> Option<&Value> {
86 self.snapshot.get_data(state_id)
87 }
88
89 pub fn set_data(&mut self, state_id: &str, value: Value) {
90 self.snapshot.set_data(state_id, value);
91 }
92
93 pub fn enabled(&self, action_id: &str) -> bool {
95 if self.schema.action_by_id(action_id).is_none() {
96 return false;
97 }
98
99 for arc in self.schema.input_arcs(action_id) {
100 if let Some(st) = self.schema.state_by_id(&arc.source) {
101 if st.is_token() && self.tokens(&arc.source) < 1 {
102 return false;
103 }
104 }
105 }
106
107 true
108 }
109
110 pub fn enabled_actions(&self) -> Vec<String> {
112 self.schema
113 .actions
114 .iter()
115 .filter(|a| self.enabled(&a.id))
116 .map(|a| a.id.clone())
117 .collect()
118 }
119
120 pub fn execute(&mut self, action_id: &str) -> Result<()> {
122 if !self.enabled(action_id) {
123 return Err(Error::ActionNotEnabled(action_id.to_string()));
124 }
125
126 for arc in self.schema.input_arcs(action_id) {
128 if let Some(st) = self.schema.state_by_id(&arc.source) {
129 if st.is_token() {
130 self.snapshot.add_tokens(&arc.source, -1);
131 }
132 }
133 }
134
135 for arc in self.schema.output_arcs(action_id) {
137 if let Some(st) = self.schema.state_by_id(&arc.target) {
138 if st.is_token() {
139 self.snapshot.add_tokens(&arc.target, 1);
140 }
141 }
142 }
143
144 self.sequence += 1;
145
146 if self.check_constraints {
147 let violations = self.check_constraints_impl();
148 if let Some(v) = violations.first() {
149 if let Some(err) = &v.err {
150 return Err(Error::ConstraintEvaluation(
151 v.constraint_id.clone(),
152 err.clone(),
153 ));
154 }
155 return Err(Error::ConstraintViolated(v.constraint_id.clone()));
156 }
157 }
158
159 Ok(())
160 }
161
162 pub fn execute_with_bindings(
164 &mut self,
165 action_id: &str,
166 bindings: &Bindings,
167 ) -> Result<()> {
168 let action = self
169 .schema
170 .action_by_id(action_id)
171 .ok_or_else(|| Error::ActionNotFound(action_id.to_string()))?
172 .clone();
173
174 if !action.guard.is_empty() {
176 if let Some(evaluator) = &self.guard_evaluator {
177 match evaluator.evaluate(&action.guard, bindings) {
178 Ok(true) => {}
179 Ok(false) => return Err(Error::GuardNotSatisfied),
180 Err(e) => return Err(Error::GuardEvaluation(e)),
181 }
182 }
183 }
184
185 if !self.enabled(action_id) {
186 return Err(Error::ActionNotEnabled(action_id.to_string()));
187 }
188
189 self.apply_arcs(action_id, bindings);
190 self.sequence += 1;
191
192 if self.check_constraints {
193 let violations = self.check_constraints_impl();
194 if let Some(v) = violations.first() {
195 if let Some(err) = &v.err {
196 return Err(Error::ConstraintEvaluation(
197 v.constraint_id.clone(),
198 err.clone(),
199 ));
200 }
201 return Err(Error::ConstraintViolated(v.constraint_id.clone()));
202 }
203 }
204
205 Ok(())
206 }
207
208 fn apply_arcs(&mut self, action_id: &str, bindings: &Bindings) {
209 let input_arcs: Vec<_> = self
211 .schema
212 .input_arcs(action_id)
213 .into_iter()
214 .cloned()
215 .collect();
216 let output_arcs: Vec<_> = self
217 .schema
218 .output_arcs(action_id)
219 .into_iter()
220 .cloned()
221 .collect();
222
223 for arc in &input_arcs {
224 let is_token = self
225 .schema
226 .state_by_id(&arc.source)
227 .map(|s| s.is_token())
228 .unwrap_or(false);
229
230 if is_token {
231 self.snapshot.add_tokens(&arc.source, -1);
232 } else {
233 self.apply_data_arc(&arc.source, arc, bindings, false);
234 }
235 }
236
237 for arc in &output_arcs {
238 let is_token = self
239 .schema
240 .state_by_id(&arc.target)
241 .map(|s| s.is_token())
242 .unwrap_or(false);
243
244 if is_token {
245 self.snapshot.add_tokens(&arc.target, 1);
246 } else {
247 self.apply_data_arc(&arc.target, arc, bindings, true);
248 }
249 }
250 }
251
252 fn apply_data_arc(
253 &mut self,
254 state_id: &str,
255 arc: &crate::schema::Arc,
256 bindings: &Bindings,
257 add: bool,
258 ) {
259 let value_name = if arc.value.is_empty() {
260 "amount"
261 } else {
262 &arc.value
263 };
264 let amount = bindings.get_i64(value_name);
265
266 if arc.keys.is_empty() {
267 return;
268 }
269
270 if arc.keys.len() == 1 {
271 let key = bindings.get_string(&arc.keys[0]);
272 if key.is_empty() {
273 return;
274 }
275
276 let current = self.get_map_i64(state_id, &key);
277 let new_val = if add {
278 current + amount
279 } else {
280 current - amount
281 };
282 self.snapshot
283 .set_data_map_value(state_id, &key, Value::Number(new_val.into()));
284 } else if arc.keys.len() == 2 {
285 let key1 = bindings.get_string(&arc.keys[0]);
286 let key2 = bindings.get_string(&arc.keys[1]);
287 if key1.is_empty() || key2.is_empty() {
288 return;
289 }
290
291 let current = self.get_nested_map_i64(state_id, &key1, &key2);
293 let new_val = if add {
294 current + amount
295 } else {
296 current - amount
297 };
298
299 let entry = self
301 .snapshot
302 .data
303 .entry(state_id.to_string())
304 .or_insert_with(|| Value::Object(Default::default()));
305
306 if let Value::Object(outer) = entry {
307 let nested = outer
308 .entry(key1)
309 .or_insert_with(|| Value::Object(Default::default()));
310 if let Value::Object(inner) = nested {
311 inner.insert(key2, Value::Number(new_val.into()));
312 }
313 }
314 }
315 }
316
317 fn get_map_i64(&self, state_id: &str, key: &str) -> i64 {
318 self.snapshot
319 .get_data_map_value(state_id, key)
320 .and_then(|v| v.as_i64())
321 .unwrap_or(0)
322 }
323
324 fn get_nested_map_i64(&self, state_id: &str, key1: &str, key2: &str) -> i64 {
325 self.snapshot
326 .get_data_map(state_id)
327 .and_then(|m| m.get(key1))
328 .and_then(|v| v.as_object())
329 .and_then(|m| m.get(key2))
330 .and_then(|v| v.as_i64())
331 .unwrap_or(0)
332 }
333
334 fn check_constraints_impl(&self) -> Vec<ConstraintViolation> {
335 let mut violations = Vec::new();
336
337 let evaluator = match &self.guard_evaluator {
338 Some(e) => e,
339 None => return violations,
340 };
341
342 for c in &self.schema.constraints {
343 match evaluator.evaluate_constraint(&c.expr, &self.snapshot.tokens) {
344 Ok(true) => {}
345 Ok(false) => {
346 violations.push(ConstraintViolation {
347 constraint_id: c.id.clone(),
348 constraint_expr: c.expr.clone(),
349 snapshot: self.snapshot.clone(),
350 err: None,
351 });
352 }
353 Err(e) => {
354 violations.push(ConstraintViolation {
355 constraint_id: c.id.clone(),
356 constraint_expr: c.expr.clone(),
357 snapshot: self.snapshot.clone(),
358 err: Some(e),
359 });
360 }
361 }
362 }
363
364 violations
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371 use crate::schema::{Action, Arc, Kind, State};
372
373 fn make_simple_schema() -> Schema {
374 let mut s = Schema::new("test");
375 s.add_state(State {
376 id: "ready".into(),
377 kind: Kind::Token,
378 initial: Some(Value::Number(1.into())),
379 typ: "int".into(),
380 exported: false,
381 });
382 s.add_state(State {
383 id: "done".into(),
384 kind: Kind::Token,
385 initial: Some(Value::Number(0.into())),
386 typ: "int".into(),
387 exported: false,
388 });
389 s.add_action(Action {
390 id: "process".into(),
391 guard: String::new(),
392 event_id: String::new(),
393 event_bindings: None,
394 });
395 s.add_arc(Arc {
396 source: "ready".into(),
397 target: "process".into(),
398 keys: vec![],
399 value: String::new(),
400 });
401 s.add_arc(Arc {
402 source: "process".into(),
403 target: "done".into(),
404 keys: vec![],
405 value: String::new(),
406 });
407 s
408 }
409
410 #[test]
411 fn test_runtime_basic() {
412 let schema = make_simple_schema();
413 let mut rt = Runtime::new(schema);
414
415 assert_eq!(rt.tokens("ready"), 1);
416 assert_eq!(rt.tokens("done"), 0);
417 assert!(rt.enabled("process"));
418
419 rt.execute("process").unwrap();
420
421 assert_eq!(rt.tokens("ready"), 0);
422 assert_eq!(rt.tokens("done"), 1);
423 assert!(!rt.enabled("process"));
424 }
425
426 #[test]
427 fn test_runtime_not_enabled() {
428 let schema = make_simple_schema();
429 let mut rt = Runtime::new(schema);
430
431 rt.execute("process").unwrap();
432 let result = rt.execute("process");
433 assert!(result.is_err());
434 }
435
436 #[test]
437 fn test_enabled_actions() {
438 let schema = make_simple_schema();
439 let rt = Runtime::new(schema);
440
441 let enabled = rt.enabled_actions();
442 assert_eq!(enabled, vec!["process"]);
443 }
444
445 #[test]
446 fn test_execute_with_bindings() {
447 let mut schema = Schema::new("erc20");
448 schema.add_data_state(
449 "balances",
450 "map[address]uint256",
451 Some(Value::Object(Default::default())),
452 true,
453 );
454 schema.add_action(Action {
455 id: "transfer".into(),
456 guard: String::new(),
457 event_id: String::new(),
458 event_bindings: None,
459 });
460 schema.add_arc(Arc {
461 source: "balances".into(),
462 target: "transfer".into(),
463 keys: vec!["from".into()],
464 value: String::new(),
465 });
466 schema.add_arc(Arc {
467 source: "transfer".into(),
468 target: "balances".into(),
469 keys: vec!["to".into()],
470 value: String::new(),
471 });
472
473 let mut rt = Runtime::new(schema);
474
475 rt.snapshot.set_data_map_value(
477 "balances",
478 "alice",
479 Value::Number(100.into()),
480 );
481
482 let mut bindings = Bindings::new();
483 bindings.insert("from".into(), Value::String("alice".into()));
484 bindings.insert("to".into(), Value::String("bob".into()));
485 bindings.insert("amount".into(), Value::Number(30.into()));
486
487 rt.execute_with_bindings("transfer", &bindings).unwrap();
488
489 assert_eq!(rt.get_map_i64("balances", "alice"), 70);
490 assert_eq!(rt.get_map_i64("balances", "bob"), 30);
491 }
492}