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

runtime-sdk: Add support for (un)delegation receipts #1510

Merged
merged 2 commits into from Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 37 additions & 3 deletions runtime-sdk-macros/src/module_derive/method_handler.rs
Expand Up @@ -100,7 +100,29 @@ impl super::Deriver for DeriveMethodHandler {
};

let dispatch_call_impl = {
let (handler_names, handler_idents) = filter_by_kind(handlers, HandlerKind::Call);
let (handler_names, handler_fns): (Vec<_>, Vec<_>) = handlers
.iter()
.filter_map(|h| h.handler.as_ref())
.filter(|h| h.attrs.kind == HandlerKind::Call)
.map(|h| {
(h.attrs.rpc_name.clone(), {
let ident = &h.ident;

if h.attrs.is_internal {
quote! {
|ctx, body| {
if !ctx.is_internal() {
return Err(sdk::modules::core::Error::Forbidden.into());
}
Self::#ident(ctx, body)
}
}
} else {
quote! { Self::#ident }
}
})
})
.unzip();

if handler_names.is_empty() {
quote! {}
Expand All @@ -113,7 +135,7 @@ impl super::Deriver for DeriveMethodHandler {
) -> DispatchResult<cbor::Value, CallResult> {
match method {
#(
#handler_names => module::dispatch_call(ctx, body, Self::#handler_idents),
#handler_names => module::dispatch_call(ctx, body, #handler_fns),
)*
_ => DispatchResult::Unhandled(body),
}
Expand Down Expand Up @@ -347,6 +369,8 @@ struct MethodHandlerAttr {
allow_private_km: bool,
/// Whether this handler is tagged as allowing interactive calls. Only applies to call handlers.
allow_interactive: bool,
/// Whether this handler is tagged as internal.
is_internal: bool,
}
impl syn::parse::Parse for MethodHandlerAttr {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
Expand All @@ -365,6 +389,7 @@ impl syn::parse::Parse for MethodHandlerAttr {
let mut is_expensive = false;
let mut allow_private_km = false;
let mut allow_interactive = false;
let mut is_internal = false;
while input.peek(syn::token::Comma) {
let _: syn::token::Comma = input.parse()?;
let tag: syn::Ident = input.parse()?;
Expand Down Expand Up @@ -393,10 +418,18 @@ impl syn::parse::Parse for MethodHandlerAttr {
));
}
allow_interactive = true;
} else if tag == "internal" {
if kind != HandlerKind::Call {
return Err(syn::Error::new(
tag.span(),
"`internal` tag is only allowed on `call` handlers",
));
}
is_internal = true;
} else {
return Err(syn::Error::new(
tag.span(),
"invalid handler tag; supported: `expensive`, `allow_private_km`, `allow_interactive`",
"invalid handler tag; supported: `expensive`, `allow_private_km`, `allow_interactive`, `internal`",
));
}
}
Expand All @@ -410,6 +443,7 @@ impl syn::parse::Parse for MethodHandlerAttr {
is_expensive,
allow_private_km,
allow_interactive,
is_internal,
})
}
}
Expand Down
27 changes: 27 additions & 0 deletions runtime-sdk-macros/src/module_derive/mod.rs
Expand Up @@ -159,6 +159,8 @@ mod tests {
fn my_call(foo2: Bar2) -> Baz2 {}
#[handler(call = "my_module.MyOtherCall")]
fn my_other_call(foo3: Bar3) -> Baz3 {}
#[handler(call = "my_module.MyInternalCall", internal)]
fn my_internal_call(foo4: Bar4) -> Baz4 {}
}
);

Expand All @@ -184,6 +186,7 @@ mod tests {
Self::prefetch_for_my_call(&mut add_prefix, body, auth_info),
),
"my_module.MyOtherCall" => module::DispatchResult::Handled(Ok(())),
"my_module.MyInternalCall" => module::DispatchResult::Handled(Ok(())),
_ => module::DispatchResult::Unhandled(body),
}
}
Expand All @@ -197,6 +200,12 @@ mod tests {
"my_module.MyOtherCall" => {
module::dispatch_call(ctx, body, Self::my_other_call)
}
"my_module.MyInternalCall" => module::dispatch_call(ctx, body, |ctx, body| {
if !ctx.is_internal() {
return Err(sdk::modules::core::Error::Forbidden.into());
}
Self::my_internal_call(ctx, body)
}),
_ => DispatchResult::Unhandled(body),
}
}
Expand All @@ -223,6 +232,10 @@ mod tests {
kind: core_types::MethodHandlerKind::Call,
name: "my_module.MyOtherCall".to_string(),
},
core_types::MethodHandlerInfo {
kind: core_types::MethodHandlerKind::Call,
name: "my_module.MyInternalCall".to_string(),
},
]
}
}
Expand All @@ -237,6 +250,8 @@ mod tests {
fn my_call(foo2: Bar2) -> Baz2 {}
#[handler(call = "my_module.MyOtherCall")]
fn my_other_call(foo3: Bar3) -> Baz3 {}
#[handler(call = "my_module.MyInternalCall", internal)]
fn my_internal_call(foo4: Bar4) -> Baz4 {}
}
};
)
Expand Down Expand Up @@ -537,6 +552,18 @@ mod tests {
super::derive_module(input);
}

