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

feat: runtime error #14

Merged
merged 27 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ddb5806
feat: add runtime error
chungquantin Oct 3, 2024
6f609f2
feat: into psp22 error
chungquantin Oct 3, 2024
4b9c94a
feat: u32 conversion
chungquantin Oct 4, 2024
a6c4b5c
feat: better runtime error
chungquantin Oct 4, 2024
6cf56a6
refactor: pop_primitive dispatch error conversion
chungquantin Oct 4, 2024
22732d3
Merge pull request #16 from r0gue-io/chungquantin/refactor-pop_primit…
chungquantin Oct 4, 2024
63ed93d
refactor: error module conversion
chungquantin Oct 7, 2024
0936849
Merge branch 'daan/feat-assets' into chungquantin/feat-runtime_error
chungquantin Oct 7, 2024
201a9b9
feat: add versioning
chungquantin Oct 8, 2024
5851852
feat: generic runtime error
chungquantin Oct 8, 2024
6cd8dbb
fix: comments
chungquantin Oct 8, 2024
b17157f
fix: resolve comments
chungquantin Oct 9, 2024
9450444
refactor: unwrap_or_else
chungquantin Oct 9, 2024
c1a6fea
fix: example
chungquantin Oct 9, 2024
c598eae
fix: comments
chungquantin Oct 10, 2024
c3a13c3
fix: update doc
chungquantin Oct 11, 2024
c4f6a87
fix: doc
chungquantin Oct 11, 2024
5ea730b
fix: doc
chungquantin Oct 11, 2024
30405b4
fix: doc
chungquantin Oct 15, 2024
364766f
fix: doc
chungquantin Oct 15, 2024
98244cd
fix: docs
chungquantin Oct 15, 2024
4ac3fab
docs: drink
Daanvdplas Oct 16, 2024
1a585d5
fix: comments
Daanvdplas Oct 17, 2024
aaf322c
fix: Cargo.lock
Daanvdplas Oct 17, 2024
dc38f73
refactor
Daanvdplas Oct 17, 2024
2710891
Merge pull request #22 from r0gue-io/daan/docs-drink
Daanvdplas Oct 18, 2024
59ae0eb
remove: pallet-api
Daanvdplas Oct 18, 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
9 changes: 7 additions & 2 deletions Cargo.lock

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

19 changes: 9 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
[workspace]
resolver = "2"
members = [
"crates/ink-sandbox",
"crates/drink/drink",
"crates/drink/drink-cli",
"crates/drink/drink/test-macro",
"crates/pop-drink",
]
exclude = [
"crates/drink/examples",
"crates/ink-sandbox",
"crates/drink/drink",
"crates/drink/drink-cli",
"crates/drink/drink/test-macro",
"crates/pop-drink",
]
exclude = ["crates/drink/examples"]

[workspace.package]
edition = "2021"
Expand All @@ -32,7 +30,7 @@ proc-macro2 = { version = "1" }
quote = { version = "1" }
ratatui = { version = "0.21.0" }
scale = { package = "parity-scale-codec", version = "3.6.9", features = [
"derive",
"derive",
] }
scale-info = { version = "2.10.0" }
serde_json = { version = "1.0" }
Expand All @@ -56,6 +54,7 @@ sp-runtime-interface = { version = "28.0.0", features = ["std"] }
# Local
drink = { path = "crates/drink/drink" }
ink_sandbox = { path = "crates/ink-sandbox" }
pallet-api = { path = "../pop-node/pallets/api" }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A reminder that these will need to be updated to the actual repos before this is merged.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#20

pop-api = { path = "../pop-node/pop-api" }
pop-drink = { path = "crates/pop-drink" }
pop-runtime-devnet = { path = "../pop-node/runtime/devnet" }
pop-api = { path = "../pop-node/pop-api" }
8 changes: 4 additions & 4 deletions crates/drink/drink/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ pub use drink_test_macro::{contract_bundle_provider, test};
pub use errors::Error;
pub use frame_support;
pub use ink_sandbox::{
self, api as sandbox_api, create_sandbox, impl_sandbox,
pallet_balances, pallet_contracts, pallet_timestamp, sp_externalities, AccountId32,
DispatchError, Sandbox, Ss58Codec, Weight,
self, api as sandbox_api, create_sandbox, impl_sandbox, pallet_assets, pallet_balances,
pallet_contracts, pallet_timestamp, sp_externalities, AccountId32, DispatchError, Sandbox,
Ss58Codec, Weight,
};
#[cfg(feature = "session")]
pub use session::mock::{mock_message, ContractMock, MessageMock, MockedCallResult, Selector};
Expand All @@ -28,4 +28,4 @@ pub mod minimal {

// create_sandbox!(MinimalSandbox);
create_sandbox!(MinimalSandbox, (), crate::pallet_contracts_debugging::DrinkDebug);
}
}
12 changes: 11 additions & 1 deletion crates/pop-drink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@ version = "0.1.0"
edition = "2021"

