Skip to content

Commit

Permalink
Implement 'transactional' annotation for runtime functions. (parityte…
Browse files Browse the repository at this point in the history
…ch#6763)

* Implement 'transactional' annotation for runtime functions.

* Allow function attributes for dispatchable calls in decl_module.

* decl_module docs: add transactional function example.

* decl_module docs: add function attributes notes.

* Fix license header.
  • Loading branch information
shaunxw committed Aug 12, 2020
1 parent d4efdf0 commit d7979d0
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 7 deletions.
26 changes: 26 additions & 0 deletions frame/support/procedural/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

mod storage;
mod construct_runtime;
mod transactional;

use proc_macro::TokenStream;

Expand Down Expand Up @@ -289,3 +290,28 @@ pub fn decl_storage(input: TokenStream) -> TokenStream {
pub fn construct_runtime(input: TokenStream) -> TokenStream {
construct_runtime::construct_runtime(input)
}

/// Execute the annotated function in a new storage transaction.
///
/// The return type of the annotated function must be `Result`. All changes to storage performed
/// by the annotated function are discarded if it returns `Err`, or committed if `Ok`.
///
/// #Example
///
/// ```nocompile
/// #[transactional]
/// fn value_commits(v: u32) -> result::Result<u32, &'static str> {
/// Value::set(v);
/// Ok(v)
/// }
///
/// #[transactional]
/// fn value_rollbacks(v: u32) -> result::Result<u32, &'static str> {
/// Value::set(v);
/// Err("nah")
/// }
/// ```
#[proc_macro_attribute]
pub fn transactional(attr: TokenStream, input: TokenStream) -> TokenStream {
transactional::transactional(attr, input)
}
40 changes: 40 additions & 0 deletions frame/support/procedural/src/transactional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// This file is part of Substrate.

// Copyright (C) 2020 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

pub fn transactional(_attr: TokenStream, input: TokenStream) -> TokenStream {
let ItemFn { attrs, vis, sig, block } = parse_macro_input!(input as ItemFn);

let output = quote! {
#(#attrs)*
#vis #sig {
use frame_support::storage::{with_transaction, TransactionOutcome};
with_transaction(|| {
let r = #block;
if r.is_ok() {
TransactionOutcome::Commit(r)
} else {
TransactionOutcome::Rollback(r)
}
})
}
};
output.into()
}
46 changes: 42 additions & 4 deletions frame/support/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,28 @@ impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {}
/// # fn main() {}
/// ```
///
/// ### Transactional Function Example
///
/// Transactional function discards all changes to storage if it returns `Err`, or commits if
/// `Ok`, via the #\[transactional\] attribute. Note the attribute must be after #\[weight\].
///
/// ```
/// # #[macro_use]
/// # extern crate frame_support;
/// # use frame_support::transactional;
/// # use frame_system::Trait;
/// decl_module! {
/// pub struct Module<T: Trait> for enum Call where origin: T::Origin {
/// #[weight = 0]
/// #[transactional]
/// fn my_short_function(origin) {
/// // Your implementation
/// }
/// }
/// }
/// # fn main() {}
/// ```
///
/// ### Privileged Function Example
///
/// A privileged function checks that the origin of the call is `ROOT`.
Expand All @@ -189,6 +211,14 @@ impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug {}
/// # fn main() {}
/// ```
///
/// ### Attributes on Functions
///
/// Attributes on functions are supported, but must be in the order of:
/// 1. Optional #\[doc\] attribute.
/// 2. #\[weight\] attribute.
/// 3. Optional function attributes, for instance #\[transactional\]. Those function attributes will be written
/// only on the dispatchable functions implemented on `Module`, not on the `Call` enum variant.
///
/// ## Multiple Module Instances Example
///
/// A Substrate module can be built such that multiple instances of the same module can be used within a single
Expand Down Expand Up @@ -1015,6 +1045,7 @@ macro_rules! decl_module {
[ $( $dispatchables:tt )* ]
$(#[doc = $doc_attr:tt])*
#[weight = $weight:expr]
$(#[$fn_attr:meta])*
$fn_vis:vis fn $fn_name:ident(
$origin:ident $( , $(#[$codec_attr:ident])* $param_name:ident : $param:ty )* $(,)?
) $( -> $result:ty )* { $( $impl:tt )* }
Expand All @@ -1039,6 +1070,7 @@ macro_rules! decl_module {
$( $dispatchables )*
$(#[doc = $doc_attr])*
#[weight = $weight]
$(#[$fn_attr])*
$fn_vis fn $fn_name(
$origin $( , $(#[$codec_attr])* $param_name : $param )*
) $( -> $result )* { $( $impl )* }
Expand Down Expand Up @@ -1066,6 +1098,7 @@ macro_rules! decl_module {
{ $( $integrity_test:tt )* }
[ $( $dispatchables:tt )* ]
$(#[doc = $doc_attr:tt])*
$(#[$fn_attr:meta])*
$fn_vis:vis fn $fn_name:ident(
$from:ident $( , $( #[$codec_attr:ident] )* $param_name:ident : $param:ty )* $(,)?
) $( -> $result:ty )* { $( $impl:tt )* }
Expand Down Expand Up @@ -1094,6 +1127,7 @@ macro_rules! decl_module {
[ $( $dispatchables:tt )* ]
$(#[doc = $doc_attr:tt])*
$(#[weight = $weight:expr])?
$(#[$fn_attr:meta])*
$fn_vis:vis fn $fn_name:ident(
$origin:ident : T::Origin $( , $( #[$codec_attr:ident] )* $param_name:ident : $param:ty )* $(,)?
) $( -> $result:ty )* { $( $impl:tt )* }
Expand Down Expand Up @@ -1121,6 +1155,7 @@ macro_rules! decl_module {
[ $( $dispatchables:tt )* ]
$(#[doc = $doc_attr:tt])*
$(#[weight = $weight:expr])?
$(#[$fn_attr:meta])*
$fn_vis:vis fn $fn_name:ident(
origin : $origin:ty $( , $( #[$codec_attr:ident] )* $param_name:ident : $param:ty )* $(,)?
) $( -> $result:ty )* { $( $impl:tt )* }
Expand Down Expand Up @@ -1148,6 +1183,7 @@ macro_rules! decl_module {
[ $( $dispatchables:tt )* ]
$(#[doc = $doc_attr:tt])*
$(#[weight = $weight:expr])?
$(#[$fn_attr:meta])*
$fn_vis:vis fn $fn_name:ident(
$( $(#[$codec_attr:ident])* $param_name:ident : $param:ty ),* $(,)?
) $( -> $result:ty )* { $( $impl:tt )* }
Expand Down Expand Up @@ -1410,13 +1446,13 @@ macro_rules! decl_module {
$origin_ty:ty;
$error_type:ty;
$ignore:ident;
$(#[doc = $doc_attr:tt])*
$(#[$fn_attr:meta])*
$vis:vis fn $name:ident (
$origin:ident $(, $param:ident : $param_ty:ty )*
) { $( $impl:tt )* }
) => {
$(#[doc = $doc_attr])*
#[allow(unreachable_code)]
$(#[$fn_attr])*
$vis fn $name(
$origin: $origin_ty $(, $param: $param_ty )*
) -> $crate::dispatch::DispatchResult {
Expand All @@ -1432,12 +1468,12 @@ macro_rules! decl_module {
$origin_ty:ty;
$error_type:ty;
$ignore:ident;
$(#[doc = $doc_attr:tt])*
$(#[$fn_attr:meta])*
$vis:vis fn $name:ident (
$origin:ident $(, $param:ident : $param_ty:ty )*
) -> $result:ty { $( $impl:tt )* }
) => {
$(#[doc = $doc_attr])*
$(#[$fn_attr])*
$vis fn $name($origin: $origin_ty $(, $param: $param_ty )* ) -> $result {
$crate::sp_tracing::enter_span!(stringify!($name));
$( $impl )*
Expand Down Expand Up @@ -1569,6 +1605,7 @@ macro_rules! decl_module {
$(
$(#[doc = $doc_attr:tt])*
#[weight = $weight:expr]
$(#[$fn_attr:meta])*
$fn_vis:vis fn $fn_name:ident(
$from:ident $( , $(#[$codec_attr:ident])* $param_name:ident : $param:ty)*
) $( -> $result:ty )* { $( $impl:tt )* }
Expand Down Expand Up @@ -1654,6 +1691,7 @@ macro_rules! decl_module {
$(#[doc = $doc_attr])*
///
/// NOTE: Calling this function will bypass origin filters.
$(#[$fn_attr])*
$fn_vis fn $fn_name (
$from $(, $param_name : $param )*
) $( -> $result )* { $( $impl )* }
Expand Down
2 changes: 1 addition & 1 deletion frame/support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ macro_rules! ord_parameter_types {
}

#[doc(inline)]
pub use frame_support_procedural::{decl_storage, construct_runtime};
pub use frame_support_procedural::{decl_storage, construct_runtime, transactional};

/// Return Err of the expression: `return Err($expression);`.
///
Expand Down
57 changes: 55 additions & 2 deletions frame/support/test/tests/storage_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,32 @@

use codec::{Encode, Decode, EncodeLike};
use frame_support::{
StorageMap, StorageValue, storage::{with_transaction, TransactionOutcome::*},
assert_ok, assert_noop, dispatch::{DispatchError, DispatchResult}, transactional, StorageMap, StorageValue,
storage::{with_transaction, TransactionOutcome::*},
};
use sp_io::TestExternalities;
use sp_std::result;

pub trait Trait {
type Origin;
type BlockNumber: Encode + Decode + EncodeLike + Default + Clone;
}

frame_support::decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {}
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
#[weight = 0]
#[transactional]
fn value_commits(_origin, v: u32) {
Value::set(v);
}

#[weight = 0]
#[transactional]
fn value_rollbacks(_origin, v: u32) -> DispatchResult {
Value::set(v);
Err(DispatchError::Other("nah"))
}
}
}

frame_support::decl_storage!{
Expand All @@ -37,6 +52,11 @@ frame_support::decl_storage!{
}
}

struct Runtime;
impl Trait for Runtime {
type Origin = u32;
type BlockNumber = u32;
}

#[test]
fn storage_transaction_basic_commit() {
Expand Down Expand Up @@ -157,3 +177,36 @@ fn storage_transaction_commit_then_rollback() {
assert_eq!(Map::get("val3"), 0);
});
}

#[test]
fn transactional_annotation() {
#[transactional]
fn value_commits(v: u32) -> result::Result<u32, &'static str> {
Value::set(v);
Ok(v)
}

#[transactional]
fn value_rollbacks(v: u32) -> result::Result<u32, &'static str> {
Value::set(v);
Err("nah")
}

TestExternalities::default().execute_with(|| {
assert_ok!(value_commits(2), 2);
assert_eq!(Value::get(), 2);

assert_noop!(value_rollbacks(3), "nah");
});
}

#[test]
fn transactional_annotation_in_decl_module() {
TestExternalities::default().execute_with(|| {
let origin = 0;
assert_ok!(<Module<Runtime>>::value_commits(origin, 2));
assert_eq!(Value::get(), 2);

assert_noop!(<Module<Runtime>>::value_rollbacks(origin, 3), "nah");
});
}

0 comments on commit d7979d0

Please sign in to comment.