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: devnet runtime api #274

Merged
merged 22 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 19 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
4 changes: 2 additions & 2 deletions pallets/api/src/fungibles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub mod pallet {
use super::*;

/// State reads for the fungibles API with required input.
#[derive(Encode, Decode, Debug, MaxEncodedLen)]
#[derive(Encode, Decode, Debug, MaxEncodedLen, PartialEq, Clone)]
peterwht marked this conversation as resolved.
Show resolved Hide resolved
#[repr(u8)]
#[allow(clippy::unnecessary_cast)]
pub enum Read<T: Config> {
Expand Down Expand Up @@ -84,7 +84,7 @@ pub mod pallet {
}

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

/// A query of runtime state.
#[derive(Decode, Debug)]
#[derive(Decode, Debug, PartialEq, Clone)]
peterwht marked this conversation as resolved.
Show resolved Hide resolved
#[repr(u8)]
pub enum RuntimeRead {
/// Fungible token queries.
Expand Down Expand Up @@ -54,7 +54,7 @@ impl Readable for RuntimeRead {
}

/// The result of a runtime state read.
#[derive(Debug)]
#[derive(Debug, PartialEq, Clone)]
pub enum RuntimeResult {
/// Fungible token read results.
Fungibles(fungibles::ReadResult<Runtime>),
Expand Down Expand Up @@ -163,53 +163,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] = [
evilrobot-01 marked this conversation as resolved.
Show resolved Hide resolved
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))
}
}
}
64 changes: 63 additions & 1 deletion runtime/devnet/src/config/api/versioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl From<VersionedRuntimeRead> for RuntimeRead {
}

/// Versioned runtime state read results.
#[derive(Debug)]
#[derive(Debug, PartialEq, Clone)]
peterwht marked this conversation as resolved.
Show resolved Hide resolved
pub enum VersionedRuntimeResult {
/// Version zero of runtime read results.
V0(RuntimeResult),
Expand Down Expand Up @@ -69,6 +69,7 @@ impl From<VersionedRuntimeResult> for Vec<u8> {

/// Versioned errors.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
Daanvdplas marked this conversation as resolved.
Show resolved Hide resolved
pub enum VersionedError {
/// Version zero of errors.
V0(pop_primitives::v0::Error),
Expand All @@ -95,6 +96,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.
peterwht marked this conversation as resolved.
Show resolved Hide resolved
struct V0Error(pop_primitives::v0::Error);
impl From<DispatchError> for V0Error {
fn from(error: DispatchError) -> Self {
Expand Down Expand Up @@ -159,12 +162,71 @@ 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;
let v1 = 1;
assert_eq!(
VersionedRuntimeResult::try_from((result.clone(), v0)),
Ok(VersionedRuntimeResult::V0(result.clone()))
);
// Unknown version.
assert_eq!(
peterwht marked this conversation as resolved.
Show resolved Hide resolved
VersionedRuntimeResult::try_from((result.clone(), v1)),
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;
let v1 = 1;
assert_eq!(
VersionedError::try_from((error, v0)),
Ok(VersionedError::V0(V0Error::from(error).0))
);
// Unknown version.
assert_eq!(
peterwht marked this conversation as resolved.
Show resolved Hide resolved
VersionedError::try_from((error, v1)),
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
37 changes: 19 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;
Daanvdplas marked this conversation as resolved.
Show resolved Hide resolved
type CallStack = [pallet_contracts::Frame<Self>; 23];
type ChainExtension = api::Extension<Config>;
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
Expand Down Expand Up @@ -91,3 +78,17 @@ 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() {
Daanvdplas marked this conversation as resolved.
Show resolved Hide resolved
use std::any::TypeId;

type ExpectedCallFilter = Nothing;
type ConfigCallFilter = <Runtime as pallet_contracts::Config>::CallFilter;

let expected = TypeId::of::<ExpectedCallFilter>();
let config = TypeId::of::<ConfigCallFilter>();

assert_eq!(config, expected);
}
37 changes: 19 additions & 18 deletions runtime/testnet/src/config/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
use frame_support::{
parameter_types,
traits::{ConstBool, ConstU32, Randomness},
traits::{ConstBool, ConstU32, Nothing, Randomness},
};
use frame_system::{pallet_prelude::BlockNumberFor, EnsureSigned};

use crate::{
deposit, extensions, Balance, Balances, BalancesCall, Perbill, Runtime, RuntimeCall,
RuntimeEvent, RuntimeHoldReason, Timestamp,
deposit, extensions, 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 @@ -48,13 +40,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.
Daanvdplas marked this conversation as resolved.
Show resolved Hide resolved
type CallFilter = Nothing;
type CallStack = [pallet_contracts::Frame<Self>; 23];
type ChainExtension = extensions::PopApiExtension;
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
Expand Down Expand Up @@ -90,3 +77,17 @@ 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;

type ExpectedCallFilter = Nothing;
type ConfigCallFilter = <Runtime as pallet_contracts::Config>::CallFilter;

let expected = TypeId::of::<ExpectedCallFilter>();
let config = TypeId::of::<ConfigCallFilter>();

assert_eq!(config, expected);
}
Loading