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

test: index #287

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e8a3045
refactor: encoding scheme
Daanvdplas Sep 10, 2024
b16430b
tests: pallet api extension
Daanvdplas Sep 10, 2024
2179645
docs: tests
Daanvdplas Sep 10, 2024
dad4beb
refactor: remove unnecessary
Daanvdplas Sep 10, 2024
1c6f03a
test: primitives
Daanvdplas Sep 10, 2024
a51d619
style: naming variable
Daanvdplas Sep 10, 2024
9ff9915
test: devnet runtime versioning
Daanvdplas Sep 10, 2024
a2b9e76
test: runtime devnet mod (small tests)
Daanvdplas Sep 10, 2024
dc0f90a
refactor: fallible conversion
evilrobot-01 Sep 10, 2024
6f8205e
refactor: fallible conversion (#275)
evilrobot-01 Sep 11, 2024
1547dca
fix: tests after fallible conversion feat
Daanvdplas Sep 11, 2024
2b07130
rebase
Daanvdplas Sep 11, 2024
36e8735
fix: test after fallible conversion feat
Daanvdplas Sep 11, 2024
438de63
Merge branch 'daan/tests-pallet_api_extension' into daan/test-devnet_…
Daanvdplas Sep 11, 2024
a1ee377
test: config contracts
Daanvdplas Sep 11, 2024
8949e13
test: ensure signed test coverage
Daanvdplas Sep 11, 2024
d2eeb08
test: coverage fungibles pallet
Daanvdplas Sep 11, 2024
538b19e
fix: comment
Daanvdplas Sep 11, 2024
ca1ccca
test: error case test coverage
Daanvdplas Sep 12, 2024
1060a91
test: no state set test
Daanvdplas Sep 12, 2024
98e89dd
refactor: test variables
Daanvdplas Sep 12, 2024
0ad8b36
test: encoding read result
Daanvdplas Sep 12, 2024
682e8f3
refactor: pallet fungibles mod.rs
Daanvdplas Sep 13, 2024
db382a8
refactor: split tests and test badorigin first
Daanvdplas Sep 13, 2024
497b2f7
refactor: assets helper functions
Daanvdplas Sep 13, 2024
95281b7
test: ensure read variant index
Daanvdplas Sep 13, 2024
ee3e859
test: dispatchable index
Daanvdplas Sep 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions extension/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@

/// A function for reading runtime state.
pub struct ReadState<M, C, R, D, F, RC = DefaultConverter<<R as Readable>::Result>, E = (), L = ()>(
PhantomData<(M, C, R, D, F, RC, E, L)>,

Check warning on line 83 in extension/src/functions.rs

View workflow job for this annotation

GitHub Actions / clippy

very complex type used. Consider factoring parts into `type` definitions

warning: very complex type used. Consider factoring parts into `type` definitions --> extension/src/functions.rs:83:2 | 83 | PhantomData<(M, C, R, D, F, RC, E, L)>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `#[warn(clippy::type_complexity)]` on by default
);
impl<
Matcher: Matches,
Expand All @@ -88,7 +88,7 @@
Read: Readable + Debug,
Decoder: Decode<Output: codec::Decode + Into<Read>>,
Filter: Contains<Read>,
ResultConverter: Converter<Source = Read::Result, Target: Into<Vec<u8>>>,
ResultConverter: Converter<Source = Read::Result, Target: Into<Vec<u8>>, Error = DispatchError>,
Error: ErrorConverter,
Logger: LogTarget,
> Function for ReadState<Matcher, Config, Read, Decoder, Filter, ResultConverter, Error, Logger>
Expand Down Expand Up @@ -116,7 +116,7 @@
log::debug!(target: Logger::LOG_TARGET, "read: result={result:?}");
// Perform any final conversion. Any implementation is expected to charge weight as
// appropriate.
let result = ResultConverter::convert(result, env).into();
let result = ResultConverter::try_convert(result, env)?.into();
log::debug!(target: Logger::LOG_TARGET, "converted: result={result:?}");
// Charge appropriate weight for writing to contract, based on result length.
let weight = ContractWeightsOf::<Config>::seal_input(result.len() as u32);
Expand Down Expand Up @@ -146,12 +146,16 @@
fn read(self) -> Self::Result;
}

