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: improve test cases #211

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 13 additions & 29 deletions extension/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,12 @@ use sp_std::vec::Vec;
mod tests;
mod v0;

/// Logging target for categorizing messages from the Pop API extension module.
// Logging target for categorizing messages from the Pop API extension module.
const LOG_TARGET: &str = "pop-api::extension";

const DECODING_FAILED_ERROR: DispatchError = DispatchError::Other("DecodingFailed");
// TODO: issue #93, we can also encode the `pop_primitives::Error::UnknownCall` which means we do use
// `Error` in the runtime and it should stay in primitives. Perhaps issue #91 will also influence
// here. Should be looked at together.
const DECODING_FAILED_ERROR_ENCODED: [u8; 4] = [255u8, 0, 0, 0];
const UNKNOWN_CALL_ERROR: DispatchError = DispatchError::Other("UnknownCall");
// TODO: see above.
const UNKNOWN_CALL_ERROR_ENCODED: [u8; 4] = [254u8, 0, 0, 0];

type ContractSchedule<T> = <T as pallet_contracts::Config>::Schedule;
Expand Down Expand Up @@ -248,7 +244,7 @@ enum VersionedDispatch<RuntimeCall: Decode> {
/// The `FuncId` specifies the available functions that can be called through the Pop API. Each
/// variant corresponds to a specific functionality provided by the API, facilitating the
/// interaction between smart contracts and the runtime.
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq)]
pub enum FuncId {
/// Represents a function call to dispatch a runtime call.
Dispatch,
Expand Down Expand Up @@ -287,8 +283,17 @@ impl TryFrom<u8> for FuncId {
///
/// - `error`: The `DispatchError` encountered during contract execution.
/// - `version`: The version of the chain extension, used to determine the known errors.
pub(crate) fn convert_to_status_code(error: DispatchError, version: u8) -> u32 {
let mut encoded_error: [u8; 4] = match error {
fn convert_to_status_code(error: DispatchError, version: u8) -> u32 {
let mut encoded_error: [u8; 4] = encode_error(error);
match version {
0 => v0::handle_unknown_error(&mut encoded_error),
_ => encoded_error = UNKNOWN_CALL_ERROR_ENCODED,
}
u32::from_le_bytes(encoded_error)
}

fn encode_error(error: DispatchError) -> [u8; 4] {
match error {
// "UnknownCall" and "DecodingFailed" are mapped to specific errors in the API and will
// never change.
UNKNOWN_CALL_ERROR => UNKNOWN_CALL_ERROR_ENCODED,
Expand All @@ -299,26 +304,5 @@ pub(crate) fn convert_to_status_code(error: DispatchError, version: u8) -> u32 {
encoded_error.resize(4, 0);
encoded_error.try_into().expect("qed, resized to 4 bytes line above")
},
};
match version {
// If an unknown variant of the `DispatchError` is detected the error needs to be converted
// into the encoded value of `Error::Other`. This conversion is performed by shifting the bytes one
// position forward (discarding the last byte as it is not used) and setting the first byte to the
// encoded value of `Other` (0u8). This ensures the error is correctly categorized as an `Other`
// variant which provides all the necessary information to debug which error occurred in the runtime.
//
// Byte layout explanation:
// - Byte 0: index of the variant within `Error`
// - Byte 1:
// - Must be zero for `UNIT_ERRORS`.
// - Represents the nested error in `SINGLE_NESTED_ERRORS`.
// - Represents the first level of nesting in `DOUBLE_NESTED_ERRORS`.
// - Byte 2:
// - Represents the second level of nesting in `DOUBLE_NESTED_ERRORS`.
// - Byte 3:
// - Unused or represents further nested information.
0 => v0::handle_unknown_error(&mut encoded_error),
_ => encoded_error = UNKNOWN_CALL_ERROR_ENCODED,
}
u32::from_le_bytes(encoded_error)
}
137 changes: 123 additions & 14 deletions extension/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
use super::{
encode_error, DispatchError::*, FuncId, DECODING_FAILED_ERROR, DECODING_FAILED_ERROR_ENCODED,
UNKNOWN_CALL_ERROR, UNKNOWN_CALL_ERROR_ENCODED,
};
use codec::{Decode, Encode};
use sp_runtime::{
ArithmeticError::*, DispatchError, ModuleError, TokenError::*, TransactionalError::*,
};

// Test ensuring `func_id()` and `ext_id()` work as expected, i.e. extracting the first two
// bytes and the last two bytes, respectively, from a 4 byte array.
// TODO: #110
// Test ensuring `func_id()` and `ext_id()` work as expected. I.e. extracting the first two
// bytes and the last two bytes from a 4 byte array, respectively.
#[test]
fn test_byte_extraction() {
use rand::Rng;
Expand Down Expand Up @@ -42,6 +50,112 @@ fn test_byte_extraction() {
}
}

// Assert encoded `DispatchError` with expected encoding.
pub(crate) fn assert_encoding_matches(dispatch_error: DispatchError, expected_encoding: [u8; 4]) {
let encoding = encode_error(dispatch_error);
assert_eq!(encoding, expected_encoding);
}

// Assert all unit error possibilities with expected encoding.
#[test]
fn encode_error_unit_variants_works() {
let test_cases = vec![
(Other(""), [0, 0, 0, 0]),
(CannotLookup, [1, 0, 0, 0]),
(BadOrigin, [2, 0, 0, 0]),
(ConsumerRemaining, [4, 0, 0, 0]),
(NoProviders, [5, 0, 0, 0]),
(TooManyConsumers, [6, 0, 0, 0]),
(Exhausted, [10, 0, 0, 0]),
(Corruption, [11, 0, 0, 0]),
(Unavailable, [12, 0, 0, 0]),
(RootNotAllowed, [13, 0, 0, 0]),
(UNKNOWN_CALL_ERROR, UNKNOWN_CALL_ERROR_ENCODED),
(DECODING_FAILED_ERROR, DECODING_FAILED_ERROR_ENCODED),
];
for (dispatch_error, expected_encoding) in test_cases {
assert_encoding_matches(dispatch_error, expected_encoding);
}
}

// Assert all single nested error possibilities with expected encoding.
#[test]
fn encode_error_single_nested_variants_works() {
// TokenError.
let test_cases = vec![
(FundsUnavailable, 0),
(OnlyProvider, 1),
(BelowMinimum, 2),
(CannotCreate, 3),
(UnknownAsset, 4),
(Frozen, 5),
(Unsupported, 6),
(CannotCreateHold, 7),
(NotExpendable, 8),
(Blocked, 9),
];
for (error, index) in test_cases {
assert_encoding_matches(Token(error), [7, index, 0, 0]);
}

// ArithmeticError.
let test_cases = vec![(Underflow, 0), (Overflow, 1), (DivisionByZero, 2)];
for (error, index) in test_cases {
assert_encoding_matches(Arithmetic(error), [8, index, 0, 0]);
}

// TransactionalError.
let test_cases = vec![(LimitReached, 0), (NoLayer, 1)];
for (error, index) in test_cases {
assert_encoding_matches(Transactional(error), [9, index, 0, 0]);
}
}

// Assert all module error possibilities with expected encoding.
#[test]
fn encode_error_module_error_works() {
let test_cases = vec![
(
Module(ModuleError { index: 1, error: [2, 0, 0, 0], message: Some("hallo") }),
[3, 1, 2, 0],
),
(
Module(ModuleError { index: 1, error: [2, 3, 0, 0], message: Some("hallo") }),
[3, 1, 2, 3],
),
(
Module(ModuleError { index: 1, error: [2, 3, 4, 0], message: Some("hallo") }),
[3, 1, 2, 3],
),
(
Module(ModuleError { index: 1, error: [2, 3, 4, 5], message: Some("hallo") }),
[3, 1, 2, 3],
),
(Module(ModuleError { index: 1, error: [2, 3, 4, 5], message: None }), [3, 1, 2, 3]),
];
for (dispatch_error, expected_encoding) in test_cases {
let encoding = encode_error(dispatch_error);
assert_eq!(encoding, expected_encoding);
}
}

#[test]
fn func_id_try_from_works() {
let test_cases = [
(0u8, Ok(FuncId::Dispatch)),
(1, Ok(FuncId::ReadState)),
(2, Err(UNKNOWN_CALL_ERROR)),
(3, Err(UNKNOWN_CALL_ERROR)),
(100, Err(UNKNOWN_CALL_ERROR)),
(u8::MAX, Err(UNKNOWN_CALL_ERROR)),
];

for (input_value, expected_result) in test_cases {
let actual_result: Result<FuncId, DispatchError> = input_value.try_into();
assert_eq!(actual_result, expected_result, "Failed on input: {}", input_value);
}
}

// Test showing all the different type of variants and its encoding.
#[test]
fn encoding_of_enum() {
Expand Down Expand Up @@ -103,31 +217,26 @@ fn encoding_of_enum() {

#[test]
fn encoding_decoding_dispatch_error() {
use sp_runtime::{ArithmeticError, DispatchError, ModuleError, TokenError};

let error = DispatchError::Module(ModuleError {
index: 255,
error: [2, 0, 0, 0],
message: Some("error message"),
});
let error =
Module(ModuleError { index: 255, error: [2, 0, 0, 0], message: Some("error message") });
let encoded = error.encode();
let decoded = DispatchError::decode(&mut &encoded[..]).unwrap();
assert_eq!(encoded, vec![3, 255, 2, 0, 0, 0]);
assert_eq!(
decoded,
// `message` is skipped for encoding.
DispatchError::Module(ModuleError { index: 255, error: [2, 0, 0, 0], message: None })
Module(ModuleError { index: 255, error: [2, 0, 0, 0], message: None })
);

// Example DispatchError::Token
let error = DispatchError::Token(TokenError::UnknownAsset);
// Example Token
let error = Token(UnknownAsset);
let encoded = error.encode();
let decoded = DispatchError::decode(&mut &encoded[..]).unwrap();
assert_eq!(encoded, vec![7, 4]);
assert_eq!(decoded, error);

// Example DispatchError::Arithmetic
let error = DispatchError::Arithmetic(ArithmeticError::Overflow);
// Example Arithmetic
let error = Arithmetic(Overflow);
let encoded = error.encode();
let decoded = DispatchError::decode(&mut &encoded[..]).unwrap();
assert_eq!(encoded, vec![8, 1]);
Expand Down
Loading
Loading