From bd2ede2a388a2cb15de9ed9c5f6bcc019b8f945d Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Thu, 9 Apr 2020 21:34:13 +0200 Subject: [PATCH 1/6] Validate schema and transitions fields --- .gitignore | 2 ++ src/common/macros.rs | 8 ++++- src/rgb/schema/field.rs | 56 +++++++++++++++++++++++++++++ src/rgb/schema/schema.rs | 13 ++++--- src/rgb/schema/transition.rs | 2 +- src/rgb/schemata/fungible.rs | 69 ++++++++++++++++++++---------------- src/rgb/schemata/mod.rs | 4 +-- src/rgb/serialize.rs | 3 ++ src/rgb/transition.rs | 1 + 9 files changed, 119 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index b9299ca5..713df3d5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ Cargo.lock **/*.rs.bk .idea + +*.swp diff --git a/src/common/macros.rs b/src/common/macros.rs index a19e4ee4..e483746c 100644 --- a/src/common/macros.rs +++ b/src/common/macros.rs @@ -24,6 +24,12 @@ macro_rules! bytes { #[macro_export] macro_rules! map { + { } => { + { + ::std::collections::HashMap::new() + } + }; + { $($key:expr => $value:expr),+ } => { { let mut m = ::std::collections::HashMap::new(); @@ -59,4 +65,4 @@ macro_rules! hlist { m } } -} \ No newline at end of file +} diff --git a/src/rgb/schema/field.rs b/src/rgb/schema/field.rs index 46b09552..ab7c316a 100644 --- a/src/rgb/schema/field.rs +++ b/src/rgb/schema/field.rs @@ -15,6 +15,8 @@ use std::io; use super::types::*; +use super::schema::ValidationError; +use crate::rgb::metadata::{Metadata, Type, Value}; use crate::csv::serialize::*; @@ -32,6 +34,35 @@ pub enum FieldFormat { Signature(SignatureAlgorithm), } +impl FieldFormat { + pub fn validate(&self, value: &Value) -> Result<(), ValidationError> { + match (self, value) { + (FieldFormat::Unsigned { bits: Bits::Bit256, min: None, max: None }, Value::U256(_)) => Ok(()), + (FieldFormat::Unsigned { bits: Bits::Bit256, .. }, Value::U256(_)) => Err(ValidationError::MinMaxBoundsOnLargeInt), + (FieldFormat::Unsigned { bits: Bits::Bit128, min: None, max: None }, Value::U128(_)) => Ok(()), + (FieldFormat::Unsigned { bits: Bits::Bit128, .. }, Value::U128(_)) => Err(ValidationError::MinMaxBoundsOnLargeInt), + (FieldFormat::Unsigned { bits: Bits::Bit64, min, max }, Value::U64(val)) if *val >= min.unwrap_or(0) && *val <= max.unwrap_or(u64::MAX) => Ok(()), + (FieldFormat::Unsigned { bits: Bits::Bit32, min, max }, Value::U32(val)) if *val as u64 >= min.unwrap_or(0) && *val as u64 <= max.unwrap_or(u32::MAX as u64) => Ok(()), + (FieldFormat::Unsigned { bits: Bits::Bit16, min, max }, Value::U16(val)) if *val as u64 >= min.unwrap_or(0) && *val as u64 <= max.unwrap_or(u16::MAX as u64) => Ok(()), + (FieldFormat::Unsigned { bits: Bits::Bit8, min, max }, Value::U8(val)) if *val as u64 >= min.unwrap_or(0) && *val as u64 <= max.unwrap_or(u8::MAX as u64) => Ok(()), + (FieldFormat::Integer { bits: Bits::Bit64, min, max }, Value::I64(val)) if *val >= min.unwrap_or(0) && *val <= max.unwrap_or(i64::MAX) => Ok(()), + (FieldFormat::Integer { bits: Bits::Bit32, min, max }, Value::I32(val)) if *val as i64 >= min.unwrap_or(0) && *val as i64 <= max.unwrap_or(i32::MAX as i64) => Ok(()), + (FieldFormat::Integer { bits: Bits::Bit16, min, max }, Value::I16(val)) if *val as i64 >= min.unwrap_or(0) && *val as i64 <= max.unwrap_or(i16::MAX as i64) => Ok(()), + (FieldFormat::Integer { bits: Bits::Bit8, min, max }, Value::I8(val)) if *val as i64 >= min.unwrap_or(0) && *val as i64 <= max.unwrap_or(i8::MAX as i64) => Ok(()), + (FieldFormat::Float { bits: Bits::Bit64, min, max }, Value::F64(val)) if *val >= min.unwrap_or(0.0) && *val <= max.unwrap_or(f64::MAX) => Ok(()), + (FieldFormat::Float { bits: Bits::Bit32, min, max }, Value::F32(val)) if *val as f64 >= min.unwrap_or(0.0) && *val as f64 <= max.unwrap_or(f32::MAX as f64) => Ok(()), + + (FieldFormat::Enum{ values }, Value::U8(val)) if values.contains(val) => Ok(()), + (FieldFormat::String(max_len), Value::Str(string)) if string.len() <= *max_len as usize => Ok(()), + (FieldFormat::Bytes(max_len), Value::Bytes(bytes)) if bytes.len() <= *max_len as usize => Ok(()), + + // TODO: other types when added to metadata::Value + + _ => Err(ValidationError::InvalidValue(value.clone())) + } + } +} + impl Commitment for FieldFormat { fn commitment_serialize(&self, mut e: E) -> Result { Ok(match self { @@ -58,6 +89,31 @@ impl Commitment for FieldFormat { #[display_from(Debug)] pub struct Field(pub FieldFormat, pub Occurences); +impl Field { + pub fn validate(&self, field_type: Type, metadata: &Metadata) -> Result<(), ValidationError> { + let count: u8 = metadata + .iter() + .filter_map(|m| { + if m.id == field_type { + Some(&m.val) + } else { + None + } + }) + .try_fold(0, |acc, val| self.0.validate(&val).and_then(|_| Ok(acc + 1)))?; + + match (self.1, count) { + (Occurences::Once, 1) => Ok(()), + (Occurences::NoneOrOnce, 0..=1 ) => Ok(()), + (Occurences::OnceOrUpTo(None), 1 ..= u8::MAX) => Ok(()), + (Occurences::OnceOrUpTo(Some(max)), x) if x > 0 && x <= max => Ok(()), + (Occurences::NoneOrUpTo(None), 0 ..= u8::MAX) => Ok(()), + (Occurences::NoneOrUpTo(Some(max)), x) if x <= max => Ok(()), + _ => Err(ValidationError::InvalidFieldOccurences), + } + } +} + impl Commitment for Field { fn commitment_serialize(&self, mut e: E) -> Result { self.0.commitment_serialize(&mut e)?; diff --git a/src/rgb/schema/schema.rs b/src/rgb/schema/schema.rs index fb549ed1..97eca9d9 100644 --- a/src/rgb/schema/schema.rs +++ b/src/rgb/schema/schema.rs @@ -22,20 +22,23 @@ use super::{ types::*, transition::* }; +use crate::rgb::metadata; use crate::csv::{ConsensusCommit, serialize, Error}; -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Display)] +#[derive(Clone, PartialEq, PartialOrd, Debug, Display)] #[display_from(Debug)] -pub struct ValidationError { - +pub enum ValidationError { + InvalidValue(metadata::Value), + MinMaxBoundsOnLargeInt, + InvalidFieldOccurences, } #[derive(Clone, Debug, Display)] #[display_from(Debug)] pub struct Schema { pub seals: HashMap, - pub transitions: Vec, + pub transitions: HashMap, } impl Schema { @@ -57,7 +60,7 @@ impl serialize::Commitment for Schema { fn commitment_deserialize(mut d: D) -> Result { Ok(Self { seals: >::commitment_deserialize(&mut d)?, - transitions: >::commitment_deserialize(&mut d)?, + transitions: >::commitment_deserialize(&mut d)?, }) } } diff --git a/src/rgb/schema/transition.rs b/src/rgb/schema/transition.rs index 110dbb83..620ccd0a 100644 --- a/src/rgb/schema/transition.rs +++ b/src/rgb/schema/transition.rs @@ -28,7 +28,7 @@ use crate::csv::{serialize::Commitment, Error}; #[display_from(Debug)] pub struct Transition { pub closes: Option>>, - pub fields: Vec, + pub fields: HashMap, pub binds: HashMap>, pub scripting: Scripting, } diff --git a/src/rgb/schemata/fungible.rs b/src/rgb/schemata/fungible.rs index beab8781..eaa954c5 100644 --- a/src/rgb/schemata/fungible.rs +++ b/src/rgb/schemata/fungible.rs @@ -62,14 +62,22 @@ pub struct Rgb1(); impl Rgb1 { const PRIM_ISSUE_TS: usize = 0; - const SEC_ISSUE_TS: usize = 0; - const TRANSFER_TS: usize = 0; - const PRINE_TS: usize = 0; + const SEC_ISSUE_TS: usize = 1; + const TRANSFER_TS: usize = 2; + const PRUNE_TS: usize = 3; const ISSUE_SEAL: usize = 0; const BALANCE_SEAL: usize = 1; const PRUNE_SEAL: usize = 2; + const TICKER_FIELD: usize = 0; + const TITLE_FIELD: usize = 1; + const DESCRIPTION_FIELD: usize = 2; + const TOTAL_SUPPLY_FIELD: usize = 3; + const FRACTIONAL_BITS_FIELD: usize = 4; + const DUST_LIMIT_FIELD: usize = 5; + const NETWORK_FIELD: usize = 6; + fn balances_to_bound_state(balances: Balances) -> Result { let seals_count = balances.len(); Ok(rgb::State::from_inner( @@ -99,36 +107,37 @@ impl Rgb1 { //let ts_schema = &schema.transitions[PRIM_ISSUE_TS]; let mut meta = rgb::Metadata::from_inner(vec![ - metadata::Field { id: metadata::Type(0), val: metadata::Value::Str(String::from(ticker)) }, - metadata::Field { id: metadata::Type(1), val: metadata::Value::Str(String::from(name)) }, - metadata::Field { id: metadata::Type(5), val: metadata::Value::U8(precision) }, - metadata::Field { id: metadata::Type(7), val: metadata::Value::U32(network.into()) }, + metadata::Field { id: metadata::Type(Self::TICKER_FIELD as u16), val: metadata::Value::Str(String::from(ticker)) }, + metadata::Field { id: metadata::Type(Self::TITLE_FIELD as u16), val: metadata::Value::Str(String::from(name)) }, + metadata::Field { id: metadata::Type(Self::FRACTIONAL_BITS_FIELD as u16), val: metadata::Value::U8(precision) }, + metadata::Field { id: metadata::Type(Self::NETWORK_FIELD as u16), val: metadata::Value::U32(network.into()) }, ]); if let Some(descr) = descr { meta.as_mut().push( - metadata::Field { id: metadata::Type(2), val: metadata::Value::Str(String::from(descr)) } + metadata::Field { id: metadata::Type(Self::DESCRIPTION_FIELD as u16), val: metadata::Value::Str(String::from(descr)) } ); } if let Some(supply) = supply { + // TODO: why is this optional? meta.as_mut().push( - metadata::Field { id: metadata::Type(3), val: metadata::Value::U64(supply) } + metadata::Field { id: metadata::Type(Self::TOTAL_SUPPLY_FIELD as u16), val: metadata::Value::U64(supply) } ); } if let Some(dust) = dust { meta.as_mut().push( - metadata::Field { id: metadata::Type(5), val: metadata::Value::U64(dust) } + metadata::Field { id: metadata::Type(Self::DUST_LIMIT_FIELD as u16), val: metadata::Value::U64(dust) } ); } let state = Self::balances_to_bound_state(balances)?; - Ok(rgb::Transition { meta, state, script: None }) + Ok(rgb::Transition { id: Self::PRIM_ISSUE_TS, meta, state, script: None }) } pub fn transfer(balances: Balances) -> Result { let state = Self::balances_to_bound_state(balances)?; - Ok(rgb::Transition { meta: rgb::Metadata::default(), state, script: None }) + Ok(rgb::Transition { id: Self::TRANSFER_TS, meta: rgb::Metadata::default(), state, script: None }) } } @@ -144,26 +153,26 @@ impl Schemata for Rgb1 { Self::BALANCE_SEAL => Amount, Self::PRUNE_SEAL => NoState }, - transitions: vec![ + transitions: map!{ // Genesis state: primary issue - Transition { + Self::PRIM_ISSUE_TS => Transition { closes: None, - fields: vec![ + fields: map!{ // Ticker - Field(FieldFormat::String(16), Once), + Self::TICKER_FIELD => Field(FieldFormat::String(16), Once), // Title - Field(FieldFormat::String(256), Once), + Self::TITLE_FIELD => Field(FieldFormat::String(256), Once), // Description - Field(FieldFormat::String(1024), NoneOrOnce), + Self::DESCRIPTION_FIELD => Field(FieldFormat::String(1024), NoneOrOnce), // Total supply - Field(FieldFormat::Unsigned { bits: Bit64, min: None, max: None }, NoneOrOnce), + Self::TOTAL_SUPPLY_FIELD => Field(FieldFormat::Unsigned { bits: Bit64, min: None, max: None }, NoneOrOnce), // Fractional bits - Field(FieldFormat::Unsigned { bits: Bit8, min: None, max: None }, Once), + Self::FRACTIONAL_BITS_FIELD => Field(FieldFormat::Unsigned { bits: Bit8, min: None, max: None }, Once), // Dust limit - Field(FieldFormat::Unsigned { bits: Bit64, min: None, max: None }, NoneOrOnce), + Self::DUST_LIMIT_FIELD => Field(FieldFormat::Unsigned { bits: Bit64, min: None, max: None }, NoneOrOnce), // Network - Field(FieldFormat::Unsigned { bits: Bit32, min: None, max: None }, Once), - ], + Self::NETWORK_FIELD => Field(FieldFormat::Unsigned { bits: Bit32, min: None, max: None }, Once) + }, binds: map!{ Self::BALANCE_SEAL => OnceOrUpTo(None), Self::ISSUE_SEAL => NoneOrOnce, @@ -175,11 +184,11 @@ impl Schemata for Rgb1 { } }, // Issuance transition: secondary issue - Transition { + Self::SEC_ISSUE_TS => Transition { closes: Some(map! { Self::ISSUE_SEAL => Once }), - fields: vec![], + fields: map!{}, binds: map!{ Self::BALANCE_SEAL => OnceOrUpTo(None), Self::ISSUE_SEAL => NoneOrUpTo(None) @@ -190,11 +199,11 @@ impl Schemata for Rgb1 { } }, // Amount transition: asset transfers - Transition { + Self::TRANSFER_TS => Transition { closes: Some(map!{ Self::BALANCE_SEAL => OnceOrUpTo(None) }), - fields: vec![], + fields: map!{}, binds: map!{ Self::BALANCE_SEAL => NoneOrUpTo(None) }, @@ -204,11 +213,11 @@ impl Schemata for Rgb1 { } }, // Pruning transition: asset re-issue - Transition { + Self::PRUNE_TS => Transition { closes: Some(map!{ Self::PRUNE_SEAL => NoneOrOnce }), - fields: vec![], + fields: map!{}, binds: map!{ Self::BALANCE_SEAL => OnceOrUpTo(None), Self::PRUNE_SEAL => Once @@ -218,7 +227,7 @@ impl Schemata for Rgb1 { extensions: ScriptsDenied } } - ] + } }))); }); diff --git a/src/rgb/schemata/mod.rs b/src/rgb/schemata/mod.rs index c8bbfb9e..b24c0f22 100644 --- a/src/rgb/schemata/mod.rs +++ b/src/rgb/schemata/mod.rs @@ -14,10 +14,10 @@ use super::schema::Schema; pub mod fungible; -pub mod collectibles; +// pub mod collectibles; pub use fungible::Rgb1; -pub use collectibles::Rgb2; +// pub use collectibles::Rgb2; pub trait Schemata { diff --git a/src/rgb/serialize.rs b/src/rgb/serialize.rs index 8c0e8e1f..bec1df14 100644 --- a/src/rgb/serialize.rs +++ b/src/rgb/serialize.rs @@ -476,6 +476,7 @@ impl csv::serialize::Commitment for rgb::Transition { fn commitment_serialize(&self, mut e: E) -> Result { use crate::rgb::commit::Identifiable; Ok( + self.id.commitment_serialize(&mut e)? + self.meta.commitment()?.commitment_serialize(&mut e)? + self.state.commitment()?.commitment_serialize(&mut e)? + self.script.commitment()?.commitment_serialize(&mut e)? @@ -490,6 +491,7 @@ impl csv::serialize::Commitment for rgb::Transition { impl csv::serialize::Network for rgb::Transition { fn network_serialize(&self, mut e: E) -> Result { Ok( + self.id.network_serialize(&mut e)? + self.meta.network_serialize(&mut e)? + self.state.network_serialize(&mut e)? + self.script.network_serialize(&mut e)? @@ -498,6 +500,7 @@ impl csv::serialize::Network for rgb::Transition { fn network_deserialize(mut d: D) -> Result { Ok(Self { + id: csv::serialize::Network::network_deserialize(&mut d)?, meta: rgb::Metadata::network_deserialize(&mut d)?, state: rgb::State::network_deserialize(&mut d)?, script: Option::::network_deserialize(&mut d)? diff --git a/src/rgb/transition.rs b/src/rgb/transition.rs index d03258cd..4b959d6b 100644 --- a/src/rgb/transition.rs +++ b/src/rgb/transition.rs @@ -17,6 +17,7 @@ use super::{State, Metadata, Script}; #[derive(Clone, PartialEq, Debug, Display)] #[display_from(Debug)] pub struct Transition { + pub id: usize, pub meta: Metadata, pub state: State, pub script: Option