/// Trait for converting a value based on additional information available from the environment.
/// Trait for fallible conversion of a value based on additional information available from the
/// environment.
pub trait Converter {
/// The type returned in the event of a conversion error.
type Error;
/// The type of value to be converted.
type Source;
/// The target type.
type Target;

/// The log target.
const LOG_TARGET: &'static str;

Expand All @@ -160,19 +164,29 @@
/// # Parameters
/// - `value` - The value to be converted.
/// - `env` - The current execution environment.
fn convert(value: Self::Source, env: &impl Environment) -> Self::Target;
fn try_convert(
value: Self::Source,
env: &impl Environment,
) -> core::result::Result<Self::Target, Self::Error>;
}

/// A default converter, for converting (encoding) from some type into a byte array.
pub struct DefaultConverter<T>(PhantomData<T>);
impl<T: Into<Vec<u8>>> Converter for DefaultConverter<T> {
/// The type returned in the event of a conversion error.
type Error = DispatchError;
/// The type of value to be converted.
type Source = T;
/// The target type.
type Target = Vec<u8>;

const LOG_TARGET: &'static str = "";

fn convert(value: Self::Source, _env: &impl Environment) -> Self::Target {
value.into()
fn try_convert(
value: Self::Source,
_env: &impl Environment,
) -> core::result::Result<Self::Target, Self::Error> {
Ok(value.into())
}
}

Expand Down Expand Up @@ -462,7 +476,7 @@
Ok(Converging(0))
));
// Check if the contract environment buffer is written correctly.
assert_eq!(env.buffer, UppercaseConverter::convert(expected, &env));
assert_eq!(env.buffer, UppercaseConverter::try_convert(expected, &env).unwrap());
}

#[test]
Expand Down Expand Up @@ -538,6 +552,6 @@
fn default_conversion_works() {
let env = MockEnvironment::default();
let source = "pop".to_string();
assert_eq!(DefaultConverter::convert(source.clone(), &env), source.as_bytes());
assert_eq!(DefaultConverter::try_convert(source.clone(), &env).unwrap(), source.as_bytes());
}
}
10 changes: 8 additions & 2 deletions extension/src/matching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,19 @@ mod tests {
}

