use std::{
collections::{BTreeMap, BTreeSet},
fmt::Debug,
};
use cbor::Encode as _;
use impl_trait_for_tuples::impl_for_tuples;
use crate::{
context::Context,
dispatcher, error,
error::Error as _,
event, modules,
modules::core::types::{MethodHandlerInfo, ModuleInfo},
state::CurrentState,
storage,
storage::Prefix,
types::{
address::Address,
message::MessageResult,
transaction::{self, AuthInfo, Call, Transaction, UnverifiedTransaction},
},
};
pub enum DispatchResult<B, R> {
Handled(R),
Unhandled(B),
}
impl<B, R> DispatchResult<B, R> {
pub fn ok_or<E>(self, err: E) -> Result<R, E> {
match self {
DispatchResult::Handled(result) => Ok(result),
DispatchResult::Unhandled(_) => Err(err),
}
}
pub fn ok_or_else<E, F: FnOnce() -> E>(self, errf: F) -> Result<R, E> {
match self {
DispatchResult::Handled(result) => Ok(result),
DispatchResult::Unhandled(_) => Err(errf()),
}
}
}
#[derive(Debug)]
pub enum CallResult {
Ok(cbor::Value),
Failed {
module: String,
code: u32,
message: String,
},
Aborted(dispatcher::Error),
}
impl CallResult {
pub fn is_success(&self) -> bool {
matches!(self, CallResult::Ok(_))
}
#[cfg(any(test, feature = "test"))]
pub fn unwrap(self) -> cbor::Value {
match self {
Self::Ok(v) => v,
Self::Failed {
module,
code,
message,
} => panic!("{module} reported failure with code {code}: {message}"),
Self::Aborted(e) => panic!("tx aborted with error: {e}"),
}
}
#[cfg(any(test, feature = "test"))]
pub fn unwrap_failed(self) -> (String, u32) {
match self {
Self::Ok(_) => panic!("call result indicates success"),
Self::Failed { module, code, .. } => (module, code),
Self::Aborted(e) => panic!("tx aborted with error: {e}"),
}
}
}
impl From<CallResult> for transaction::CallResult {
fn from(v: CallResult) -> Self {
match v {
CallResult::Ok(data) => Self::Ok(data),
CallResult::Failed {
module,
code,
message,
} => Self::Failed {
module,
code,
message,
},
CallResult::Aborted(err) => Self::Failed {
module: err.module_name().to_string(),
code: err.code(),
message: err.to_string(),
},
}
}
}
pub fn dispatch_call<C, B, R, E, F>(
ctx: &C,
body: cbor::Value,
f: F,
) -> DispatchResult<cbor::Value, CallResult>
where
C: Context,
B: cbor::Decode,
R: cbor::Encode,
E: error::Error,
F: FnOnce(&C, B) -> Result<R, E>,
{
DispatchResult::Handled((|| {
let args = match cbor::from_value(body)
.map_err(|err| modules::core::Error::InvalidArgument(err.into()))
{
Ok(args) => args,
Err(err) => return err.into_call_result(),
};
match f(ctx, args) {
Ok(value) => CallResult::Ok(cbor::to_value(value)),
Err(err) => err.into_call_result(),
}
})())
}
pub fn dispatch_query<C, B, R, E, F>(
ctx: &C,
body: cbor::Value,
f: F,
) -> DispatchResult<cbor::Value, Result<cbor::Value, error::RuntimeError>>
where
C: Context,
B: cbor::Decode,
R: cbor::Encode,
E: error::Error,
error::RuntimeError: From<E>,
F: FnOnce(&C, B) -> Result<R, E>,
{
DispatchResult::Handled((|| {
let args = cbor::from_value(body).map_err(|err| -> error::RuntimeError {
modules::core::Error::InvalidArgument(err.into()).into()
})?;
Ok(cbor::to_value(f(ctx, args)?))
})())
}
pub trait MethodHandler {
fn prefetch(
_prefixes: &mut BTreeSet<Prefix>,
_method: &str,
body: cbor::Value,
_auth_info: &AuthInfo,
) -> DispatchResult<cbor::Value, Result<(), error::RuntimeError>> {
DispatchResult::Unhandled(body)
}
fn dispatch_call<C: Context>(
_ctx: &C,
_method: &str,
body: cbor::Value,
) -> DispatchResult<cbor::Value, CallResult> {
DispatchResult::Unhandled(body)
}
fn dispatch_query<C: Context>(
_ctx: &C,
_method: &str,
args: cbor::Value,
) -> DispatchResult<cbor::Value, Result<cbor::Value, error::RuntimeError>> {
DispatchResult::Unhandled(args)
}
fn dispatch_message_result<C: Context>(
_ctx: &C,
_handler_name: &str,
result: MessageResult,
) -> DispatchResult<MessageResult, ()> {
DispatchResult::Unhandled(result)
}
fn supported_methods() -> Vec<MethodHandlerInfo> {
vec![]
}
fn is_expensive_query(_method: &str) -> bool {
false
}
fn is_allowed_private_km_query(_method: &str) -> bool {
false
}
fn is_allowed_interactive_call(_method: &str) -> bool {
false
}
}
#[impl_for_tuples(30)]
impl MethodHandler for Tuple {
fn prefetch(
prefixes: &mut BTreeSet<Prefix>,
method: &str,
body: cbor::Value,
auth_info: &AuthInfo,
) -> DispatchResult<cbor::Value, Result<(), error::RuntimeError>> {
for_tuples!( #(
let body = match Tuple::prefetch(prefixes, method, body, auth_info) {
DispatchResult::Handled(result) => return DispatchResult::Handled(result),
DispatchResult::Unhandled(body) => body,
};
)* );
DispatchResult::Unhandled(body)
}
fn dispatch_call<C: Context>(
ctx: &C,
method: &str,
body: cbor::Value,
) -> DispatchResult<cbor::Value, CallResult> {
for_tuples!( #(
let body = match Tuple::dispatch_call::<C>(ctx, method, body) {
DispatchResult::Handled(result) => return DispatchResult::Handled(result),
DispatchResult::Unhandled(body) => body,
};
)* );
DispatchResult::Unhandled(body)
}
fn dispatch_query<C: Context>(
ctx: &C,
method: &str,
args: cbor::Value,
) -> DispatchResult<cbor::Value, Result<cbor::Value, error::RuntimeError>> {
for_tuples!( #(
let args = match Tuple::dispatch_query::<C>(ctx, method, args) {
DispatchResult::Handled(result) => return DispatchResult::Handled(result),
DispatchResult::Unhandled(args) => args,
};
)* );
DispatchResult::Unhandled(args)
}
fn dispatch_message_result<C: Context>(
ctx: &C,
handler_name: &str,
result: MessageResult,
) -> DispatchResult<MessageResult, ()> {
for_tuples!( #(
let result = match Tuple::dispatch_message_result::<C>(ctx, handler_name, result) {
DispatchResult::Handled(result) => return DispatchResult::Handled(result),
DispatchResult::Unhandled(result) => result,
};
)* );
DispatchResult::Unhandled(result)
}
fn is_expensive_query(method: &str) -> bool {
for_tuples!( #(
if Tuple::is_expensive_query(method) {
return true;
}
)* );
false
}
fn is_allowed_private_km_query(method: &str) -> bool {
for_tuples!( #(
if Tuple::is_allowed_private_km_query(method) {
return true;
}
)* );
false
}
fn is_allowed_interactive_call(method: &str) -> bool {
for_tuples!( #(
if Tuple::is_allowed_interactive_call(method) {
return true;
}
)* );
false
}
}
#[derive(Clone, Debug)]
pub enum AuthDecision {
Continue,
Stop,
}
pub trait TransactionHandler {
fn approve_raw_tx<C: Context>(_ctx: &C, _tx: &[u8]) -> Result<(), modules::core::Error> {
Ok(())
}
fn approve_unverified_tx<C: Context>(
_ctx: &C,
_utx: &UnverifiedTransaction,
) -> Result<(), modules::core::Error> {
Ok(())
}
fn decode_tx<C: Context>(
_ctx: &C,
_scheme: &str,
_body: &[u8],
) -> Result<Option<Transaction>, modules::core::Error> {
Ok(None)
}
fn authenticate_tx<C: Context>(
_ctx: &C,
_tx: &Transaction,
) -> Result<AuthDecision, modules::core::Error> {
Ok(AuthDecision::Continue)
}
fn before_handle_call<C: Context>(_ctx: &C, _call: &Call) -> Result<(), modules::core::Error> {
Ok(())
}
fn before_authorized_call_dispatch<C: Context>(
_ctx: &C,
_call: &Call,
) -> Result<(), modules::core::Error> {
Ok(())
}
fn after_handle_call<C: Context>(
_ctx: &C,
result: CallResult,
) -> Result<CallResult, modules::core::Error> {
Ok(result)
}
fn after_dispatch_tx<C: Context>(_ctx: &C, _tx_auth_info: &AuthInfo, _result: &CallResult) {
}
}
#[impl_for_tuples(30)]
impl TransactionHandler for Tuple {
fn approve_raw_tx<C: Context>(ctx: &C, tx: &[u8]) -> Result<(), modules::core::Error> {
for_tuples!( #( Tuple::approve_raw_tx(ctx, tx)?; )* );
Ok(())
}
fn approve_unverified_tx<C: Context>(
ctx: &C,
utx: &UnverifiedTransaction,
) -> Result<(), modules::core::Error> {
for_tuples!( #( Tuple::approve_unverified_tx(ctx, utx)?; )* );
Ok(())
}
fn decode_tx<C: Context>(
ctx: &C,
scheme: &str,
body: &[u8],
) -> Result<Option<Transaction>, modules::core::Error> {
for_tuples!( #(
let decoded = Tuple::decode_tx(ctx, scheme, body)?;
if (decoded.is_some()) {
return Ok(decoded);
}
)* );
Ok(None)
}
fn authenticate_tx<C: Context>(
ctx: &C,
tx: &Transaction,
) -> Result<AuthDecision, modules::core::Error> {
for_tuples!( #(
match Tuple::authenticate_tx(ctx, tx)? {
AuthDecision::Stop => return Ok(AuthDecision::Stop),
AuthDecision::Continue => {},
}
)* );
Ok(AuthDecision::Continue)
}
fn before_handle_call<C: Context>(ctx: &C, call: &Call) -> Result<(), modules::core::Error> {
for_tuples!( #( Tuple::before_handle_call(ctx, call)?; )* );
Ok(())
}
fn before_authorized_call_dispatch<C: Context>(
ctx: &C,
call: &Call,
) -> Result<(), modules::core::Error> {
for_tuples!( #( Tuple::before_authorized_call_dispatch(ctx, call)?; )* );
Ok(())
}
fn after_handle_call<C: Context>(
ctx: &C,
mut result: CallResult,
) -> Result<CallResult, modules::core::Error> {
for_tuples!( #(
result = Tuple::after_handle_call(ctx, result)?;
)* );
Ok(result)
}
fn after_dispatch_tx<C: Context>(ctx: &C, tx_auth_info: &AuthInfo, result: &CallResult) {
for_tuples!( #( Tuple::after_dispatch_tx(ctx, tx_auth_info, result); )* );
}
}
pub trait FeeProxyHandler {
fn resolve_payer<C: Context>(
ctx: &C,
tx: &Transaction,
) -> Result<Option<Address>, modules::core::Error>;
}
#[impl_for_tuples(30)]
impl FeeProxyHandler for Tuple {
fn resolve_payer<C: Context>(
ctx: &C,
tx: &Transaction,
) -> Result<Option<Address>, modules::core::Error> {
for_tuples!( #(
if let Some(payer) = Tuple::resolve_payer(ctx, tx)? {
return Ok(Some(payer));
}
)* );
Ok(None)
}
}
pub trait MigrationHandler {
type Genesis;
fn init_or_migrate<C: Context>(
_ctx: &C,
_meta: &mut modules::core::types::Metadata,
_genesis: Self::Genesis,
) -> bool {
false
}
}
#[allow(clippy::type_complexity)]
#[impl_for_tuples(30)]
impl MigrationHandler for Tuple {
for_tuples!( type Genesis = ( #( Tuple::Genesis ),* ); );
fn init_or_migrate<C: Context>(
ctx: &C,
meta: &mut modules::core::types::Metadata,
genesis: Self::Genesis,
) -> bool {
[for_tuples!( #( Tuple::init_or_migrate(ctx, meta, genesis.Tuple) ),* )]
.iter()
.any(|x| *x)
}
}
pub trait BlockHandler {
fn begin_block<C: Context>(_ctx: &C) {
}
fn end_block<C: Context>(_ctx: &C) {
}
}
#[impl_for_tuples(30)]
impl BlockHandler for Tuple {
fn begin_block<C: Context>(ctx: &C) {
for_tuples!( #( Tuple::begin_block(ctx); )* );
}
fn end_block<C: Context>(ctx: &C) {
for_tuples!( #( Tuple::end_block(ctx); )* );
}
}
pub trait InvariantHandler {
fn check_invariants<C: Context>(_ctx: &C) -> Result<(), modules::core::Error> {
Ok(())
}
}
#[impl_for_tuples(30)]
impl InvariantHandler for Tuple {
fn check_invariants<C: Context>(ctx: &C) -> Result<(), modules::core::Error> {
for_tuples!( #( Tuple::check_invariants(ctx)?; )* );
Ok(())
}
}
pub trait ModuleInfoHandler {
fn module_info<C: Context>(_ctx: &C) -> BTreeMap<String, ModuleInfo>;
}
impl<M: Module + MethodHandler> ModuleInfoHandler for M {
fn module_info<C: Context>(_ctx: &C) -> BTreeMap<String, ModuleInfo> {
let mut info = BTreeMap::new();
info.insert(
Self::NAME.to_string(),
ModuleInfo {
version: Self::VERSION,
params: Self::params().into_cbor_value(),
methods: Self::supported_methods(),
},
);
info
}
}
#[impl_for_tuples(30)]
impl ModuleInfoHandler for Tuple {
#[allow(clippy::let_and_return)]
fn module_info<C: Context>(ctx: &C) -> BTreeMap<String, ModuleInfo> {
let mut merged = BTreeMap::new();
for_tuples!( #(
merged.extend(Tuple::module_info(ctx));
)* );
merged
}
}
pub trait Module {
const NAME: &'static str;
const VERSION: u32 = 1;
type Error: error::Error + 'static;
type Event: event::Event + 'static;
type Parameters: Parameters + 'static;
fn params() -> Self::Parameters {
CurrentState::with_store(|store| {
let store = storage::PrefixStore::new(store, &Self::NAME);
let store = storage::TypedStore::new(store);
store.get(Self::Parameters::STORE_KEY).unwrap_or_default()
})
}
fn set_params(params: Self::Parameters) {
CurrentState::with_store(|store| {
let store = storage::PrefixStore::new(store, &Self::NAME);
let mut store = storage::TypedStore::new(store);
store.insert(Self::Parameters::STORE_KEY, params);
});
}
}
pub trait Parameters: Debug + Default + cbor::Encode + cbor::Decode {
type Error;
const STORE_KEY: &'static [u8] = &[0x00];
fn validate_basic(&self) -> Result<(), Self::Error> {
Ok(())
}
}
impl Parameters for () {
type Error = std::convert::Infallible;
}
#[cfg(test)]
mod test {
use super::*;
use crate::testing::mock;
struct TestAuthContinue;
struct TestAuthStop;
struct TestAuthFail;
impl super::TransactionHandler for TestAuthContinue {
fn authenticate_tx<C: Context>(
_ctx: &C,
_tx: &Transaction,
) -> Result<AuthDecision, modules::core::Error> {
Ok(AuthDecision::Continue)
}
}
impl super::TransactionHandler for TestAuthStop {
fn authenticate_tx<C: Context>(
_ctx: &C,
_tx: &Transaction,
) -> Result<AuthDecision, modules::core::Error> {
Ok(AuthDecision::Stop)
}
}
impl super::TransactionHandler for TestAuthFail {
fn authenticate_tx<C: Context>(
_ctx: &C,
_tx: &Transaction,
) -> Result<AuthDecision, modules::core::Error> {
Err(modules::core::Error::NotAuthenticated)
}
}
#[test]
fn test_authenticate_tx() {
let mut mock = mock::Mock::default();
let ctx = mock.create_ctx();
let tx = mock::transaction();
let result = TestAuthContinue::authenticate_tx(&ctx, &tx).unwrap();
assert!(matches!(result, AuthDecision::Continue));
let result = TestAuthStop::authenticate_tx(&ctx, &tx).unwrap();
assert!(matches!(result, AuthDecision::Stop));
let _ = TestAuthFail::authenticate_tx(&ctx, &tx).unwrap_err();
type Composed1 = (TestAuthContinue, TestAuthContinue, TestAuthContinue);
let result = Composed1::authenticate_tx(&ctx, &tx).unwrap();
assert!(matches!(result, AuthDecision::Continue));
type Composed2 = (TestAuthContinue, TestAuthStop, TestAuthContinue);
let result = Composed2::authenticate_tx(&ctx, &tx).unwrap();
assert!(matches!(result, AuthDecision::Stop));
type Composed3 = (TestAuthContinue, TestAuthStop, TestAuthFail);
let result = Composed3::authenticate_tx(&ctx, &tx).unwrap();
assert!(matches!(result, AuthDecision::Stop));
type Composed4 = (TestAuthFail, TestAuthStop, TestAuthContinue);
let _ = Composed4::authenticate_tx(&ctx, &tx).unwrap_err();
type Composed5 = (TestAuthContinue, TestAuthContinue, TestAuthFail);
let _ = Composed5::authenticate_tx(&ctx, &tx).unwrap_err();
}
}