Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,24 @@ impl Counter {

The derived implementation of `AccessControllable` provides more methods that are documented in the [definition of the trait](/near-plugins/src/access_controllable.rs). More usage patterns are explained in [examples](/examples/access-controllable-examples/) and in [integration tests](/near-plugins/tests/access_controllable.rs).

## Internal Architecture

Each plugin's functionality is described by a trait defined in `near-plugins/src/<plugin_name>.rs`. The trait's methods will be available on contracts that use the corresponding plugin, whereas the implementation of the trait is provided by procedural macros.

The code that is generated for a trait implementation is based on `near-plugins-derive/src/<plugin_name.rs>`. To inspect the code generated for your particular smart contract, [`cargo-expand`](https://github.com/dtolnay/cargo-expand) can be helpful.

## Testing

Tests should verify that once the macros provided by this crate are expanded, the contract they are used in has the intended functionality. Integration tests are utilized for that purpose:

- A contract using the plugin is contained in `near-plugins/tests/contracts/<plugin_name>/`.
- This contract is used in `near-plugins/tests/<plugin_name>.rs` which:
- Compiles and deploys the contract on chain via [NEAR `workspaces`](https://docs.rs/workspaces/0.7.0/workspaces/).
- Sends transactions to the deployed contract to verify plugin functionality.

> **Note**
> Currently some plugins are still tested by unit tests in `near-plugins/src/<plugin_name>.rs`, not by integration tests. Migrating these unit tests to integration tests as described above is WIP.

## Contributors Notes

Traits doesn't contain any implementation, even though some interfaces are self-contained enough to have it.
Expand Down
2 changes: 2 additions & 0 deletions near-plugins-derive/src/access_control_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const DEFAULT_SUPER_ADMIN_NAME: &str = "__SUPER_ADMIN";
const DEFAULT_BITFLAGS_TYPE_NAME: &str = "RoleFlags";
const DEFAULT_BOUNDCHECKER_TYPE_NAME: &str = "__AclBoundchecker";

/// Generates the token stream that implements `AccessControlRole`.
pub fn derive_access_control_role(input: TokenStream) -> TokenStream {
// This derive doesn't take attributes, so no need to use `darling`.
let cratename = cratename();
Expand Down Expand Up @@ -190,6 +191,7 @@ pub fn derive_access_control_role(input: TokenStream) -> TokenStream {
output.into()
}

/// Generates and identifier for the bitflag type that represents permissions.
pub fn new_bitflags_type_ident(span: Span) -> Ident {
Ident::new(DEFAULT_BITFLAGS_TYPE_NAME, span)
}
Expand Down
4 changes: 4 additions & 0 deletions near-plugins-derive/src/access_controllable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use quote::quote;
use syn::parse::Parser;
use syn::{parse_macro_input, AttributeArgs, ItemFn, ItemStruct};

/// Defines attributes for the `access_controllable` macro.
#[derive(Debug, FromMeta)]
pub struct MacroArgs {
#[darling(default)]
Expand All @@ -22,6 +23,7 @@ const DEFAULT_ACL_TYPE_NAME: &str = "__Acl";
const ERR_PARSE_BITFLAG: &str = "Value does not correspond to a permission";
const ERR_PARSE_ROLE: &str = "Value does not correspond to a role";

/// Generates the token stream that implements `AccessControllable`.
pub fn access_controllable(attrs: TokenStream, item: TokenStream) -> TokenStream {
let cratename = cratename();
let attr_args = parse_macro_input!(attrs as AttributeArgs);
Expand Down Expand Up @@ -539,11 +541,13 @@ fn inject_acl_field(
Ok(())
}

/// Defines attributes for the `access_control_any` macro.
#[derive(Debug, FromMeta)]
pub struct MacroArgsAny {
roles: darling::util::PathList,
}

/// Generates the token stream for the `access_control_any` macro.
pub fn access_control_any(attrs: TokenStream, item: TokenStream) -> TokenStream {
let attr_args = parse_macro_input!(attrs as AttributeArgs);
let cloned_item = item.clone();
Expand Down
1 change: 1 addition & 0 deletions near-plugins-derive/src/full_access_key_fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use proc_macro::{self, TokenStream};
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

/// Generates the token stream that implements `FullAccessKeyFallback`.
pub fn derive_fak_fallback(input: TokenStream) -> TokenStream {
let cratename = cratename();

Expand Down
10 changes: 10 additions & 0 deletions near-plugins-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,61 @@ mod pausable;
mod upgradable;
mod utils;

/// Defines the derive macro for `Ownable`.
#[proc_macro_derive(Ownable, attributes(ownable))]
pub fn derive_ownable(input: TokenStream) -> TokenStream {
ownable::derive_ownable(input)
}

/// Defines attribute macro `only`.
#[proc_macro_attribute]
pub fn only(attrs: TokenStream, item: TokenStream) -> TokenStream {
ownable::only(attrs, item)
}

/// Defines the derive macro for `Upgradable`.
#[proc_macro_derive(Upgradable, attributes(upgradable))]
pub fn derive_upgradable(input: TokenStream) -> TokenStream {
upgradable::derive_upgradable(input)
}

/// Defines the derive macro for `FullAccessKeyFallback`.
#[proc_macro_derive(FullAccessKeyFallback)]
pub fn derive_fak_fallback(input: TokenStream) -> TokenStream {
full_access_key_fallback::derive_fak_fallback(input)
}

/// Defines the derive macro for `Pausable`.
#[proc_macro_derive(Pausable, attributes(pausable))]
pub fn derive_pausable(input: TokenStream) -> TokenStream {
pausable::derive_pausable(input)
}

/// Defines the attribute macro `pause`.
#[proc_macro_attribute]
pub fn pause(attrs: TokenStream, item: TokenStream) -> TokenStream {
pausable::pause(attrs, item)
}

/// Defines the attribute macro `if_paused`.
#[proc_macro_attribute]
pub fn if_paused(attrs: TokenStream, item: TokenStream) -> TokenStream {
pausable::if_paused(attrs, item)
}

/// Defines the derive macro for `AccessControlRole`.
#[proc_macro_derive(AccessControlRole)]
pub fn derive_access_control_role(input: TokenStream) -> TokenStream {
access_control_role::derive_access_control_role(input)
}

/// Defines the attribute macro `access_control`.
#[proc_macro_attribute]
pub fn access_control(attrs: TokenStream, item: TokenStream) -> TokenStream {
access_controllable::access_controllable(attrs, item)
}

/// Defines the attribute macro `access_control_any`.
#[proc_macro_attribute]
pub fn access_control_any(attrs: TokenStream, item: TokenStream) -> TokenStream {
access_controllable::access_control_any(attrs, item)
Expand Down
2 changes: 2 additions & 0 deletions near-plugins-derive/src/ownable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct Opts {
owner_storage_key: Option<String>,
}

/// Generates the token stream that implements `Ownable`.
pub fn derive_ownable(input: TokenStream) -> TokenStream {
let cratename = cratename();

Expand Down Expand Up @@ -83,6 +84,7 @@ pub fn derive_ownable(input: TokenStream) -> TokenStream {
output.into()
}

/// Generates the token stream for the `only` macro.
pub fn only(attrs: TokenStream, item: TokenStream) -> TokenStream {
let input = parse::<ItemFn>(item.clone()).unwrap();
if is_near_bindgen_wrapped_or_marshall(&input) {
Expand Down
6 changes: 6 additions & 0 deletions near-plugins-derive/src/pausable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct Opts {
manager_roles: PathList,
}

/// Generates the token stream that implements `Pausable`.
pub fn derive_pausable(input: TokenStream) -> TokenStream {
let cratename = cratename();

Expand Down Expand Up @@ -103,13 +104,15 @@ pub fn derive_pausable(input: TokenStream) -> TokenStream {
output.into()
}

/// Defines sub-attributes for the `except` attribute.
#[derive(Default, FromMeta, Debug)]
#[darling(default)]
pub struct ExceptSubArgs {
/// Grantees of these roles are exempted and may always call the method.
roles: PathList,
}

/// Defines attributes for the `pause` macro.
#[derive(Debug, FromMeta)]
pub struct PauseArgs {
#[darling(default)]
Expand All @@ -118,6 +121,7 @@ pub struct PauseArgs {
except: ExceptSubArgs,
}

/// Generates the token stream for the `pause` macro.
pub fn pause(attrs: TokenStream, item: TokenStream) -> TokenStream {
let input = parse::<ItemFn>(item.clone()).unwrap();

Expand All @@ -143,13 +147,15 @@ pub fn pause(attrs: TokenStream, item: TokenStream) -> TokenStream {
utils::add_extra_code_to_fn(&input, check_pause)
}

/// Defines attributes for the `if_paused` macro.
#[derive(Debug, FromMeta)]
pub struct IfPausedArgs {
name: String,
#[darling(default)]
except: ExceptSubArgs,
}

/// Generates the token stream for the `if_paused` macro.
pub fn if_paused(attrs: TokenStream, item: TokenStream) -> TokenStream {
let input = parse::<ItemFn>(item.clone()).unwrap();

Expand Down
1 change: 1 addition & 0 deletions near-plugins-derive/src/upgradable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct Opts {
code_storage_key: Option<String>,
}

/// Generates the token stream for the `Upgradable` macro.
pub fn derive_upgradable(input: TokenStream) -> TokenStream {
let cratename = cratename();

Expand Down
2 changes: 2 additions & 0 deletions near-plugins-derive/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ pub(crate) fn is_near_bindgen_wrapped_or_marshall(item: &ItemFn) -> bool {
})
}

/// Returns an identifier for the name of the crate which is imported by plugin users.
pub(crate) fn cratename() -> Ident {
Ident::new(
&crate_name("near-plugins").unwrap_or_else(|_| "near_plugins".to_string()),
Span::call_site(),
)
}

/// Injects extra code into a function.
pub(crate) fn add_extra_code_to_fn(
fn_code: &ItemFn,
extra_code: proc_macro2::TokenStream,
Expand Down
1 change: 1 addition & 0 deletions near-plugins/src/access_control_role.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// Represents permissions for the [`AccessControllable`](crate::AccessControllable) plugin.
pub trait AccessControlRole {
/// Returns the bitflag corresponding to the super admin permission.
fn acl_super_admin_permission() -> u128;
Expand Down
4 changes: 4 additions & 0 deletions near-plugins/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,20 @@ pub struct EventMetadata<T: Serialize = ()> {
pub data: Option<T>,
}

/// Trait to generate and emit NEAR events.
pub trait AsEvent<T: Serialize> {
/// Returns the metadata that makes up the event.
fn metadata(&self) -> EventMetadata<T>;

/// Returns the string representation of the event.
fn event(&self) -> String {
format!(
"EVENT_JSON:{}",
near_sdk::serde_json::to_string(&self.metadata()).unwrap()
)
}

/// Emits the event on chain.
fn emit(&self) {
near_sdk::log!(self.event());
}
Expand Down
3 changes: 3 additions & 0 deletions near-plugins/src/full_access_key_fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::events::{AsEvent, EventMetadata};
use near_sdk::{AccountId, PublicKey};
use serde::Serialize;

/// Trait describing the functionality of the _Full Access Key Fallback_ plugin.
pub trait FullAccessKeyFallback {
/// Attach a new full access to the current contract.
fn attach_full_access_key(&mut self, public_key: PublicKey) -> near_sdk::Promise;
Expand All @@ -25,7 +26,9 @@ pub trait FullAccessKeyFallback {
/// Event emitted every time a new FullAccessKey is added
#[derive(Serialize, Clone)]
pub struct FullAccessKeyAdded {
/// The account that added the full access key.
pub by: AccountId,
/// The public key that was added.
pub public_key: PublicKey,
}

Expand Down
3 changes: 3 additions & 0 deletions near-plugins/src/ownable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::events::{AsEvent, EventMetadata};
use near_sdk::AccountId;
use serde::Serialize;

/// Trait describing the functionality of the _Ownable_ plugin.
pub trait Ownable {
/// Key of storage slot to save the current owner.
/// By default b"__OWNER__" is used.
Expand All @@ -40,7 +41,9 @@ pub trait Ownable {
/// Event emitted when ownership is changed.
#[derive(Serialize, Clone)]
pub struct OwnershipTransferred {
/// The previous owner, if any.
pub previous_owner: Option<AccountId>,
/// The new owner, if any.
pub new_owner: Option<AccountId>,
}

Expand Down
1 change: 1 addition & 0 deletions near-plugins/src/pausable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use near_sdk::AccountId;
use serde::Serialize;
use std::collections::HashSet;

/// Trait describing the functionality of the `Pausable` plugin.
pub trait Pausable {
/// Returns the key of the storage slot which contains the list of features that are paused. By
/// default `b"__PAUSED__"` is used.
Expand Down
5 changes: 5 additions & 0 deletions near-plugins/src/upgradable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::events::{AsEvent, EventMetadata};
use near_sdk::{AccountId, CryptoHash, Promise};
use serde::Serialize;

/// Trait describing the functionality of the _Upgradable_ plugin.
pub trait Upgradable {
/// Key of storage slot to save the staged code.
/// By default b"__CODE__" is used.
Expand All @@ -45,7 +46,9 @@ pub trait Upgradable {
/// Event emitted when the code is staged
#[derive(Serialize, Clone)]
struct StageCode {
/// The account which staged the code.
by: AccountId,
/// The hash of the code that was staged.
code_hash: CryptoHash,
}

Expand All @@ -63,7 +66,9 @@ impl AsEvent<StageCode> for StageCode {
/// Event emitted when the code is deployed
#[derive(Serialize, Clone)]
struct DeployCode {
/// The account that deployed the code.
by: AccountId,
/// The hash of the code that was deployed.
code_hash: CryptoHash,
}

Expand Down