#[test]
fn func_id_matches() {
fn with_func_id_matches() {
let env = MockEnvironment::default();
assert!(WithFuncId::<ConstU32<0>>::matches(&env));

let env = MockEnvironment::new(1, vec![]);
assert!(WithFuncId::<ConstU32<1>>::matches(&env));

let env = MockEnvironment::new(100, vec![]);
assert!(WithFuncId::<ConstU32<100>>::matches(&env));
}

#[test]
fn func_id_does_not_match() {
fn with_func_id_does_not_match() {
let env = MockEnvironment::new(1, vec![]);
assert!(!WithFuncId::<ConstU32<0>>::matches(&env));
}
Expand Down
10 changes: 7 additions & 3 deletions extension/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use frame_support::{
};
use frame_system::{pallet_prelude::BlockNumberFor, EnsureSigned};
use pallet_contracts::{chain_extension::RetVal, DefaultAddressGenerator, Frame, Schedule};
use sp_runtime::{BuildStorage, Perbill};
use sp_runtime::{BuildStorage, DispatchError, Perbill};

use crate::{
decoding::Identity, environment, matching::WithFuncId, AccountIdOf, ContractWeightsOf,
Expand Down Expand Up @@ -373,14 +373,18 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
/// A converter for converting string results to uppercase.
pub(crate) struct UppercaseConverter;
impl Converter for UppercaseConverter {
type Error = DispatchError;
type Source = RuntimeResult;
type Target = Vec<u8>;

const LOG_TARGET: &'static str = "";

fn convert(value: Self::Source, _env: &impl crate::Environment) -> Self::Target {
fn try_convert(
value: Self::Source,
_env: &impl crate::Environment,
) -> Result<Self::Target, Self::Error> {
match value {
RuntimeResult::Pong(value) => value.to_uppercase().encode(),
RuntimeResult::Pong(value) => Ok(value.to_uppercase().encode()),
}
}
}
190 changes: 172 additions & 18 deletions pallets/api/src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use core::{fmt::Debug, marker::PhantomData};

use frame_support::traits::Get;
pub use pop_chain_extension::{
Config, ContractWeightsOf, DecodingFailed, DispatchCall, ReadState, Readable,
Config, ContractWeightsOf, DecodingFailed, DispatchCall, ErrorConverter, ReadState, Readable,
};
use pop_chain_extension::{
Converter, Decodes, Environment, LogTarget, Matches, Processor, Result, RetVal,
Expand Down Expand Up @@ -70,7 +70,7 @@ impl LogTarget for ReadStateLogTarget {

/// Conversion of a `DispatchError` to a versioned error.
pub struct VersionedErrorConverter<E>(PhantomData<E>);
impl<Error: From<(DispatchError, u8)> + Into<u32> + Debug> pop_chain_extension::ErrorConverter
impl<Error: TryFrom<(DispatchError, u8), Error = DispatchError> + Into<u32> + Debug> ErrorConverter
for VersionedErrorConverter<Error>
{
/// The log target.
Expand All @@ -85,17 +85,19 @@ impl<Error: From<(DispatchError, u8)> + Into<u32> + Debug> pop_chain_extension::
// Defer to supplied versioned error conversion type
let version = version(env);
log::debug!(target: Self::LOG_TARGET, "versioned error converter: error={error:?}, version={version}");
let error: Error = (error, version).into();
let error: Error = (error, version).try_into()?;
log::debug!(target: Self::LOG_TARGET, "versioned error converter: converted error={error:?}");
Ok(RetVal::Converging(error.into()))
}
}

/// Conversion of a read result to a versioned read result.
pub struct VersionedResultConverter<S, T>(PhantomData<(S, T)>);
impl<Source: Debug, Target: From<(Source, u8)> + Debug> Converter
impl<Source: Debug, Target: TryFrom<(Source, u8), Error = DispatchError> + Debug> Converter
for VersionedResultConverter<Source, Target>
{
/// The type returned in the event of a conversion error.
type Error = DispatchError;
/// The type of value to be converted.
type Source = Source;
/// The target type.
Expand All @@ -109,47 +111,199 @@ impl<Source: Debug, Target: From<(Source, u8)> + Debug> Converter
/// # Parameters
/// - `value` - The value to be converted.
/// - `env` - The current execution environment.
fn convert(value: Self::Source, env: &impl Environment) -> Self::Target {
fn try_convert(value: Self::Source, env: &impl Environment) -> Result<Self::Target> {
// Defer to supplied versioned result conversion type
let version = version(env);
log::debug!(target: Self::LOG_TARGET, "versioned result converter: result={value:?}, version={version}");
let converted: Target = (value, version).into();
let converted: Target = (value, version).try_into()?;
log::debug!(target: Self::LOG_TARGET, "versioned result converter: converted result={converted:?}");
converted
Ok(converted)
}
}

fn func_id(env: &impl Environment) -> u8 {
// TODO: update once the encoding scheme order has been finalised: expected to be
// env.ext_id().to_le_bytes()[0]
env.func_id().to_le_bytes()[1]
env.func_id().to_le_bytes()[0]
}

fn module_and_index(env: &impl Environment) -> (u8, u8) {
// TODO: update once the encoding scheme order has been finalised: expected to be
// env.func_id().to_le_bytes()[0..1]
let bytes = env.ext_id().to_le_bytes();
(bytes[0], bytes[1])
}

fn version(env: &impl Environment) -> u8 {
// TODO: update once the encoding scheme order has been finalised: expected to be
// env.ext_id().to_le_bytes()[1]
env.func_id().to_le_bytes()[0]
env.func_id().to_le_bytes()[1]
}

#[cfg(test)]
mod tests {
use frame_support::pallet_prelude::Weight;
use pop_chain_extension::Ext;
use sp_core::ConstU8;

use super::*;
use super::{DispatchError::*, *};
use crate::extension::Prepender;

#[test]
fn func_id_works() {
let env = MockEnvironment { func_id: u16::from_le_bytes([1, 2]), ext_id: 0u16 };
assert_eq!(func_id(&env), 1);
}

#[test]
fn module_and_index_works() {
let env = MockEnvironment { func_id: 0u16, ext_id: u16::from_le_bytes([2, 3]) };
assert_eq!(module_and_index(&env), (2, 3));
}

#[test]
fn version_works() {
let env = MockEnvironment { func_id: u16::from_le_bytes([1, 2]), ext_id: 0u16 };
assert_eq!(version(&env), 2);
}

#[test]
fn prepender_works() {
let env = MockEnvironment { func_id: 1, ext_id: u16::from_le_bytes([2, 3]) };
assert_eq!(Prepender::process(vec![0u8], &env), vec![1, 2, 3, 0]);
let env = MockEnvironment {
func_id: u16::from_le_bytes([1, 2]),
ext_id: u16::from_le_bytes([3, 4]),
};
assert_eq!(Prepender::process(vec![0u8], &env), vec![2, 3, 4, 0]);
assert_eq!(Prepender::process(vec![0u8, 5, 10], &env), vec![2, 3, 4, 0, 5, 10]);
}

#[test]
fn identified_by_first_byte_of_function_id_matches() {
let env = MockEnvironment { func_id: u16::from_le_bytes([1, 2]), ext_id: 0u16 };
assert!(IdentifiedByFirstByteOfFunctionId::<ConstU8<1>>::matches(&env));
}

#[test]
fn identified_by_first_byte_of_function_id_does_not_match() {
let env = MockEnvironment { func_id: u16::from_le_bytes([1, 2]), ext_id: 0u16 };
assert!(!IdentifiedByFirstByteOfFunctionId::<ConstU8<2>>::matches(&env));
}

#[test]
fn dispatch_call_log_target_works() {
assert!(matches!(
<DispatchCallLogTarget as LogTarget>::LOG_TARGET,
"pop-api::extension::dispatch"
));
}

#[test]
fn read_state_log_target_works() {
assert!(matches!(
<ReadStateLogTarget as LogTarget>::LOG_TARGET,
"pop-api::extension::read-state"
));
}

#[test]
fn versioned_error_converter_works() {
use super::RetVal::Converging;

// Mock versioned error.
#[derive(Debug)]
pub enum VersionedError {
V0(DispatchError),
V1(DispatchError),
}

impl TryFrom<(DispatchError, u8)> for VersionedError {
type Error = DispatchError;

fn try_from(value: (DispatchError, u8)) -> Result<Self> {
let (error, version) = value;
match version {
0 => Ok(VersionedError::V0(error)),
1 => Ok(VersionedError::V1(error)),
_ => Err(Other("DecodingFailed")),
}
}
}

impl From<VersionedError> for u32 {
// Mock conversion based on error and version.
fn from(value: VersionedError) -> Self {
match value {
VersionedError::V0(error) => match error {
BadOrigin => 1,
_ => 100,
},
VersionedError::V1(error) => match error {
BadOrigin => 2,
_ => 200,
},
}
}
}

// Because `Retval` does not implement the `Debug` trait the success and error test cases
// are separated.
for (version, error, expected_result) in vec![
(0, BadOrigin, 1),
(0, CannotLookup, 100),
(1, BadOrigin, 2),
(1, CannotLookup, 200),
] {
let env = MockEnvironment { func_id: u16::from_le_bytes([0, version]), ext_id: 0u16 };
// Again, due to missing `Debug` trait the result has to be unwrapped.
let Ok(Converging(result)) =
VersionedErrorConverter::<VersionedError>::convert(error, &env)
else {
panic!("should not happen")
};
assert_eq!(result, expected_result);
}

// Error case.
let env = MockEnvironment { func_id: u16::from_le_bytes([0, 2]), ext_id: 0u16 };
let result = VersionedErrorConverter::<VersionedError>::convert(BadOrigin, &env)
.err()
.unwrap(); // Again, can't use `unwrap_err()` due to missing `Debug` trait.
assert_eq!(result, Other("DecodingFailed"));
}

#[test]
fn versioned_result_converter_works() {
use VersionedRuntimeResult::{V0, V1};

// Mock versioned runtime result.
#[derive(Debug, PartialEq)]
pub enum VersionedRuntimeResult {
V0(u8),
V1(u8),
}

impl TryFrom<(u8, u8)> for VersionedRuntimeResult {
type Error = DispatchError;

// Mock conversion based on result and version.
fn try_from(value: (u8, u8)) -> Result<Self> {
let (result, version) = value;
match version {
0 if result <= 50 => Ok(V0(result)),
0 if result > 50 => Ok(V0(50)),
1 if result <= 100 => Ok(V1(result)),
1 if result > 100 => Ok(V1(100)),
_ => Err(Other("DecodingFailed")),
}
}
}

for (version, value, expected_result) in vec![
(0, 10, Ok(V0(10))),
(0, 100, Ok(V0(50))),
(1, 10, Ok(V1(10))),
(1, 100, Ok(V1(100))),
(2, 10, Err(Other("DecodingFailed"))),
] {
let env = MockEnvironment { func_id: u16::from_le_bytes([0, version]), ext_id: 0u16 };
let result =
VersionedResultConverter::<u8, VersionedRuntimeResult>::try_convert(value, &env);
assert_eq!(result, expected_result);
}
}

struct MockEnvironment {
Expand Down
Loading
Loading