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

Add the article for Aurora's dev portal #1

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
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
178 changes: 178 additions & 0 deletions Article.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Plugins for smart contract devs building on NEAR

Some common functionality is required for many smart contracts. Examples are temporarily pausing certain features, staging and deploying a new version of the contract, and restricting access to contract methods. While such functionality is out of scope for an SDK like `near-sdk-rs`, ideally it is not implemented anew for every smart contract.

The most obvious benefits of an open-source library are reusability and the value it adds to the ecosystem. The smart contract features mentioned above can be tricky to implement and cumbersome to test. Without a library, developers might gloss over functionality that does not add business value but still is critical for security. As `near-plugins` is open-source and used by many developers, there are more engineering hours and eyeballs dedicated to it compared to a solution specific to a single smart contract.

## Case study: A counter managing permissions with ACL

Let’s look at a case study to see how `near-plugins` can be useful to smart contract developers. We are building a `Counter` which stores its current value and has methods to increment, decrement and reset the value. It is intentionally kept simple to allow us to focus on how `near-plugins` adds functionality. This is what the contract looks like prior to using any plugins:

```rust
#[near_bindgen]
impl Counter {
#[init]
pub fn new() -> Self {
Self { value: 0 }
}

/// Anyone can retrieve the current value.
pub fn value(&self) -> i64 {
self.value
}

/// Increases the value of the counter by one.
pub fn increment(&mut self) {
self.value += 1;
}

/// Decreases the value of the counter by one.
pub fn decrement(&mut self) {
self.value -= 1;
}

/// Resets the value of the counter to zero.
pub fn reset(&mut self) {
self.value = 0;
}
}
```

