diff --git a/Cargo.toml b/Cargo.toml index ed7fb5f..ffcff44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ authors = ["ennucore "] [workspace] members = [ - "agolytics", "agorata_contracts" + "agolytics", "agorata_contracts", "agolang" ] [dependencies] diff --git a/agolang/Cargo.toml b/agolang/Cargo.toml new file mode 100644 index 0000000..eea2ae6 --- /dev/null +++ b/agolang/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "agolang" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0", features = ["derive"] } diff --git a/agolang/src/lib.rs b/agolang/src/lib.rs new file mode 100644 index 0000000..8084cd6 --- /dev/null +++ b/agolang/src/lib.rs @@ -0,0 +1,88 @@ +use serde::{Deserialize, Serialize}; +use crate::value::Value; + +/// Variables for matching. +pub mod value; +/// Templates for types. +pub mod templates; + +/// The Error type +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum AgolangError { + /// The given value is not of the expected type. + TypeError(String), + /// The given value(s) could not be matched. + ValueError(String), + /// The code is not valid. + CodeError(String), +} + +/// Code - a function that works on the variables. For now, the output is fixed. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Code { + /// Output + pub output: Vec, +} + +/// Function that takes a set of variables, matches them and returns a set of variables. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Function { + /// Input variables. + pub input: Vec, + /// Output variables. + pub output: Vec, + /// Code to run depending on the template. + pub code: Vec<(templates::ComplexMatcher, Code)>, +} + +impl Code { + /// Check if all the types add up. + pub fn check_correctness(&self) -> bool { + true + } + + /// Run the code and get the output. + pub fn eval(&self, _variables: &[Value]) -> Vec { + self.output.clone() + } +} + +impl Function { + /// Check if all the types add up. + pub fn check_correctness(&self) -> bool { + for c in &self.code { + if !c.0.check_correctness_for_types(&self.input) { + return false; + } + if !c.1.check_correctness() { + return false; + } + if !c.1.output.iter().zip(self.output.iter()).all(|(o, t)| o.var_type() == *t) { + return false; + } + } + true + } + + /// Get matcher for the whole input. + pub fn get_total_matcher(&self) -> templates::ComplexMatcher { + templates::ComplexMatcher::Or(self.code.iter().map(|c| c.0.clone()).collect()) + } + + /// Evaluate the function on the given values. + pub fn evaluate(&self, values: &[Value]) -> Result, AgolangError> { + if !self.check_correctness() { + return Err(AgolangError::CodeError("Function is not valid".to_string())); + } + for c in &self.code { + if c.0.match_values(values) { + return Ok(c.1.eval(values)); + } + } + Err(AgolangError::CodeError("Function could not be matched".to_string())) + } +} + +#[cfg(test)] +mod tests { +} diff --git a/agolang/src/templates.rs b/agolang/src/templates.rs new file mode 100644 index 0000000..8a6dfaf --- /dev/null +++ b/agolang/src/templates.rs @@ -0,0 +1,132 @@ +use serde::{Deserialize, Serialize}; +use crate::value::{Type, Value}; + +/// Matchers of values +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Matcher { + Any, + Fixed(Value), + NotEqual(Value), + Whitelist(Vec), + Blacklist(Vec), + IntRange(i64, i64), + Regexp(String), +} + +impl Matcher { + /// Check if this is a valid matcher for a given type. + pub fn check_correctness_for_type(&self, type_: Type) -> bool { + match self { + Matcher::Any => true, + Matcher::Fixed(v) => v.var_type() == type_, + Matcher::NotEqual(v) => v.var_type() == type_, + Matcher::Whitelist(v) | Matcher::Blacklist(v) => { + if v.is_empty() { + return false; + } + // Check that all values are of the same type and that there are no duplicates. + if v.iter().any(|v| v.var_type() != type_) { + return false; + } + let mut v_clone = v.clone(); + v_clone.dedup(); + v_clone.len() == v.len() + }, + Matcher::IntRange(i1, i2) => i1 <= i2 && type_ == Type::Int, + Matcher::Regexp(_) => type_ == Type::String, + } + } + + /// Match a value + pub fn match_value(&self, value: &Value) -> bool { + if !self.check_correctness_for_type(value.var_type()) { + return false; + } + match self { + Matcher::Any => true, + Matcher::Fixed(v) => v == value, + Matcher::NotEqual(v) => v != value, + Matcher::Whitelist(v) => v.contains(value), + Matcher::Blacklist(v) => !v.contains(value), + Matcher::IntRange(i1, i2) => match value { + Value::Int(i) => i >= i1 && i <= i2, + _ => false, + }, + Matcher::Regexp(_regexp) => match value { + Value::String(_s) => todo!(), + _ => false, + }, + } + } +} + + +/// **The Complex Matcher** +/// +/// It works on lists of variables and can be composed of other (primitive) matchers. +/// The length of the input list will be called the arity of the matcher. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ComplexMatcher { + /// Checks one value using a primitive matcher. The arity of the matcher is 1. + Primitive(Matcher), + /// Checks if the variables match all matchers. The arity of all matchers is the same. + And(Vec), + /// Checks if the variables match any matchers. The arity of all matchers is the same. + Or(Vec), + /// Checks the first variables (as many as possible) using the first matcher, then the second variables (as many as possible) using the second matcher, etc. + /// The arity of this matcher is the sum of the arities of all matchers. + Compose(Vec), +} + +impl ComplexMatcher { + /// Get the arity of the matcher. + pub fn arity(&self) -> usize { + match self { + ComplexMatcher::Primitive(_m) => 1, + ComplexMatcher::And(m) => m[0].arity(), + ComplexMatcher::Or(m) => m[0].arity(), + ComplexMatcher::Compose(m) => m.iter().map(|m| m.arity()).sum(), + } + } + + /// Check if this is a valid matcher for the given types. + pub fn check_correctness_for_types(&self, types: &[Type]) -> bool { + match self { + ComplexMatcher::Primitive(m) => types.len() == 1 && m.check_correctness_for_type(types[0]), + ComplexMatcher::And(ms) => ms.iter().all(|m| m.check_correctness_for_types(types)), + ComplexMatcher::Or(ms) => ms.iter().any(|m| m.check_correctness_for_types(types)), + ComplexMatcher::Compose(ms) => { + let mut sum = 0; + for m in ms { + if !m.check_correctness_for_types(&types[sum..sum + m.arity()]) { + return false; + } + sum += m.arity(); + } + sum == types.len() + }, + } + } + + /// Check a list of values against this matcher. + pub fn match_values(&self, values: &[Value]) -> bool { + if !self.check_correctness_for_types(&values.iter().map(|v| v.var_type()).collect::>()) { + return false; + } + match self { + ComplexMatcher::Primitive(m) => m.match_value(&values[0]), + ComplexMatcher::And(ms) => ms.iter().all(|m| m.match_values(values)), + ComplexMatcher::Or(ms) => ms.iter().any(|m| m.match_values(values)), + ComplexMatcher::Compose(ms) => { + let mut sum = 0; + for m in ms { + if !m.match_values(&values[sum..sum + m.arity()]) { + return false; + } + sum += m.arity(); + } + sum == values.len() + }, + } + } +} diff --git a/agolang/src/value.rs b/agolang/src/value.rs new file mode 100644 index 0000000..9e62c08 --- /dev/null +++ b/agolang/src/value.rs @@ -0,0 +1,69 @@ +use serde::{Deserialize, Serialize}; + +/// Values used for variables in templates. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Value { + Int(i64), + Bool(bool), + Address(Address), + Token(Address), + Data(Vec), + String(String), +} + +/// Types for variable values. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] +pub enum Type { + Int, + Bool, + Address, + Token, + Data, + String, +} + +impl Value { + /// Returns the type of the value. + pub fn var_type(&self) -> Type { + match self { + Value::Int(_) => Type::Int, + Value::Bool(_) => Type::Bool, + Value::Address(_) => Type::Address, + Value::Token(_) => Type::Token, + Value::Data(_) => Type::Data, + Value::String(_) => Type::String, + } + } +} + +/// For now, only TON addresses are supported. +/// See [here](https://ton.org/docs/#/howto/step-by-step) for docs. +/// Later, multiple blockchains will be added. +/// Also, another networks might be added to TON as separate workchains, thus they will be supported automatically. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Address { + /// Workchain id. + pub workchain: i32, + /// The address inside the workchain (64-512 bits depending on the workchain). + /// `address.0` is the length in bytes, the rest is the address. + pub address: (u8, [u8; 32], [u8; 32]), +} + +impl Address { + /// Creates a new address. + pub fn new(workchain: i32, address_vec: Vec) -> Self { + let address_len = address_vec.len(); + let address_bytes = address_vec.as_slice(); + Address { + workchain, + address: (address_len as u8, address_bytes[0..32].try_into().unwrap(), address_bytes[32..64].try_into().unwrap()), + } + } + + /// Get address in the workchain as bytes. + pub fn address_as_bytes(&self) -> Vec { + let mut address = self.address.1[..self.address.0.min(32) as usize].to_vec(); + address.extend_from_slice(&self.address.2[..(self.address.0 as i16 - 32).max(0) as usize]); + address + } +} diff --git a/agorata_contracts/Cargo.toml b/agorata_contracts/Cargo.toml index 05642e4..d81a327 100644 --- a/agorata_contracts/Cargo.toml +++ b/agorata_contracts/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] serde = { version = "1.0", features = ["derive"] } +agolang = { path = "../agolang" } diff --git a/agorata_contracts/src/lib.rs b/agorata_contracts/src/lib.rs index 61f892a..1d3fefe 100644 --- a/agorata_contracts/src/lib.rs +++ b/agorata_contracts/src/lib.rs @@ -2,10 +2,6 @@ extern crate serde; /// Message and Address types pub mod message; -/// Templates for types. -pub mod templates; -/// Variables for matching. -pub mod value; pub mod state; #[cfg(test)] diff --git a/agorata_contracts/src/message.rs b/agorata_contracts/src/message.rs index a8d571d..7cec5fb 100644 --- a/agorata_contracts/src/message.rs +++ b/agorata_contracts/src/message.rs @@ -1,36 +1,5 @@ use serde::{Deserialize, Serialize}; - -/// For now, only TON addresses are supported. -/// See [here](https://ton.org/docs/#/howto/step-by-step) for docs. -/// Later, multiple blockchains will be added. -/// Also, another networks might be added to TON as separate workchains, thus they will be supported automatically. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub struct Address { - /// Workchain id. - pub workchain: i32, - /// The address inside the workchain (64-512 bits depending on the workchain). - /// `address.0` is the length in bytes, the rest is the address. - pub address: (u8, [u8; 32], [u8; 32]), -} - -impl Address { - /// Creates a new address. - pub fn new(workchain: i32, address_vec: Vec) -> Self { - let address_len = address_vec.len(); - let address_bytes = address_vec.as_slice(); - Address { - workchain, - address: (address_len as u8, address_bytes[0..32].try_into().unwrap(), address_bytes[32..64].try_into().unwrap()), - } - } - - /// Get address in the workchain as bytes. - pub fn address_as_bytes(&self) -> Vec { - let mut address = self.address.1[..self.address.0.min(32) as usize].to_vec(); - address.extend_from_slice(&self.address.2[..(self.address.0 as i16 - 32).max(0) as usize]); - address - } -} +use agolang::value::Address; /// The Message. For now, it's the TON Message. Later, it will be adapted to other blockchains. /// See [here](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb) for the full TON message schema (seizure warning) or [here](https://ton.org/docs/#/smart-contracts/messages) for a humane version. diff --git a/agorata_contracts/src/state.rs b/agorata_contracts/src/state.rs index edc8910..dad6088 100644 --- a/agorata_contracts/src/state.rs +++ b/agorata_contracts/src/state.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use crate::value::{Type, Value}; +use agolang::value::{Type, Value}; /// Description of a smart contract's state format. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/agorata_contracts/src/templates.rs b/agorata_contracts/src/templates.rs deleted file mode 100644 index b7710ba..0000000 --- a/agorata_contracts/src/templates.rs +++ /dev/null @@ -1,61 +0,0 @@ -use serde::{Deserialize, Serialize}; -use crate::value::{Type, Value}; - -/// Matchers of values -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Matcher { - Any, - Fixed(Value), - NotEqual(Value), - Whitelist(Vec), - Blacklist(Vec), - IntRange(i64, i64), - Regexp(String), -} - -impl Matcher { - /// Check if this is a valid matcher for a given type. - pub fn check_correctness_for_type(&self, type_: Type) -> bool { - match self { - Matcher::Any => true, - Matcher::Fixed(v) => v.var_type() == type_, - Matcher::NotEqual(v) => v.var_type() == type_, - Matcher::Whitelist(v) | Matcher::Blacklist(v) => { - if v.is_empty() { - return false; - } - // Check that all values are of the same type and that there are no duplicates. - if v.iter().any(|v| v.var_type() != type_) { - return false; - } - let mut v_clone = v.clone(); - v_clone.dedup(); - v_clone.len() == v.len() - }, - Matcher::IntRange(i1, i2) => i1 <= i2 && type_ == Type::Int, - Matcher::Regexp(_) => type_ == Type::String, - } - } - - /// Match a value - pub fn match_value(&self, value: &Value) -> bool { - if !self.check_correctness_for_type(value.var_type()) { - return false; - } - match self { - Matcher::Any => true, - Matcher::Fixed(v) => v == value, - Matcher::NotEqual(v) => v != value, - Matcher::Whitelist(v) => v.contains(value), - Matcher::Blacklist(v) => !v.contains(value), - Matcher::IntRange(i1, i2) => match value { - Value::Int(i) => i >= i1 && i <= i2, - _ => false, - }, - Matcher::Regexp(_regexp) => match value { - Value::String(_s) => todo!(), - _ => false, - }, - } - } -} diff --git a/agorata_contracts/src/value.rs b/agorata_contracts/src/value.rs deleted file mode 100644 index 77a8f7f..0000000 --- a/agorata_contracts/src/value.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::message::Address; -use serde::{Deserialize, Serialize}; - -/// Values used for variables in templates. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Value { - Int(i64), - Bool(bool), - Address(Address), - Token(Address), - Data(Vec), - String(String), -} - -/// Types for variable values. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] -pub enum Type { - Int, - Bool, - Address, - Token, - Data, - String, -} - -impl Value { - /// Returns the type of the value. - pub fn var_type(&self) -> Type { - match self { - Value::Int(_) => Type::Int, - Value::Bool(_) => Type::Bool, - Value::Address(_) => Type::Address, - Value::Token(_) => Type::Token, - Value::Data(_) => Type::Data, - Value::String(_) => Type::String, - } - } -}