forked from near/near-sdk-contract-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
project structure; ownership & utils
- Loading branch information
Showing
7 changed files
with
299 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/target | ||
Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"editor.tabSize": 4 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[package] | ||
name = "near-contract-tools" | ||
version = "0.1.0" | ||
edition = "2021" | ||
authors = ["Jacob Lindahl <[email protected]>"] | ||
license = "GPL-3.0" | ||
categories = ["wasm"] | ||
description = """ | ||
Helpful functions and macros for developing smart contracts on NEAR Protocol. | ||
""" | ||
|
||
[dependencies] | ||
near-sdk = "4.0.0-pre.8" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
tab_spaces = 4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
pub mod ownership; | ||
pub mod utils; | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
#[test] | ||
fn it_works() { | ||
near_sdk::testing_env!(near_sdk::test_utils::VMContextBuilder::new() | ||
.attached_deposit(near_sdk::ONE_NEAR) | ||
.build()); | ||
|
||
// near_sdk::testing_env!(); | ||
let result = 2 + 2; | ||
assert_eq!(result, 4); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
use near_sdk::{ | ||
borsh::{self, BorshDeserialize, BorshSerialize}, | ||
collections::LazyOption, | ||
env, require, AccountId, IntoStorageKey, | ||
}; | ||
|
||
use crate::utils::prefix_key; | ||
|
||
/// State for contract ownership management | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use near_contract_tools::ownership::Ownership; | ||
/// | ||
/// struct Contract { | ||
/// // ... | ||
/// pub ownership: Ownership, | ||
/// } | ||
/// ``` | ||
#[derive(BorshDeserialize, BorshSerialize)] | ||
pub struct Ownership { | ||
pub owner: Option<AccountId>, | ||
pub proposed_owner: LazyOption<AccountId>, | ||
} | ||
|
||
impl Ownership { | ||
/// Creates a new Ownership using the specified storage key prefix. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use near_contract_tools::ownership::Ownership; | ||
/// | ||
/// let ownership = Ownership::new( | ||
/// b"o", | ||
/// near_sdk::env::predecessor_account_id(), | ||
/// ); | ||
/// ``` | ||
pub fn new<S>(storage_key_prefix: S, owner_id: AccountId) -> Self | ||
where | ||
S: IntoStorageKey, | ||
{ | ||
let k = storage_key_prefix.into_storage_key(); | ||
|
||
Self { | ||
owner: Some(owner_id), | ||
proposed_owner: LazyOption::new(prefix_key(&k, b"p"), None), | ||
} | ||
} | ||
|
||
/// Requires the predecessor to be the owner | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use near_contract_tools::ownership::Ownership; | ||
/// | ||
/// let ownership = Ownership::new( | ||
/// b"o", | ||
/// near_sdk::env::predecessor_account_id(), | ||
/// ); | ||
/// ownership.assert_owner(); | ||
/// ``` | ||
pub fn assert_owner(&self) { | ||
require!( | ||
&env::predecessor_account_id() | ||
== self | ||
.owner | ||
.as_ref() | ||
.unwrap_or_else(|| env::panic_str("No owner")), | ||
"Owner only" | ||
); | ||
} | ||
|
||
/// Removes the contract's owner. Can only be called by the current owner. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use near_contract_tools::ownership::Ownership; | ||
/// | ||
/// let owner_id = near_sdk::env::predecessor_account_id(); | ||
/// let mut ownership = Ownership::new( | ||
/// b"o", | ||
/// owner_id.clone(), | ||
/// ); | ||
/// assert_eq!(ownership.owner, Some(owner_id)); | ||
/// ownership.renounce_owner(); | ||
/// assert_eq!(ownership.owner, None); | ||
/// ``` | ||
pub fn renounce_owner(&mut self) { | ||
self.assert_owner(); | ||
self.owner = None; | ||
self.proposed_owner.remove(); | ||
} | ||
|
||
/// Prepares the contract to change owners, setting the proposed owner to | ||
/// the provided account ID. Can only be called by the current owner. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use near_contract_tools::ownership::Ownership; | ||
/// | ||
/// let mut ownership = Ownership::new( | ||
/// b"o", | ||
/// near_sdk::env::predecessor_account_id(), | ||
/// ); | ||
/// let proposed_owner: near_sdk::AccountId = "account".parse().unwrap(); | ||
/// assert_eq!(ownership.proposed_owner.get(), None); | ||
/// ownership.propose_owner(Some(proposed_owner.clone())); | ||
/// assert_eq!(ownership.proposed_owner.get(), Some(proposed_owner)); | ||
/// ``` | ||
pub fn propose_owner(&mut self, account_id: Option<AccountId>) { | ||
self.assert_owner(); | ||
if let Some(a) = account_id { | ||
self.proposed_owner.set(&a); | ||
} else { | ||
self.proposed_owner.remove(); | ||
} | ||
} | ||
|
||
/// Sets new owner equal to proposed owner. Can only be called by proposed | ||
/// owner. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use near_contract_tools::ownership::Ownership; | ||
/// | ||
/// let owner_id = "account".parse().unwrap(); | ||
/// let mut ownership = Ownership::new( | ||
/// b"o", | ||
/// owner_id, | ||
/// ); | ||
/// let proposed_owner = near_sdk::env::predecessor_account_id(); | ||
/// ownership.proposed_owner.set(&proposed_owner); | ||
/// ownership.accept_owner(); | ||
/// assert_eq!(ownership.owner, Some(proposed_owner)); | ||
/// ``` | ||
pub fn accept_owner(&mut self) { | ||
let proposed_owner = self | ||
.proposed_owner | ||
.take() | ||
.unwrap_or_else(|| env::panic_str("No proposed owner")); | ||
require!( | ||
&env::predecessor_account_id() == &proposed_owner, | ||
"Proposed owner only" | ||
); | ||
self.owner = Some(proposed_owner); | ||
} | ||
} | ||
|
||
pub trait Ownable { | ||
fn own_get_owner(&self) -> Option<AccountId>; | ||
fn own_get_proposed_owner(&self) -> Option<AccountId>; | ||
fn own_renounce_owner(&mut self); | ||
fn own_propose_owner(&mut self, account_id: Option<AccountId>); | ||
fn own_accept_owner(&mut self); | ||
} | ||
|
||
#[macro_export] | ||
macro_rules! impl_ownership { | ||
($contract: ident, $ownership: ident) => { | ||
use $crate::ownership::Ownable; | ||
|
||
#[near_bindgen] | ||
impl Ownable for $contract { | ||
fn own_get_owner(&self) -> Option<AccountId> { | ||
self.$ownership.owner.clone() | ||
} | ||
|
||
fn own_get_proposed_owner(&self) -> Option<AccountId> { | ||
self.$ownership.proposed_owner.get() | ||
} | ||
|
||
#[payable] | ||
fn own_renounce_owner(&mut self) { | ||
assert_one_yocto(); | ||
self.$ownership.renounce_owner() | ||
} | ||
|
||
#[payable] | ||
fn own_propose_owner(&mut self, account_id: Option<AccountId>) { | ||
assert_one_yocto(); | ||
self.$ownership.propose_owner(account_id); | ||
} | ||
|
||
#[payable] | ||
fn own_accept_owner(&mut self) { | ||
assert_one_yocto(); | ||
self.$ownership.accept_owner(); | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
use near_sdk::{env, require, Promise}; | ||
|
||
/// Concatenate bytes to form a key. Useful for generating storage keys. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use near_contract_tools::utils::prefix_key; | ||
/// | ||
/// assert_eq!(prefix_key(b"p", b"key"), b"pkey".to_vec()); | ||
/// ``` | ||
pub fn prefix_key(prefix: &dyn AsRef<[u8]>, key: &dyn AsRef<[u8]>) -> Vec<u8> { | ||
[prefix.as_ref(), key.as_ref()].concat() | ||
} | ||
|
||
/// Calculates the storage fee of an action, given an initial storage amount, | ||
/// and refunds the predecessor a portion of the attached deposit if necessary. | ||
/// Returns refund Promise if refund was applied. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use near_contract_tools::utils::apply_storage_fee_and_refund; | ||
/// | ||
/// let initial_storage_usage = near_sdk::env::storage_usage(); | ||
/// let additional_fees = 0; | ||
/// | ||
/// // Action that consumes storage. | ||
/// near_sdk::env::storage_write(b"key", b"value"); | ||
/// | ||
/// near_sdk::testing_env!(near_sdk::test_utils::VMContextBuilder::new() | ||
/// .attached_deposit(near_sdk::ONE_NEAR) | ||
/// .build()); | ||
/// // Attached deposit must cover storage fee or this function will panic | ||
/// apply_storage_fee_and_refund(initial_storage_usage, additional_fees); | ||
/// ``` | ||
pub fn apply_storage_fee_and_refund( | ||
initial_storage_usage: u64, | ||
additional_fees: u128, | ||
) -> Option<Promise> { | ||
// Storage consumption after storage event | ||
let storage_usage_end = env::storage_usage(); | ||
|
||
// Storage fee incurred by storage event, clamped >= 0 | ||
let storage_fee = u128::from(storage_usage_end.saturating_sub(initial_storage_usage)) | ||
* env::storage_byte_cost(); | ||
|
||
let total_required_deposit = storage_fee + additional_fees; | ||
|
||
let attached_deposit = env::attached_deposit(); | ||
|
||
require!( | ||
attached_deposit >= total_required_deposit, | ||
format!( | ||
"Insufficient deposit: attached {attached_deposit} yoctoNEAR < required {total_required_deposit} yoctoNEAR ({storage_fee} storage + {additional_fees} additional)", | ||
) | ||
); | ||
|
||
let refund = attached_deposit - total_required_deposit; | ||
|
||
// Send refund transfer if required | ||
if refund > 0 { | ||
Some(Promise::new(env::predecessor_account_id()).transfer(refund)) | ||
} else { | ||
None | ||
} | ||
} |