-
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.
- Loading branch information
Thang Tran
committed
Jun 8, 2022
0 parents
commit 3340252
Showing
11 changed files
with
690 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,22 @@ | ||
[package] | ||
name = "market-contract" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
near-sdk = "3.1.0" | ||
|
||
[lib] | ||
crate-type = ["cdylib", "rlib"] | ||
|
||
[profile.release] | ||
codegen-units = 1 | ||
# Tell `rustc` to optimize for small code size. | ||
opt-level = "z" | ||
lto = true | ||
debug = false | ||
panic = "abort" | ||
# Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801 | ||
overflow-checks = true |
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,6 @@ | ||
#!/bin/bash | ||
set -e | ||
|
||
RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release | ||
mkdir -p out | ||
cp target/wasm32-unknown-unknown/release/*.wasm out/market-contract.wasm |
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,76 @@ | ||
use crate::*; | ||
|
||
#[near_bindgen] | ||
impl Contract { | ||
pub(crate) fn internal_remove_sale( | ||
&mut self, | ||
nft_contract_id: AccountId, | ||
token_id: TokenId, | ||
) -> Sale { | ||
let contract_and_token_id = format!("{}{}{}", nft_contract_id, DELIMETER, token_id); | ||
let sale = self | ||
.sales | ||
.remove(&contract_and_token_id) | ||
.expect("Not found sale"); | ||
let mut by_owner_id = self | ||
.by_owner_id | ||
.get(&sale.owner_id) | ||
.expect("Not found sale by owner"); | ||
by_owner_id.remove(&contract_and_token_id); | ||
self.by_owner_id.insert(&sale.owner_id, &by_owner_id); | ||
if by_owner_id.is_empty() { | ||
self.by_owner_id.remove(&sale.owner_id); | ||
} else { | ||
self.by_owner_id.insert(&sale.owner_id, &by_owner_id); | ||
}; | ||
|
||
let mut by_contract_id = self | ||
.by_contract_id | ||
.get(&nft_contract_id) | ||
.expect("Not found sale by contract_id"); | ||
|
||
by_contract_id.remove(&token_id); | ||
if by_contract_id.is_empty() { | ||
self.by_contract_id.remove(&nft_contract_id); | ||
} else { | ||
self.by_contract_id | ||
.insert(&nft_contract_id, &by_contract_id); | ||
} | ||
|
||
sale | ||
} | ||
|
||
pub(crate) fn internal_payout(&mut self, buyer_id: AccountId, price: U128) -> U128 { | ||
let payout_option = promise_result_as_success().and_then(|value| { | ||
let payout_object = | ||
near_sdk::serde_json::from_slice::<Payout>(&value).expect("Invalid payout object"); | ||
if payout_object.payout.len() > 10 || payout_object.payout.is_empty() { | ||
env::log("Cannot have more than 10 royalities".as_bytes()); | ||
None | ||
} else { | ||
let mut remainder = price.0; | ||
for &value in payout_object.payout.values() { | ||
remainder = remainder.checked_sub(value.0)?; | ||
} | ||
|
||
if remainder == 0 || remainder == 1 { | ||
Some(payout_object.payout) | ||
} else { | ||
None | ||
} | ||
} | ||
}); | ||
|
||
let payout = if let Some(payout_option) = payout_option { | ||
payout_option | ||
} else { | ||
Promise::new(buyer_id).transfer(u128::from(price)); | ||
return price; | ||
}; | ||
|
||
for (receiver_id, amount) in payout { | ||
Promise::new(receiver_id).transfer(amount.into()); | ||
} | ||
price | ||
} | ||
} |
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,133 @@ | ||
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; | ||
use near_sdk::collections::{LookupMap, UnorderedMap, UnorderedSet}; | ||
use near_sdk::json_types::U128; | ||
use near_sdk::serde::{Deserialize, Serialize}; | ||
use near_sdk::{ | ||
env, env::STORAGE_PRICE_PER_BYTE, ext_contract, log, near_bindgen, promise_result_as_success, | ||
AccountId, Balance, CryptoHash, Gas, PanicOnDefault, Promise, | ||
}; | ||
|
||
use crate::internal::*; | ||
use crate::nft_callback::*; | ||
use crate::sale::*; | ||
use crate::sale_view::*; | ||
use crate::uses::*; | ||
use crate::uses_view::*; | ||
use crate::utils::*; | ||
|
||
mod internal; | ||
mod nft_callback; | ||
mod sale; | ||
mod sale_view; | ||
mod uses; | ||
mod uses_view; | ||
mod utils; | ||
|
||
const STORAGE_PER_SALE: u128 = 1000 * STORAGE_PRICE_PER_BYTE; | ||
static DELIMETER: &str = "."; | ||
|
||
pub type TokenId = String; | ||
pub type NFTContractId = String; | ||
pub type SalePriceInYoctoNear = U128; | ||
pub type UsePriceInYoctoNear = U128; | ||
pub type ContractAndTokenId = String; // nft-tutorial.vbi.dev.testnet.VBI_NFT#01 | ||
|
||
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize)] | ||
#[serde(crate = "near_sdk::serde")] | ||
pub struct Sale { | ||
pub owner_id: AccountId, | ||
pub approval_id: u64, | ||
pub nft_contract_id: NFTContractId, | ||
pub token_id: TokenId, | ||
pub sale_conditions: SalePriceInYoctoNear, | ||
} | ||
|
||
#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize)] | ||
#[serde(crate = "near_sdk::serde")] | ||
pub struct Uses { | ||
pub owner_id: AccountId, | ||
pub nft_contract_id: NFTContractId, | ||
pub token_id: TokenId, | ||
pub use_conditions: UsePriceInYoctoNear, | ||
} | ||
|
||
#[near_bindgen] | ||
#[derive(BorshSerialize, BorshDeserialize, PanicOnDefault)] | ||
pub struct Contract { | ||
pub owner_id: AccountId, | ||
// Sales token | ||
pub sales: UnorderedMap<ContractAndTokenId, Sale>, | ||
pub uses: UnorderedMap<ContractAndTokenId, Uses>, | ||
// Sales list by ID | ||
pub by_owner_id: LookupMap<AccountId, UnorderedSet<ContractAndTokenId>>, | ||
pub by_contract_id: LookupMap<NFTContractId, UnorderedSet<TokenId>>, | ||
pub storage_deposit: LookupMap<AccountId, Balance>, | ||
} | ||
|
||
#[derive(BorshSerialize, BorshDeserialize)] | ||
pub enum StorageKey { | ||
SaleKey, | ||
UsesKey, | ||
ByOwnerIdKey, | ||
InnerByOwnerIdKey { account_id_hash: CryptoHash }, | ||
ByContractIdKey, | ||
InnerByContractIdKey { account_id_hash: CryptoHash }, | ||
StorageDepositKey, | ||
} | ||
|
||
#[near_bindgen] | ||
impl Contract { | ||
#[init] | ||
pub fn new(owner_id: AccountId) -> Self { | ||
Self { | ||
owner_id, | ||
sales: UnorderedMap::new(StorageKey::SaleKey.try_to_vec().unwrap()), | ||
uses: UnorderedMap::new(StorageKey::UsesKey.try_to_vec().unwrap()), | ||
by_owner_id: LookupMap::new(StorageKey::ByOwnerIdKey.try_to_vec().unwrap()), | ||
by_contract_id: LookupMap::new(StorageKey::ByContractIdKey.try_to_vec().unwrap()), | ||
storage_deposit: LookupMap::new(StorageKey::StorageDepositKey.try_to_vec().unwrap()), | ||
} | ||
} | ||
|
||
#[payable] | ||
pub fn storage_deposit(&mut self, account_id: Option<AccountId>) { | ||
let storage_account_id = account_id.unwrap_or(env::predecessor_account_id()); | ||
let deposit = env::attached_deposit(); | ||
assert!( | ||
deposit >= STORAGE_PER_SALE, | ||
"Require deposit minimum of {}", | ||
STORAGE_PER_SALE | ||
); | ||
|
||
let mut balance = self.storage_deposit.get(&storage_account_id).unwrap_or(0); | ||
balance += deposit; | ||
self.storage_deposit.insert(&storage_account_id, &balance); | ||
} | ||
|
||
#[payable] | ||
pub fn storage_withdraw(&mut self) { | ||
assert_one_yocto(); | ||
let owner_id = env::predecessor_account_id(); | ||
let amount = self.storage_deposit.remove(&owner_id).unwrap_or(0); | ||
let sales = self.by_owner_id.get(&owner_id); | ||
let len = sales.map(|s| s.len()).unwrap_or_default(); | ||
let storage_required = u128::from(len) * STORAGE_PER_SALE; | ||
assert!(amount >= storage_required); | ||
let diff = amount - storage_required; | ||
if diff > 0 { | ||
Promise::new(owner_id.clone()).transfer(diff); | ||
} | ||
|
||
if storage_required > 0 { | ||
self.storage_deposit.insert(&owner_id, &storage_required); | ||
} | ||
} | ||
|
||
pub fn storage_minimum_balance(&self) -> U128 { | ||
U128(STORAGE_PER_SALE) | ||
} | ||
|
||
pub fn storage_balance_of(&self, account_id: AccountId) -> U128 { | ||
U128(self.storage_deposit.get(&account_id).unwrap_or(0)) | ||
} | ||
} |
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,111 @@ | ||
use crate::*; | ||
|
||
pub trait NonFungibleTokenApprovalReceiver { | ||
fn nft_on_approve( | ||
&mut self, | ||
token_id: TokenId, | ||
owner_id: AccountId, | ||
approval_id: u64, | ||
msg: String, | ||
); | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
#[serde(crate = "near_sdk::serde")] | ||
pub struct ListingArgs { | ||
pub sale_condition: SalePriceInYoctoNear, | ||
pub use_condition: UsePriceInYoctoNear, | ||
} | ||
|
||
#[near_bindgen] | ||
impl NonFungibleTokenApprovalReceiver for Contract { | ||
/** | ||
msg: {"sale_condition": "100000000000", "use_conditions:"1000000000"} | ||
*/ | ||
fn nft_on_approve( | ||
&mut self, | ||
token_id: TokenId, | ||
owner_id: AccountId, | ||
approval_id: u64, | ||
msg: String, | ||
) { | ||
let nft_contract_id = env::predecessor_account_id(); | ||
let signer_id = env::signer_account_id(); | ||
|
||
assert_ne!( | ||
nft_contract_id, signer_id, | ||
"nft_on_approve should only called via cross contract call" | ||
); | ||
assert_eq!(signer_id, owner_id, "owner_id should be signer_id"); | ||
|
||
// Check cover storage | ||
let storage_balance = self.storage_deposit.get(&signer_id).unwrap_or(0); | ||
let storage_minimum_amount = self.storage_minimum_balance().0; | ||
let storage_required = | ||
(self.get_supply_by_owner_id(signer_id.clone()).0 + 1) * storage_minimum_amount; | ||
|
||
assert!( | ||
storage_balance >= storage_required, | ||
"Insufficient storage paid: {}, for {} sales at {} rate of per sale", | ||
storage_balance, | ||
storage_required / STORAGE_PER_SALE, | ||
STORAGE_PER_SALE | ||
); | ||
|
||
let ListingArgs { | ||
sale_condition, | ||
use_condition, | ||
} = near_sdk::serde_json::from_str(&msg).expect("Not valid Sale Args"); | ||
let contract_and_token_id = format!("{}{}{}", nft_contract_id.clone(), DELIMETER, token_id); | ||
|
||
self.sales.insert( | ||
&contract_and_token_id, | ||
&Sale { | ||
owner_id: owner_id.clone(), | ||
approval_id, | ||
nft_contract_id: nft_contract_id.clone(), | ||
token_id: token_id.clone(), | ||
sale_conditions: sale_condition, | ||
}, | ||
); | ||
|
||
self.uses.insert( | ||
&contract_and_token_id, | ||
&Uses { | ||
owner_id: owner_id.clone(), | ||
nft_contract_id: nft_contract_id.clone(), | ||
token_id: token_id.clone(), | ||
use_conditions: use_condition, | ||
}, | ||
); | ||
|
||
let mut by_owner_id = self.by_owner_id.get(&owner_id).unwrap_or_else(|| { | ||
UnorderedSet::new( | ||
StorageKey::InnerByOwnerIdKey { | ||
account_id_hash: hash_account_id(&owner_id), | ||
} | ||
.try_to_vec() | ||
.unwrap(), | ||
) | ||
}); | ||
|
||
by_owner_id.insert(&contract_and_token_id); | ||
self.by_owner_id.insert(&owner_id, &by_owner_id); | ||
|
||
let mut by_contract_id = self | ||
.by_contract_id | ||
.get(&nft_contract_id) | ||
.unwrap_or_else(|| { | ||
UnorderedSet::new( | ||
StorageKey::InnerByContractIdKey { | ||
account_id_hash: hash_account_id(&nft_contract_id), | ||
} | ||
.try_to_vec() | ||
.unwrap(), | ||
) | ||
}); | ||
by_contract_id.insert(&token_id); | ||
self.by_contract_id | ||
.insert(&nft_contract_id, &by_contract_id); | ||
} | ||
} |
Oops, something went wrong.