Skip to content

Commit

Permalink
test(devnet): runtime api (#274)
Browse files Browse the repository at this point in the history
Co-authored-by: Frank Bell <frank@r0gue.io>
Co-authored-by: Frank Bell <60948618+evilrobot-01@users.noreply.github.com>
Co-authored-by: Peter White <petras9789@gmail.com>
  • Loading branch information
4 people authored Sep 13, 2024
1 parent 5f92dab commit 4b9da05
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 97 deletions.
2 changes: 2 additions & 0 deletions pallets/api/src/fungibles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub mod pallet {

/// State reads for the fungibles API with required input.
#[derive(Encode, Decode, Debug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(PartialEq, Clone))]
#[repr(u8)]
#[allow(clippy::unnecessary_cast)]
pub enum Read<T: Config> {
Expand Down Expand Up @@ -85,6 +86,7 @@ pub mod pallet {

/// Results of state reads for the fungibles API.
#[derive(Debug)]
#[cfg_attr(feature = "std", derive(PartialEq, Clone))]
pub enum ReadResult<T: Config> {
/// Total token supply for a specified token.
TotalSupply(BalanceOf<T>),
Expand Down
121 changes: 77 additions & 44 deletions runtime/devnet/src/config/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type DecodesAs<Output, Logger = ()> = pallet_api::extension::DecodesAs<

/// A query of runtime state.
#[derive(Decode, Debug)]
#[cfg_attr(test, derive(PartialEq, Clone))]
#[repr(u8)]
pub enum RuntimeRead {
/// Fungible token queries.
Expand Down Expand Up @@ -55,6 +56,7 @@ impl Readable for RuntimeRead {

/// The result of a runtime state read.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq, Clone))]
pub enum RuntimeResult {
/// Fungible token read results.
Fungibles(fungibles::ReadResult<Runtime>),
Expand Down Expand Up @@ -163,53 +165,84 @@ impl<T: frame_system::Config> Contains<RuntimeRead> for Filter<T> {
}
}

#[test]
fn filter_prevents_runtime_filtered_calls() {
use pallet_balances::{AdjustmentDirection, Call::*};
use sp_runtime::MultiAddress;
use RuntimeCall::Balances;

const CALLS: [RuntimeCall; 4] = [
Balances(force_adjust_total_issuance {
direction: AdjustmentDirection::Increase,
delta: 0,
}),
Balances(force_set_balance { who: MultiAddress::Address32([0u8; 32]), new_free: 0 }),
Balances(force_transfer {
source: MultiAddress::Address32([0u8; 32]),
dest: MultiAddress::Address32([0u8; 32]),
value: 0,
}),
Balances(force_unreserve { who: MultiAddress::Address32([0u8; 32]), amount: 0 }),
];

for call in CALLS {
assert!(!Filter::<Runtime>::contains(&call))
}
}

#[test]
fn filter_allows_fungibles_calls() {
#[cfg(test)]
mod tests {
use codec::Encode;
use pallet_api::fungibles::Call::*;
use sp_core::crypto::AccountId32;
use RuntimeCall::Fungibles;
use RuntimeCall::{Balances, Fungibles};

use super::*;

const ACCOUNT: AccountId32 = AccountId32::new([0u8; 32]);
const CALLS: [RuntimeCall; 11] = [
Fungibles(transfer { token: 0, to: ACCOUNT, value: 0 }),
Fungibles(transfer_from { token: 0, from: ACCOUNT, to: ACCOUNT, value: 0 }),
Fungibles(approve { token: 0, spender: ACCOUNT, value: 0 }),
Fungibles(increase_allowance { token: 0, spender: ACCOUNT, value: 0 }),
Fungibles(decrease_allowance { token: 0, spender: ACCOUNT, value: 0 }),
Fungibles(create { id: 0, admin: ACCOUNT, min_balance: 0 }),
Fungibles(set_metadata { token: 0, name: vec![], symbol: vec![], decimals: 0 }),
Fungibles(start_destroy { token: 0 }),
Fungibles(clear_metadata { token: 0 }),
Fungibles(mint { token: 0, account: ACCOUNT, value: 0 }),
Fungibles(burn { token: 0, account: ACCOUNT, value: 0 }),
];

for call in CALLS {
assert!(Filter::<Runtime>::contains(&call))

#[test]
fn runtime_result_encode_works() {
let value = 1_000;
let result = fungibles::ReadResult::<Runtime>::TotalSupply(value);
assert_eq!(RuntimeResult::Fungibles(result).encode(), value.encode());
}

#[test]
fn filter_prevents_runtime_filtered_calls() {
use pallet_balances::{AdjustmentDirection, Call::*};
use sp_runtime::MultiAddress;

const CALLS: [RuntimeCall; 4] = [
Balances(force_adjust_total_issuance {
direction: AdjustmentDirection::Increase,
delta: 0,
}),
Balances(force_set_balance { who: MultiAddress::Address32([0u8; 32]), new_free: 0 }),
Balances(force_transfer {
source: MultiAddress::Address32([0u8; 32]),
dest: MultiAddress::Address32([0u8; 32]),
value: 0,
}),
Balances(force_unreserve { who: MultiAddress::Address32([0u8; 32]), amount: 0 }),
];

for call in CALLS {
assert!(!Filter::<Runtime>::contains(&call))
}
}

#[test]
fn filter_allows_fungibles_calls() {
const CALLS: [RuntimeCall; 11] = [
Fungibles(transfer { token: 0, to: ACCOUNT, value: 0 }),
Fungibles(transfer_from { token: 0, from: ACCOUNT, to: ACCOUNT, value: 0 }),
Fungibles(approve { token: 0, spender: ACCOUNT, value: 0 }),
Fungibles(increase_allowance { token: 0, spender: ACCOUNT, value: 0 }),
Fungibles(decrease_allowance { token: 0, spender: ACCOUNT, value: 0 }),
Fungibles(create { id: 0, admin: ACCOUNT, min_balance: 0 }),
Fungibles(set_metadata { token: 0, name: vec![], symbol: vec![], decimals: 0 }),
Fungibles(start_destroy { token: 0 }),
Fungibles(clear_metadata { token: 0 }),
Fungibles(mint { token: 0, account: ACCOUNT, value: 0 }),
Fungibles(burn { token: 0, account: ACCOUNT, value: 0 }),
];

for call in CALLS {
assert!(Filter::<Runtime>::contains(&call))
}
}

#[test]
fn filter_allows_fungibles_reads() {
use super::{fungibles::Read::*, RuntimeRead::*};
const READS: [RuntimeRead; 7] = [
Fungibles(TotalSupply(1)),
Fungibles(BalanceOf { token: 1, owner: ACCOUNT }),
Fungibles(Allowance { token: 1, owner: ACCOUNT, spender: ACCOUNT }),
Fungibles(TokenName(1)),
Fungibles(TokenSymbol(1)),
Fungibles(TokenDecimals(10)),
Fungibles(TokenExists(1)),
];

for read in READS {
assert!(Filter::<Runtime>::contains(&read))
}
}
}
73 changes: 73 additions & 0 deletions runtime/devnet/src/config/api/versioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ impl From<VersionedRuntimeRead> for RuntimeRead {

/// Versioned runtime state read results.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq, Clone))]
pub enum VersionedRuntimeResult {
/// Version zero of runtime read results.
V0(RuntimeResult),
Expand Down Expand Up @@ -69,6 +70,7 @@ impl From<VersionedRuntimeResult> for Vec<u8> {

/// Versioned errors.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub enum VersionedError {
/// Version zero of errors.
V0(pop_primitives::v0::Error),
Expand All @@ -95,6 +97,8 @@ impl From<VersionedError> for u32 {
}
}

// Type for conversion to a versioned `pop_primitives::Error` to avoid taking a dependency of
// sp-runtime on pop-primitives.
struct V0Error(pop_primitives::v0::Error);
impl From<DispatchError> for V0Error {
fn from(error: DispatchError) -> Self {
Expand Down Expand Up @@ -159,12 +163,81 @@ impl From<DispatchError> for V0Error {

#[cfg(test)]
mod tests {
use codec::Encode;
use frame_system::Call;
use pop_primitives::{ArithmeticError::*, Error, TokenError::*, TransactionalError::*};
use sp_runtime::ModuleError;
use DispatchError::*;

use super::*;

#[test]
fn from_versioned_runtime_call_to_runtime_call_works() {
let call =
RuntimeCall::System(Call::remark_with_event { remark: "pop".as_bytes().to_vec() });
assert_eq!(RuntimeCall::from(VersionedRuntimeCall::V0(call.clone())), call);
}

#[test]
fn from_versioned_runtime_read_to_runtime_read_works() {
let read = RuntimeRead::Fungibles(fungibles::Read::<Runtime>::TotalSupply(42));
assert_eq!(RuntimeRead::from(VersionedRuntimeRead::V0(read.clone())), read);
}

#[test]
fn versioned_runtime_result_works() {
let result = RuntimeResult::Fungibles(fungibles::ReadResult::<Runtime>::TotalSupply(1_000));
let v0 = 0;
assert_eq!(
VersionedRuntimeResult::try_from((result.clone(), v0)),
Ok(VersionedRuntimeResult::V0(result.clone()))
);
}

#[test]
fn versioned_runtime_result_fails() {
// Unknown version 1.
assert_eq!(
VersionedRuntimeResult::try_from((
RuntimeResult::Fungibles(fungibles::ReadResult::<Runtime>::TotalSupply(1_000)),
1
)),
Err(pallet_contracts::Error::<Runtime>::DecodingFailed.into())
);
}

#[test]
fn versioned_runtime_result_to_bytes_works() {
let value = 1_000;
let result = RuntimeResult::Fungibles(fungibles::ReadResult::<Runtime>::TotalSupply(value));
assert_eq!(<Vec<u8>>::from(VersionedRuntimeResult::V0(result)), value.encode());
}

#[test]
fn versioned_error_works() {
let error = BadOrigin;
let v0 = 0;

assert_eq!(
VersionedError::try_from((error, v0)),
Ok(VersionedError::V0(V0Error::from(error).0))
);
}

#[test]
fn versioned_error_fails() {
// Unknown version 1.
assert_eq!(
VersionedError::try_from((BadOrigin, 1)),
Err(pallet_contracts::Error::<Runtime>::DecodingFailed.into())
);
}

#[test]
fn versioned_error_to_u32_works() {
assert_eq!(u32::from(VersionedError::V0(Error::BadOrigin)), u32::from(Error::BadOrigin));
}

// Compare all the different `DispatchError` variants with the expected `Error`.
#[test]
fn dispatch_error_to_error() {
Expand Down
33 changes: 15 additions & 18 deletions runtime/devnet/src/config/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
use frame_support::{
parameter_types,
traits::{ConstBool, ConstU32, Randomness},
traits::{ConstBool, ConstU32, Nothing, Randomness},
};
use frame_system::{pallet_prelude::BlockNumberFor, EnsureSigned};

use super::api::{self, Config};
use crate::{
deposit, Balance, Balances, BalancesCall, Perbill, Runtime, RuntimeCall, RuntimeEvent,
RuntimeHoldReason, Timestamp,
deposit, Balance, Balances, Perbill, Runtime, RuntimeCall, RuntimeEvent, RuntimeHoldReason,
Timestamp,
};

pub enum AllowBalancesCall {}

impl frame_support::traits::Contains<RuntimeCall> for AllowBalancesCall {
fn contains(call: &RuntimeCall) -> bool {
matches!(call, RuntimeCall::Balances(BalancesCall::transfer_allow_death { .. }))
}
}

fn schedule<T: pallet_contracts::Config>() -> pallet_contracts::Schedule<T> {
pallet_contracts::Schedule {
limits: pallet_contracts::Limits {
Expand Down Expand Up @@ -49,13 +41,8 @@ parameter_types! {
impl pallet_contracts::Config for Runtime {
type AddressGenerator = pallet_contracts::DefaultAddressGenerator;
type ApiVersion = ();
/// The safest default is to allow no calls at all.
///
/// Runtimes should whitelist dispatchables that are allowed to be called from contracts
/// and make sure they are stable. Dispatchables exposed to contracts are not allowed to
/// change because that would break already deployed contracts. The `RuntimeCall` structure
/// itself is not allowed to change the indices of existing pallets, too.
type CallFilter = AllowBalancesCall;
// IMPORTANT: only runtime calls through the api are allowed.
type CallFilter = Nothing;
type CallStack = [pallet_contracts::Frame<Self>; 23];
type ChainExtension = api::Extension<Config>;
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
Expand Down Expand Up @@ -91,3 +78,13 @@ impl pallet_contracts::Config for Runtime {
type WeightPrice = pallet_transaction_payment::Pallet<Self>;
type Xcm = pallet_xcm::Pallet<Self>;
}

// IMPORTANT: only runtime calls through the api are allowed.
#[test]
fn contracts_prevents_runtime_calls() {
use std::any::TypeId;
assert_eq!(
TypeId::of::<<Runtime as pallet_contracts::Config>::CallFilter>(),
TypeId::of::<Nothing>()
);
}
25 changes: 8 additions & 17 deletions runtime/devnet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -995,22 +995,13 @@ cumulus_pallet_parachain_system::register_validate_block! {
BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::<Runtime, Executive>,
}

#[cfg(test)]
mod tests {
// Ensures that the account id lookup does not perform any state reads. When this changes,
// `pallet_api::fungibles` dispatchables need to be re-evaluated.
#[test]
fn test_lookup_config() {
use std::any::TypeId;

use crate::Runtime;

// Ensures that the account id lookup does not perform any state reads. When this changes,
// `pallet_api::fungibles` dispatchables need to be re-evaluated.
#[test]
fn test_lookup_config() {
type ExpectedLookup = sp_runtime::traits::AccountIdLookup<sp_runtime::AccountId32, ()>;
type ConfigLookup = <Runtime as frame_system::Config>::Lookup;

let expected_type_id = TypeId::of::<ExpectedLookup>();
let config_type_id = TypeId::of::<ConfigLookup>();

assert_eq!(config_type_id, expected_type_id);
}
assert_eq!(
TypeId::of::<<Runtime as frame_system::Config>::Lookup>(),
TypeId::of::<sp_runtime::traits::AccountIdLookup<sp_runtime::AccountId32, ()>>()
);
}
Loading

0 comments on commit 4b9da05

Please sign in to comment.