Skip to content

Commit

Permalink
chore: use Result instead of panicking in nep141 functions
Browse files Browse the repository at this point in the history
  • Loading branch information
encody committed Sep 21, 2023
1 parent f384da2 commit 26d4ee2
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 44 deletions.
9 changes: 6 additions & 3 deletions macros/src/standard/nep141.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ pub fn expand(meta: Nep141Meta) -> Result<TokenStream, darling::Error> {
revert: false,
};

Nep141Controller::transfer(self, &transfer);
Nep141Controller::transfer(self, &transfer)
.unwrap_or_else(|e| #near_sdk::env::panic_str(&e.to_string()));
}

#[payable]
Expand Down Expand Up @@ -108,7 +109,8 @@ pub fn expand(meta: Nep141Meta) -> Result<TokenStream, darling::Error> {
revert: false,
};

Nep141Controller::transfer(self, &transfer);
Nep141Controller::transfer(self, &transfer)
.unwrap_or_else(|e| #near_sdk::env::panic_str(&e.to_string()));

let receiver_gas = prepaid_gas
.0
Expand Down Expand Up @@ -180,7 +182,8 @@ pub fn expand(meta: Nep141Meta) -> Result<TokenStream, darling::Error> {
revert: true,
};

Nep141Controller::transfer(self, &transfer);
Nep141Controller::transfer(self, &transfer)
.unwrap_or_else(|e| env::panic_str(&e.to_string()));

refund_amount
} else {
Expand Down
81 changes: 81 additions & 0 deletions src/standard/nep141/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//! Error types for NEP-141 implementations.

use near_sdk::AccountId;
use thiserror::Error;

/// Errors that may occur when withdrawing (burning) tokens.
#[derive(Debug, Error)]
pub enum WithdrawError {
/// The account does not have enough balance to withdraw the given amount.
#[error(transparent)]
BalanceUnderflow(#[from] BalanceUnderflowError),
/// The total supply is less than the amount to be burned.
#[error(transparent)]
TotalSupplyUnderflow(#[from] TotalSupplyUnderflowError),
}

/// An account does not have enough balance to withdraw the given amount.
#[derive(Debug, Error)]
#[error("The account {account_id} does not have enough balance to withdraw {amount} (current balance: {balance}).")]
pub struct BalanceUnderflowError {
/// The account ID.
pub account_id: AccountId,
/// The current balance of the account.
pub balance: u128,
/// The amount of the failed withdrawal attempt.
pub amount: u128,
}

/// The total supply is less than the amount to be burned.
#[derive(Debug, Error)]
#[error("The total supply ({total_supply}) is less than the amount to be burned ({amount}).")]
pub struct TotalSupplyUnderflowError {
/// The total supply.
pub total_supply: u128,
/// The amount of the failed withdrawal attempt.
pub amount: u128,
}

/// Errors that may occur when depositing (minting) tokens.
#[derive(Debug, Error)]
pub enum DepositError {
/// The balance of the receiver would overflow u128.
#[error(transparent)]
BalanceOverflow(#[from] BalanceOverflowError),
/// The total supply would overflow u128.
#[error(transparent)]
TotalSupplyOverflow(#[from] TotalSupplyOverflowError),
}

/// The balance of the account would overflow u128.
#[derive(Debug, Error)]
#[error("The balance of {account_id} ({balance}) plus {amount} would overflow u128.")]
pub struct BalanceOverflowError {
/// The account ID.
pub account_id: AccountId,
/// The current balance of the account.
pub balance: u128,
/// The amount of the failed deposit attempt.
pub amount: u128,
}

/// The total supply would overflow u128.
#[derive(Debug, Error)]
#[error("The total supply ({total_supply}) plus {amount} would overflow u128.")]
pub struct TotalSupplyOverflowError {
/// The total supply.
pub total_supply: u128,
/// The amount of the failed deposit attempt.
pub amount: u128,
}

/// Errors that may occur when transferring tokens.
#[derive(Debug, Error)]
pub enum TransferError {
/// The balance of the receiver would overflow u128.
#[error("Balance of the receiver would overflow u128: {0}")]
ReceiverBalanceOverflow(#[from] BalanceOverflowError),
/// The balance of the sender is insufficient.
#[error("Balance of the sender is insufficient: {0}")]
SenderBalanceUnderflow(#[from] BalanceUnderflowError),
}
136 changes: 102 additions & 34 deletions src/standard/nep141/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

use near_sdk::{
borsh::{self, BorshDeserialize, BorshSerialize},
env, AccountId, BorshStorageKey, Gas,
AccountId, BorshStorageKey, Gas,
};
use serde::{Deserialize, Serialize};

use crate::{slot::Slot, standard::nep297::*, DefaultStorageKey};

pub mod error;
pub use error::*;
pub mod event;
pub use event::*;
pub mod ext;
Expand Down Expand Up @@ -93,21 +95,19 @@ pub trait Nep141Controller {

/// Removes tokens from an account and decreases total supply. No event
/// emission.
///
/// # Panics
///
/// Panics if the current balance of `account_id` is less than `amount` or
/// if `total_supply` is less than `amount`.
fn withdraw_unchecked(&mut self, account_id: &AccountId, amount: u128);
fn withdraw_unchecked(
&mut self,
account_id: &AccountId,
amount: u128,
) -> Result<(), WithdrawError>;

/// Increases the token balance of an account. Updates total supply. No
/// event emission.
///
/// # Panics
///
/// Panics if the balance of `account_id` plus `amount` >= `u128::MAX`, or
/// if the total supply plus `amount` >= `u128::MAX`.
fn deposit_unchecked(&mut self, account_id: &AccountId, amount: u128);
fn deposit_unchecked(
&mut self,
account_id: &AccountId,
amount: u128,
) -> Result<(), DepositError>;

/// Decreases the balance of `sender_account_id` by `amount` and increases
/// the balance of `receiver_account_id` by the same. No change to total
Expand All @@ -122,28 +122,38 @@ pub trait Nep141Controller {
sender_account_id: &AccountId,
receiver_account_id: &AccountId,
amount: u128,
);
) -> Result<(), TransferError>;

/// Performs an NEP-141 token transfer, with event emission.
///
/// # Panics
///
/// See: [`Nep141Controller::transfer_unchecked`]
fn transfer(&mut self, transfer: &Nep141Transfer);
fn transfer(&mut self, transfer: &Nep141Transfer) -> Result<(), TransferError>;

/// Performs an NEP-141 token mint, with event emission.
///
/// # Panics
///
/// See: `Nep141Controller::deposit_unchecked`
fn mint(&mut self, account_id: AccountId, amount: u128, memo: Option<String>);
/// See: [`Nep141Controller::deposit_unchecked`]
fn mint(
&mut self,
account_id: AccountId,
amount: u128,
memo: Option<String>,
) -> Result<(), DepositError>;

/// Performs an NEP-141 token burn, with event emission.
///
/// # Panics
///
/// See: `Nep141Controller::withdraw_unchecked`
fn burn(&mut self, account_id: AccountId, amount: u128, memo: Option<String>);
/// See: [`Nep141Controller::withdraw_unchecked`]
fn burn(
&mut self,
account_id: AccountId,
amount: u128,
memo: Option<String>,
) -> Result<(), WithdrawError>;
}

impl<T: Nep141ControllerInternal> Nep141Controller for T {
Expand All @@ -157,48 +167,78 @@ impl<T: Nep141ControllerInternal> Nep141Controller for T {
Self::slot_total_supply().read().unwrap_or(0)
}

fn withdraw_unchecked(&mut self, account_id: &AccountId, amount: u128) {
fn withdraw_unchecked(
&mut self,
account_id: &AccountId,
amount: u128,
) -> Result<(), WithdrawError> {
if amount != 0 {
let balance = self.balance_of(account_id);
if let Some(balance) = balance.checked_sub(amount) {
Self::slot_account(account_id).write(&balance);
} else {
env::panic_str("Balance underflow");
return Err(BalanceUnderflowError {
account_id: account_id.clone(),
balance,
amount,
}
.into());
}

let total_supply = self.total_supply();
if let Some(total_supply) = total_supply.checked_sub(amount) {
Self::slot_total_supply().write(&total_supply);
} else {
env::panic_str("Total supply underflow");
return Err(TotalSupplyUnderflowError {
total_supply,
amount,
}
.into());
}
}

Ok(())
}

fn deposit_unchecked(&mut self, account_id: &AccountId, amount: u128) {
fn deposit_unchecked(
&mut self,
account_id: &AccountId,
amount: u128,
) -> Result<(), DepositError> {
if amount != 0 {
let balance = self.balance_of(account_id);
if let Some(balance) = balance.checked_add(amount) {
Self::slot_account(account_id).write(&balance);
} else {
env::panic_str("Balance overflow");
return Err(BalanceOverflowError {
account_id: account_id.clone(),
balance,
amount,
}
.into());
}

let total_supply = self.total_supply();
if let Some(total_supply) = total_supply.checked_add(amount) {
Self::slot_total_supply().write(&total_supply);
} else {
env::panic_str("Total supply overflow");
return Err(TotalSupplyOverflowError {
total_supply,
amount,
}
.into());
}
}

Ok(())
}

fn transfer_unchecked(
&mut self,
sender_account_id: &AccountId,
receiver_account_id: &AccountId,
amount: u128,
) {
) -> Result<(), TransferError> {
let sender_balance = self.balance_of(sender_account_id);

if let Some(sender_balance) = sender_balance.checked_sub(amount) {
Expand All @@ -207,17 +247,29 @@ impl<T: Nep141ControllerInternal> Nep141Controller for T {
Self::slot_account(sender_account_id).write(&sender_balance);
Self::slot_account(receiver_account_id).write(&receiver_balance);
} else {
env::panic_str("Receiver balance overflow");
return Err(BalanceOverflowError {
account_id: receiver_account_id.clone(),
balance: receiver_balance,
amount,
}
.into());
}
} else {
env::panic_str("Sender balance underflow");
return Err(BalanceUnderflowError {
account_id: sender_account_id.clone(),
balance: sender_balance,
amount,
}
.into());
}

Ok(())
}

fn transfer(&mut self, transfer: &Nep141Transfer) {
fn transfer(&mut self, transfer: &Nep141Transfer) -> Result<(), TransferError> {
let state = Self::Hook::before_transfer(self, transfer);

self.transfer_unchecked(&transfer.sender_id, &transfer.receiver_id, transfer.amount);
self.transfer_unchecked(&transfer.sender_id, &transfer.receiver_id, transfer.amount)?;

Nep141Event::FtTransfer(vec![FtTransferData {
old_owner_id: transfer.sender_id.clone(),
Expand All @@ -228,12 +280,19 @@ impl<T: Nep141ControllerInternal> Nep141Controller for T {
.emit();

Self::Hook::after_transfer(self, transfer, state);

Ok(())
}

fn mint(&mut self, account_id: AccountId, amount: u128, memo: Option<String>) {
fn mint(
&mut self,
account_id: AccountId,
amount: u128,
memo: Option<String>,
) -> Result<(), DepositError> {
let state = Self::Hook::before_mint(self, amount, &account_id);

self.deposit_unchecked(&account_id, amount);
self.deposit_unchecked(&account_id, amount)?;

Self::Hook::after_mint(self, amount, &account_id, state);

Expand All @@ -243,12 +302,19 @@ impl<T: Nep141ControllerInternal> Nep141Controller for T {
memo,
}])
.emit();

Ok(())
}

fn burn(&mut self, account_id: AccountId, amount: u128, memo: Option<String>) {
fn burn(
&mut self,
account_id: AccountId,
amount: u128,
memo: Option<String>,
) -> Result<(), WithdrawError> {
let state = Self::Hook::before_burn(self, amount, &account_id);

self.withdraw_unchecked(&account_id, amount);
self.withdraw_unchecked(&account_id, amount)?;

Self::Hook::after_burn(self, amount, &account_id, state);

Expand All @@ -258,5 +324,7 @@ impl<T: Nep141ControllerInternal> Nep141Controller for T {
memo,
}])
.emit();

Ok(())
}
}
4 changes: 2 additions & 2 deletions tests/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ mod pausable_fungible_token {

let mut c = Contract { storage_usage: 0 };

c.deposit_unchecked(&alice, 100);
c.deposit_unchecked(&alice, 100).unwrap();

let context = VMContextBuilder::new()
.attached_deposit(1)
Expand All @@ -469,7 +469,7 @@ mod pausable_fungible_token {

let mut c = Contract { storage_usage: 0 };

c.deposit_unchecked(&alice, 100);
c.deposit_unchecked(&alice, 100).unwrap();

let context = VMContextBuilder::new()
.attached_deposit(1)
Expand Down
Loading

0 comments on commit 26d4ee2

Please sign in to comment.