pub fn internal_withdraw(&mut self, account_id: &AccountId, amount: Balance) {
let balance = self.internal_unwrap_balance_of(account_id);
if let Some(new_balance) = balance.checked_sub(amount) {
self.accounts.insert(account_id, &new_balance);
self.total_supply = self
.total_supply
.checked_sub(amount)
.unwrap_or_else(|| env::panic_str(ERR_TOTAL_SUPPLY_OVERFLOW));
} else {
env::panic_str("The account doesn't have enough balance");
}
}
pub fn internal_deposit(&mut self, account_id: &AccountId, amount: Balance) {
let balance = self.internal_unwrap_balance_of(account_id);
if let Some(new_balance) = balance.checked_add(amount) {
self.accounts.insert(account_id, &new_balance);
self.total_supply = self
.total_supply
.checked_add(amount)
.unwrap_or_else(|| env::panic_str(ERR_TOTAL_SUPPLY_OVERFLOW));
} else {
env::panic_str("Balance overflow");
}
}
fn ft_transfer(&mut self, receiver_id: AccountId, amount: U128, memo: Option<String>) {
assert_one_yocto();
let sender_id = env::predecessor_account_id();
let amount: Balance = amount.into();
self.internal_transfer(&sender_id, &receiver_id, amount, memo);
}
fn ft_transfer_call(
&mut self,
receiver_id: AccountId,
amount: U128,
memo: Option<String>,
msg: String,
) -> PromiseOrValue<U128> {
assert_one_yocto();
require!(env::prepaid_gas() > GAS_FOR_FT_TRANSFER_CALL, "More gas is required");
let sender_id = env::predecessor_account_id();
let amount: Balance = amount.into();
self.internal_transfer(&sender_id, &receiver_id, amount, memo);
let receiver_gas = env::prepaid_gas()
.0
.checked_sub(GAS_FOR_FT_TRANSFER_CALL.0)
.unwrap_or_else(|| env::panic_str("Prepaid gas overflow"));
// Initiating receiver's call and the callback
ext_ft_receiver::ext(receiver_id.clone())
.with_static_gas(receiver_gas.into())
.ft_on_transfer(sender_id.clone(), amount.into(), msg)
.then(
ext_ft_resolver::ext(env::current_account_id())
.with_static_gas(GAS_FOR_RESOLVE_TRANSFER)
.ft_resolve_transfer(sender_id, receiver_id, amount.into()),
)
.into()
}
impl FungibleTokenResolver for FungibleToken {
fn ft_resolve_transfer(
&mut self,
sender_id: AccountId,
receiver_id: AccountId,
amount: U128,
) -> U128 {
self.internal_ft_resolve_transfer(&sender_id, receiver_id, amount).0.into()
}
}
impl FungibleToken {
/// Internal method that returns the amount of burned tokens in a corner case when the sender
/// has deleted (unregistered) their account while the `ft_transfer_call` was still in flight.
/// Returns (Used token amount, Burned token amount)
pub fn internal_ft_resolve_transfer(
&mut self,
sender_id: &AccountId,
receiver_id: AccountId,
amount: U128,
) -> (u128, u128) {
let amount: Balance = amount.into();
// Get the unused amount from the `ft_on_transfer` call result.
let unused_amount = match env::promise_result(0) {
PromiseResult::NotReady => env::abort(),
PromiseResult::Successful(value) => {
if let Ok(unused_amount) = near_sdk::serde_json::from_slice::<U128>(&value) {
std::cmp::min(amount, unused_amount.0)
} else {
amount
}
}
PromiseResult::Failed => amount,
};
if unused_amount > 0 {
let receiver_balance = self.accounts.get(&receiver_id).unwrap_or(0);
if receiver_balance > 0 {
let refund_amount = std::cmp::min(receiver_balance, unused_amount);
if let Some(new_receiver_balance) = receiver_balance.checked_sub(refund_amount) {
self.accounts.insert(&receiver_id, &new_receiver_balance);
} else {
env::panic_str("The receiver account doesn't have enough balance");
}
if let Some(sender_balance) = self.accounts.get(sender_id) {
if let Some(new_sender_balance) = sender_balance.checked_add(refund_amount) {
self.accounts.insert(sender_id, &new_sender_balance);
} else {
env::panic_str("Sender balance overflow");
}
FtTransfer {
old_owner_id: &receiver_id,
new_owner_id: sender_id,
amount: &U128(refund_amount),
memo: Some("refund"),
}
.emit();
let used_amount = amount
.checked_sub(refund_amount)
.unwrap_or_else(|| env::panic_str(ERR_TOTAL_SUPPLY_OVERFLOW));
return (used_amount, 0);
} else {
// Sender's account was deleted, so we need to burn tokens.
self.total_supply = self
.total_supply
.checked_sub(refund_amount)
.unwrap_or_else(|| env::panic_str(ERR_TOTAL_SUPPLY_OVERFLOW));
log!("The account of the sender was deleted");
FtBurn {
owner_id: &receiver_id,
amount: &U128(refund_amount),
memo: Some("refund"),
}
.emit();
return (amount, refund_amount);
}
}
}
(amount, 0)
}
}
References