[dependencies]
scale-info = { workspace = true, default-features = false, features = [
"derive",
] }

drink.workspace = true
ink_sandbox.workspace = true
pop-runtime-devnet.workspace = true
pallet-contracts.workspace = true
pop-runtime-devnet.workspace = true
frame-system.workspace = true
frame-support.workspace = true
sp-io.workspace = true
scale.workspace = true
pop-api.workspace = true

[dev-dependencies]
pallet-api.workspace = true
pallet-assets.workspace = true
pallet-balances.workspace = true
pallet-timestamp.workspace = true
217 changes: 217 additions & 0 deletions crates/pop-drink/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
//! A set of errors used for testing smart contracts.

use std::fmt::Debug;

pub use drink::{
pallet_assets::Error as AssetsError, pallet_balances::Error as BalancesError,
pallet_contracts::Error as ContractsError,
};
use scale::{Decode, Encode};

fn decode<T: Decode>(data: &[u8]) -> T {
T::decode(&mut &data[..]).expect("Decoding failed")
}

/// Runtime error for testing.
///
/// # Generic Parameters:
///
/// - `ModuleError` - Error type of the runtime modules. Reference: https://paritytech.github.io/polkadot-sdk/master/solochain_template_runtime/enum.Error.html.
/// - `ApiError` - Error type of the API, which depends on version.
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
/// - `MODULE_INDEX` - Index of the variant `Error::Module`. This is based on the index of
/// `ApiError::Module`
#[derive(Encode, Decode, Debug)]
pub enum Error<ModuleError, ApiError, const MODULE_INDEX: u8>
where
ModuleError: Decode + Encode + Debug,
ApiError: Decode + Encode + Debug + From<u32> + Into<u32>,
{
/// Module errors of the runtime.
Module(ModuleError),
/// Every `ApiError`.
Api(ApiError),
}

impl<ModuleError, ApiError, const MODULE_INDEX: u8> From<Error<ModuleError, ApiError, MODULE_INDEX>>
for u32
where
ModuleError: Decode + Encode + Debug,
ApiError: Decode + Encode + Debug + From<u32> + Into<u32>,
{
/// Converts a `Error` into a numerical value of `ApiError`.
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
///
/// This conversion is necessary for comparing `Error` instances with other types.
// Compared types must implement `Into<u32>`, as `Error` does not implement `Eq`.
// Use this function to obtain a numerical representation of the error for comparison or
// further processing.
fn from(error: Error<ModuleError, ApiError, MODULE_INDEX>) -> Self {
match error {
Error::Module(error) => {
let mut encoded = error.encode();
encoded.insert(0, MODULE_INDEX);
encoded.resize(4, 0);
decode::<ApiError>(&encoded)
},
Error::Api(error) => decode::<ApiError>(&error.encode()),
}
.into()
}
}

impl<ModuleError, ApiError, const MODULE_INDEX: u8> From<u32>
for Error<ModuleError, ApiError, MODULE_INDEX>
where
ModuleError: Decode + Encode + Debug,
ApiError: Decode + Encode + Debug + From<u32> + Into<u32>,
{
/// Converts a numerical value of `ApiError` into a `Error`.
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
///
/// This is used to reconstruct and display a `Error` from its numerical representation
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
/// when an error is thrown.
fn from(value: u32) -> Self {
let error = ApiError::from(value);
let encoded = error.encode();
if encoded[0] == MODULE_INDEX {
let (index, module_error) = (encoded[1], &encoded[2..]);
let data = vec![vec![index], module_error.to_vec()].concat();
return Error::Module(decode(&data));
}
Error::Api(error)
}
}

