Explorar el Código

Working a Expr

Cesar Rodas hace 10 meses
padre
commit
75a068eaa8

+ 90 - 44
Cargo.lock

@@ -265,7 +265,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -357,7 +357,7 @@ dependencies = [
  "heck 0.4.1",
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -451,7 +451,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
  "syn_derive",
 ]
 
@@ -485,15 +485,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.6.0"
+version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
 
 [[package]]
 name = "cc"
-version = "1.1.0"
+version = "1.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8"
+checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052"
 
 [[package]]
 name = "cfg-if"
@@ -554,7 +554,7 @@ dependencies = [
  "heck 0.5.0",
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -722,7 +722,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "regex",
- "syn 2.0.70",
+ "syn 2.0.71",
  "synthez",
 ]
 
@@ -765,7 +765,7 @@ checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -1025,7 +1025,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -1090,7 +1090,7 @@ dependencies = [
  "quote",
  "serde",
  "serde_json",
- "syn 2.0.70",
+ "syn 2.0.71",
  "textwrap",
  "thiserror",
  "typed-builder",
@@ -1249,9 +1249,9 @@ dependencies = [
 
 [[package]]
 name = "http-body"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
 dependencies = [
  "bytes",
  "http",
@@ -1484,7 +1484,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "regex",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -1663,6 +1663,30 @@ dependencies = [
 ]
 
 [[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
 name = "num-bigint-dig"
 version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1680,6 +1704,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
 name = "num-integer"
 version = "0.1.46"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1700,6 +1733,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
 name = "num-traits"
 version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1757,7 +1801,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -1802,7 +1846,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
 dependencies = [
  "cfg-if",
  "libc",
- "redox_syscall 0.5.2",
+ "redox_syscall 0.5.3",
  "smallvec",
  "windows-targets 0.52.6",
 ]
@@ -1872,7 +1916,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -2053,9 +2097,9 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
+checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
 dependencies = [
  "bitflags 2.6.0",
 ]
@@ -2193,14 +2237,14 @@ dependencies = [
  "heck 0.4.1",
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
 name = "security-framework"
-version = "2.11.0"
+version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
 dependencies = [
  "bitflags 2.6.0",
  "core-foundation",
@@ -2211,9 +2255,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework-sys"
-version = "2.11.0"
+version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7"
+checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -2236,7 +2280,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -2336,7 +2380,7 @@ checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -2632,9 +2676,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.70"
+version = "2.0.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16"
+checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2650,7 +2694,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -2671,7 +2715,7 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a3d2c2202510a1e186e63e596d9318c91a8cbe85cd1a56a7be0c333e5f59ec8d"
 dependencies = [
- "syn 2.0.70",
+ "syn 2.0.71",
  "synthez-codegen",
  "synthez-core",
 ]
@@ -2682,7 +2726,7 @@ version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f724aa6d44b7162f3158a57bccd871a77b39a4aef737e01bcdff41f4772c7746"
 dependencies = [
- "syn 2.0.70",
+ "syn 2.0.71",
  "synthez-core",
 ]
 
@@ -2695,7 +2739,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "sealed",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -2742,22 +2786,22 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.61"
+version = "1.0.62"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
+checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.61"
+version = "1.0.62"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
+checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -2806,7 +2850,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -2910,7 +2954,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -2958,7 +3002,7 @@ checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]
@@ -3070,6 +3114,8 @@ dependencies = [
  "cucumber",
  "futures",
  "hmac",
+ "num",
+ "parking_lot",
  "rand",
  "serde",
  "sha2",
@@ -3133,7 +3179,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
  "wasm-bindgen-shared",
 ]
 
@@ -3167,7 +3213,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -3403,7 +3449,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.70",
+ "syn 2.0.71",
 ]
 
 [[package]]

+ 2 - 0
utxo/Cargo.toml

@@ -10,6 +10,8 @@ borsh = { version = "1.3.1", features = ["derive", "bytes", "de_strict_order"] }
 chrono = { version = "0.4.31", features = ["serde"] }
 futures = { version = "0.3.30", optional = true }
 hmac = "0.12.1"
+num = "0.4.3"
+parking_lot = "0.12.2"
 rand = "0.8.5"
 serde = { version = "1.0.188", features = ["derive"] }
 sha2 = "0.10.7"

+ 8 - 1
utxo/src/amount.rs

@@ -68,7 +68,14 @@ impl TryInto<Amount> for AnyAmount {
 /// The `cents` and `Asset` must be used to store amounts in the storage
 /// layer. Float or string representations should be used to display
 #[derive(
-    Clone, Debug, Eq, PartialEq, Deserialize, borsh::BorshSerialize, borsh::BorshDeserialize,
+    Clone,
+    Debug,
+    Eq,
+    PartialEq,
+    PartialOrd,
+    Deserialize,
+    borsh::BorshSerialize,
+    borsh::BorshDeserialize,
 )]
 pub struct Amount {
     asset: Asset,

+ 3 - 1
utxo/src/asset.rs

@@ -8,7 +8,9 @@ use std::{
     str::FromStr,
 };
 
-#[derive(Debug, Hash, PartialEq, Eq, Clone, borsh::BorshSerialize, borsh::BorshDeserialize)]
+#[derive(
+    Debug, Hash, PartialEq, PartialOrd, Eq, Clone, borsh::BorshSerialize, borsh::BorshDeserialize,
+)]
 /// An asset type
 pub struct Asset {
     /// The name of the asset

+ 31 - 0
utxo/src/expr/mod.rs

@@ -0,0 +1,31 @@
+//! Expression module
+
+#![allow(warnings)]
+
+mod opcode;
+mod program;
+mod runtime;
+mod value;
+
+type Register = usize;
+type Addr = usize;
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[error("Program is out of bound")]
+    OutOfBoundaries,
+
+    #[error("Register {0} is out of boundaries")]
+    RegisterOutOfBoundaries(Register),
+
+    #[error("Empty registers")]
+    EmptyRegisters,
+
+    #[error("Value is not a number")]
+    NotANumber,
+
+    #[error("Math overflow")]
+    Overflow,
+}
+
+pub use self::program::Program;

+ 34 - 0
utxo/src/expr/opcode.rs

@@ -0,0 +1,34 @@
+use super::{value::Value, Addr, Register};
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum OpCode {
+    /// LOAD <destination> <value>
+    LOAD(Register, Value),
+    /// Copy the value from the source register to the destination register
+    /// CPY <destination> <source>
+    CPY(Register, Register),
+    /// Move the value from the source register to the destination register
+    /// MOV <destination> <source>
+    MOV(Register, Register),
+    /// Compare two registers and store the result in the destination register
+    /// CMP <destination> <source1> <source2>
+    CMP(Register, Register, Register),
+    /// ADD <destination> <source1> <source2>
+    ADD(Register, Register, Register),
+    /// ADD <destination> <source1> <source2>
+    AND(Register, Register, Register),
+    OR(Register, Register, Register),
+    XOR(Register, Register, Register),
+    NOT(Register, Register),
+    /// JMP <address>
+    JMP(Addr),
+    /// JEQ <register> <address>
+    ///
+    /// Jumps if the value in the register is true
+    JEQ(Register, Addr),
+    /// JNE <register> <address>
+    ///
+    /// Jumps if the value in the register is false
+    JNE(Register, Addr),
+    HLT,
+}

+ 55 - 0
utxo/src/expr/program.rs

@@ -0,0 +1,55 @@
+use super::{opcode::OpCode, runtime::execute, value::Value, Addr, Error};
+use crate::Transaction;
+
+#[derive(Debug, Clone)]
+pub struct Program {
+    /// The list of opcodes that make up the program
+    opcodes: Vec<OpCode>,
+    /// If the program has some boilerplate that can be skipped. This is non-zero when the initial
+    /// program has been executed at compile time, and the register has been populated to
+    /// `initial_register`. Everytime a new Runtime is created, instead of executing the program,
+    /// it can safely start-off a different Address, and clone the initial_register.
+    start_at: Addr,
+    /// The state of the register
+    initial_register: Vec<Value>,
+}
+
+impl Program {
+    pub fn compile(_program: &str) -> Result<Self, Error> {
+        todo!()
+    }
+
+    /// Creates a program tailored for a transaction
+    pub fn execute(&self, transaction: Option<&Transaction>) -> Result<Value, Error> {
+        execute(
+            transaction,
+            &self.opcodes,
+            self.initial_register.clone(),
+            self.start_at,
+        )
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::Program;
+    use crate::expr::{opcode::OpCode, value::Value};
+
+    #[test]
+    fn simple_program() {
+        let program = Program {
+            opcodes: vec![
+                OpCode::LOAD(1, 12.into()),
+                OpCode::LOAD(2, 13.into()),
+                OpCode::ADD(3, 1, 2),
+                OpCode::ADD(4, 0, 3),
+                OpCode::MOV(0, 4),
+                OpCode::HLT,
+            ],
+            start_at: 0,
+            initial_register: vec![15.into()],
+        };
+        let x = program.execute(None).expect("valid execution");
+        assert_eq!(x, 40.into());
+    }
+}

+ 157 - 0
utxo/src/expr/runtime.rs

@@ -0,0 +1,157 @@
+use super::{opcode::OpCode, value::Value, Addr, Error};
+use crate::Transaction;
+use num::CheckedAdd;
+use std::{collections::VecDeque, ops::Deref};
+
+#[derive(Debug, PartialEq, PartialOrd)]
+/// Value or reference to a value.
+///
+/// A reference to a value is being used to avoid cloning from the source code to the registers,
+/// instead to just use a readonly reference
+enum ValueOrRef<'a> {
+    Ref(&'a Value),
+    Value(Value),
+}
+
+impl<'a> Clone for ValueOrRef<'a> {
+    fn clone(&self) -> Self {
+        match self {
+            ValueOrRef::Ref(value) => ValueOrRef::Value((*value).clone()),
+            ValueOrRef::Value(value) => ValueOrRef::Value(value.clone()),
+        }
+    }
+}
+
+impl<'a> Into<Value> for ValueOrRef<'a> {
+    fn into(self) -> Value {
+        match self {
+            ValueOrRef::Ref(value) => value.clone(),
+            ValueOrRef::Value(value) => value,
+        }
+    }
+}
+
+impl Deref for ValueOrRef<'_> {
+    type Target = Value;
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            ValueOrRef::Ref(value) => *value,
+            ValueOrRef::Value(value) => &value,
+        }
+    }
+}
+
+impl<'a> From<&'a Value> for ValueOrRef<'a> {
+    fn from(value: &'a Value) -> Self {
+        ValueOrRef::Ref(value)
+    }
+}
+
+impl<'a> From<Value> for ValueOrRef<'a> {
+    fn from(value: Value) -> Self {
+        ValueOrRef::Value(value)
+    }
+}
+
+macro_rules! get {
+    ($r:expr,$pos:expr) => {
+        ($r.get($pos)
+            .ok_or_else(|| Error::RegisterOutOfBoundaries($pos))?)
+    };
+}
+
+macro_rules! set {
+    ($r:expr, $pos:expr, $new_value:expr) => {
+        (if let Some(previous_value) = $r.get_mut($pos) {
+            *previous_value = $new_value;
+        } else {
+            $r.push_back($new_value);
+            if $r.len() != $pos + 1 {
+                return Err(Error::RegisterOutOfBoundaries($pos));
+            }
+        })
+    };
+}
+
+#[inline]
+pub fn execute(
+    transaction: Option<&Transaction>,
+    code: &[OpCode],
+    initial_registers: Vec<Value>,
+    start_at: Addr,
+) -> Result<Value, Error> {
+    let mut execution = start_at;
+    let mut registers: VecDeque<ValueOrRef> = initial_registers
+        .iter()
+        .map(|a| a.into())
+        .collect::<VecDeque<_>>();
+
+    loop {
+        match code.get(execution).ok_or(Error::OutOfBoundaries)? {
+            OpCode::LOAD(dst, ref val) => set!(registers, *dst, val.into()),
+            OpCode::CPY(dst, reg2) => {
+                let value = get!(registers, *reg2).clone();
+                set!(registers, *dst, value);
+            }
+            OpCode::MOV(dst, reg2) => {
+                let _ = get!(registers, *reg2);
+                let previous_value = std::mem::replace(&mut registers[*reg2], Value::Nil.into());
+                set!(registers, *dst, previous_value);
+            }
+            OpCode::CMP(dst, reg2, reg3) => {
+                let new_value =
+                    Value::Bool(get!(registers, *reg2) == get!(registers, *reg3)).into();
+                set!(registers, *dst, new_value);
+            }
+            OpCode::ADD(dst, a, b) => {
+                let new_value = get!(registers, *a)
+                    .checked_add(get!(registers, *b))
+                    .ok_or(Error::Overflow)?
+                    .into();
+                set!(registers, *dst, new_value);
+            }
+            OpCode::AND(dst, reg2, reg3) => {
+                let new_value = Value::Bool(
+                    get!(registers, *reg2).as_boolean()? && get!(registers, *reg3).as_boolean()?,
+                )
+                .into();
+                set!(registers, *dst, new_value);
+            }
+            OpCode::OR(dst1, reg2, reg3) => {
+                todo!()
+            }
+            OpCode::XOR(dst, reg2, reg3) => {
+                todo!()
+            }
+            OpCode::NOT(dst, reg2) => {
+                let new_value = Value::Bool(!get!(registers, *reg2).as_boolean()?).into();
+                set!(registers, *dst, new_value);
+            }
+            OpCode::JMP(addr) => {
+                execution = *addr;
+                continue;
+            }
+            OpCode::JEQ(reg, addr) => {
+                if get!(registers, *reg).as_boolean()? {
+                    execution = *addr;
+                    continue;
+                }
+            }
+            OpCode::JNE(reg, addr) => {
+                if !get!(registers, *reg).as_boolean()? {
+                    execution = *addr;
+                    continue;
+                }
+            }
+            OpCode::HLT => break,
+        }
+
+        execution += 1;
+    }
+
+    registers
+        .pop_front()
+        .map(|x| x.into())
+        .ok_or(Error::EmptyRegisters)
+}

