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
195 changes: 0 additions & 195 deletions near-plugins/src/ownable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,198 +57,3 @@ impl AsEvent<OwnershipTransferred> for OwnershipTransferred {
}
}
}

#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {
use crate as near_plugins;
use crate::test_utils::get_context;
use crate::{only, Ownable};
use near_sdk::{near_bindgen, testing_env, VMContext};
use std::convert::TryInto;

#[near_bindgen]
#[derive(Ownable)]
struct Counter {
counter: u64,
}

#[near_bindgen]
impl Counter {
/// Specify the owner of the contract in the constructor
#[init]
fn new() -> Self {
let mut contract = Self { counter: 0 };
contract.owner_set(Some(near_sdk::env::predecessor_account_id()));
contract
}

/// Only owner account, or the contract itself can call this method.
#[only(self, owner)]
fn protected(&mut self) {
self.counter += 1;
}

/// *Only* owner account can call this method.
#[only(owner)]
fn protected_owner(&mut self) {
self.counter += 1;
}

/// *Only* self account can call this method. This can be used even if the contract is not Ownable.
#[only(self)]
fn protected_self(&mut self) {
self.counter += 1;
}

/// Everyone can call this method
fn unprotected(&mut self) {
self.counter += 1;
}
}

/// Setup basic account. Owner of the account is `carol.test`
fn setup_basic() -> (Counter, VMContext) {
let ctx = get_context();
testing_env!(ctx.clone());
let mut counter = Counter::new();
counter.owner_set(Some("carol.test".to_string().try_into().unwrap()));
(counter, ctx)
}

#[test]
fn build_contract() {
let _ = setup_basic();
}

#[test]
fn test_is_owner() {
let (counter, mut ctx) = setup_basic();
assert!(!counter.owner_is());
ctx.predecessor_account_id = "carol.test".to_string().try_into().unwrap();
testing_env!(ctx);
assert!(counter.owner_is());
}

#[test]
fn test_set_owner_ok() {
let (mut counter, mut ctx) = setup_basic();
ctx.predecessor_account_id = "carol.test".to_string().try_into().unwrap();
testing_env!(ctx);
counter.owner_set(Some("eve.test".to_string().try_into().unwrap()));
}

#[test]
#[should_panic(expected = r#"Ownable: Only owner can update current owner"#)]
fn test_set_owner_fail() {
let (mut counter, _) = setup_basic();
counter.owner_set(Some("eve.test".to_string().try_into().unwrap()));
}

#[test]
fn test_remove_owner() {
let (mut counter, mut ctx) = setup_basic();
ctx.predecessor_account_id = "carol.test".to_string().try_into().unwrap();
testing_env!(ctx);
counter.owner_set(None);
assert_eq!(counter.owner_get(), None);
}

#[test]
fn counter_unprotected() {
let (mut counter, _) = setup_basic();
assert_eq!(counter.counter, 0);
counter.unprotected();
assert_eq!(counter.counter, 1);
}

#[test]
fn protected_self_ok() {
let (mut counter, _) = setup_basic();

counter.protected_self();
assert_eq!(counter.counter, 1);
}

#[test]
#[should_panic(expected = r#"Method is private"#)]
fn protected_self_fail() {
let (mut counter, mut ctx) = setup_basic();

ctx.predecessor_account_id = "mallory.test".to_string().try_into().unwrap();
testing_env!(ctx);

counter.protected_self();
assert_eq!(counter.counter, 0);
}

#[test]
#[should_panic(expected = r#"Method is private"#)]
fn protected_self_owner_fail() {
let (mut counter, mut ctx) = setup_basic();

ctx.predecessor_account_id = "carol.test".to_string().try_into().unwrap();
testing_env!(ctx);

counter.protected_self();
assert_eq!(counter.counter, 0);
}

#[test]
fn protected_owner_ok() {
let (mut counter, mut ctx) = setup_basic();

ctx.predecessor_account_id = "carol.test".to_string().try_into().unwrap();
testing_env!(ctx);

counter.protected_owner();
assert_eq!(counter.counter, 1);
}

#[test]
#[should_panic(expected = r#"Ownable: Method must be called from owner"#)]
fn protected_owner_self_fail() {
let (mut counter, _) = setup_basic();

counter.protected_owner();
assert_eq!(counter.counter, 0);
}

#[test]
#[should_panic(expected = r#"Ownable: Method must be called from owner"#)]
fn protected_owner_fail() {
let (mut counter, mut ctx) = setup_basic();

ctx.predecessor_account_id = "mallory.test".to_string().try_into().unwrap();
testing_env!(ctx);

counter.protected_owner();
assert_eq!(counter.counter, 0);
}

#[test]
fn protected_ok() {
let (mut counter, mut ctx) = setup_basic();

counter.protected();
assert_eq!(counter.counter, 1);

ctx.predecessor_account_id = "carol.test".to_string().try_into().unwrap();
testing_env!(ctx);

counter.protected();
assert_eq!(counter.counter, 2);
}

#[test]
#[should_panic(expected = r#"Method is private"#)]
fn protected_fail() {
let (mut counter, mut ctx) = setup_basic();

ctx.predecessor_account_id = "mallory.test".to_string().try_into().unwrap();
testing_env!(ctx);

counter.protected();
assert_eq!(counter.counter, 0);
}
}
1 change: 1 addition & 0 deletions near-plugins/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod access_controllable_contract;
pub mod ownable_contract;
pub mod pausable_contract;
pub mod repo;
pub mod utils;
46 changes: 46 additions & 0 deletions near-plugins/tests/common/ownable_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use near_sdk::serde_json::json;
use workspaces::result::ExecutionFinalResult;
use workspaces::{Account, AccountId, Contract};

