//! # Treasury Pallet
//! <!-- Original author of paragraph: @pablolteixeira
//!
//! ## Overview
//!
//! Pallet enables users to engage with the token's treasury, allowing for the locking and
//! redemption of funds by token holders. When a quantity of tokens is transferred to its treasury,
//! they are subsequently burned, reducing the overall maximum supply of the token.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * Transfer token from a user to a token's treasury.
//! * Redeem reserve assets based on the chosen proportion of the token amount.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! - `transfer_token_from_user_to_treasury`: Transfer an amount of token "x" to token's "y"
//! treasury.
//! - `redeem`: Redeem a portion of token 'x' into reserve assets using the treasury token.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
//!
//! Change History:
//!
//! - 10/03/23 - Walquer Valles - Benchmarking
#![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::FixedPointOperand;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod testing_utils;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
use traits::{
asset::{ AssetInterface, TokenType },
oracle::OracleInterface,
pool::PoolInterface,
subaccounts::{ SubAccounts, AccountOrigin },
treasury::TreasuryInterface,
};
pub mod weights;
pub use weights::WeightInfo;
use frame_support::{
pallet_prelude::*,
sp_runtime::{
ArithmeticError,
DispatchError,
traits::{
AccountIdConversion,
CheckedAdd,
CheckedSub,
CheckedMul,
CheckedDiv,
Zero,
SaturatedConversion,
},
},
traits::{
fungibles,
fungibles::{ Mutate, * },
tokens::{ Balance, Preservation::Expendable },
UnixTime,
},
PalletId,
};
use sp_std::prelude::Vec;
use frame_system::pallet_prelude::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
/// Type alias used for interaction with fungibles(assets).
pub type AssetBalanceOf<T> = <T as Config>::AssetBalance;
/// 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;
/// Type used to represent the token decimals.
pub type Decimals = u8;
/// Transaction Enum is used to represent different types of transactions that can happen
/// related to the treasury pallet.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub enum Transaction<T: Config> {
TransferFromUserToTreasury {
origin: AccountIdOf<T>,
token_treasury_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,
},
TransferFromPoolToTreasury {
token_pool_id: AssetIdOf<T>,
token_treasury_id: AssetIdOf<T>,
usdu_amount: AssetBalanceOf<T>,
time: u64,
},
Redeem {
origin: AccountIdOf<T>,
token_treasury_id: AssetIdOf<T>,
token_treasury_id_amount: AssetBalanceOf<T>,
token_id: AssetIdOf<T>,
token_id_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> +
AssetInterface<
<Self::Fungibles as Inspect<Self::AccountId>>::AssetId,
<Self::Fungibles as Inspect<Self::AccountId>>::Balance,
Self::AccountId,
Decimals,
TokenType
>;
type AssetBalance: Balance +
FixedPointOperand +
MaxEncodedLen +
MaybeSerializeDeserialize +
TypeInfo;
/// Type to access the Pool Pallet interface.
type PoolInterface: PoolInterface<
<Self::Fungibles as Inspect<Self::AccountId>>::AssetId,
<Self::Fungibles as Inspect<Self::AccountId>>::Balance
>;
/// 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
>;
/// Treasury 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;
/// Helper type to hold traits for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type PoolBenchmarkHelper;
/// Helper type to hold traits for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type PoolInterfaceBenchmarkHelper;
/// Weight Information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
#[pallet::constant]
type ProfileStringLimit: Get<u32>;
}
/// Store the amount of tokens inside a treasury.
/// asset_id -> asset_id -> asset_balance
#[pallet::storage]
#[pallet::getter(fn get_treasury_tokens)]
pub type TreasuryTokens<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
Blake2_128Concat,
AssetIdOf<T>,
AssetBalanceOf<T>,
ValueQuery
>;
/// Store the next transaction ID within each treasury.
/// 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 treasury.
/// asset_id -> transaction_id -> transaction
#[pallet::storage]
#[pallet::getter(fn get_bank_transactions)]
pub type TreasuryTransactions<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
Blake2_128Concat,
TransactionId,
Transaction<T>
>;
/// Store the total amount redeemed within each treasury.
/// asset_id -> amount_redeemed
#[pallet::storage]
#[pallet::getter(fn get_treasury_redeemed_amount)]
pub type TreasuryRedeemedAmount<T: Config> = StorageMap<
_,
Blake2_128Concat,
AssetIdOf<T>,
AssetBalanceOf<T>,
ValueQuery
>;
// 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 treasury event
TransferFromUserToTreasury {
origin: AccountIdOf<T>,
token_treasury_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
},
// Redeem token event
Redeem {
origin: AccountIdOf<T>,
token_treasury_id: AssetIdOf<T>,
token_treasury_amount: AssetBalanceOf<T>,
token_redeem_id: AssetIdOf<T>,
token_redeem_amount: AssetBalanceOf<T>,
},
}
// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
/// Asset does not exist.
AssetDoesNotExist,
/// Does not have permission to use the bank.
DontHavePermission,
/// Insufficient balance to transfer.
InsufficientBalance,
/// Asset price cannot be equal to zero, otherwise an error will occur in division
/// operations.
AssetPriceIsZero,
/// UNIT is not registered in the assets pallet.
UnitAssetIdNotStored,
/// Total token to redeem cannot be equal to zero.
TotalTokenToRedeemIsZero,
/// The asset balance in the liquidity pool is equal to zero, so it does not have a price.
AssetBalanceInPoolIsZero,
/// The Unit token balance in the liquidity pool is equal to zero, so it does not have a
/// price.
UnitBalanceInPoolIsZero,
/// The token chosen to be redeemed is not in the treasury.
TokenNotFoundInTreasury,
/// The token chosen to be redeemed is the same as the treasury's token.
CannotRedeemTreasuryToken,
/// The token amount to be redeemed is equal to zero.
RedeemAmountTooLow,
/// The conversion between types panicked.
ConversionError,
/// The redemption option is exclusive to the treasury associated with tokens of the 'CRYPTO' type.
OnlyCryptoTreasuryAreRedeemable,
/// Only treasuries associated with tokens of the 'CRYPTO' type are authorized to receive transfers of other tokens from users or banks.
OnlyCryptoTreasuryCanReceiveTokens,
/// The treasury of 'CRYPTO' tokens can only receive tokens of type 'WRAPPED', 'STABLE', the UNIT token, or the token itself.
CryptoTreasuryCannotReceiveOtherCryptoTokens
}
// 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 treasury with the ID 'token_treasury_id'.
///
/// Parameters:
/// - `token_treasury_id`: The token ID of the treasury.
/// - `token_id`: The token ID of the token that is sent to the treasury.
/// - `token_amount`: The amount of token with ID that is sent to the treasury.
///
/// Emits `TransferFromUserToTreasury` event when successful.
#[pallet::call_index(0)]
pub fn transfer_token_from_user_to_treasury(
origin: OriginFor<T>,
token_treasury_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
let mut origin = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
origin = T::SubAccounts::get_main_account(origin)?;
let treasury_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_treasury_id)?;
<<T as pallet::Config>::Fungibles as traits::asset::AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_id)?;
Self::can_this_treasury_receive_tokens(token_treasury_id, token_id)?;
T::Fungibles::transfer(token_id, &origin, &treasury_id, token_amount, Expendable)?;
let treasury_balance = TreasuryTokens::<T>::get(token_treasury_id, token_id);
let new_treasury_balance = treasury_balance
.checked_add(&token_amount)
.ok_or(ArithmeticError::Overflow)?;
TreasuryTokens::<T>::insert(token_treasury_id, token_id, new_treasury_balance);
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_treasury_id);
let transaction = Transaction::<T>::TransferFromUserToTreasury {
origin: origin.clone(),
token_treasury_id,
token_id,
token_amount,
time: T::TimeProvider::now().as_secs(),
};
TreasuryTransactions::<T>::insert(token_treasury_id, next_transaction_id, transaction);
NextTransactionId::<T>::insert(
token_treasury_id,
next_transaction_id.checked_add(1).ok_or(ArithmeticError::Overflow)?
);
Self::deposit_event(Event::TransferFromUserToTreasury {
origin,
token_treasury_id,
token_id,
token_amount,
});
Ok(())
}
/// The origin can redeem an amount of token with the ID 'token_id' based on the treasury ID
/// 'token_treasury_id' and the specified amount.
///
/// Parameters:
/// - `token_treasury_id`: The token ID of the treasury.
/// - `token_id`: The token ID of the token that is redeemed from the treasury.
/// - `token_amount`: The amount of token that is sent to the treasury.
///
/// Emits `Redeem` event when successful.
#[pallet::call_index(1)]
pub fn redeem(
origin: OriginFor<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 redeem from the main account
who = T::SubAccounts::get_main_account(who)?;
// Get the pallet treasury account.
let treasury_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_treasury_id)?;
<<T as pallet::Config>::Fungibles as traits::asset::AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_id)?;
ensure!(
T::Fungibles::is_crypto(token_treasury_id),
Error::<T>::OnlyCryptoTreasuryAreRedeemable
);
// Get the UNIT token ID that is set in assets pallet storage.
let unit_asset_id = T::Fungibles::get_unit_asset_id().ok_or(
Error::<T>::UnitAssetIdNotStored
)?;
ensure!(token_treasury_id != token_id, Error::<T>::CannotRedeemTreasuryToken);
ensure!(
TreasuryTokens::<T>::contains_key(token_treasury_id, token_id),
Error::<T>::TokenNotFoundInTreasury
);
ensure!(!token_amount.is_zero(), Error::<T>::RedeemAmountTooLow);
// Enumerate all the tokens within the token's treasury and generate a vector consisting
// of tuples (asset_id, asset_balance).
let assets_in_treasury: Vec<(AssetIdOf<T>, AssetBalanceOf<T>)> = TreasuryTokens::<T>
::iter_prefix(token_treasury_id)
//.map(|(asset_id, asset_balance)| (asset_id, asset_balance))
.collect();
// Total value of all tokens within the token_treasury_id treasury. It will be
// calculated in the loop below.
let mut treasury_balance: AssetBalanceOf<T> = Zero::zero();
// Compute the balance of all tokens held within the treasury, with calculations
// restricted to tokens associated with the oracle and UNIT price.
for (asset_id, asset_balance) in assets_in_treasury {
match T::OracleInterface::get_oracle_key_value(asset_id) {
// If the token exists within the oracle, its price will be calculated and added
// to the 'treasury_balance' variable.
Some((asset_price, _)) => {
// To calculate the total value of a token inside, you should follow this
// formula: token's balance value inside the treasury = (token price * token
// balance inside the treasury).
// Total value for this asset
let asset_multiplication = asset_price
.checked_mul(&asset_balance)
.ok_or(ArithmeticError::Overflow)?
.checked_div(&(10u128).pow(10).saturated_into::<AssetBalanceOf<T>>())
.ok_or(Error::<T>::ConversionError)?;
treasury_balance = treasury_balance
.checked_add(&asset_multiplication)
.ok_or(ArithmeticError::Overflow)?;
}
// If the token does not exist within the oracle, the loop will proceed, and the
// price of this token will not be added to the 'treasury_balance' variable.
None => {
// The UNIT token is also considered an asset that can be redeemed.
// if the token is the UNIT token, and the token_treasury_id is not the UNIT
// token id, then the value of the UNIT tokens will be calculated and added
// to the 'treasury_balance'. The value is calculated using the formula:
// (unit price from the liquidity pool * unit balance in the treasury).
if asset_id == unit_asset_id && token_treasury_id != unit_asset_id {
let unit_balance_in_treasury = TreasuryTokens::<T>::get(
token_treasury_id,
unit_asset_id
);
// Unit balance and USDU balance in the liquidity pool for the UNIT
// token.
let (unit_balance, usdu_balance) =
T::PoolInterface::get_liquidity_pool(unit_asset_id);
// Unit balance cannot be equal to zero, otherwise an error will occur
// in division operations.
ensure!(!unit_balance.is_zero(), Error::<T>::UnitBalanceInPoolIsZero);
// Unit price = USDU balance in the liquidity pool * 10^10 / UNIT
// balance in the liquidity pool.
let unit_price = usdu_balance
.checked_mul(
&(10u128).pow(10).saturated_into::<AssetBalanceOf<T>>()
)
.ok_or(ArithmeticError::Overflow)?
.checked_div(&unit_balance)
.ok_or(ArithmeticError::DivisionByZero)?;
// unit_multiplication is the value of the UNIT tokens in the treasury.
let unit_multiplication = unit_price
.checked_mul(&unit_balance_in_treasury)
.ok_or(ArithmeticError::Overflow)?
.checked_div(
&(10u128).pow(10).saturated_into::<AssetBalanceOf<T>>()
)
.ok_or(Error::<T>::ConversionError)?;
treasury_balance = treasury_balance
.checked_add(&unit_multiplication)
.ok_or(ArithmeticError::Overflow)?;
} else {
continue;
}
}
}
}
let asset_supply = T::Fungibles::get_asset_max_supply(&token_treasury_id).ok_or(
Error::<T>::AssetDoesNotExist
)?;
// To calculate the lowest price of a token based on its treasury balance,
// you can use the formula: treasury balance / treasury token supply.
let lowest_price = treasury_balance
.checked_mul(&(10u128).pow(10).saturated_into::<AssetBalanceOf<T>>())
.ok_or(ArithmeticError::Overflow)?
.checked_div(&asset_supply)
.ok_or(ArithmeticError::DivisionByZero)?;
// The total amount value considering the lowest price of the token.
let total_amount_price = lowest_price
.checked_mul(&token_amount)
.ok_or(ArithmeticError::Overflow)?
.checked_div(&(10u128).pow(10).saturated_into::<AssetBalanceOf<T>>())
.ok_or(Error::<T>::ConversionError)?;
#[allow(unused_assignments)]
let mut token_price: AssetBalanceOf<T> = Zero::zero();
match T::OracleInterface::get_oracle_key_value(token_id) {
Some((asset_price, _)) => {
token_price = asset_price;
}
None => {
let (token_balance, usdu_balance) =
T::PoolInterface::get_liquidity_pool(token_id);
ensure!(!token_balance.is_zero(), Error::<T>::AssetBalanceInPoolIsZero);
// Token price = USDU balance in the liquidity pool * 10^10 / token balance in
// the liquidity pool.
token_price = usdu_balance
.checked_mul(&(10u128).pow(10).saturated_into::<AssetBalanceOf<T>>())
.ok_or(ArithmeticError::Overflow)?
.checked_div(&token_balance)
.ok_or(ArithmeticError::DivisionByZero)?;
}
}
ensure!(!token_price.is_zero(), Error::<T>::AssetPriceIsZero);
// Total_amount_price is the total value of the tokens the user wants to redeem
// considering the lowest price of the token. Token_price_u128 is the price of the token
// considering the oracle or the liquidity pool. Total_token_to_redeem is the total
// amount (quantity) of tokens the user will receive.
let total_token_to_redeem = total_amount_price
.checked_mul(&(10u128).pow(10).saturated_into::<AssetBalanceOf<T>>())
.ok_or(ArithmeticError::Overflow)?
.checked_div(&token_price)
.ok_or(ArithmeticError::DivisionByZero)?;
ensure!(!total_token_to_redeem.is_zero(), Error::<T>::TotalTokenToRedeemIsZero);
let token_id_balance_in_treasury = TreasuryTokens::<T>::get(
token_treasury_id,
token_id
);
// Transferring the token with the ID 'token_treasury_id' in the amount of
// 'amount_asset_balance' from the user to the treasury. From the user's account to the
// treasury account.
T::Fungibles::transfer(
token_treasury_id,
&who,
&treasury_id,
token_amount,
Expendable
)?;
// Updating the amount of the token with the ID 'token_treasury_id' inside the treasury.
let treasury_token_id_balance = TreasuryTokens::<T>::get(
token_treasury_id,
token_treasury_id
);
let new_treasury_token_id_balance = treasury_token_id_balance
.checked_add(&token_amount)
.ok_or(ArithmeticError::Overflow)?;
TreasuryTokens::<T>::insert(
token_treasury_id,
token_treasury_id,
new_treasury_token_id_balance
);
// Reducing the total supply of the token with the ID 'treasury_token_id' as they are
// burned upon transfer to the treasury.
T::Fungibles::decrease_supply(token_treasury_id, token_amount);
T::Fungibles::decrease_total_supply(token_treasury_id, token_amount);
// Transferring the token with the ID 'token_id' in the amount of
// 'total_token_to_redeem' From the treasury to the user.
T::Fungibles::transfer(
token_id,
&treasury_id,
&who,
total_token_to_redeem,
Expendable
)?;
// Updating the quantity of the token with the ID 'token_id' within the treasury.
let new_token_id_balance_in_treasury = token_id_balance_in_treasury
.checked_sub(&total_token_to_redeem)
.ok_or(ArithmeticError::Underflow)?;
TreasuryTokens::<T>::insert(
token_treasury_id,
token_id,
new_token_id_balance_in_treasury
);
let next_transaction_id = NextTransactionId::<T>::get(token_treasury_id);
let transaction = Transaction::<T>::Redeem {
origin: who.clone(),
token_treasury_id,
token_treasury_id_amount: token_amount,
token_id,
token_id_amount: total_token_to_redeem,
time: T::TimeProvider::now().as_secs(),
};
TreasuryTransactions::<T>::insert(token_treasury_id, next_transaction_id, transaction);
NextTransactionId::<T>::insert(
token_treasury_id,
next_transaction_id.checked_add(1).ok_or(ArithmeticError::Overflow)?
);
let treasury_redeemed_amount = TreasuryRedeemedAmount::<T>::get(&token_treasury_id);
let new_treasury_redeemed_amount = treasury_redeemed_amount
.checked_add(&token_amount)
.ok_or(ArithmeticError::Overflow)?;
TreasuryRedeemedAmount::<T>::insert(&token_treasury_id, new_treasury_redeemed_amount);
Self::deposit_event(Event::Redeem {
origin: who,
token_treasury_id,
token_treasury_amount: token_amount,
token_redeem_id: token_id,
token_redeem_amount: total_token_to_redeem,
});
Ok(())
}
}
impl<T: Config> Pallet<T> {
pub fn account_id() -> T::AccountId {
T::PalletId::get().into_account_truncating()
}
}
impl<T: Config> TreasuryInterface<AssetIdOf<T>, AssetBalanceOf<T>, AccountIdOf<T>>
for Pallet<T> {
fn increase_treasury_balance(
treasury_id: AssetIdOf<T>,
asset: AssetIdOf<T>,
amount: AssetBalanceOf<T>
) {
let treasury_asset_id_balance = TreasuryTokens::<T>::get(treasury_id, asset);
let new_treasury_asset_id_balance = treasury_asset_id_balance + amount;
TreasuryTokens::<T>::insert(treasury_id, asset, new_treasury_asset_id_balance);
}
fn insert_treasury_transaction(
token_treasury_id: AssetIdOf<T>,
token_bank_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>,
time: u64
) {
let next_transaction_id = NextTransactionId::<T>::get(token_treasury_id);
let transaction = Transaction::<T>::TransferFromBankToTreasury {
token_treasury_id,
token_bank_id,
token_id,
token_amount,
time,
};
TreasuryTransactions::<T>::insert(token_treasury_id, next_transaction_id, transaction);
NextTransactionId::<T>::insert(token_treasury_id, next_transaction_id + 1);
}
fn get_treasury_account() -> AccountIdOf<T> {
Self::account_id()
}
fn transfer_usdu_from_pool_to_treasury(
token_pool_id: AssetIdOf<T>,
token_treasury_id: AssetIdOf<T>,
usdu_amount: AssetBalanceOf<T>,
usdu_id: AssetIdOf<T>
) {
Self::increase_treasury_balance(token_treasury_id, usdu_id, usdu_amount);
let next_transaction_id = NextTransactionId::<T>::get(token_treasury_id);
let time = T::TimeProvider::now().as_secs();
let transaction = Transaction::<T>::TransferFromPoolToTreasury {
token_pool_id,
token_treasury_id,
usdu_amount,
time,
};
TreasuryTransactions::<T>::insert(token_treasury_id, next_transaction_id, transaction);
NextTransactionId::<T>::insert(token_treasury_id, next_transaction_id + 1);
}
fn can_this_treasury_receive_tokens(
token_treasury_id: AssetIdOf<T>,
token_id: AssetIdOf<T>
) -> Result<(), DispatchError> {
ensure!(
T::Fungibles::is_crypto(token_treasury_id),
Error::<T>::OnlyCryptoTreasuryCanReceiveTokens
);
ensure!(
!T::Fungibles::is_crypto(token_id) ||
token_id == token_treasury_id ||
token_id == T::Fungibles::get_unit_asset_id().ok_or(Error::<T>::UnitAssetIdNotStored)?,
Error::<T>::CryptoTreasuryCannotReceiveOtherCryptoTokens
);
Ok(())
}
fn get_treasury_balance(
treasury_id: AssetIdOf<T>,
asset: AssetIdOf<T>
) -> AssetBalanceOf<T> {
TreasuryTokens::<T>::get(treasury_id, asset)
}
fn transfer_from_user_to_treasury(
origin: AccountIdOf<T>,
token_treasury_id: AssetIdOf<T>,
token_id: AssetIdOf<T>,
token_amount: AssetBalanceOf<T>
) -> DispatchResult {
let treasury_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_treasury_id)?;
<<T as pallet::Config>::Fungibles as traits::asset::AssetInterface<
AssetIdOf<T>,
AssetBalanceOf<T>,
T::AccountId,
Decimals,
TokenType
>>::asset_exists(token_id)?;
Self::can_this_treasury_receive_tokens(token_treasury_id, token_id)?;
T::Fungibles::transfer(token_id, &origin, &treasury_id, token_amount, Expendable)?;
let treasury_balance = TreasuryTokens::<T>::get(token_treasury_id, token_id);
let new_treasury_balance = treasury_balance
.checked_add(&token_amount)
.ok_or(ArithmeticError::Overflow)?;
TreasuryTokens::<T>::insert(token_treasury_id, token_id, new_treasury_balance);
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_treasury_id);
let transaction = Transaction::<T>::TransferFromUserToTreasury {
origin: origin.clone(),
token_treasury_id,
token_id,
token_amount,
time: T::TimeProvider::now().as_secs(),
};
TreasuryTransactions::<T>::insert(token_treasury_id, next_transaction_id, transaction);
NextTransactionId::<T>::insert(
token_treasury_id,
next_transaction_id.checked_add(1).ok_or(ArithmeticError::Overflow)?
);
Self::deposit_event(Event::TransferFromUserToTreasury {
origin,
token_treasury_id,
token_id,
token_amount,
});
Ok(())
}
}
}