From e040f7da048f9eab1e3800769da5bc669323c971 Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Fri, 14 Jan 2022 13:53:58 +0100 Subject: [PATCH 1/2] introduce `Validated` and `Validate` for custom codec validation --- Cargo.lock | 14 ++++ frame/composable-support/Cargo.toml | 38 +++++++++ frame/composable-support/src/lib.rs | 15 ++++ frame/composable-support/src/validation.rs | 98 ++++++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 frame/composable-support/Cargo.toml create mode 100644 frame/composable-support/src/lib.rs create mode 100644 frame/composable-support/src/validation.rs diff --git a/Cargo.lock b/Cargo.lock index aeebd7302f3..637ad043505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1537,6 +1537,20 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "composable-support" +version = "0.0.1" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "proptest 1.0.0", + "scale-info", + "sp-arithmetic", + "sp-runtime", + "sp-std", +] + [[package]] name = "composable-tests-helpers" version = "0.0.1" diff --git a/frame/composable-support/Cargo.toml b/frame/composable-support/Cargo.toml new file mode 100644 index 00000000000..fa29d531d8f --- /dev/null +++ b/frame/composable-support/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "composable-support" +version = "0.0.1" +authors = ["Composable Developers"] +homepage = "https://composable.finance" +edition = "2021" +rust-version = "1.56" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dev-dependencies] +proptest = { version = "1.0" } + +[dependencies] +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } +sp-arithmetic = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" } +scale-info = { version = "1.0", default-features = false, features = ["derive"] } + +[dependencies.codec] +default-features = false +features = ["derive"] +package = "parity-scale-codec" +version = "2.0.0" + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "scale-info/std", +] \ No newline at end of file diff --git a/frame/composable-support/src/lib.rs b/frame/composable-support/src/lib.rs new file mode 100644 index 00000000000..eae93b38985 --- /dev/null +++ b/frame/composable-support/src/lib.rs @@ -0,0 +1,15 @@ +#![cfg_attr( + not(test), + warn( + clippy::disallowed_method, + clippy::disallowed_type, + clippy::indexing_slicing, + clippy::todo, + clippy::unwrap_used, + clippy::panic + ) +)] // allow in tests +#![warn(clippy::unseparated_literal_suffix)] +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod validation; diff --git a/frame/composable-support/src/validation.rs b/frame/composable-support/src/validation.rs new file mode 100644 index 00000000000..a96fdc2cdbd --- /dev/null +++ b/frame/composable-support/src/validation.rs @@ -0,0 +1,98 @@ +use core::marker::PhantomData; +use scale_info::TypeInfo; + +/// Black box that embbed the validated value. +#[derive(Eq, PartialEq, Debug, TypeInfo, codec::Encode)] +pub struct Validated { + pub value: T, + _marker: PhantomData, +} + +pub trait Validate { + fn validate(&self) -> Result<(), &'static str>; +} + +impl, U> codec::Decode for Validated { + fn decode(input: &mut I) -> Result { + let value = T::decode(input)?; + Validate::::validate(&value).map_err(|desc| Into::::into(desc))?; + Ok(Validated { value, _marker: PhantomData }) + } + fn skip(input: &mut I) -> Result<(), codec::Error> { + T::skip(input) + } +} + +impl, U> codec::EncodeLike for Validated {} + +#[cfg(test)] +mod test { + use super::*; + use codec::{Decode, Encode}; + + #[derive(Debug, Eq, PartialEq, codec::Encode, codec::Decode)] + struct X { + a: u32, + b: u32, + } + + #[derive(Debug, Eq, PartialEq)] + struct ValidateARange; + + impl Validate for X { + fn validate(&self) -> Result<(), &'static str> { + if self.a > 10 { + Err("Out of range") + } else { + Ok(()) + } + } + } + + #[derive(Debug, Eq, PartialEq)] + struct ValidateBRange; + + impl Validate for X { + fn validate(&self) -> Result<(), &'static str> { + if self.b > 10 { + Err("Out of range") + } else { + Ok(()) + } + } + } + + #[test] + fn test_valid_a() { + let valid = X { a: 10, b: 0xCAFEBABE }; + let bytes = valid.encode(); + assert_eq!( + Ok(Validated { value: valid, _marker: PhantomData }), + Validated::::decode(&mut &bytes[..]) + ); + } + + #[test] + fn test_invalid_a() { + let invalid = X { a: 0xDEADC0DE, b: 0xCAFEBABE }; + let bytes = invalid.encode(); + assert!(Validated::::decode(&mut &bytes[..]).is_err()); + } + + #[test] + fn test_valid_b() { + let valid = X { a: 0xCAFEBABE, b: 10 }; + let bytes = valid.encode(); + assert_eq!( + Ok(Validated { value: valid, _marker: PhantomData }), + Validated::::decode(&mut &bytes[..]) + ); + } + + #[test] + fn test_invalid_b() { + let invalid = X { a: 0xCAFEBABE, b: 0xDEADC0DE }; + let bytes = invalid.encode(); + assert!(Validated::::decode(&mut &bytes[..]).is_err()); + } +} From 3f9b13ff6ee990f5cde112835a321daa07822f1c Mon Sep 17 00:00:00 2001 From: Hussein Ait Lahcen Date: Fri, 14 Jan 2022 17:34:13 +0100 Subject: [PATCH 2/2] add missing Deref/AsRef, use WrapperTypeEncode and introduce `And` --- frame/composable-support/src/validation.rs | 128 +++++++++++++++++---- 1 file changed, 103 insertions(+), 25 deletions(-) diff --git a/frame/composable-support/src/validation.rs b/frame/composable-support/src/validation.rs index a96fdc2cdbd..3da85f37f74 100644 --- a/frame/composable-support/src/validation.rs +++ b/frame/composable-support/src/validation.rs @@ -1,21 +1,62 @@ -use core::marker::PhantomData; +use core::{marker::PhantomData, ops::Deref}; use scale_info::TypeInfo; /// Black box that embbed the validated value. -#[derive(Eq, PartialEq, Debug, TypeInfo, codec::Encode)] +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, TypeInfo)] pub struct Validated { - pub value: T, + value: T, _marker: PhantomData, } -pub trait Validate { - fn validate(&self) -> Result<(), &'static str>; +impl Validated { + #[inline(always)] + pub fn value(&self) -> T { + self.value + } +} + +impl Deref for Validated { + type Target = T; + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl AsRef for Validated { + #[inline(always)] + fn as_ref(&self) -> &T { + &self.value + } +} + +pub trait Validate: Sized { + fn validate(self) -> Result; } -impl, U> codec::Decode for Validated { +#[derive(Debug, Eq, PartialEq)] +pub struct QED; + +impl Validate for T { + #[inline(always)] + fn validate(self) -> Result { + Ok(self) + } +} + +impl + Validate, U, V> Validate<(U, V)> for T { + #[inline(always)] + fn validate(self) -> Result { + let value = Validate::::validate(self)?; + let value = Validate::::validate(value)?; + Ok(value) + } +} + +impl, U, V> codec::Decode for Validated { fn decode(input: &mut I) -> Result { - let value = T::decode(input)?; - Validate::::validate(&value).map_err(|desc| Into::::into(desc))?; + let value = Validate::validate(T::decode(input)?) + .map_err(|desc| Into::::into(desc))?; Ok(Validated { value, _marker: PhantomData }) } fn skip(input: &mut I) -> Result<(), codec::Error> { @@ -23,41 +64,47 @@ impl, U> codec::Decode for Validated { } } -impl, U> codec::EncodeLike for Validated {} +impl, U> codec::WrapperTypeEncode + for Validated +{ +} #[cfg(test)] mod test { use super::*; use codec::{Decode, Encode}; + #[derive(Debug, Eq, PartialEq)] + struct ValidARange; + #[derive(Debug, Eq, PartialEq)] + struct ValidBRange; + + type CheckARange = (ValidARange, QED); + type CheckBRange = (ValidBRange, QED); + type CheckABRange = (ValidARange, (ValidBRange, QED)); + #[derive(Debug, Eq, PartialEq, codec::Encode, codec::Decode)] struct X { a: u32, b: u32, } - #[derive(Debug, Eq, PartialEq)] - struct ValidateARange; - - impl Validate for X { - fn validate(&self) -> Result<(), &'static str> { + impl Validate for X { + fn validate(self) -> Result { if self.a > 10 { Err("Out of range") } else { - Ok(()) + Ok(self) } } } - #[derive(Debug, Eq, PartialEq)] - struct ValidateBRange; - - impl Validate for X { - fn validate(&self) -> Result<(), &'static str> { + impl Validate for X { + fn validate(self) -> Result { if self.b > 10 { Err("Out of range") } else { - Ok(()) + Ok(self) } } } @@ -68,7 +115,7 @@ mod test { let bytes = valid.encode(); assert_eq!( Ok(Validated { value: valid, _marker: PhantomData }), - Validated::::decode(&mut &bytes[..]) + Validated::::decode(&mut &bytes[..]) ); } @@ -76,7 +123,7 @@ mod test { fn test_invalid_a() { let invalid = X { a: 0xDEADC0DE, b: 0xCAFEBABE }; let bytes = invalid.encode(); - assert!(Validated::::decode(&mut &bytes[..]).is_err()); + assert!(Validated::::decode(&mut &bytes[..]).is_err()); } #[test] @@ -85,7 +132,7 @@ mod test { let bytes = valid.encode(); assert_eq!( Ok(Validated { value: valid, _marker: PhantomData }), - Validated::::decode(&mut &bytes[..]) + Validated::::decode(&mut &bytes[..]) ); } @@ -93,6 +140,37 @@ mod test { fn test_invalid_b() { let invalid = X { a: 0xCAFEBABE, b: 0xDEADC0DE }; let bytes = invalid.encode(); - assert!(Validated::::decode(&mut &bytes[..]).is_err()); + assert!(Validated::::decode(&mut &bytes[..]).is_err()); + } + + #[test] + fn test_valid_ab() { + let valid = X { a: 10, b: 10 }; + let bytes = valid.encode(); + assert_eq!( + Ok(Validated { value: valid, _marker: PhantomData }), + Validated::::decode(&mut &bytes[..]) + ); + } + + #[test] + fn test_invalid_ab() { + let invalid = X { a: 0xDEADC0DE, b: 0xCAFEBABE }; + let bytes = invalid.encode(); + assert!(Validated::::decode(&mut &bytes[..]).is_err()); + } + + #[test] + fn test_invalid_a_ab() { + let invalid = X { a: 0xDEADC0DE, b: 10 }; + let bytes = invalid.encode(); + assert!(Validated::::decode(&mut &bytes[..]).is_err()); + } + + #[test] + fn test_invalid_b_ab() { + let invalid = X { a: 10, b: 0xDEADC0DE }; + let bytes = invalid.encode(); + assert!(Validated::::decode(&mut &bytes[..]).is_err()); } }