*The final version of the code is available [in this repository on github](https://github.com/mooori/counter-acl-example). The `Counter` example is inspired by [near-examples/counter-rust](https://github.com/near-examples/counter-rust).*
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Will this repo live in your personal github account or do you plan on eventually transferring it to the near or aurora or some other org? I am asking as we may need to update the link here. I wonder if it is possible to use relative paths here instead so this question is mute.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the links relative in 1582d56.

I’ve created the repo in my personal github as I wasn’t sure were it should live, to avoid triggering some workflows in a potentially wrong org. Moving it to an org is fine for me. Especially if this should be an official example/reference moving it to an org would make sense.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I think we can keep it here for now and figure out eventually which org to place it in. Longer term, it should probably be in some org.


### Permissions

The contract methods defined above can be called by anyone since they are public and inside an implementation block marked with `#[near_bindgen]`. Using `near-sdk-rs` it is possible to restrict some methods such that they can be called only by the contract itself. Either by using `#[private]` or by not exposing the method publicly, as described in the [documentation](https://docs.near.org/sdk/rust/contract-interface/private-methods).

However, what if some accounts other than the contract should be able to call restricted methods? This is were [`AccessControllable`](https://github.com/aurora-is-near/near-plugins#accesscontrollable) plugin comes in handy.
mooori marked this conversation as resolved.
Show resolved Hide resolved

### Managing permissions with ACL

ACL stands for *access control lists* and they are used in the following way within the `AccessControllable` plugin. The user defines the roles required for their use case as Rust enum variants. Then it is possible to restrict access to a method to accounts that have been granted roles. Restricting access is possible with one line of code, for example:

```rust
#[access_control_any(roles(Role::Decrementer))]
pub fn decrement(&mut self) {
// ...
}
```

Let’s walk through it step by step to see how you can make your NEAR smart contract `AccessControllable`.

### Stop 1: Add `near-plugins` as a dependency
mooori marked this conversation as resolved.
Show resolved Hide resolved

For now, `near-plugins` has not yet been published on crates.io. Still, the crate is ready for usage and it can be added as git dependency:

```toml
# Add `near-plugins` under `dependencies` in your Cargo.toml.

[dependencies]
near-plugins = { git = "https://github.com/aurora-is-near/near-plugins.git", rev = "102d95b" }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to specify a rev? That might mean that people could miss bugfixes, etc. easily. I suppose this would be resolved when we have released the crate. Maybe in the mean time, we can use tags? I am afraid of too many people hardcoding a specific revision in their repo and then forgetting to update.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this would be resolved when the crate is released and becomes a “regular” dependency. Unless we manually move the tag as near-plugins is changed, I think it would basically be the same: a reference to a commit.

Still, using a tag to the latest release helps explaining why that particular commit is used. It’s about time to release a new version of near-plugins. I’ll look into that and then replace the rev here with a tag to the new version.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thanks, that makes sense.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new release of near-plugins has been published. This repo now specifies the near-plugins dependency with tag = "v0.2.0" (cc1c75e).

```

### Step 2: Define roles

Every use case may require a different set of roles, so users may define their own roles as variants of an enum. For the `Counter` example we define the following roles:

```rust
#[derive(AccessControlRole, Deserialize, Serialize, Copy, Clone)]
#[serde(crate = "near_sdk::serde")]
pub enum Role {
/// Grantees of this role may decrease the counter.
Decrementer,
/// Grantees of this role may reset the counter.
Resetter,
}
```

Deriving the `AccessControlRole` trait prepares the `Role` enum for usage in the `AccessControllable` plugin.

### Step 3: Make the contract `AccessControllable`

The contract is made `AccessControllable` by attaching the `access_control` attribute macro on the definition of the struct which represents the contract’s state. We pass our `Role` as argument to make the `AccessControllable` implementation aware of it:

```rust
#[access_control(role_type(Role))]
#[near_bindgen]
#[derive(PanicOnDefault, BorshDeserialize, BorshSerialize)]
pub struct Counter {
value: i64,
}
```

### Step 4: Restrict contract methods

Access to a contract method is restricted by attaching `#[access_control_any]` and providing the roles to be whitelisted as arguments:

```rust
#[near_bindgen]
impl Counter {
// We must be inside an implementation block with `#[near-bindgen]`.

/// Resets the value of the counter to zero.
///
/// Only accounts that have been granted `Role::Resetter` may successfully call this method.
/// If called by an account without this role, the method panics and state remains unchanged.
#[access_control_any(roles(Role::Resetter))] // enables ACL for this method
pub fn reset(&mut self) {
self.value = 0;
}

/// By the way, it is also possible to restrict access to accounts that have been granted any of
/// multiple roles. This is how the syntax looks.
#[access_control_any(roles(Role::Decrementer, Role::Resetter))]
pub fn no_op(&self) { }
}
```

Now the contract is set up for access control. The only step that is missing is granting roles to accounts, enabling them to call restricted methods.

### Step 5: Grant permissions

In our contract’s constructor method `new()` we make the contract itself super admin:

```rust
near_sdk::require!(
contract.acl_init_super_admin(env::current_account_id()),
"Failed to initialize super admin",
);
```

The `AccessControllable` super admin is an admin for every role defined in the `Role` enum. For this example it is sufficient to know that a super admin may grant and revoke every role. Making the contract itself super admin facilitates the setup procedure as well as testing. More detailed information on admin roles can be found in the [documentation](https://github.com/aurora-is-near/near-plugins/blob/master/near-plugins/src/access_controllable.rs) of the `AccessControllable` trait.

To grant the `Resetter` role to the account `alice.near`, the contract can call the following function on itself:

```rust
/// See `AccessControllable::acl_grant_role` for details.
acl_grant_role("Resetter", "alice.near");
```

The `AccessControllable` trait provides many more methods to administer ACL permissions. After following the steps above, all of them are automatically implemented for a contract using the `AccessControllable` plugin.

# Done!

The steps above are sufficient to add complex and configurable ACL permissions to a contract using `near-plugins`. At this point, `alice.near` is the only account which has been granted the `Resetter` role. This means that only `alice.near` may successfully call the contract’s `reset()` method.

The repo contains an [integration test](https://example.com) which verifies that `AccessControllable` was setup up correctly for our `Counter` contract. Take a look at it to learn more about interacting with an `AccessControllable` contract. To run the test on-chain in a local sandbox, it suffices to clone the repo and execute the following command. This is made possible by [near-workspaces-rs](https://github.com/near/near-workspaces-rs).

```
cargo test
```
mooori marked this conversation as resolved.
Show resolved Hide resolved

## Teaser: How it works internally

Using `AccessControllable` extends the contracts state to store the permissions that have been granted. Moreover, the `AccessControllable` trait is implemented for the contract to enable administering permissions. When `#[access_control_any(roles(...))]` is attached to a method, `near-plugin` injects code which checks if the caller was granted any of the required roles. If not, a panic is generated which aborts the function call.

To learn about all the details, you can dive into the [implementation](https://github.com/aurora-is-near/near-plugins/blob/master/near-plugins-derive/src/access_controllable.rs) of the `AccessControllable` macro.
## A note on testing
mooori marked this conversation as resolved.
Show resolved Hide resolved

The functionality provided by `near-plugins` is critical for security and we strive to test it exhaustively. In tests we compile demo contracts for all plugins and deploy them on-chain in a local sandbox. Then we verify that using a particular plugin adds exactly the expected functionality to the contract. These tests and demo contracts can be found [here](https://github.com/aurora-is-near/near-plugins/tree/master/near-plugins-derive/tests).
## Ready for production, though?

As mentioned earlier, `near-plugins` comes with the caveat of not yet being published to crates.io. Nevertheless, we are confident that `near-plugins` can already be used in production. In fact, smart contracts related to the [Rainbow Bridge](https://rainbowbridge.app/transfer) already use `near-plugins`. Moreover, both [Hacken](https://www.datocms-assets.com/50156/1680101850-hacken-near-plugins-final-report-updated-march2023.pdf) and [AuditOne](https://www.datocms-assets.com/50156/1680590522-auditone-near-plugins-final-report-updated-march2023.pdf) audited `near-plugins`, awarding it high scores.
mooori marked this conversation as resolved.
Show resolved Hide resolved
# Conclusion

Using `near-plugins`, developers can add complex functionality to their smart contracts with just a few lines of code. Developers can focus on creating value for their users by relying on `near-plugins` for some cumbersome administrative tasks that are nevertheless critical for security. We are testing all plugins extensively and the `near-plugins` crate has been audited twice. We hope to contribute to the NEAR ecosystem by providing secure smart contract plugins which developers can build upon.

This article provides a step-by-step guide to use the `AccessControllable` plugin. In principle, using other plugins is similar. Head over to the [repository](https://github.com/aurora-is-near/near-plugins) and have a look at the documentation and tests to get started with other plugins.