diff --git a/bin/runtime/src/chain_extension/environment.rs b/bin/runtime/src/chain_extension/environment.rs new file mode 100644 index 0000000000..86f2fbae6f --- /dev/null +++ b/bin/runtime/src/chain_extension/environment.rs @@ -0,0 +1,54 @@ +use frame_support::weights::Weight; +use pallet_contracts::{ + chain_extension::{BufInBufOutState, Environment as SubstrateEnvironment, Ext, SysConfig}, + ChargedAmount, +}; +use sp_core::crypto::UncheckedFrom; +use sp_runtime::DispatchError; +use sp_std::vec::Vec; + +use crate::chain_extension::ByteCount; + +/// Abstraction around `pallet_contracts::chain_extension::Environment`. Makes testing easier. +/// +/// Gathers all the methods that are used by `SnarcosChainExtension`. For now, all operations are +/// performed in the `BufInBufOut` mode, so we don't have to take care of other modes. +/// +/// Each method is already documented in `pallet_contracts::chain_extension`. +pub(super) trait Environment: Sized { + /// A type returned by `charge_weight` and passed to `adjust_weight`. + /// + /// The original type `ChargedAmount` has only a private constructor and thus we have to + /// abstract it as well to make testing it possible. + type ChargedAmount; + + fn in_len(&self) -> ByteCount; + fn read(&self, max_len: u32) -> Result, DispatchError>; + + fn charge_weight(&mut self, amount: Weight) -> Result; + fn adjust_weight(&mut self, charged: Self::ChargedAmount, actual_weight: Weight); +} + +/// Transparent delegation. +impl Environment for SubstrateEnvironment<'_, '_, E, BufInBufOutState> +where + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, +{ + type ChargedAmount = ChargedAmount; + + fn in_len(&self) -> ByteCount { + self.in_len() + } + + fn read(&self, max_len: u32) -> Result, DispatchError> { + self.read(max_len) + } + + fn charge_weight(&mut self, amount: Weight) -> Result { + self.charge_weight(amount) + } + + fn adjust_weight(&mut self, charged: ChargedAmount, actual_weight: Weight) { + self.adjust_weight(charged, actual_weight) + } +} diff --git a/bin/runtime/src/chain_extension/executor.rs b/bin/runtime/src/chain_extension/executor.rs new file mode 100644 index 0000000000..a8261a4a1f --- /dev/null +++ b/bin/runtime/src/chain_extension/executor.rs @@ -0,0 +1,49 @@ +use pallet_snarcos::{Error, Pallet as Snarcos, ProvingSystem, VerificationKeyIdentifier}; +use sp_std::vec::Vec; + +use crate::Runtime; + +/// Abstraction around `Runtime`. Makes testing easier. +/// +/// Gathers all the methods that are used by `SnarcosChainExtension`. +/// +/// Each method is already documented in `pallet_snarcos`. +pub(super) trait Executor: Sized { + /// The error returned from dispatchables is generic. For most purposes however, it doesn't + /// matter what type will be passed there. Normally, `Runtime` will be the generic argument, + /// but in testing it will be sufficient to instantiate it with `()`. + type ErrorGenericType; + + fn store_key( + identifier: VerificationKeyIdentifier, + key: Vec, + ) -> Result<(), Error>; + + fn verify( + verification_key_identifier: VerificationKeyIdentifier, + proof: Vec, + public_input: Vec, + system: ProvingSystem, + ) -> Result<(), Error>; +} + +/// Transparent delegation. +impl Executor for Runtime { + type ErrorGenericType = Runtime; + + fn store_key( + identifier: VerificationKeyIdentifier, + key: Vec, + ) -> Result<(), Error> { + Snarcos::::bare_store_key(identifier, key) + } + + fn verify( + verification_key_identifier: VerificationKeyIdentifier, + proof: Vec, + public_input: Vec, + system: ProvingSystem, + ) -> Result<(), Error> { + Snarcos::::bare_verify(verification_key_identifier, proof, public_input, system) + } +} diff --git a/bin/runtime/src/chain_extension/mod.rs b/bin/runtime/src/chain_extension/mod.rs index 3a8167e35d..f90bee1f66 100644 --- a/bin/runtime/src/chain_extension/mod.rs +++ b/bin/runtime/src/chain_extension/mod.rs @@ -1,17 +1,21 @@ -use codec::Decode; -use frame_support::{log::error, pallet_prelude::Weight}; +use codec::{Decode, Encode}; +use environment::Environment; +use executor::Executor; +use frame_support::{log::error, weights::Weight}; use pallet_contracts::chain_extension::{ - ChainExtension, Environment, Ext, InitState, RetVal, SysConfig, -}; -use pallet_snarcos::{ - Config, Error, Pallet as Snarcos, ProvingSystem, VerificationKeyIdentifier, WeightInfo, + ChainExtension, Environment as SubstrateEnvironment, Ext, InitState, RetVal, SysConfig, }; +use pallet_snarcos::{Config, Error, ProvingSystem, VerificationKeyIdentifier, WeightInfo}; use sp_core::crypto::UncheckedFrom; use sp_runtime::DispatchError; use sp_std::{mem::size_of, vec::Vec}; use Error::*; use crate::{MaximumVerificationKeyLength, Runtime}; +mod environment; +mod executor; +#[cfg(test)] +mod tests; pub const SNARCOS_STORE_KEY_FUNC_ID: u32 = 41; pub const SNARCOS_VERIFY_FUNC_ID: u32 = 42; @@ -19,7 +23,7 @@ pub const SNARCOS_VERIFY_FUNC_ID: u32 = 42; // Return codes for `SNARCOS_STORE_KEY_FUNC_ID`. pub const SNARCOS_STORE_KEY_OK: u32 = 10_000; pub const SNARCOS_STORE_KEY_TOO_LONG_KEY: u32 = 10_001; -pub const SNARCOS_STORE_KEY_IN_USE: u32 = 10_002; +pub const SNARCOS_STORE_KEY_IDENTIFIER_IN_USE: u32 = 10_002; pub const SNARCOS_STORE_KEY_ERROR_UNKNOWN: u32 = 10_003; // Return codes for `SNARCOS_VERIFY_FUNC_ID`. @@ -35,13 +39,18 @@ pub const SNARCOS_VERIFY_ERROR_UNKNOWN: u32 = 11_007; pub struct SnarcosChainExtension; impl ChainExtension for SnarcosChainExtension { - fn call(func_id: u32, env: Environment) -> Result + fn call( + func_id: u32, + env: SubstrateEnvironment, + ) -> Result where ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { match func_id { - SNARCOS_STORE_KEY_FUNC_ID => Self::snarcos_store_key(env), - SNARCOS_VERIFY_FUNC_ID => Self::snarcos_verify(env), + SNARCOS_STORE_KEY_FUNC_ID => { + Self::snarcos_store_key::<_, Runtime>(env.buf_in_buf_out()) + } + SNARCOS_VERIFY_FUNC_ID => Self::snarcos_verify::<_, Runtime>(env.buf_in_buf_out()), _ => { error!("Called an unregistered `func_id`: {}", func_id); Err(DispatchError::Other("Unimplemented func_id")) @@ -58,7 +67,7 @@ pub type ByteCount = u32; /// the order of values is important. /// /// It cannot be `MaxEncodedLen` due to `Vec<_>` and thus `Environment::read_as` cannot be used. -#[derive(Decode)] +#[derive(Decode, Encode)] struct StoreKeyArgs { pub identifier: VerificationKeyIdentifier, pub key: Vec, @@ -70,7 +79,7 @@ struct StoreKeyArgs { /// the order of values is important. /// /// It cannot be `MaxEncodedLen` due to `Vec<_>` and thus `Environment::read_as` cannot be used. -#[derive(Decode)] +#[derive(Decode, Encode)] struct VerifyArgs { pub identifier: VerificationKeyIdentifier, pub proof: Vec, @@ -78,27 +87,45 @@ struct VerifyArgs { pub system: ProvingSystem, } -impl SnarcosChainExtension { - fn snarcos_store_key(env: Environment) -> Result - where - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, - { - // We need to read input as plain bytes (encoded args). - let mut env = env.buf_in_buf_out(); +/// Provides a weight of `store_key` dispatchable. +fn weight_of_store_key(key_length: ByteCount) -> Weight { + <::WeightInfo as WeightInfo>::store_key(key_length) +} + +/// Provides a weight of `verify` dispatchable depending on the `ProvingSystem`. In case no system +/// is passed, we return maximal amongst all the systems. +fn weight_of_verify(system: Option) -> Weight { + match system { + Some(ProvingSystem::Groth16) => { + <::WeightInfo as WeightInfo>::verify_groth16() + } + Some(ProvingSystem::Gm17) => <::WeightInfo as WeightInfo>::verify_gm17(), + Some(ProvingSystem::Marlin) => { + <::WeightInfo as WeightInfo>::verify_marlin() + } + None => weight_of_verify(Some(ProvingSystem::Groth16)) + .max(weight_of_verify(Some(ProvingSystem::Gm17))) + .max(weight_of_verify(Some(ProvingSystem::Marlin))), + } +} - // Check if it makes sense to read and decode data. - let key_length = env +impl SnarcosChainExtension { + fn snarcos_store_key( + mut env: Env, + ) -> Result { + // Check if it makes sense to read and decode data. This is only an upperbound for the key + // length, because this bytes suffix contains (possibly compressed) info about actual key + // length (needed for decoding). + let approx_key_length = env .in_len() .saturating_sub(size_of::() as ByteCount); - if key_length > MaximumVerificationKeyLength::get() { + if approx_key_length > MaximumVerificationKeyLength::get() { return Ok(RetVal::Converging(SNARCOS_STORE_KEY_TOO_LONG_KEY)); } // We charge now - even if decoding fails and we shouldn't touch storage, we have to incur // fee for reading memory. - env.charge_weight(<::WeightInfo as WeightInfo>::store_key( - key_length, - ))?; + let pre_charged = env.charge_weight(weight_of_store_key(approx_key_length))?; // Parsing will have to be done here. This is due to the fact that methods // `Environment<_,_,_,S: BufIn>::read*` don't move starting pointer and thus we can make @@ -111,42 +138,27 @@ impl SnarcosChainExtension { let args = StoreKeyArgs::decode(&mut &*bytes) .map_err(|_| DispatchError::Other("Failed to decode arguments"))?; - let return_status = match Snarcos::::bare_store_key(args.identifier, args.key) { + // Now we know the exact key length. + env.adjust_weight( + pre_charged, + weight_of_store_key(args.key.len() as ByteCount), + ); + + let return_status = match Exc::store_key(args.identifier, args.key) { Ok(_) => SNARCOS_STORE_KEY_OK, // In case `DispatchResultWithPostInfo` was returned (or some simpler equivalent for - // `bare_store_key`), we could adjust weight. However, for the storing key action it - // doesn't make sense. + // `bare_store_key`), we could have adjusted weight. However, for the storing key action + // it doesn't make much sense. Err(VerificationKeyTooLong) => SNARCOS_STORE_KEY_TOO_LONG_KEY, - Err(IdentifierAlreadyInUse) => SNARCOS_STORE_KEY_IN_USE, + Err(IdentifierAlreadyInUse) => SNARCOS_STORE_KEY_IDENTIFIER_IN_USE, _ => SNARCOS_STORE_KEY_ERROR_UNKNOWN, }; Ok(RetVal::Converging(return_status)) } - fn weight_of_verify(system: Option) -> Weight { - match system { - Some(ProvingSystem::Groth16) => { - <::WeightInfo as WeightInfo>::verify_groth16() - } - Some(ProvingSystem::Gm17) => { - <::WeightInfo as WeightInfo>::verify_gm17() - } - Some(ProvingSystem::Marlin) => { - <::WeightInfo as WeightInfo>::verify_marlin() - } - None => Self::weight_of_verify(Some(ProvingSystem::Groth16)) - .max(Self::weight_of_verify(Some(ProvingSystem::Gm17))) - .max(Self::weight_of_verify(Some(ProvingSystem::Marlin))), - } - } - - fn snarcos_verify(env: Environment) -> Result - where - ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, - { - // We need to read input as plain bytes (encoded args). - let mut env = env.buf_in_buf_out(); - + fn snarcos_verify( + mut env: Env, + ) -> Result { // We charge optimistically, i.e. assuming that decoding succeeds and the verification // key is present. However, since we don't know the system yet, we have to charge maximal // possible fee. We will adjust it as soon as possible. @@ -156,7 +168,7 @@ impl SnarcosChainExtension { // `pallet_snarcos::WeightInfo::verify_decoding_failure`, we can both charge less here // (with further `env.adjust_weight`) and in the pallet itself (returning // `DispatchErrorWithPostInfo` reducing actual fee and the block weight). - let pre_charge = env.charge_weight(Self::weight_of_verify(None))?; + let pre_charge = env.charge_weight(weight_of_verify(None))?; // Parsing is done here for similar reasons as in `Self::snarcos_store_key`. let bytes = env.read(env.in_len())?; @@ -164,15 +176,15 @@ impl SnarcosChainExtension { let args: VerifyArgs = VerifyArgs::decode(&mut &*bytes) .map_err(|_| DispatchError::Other("Failed to decode arguments"))?; - env.adjust_weight(pre_charge, Self::weight_of_verify(Some(args.system))); + // Now we know the proving system and we can charge appropriate amount of gas. + env.adjust_weight(pre_charge, weight_of_verify(Some(args.system))); - let result = - Snarcos::::bare_verify(args.identifier, args.proof, args.input, args.system); + let result = Exc::verify(args.identifier, args.proof, args.input, args.system); let return_status = match result { Ok(_) => SNARCOS_VERIFY_OK, // In case `DispatchResultWithPostInfo` was returned (or some simpler equivalent for - // `bare_store_key`), we could adjust weight. However, we don't support it yet. + // `bare_verify`), we could adjust weight. However, we don't support it yet. Err(DeserializingProofFailed) => SNARCOS_VERIFY_DESERIALIZING_PROOF_FAIL, Err(DeserializingPublicInputFailed) => SNARCOS_VERIFY_DESERIALIZING_INPUT_FAIL, Err(UnknownVerificationKeyIdentifier) => SNARCOS_VERIFY_UNKNOWN_IDENTIFIER, diff --git a/bin/runtime/src/chain_extension/tests/environment.rs b/bin/runtime/src/chain_extension/tests/environment.rs new file mode 100644 index 0000000000..9a883b9e52 --- /dev/null +++ b/bin/runtime/src/chain_extension/tests/environment.rs @@ -0,0 +1,150 @@ +use std::{ + marker::PhantomData, + ops::Neg, + sync::mpsc::{channel, Sender}, +}; + +use super::*; + +/// Trait serving as a type-level flag indicating which method we are testing. +pub(super) trait FunctionMode {} +/// We are testing `pallet_snarcos::store_key`. +pub(super) enum StoreKeyMode {} +impl FunctionMode for StoreKeyMode {} +/// We are testing `pallet_snarcos::verify`. +pub(super) enum VerifyMode {} +impl FunctionMode for VerifyMode {} + +/// Trait serving as a type-level flag indicating how reading input from a contract should be done. +pub(super) trait ReadingMode {} +/// Reading fails - we won't be able to read a single byte. +pub(super) enum CorruptedMode {} +impl ReadingMode for CorruptedMode {} +/// Reading succeeds - we will read everything. +pub(super) enum StandardMode {} +impl ReadingMode for StandardMode {} + +/// We will implement reading for every `ReadingMode`. However, there is no other way than such +/// `_Read` trait to tell Rust compiler that in fact, for every `RM` in `MockedEnvironment<_, RM>` +/// there will be such function. +trait _Read { + fn _read(&self, max_len: ByteCount) -> Result, DispatchError>; +} + +/// Testing counterpart for `pallet_snarcos::chain_extension::Environment`. +pub(super) struct MockedEnvironment { + /// Channel to report all charges. + /// + /// We have to save charges outside this object, because it is consumed by the main call. + charging_channel: Sender, + + /// `Some(_)` only if `RM = CorruptedMode`. + /// + /// An optional callback to be invoked just before (failing to) read. + on_read: Option>, + /// `Some(_)` iff `RM = StandardMode`. + content: Option>, + + /// How many bytes are there waiting to be read. + in_len: ByteCount, + + _phantom: PhantomData<(FM, RM)>, +} + +/// Creating environment with corrupted reading. +impl MockedEnvironment { + pub fn new( + in_len: ByteCount, + on_read: Option>, + ) -> (Self, Receiver) { + let (sender, receiver) = channel(); + ( + Self { + charging_channel: sender, + on_read, + content: None, + in_len, + _phantom: Default::default(), + }, + receiver, + ) + } +} + +/// Corrupted reading with possible additional callback invoked. +impl _Read for MockedEnvironment { + fn _read(&self, _max_len: ByteCount) -> Result, DispatchError> { + self.on_read.as_ref().map(|action| action()); + Err(DispatchError::Other("Some error")) + } +} + +/// Creating environment with correct reading of `content`. +impl MockedEnvironment { + pub fn new(content: Vec) -> (Self, Receiver) { + let (sender, receiver) = channel(); + ( + Self { + charging_channel: sender, + on_read: None, + in_len: content.len() as ByteCount, + content: Some(content), + _phantom: Default::default(), + }, + receiver, + ) + } +} + +/// Successful reading. +impl _Read for MockedEnvironment { + fn _read(&self, max_len: ByteCount) -> Result, DispatchError> { + let content = self.content.as_ref().unwrap(); + if max_len > self.in_len { + Ok(content.clone()) + } else { + Ok(content[..max_len as usize].to_vec()) + } + } +} + +/// In case we are testing `pallet_snarcos::store_key`, we might want to approximate how long is the +/// verifying key. +/// +/// The returned value will be an upperbound - it will be the sum of the whole key encoding +/// (including its length). +impl MockedEnvironment { + pub fn approx_key_len(&self) -> ByteCount { + self.in_len + .checked_sub(size_of::() as ByteCount) + .unwrap() + } +} + +impl Environment for MockedEnvironment +where + MockedEnvironment: _Read, +{ + type ChargedAmount = Weight; + + fn in_len(&self) -> ByteCount { + self.in_len + } + + fn read(&self, max_len: u32) -> Result, DispatchError> { + self._read(max_len) + } + + fn charge_weight(&mut self, amount: Weight) -> Result { + self.charging_channel + .send(amount as RevertibleWeight) + .unwrap(); + Ok(amount) + } + + fn adjust_weight(&mut self, charged: Weight, actual_weight: Weight) { + self.charging_channel + .send(((charged - actual_weight) as RevertibleWeight).neg()) + .unwrap(); + } +} diff --git a/bin/runtime/src/chain_extension/tests/executor.rs b/bin/runtime/src/chain_extension/tests/executor.rs new file mode 100644 index 0000000000..8725d3c1e4 --- /dev/null +++ b/bin/runtime/src/chain_extension/tests/executor.rs @@ -0,0 +1,75 @@ +use pallet_snarcos::{Error as SnarcosError, ProvingSystem, VerificationKeyIdentifier}; + +use crate::chain_extension::executor::Executor; + +type Error = SnarcosError<()>; +type Result = core::result::Result<(), Error>; + +/// Describes how the `Executor` should behave when one of its methods is called. +#[derive(Clone, Eq, PartialEq)] +pub(super) enum Responder { + /// Twist and shout. + Panicker, + /// Return `Ok(())`. + Okayer, + /// Return `Err(Error)`. + Errorer(Error), +} + +/// Testing counterpart for `Runtime`. +/// +/// `STORE_KEY_RESPONDER` instructs how to behave then `store_key` is called. +/// `VERIFY_RESPONDER` instructs how to behave then `verify` is called. +pub(super) struct MockedExecutor< + const STORE_KEY_RESPONDER: Responder, + const VERIFY_RESPONDER: Responder, +>; + +/// Auxiliary method to construct type argument. +/// +/// Due to "`struct/enum construction is not supported in generic constants`". +pub(super) const fn make_errorer() -> Responder { + Responder::Errorer(ERROR) +} + +/// Executor that will scream for every associated method. +pub(super) type Panicker = MockedExecutor<{ Responder::Panicker }, { Responder::Panicker }>; + +/// Executor that will return `Ok(())` for `store_key` and scream for `verify`. +pub(super) type StoreKeyOkayer = MockedExecutor<{ Responder::Okayer }, { Responder::Panicker }>; +/// Executor that will return `Ok(())` for `verify` and scream for `store_key`. +pub(super) type VerifyOkayer = MockedExecutor<{ Responder::Panicker }, { Responder::Okayer }>; + +/// Executor that will return `Err(ERROR)` for `store_key` and scream for `verify`. +pub(super) type StoreKeyErrorer = + MockedExecutor<{ make_errorer::() }, { Responder::Panicker }>; +/// Executor that will return `Err(ERROR)` for `verify` and scream for `store_key`. +pub(super) type VerifyErrorer = + MockedExecutor<{ Responder::Panicker }, { make_errorer::() }>; + +impl Executor + for MockedExecutor +{ + type ErrorGenericType = (); + + fn store_key(_identifier: VerificationKeyIdentifier, _key: Vec) -> Result { + match STORE_KEY_RESPONDER { + Responder::Panicker => panic!("Function `store_key` shouldn't have been executed"), + Responder::Okayer => Ok(()), + Responder::Errorer(e) => Err(e), + } + } + + fn verify( + _verification_key_identifier: VerificationKeyIdentifier, + _proof: Vec, + _public_input: Vec, + _system: ProvingSystem, + ) -> Result { + match VERIFY_RESPONDER { + Responder::Panicker => panic!("Function `verify` shouldn't have been executed"), + Responder::Okayer => Ok(()), + Responder::Errorer(e) => Err(e), + } + } +} diff --git a/bin/runtime/src/chain_extension/tests/mod.rs b/bin/runtime/src/chain_extension/tests/mod.rs new file mode 100644 index 0000000000..2562d7f68c --- /dev/null +++ b/bin/runtime/src/chain_extension/tests/mod.rs @@ -0,0 +1,195 @@ +use std::sync::mpsc::Receiver; + +use environment::{CorruptedMode, MockedEnvironment, StandardMode, StoreKeyMode, VerifyMode}; + +use super::*; +use crate::chain_extension::tests::executor::{ + Panicker, StoreKeyErrorer, StoreKeyOkayer, VerifyErrorer, VerifyOkayer, +}; + +mod environment; +mod executor; + +/// In order to compute final fee (after all adjustments) sometimes we will have to subtract +/// weights. +type RevertibleWeight = i64; + +const IDENTIFIER: VerificationKeyIdentifier = [1, 7, 2, 9]; +const VK: [u8; 2] = [4, 1]; +const PROOF: [u8; 20] = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4]; +const INPUT: [u8; 11] = [0, 5, 7, 7, 2, 1, 5, 6, 6, 4, 9]; +const SYSTEM: ProvingSystem = ProvingSystem::Groth16; + +/// Returns encoded arguments to `store_key`. +fn store_key_args() -> Vec { + StoreKeyArgs { + identifier: IDENTIFIER, + key: VK.to_vec(), + } + .encode() +} + +/// Returns encoded arguments to `verify`. +fn verify_args() -> Vec { + VerifyArgs { + identifier: IDENTIFIER, + proof: PROOF.to_vec(), + input: INPUT.to_vec(), + system: SYSTEM, + } + .encode() +} + +/// Fetches all charges and computes the final fee. +fn charged(charging_listener: Receiver) -> RevertibleWeight { + charging_listener.into_iter().sum() +} + +#[test] +fn extension_is_enabled() { + assert!(SnarcosChainExtension::enabled()) +} + +#[test] +#[allow(non_snake_case)] +fn store_key__charges_before_reading() { + let (env, charging_listener) = MockedEnvironment::::new(41, None); + let key_length = env.approx_key_len(); + + let result = SnarcosChainExtension::snarcos_store_key::<_, Panicker>(env); + + assert!(matches!(result, Err(_))); + assert_eq!( + charged(charging_listener), + weight_of_store_key(key_length) as RevertibleWeight + ); +} + +#[test] +#[allow(non_snake_case)] +fn store_key__too_much_to_read() { + let (env, charging_listener) = MockedEnvironment::::new( + ByteCount::MAX, + Some(Box::new(|| panic!("Shouldn't read anything at all"))), + ); + + let result = SnarcosChainExtension::snarcos_store_key::<_, Panicker>(env); + + assert!(matches!( + result, + Ok(RetVal::Converging(SNARCOS_STORE_KEY_TOO_LONG_KEY)) + )); + assert_eq!(charged(charging_listener), 0); +} + +fn simulate_store_key(expected_ret_val: u32) { + let (env, charging_listener) = + MockedEnvironment::::new(store_key_args()); + + let result = SnarcosChainExtension::snarcos_store_key::<_, Exc>(env); + + assert!(matches!(result, Ok(RetVal::Converging(ret_val)) if ret_val == expected_ret_val)); + assert_eq!( + charged(charging_listener), + weight_of_store_key(VK.len() as ByteCount) as RevertibleWeight + ); +} + +#[test] +#[allow(non_snake_case)] +fn store_key__pallet_says_too_long_vk() { + simulate_store_key::>( + SNARCOS_STORE_KEY_TOO_LONG_KEY, + ) +} + +#[test] +#[allow(non_snake_case)] +fn store_key__pallet_says_identifier_in_use() { + simulate_store_key::>( + SNARCOS_STORE_KEY_IDENTIFIER_IN_USE, + ) +} + +#[test] +#[allow(non_snake_case)] +fn store_key__positive_scenario() { + simulate_store_key::(SNARCOS_STORE_KEY_OK) +} + +#[test] +#[allow(non_snake_case)] +fn verify__charges_before_reading() { + let (env, charging_listener) = MockedEnvironment::::new(41, None); + + let result = SnarcosChainExtension::snarcos_verify::<_, Panicker>(env); + + assert!(matches!(result, Err(_))); + assert_eq!( + charged(charging_listener), + weight_of_verify(None) as RevertibleWeight + ); +} + +fn simulate_verify(expected_ret_val: u32) { + let (env, charging_listener) = + MockedEnvironment::::new(verify_args()); + + let result = SnarcosChainExtension::snarcos_verify::<_, Exc>(env); + + assert!(matches!(result, Ok(RetVal::Converging(ret_val)) if ret_val == expected_ret_val)); + assert_eq!( + charged(charging_listener), + weight_of_verify(Some(SYSTEM)) as RevertibleWeight + ); +} + +#[test] +#[allow(non_snake_case)] +fn verify__pallet_says_proof_deserialization_failed() { + simulate_verify::>( + SNARCOS_VERIFY_DESERIALIZING_PROOF_FAIL, + ) +} + +#[test] +#[allow(non_snake_case)] +fn verify__pallet_says_input_deserialization_failed() { + simulate_verify::>( + SNARCOS_VERIFY_DESERIALIZING_INPUT_FAIL, + ) +} + +#[test] +#[allow(non_snake_case)] +fn verify__pallet_says_no_such_vk() { + simulate_verify::>( + SNARCOS_VERIFY_UNKNOWN_IDENTIFIER, + ) +} + +#[test] +#[allow(non_snake_case)] +fn verify__pallet_says_vk_deserialization_failed() { + simulate_verify::>( + SNARCOS_VERIFY_DESERIALIZING_KEY_FAIL, + ) +} + +#[test] +#[allow(non_snake_case)] +fn verify__pallet_says_verification_failed() { + simulate_verify::>(SNARCOS_VERIFY_VERIFICATION_FAIL) +} + +#[test] +#[allow(non_snake_case)] +fn verify__pallet_says_incorrect_proof() { + simulate_verify::>(SNARCOS_VERIFY_INCORRECT_PROOF) +} + +#[test] +#[allow(non_snake_case)] +fn verify__positive_scenario() { + simulate_verify::(SNARCOS_VERIFY_OK) +} diff --git a/bin/runtime/src/lib.rs b/bin/runtime/src/lib.rs index 7b6f2a0777..d06553fb57 100644 --- a/bin/runtime/src/lib.rs +++ b/bin/runtime/src/lib.rs @@ -1,6 +1,10 @@ #![cfg_attr(not(feature = "std"), no_std)] // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] +// For *TESTING PURPOSES ONLY* we use magnificent additional const generics. +#![cfg_attr(test, allow(incomplete_features))] +#![cfg_attr(test, feature(adt_const_params))] +#![cfg_attr(test, feature(generic_const_exprs))] // Make the WASM binary available. #[cfg(feature = "std")] diff --git a/pallets/snarcos/src/lib.rs b/pallets/snarcos/src/lib.rs index 7f441edbf0..083a6c6baf 100644 --- a/pallets/snarcos/src/lib.rs +++ b/pallets/snarcos/src/lib.rs @@ -38,6 +38,7 @@ pub mod pallet { } #[pallet::error] + #[derive(Clone, Eq, PartialEq)] pub enum Error { /// This verification key identifier is already taken. IdentifierAlreadyInUse,