#[test]
#[should_panic(expected = "only allowed on `call` handlers")]
fn generate_method_handler_malformed_internal_noncall() {
let input: syn::ItemImpl = syn::parse_quote!(
impl<C: Cfg> MyModule<C> {
#[handler(query = "foo", internal)]
fn my_method_call() -> () {}
}
);
super::derive_module(input);
}

#[test]
#[should_panic]
fn generate_method_handler_malformed_multiple_metas() {
Expand Down
15 changes: 15 additions & 0 deletions runtime-sdk/src/modules/consensus/mod.rs
Expand Up @@ -45,13 +45,20 @@ const MODULE_NAME: &str = "consensus";
pub struct Parameters {
pub consensus_denomination: token::Denomination,
pub consensus_scaling_factor: u64,

/// Minimum amount that is allowed to be delegated. This should be greater than or equal to what
/// is configured in the consensus layer as the consensus layer will do its own checks.
///
/// The amount is in consensus units.
pub min_delegate_amount: u128,
}

impl Default for Parameters {
fn default() -> Self {
Self {
consensus_denomination: token::Denomination::from_str("TEST").unwrap(),
consensus_scaling_factor: 1,
min_delegate_amount: 0,
}
}
}
Expand Down Expand Up @@ -119,6 +126,10 @@ pub enum Error {
#[sdk_error(code = 5)]
AmountNotRepresentable,

#[error("amount is lower than the minimum delegation amount")]
#[sdk_error(code = 6)]
UnderMinDelegationAmount,

#[error("history: {0}")]
#[sdk_error(transparent)]
History(#[from] history::Error),
Expand Down Expand Up @@ -278,6 +289,10 @@ impl API for Module {
Self::ensure_consensus_denomination(ctx, amount.denomination())?;
let amount = Self::amount_to_consensus(ctx, amount.amount())?;

if amount < Self::params().min_delegate_amount {
return Err(Error::UnderMinDelegationAmount);
}

ctx.emit_message(
Message::Staking(Versioned::new(
0,
Expand Down
42 changes: 41 additions & 1 deletion runtime-sdk/src/modules/consensus/test.rs
Expand Up @@ -19,7 +19,7 @@ use crate::{
},
};

use super::{Genesis, Parameters, API as _};
use super::{Error, Genesis, Parameters, API as _};

#[test]
fn test_api_transfer_invalid_denomination() {
Expand Down Expand Up @@ -276,6 +276,43 @@ fn test_api_escrow() {
});
}

#[test]
fn test_api_escrow_min_delegate_amount() {
let mut mock = mock::Mock::default();
let mut ctx = mock.create_ctx();

Consensus::set_params(Parameters {
min_delegate_amount: 10,
..Default::default()
});

ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| {
let hook_name = "test_event_handler";
let amount = BaseUnits::new(5, Denomination::from_str("TEST").unwrap());
let result = Consensus::escrow(
&mut tx_ctx,
keys::alice::address(),
&amount,
MessageEventHookInvocation::new(hook_name.to_string(), 0),
);

assert!(matches!(result, Err(Error::UnderMinDelegationAmount)));
});

ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| {
let hook_name = "test_event_handler";
let amount = BaseUnits::new(15, Denomination::from_str("TEST").unwrap());
let result = Consensus::escrow(
&mut tx_ctx,
keys::alice::address(),
&amount,
MessageEventHookInvocation::new(hook_name.to_string(), 0),
);

assert!(result.is_ok());
});
}

#[test]
fn test_api_escrow_scaling() {
let mut mock = mock::Mock::default();
Expand Down Expand Up @@ -430,6 +467,7 @@ fn test_query_parameters() {
let params = Parameters {
consensus_denomination: Denomination::NATIVE,
consensus_scaling_factor: 1_000,
min_delegate_amount: 10,
};
Consensus::set_params(params.clone());

Expand All @@ -445,6 +483,7 @@ fn test_init_bad_scaling_factor_1() {
consensus_denomination: Denomination::NATIVE,
// Zero scaling factor is invalid.
consensus_scaling_factor: 0,
min_delegate_amount: 0,
},
});
}
Expand All @@ -457,6 +496,7 @@ fn test_init_bad_scaling_factor_2() {
consensus_denomination: Denomination::NATIVE,
// Scaling factor that is not a power of 10 is invalid.
consensus_scaling_factor: 1230,
min_delegate_amount: 0,
},
});
}