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

Non-Fungible Token Standards #69

Merged
merged 38 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b102336
feat: nep171 wip
encody Sep 26, 2022
eedb85d
feat: nep171 events are owned; some experiments for allowing approval…
encody Oct 3, 2022
1fc7175
Merge branch 'develop' into nep171
encody Oct 3, 2022
05f8df4
feat: some ideas for implementing approval extension (wip)
encody Oct 10, 2022
30114b3
Merge branch 'develop' into nep171
encody Jun 21, 2023
6ee231b
fix: crate name change
encody Jun 21, 2023
3a1b4af
feat: nep171 macro progress
encody Jun 21, 2023
5d1a622
full nep171 implementation
encody Jun 27, 2023
fe5cf8a
chore: simplify nft_resolve_transfer
encody Jun 27, 2023
4cc562d
chore: finished nep171 macro
encody Jun 29, 2023
1476339
chore: cleanup, tests, renaming
encody Jul 10, 2023
438d83c
chore: documentation comments
encody Jul 13, 2023
0f1445f
chore: workspaces tests & multiple token ids
encody Jul 17, 2023
af2d805
feat: nep171 multiple token id transfer
encody Jul 17, 2023
8a3eb77
Merge branch 'develop' into nep171
encody Jul 17, 2023
fa881d7
chore: more tests + small fixes
encody Jul 18, 2023
66bb5d7
fix: warnings
encody Jul 20, 2023
e9e8422
Merge branch 'develop' into nep171
encody Jul 20, 2023
89143f2
feat: predicate api improvements
encody Jul 20, 2023
c466c91
feat: remove predicate, introduce check function
encody Jul 26, 2023
14bd100
feat: nft events version 1.2.0 update
encody Jul 26, 2023
eded8d8
Nep177: NFT Metadata (#124)
encody Jul 31, 2023
76e1b34
feat: switch to using #[serde(flatten)] for token metadata
encody Jul 31, 2023
e5542be
feat: nep178
encody Jul 31, 2023
7d59168
chore: upgrade tests & organize libs
encody Aug 11, 2023
2907ee0
feat: nep178 macro integration
encody Aug 16, 2023
4ca7b90
feat: nep178 testing
encody Aug 26, 2023
6824eb4
chore: docs
encody Aug 26, 2023
1df7e02
chore: more tests and examples in docs
encody Aug 26, 2023
7ba549f
chore: lots of docs
encody Aug 28, 2023
9854111
feat: switch hook state to associated type
encody Sep 7, 2023
1f72173
feat: nep181 internals done, improved hooks
encody Sep 12, 2023
768512c
feat: nep181 macro, sanity test
encody Sep 12, 2023
3780100
chore: qol nft module
encody Sep 12, 2023
17afaf0
feat: docs
encody Sep 13, 2023
21370a8
feat: further enumeration tests
encody Sep 13, 2023
56cd762
feat: final (?) tests for enumeration
encody Sep 13, 2023
27bc4b8
chore: update readme to mention nep-171 and related
encody Sep 13, 2023
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
11 changes: 11 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,17 @@ pub fn derive_fungible_token(input: TokenStream) -> TokenStream {
make_derive(input, standard::fungible_token::expand)
}

/// Adds NEP-171 non-fungible token core functionality to a contract. Exposes
/// `nft_*` functions to the public blockchain, implements internal controller
/// and receiver functionality (see: [`near_sdk_contract_tools::standard::nep171`]).
///
/// The storage key prefix for the fields can be optionally specified (default:
/// `"~$171"`) using `#[nep171(storage_key = "<expression>")]`.
#[proc_macro_derive(Nep171, attributes(nep171))]
pub fn derive_nep171(input: TokenStream) -> TokenStream {
make_derive(input, standard::nep171::expand)
}

/// Migrate a contract's default struct from one schema to another.
///
/// Fields may be specified in the `#[migrate(...)]` attribute.
Expand Down
1 change: 1 addition & 0 deletions macros/src/standard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ pub mod fungible_token;

pub mod nep141;
pub mod nep148;
pub mod nep171;
pub mod nep297;
243 changes: 243 additions & 0 deletions macros/src/standard/nep171.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
use std::ops::Not;

use darling::{util::Flag, FromDeriveInput};
use proc_macro2::TokenStream;
use quote::quote;
use syn::Expr;

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(nep171), supports(struct_named))]
pub struct Nep171Meta {
pub storage_key: Option<Expr>,
pub no_hooks: Flag,
pub generics: syn::Generics,
pub ident: syn::Ident,

// crates
#[darling(rename = "crate", default = "crate::default_crate_name")]
pub me: syn::Path,
#[darling(default = "crate::default_near_sdk")]
pub near_sdk: syn::Path,
}

