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

Feat/payout #778

Closed
wants to merge 6 commits into from
Closed
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
members = [
"near-sdk",
"near-sdk-macros",
"near-sdk-sim",
"near-sdk-sim",
Copy link
Contributor

Choose a reason for hiding this comment

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

can you revert this spacing?

Copy link
Author

Choose a reason for hiding this comment

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

No Problem

"near-contract-standards",
"sys",
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::resolver::NonFungibleTokenResolver;
use crate::non_fungible_token::core::NonFungibleTokenCore;
use crate::non_fungible_token::events::{NftMint, NftTransfer};
use crate::non_fungible_token::metadata::TokenMetadata;
use crate::non_fungible_token::payout::Royalties;
use crate::non_fungible_token::token::{Token, TokenId};
use crate::non_fungible_token::utils::{
hash_account_id, refund_approved_account_ids, refund_deposit_to_account,
Expand Down Expand Up @@ -72,6 +73,7 @@ pub struct NonFungibleToken {
// required by approval extension
pub approvals_by_id: Option<LookupMap<TokenId, HashMap<AccountId, u64>>>,
pub next_approval_id_by_id: Option<LookupMap<TokenId, u64>>,
pub royalties: Option<Royalties>,
}

#[derive(BorshStorageKey, BorshSerialize)]
Expand All @@ -81,18 +83,20 @@ pub enum StorageKey {
}

impl NonFungibleToken {
pub fn new<Q, R, S, T>(
pub fn new<Q, R, S, T, Y>(
owner_by_id_prefix: Q,
owner_id: AccountId,
token_metadata_prefix: Option<R>,
enumeration_prefix: Option<S>,
approval_prefix: Option<T>,
royalties_prefix: Option<Y>,
) -> Self
where
Q: IntoStorageKey,
R: IntoStorageKey,
S: IntoStorageKey,
T: IntoStorageKey,
Y: IntoStorageKey,
{
let (approvals_by_id, next_approval_id_by_id) = if let Some(prefix) = approval_prefix {
let prefix: Vec<u8> = prefix.into_storage_key();
Expand All @@ -112,6 +116,7 @@ impl NonFungibleToken {
tokens_per_owner: enumeration_prefix.map(LookupMap::new),
approvals_by_id,
next_approval_id_by_id,
royalties: royalties_prefix.map(Royalties::new),
};
this.measure_min_token_storage_cost();
this
Expand Down Expand Up @@ -426,7 +431,10 @@ impl NonFungibleTokenCore for NonFungibleToken {
msg: String,
) -> PromiseOrValue<bool> {
assert_one_yocto();
require!(env::prepaid_gas() > GAS_FOR_NFT_TRANSFER_CALL, "More gas is required");
require!(
env::prepaid_gas() > GAS_FOR_NFT_TRANSFER_CALL + GAS_FOR_RESOLVE_TRANSFER,
Copy link
Contributor

Choose a reason for hiding this comment

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

revert this change please

"More gas is required"
);
let sender_id = env::predecessor_account_id();
let (old_owner, old_approvals) =
self.internal_transfer(&sender_id, &receiver_id, &token_id, approval_id, memo);
Expand Down
40 changes: 40 additions & 0 deletions near-contract-standards/src/non_fungible_token/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,43 @@ macro_rules! impl_non_fungible_token_enumeration {
}
};
}

/// Non-fungible enumeration adds the extension standard offering several
/// view-only methods to get token supply, tokens per owner, etc.
#[macro_export]
macro_rules! impl_non_fungible_token_payout {
($contract: ident, $token: ident) => {
use $crate::non_fungible_token::payout::NonFungibleTokenPayout;

#[near_bindgen]
impl NonFungibleTokenPayout for $contract {
#[allow(unused_variables)]
fn nft_payout(
&self,
token_id: String,
balance: U128,
max_len_payout: Option<u32>,
) -> Payout {
let owner_id = self.tokens.owner_by_id.get(&token_id).expect("No such token_id");
self.royalties
.get()
.map_or(Payout::default(), |r| r.create_payout(balance.0, &owner_id))
}
#[payable]
fn nft_transfer_payout(
&mut self,
receiver_id: AccountId,
token_id: String,
approval_id: Option<u64>,
memo: Option<String>,
balance: U128,
max_len_payout: Option<u32>,
) -> Payout {
assert_one_yocto();
let payout = self.nft_payout(token_id.clone(), balance, max_len_payout);
self.nft_transfer(receiver_id, token_id, approval_id, memo);
payout
}
}
};
}
3 changes: 3 additions & 0 deletions near-contract-standards/src/non_fungible_token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ mod macros;
/// Metadata traits and implementation according to the [NFT enumeration standard](https://nomicon.io/Standards/NonFungibleToken/Metadata.html).
/// This covers both the contract metadata and the individual token metadata.
pub mod metadata;

pub mod payout;
Copy link
Contributor

Choose a reason for hiding this comment

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

Module isn't documented here or in .../payout/mod.rs,

can you describe this and link to the spec there using //! rustdoc comments at the top of the file, please?


/// The Token struct for the non-fungible token.
mod token;
pub use self::token::{Token, TokenId};
Expand Down
36 changes: 36 additions & 0 deletions near-contract-standards/src/non_fungible_token/payout/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
mod payout_impl;

use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::json_types::U128;
use near_sdk::AccountId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Default, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
#[serde(crate = "near_sdk::serde")]

pub struct Payout {
pub payout: HashMap<AccountId, U128>,
}
#[derive(Deserialize, Serialize, BorshDeserialize, BorshSerialize, Default, Debug)]
#[serde(crate = "near_sdk::serde")]
pub struct Royalties {
key_prefix: Vec<u8>,
pub accounts: HashMap<AccountId, u8>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Also should be basis points.

Copy link
Author

Choose a reason for hiding this comment

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

No Problem

pub percent: u8,
Copy link
Contributor

Choose a reason for hiding this comment

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

}
/// Offers methods helpful in determining account ownership of NFTs and provides a way to page through NFTs per owner, determine total supply, etc.
pub trait NonFungibleTokenPayout {
fn nft_payout(&self, token_id: String, balance: U128, max_len_payout: Option<u32>) -> Payout;
/// Given a `token_id` and NEAR-denominated balance, transfer the token
/// and return the `Payout` struct for the given token. Panic if the
/// length of the payout exceeds `max_len_payout.`
fn nft_transfer_payout(
&mut self,
receiver_id: AccountId,
token_id: String,
approval_id: Option<u64>,
memo: Option<String>,
balance: U128,
max_len_payout: Option<u32>,
) -> Payout;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use super::NonFungibleTokenPayout;
use crate::non_fungible_token::core::NonFungibleTokenCore;
use crate::non_fungible_token::payout::*;
use crate::non_fungible_token::NonFungibleToken;
use near_sdk::assert_one_yocto;
use near_sdk::{require, AccountId, Balance, IntoStorageKey};
use std::collections::HashMap;
impl Royalties {
Comment on lines +7 to +8
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
use std::collections::HashMap;
impl Royalties {
use std::collections::HashMap;
impl Royalties {

/* pub fn new(accounts: HashMap<AccountId, u8>, percent: u8) -> Self {
let this = Self { accounts, percent };
this.validate();
this
} */
Comment on lines +9 to +13
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/* pub fn new(accounts: HashMap<AccountId, u8>, percent: u8) -> Self {
let this = Self { accounts, percent };
this.validate();
this
} */

pub fn new<S>(key_prefix: S) -> Self
where
S: IntoStorageKey,
{
let temp_accounts: HashMap<AccountId, u8> = HashMap::new();
let this =
Self { key_prefix: key_prefix.into_storage_key(), accounts: temp_accounts, percent: 0 };
this.validate();
this
}
pub(crate) fn validate(&self) {
require!(self.percent <= 100, "royalty percent must be between 0 - 100");
require!(
self.accounts.len() <= 10,
"can only have a maximum of 10 accounts spliting royalties"
);
let mut total: u8 = 0;
self.accounts.iter().for_each(|(_, percent)| {
require!(*percent <= 100, "each royalty should be less than 100");
total += percent;
});
require!(total <= 100, "total percent of each royalty split must be less than 100")
}
pub fn create_payout(&self, balance: Balance, owner_id: &AccountId) -> Payout {
let royalty_payment = apply_percent(self.percent, balance);
let mut payout = Payout {
payout: self
.accounts
.iter()
.map(|(account, percent)| {
(account.clone(), apply_percent(*percent, royalty_payment).into())
})
.collect(),
};
let rest = balance - royalty_payment;
let owner_payout: u128 = payout.payout.get(owner_id).map_or(0, |x| x.0) + rest;
payout.payout.insert(owner_id.clone(), owner_payout.into());
payout
}
}

fn apply_percent(percent: u8, int: u128) -> u128 {
int * percent as u128 / 100u128
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like this can overflow, are there any guarantees around this? Probably want to do a checked multiplication with an expect to make sure someone doesn't need overflow-checks enabled to catch this

}

impl NonFungibleTokenPayout for NonFungibleToken {
#[allow(unused_variables)]
fn nft_payout(&self, token_id: String, balance: U128, max_len_payout: Option<u32>) -> Payout {
let owner_id = self.owner_by_id.get(&token_id).expect("No such token_id");
self.royalties.as_ref().map_or(Payout::default(), |r| r.create_payout(balance.0, &owner_id))
}

fn nft_transfer_payout(
&mut self,
receiver_id: AccountId,
token_id: String,
approval_id: Option<u64>,
memo: Option<String>,
balance: U128,
max_len_payout: Option<u32>,
) -> Payout {
assert_one_yocto();
let payout = self.nft_payout(token_id.clone(), balance, max_len_payout);
self.nft_transfer(receiver_id, token_id, approval_id, memo);
payout
}
}