Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inverted Validate trait #572

Merged
merged 11 commits into from
Feb 3, 2022
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 {
KaiserKarel marked this conversation as resolved.
Show resolved Hide resolved
#[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