+ 77 - 0
utxo/src/expr/value.rs

@@ -0,0 +1,77 @@
+use std::ops::Add;
+
+use crate::{
+    payment::PaymentTo, AccountId, Amount, Asset, PaymentFrom, RevId, Status, Tag, TxId, Type,
+};
+use chrono::{DateTime, Utc};
+use num::CheckedAdd;
+
+use super::Error;
+
+#[derive(Clone, Debug, PartialEq, PartialOrd)]
+pub enum Value {
+    Nil,
+    Amount(Amount),
+    Asset(Asset),
+    AccountId(AccountId),
+    TxId(TxId),
+    Type(Type),
+    RevId(RevId),
+    Status(Status),
+    Tag(Tag),
+    Date(DateTime<Utc>),
+    String(String),
+    Number(i128),
+    From(Vec<PaymentFrom>),
+    To(Vec<PaymentTo>),
+    Tags(Vec<Tag>),
+    Bool(bool),
+}
+
+impl From<i128> for Value {
+    fn from(x: i128) -> Self {
+        Value::Number(x)
+    }
+}
+
+impl Value {
+    /// Attempts to convert the value as a boolean if possible or returns an error.
+    pub fn as_boolean(&self) -> Result<bool, Error> {
+        match self {
+            Value::Bool(b) => Ok(*b),
+            Value::Nil => Ok(false),
+            Value::Number(x) => Ok(*x > 0),
+            _ => panic!("Cannot convert to bool"),
+        }
+    }
+
+    pub fn as_number(&self) -> Result<i128, Error> {
+        match self {
+            Value::Number(x) => Ok(*x),
+            Value::String(x) => x.parse().map_err(|_| Error::NotANumber),
+            _ => Err(Error::NotANumber),
+        }
+    }
+}
+
+impl Add for Value {
+    type Output = Self;
+
+    fn add(self, other: Self) -> Self {
+        Value::Number(
+            self.as_number()
+                .expect("Not a number")
+                .checked_add(other.as_number().expect("Not a number"))
+                .expect("Overflow"),
+        )
+    }
+}
+
+impl CheckedAdd for Value {
+    fn checked_add(&self, other: &Self) -> Option<Self> {
+        self.as_number()
+            .ok()?
+            .checked_add(other.as_number().ok()?)
+            .map(Value::Number)
+    }
+}

+ 1 - 0
utxo/src/lib.rs

@@ -28,6 +28,7 @@ mod asset;
 mod broadcaster;
 mod config;
 mod error;
+mod expr;
 mod filter;
 mod id;
 mod ledger;

+ 2 - 0
utxo/src/payment.rs

@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
     Debug,
     Eq,
     PartialEq,
+    PartialOrd,
     Deserialize,
     Serialize,
     borsh::BorshDeserialize,
@@ -26,6 +27,7 @@ pub struct PaymentTo {
     Eq,
     PartialEq,
     Deserialize,
+    PartialOrd,
     Serialize,
     borsh::BorshDeserialize,
     borsh::BorshSerialize,