Skip to content

Commit

Permalink
CU-1ubvcrq - introduce Validated and Validate for custom codec va…
Browse files Browse the repository at this point in the history
…lidation (#492)

* introduce `Validated` and `Validate` for custom codec validation

* add missing Deref/AsRef, use WrapperTypeEncode and introduce `And`
  • Loading branch information
hussein-aitlahcen authored Jan 17, 2022
1 parent e2ac78f commit 885a8ca
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions frame/composable-support/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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",
]
15 changes: 15 additions & 0 deletions frame/composable-support/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
176 changes: 176 additions & 0 deletions frame/composable-support/src/validation.rs
Original file line number Diff line number Diff line change
@@ -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<T, U> {
value: T,
_marker: PhantomData<U>,
}

impl<T: Copy, U> Validated<T, U> {
#[inline(always)]
pub fn value(&self) -> T {
self.value
}
}

impl<T, U> Deref for Validated<T, U> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.value
}
}

impl<T, U> AsRef<T> for Validated<T, U> {
#[inline(always)]
fn as_ref(&self) -> &T {
&self.value
}
}

pub trait Validate<U>: Sized {
fn validate(self) -> Result<Self, &'static str>;
}

#[derive(Debug, Eq, PartialEq)]
pub struct QED;

impl<T> Validate<QED> for T {
#[inline(always)]
fn validate(self) -> Result<Self, &'static str> {
Ok(self)
}
}

impl<T: Validate<U> + Validate<V>, U, V> Validate<(U, V)> for T {
#[inline(always)]
fn validate(self) -> Result<Self, &'static str> {
let value = Validate::<U>::validate(self)?;
let value = Validate::<V>::validate(value)?;
Ok(value)
}
}

impl<T: codec::Decode + Validate<(U, V)>, U, V> codec::Decode for Validated<T, (U, V)> {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let value = Validate::validate(T::decode(input)?)
.map_err(|desc| Into::<codec::Error>::into(desc))?;
Ok(Validated { value, _marker: PhantomData })
}
fn skip<I: codec::Input>(input: &mut I) -> Result<(), codec::Error> {
T::skip(input)
}
}

impl<T: codec::Encode + codec::Decode + Validate<U>, U> codec::WrapperTypeEncode
for Validated<T, U>
{
}

#[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<ValidARange> for X {
fn validate(self) -> Result<X, &'static str> {
if self.a > 10 {
Err("Out of range")
} else {
Ok(self)
}
}
}

impl Validate<ValidBRange> for X {
fn validate(self) -> Result<X, &'static str> {
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::<X, CheckARange>::decode(&mut &bytes[..])
);
}

#[test]
fn test_invalid_a() {
let invalid = X { a: 0xDEADC0DE, b: 0xCAFEBABE };
let bytes = invalid.encode();
assert!(Validated::<X, CheckARange>::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::<X, CheckBRange>::decode(&mut &bytes[..])
);
}

#[test]
fn test_invalid_b() {
let invalid = X { a: 0xCAFEBABE, b: 0xDEADC0DE };
let bytes = invalid.encode();
assert!(Validated::<X, CheckBRange>::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::<X, CheckABRange>::decode(&mut &bytes[..])
);
}

#[test]
fn test_invalid_ab() {
let invalid = X { a: 0xDEADC0DE, b: 0xCAFEBABE };
let bytes = invalid.encode();
assert!(Validated::<X, CheckABRange>::decode(&mut &bytes[..]).is_err());
}

#[test]
fn test_invalid_a_ab() {
let invalid = X { a: 0xDEADC0DE, b: 10 };
let bytes = invalid.encode();
assert!(Validated::<X, CheckABRange>::decode(&mut &bytes[..]).is_err());
}

#[test]
fn test_invalid_b_ab() {
let invalid = X { a: 10, b: 0xDEADC0DE };
let bytes = invalid.encode();
assert!(Validated::<X, CheckABRange>::decode(&mut &bytes[..]).is_err());
}
}

0 comments on commit 885a8ca

Please sign in to comment.