Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Thang Tran committed Jun 8, 2022
0 parents commit 3340252
Show file tree
Hide file tree
Showing 11 changed files with 690 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
Cargo.lock
22 changes: 22 additions & 0 deletions Cargo.toml
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
6 changes: 6 additions & 0 deletions build.sh
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
76 changes: 76 additions & 0 deletions src/internal.rs
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
}
}
133 changes: 133 additions & 0 deletions src/lib.rs
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))
}
}
111 changes: 111 additions & 0 deletions src/nft_callback.rs
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);
}
}
Loading

0 comments on commit 3340252

Please sign in to comment.