use crate::mt::{ MultiFungibleTokenCore, MtTransfer, MultiFungibleTokenResolver };
use near_sdk::borsh::{ self, BorshDeserialize, BorshSerialize };
use near_sdk::collections::{ LookupMap, TreeMap };
use near_sdk::json_types::U128;
use near_sdk::{
BorshStorageKey,
env,
ext_contract,
log,
require,
AccountId,
Balance,
Gas,
IntoStorageKey,
PromiseOrValue,
PromiseResult,
StorageUsage,
};
use crate::mt::utils::assert_at_least_one_yocto;
use crate::mt::base::metadata::{ MtToken };
use crate::mt::error::MtError;
const GAS_FOR_MT_RESOLVE_TRANSFER: Gas = Gas(5_000_000_000_000);
const GAS_FOR_FT_TRANSFER_CALL: Gas = Gas(25_000_000_000_000 + GAS_FOR_MT_RESOLVE_TRANSFER.0);
#[derive(BorshStorageKey, BorshSerialize)]
pub enum StorageKey {
AccountsPerToken {
token_id: Vec<u8>,
},
}
#[ext_contract(ext_self)]
pub trait ExtMultiFungibleTokenResolver {
fn mt_resolve_transfer(
&mut self,
token_ids: Vec<AccountId>,
sender_id: AccountId,
receiver_id: AccountId,
amounts: Vec<U128>
) -> Vec<U128>;
}
#[ext_contract(ext_fungible_token_receiver)]
pub trait MultiFungibleTokenReceiver {
fn mt_on_transfer(
&mut self,
sender_id: AccountId,
token_ids: Vec<AccountId>,
amounts: Vec<U128>,
msg: String
) -> PromiseOrValue<Vec<U128>>;
}
#[derive(BorshDeserialize, BorshSerialize)]
pub struct MultiFungibleToken {
pub tokens: TreeMap<AccountId, MtToken>,
pub account_storage_usage: StorageUsage,
}
impl MultiFungibleToken {
pub fn new<S>(prefix: S) -> Self where S: IntoStorageKey {
let mut this = Self {
tokens: TreeMap::new(prefix),
account_storage_usage: 0,
};
this.measure_account_storage_usage();
this
}
pub(crate) fn internal_get_token(&self, token_id: &AccountId) -> MtToken {
self.tokens.get(token_id).expect(&MtError::NotFoundToken.to_string())
}
fn measure_account_storage_usage(&mut self) {
let tmp_token_id = AccountId::new_unchecked("b".repeat(64));
self.tokens.insert(
&tmp_token_id,
&(MtToken {
accounts: LookupMap::new(StorageKey::AccountsPerToken {
token_id: env::sha256(tmp_token_id.as_bytes()),
}),
total_supply: 0,
})
);
let initial_storage_usage = env::storage_usage();
let tmp_account_id = AccountId::new_unchecked("a".repeat(64));
let mut mft = self.tokens.get(&tmp_token_id).expect("Not found token");
mft.accounts.insert(&tmp_account_id, &0u128);
self.account_storage_usage = env::storage_usage() - initial_storage_usage;
mft.accounts.remove(&tmp_account_id);
self.tokens.remove(&tmp_token_id);
}
pub fn internal_unwrap_balance_of(&self, mft: &MtToken, account_id: &AccountId) -> Balance {
match mft.accounts.get(account_id) {
Some(balance) => balance,
None => {
0
}
}
}
pub fn internal_deposit(
&mut self,
token_id: &AccountId,
account_id: &AccountId,
amount: Balance
) {
let mut mft = self.internal_get_token(token_id);
let balance = self.internal_unwrap_balance_of(&mft, account_id);
if let Some(new_balance) = balance.checked_add(amount) {
mft.accounts.insert(account_id, &new_balance);
mft.total_supply = mft.total_supply
.checked_add(amount)
.unwrap_or_else(|| env::panic_str("Total supply overflow"));
self.tokens.insert(&token_id, &mft);
} else {
env::panic_str("Balance overflow");
}
}
pub fn internal_withdraw(
&mut self,
token_id: &AccountId,
account_id: &AccountId,
amount: Balance
) {
let mut mft = self.internal_get_token(token_id);
let balance = self.internal_unwrap_balance_of(&mft, account_id);
if let Some(new_balance) = balance.checked_sub(amount) {
mft.accounts.insert(account_id, &new_balance);
mft.total_supply = mft.total_supply
.checked_sub(amount)
.unwrap_or_else(|| env::panic_str("Total supply overflow"));
self.tokens.insert(&token_id, &mft);
} else {
env::panic_str("The account doesn't have enough balance");
}
}
pub fn internal_transfer_batch(
&mut self,
token_ids: &Vec<AccountId>,
sender_id: &AccountId,
receiver_id: &AccountId,
amounts: &Vec<U128>,
memo: Option<String>
) {
for i in 0..token_ids.len() {
let _token_id = token_ids.get(i).expect("Invalid params");
let _amount = amounts.get(i).expect("Invalid params");
self.internal_transfer(&_token_id, &sender_id, &receiver_id, _amount.0, memo.clone());
}
(MtTransfer {
token_ids,
old_owner_id: sender_id,
new_owner_id: receiver_id,
amounts,
memo: memo.as_deref(),
}).emit();
}
pub fn internal_transfer(
&mut self,
token_id: &AccountId,
sender_id: &AccountId,
receiver_id: &AccountId,
amount: Balance,
_memo: Option<String>
) {
require!(sender_id != receiver_id, "Sender and receiver should be different");
require!(amount > 0, "The amount should be a positive number");
self.internal_withdraw(token_id, sender_id, amount);
self.internal_deposit(token_id, receiver_id, amount);
}
pub fn internal_register_account(&mut self, token_id: &AccountId, account_id: &AccountId) {
let mut mft = self.internal_get_token(token_id);
if mft.accounts.insert(account_id, &0).is_some() {
env::panic_str("The account is already registered");
}
}
pub fn internal_on_ft_transfer(
&mut self,
ft_token_id: &AccountId,
sender_id: &AccountId,
amount: &U128
) -> PromiseOrValue<U128> {
self.internal_deposit(&ft_token_id, &sender_id, amount.0);
PromiseOrValue::Value(U128::from(0))
}
}
impl MultiFungibleTokenCore for MultiFungibleToken {
fn mt_batch_transfer(
&mut self,
receiver_id: AccountId,
token_ids: Vec<AccountId>,
amounts: Vec<U128>,
memo: Option<String>
) {
assert_at_least_one_yocto();
let sender_id = env::predecessor_account_id();
assert_eq!(token_ids.len(), amounts.len(), "Invalid params");
self.internal_transfer_batch(&token_ids, &sender_id, &receiver_id, &amounts, memo)
}
fn mt_batch_transfer_call(
&mut self,
receiver_id: AccountId,
token_ids: Vec<AccountId>,
amounts: Vec<U128>,
memo: Option<String>,
msg: String
) -> PromiseOrValue<U128> {
assert_at_least_one_yocto();
assert_eq!(token_ids.len(), amounts.len(), "Invalid params");
require!(
env::prepaid_gas() > GAS_FOR_FT_TRANSFER_CALL + GAS_FOR_MT_RESOLVE_TRANSFER,
"More gas is required"
);
let sender_id = env::predecessor_account_id();
self.internal_transfer_batch(&token_ids, &sender_id, &receiver_id, &amounts, memo);
ext_fungible_token_receiver
::ext(receiver_id.clone())
.with_static_gas(env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL)
.mt_on_transfer(sender_id.clone(), token_ids.clone(), amounts.clone(), msg)
.then(
ext_self
::ext(env::current_account_id())
.with_static_gas(GAS_FOR_MT_RESOLVE_TRANSFER)
.mt_resolve_transfer(token_ids.clone(), sender_id, receiver_id, amounts.clone())
)
.into()
}
fn mt_total_supply(&self, token_id: AccountId) -> U128 {
let mft = self.internal_get_token(&token_id);
mft.total_supply.into()
}
fn mt_balance_of(&self, account_id: AccountId, token_id: AccountId) -> U128 {
let mft = self.internal_get_token(&token_id);
mft.accounts.get(&account_id).unwrap_or(0).into()
}
fn mt_add_token(&mut self, token_id: AccountId) {
self.tokens.insert(
&token_id,
&(MtToken {
accounts: LookupMap::new(StorageKey::AccountsPerToken {
token_id: env::sha256(token_id.as_bytes()),
}),
total_supply: 0,
})
);
}
}
impl MultiFungibleToken {
pub fn internal_mt_resolve_transfer(
&mut self,
token_ids: &Vec<AccountId>,
sender_id: &AccountId,
receiver_id: AccountId,
amounts: &Vec<U128>
) -> Vec<U128> {
let unused_amounts = match env::promise_result(0) {
PromiseResult::NotReady => env::abort(),
PromiseResult::Successful(value) => {
if let Ok(unused_amounts) = near_sdk::serde_json::from_slice::<Vec<U128>>(&value) {
if unused_amounts.len() < amounts.len() {
amounts
.iter()
.map(|_| U128::from(0))
.collect()
} else {
amounts
.iter()
.enumerate()
.map(|(i, amount)| {
let zero = U128::from(0);
let unused_amount = unused_amounts.get(i).unwrap_or_else(|| &zero);
U128::from(std::cmp::min(amount.0, unused_amount.0))
})
.collect()
}
} else {
amounts.clone()
}
}
PromiseResult::Failed => amounts.clone(),
};
for i in 0..token_ids.len() {
let _token_id = token_ids.get(i).expect("Invalid resolve params");
let _amount = amounts.get(i).expect("Invalid resolve params").0;
let unused_amount = unused_amounts.get(i).expect("Invalid unused params").0;
let mut mft = self.internal_get_token(&_token_id);
if unused_amount > 0 {
let receiver_balance = mft.accounts.get(&receiver_id).unwrap_or(0);
if receiver_balance > 0 {
let refund_amount = std::cmp::min(receiver_balance, unused_amount);
mft.accounts.insert(&receiver_id, &(receiver_balance - refund_amount));
if let Some(sender_balance) = mft.accounts.get(sender_id) {
mft.accounts.insert(sender_id, &(sender_balance + refund_amount));
log!("Refund {} from {} to {}", refund_amount, receiver_id, sender_id);
} else {
mft.total_supply -= refund_amount;
log!("The account of the sender was deleted");
}
}
}
}
unused_amounts
}
}
impl MultiFungibleTokenResolver for MultiFungibleToken {
fn mt_resolve_transfer(
&mut self,
sender_id: AccountId,
receiver_id: AccountId,
token_ids: Vec<AccountId>,
amounts: Vec<U128>
) -> Vec<U128> {
let res = self.internal_mt_resolve_transfer(&token_ids, &sender_id, receiver_id, &amounts);
let result = res
.iter()
.map(|el| U128::from(el.0))
.collect();
result
}
}