pub fn expand(meta: Nep171Meta) -> Result<TokenStream, darling::Error> {
let Nep171Meta {
storage_key,
no_hooks,
generics,
ident,

me,
near_sdk,
} = meta;

let (imp, ty, wher) = generics.split_for_impl();

let root = storage_key.map(|storage_key| {
quote! {
fn root() -> #me::slot::Slot<()> {
#me::slot::Slot::root(#storage_key)
}
}
});

let before_nft_transfer = no_hooks.is_present().not().then(|| {
quote! {
let hook_state = <Self as #me::standard::nep171::Nep171Hook::<_>>::before_nft_transfer(self, &transfer);
}
});

let after_nft_transfer = no_hooks.is_present().not().then(|| {
quote! {
<Self as #me::standard::nep171::Nep171Hook::<_>>::after_nft_transfer(self, &transfer, hook_state);
}
});

Ok(quote! {
impl #imp #me::standard::nep171::Nep171ControllerInternal for #ident #ty #wher {
#root
}

#[#near_sdk::near_bindgen]
impl #imp #me::standard::nep171::Nep171Resolver for #ident #ty #wher {
#[private]
fn nft_resolve_transfer(
&mut self,
previous_owner_id: #near_sdk::AccountId,
receiver_id: #near_sdk::AccountId,
token_id: #me::standard::nep171::TokenId,
approved_account_ids: Option<std::collections::HashMap<#near_sdk::AccountId, u64>>,
) -> bool {
let _ = approved_account_ids; // #[near_bindgen] cares about parameter names

#near_sdk::require!(
#near_sdk::env::promise_results_count() == 1,
"Requires exactly one promise result.",
);

let should_revert =
if let #near_sdk::PromiseResult::Successful(value) = #near_sdk::env::promise_result(0) {
#near_sdk::serde_json::from_slice::<bool>(&value).unwrap_or(true)
} else {
true
};

if should_revert {
let transfer = #me::standard::nep171::Nep171Transfer {
token_id: token_id.clone(),
owner_id: receiver_id.clone(),
sender_id: receiver_id.clone(),
receiver_id: previous_owner_id.clone(),
approval_id: None,
memo: None,
msg: None,
};

#before_nft_transfer

let result = #me::standard::nep171::Nep171Controller::transfer(
self,
&[token_id],
receiver_id.clone(),
receiver_id,
previous_owner_id,
None,
)
.is_err();

#after_nft_transfer

result
} else {
true
}
}
}

#[#near_sdk::near_bindgen]
impl #imp #me::standard::nep171::Nep171 for #ident #ty #wher {
#[payable]
fn nft_transfer(
&mut self,
receiver_id: #near_sdk::AccountId,
token_id: #me::standard::nep171::TokenId,
approval_id: Option<u64>,
memo: Option<String>,
) {
use #me::standard::nep171::*;

#near_sdk::require!(
approval_id.is_none(),
APPROVAL_MANAGEMENT_NOT_SUPPORTED_MESSAGE,
);

#near_sdk::assert_one_yocto();

let sender_id = #near_sdk::env::predecessor_account_id();

let transfer = #me::standard::nep171::Nep171Transfer {
token_id: token_id.clone(),
owner_id: sender_id.clone(),
sender_id: sender_id.clone(),
receiver_id: receiver_id.clone(),
approval_id: None,
memo: memo.clone(),
msg: None,
};

#before_nft_transfer