/// Wrapper for a contract that is `#[ownable]`. It allows implementing helpers for calling contract
/// methods.
pub struct OwnableContract {
contract: Contract,
}

impl OwnableContract {
pub fn new(contract: Contract) -> Self {
Self { contract }
}

pub fn contract(&self) -> &Contract {
&self.contract
}

pub async fn owner_get(&self, caller: &Account) -> anyhow::Result<Option<AccountId>> {
let res = caller.call(self.contract.id(), "owner_get").view().await?;
Ok(res.json::<Option<AccountId>>()?)
}

pub async fn owner_set(
&self,
caller: &Account,
owner: Option<AccountId>,
) -> workspaces::Result<ExecutionFinalResult> {
caller
.call(self.contract.id(), "owner_set")
.args_json(json!({ "owner": owner }))
.max_gas()
.transact()
.await
}

pub async fn owner_is(&self, caller: &Account) -> anyhow::Result<bool> {
let res = caller
.call(self.contract.id(), "owner_is")
.max_gas()
.transact()
.await?;
Ok(res.json::<bool>()?)
}
}
45 changes: 45 additions & 0 deletions near-plugins/tests/common/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,51 @@ pub fn assert_method_is_paused(res: ExecutionFinalResult) {
);
}

pub fn assert_owner_update_failure(res: ExecutionFinalResult) {
let err = res
.into_result()
.err()
.expect("Transaction should have failed");
let err = format!("{}", err);
let must_contain = "Ownable: Only owner can update current owner";
assert!(
err.contains(&must_contain),
"Expected failure due to caller not being owner, instead it failed with: {}",
err
);
}

/// Assert failure due to calling a method protected by `#[only]` without required permissions.
pub fn assert_ownable_permission_failure(res: ExecutionFinalResult) {
let err = res
.into_result()
.err()
.expect("Transaction should have failed");
let err = format!("{}", err);
let must_contain = "Method is private";
assert!(
err.contains(&must_contain),
"Expected failure due to insufficient permissions, instead it failed with: {}",
err
);
}

/// Assert failure due to calling a method protected by `#[only(owner)]` from an account other than the
/// owner.
pub fn assert_only_owner_permission_failure(res: ExecutionFinalResult) {
let err = res
.into_result()
.err()
.expect("Transaction should have failed");
let err = format!("{}", err);
let must_contain = "Ownable: Method must be called from owner";
assert!(
err.contains(&must_contain),
"Expected failure due to caller not being owner, instead it failed with: {}",
err
);
}

/// Asserts the execution of `res` failed and the error contains `must_contain`.
pub fn assert_failure_with(res: ExecutionFinalResult, must_contain: &str) {
let err = res
Expand Down
21 changes: 21 additions & 0 deletions near-plugins/tests/contracts/ownable/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "ownable"
version = "0.0.0"
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
near-plugins = { path = "../../../../near-plugins" }
near-sdk = "4.1.0"

[profile.release]
codegen-units = 1
opt-level = "z"
lto = true
debug = false
panic = "abort"
overflow-checks = true

[workspace]
8 changes: 8 additions & 0 deletions near-plugins/tests/contracts/ownable/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
build:
cargo build --target wasm32-unknown-unknown --release

# Helpful for debugging. Requires `cargo-expand`.
expand:
cargo expand > expanded.rs

.PHONY: build expand
3 changes: 3 additions & 0 deletions near-plugins/tests/contracts/ownable/rust-toolchain
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[toolchain]
channel = "1.64.0"
components = ["clippy", "rustfmt"]
58 changes: 58 additions & 0 deletions near-plugins/tests/contracts/ownable/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use near_plugins::{only, Ownable};
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::{near_bindgen, AccountId, PanicOnDefault};

#[near_bindgen]
#[derive(Ownable, PanicOnDefault, BorshDeserialize, BorshSerialize)]
pub struct Counter {
counter: u64,
}

#[near_bindgen]
impl Counter {
/// Optionally set the owner in the constructor.
#[init]
pub fn new(owner: Option<AccountId>) -> Self {
let mut contract = Self { counter: 0 };
if owner.is_some() {
contract.owner_set(owner);
}
contract
}

/// Returns the value of the counter.
pub fn get_counter(&self) -> u64 {
self.counter
}

/// Anyone may call this method successfully.
pub fn increase(&mut self) -> u64 {
self.counter += 1;
self.counter
}

/// _Only_ the owner or the contract itself may call this method successfully. It panics if
/// anyone else calls it.
#[only(self, owner)]
pub fn increase_2(&mut self) -> u64 {
self.counter += 2;
self.counter
}

/// _Only_ the owner may call this method successfully. It panics if anyone else calls it.
#[only(owner)]
pub fn increase_3(&mut self) -> u64 {
self.counter += 3;
self.counter
}

/// _Only_ the contract itself may call this method successfully. It panics if anyone else calls
/// it.
///
/// It is possible to use `#[only(self)]` even if the contract does not derive `Ownable`.
#[only(self)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is only(self) equivilent to private from near-sdk?

Copy link
Copy Markdown
Contributor Author

@mooori mooori Jan 10, 2023

Choose a reason for hiding this comment

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

True, they are equivalent. I think we should mention that in the docs and recommend using #[private] rather than #[only(self)], since the latter is provided by near-sdk directly. Would that make sense?

I’ve merged this PR since it’s intended to be a port of unit to integration tests. In a follow-up PR I can handle the self docs, if necessary.

pub fn increase_4(&mut self) -> u64 {
self.counter += 4;
self.counter
}
}
Loading