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..3da85f37f74 --- /dev/null +++ b/frame/composable-support/src/validation.rs @@ -0,0 +1,176 @@ +use core::{marker::PhantomData, ops::Deref}; +use scale_info::TypeInfo; + +/// Black box that embbed the validated value. +#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, TypeInfo)] +pub struct Validated { + value: T, + _marker: PhantomData, +} + +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; +} + +#[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 = Validate::validate(T::decode(input)?) + .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::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, + } + + impl Validate for X { + fn validate(self) -> Result { + if self.a > 10 { + Err("Out of range") + } else { + Ok(self) + } + } + } + + impl Validate for X { + fn validate(self) -> Result { + if self.b > 10 { + Err("Out of range") + } else { + Ok(self) + } + } + } + + #[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()); + } + + #[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()); + } +}