/// A utility macro to assert that an error returned from a smart contract method using API
/// V0.
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Parameters:
///
/// - `result` - The result returned by a smart contract method. It is of type `Result<R, E>`, where
/// the error type `E` must implement a conversion to `u32`.
/// - `error` - A `Error` type configured specifically for the API V0.
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
///
/// # Example:
///
/// ```rs
/// use drink::devnet::{
/// error::{
/// v0::{assert_err, Error},
/// Assets,
/// AssetsError::AssetNotLive,
/// }
/// };
///
/// /// Example `pop-drink` testing method to interact with PSP22 contract.
/// fn transfer(session: &mut Session<Pop>, to: AccountId, amount: Balance) -> Result<(), PSP22Error> {
/// call::<Pop, (), PSP22Error>(
/// session,
/// "Psp22::transfer",
/// vec![to.to_string(), amount.to_string(), serde_json::to_string::<[u8; 0]>(&[]).unwrap()],
/// None,
/// )
/// }
///
/// /// Using macro to test the returned error.
/// assert_err!(
/// transfer(&mut session, ALICE, AMOUNT),
/// Error::Module(Assets(AssetNotLive))
/// );
/// ```
#[macro_export]
macro_rules! assert_err {
($result:expr, $error:expr $(,)?) => {
$crate::error::assert_err_inner::<_, _, _>($result, $error);
};
}

/// A utility macro to assert that an error returned from a smart contract method matches the
/// `Error`.
///
/// # Generic parameters:
///
/// - `R` - Success type returned if Ok().
/// - `E` - Returned `Err()` value of a method result. Must be convertable to `u32`.
/// - `Error` - Runtime error type.
///
/// # Parameters:
///
/// - `result` - Result returned by a smart contract method.
/// - `expected_error` - `Error` to be asserted.
#[track_caller]
pub fn assert_err_inner<R, E, Error>(result: Result<R, E>, expected_error: Error)
where
E: Into<u32>,
Error: From<u32> + Into<u32> + Debug,
{
let expected_code: u32 = expected_error.into();
let expected_error = Error::from(expected_code);
if let Err(error) = result {
let error_code: u32 = error.into();
if error_code != expected_code {
panic!(
r#"assertion `left == right` failed
left: {:?}
right: {:?}"#,
Error::from(error_code),
expected_error
);
}
} else {
panic!(
r#"assertion `left == right` failed
left: Ok()
right: {:?}"#,
expected_error
);
}
}

#[cfg(test)]
mod test {
use pop_api::primitives::v0::Error as ApiError;

use crate::error::{AssetsError::*, BalancesError::*, *};

fn test_cases() -> Vec<(Error<crate::mock::RuntimeError, ApiError, 3>, ApiError)> {
use frame_support::traits::PalletInfoAccess;
use pop_api::primitives::{ArithmeticError::*, TokenError::*};

use crate::mock::RuntimeError::*;
vec![
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
(Error::Api(ApiError::BadOrigin), ApiError::BadOrigin),
(Error::Api(ApiError::Token(BelowMinimum)), ApiError::Token(BelowMinimum)),
(Error::Api(ApiError::Arithmetic(Overflow)), ApiError::Arithmetic(Overflow)),
(
Error::Module(Assets(BalanceLow)),
ApiError::Module { index: crate::mock::Assets::index() as u8, error: [0, 0] },
),
(
Error::Module(Assets(NoAccount)),
ApiError::Module { index: crate::mock::Assets::index() as u8, error: [1, 0] },
),
(
Error::Module(Balances(VestingBalance)),
ApiError::Module { index: crate::mock::Balances::index() as u8, error: [0, 0] },
),
(
Error::Module(Balances(LiquidityRestrictions)),
ApiError::Module { index: crate::mock::Balances::index() as u8, error: [1, 0] },
),
]
}

#[test]
fn runtime_error_to_primitives_error_conversion_works() {
test_cases().into_iter().for_each(|t| {
let runtime_error: u32 = t.0.into();
let pop_api_error: u32 = t.1.into();
assert_eq!(runtime_error, pop_api_error);
});
}

#[test]
fn assert_err_works() {
test_cases().into_iter().for_each(|t| {
crate::assert_err!(Result::<(), pop_api::primitives::v0::Error>::Err(t.1), t.0,);
});
}
}
Loading
Loading