Skip to content

Commit

Permalink
Inverted Validate trait (#572)
Browse files Browse the repository at this point in the history
* failed to decode wrapped types

* inverted validate

* inverted existing impl of Validate

* Added module doc and examples

* cargo +nightly fmt

* fixed module doc examples

* removed commented out code

* restored validation

* cargo +nightly fmt

* made validate param more generic

* cleaned up validate calls
  • Loading branch information
PoisonPhang authored Feb 3, 2022
1 parent 29663a6 commit 3b9fffb
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 60 deletions.
212 changes: 168 additions & 44 deletions frame/composable-support/src/validation.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@
//! Module for validating extrinsic inputs
//!
//! This module is made of two main parts that are needed to validate an
//! extrinsic input, the `Validated` struct and the `Valitate` trait.
//!
//! # Example
//! ## Single Validation
//! ```
//! use composable_support::validation::{self, Validate, Validated};
//! use scale_info::TypeInfo;
//!
//! pub struct SomeInput;
//!
//! #[derive(Clone, Copy, Debug, PartialEq, TypeInfo)]
//! pub struct ValidateSomeInput;
//!
//! impl Validate<SomeInput, ValidateSomeInput>
//! for ValidateSomeInput {
//! fn validate(input: SomeInput) -> Result<SomeInput, &'static str> {
//! // ... validation code
//! Ok(input)
//! }
//! }
//!
//! pub type CheckSomeCondition = (ValidateSomeInput, validation::Valid);
//!
//! pub fn someExtrinsic(input: Validated<SomeInput, CheckSomeCondition>) {
//! // ... extrinsic code
//! }
//! ```
//!
//! ## Multiple Validations (Up to 3)
//! ```
//! use composable_support::validation::{self, Validate, Validated};
//! use scale_info::TypeInfo;
//!
//! pub struct SomeInput;
//!
//! #[derive(Clone, Copy, Debug, PartialEq, TypeInfo)]
//! pub struct ValidateSomeInput;
//!
//! #[derive(Clone, Copy, Debug, PartialEq, TypeInfo)]
//! pub struct ValidateAnotherCondition;
//!
//! impl Validate<SomeInput, ValidateSomeInput>
//! for ValidateSomeInput {
//! fn validate(input: SomeInput) -> Result<SomeInput, &'static str> {
//! // ... validation code
//! return Ok(input)
//! }
//! }
//!
//! impl Validate<SomeInput, ValidateAnotherCondition>
//! for ValidateAnotherCondition {
//! fn validate(input: SomeInput) -> Result<SomeInput, &'static str> {
//! // ... validation code
//! return Ok(input)
//! }
//! }
//!
//! pub type CheckSomeConditions = (ValidateSomeInput, ValidateAnotherCondition, validation::Valid);
//!
//! pub fn someExtrinsic(input: Validated<SomeInput, CheckSomeConditions>) {
//! // ... extrinsic code
//! }
//! ```

use core::marker::PhantomData;
use scale_info::TypeInfo;
use sp_runtime::DispatchError;
Expand All @@ -11,20 +78,24 @@ pub struct Validated<T, U> {

impl<T, U> Validated<T, U>
where
Validated<T, U>: Validate<U>,
Validated<T, U>: Validate<T, U>,
U: Validate<T, U>,
{
pub fn new(value: T, _validator_tag: U) -> Result<Self, &'static str> {
Validate::<U>::validate(Self { value, _marker: PhantomData })
match <U as Validate<T, U>>::validate(value) {
Ok(value) => Ok(Self { value, _marker: PhantomData }),
Err(e) => Err(e),
}
}
}

pub trait ValidateDispatch<U>: Sized {
fn validate(self) -> Result<Self, DispatchError>;
}

pub trait Validate<U>: Sized {
pub trait Validate<T, U> {
// use string here because in serde layer there is not dispatch
fn validate(self) -> Result<Self, &'static str>;
fn validate(input: T) -> Result<T, &'static str>;
}

#[derive(Debug, Eq, PartialEq, Default)]
Expand All @@ -33,64 +104,77 @@ pub struct Valid;
#[derive(Debug, Eq, PartialEq, Default)]
pub struct Invalid;

impl<T> Validate<Invalid> for T {
impl<T> Validate<T, Invalid> for Invalid {
#[inline(always)]
fn validate(self) -> Result<Self, &'static str> {
fn validate(_input: T) -> Result<T, &'static str> {
Err("not valid")
}
}

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

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

// as per substrate pattern and existing macroses for similar purposes, they tend to make things
// flat like `#[impl_trait_for_tuples::impl_for_tuples(30)]`
// so if we will need more than 3, can consider it
impl<T: Validate<U> + Validate<V> + Validate<W>, U, V, W> Validate<(U, V, W)> for T {
impl<T, U, V, W> Validate<T, (U, V, W)> for (U, V, W)
where
U: Validate<T, U>,
V: Validate<T, V>,
W: Validate<T, W>,
{
#[inline(always)]
fn validate(self) -> Result<Self, &'static str> {
let value = Validate::<U>::validate(self)?;
let value = Validate::<V>::validate(value)?;
let value = Validate::<W>::validate(value)?;
fn validate(input: T) -> Result<T, &'static str> {
let value = U::validate(input)?;
let value = V::validate(value)?;
let value = W::validate(value)?;
Ok(value)
}
}

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

impl<T: Validate<U>, U> Validated<T, U> {
impl<T, U: Validate<T, U>> Validated<T, U> {
pub fn value(self) -> T {
self.value
}
}

impl<T: codec::Decode + Validate<U>, U> codec::Decode for Validated<T, U> {
impl<T: codec::Decode, U: Validate<T, U>> codec::Decode for Validated<T, U> {
fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
let value = Validate::<U>::validate(T::decode(input)?)?;
let value = <U as Validate<T, U>>::validate(T::decode(input)?)?;
Ok(Validated { value, _marker: PhantomData })
}
fn skip<I: codec::Input>(input: &mut I) -> Result<(), codec::Error> {
Expand All @@ -113,11 +197,17 @@ pub(crate) mod private {
}
}

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

impl<T, U: Validate<T, U>> Validate<T, U> for Validated<T, U> {
fn validate(input: T) -> Result<T, &'static str> {
<U as Validate<T, U>>::validate(input)
}
}

#[cfg(test)]
mod test {
use super::*;
Expand All @@ -132,6 +222,10 @@ mod test {
type CheckARangeTag = (ValidARange, Valid);
type CheckBRangeTag = (ValidBRange, Valid);
type CheckABRangeTag = (ValidARange, (ValidBRange, Valid));
type ManyValidatorsTagsNestedInvalid = (ValidARange, (ValidBRange, (Invalid, Valid)));
type ManyValidatorsTagsNestedValid = (ValidARange, (ValidBRange, Valid));
type ManyValidatorsTagsFlatInvalid = (ValidARange, ValidBRange, Invalid, Valid);
type ManyValidatorsTagsFlatValid = (ValidARange, ValidBRange, Valid);
// note: next seems is not supported yet
// type NestedValidated = (Validated<X, Valid>, Validated<Y, Valid>);
// #[derive(Debug, Eq, PartialEq, codec::Encode, codec::Decode, Default)]
Expand All @@ -144,43 +238,72 @@ mod test {
b: u32,
}

impl Validate<ValidARange> for X {
fn validate(self) -> Result<X, &'static str> {
if self.a > 10 {
impl Validate<X, ValidARange> for ValidARange {
fn validate(input: X) -> Result<X, &'static str> {
if input.a > 10 {
Err("Out of range")
} else {
Ok(self)
Ok(input)
}
}
}

impl Validate<ValidBRange> for X {
fn validate(self) -> Result<X, &'static str> {
if self.b > 10 {
impl Validate<X, ValidBRange> for ValidBRange {
fn validate(input: X) -> Result<X, &'static str> {
if input.b > 10 {
Err("Out of range")
} else {
Ok(self)
Ok(input)
}
}
}

#[test]
fn nested_validator() {
let valid = X { a: 10, b: 0xCAFEBABE };
assert!(<ManyValidatorsTagsNestedInvalid as Validate<
X,
ManyValidatorsTagsNestedInvalid,
>>::validate(valid)
.is_err());

type ManyValidatorsTagsNested = (ValidARange, (ValidBRange, (Invalid, Valid)));

assert!(Validate::<ManyValidatorsTagsNested>::validate(valid).is_err());
let valid = X { a: 10, b: 10 };
assert_ok!(
<ManyValidatorsTagsNestedValid as Validate<X, ManyValidatorsTagsNestedValid>>::validate(
valid
)
);
}

#[test]
fn either_nested_or_flat() {
let valid = X { a: 10, b: 0xCAFEBABE };
type ManyValidatorsTagsNested = (ValidARange, (ValidBRange, (Invalid, Valid)));
type ManyValidatorsTagsFlat = (ValidARange, ValidBRange, Invalid, Valid);
assert_eq!(
Validate::<ManyValidatorsTagsNested>::validate(valid.clone()),
Validate::<ManyValidatorsTagsFlat>::validate(valid)
<ManyValidatorsTagsNestedInvalid as Validate<X, ManyValidatorsTagsNestedInvalid>>::validate(
valid.clone()
),
<ManyValidatorsTagsFlatInvalid as Validate<X, ManyValidatorsTagsFlatInvalid>>::validate(valid)
);
}

#[test]
fn flat_validator_multiple_invalid() {
let value = X { a: 10, b: 0xCAFEBABE };

assert!(
<ManyValidatorsTagsFlatInvalid as Validate<X, ManyValidatorsTagsFlatInvalid>>::validate(value).is_err()
);
}

#[test]
fn flat_validator_multiple_valid() {
let value = X { a: 10, b: 0xCAFEBABE };

assert!(
<ManyValidatorsTagsFlatValid as Validate<X, ManyValidatorsTagsFlatValid>>::validate(
value
)
.is_err()
);
}

Expand All @@ -196,6 +319,7 @@ mod 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, CheckARangeTag>::decode(&mut &bytes[..])
Expand Down
6 changes: 3 additions & 3 deletions frame/composable-traits/src/lending/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ impl InterestRateModel {
}

pub struct InteresteRateModelIsValid;
impl Validate<InteresteRateModelIsValid> for InterestRateModel {
fn validate(self) -> Result<Self, &'static str> {
impl Validate<InterestRateModel, InteresteRateModelIsValid> for InteresteRateModelIsValid {
fn validate(interest_rate_model: InterestRateModel) -> Result<InterestRateModel, &'static str> {
const ERROR: &str = "interest rate model is not valid";
match self {
match interest_rate_model {
InterestRateModel::Jump(x) =>
JumpModel::new(x.base_rate, x.jump_rate, x.full_rate, x.target_utilization)
.ok_or(ERROR)
Expand Down
36 changes: 23 additions & 13 deletions frame/composable-traits/src/lending/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,39 @@ pub struct MarketModelValid;
#[derive(Clone, Copy, Debug, PartialEq, TypeInfo)]
pub struct CurrencyPairIsNotSame;

impl<LiquidationStrategyId, Asset: Eq> Validate<MarketModelValid>
for CreateInput<LiquidationStrategyId, Asset>
impl<LiquidationStrategyId, Asset: Eq>
Validate<CreateInput<LiquidationStrategyId, Asset>, MarketModelValid> for MarketModelValid
{
fn validate(self) -> Result<Self, &'static str> {
if self.updatable.collateral_factor < MoreThanOneFixedU128::one() {
return Err("collateral factor must be >=1")
fn validate(
create_input: CreateInput<LiquidationStrategyId, Asset>,
) -> Result<CreateInput<LiquidationStrategyId, Asset>, &'static str> {
if create_input.updatable.collateral_factor < MoreThanOneFixedU128::one() {
return Err("collateral factor must be >= 1")
}

let interest_rate_model =
Validate::<InteresteRateModelIsValid>::validate(self.updatable.interest_rate_model)?;
let interest_rate_model = <InteresteRateModelIsValid as Validate<
InterestRateModel,
InteresteRateModelIsValid,
>>::validate(create_input.updatable.interest_rate_model)?;

Ok(Self { updatable: UpdateInput { interest_rate_model, ..self.updatable }, ..self })
Ok(CreateInput {
updatable: UpdateInput { interest_rate_model, ..create_input.updatable },
..create_input
})
}
}

impl<LiquidationStrategyId, Asset: Eq> Validate<CurrencyPairIsNotSame>
for CreateInput<LiquidationStrategyId, Asset>
impl<LiquidationStrategyId, Asset: Eq>
Validate<CreateInput<LiquidationStrategyId, Asset>, CurrencyPairIsNotSame>
for CurrencyPairIsNotSame
{
fn validate(self) -> Result<Self, &'static str> {
if self.currency_pair.base == self.currency_pair.quote {
fn validate(
create_input: CreateInput<LiquidationStrategyId, Asset>,
) -> Result<CreateInput<LiquidationStrategyId, Asset>, &'static str> {
if create_input.currency_pair.base == create_input.currency_pair.quote {
Err("currency pair must be different assets")
} else {
Ok(self)
Ok(create_input)
}
}
}
Expand Down

0 comments on commit 3b9fffb

Please sign in to comment.