Nep171Controller::transfer(
self,
&[token_id],
sender_id.clone(),
sender_id,
receiver_id,
memo,
)
.unwrap_or_else(|e| #near_sdk::env::panic_str(&e.to_string()));

#after_nft_transfer
}

#[payable]
fn nft_transfer_call(
&mut self,
receiver_id: #near_sdk::AccountId,
token_id: #me::standard::nep171::TokenId,
approval_id: Option<u64>,
memo: Option<String>,
msg: String,
) -> #near_sdk::PromiseOrValue<bool> {
use #me::standard::nep171::*;

#near_sdk::require!(
approval_id.is_none(),
APPROVAL_MANAGEMENT_NOT_SUPPORTED_MESSAGE,
);

#near_sdk::assert_one_yocto();

#near_sdk::require!(
#near_sdk::env::prepaid_gas() >= GAS_FOR_NFT_TRANSFER_CALL,
INSUFFICIENT_GAS_MESSAGE,
);

let sender_id = #near_sdk::env::predecessor_account_id();

let transfer = #me::standard::nep171::Nep171Transfer {
token_id: token_id.clone(),
owner_id: sender_id.clone(),
sender_id: sender_id.clone(),
receiver_id: receiver_id.clone(),
approval_id: None,
memo: memo.clone(),
msg: Some(msg.clone()),
};

#before_nft_transfer

let token_ids = [token_id];

Nep171Controller::transfer(
self,
&token_ids,
sender_id.clone(),
sender_id.clone(),
receiver_id.clone(),
memo,
)
.unwrap_or_else(|e| #near_sdk::env::panic_str(&e.to_string()));

let [token_id] = token_ids;

#after_nft_transfer

ext_nep171_receiver::ext(receiver_id.clone())
.with_static_gas(#near_sdk::env::prepaid_gas() - GAS_FOR_NFT_TRANSFER_CALL)
.nft_on_transfer(
sender_id.clone(),
sender_id.clone(),
token_id.clone(),
msg,
)
.then(
ext_nep171_resolver::ext(#near_sdk::env::current_account_id())
.with_static_gas(GAS_FOR_RESOLVE_TRANSFER)
.nft_resolve_transfer(sender_id, receiver_id, token_id, None),
)
.into()
}

fn nft_token(
&self,
token_id: #me::standard::nep171::TokenId,
) -> Option<#me::standard::nep171::Token> {
use #me::standard::nep171::*;

Nep171Controller::token_owner(self, &token_id)
.map(|owner_id| Token { token_id, owner_id })
}
}
})
}
9 changes: 6 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
#![doc = include_str!("../README.md")]

use near_sdk::IntoStorageKey;
pub use near_sdk_contract_tools_macros::*;

/// Default storage keys used by various traits' `root()` functions.
#[derive(Clone, Debug)]
pub enum DefaultStorageKey {
/// Default storage key for [`approval::ApprovalManager::root`]
ApprovalManager,
/// Default storage key for [`standard::nep141::Nep141Controller::root`]
Nep141,
/// Default storage key for [`standard::nep171::Nep171Controller::root`]
Nep171,
/// Default storage key for [`owner::Owner::root`]
Owner,
/// Default storage key for [`pause::Pause::root`]
Expand All @@ -20,6 +25,7 @@ impl IntoStorageKey for DefaultStorageKey {
match self {
DefaultStorageKey::ApprovalManager => b"~am".to_vec(),
DefaultStorageKey::Nep141 => b"~$141".to_vec(),
DefaultStorageKey::Nep171 => b"~$171".to_vec(),
DefaultStorageKey::Owner => b"~o".to_vec(),
DefaultStorageKey::Pause => b"~p".to_vec(),
DefaultStorageKey::Rbac => b"~r".to_vec(),
Expand All @@ -37,6 +43,3 @@ pub mod rbac;
pub mod slot;
pub mod upgrade;
pub mod utils;

use near_sdk::IntoStorageKey;
pub use near_sdk_contract_tools_macros::*;
1 change: 1 addition & 0 deletions src/standard/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

pub mod nep141;
pub mod nep148;
pub mod nep171;
pub mod nep297;
Loading