//! # Bank Pallet
//! <!-- Original author of paragraph: @pablolteixeira
//!
//! ## Overview
//!
//! Pallet Banks empowers the management, redemption, and transfer of tokens among banks, users, and treasuries.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * Transfer token from user to bank.
//! * Transfer token from bank to user.
//! * Transfer token from bank to bank.
//! * Transfer token from bank to treasury.
//! * Redeem the token of type 'WRAPPED CRYPTO' from a stable bank.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! `transfer_token_from_user_to_bank`: This function transfers a specified amount of a token from
//! the user's account to any token's bank.
//!
//! `transfer_token_from_bank_to_user`: This function transfers a specified amount of a token from
//! any token's bank that holds this token to the user's account.
//!
//! `transfer_token_from_bank_to_bank`: This function allows for the transfer of a specified
//! amount of a token from one token's bank, which holds the token, to a different token's bank.
//!
//! `transfer_token_from_bank_to_treasury`: This function allows for the transfer of a specified
//! amount of a token from one token's bank, which holds the token, to a token's treasury.
//!
//! `redeem`: This function allows the redeemption of token with 'WRAPPED CRYPTO' type from a bank whose
//! token type is 'STABLE'.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
//!
//! ### Change history
//!
//! 09/29/2023 - added benchmarks
#![cfg_attr(not(feature = "std"), no_std)]
/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;
use sp_runtime::{ traits::Zero, FixedPointOperand };
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::WeightInfo;
use traits::{
asset::{ AssetInterface, TokenType },
bank::BankInterface,
subaccounts::{ SubAccounts, AccountOrigin },
treasury::TreasuryInterface,
oracle::OracleInterface,
};
#[frame_support::pallet]
pub mod pallet {
use frame_support::{
pallet_prelude::*,
sp_runtime::{ traits::{ AccountIdConversion, CheckedAdd, CheckedSub }, ArithmeticError },
traits::{
fungibles::{ self, Inspect, Mutate },
tokens::{ Balance, Preservation::Expendable },
UnixTime,
},
PalletId,
};
use super::*;
use frame_system::{ ensure_signed, pallet_prelude::* };
// Balance type alias.
pub type AssetBalanceOf<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
/// Asset id type alias.
pub type AssetIdOf<T> = <T as Config>::AssetId;
/// Account id type alias.
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
/// Type used to represent the transaction_id.
pub type TransactionId = u64;
pub type Decimals = u8;
/// Transaction Enum is used to represent different types of transactions that can happen
/// related to the bank pallet.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub enum Transaction<T: Config> {
TransferFromCreateToBank {
token_bank_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
time: Option<u64>,
},
TransferFromUserToBank {
origin: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
time: u64,
},
TransferFromBankToUser {
destination: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
time: u64,
},
TransferFromBankToBank {
origin_token_bank_id: AssetIdOf<T>,
destination_token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
time: u64,
},
TransferFromBankToTreasury {
token_treasury_id: AssetIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
time: u64,
},
TransferFromBankToPallet {
pallet_id: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
time: u64,
},
TransferFromPalletToBank {
pallet_id: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
time: u64,
},
}
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type AssetId: Member +
Parameter +
Copy +
MaybeSerializeDeserialize +
MaxEncodedLen +
Default +
Zero +
From<u32>;
/// Type to access the Assets Pallet.
type Fungibles: fungibles::Inspect<
Self::AccountId,
AssetId = Self::AssetId,
Balance = Self::AssetBalance
> + //, AssetId = u32> // Hash this
fungibles::Mutate<Self::AccountId> +
fungibles::Create<Self::AccountId> +
fungibles::roles::Inspect<Self::AccountId> +
AssetInterface<
AssetIdOf<Self>,
AssetBalanceOf<Self>,
Self::AccountId,
Decimals,
TokenType
>;
type AssetBalance: Balance +
FixedPointOperand +
MaxEncodedLen +
MaybeSerializeDeserialize +
TypeInfo;
/// Type to access the Treasury Pallet interface.
type TreasuryInterface: TreasuryInterface<
<Self::Fungibles as Inspect<Self::AccountId>>::AssetId,
<Self::Fungibles as Inspect<Self::AccountId>>::Balance,
Self::AccountId
>;
/// Type to access the Oracle Pallet interface.
type OracleInterface: OracleInterface<
<Self::Fungibles as Inspect<Self::AccountId>>::AssetId,
<Self::Fungibles as Inspect<Self::AccountId>>::Balance,
Decimals
>;
/// Bank pallet id.
#[pallet::constant]
type PalletId: Get<PalletId>;
/// Provides an interface to get the actual time in Unix Time.
type TimeProvider: UnixTime;
/// Type to access the sub account pallet
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>;
/// Helper type to hold traits for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper;
// Weight information for extrinsics in this pallet
type WeightInfo: WeightInfo;
/// The maximum length of strings.
#[pallet::constant]
type ProfileStringLimit: Get<u32>;
}
/// Store the amount of tokens inside a bank.
/// asset_id -> asset_id -> asset_balance
#[pallet::storage]
#[pallet::getter(fn get_banks_tokens)]
pub type BankTokens<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
Blake2_128Concat,
AssetIdOf<T>,
AssetBalanceOf<T>,
ValueQuery
>;
/// Store the next transaction id within each bank.
/// asset_id -> transaction_id
#[pallet::storage]
#[pallet::getter(fn get_next_transaction_id)]
pub type NextTransactionId<T: Config> = StorageMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
TransactionId,
ValueQuery
>;
/// Store transactions within each bank.
/// asset_id -> transaction_id -> transaction
#[pallet::storage]
#[pallet::getter(fn get_bank_transactions)]
pub type BankTransactions<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
Blake2_128Concat,
TransactionId,
Transaction<T>
>;
// Pallets use events to inform users when important changes are made.
// https://docs.substrate.io/main-docs/build/events-errors/
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Transfer from user to bank event.
TransferFromUserToBank {
origin: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
},
/// Transfer from bank to user event.
TransferFromBankToUser {
who: AccountIdOf<T>,
destination: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
},
/// Transfer from bank to bank event.
TransferFromBankToBank {
who: AccountIdOf<T>,
origin_token_bank_id: AssetIdOf<T>,
destination_token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
},
/// Transfer from bank to treasury event.
TransferFromBankToTreasury {
who: AccountIdOf<T>,
token_treasury_id: AssetIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
},
/// Transfer from bank to pallet event.
TransferFromBankToPallet {
pallet_id: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
},
/// Transfer from pallet to bank event.
TransferFromPalletToBank {
pallet_id: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
},
/// Redeemed wrapped crypto from a stable bank.
RedeemedFromBank {
who: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_bank_amount: AssetBalanceOf<T>,
token_redeemed_amount: AssetBalanceOf<T>,
},
}
// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
/// Does not have permission to use the bank.
DontHavePermission,
/// Insufficient balance to transfer.
InsufficientBalance,
/// Cannot send a token in the bank to its bank.
CannotSendToSameBank,
/// Control is restricted to banks containing tokens of type 'CRYPTO'.
CryptoBankControlError,
/// Tokens of 'WRAPPED' type do not have a bank.
WrappedCryptoDoesNotHaveBank,
/// The redemption option is exclusive to the bank associated with tokens of the 'STABLE' type.
OnlyStableBankCanRedeem,
/// The bank of tokens of 'STABLE' type only holds tokens of the 'WRAPPED' type or itself.
StableBankOnlyHoldWrappedOrItSelf,
/// Only the tokens of 'WRAPPED' type are redeemable.
OnlyWrappedCryptoAreRedeemable,
/// The token chosen to be redeemed is the same as the bank's token.
CannotRedeemBankToken,
/// The token chosen to be redeemed is not in the bank.
TokenNotFoundInBank,
/// The token amount to be redeemed is equal to zero.
RedeemAmountTooLow,
/// The conversion between types panicked.
ConversionError,
/// Insufficient crypto value in the bank.
InsufficientCryptoValueInBank,
/// Insufficient crypto balance in the bank.
InsufficientCryptoBalanceInBank,
/// Unit asset id not registered.
UnitAssetIdNotStored,
/// The bank of 'CRYPTO' tokens can only receive tokens of type 'WRAPPED', 'STABLE', the UNIT token, or the token itself.
CryptoBankCannotReceiveOtherCryptoTokens
}
// Dispatchable functions allows users to interact with the pallet and invoke state changes.
#[pallet::call(weight(<T as Config>::WeightInfo))]
impl<T: Config> Pallet<T> {
/// The origin can send a token with the ID 'token_id' and an amount of 'token_amount'
/// to the bank with the ID 'token_bank_id'.
///
/// Parameters:
/// - `token_bank_id`: The token ID of the bank.
/// - `token_id`: The token ID of the token that is sent to the bank.
/// - `token_amount`: The amount of token with ID that is sent to the bank.
///
/// Emits `TransferFromUserToBank` event when successful.
#[pallet::call_index(0)]
pub fn transfer_token_from_user_to_bank(
origin: OriginFor<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
if T::Fungibles::is_ecosystem_asset(token_bank_id) {
T::TreasuryInterface::transfer_from_user_to_treasury(
who,
token_bank_id,
token_id,
token_amount
)?;
} else {
// We must verify the existence of the asset ID before checking its token type,
// as the default type in metadata is 'CRYPTO'.
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_bank_id)?;
ensure!(
!T::Fungibles::is_wrapped_crypto(token_bank_id),
Error::<T>::WrappedCryptoDoesNotHaveBank
);
Self::transfer_from_user_to_bank(who, token_bank_id, token_id, token_amount)?;
}
Ok(())
}
/// The token owner can initiate a transfer of a token with the ID 'token_id' and an amount
/// of 'token_amount' from the bank with the ID 'token_bank_id' to the user with the account
/// ID 'destination'.
///
/// Parameters:
/// - `destination`: The users account ID that receives the token transfer from the bank.
/// - `token_bank_id`: The token ID of the bank.
/// - `token_id`: The token ID of the token that is sent to the user from the bank.
/// - `token_amount`: The amount of token with ID that is sent to the user from the bank.
///
/// Emits `TransferFromBankToUser` event when successful.
#[pallet::call_index(1)]
pub fn transfer_token_from_bank_to_user(
origin: OriginFor<T>,
destination: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
// We must verify the existence of the asset ID before checking its token type,
// as the default type in metadata is 'CRYPTO'.
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_bank_id)?;
ensure!(T::Fungibles::is_crypto(token_bank_id), Error::<T>::CryptoBankControlError);
let asset_owner = T::Fungibles::get_owner(token_bank_id)?;
ensure!(asset_owner == who, Error::<T>::DontHavePermission);
Self::transfer_from_bank_to_user(
who,
destination,
token_bank_id,
token_id,
token_amount
)?;
Ok(())
}
/// The token owner can initiate a transfer of a token with the ID 'token_id' and an amount
/// of 'token_amount' from the bank with the ID 'origin_token_bank_id' to the bank with the
/// ID 'destination_token_bank_id'.
///
/// Parameters:
/// - `origin_token_bank_id`: The token ID of the bank that transfer token to the
/// destination bank.
/// - `destination_token_bank_id`: The token ID of the bank that receives token to from the
/// origin bank.
/// - `token_id`: The token ID of the token that is sent to the origin bank from the
/// destination bank.
/// - `token_amount`: The amount of token with ID that is sent to the destination bank.
///
/// Emits `TransferFromBankToBank` event when successful.
#[pallet::call_index(2)]
pub fn transfer_token_from_bank_to_bank(
origin: OriginFor<T>,
origin_token_bank_id: AssetIdOf<T>,
destination_token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
if T::Fungibles::is_ecosystem_asset(destination_token_bank_id) {
Self::transfer_from_bank_to_treasury(
who,
origin_token_bank_id,
destination_token_bank_id,
token_id,
token_amount
)?;
} else {
// ensure that the assets exists
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(origin_token_bank_id)?;
ensure!(
T::Fungibles::is_crypto(origin_token_bank_id),
Error::<T>::CryptoBankControlError
);
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(destination_token_bank_id)?;
ensure!(
!T::Fungibles::is_wrapped_crypto(destination_token_bank_id),
Error::<T>::WrappedCryptoDoesNotHaveBank
);
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_id)?;
if
!T::Fungibles::is_wrapped_crypto(token_id) &&
T::Fungibles::is_stable(destination_token_bank_id) &&
token_id != destination_token_bank_id
{
return Err(Error::<T>::StableBankOnlyHoldWrappedOrItSelf.into());
}
// Verify if 'CRYPTO' token bank is receiving another 'CRYPTO' tokens that is not the UNIT token or the bank token itself.
if
T::Fungibles::is_crypto(destination_token_bank_id) &&
T::Fungibles::is_crypto(token_id) &&
token_id != T::Fungibles::get_unit_asset_id().ok_or(Error::<T>::UnitAssetIdNotStored)? &&
destination_token_bank_id != token_id
{
return Err(Error::<T>::CryptoBankCannotReceiveOtherCryptoTokens.into());
}
ensure!(
origin_token_bank_id != destination_token_bank_id,
Error::<T>::CannotSendToSameBank
);
let asset_owner = T::Fungibles::get_owner(origin_token_bank_id)?;
ensure!(asset_owner == who, Error::<T>::DontHavePermission);
let origin_bank_balance = BankTokens::<T>::get(origin_token_bank_id, token_id);
ensure!(origin_bank_balance >= token_amount, Error::<T>::InsufficientBalance);
let destination_bank_balance = BankTokens::<T>::get(
destination_token_bank_id,
token_id
);
let new_destination_bank_balance = destination_bank_balance
.checked_add(&token_amount)
.ok_or(ArithmeticError::Overflow)?;
BankTokens::<T>::insert(
destination_token_bank_id,
token_id,
new_destination_bank_balance
);
let new_origin_bank_balance = origin_bank_balance
.checked_sub(&token_amount)
.ok_or(ArithmeticError::Underflow)?;
BankTokens::<T>::insert(origin_token_bank_id, token_id, new_origin_bank_balance);
if origin_token_bank_id == token_id && T::Fungibles::is_crypto(token_id) {
T::Fungibles::increase_supply(token_id, token_amount);
T::Fungibles::set_is_changeable(token_id);
}
if T::Fungibles::is_crypto(destination_token_bank_id) {
T::Fungibles::set_is_deletable(destination_token_bank_id);
}
if destination_token_bank_id == token_id {
T::Fungibles::decrease_supply(token_id, token_amount);
if T::Fungibles::is_stable(token_id) {
// Verify whether the stable token is being sent to its own bank; if this is the case, it should be burned.
T::Fungibles::decrease_total_supply(token_id, token_amount);
}
}
let next_transaction_id_origin = NextTransactionId::<T>::get(origin_token_bank_id);
let next_transaction_id_destination = NextTransactionId::<T>::get(
destination_token_bank_id
);
let transaction = Transaction::<T>::TransferFromBankToBank {
origin_token_bank_id,
destination_token_bank_id,
token_id,
token_amount,
time: T::TimeProvider::now().as_secs(),
};
BankTransactions::<T>::insert(
origin_token_bank_id,
next_transaction_id_origin,
transaction.clone()
);
BankTransactions::<T>::insert(
destination_token_bank_id,
next_transaction_id_destination,
transaction
);
NextTransactionId::<T>::insert(origin_token_bank_id, next_transaction_id_origin + 1);
NextTransactionId::<T>::insert(
destination_token_bank_id,
next_transaction_id_destination + 1
);
Self::deposit_event(Event::TransferFromBankToBank {
who,
origin_token_bank_id,
destination_token_bank_id,
token_id,
token_amount,
});
}
Ok(())
}
/// The token owner can initiate a transfer of a token with the ID 'token_id' and an amount
/// of 'token_amount' from the bank with the ID 'token_bank_id' to the treasury with the ID
/// 'token_treasury_id'.
///
/// Parameters:
/// - `token_bank_id`: The token ID of the bank.
/// - `token_treasury_id`: The token ID of the treasury.
/// - `token_id`: The token ID of the token that is sent to the treasury from the bank.
/// - `token_amount`: The amount of token with ID that is sent to the treasury bank.
///
/// Emits `TransferFromBankToTreasury` event when successful.
#[pallet::call_index(3)]
pub fn transfer_token_from_bank_to_treasury(
origin: OriginFor<T>,
token_bank_id: AssetIdOf<T>,
token_treasury_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
Self::transfer_from_bank_to_treasury(
who,
token_bank_id,
token_treasury_id,
token_id,
token_amount
)?;
Ok(())
}
/// The origin can redeem a specified amount of tokens with the ID 'token_id' of type 'WRAPPED CRYPTO'.
/// This redemption is based on the bank ID 'token_bank_id' of type 'STABLE', and the provided amount is
/// used to calculate the total redeemed amount of the token with the ID 'token_id'.
///
/// Parameters:
/// - `token_bank_id`: The token ID of the bank.
/// - `token_id`: The token ID of the token that is redeemed from the bank.
/// - `token_amount`: The amount of token that is sent to the bank.
///
/// Emits `Redeem` event when successful.
#[pallet::call_index(4)]
#[pallet::weight({ 0 })]
pub fn redeem(
origin: OriginFor<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to redeem from the main account
who = T::SubAccounts::get_main_account(who)?;
// Get the pallet bank account.
let bank_id = Self::account_id();
// Ensure that the assets exists
<<T as pallet::Config>::Fungibles as traits::asset::AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_bank_id)?;
<<T as pallet::Config>::Fungibles as traits::asset::AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_id)?;
// Verify if the token bank is a stable token.
ensure!(T::Fungibles::is_stable(token_bank_id), Error::<T>::OnlyStableBankCanRedeem);
// Verify if the redemption token is a crypto token.
ensure!(
T::Fungibles::is_wrapped_crypto(token_id),
Error::<T>::OnlyWrappedCryptoAreRedeemable
);
ensure!(token_bank_id != token_id, Error::<T>::CannotRedeemBankToken);
ensure!(
BankTokens::<T>::contains_key(token_bank_id, token_id),
Error::<T>::TokenNotFoundInBank
);
ensure!(!token_amount.is_zero(), Error::<T>::RedeemAmountTooLow);
// Calculate the total price of the Wrapped Crypto type tokens
let mut bank_wrapped_crypto_balance = 0u128;
for asset_id in T::OracleInterface::get_oracle_asset_list() {
if T::Fungibles::is_wrapped_crypto(asset_id) {
let bank_asset_id_balance = BankTokens::<T>::get(token_bank_id, asset_id);
if bank_asset_id_balance > AssetBalanceOf::<T>::zero() {
if
let Some((asset_price, _)) =
T::OracleInterface::get_oracle_key_value(asset_id)
{
let asset_price_u128 = TryInto::<u128>
::try_into(asset_price)
.map_err(|_| Error::<T>::ConversionError)?;
let bank_asset_id_balance_u128 = TryInto::<u128>
::try_into(bank_asset_id_balance)
.map_err(|_| Error::<T>::ConversionError)?;
let asset_balance_u128 = asset_price_u128
.checked_mul(bank_asset_id_balance_u128)
.ok_or(ArithmeticError::Overflow)?;
bank_wrapped_crypto_balance = bank_wrapped_crypto_balance
.checked_add(asset_balance_u128)
.ok_or(ArithmeticError::Overflow)?;
}
}
}
}
// Calculate the total amount of the Stable token id circullating
let stable_supply = match T::Fungibles::get_asset_max_supply(&token_bank_id) {
Some(stable_supply) => stable_supply,
None => AssetBalanceOf::<T>::zero(),
};
let stable_supply_u128 = TryInto::<u128>
::try_into(stable_supply)
.map_err(|_| Error::<T>::ConversionError)?;
let stable_price = match T::OracleInterface::get_oracle_key_value(token_bank_id) {
Some((stable_price, _)) => stable_price,
None => AssetBalanceOf::<T>::zero(),
};
let stable_price_u128 = TryInto::<u128>
::try_into(stable_price)
.map_err(|_| Error::<T>::ConversionError)?;
// See if the the total price is equal or greater then 10x the total amount of the Stable
let stable_total_balance = stable_supply_u128
.checked_mul(stable_price_u128)
.ok_or(ArithmeticError::Overflow)?;
let stable_total_balance_ten_times = stable_total_balance
.checked_mul(10)
.ok_or(ArithmeticError::Overflow)?;
ensure!(
bank_wrapped_crypto_balance > stable_total_balance_ten_times,
Error::<T>::InsufficientCryptoValueInBank
);
// Calculate the total amount of Stable the user is trying to redeem
let stable_redeem_amount_u128 = TryInto::<u128>
::try_into(token_amount)
.map_err(|_| Error::<T>::ConversionError)?;
let stable_redeem_total = stable_redeem_amount_u128
.checked_mul(stable_price_u128)
.ok_or(ArithmeticError::Overflow)?;
// Transform the total Stable amount to the Wrapped Crypto amount
let wrapped_crypto_price = match T::OracleInterface::get_oracle_key_value(token_id) {
Some((stable_price, _)) => stable_price,
None => AssetBalanceOf::<T>::zero(),
};
let wrapped_crypto_price_u128 = TryInto::<u128>
::try_into(wrapped_crypto_price)
.map_err(|_| Error::<T>::ConversionError)?;
let wrapped_crypto_amount_redeem = stable_redeem_total
.checked_div(wrapped_crypto_price_u128)
.ok_or(ArithmeticError::DivisionByZero)?;
// Ensure that there is the enough amount of Wrapped Crypto inside the bank
let bank_wrapped_crypto_balance = BankTokens::<T>::get(token_bank_id, token_id);
let bank_wrapped_crypto_balance_u128 = TryInto::<u128>
::try_into(bank_wrapped_crypto_balance)
.map_err(|_| Error::<T>::ConversionError)?;
ensure!(
bank_wrapped_crypto_balance_u128 >= wrapped_crypto_amount_redeem,
Error::<T>::InsufficientCryptoBalanceInBank
);
let new_bank_wrapped_crypto_balance =
bank_wrapped_crypto_balance_u128 - wrapped_crypto_amount_redeem;
let new_bank_wrapped_crypto_balance_asset_balance = TryInto::<AssetBalanceOf<T>>
::try_into(new_bank_wrapped_crypto_balance)
.map_err(|_| Error::<T>::ConversionError)?;
BankTokens::<T>::insert(
token_bank_id,
token_id,
new_bank_wrapped_crypto_balance_asset_balance
);
let bank_stable_balance = BankTokens::<T>::get(&token_bank_id, &token_bank_id);
let bank_stable_balance_u128 = TryInto::<u128>
::try_into(bank_stable_balance)
.map_err(|_| Error::<T>::ConversionError)?;
let new_bank_balance = bank_stable_balance_u128 + stable_redeem_amount_u128;
let new_bank_balance_asset_balance = TryInto::<AssetBalanceOf<T>>
::try_into(new_bank_balance)
.map_err(|_| Error::<T>::ConversionError)?;
BankTokens::<T>::insert(&token_bank_id, &token_bank_id, new_bank_balance_asset_balance);
T::Fungibles::burn_stable_redeem_bank(token_bank_id, token_amount);
// Transfer the Stable from the user to the bank
T::Fungibles::transfer(token_bank_id, &who, &bank_id, token_amount, Expendable)?;
let wrapped_crypto_amount_redeem_asset_balance = TryInto::<AssetBalanceOf<T>>
::try_into(wrapped_crypto_amount_redeem)
.map_err(|_| Error::<T>::ConversionError)?;
// Transfer the Wrapped Crypto from the bank to the user
T::Fungibles::transfer(
token_id,
&bank_id,
&who,
wrapped_crypto_amount_redeem_asset_balance,
Expendable
)?;
Self::deposit_event(Event::RedeemedFromBank {
who,
token_bank_id,
token_id,
token_bank_amount: token_amount,
token_redeemed_amount: wrapped_crypto_amount_redeem_asset_balance,
});
Ok(())
}
}
impl<T: Config> Pallet<T> {
pub fn account_id() -> AccountIdOf<T> {
T::PalletId::get().into_account_truncating()
}
}
impl<T: Config> BankInterface<AssetIdOf<T>, AssetBalanceOf<T>, AccountIdOf<T>> for Pallet<T> {
fn insert_bank_balance(asset: AssetIdOf<T>, amount: AssetBalanceOf<T>) {
BankTokens::<T>::insert(asset, asset, amount);
}
fn insert_create_transaction(token_bank_id: AssetIdOf<T>, token_amount: AssetBalanceOf<T>) {
let transaction = Transaction::<T>::TransferFromCreateToBank {
token_bank_id,
token_amount,
time: None,
};
BankTransactions::<T>::insert(token_bank_id, 0, transaction);
NextTransactionId::<T>::insert(token_bank_id, 1);
}
fn delete_all_data_from_asset(token_id: AssetIdOf<T>) {
let _ = BankTokens::<T>::clear_prefix(token_id, 10, None);
let _ = BankTransactions::<T>::clear_prefix(token_id, 10, None);
}
fn get_bank_account() -> AccountIdOf<T> {
Self::account_id()
}
fn transfer_from_bank_to_user(
who: AccountIdOf<T>,
destination: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
// The account id of the bank
let bank_id = Self::account_id();
// ensure that the assets exists
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_bank_id)?;
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_id)?;
let bank_balance = BankTokens::<T>::get(token_bank_id, token_id);
ensure!(bank_balance >= token_amount, Error::<T>::InsufficientBalance);
T::Fungibles::transfer(token_id, &bank_id, &destination, token_amount, Expendable)?;
let new_balance = bank_balance
.checked_sub(&token_amount)
.ok_or(ArithmeticError::Underflow)?;
BankTokens::<T>::insert(token_bank_id, token_id, new_balance);
if token_bank_id == token_id {
T::Fungibles::increase_supply(token_id, token_amount);
T::Fungibles::set_is_changeable(token_id);
}
let next_transaction_id = NextTransactionId::<T>::get(token_bank_id);
let transaction = Transaction::<T>::TransferFromBankToUser {
destination: destination.clone(),
token_bank_id,
token_id,
token_amount,
time: T::TimeProvider::now().as_secs(),
};
BankTransactions::<T>::insert(token_bank_id, next_transaction_id, transaction);
NextTransactionId::<T>::insert(token_bank_id, next_transaction_id + 1);
Self::deposit_event(Event::TransferFromBankToUser {
who,
destination,
token_bank_id,
token_id,
token_amount,
});
Ok(())
}
fn transfer_from_user_to_bank(
origin: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
// The account id of the bank
let bank_id = Self::account_id();
// ensure that the assets exists
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_bank_id)?;
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_id)?;
if
!T::Fungibles::is_wrapped_crypto(token_id) &&
T::Fungibles::is_stable(token_bank_id) &&
token_id != token_bank_id
{
return Err(Error::<T>::StableBankOnlyHoldWrappedOrItSelf.into());
}
// Verify if 'CRYPTO' token bank is receiving another 'CRYPTO' tokens that is not the UNIT token or the bank token itself.
if
T::Fungibles::is_crypto(token_bank_id) &&
T::Fungibles::is_crypto(token_id) &&
token_id != T::Fungibles::get_unit_asset_id().ok_or(Error::<T>::UnitAssetIdNotStored)? &&
token_bank_id != token_id
{
return Err(Error::<T>::CryptoBankCannotReceiveOtherCryptoTokens.into());
}
let user_balance = T::Fungibles::balance(token_id, &origin);
ensure!(user_balance >= token_amount, Error::<T>::InsufficientBalance);
T::Fungibles::transfer(token_id, &origin, &bank_id, token_amount, Expendable)?;
let bank_balance = BankTokens::<T>::get(token_bank_id, token_id);
let new_bank_balance: <T as Config>::AssetBalance = bank_balance
.checked_add(&token_amount)
.ok_or(ArithmeticError::Overflow)?;
BankTokens::<T>::insert(token_bank_id, token_id, new_bank_balance);
if token_bank_id == token_id {
// Verify whether the stable token is being sent to its own bank; if this is the case, it should be burned.
T::Fungibles::decrease_supply(token_id, token_amount);
if T::Fungibles::is_stable(token_id) {
T::Fungibles::decrease_total_supply(token_id, token_amount);
}
}
let next_transaction_id = NextTransactionId::<T>::get(token_bank_id);
let transaction = Transaction::<T>::TransferFromUserToBank {
origin: origin.clone(),
token_bank_id,
token_id,
token_amount,
time: T::TimeProvider::now().as_secs(),
};
BankTransactions::<T>::insert(token_bank_id, next_transaction_id, transaction);
NextTransactionId::<T>::insert(token_bank_id, next_transaction_id + 1);
Self::deposit_event(Event::TransferFromUserToBank {
origin,
token_bank_id,
token_id,
token_amount,
});
Ok(())
}
fn transfer_all_from_bank_to_treasury(
who: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>
) -> DispatchResult {
let bank_id = Self::account_id();
let treasury_id = T::TreasuryInterface::get_treasury_account();
let bank_balance = BankTokens::<T>::get(token_bank_id, token_bank_id);
T::Fungibles::transfer(
token_bank_id,
&bank_id,
&treasury_id,
bank_balance,
Expendable
)?;
let new_balance: AssetBalanceOf<T> = Zero::zero();
BankTokens::<T>::insert(token_bank_id, token_bank_id, new_balance);
T::TreasuryInterface::increase_treasury_balance(
token_bank_id,
token_bank_id,
bank_balance
);
T::Fungibles::set_is_changeable(token_bank_id);
T::Fungibles::decrease_total_supply(token_bank_id, bank_balance);
let next_transaction_id = NextTransactionId::<T>::get(token_bank_id);
let transaction = Transaction::<T>::TransferFromBankToTreasury {
token_treasury_id: token_bank_id,
token_bank_id,
token_id: token_bank_id,
token_amount: bank_balance,
time: T::TimeProvider::now().as_secs(),
};
BankTransactions::<T>::insert(token_bank_id, next_transaction_id, transaction);
NextTransactionId::<T>::insert(token_bank_id, next_transaction_id + 1);
T::TreasuryInterface::insert_treasury_transaction(
token_bank_id,
token_bank_id,
token_bank_id,
bank_balance,
T::TimeProvider::now().as_secs()
);
Self::deposit_event(Event::TransferFromBankToTreasury {
who,
token_treasury_id: token_bank_id,
token_bank_id,
token_id: token_bank_id,
token_amount: bank_balance,
});
Ok(())
}
// Function to transfer tokens from the bank to a pallet and keep track accordingly.
fn transfer_from_bank_to_pallet(
pallet_id: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
// The account id of the bank
let bank_id = Self::account_id();
// ensure that the assets exists
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_bank_id.clone())?;
let bank_balance = BankTokens::<T>::get(&token_bank_id, &token_bank_id);
ensure!(bank_balance >= token_amount, Error::<T>::InsufficientBalance);
T::Fungibles::transfer(
token_bank_id.clone(),
&bank_id,
&pallet_id,
token_amount.clone(),
Expendable
)?;
let new_balance = bank_balance
.checked_sub(&token_amount)
.ok_or(ArithmeticError::Underflow)?;
BankTokens::<T>::insert(&token_bank_id, &token_bank_id, new_balance);
T::Fungibles::set_is_changeable(token_bank_id.clone());
let next_transaction_id = NextTransactionId::<T>::get(&token_bank_id);
let transaction = Transaction::<T>::TransferFromBankToPallet {
pallet_id: pallet_id.clone(),
token_bank_id: token_bank_id.clone(),
token_amount: token_amount.clone(),
time: T::TimeProvider::now().as_secs(),
};
BankTransactions::<T>::insert(&token_bank_id, &next_transaction_id, transaction);
NextTransactionId::<T>::insert(&token_bank_id, next_transaction_id + 1);
Self::deposit_event(Event::TransferFromBankToPallet {
pallet_id,
token_bank_id,
token_amount,
});
Ok(().into())
}
// Function to transfer tokens from the bank to a pallet and keep track accordingly.
fn transfer_from_pallet_to_bank(
origin: AccountIdOf<T>,
token_id: AssetIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
// ensure that the assets exists
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_bank_id.clone())?;
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_id.clone())?;
// The account id of the bank
let bank_id = Self::account_id();
T::Fungibles::transfer(
token_id.clone(),
&origin,
&bank_id,
token_amount.clone(),
Expendable
)?;
let bank_balance = BankTokens::<T>::get(&token_bank_id, &token_id);
let new_balance = bank_balance
.checked_add(&token_amount)
.ok_or(ArithmeticError::Overflow)?;
BankTokens::<T>::insert(&token_bank_id, &token_id, new_balance);
let next_transaction_id = NextTransactionId::<T>::get(&token_bank_id);
let transaction = Transaction::<T>::TransferFromPalletToBank {
pallet_id: origin.clone(),
token_bank_id: token_bank_id.clone(),
token_id: token_id.clone(),
token_amount: token_amount.clone(),
time: T::TimeProvider::now().as_secs(),
};
BankTransactions::<T>::insert(&token_bank_id, &next_transaction_id, transaction);
NextTransactionId::<T>::insert(&token_bank_id, next_transaction_id + 1);
Self::deposit_event(Event::TransferFromPalletToBank {
pallet_id: origin,
token_bank_id,
token_id,
token_amount,
});
Ok(().into())
}
fn transfer_from_bank_to_treasury(
who: AccountIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_treasury_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
let bank_id = Self::account_id();
let treasury_id = T::TreasuryInterface::get_treasury_account();
// ensure that the assets exists
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_bank_id)?;
ensure!(T::Fungibles::is_crypto(token_bank_id), Error::<T>::CryptoBankControlError);
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_treasury_id)?;
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_id)?;
T::TreasuryInterface::can_this_treasury_receive_tokens(token_treasury_id, token_id)?;
let asset_owner = T::Fungibles::get_owner(token_bank_id)?;
ensure!(asset_owner == who, Error::<T>::DontHavePermission);
let bank_balance = BankTokens::<T>::get(token_bank_id, token_id);
ensure!(bank_balance >= token_amount, Error::<T>::InsufficientBalance);
T::Fungibles::transfer(token_id, &bank_id, &treasury_id, token_amount, Expendable)?;
let new_balance = bank_balance
.checked_sub(&token_amount)
.ok_or(ArithmeticError::Underflow)?;
BankTokens::<T>::insert(token_bank_id, token_id, new_balance);
T::TreasuryInterface::increase_treasury_balance(
token_treasury_id,
token_id,
token_amount
);
if token_bank_id == token_id && T::Fungibles::is_crypto(token_id) {
T::Fungibles::increase_supply(token_id, token_amount);
T::Fungibles::set_is_changeable(token_id);
}
if token_treasury_id == token_id {
T::Fungibles::decrease_supply(token_id, token_amount);
T::Fungibles::decrease_total_supply(token_id, token_amount);
}
if T::Fungibles::is_crypto(token_treasury_id) {
T::Fungibles::set_is_deletable(token_treasury_id);
}
let next_transaction_id = NextTransactionId::<T>::get(token_bank_id);
let transaction = Transaction::<T>::TransferFromBankToTreasury {
token_treasury_id,
token_bank_id,
token_id,
token_amount,
time: T::TimeProvider::now().as_secs(),
};
BankTransactions::<T>::insert(token_bank_id, next_transaction_id, transaction);
NextTransactionId::<T>::insert(token_bank_id, next_transaction_id + 1);
T::TreasuryInterface::insert_treasury_transaction(
token_treasury_id,
token_bank_id,
token_id,
token_amount,
T::TimeProvider::now().as_secs()
);
Self::deposit_event(Event::TransferFromBankToTreasury {
who,
token_treasury_id,
token_bank_id,
token_id,
token_amount,
});
Ok(())
}
fn transfer_all_tokens_from_bank_ecosystem_to_treasury(
who: AccountIdOf<T>,
token_ecosystem_id: AssetIdOf<T>
) -> DispatchResult {
for (token_id, token_balance) in BankTokens::<T>::iter_prefix(&token_ecosystem_id) {
if (T::Fungibles::is_wrapped_crypto(token_id) || T::Fungibles::is_stable(token_id) || token_id == T::Fungibles::get_unit_asset_id().ok_or(Error::<T>::UnitAssetIdNotStored)?) && token_ecosystem_id != token_id && token_balance != AssetBalanceOf::<T>::zero() {
Self::transfer_from_bank_to_treasury(
who.clone(),
token_ecosystem_id,
token_ecosystem_id,
token_id,
token_balance
)?;
}
}
Ok(())
}
}
}