//! # Assets Pallet
//! <!-- Original author of paragraph: @pablolteixeira
//!
//! ## Overview
//!
//! Pallet asset enables the creation, update, and transfer of assets.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * Create an asset.
//! * Transfer assets between accounts.
//! * Update the metadata.
//! * Set the asset to be an ecosystem token.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! `create`: This function creates a new asset with a name.
//!
//! `transfer`: This function transfers a specified amount of an asset from one account to another
//! one.
//!
//! `set_metadata`: This function updates the metadata of an asset.
//!
//! `set_ecosystem_asset`: This function sets an asset to an ecosystem asset.
//!
//! `delete_asset`: This function deletes an asset and all data related to it.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
// This recursion limit is needed because we have too many benchmarks and benchmarking will fail if
// we add more without this limit.
#![recursion_limit = "1024"]
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
pub mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
pub use weights::WeightInfo;
mod extra_mutator;
pub use extra_mutator::*;
mod functions;
mod impl_fungibles;
mod impl_stored_map;
mod types;
pub use types::*;
use traits::{
asset::{ AssetInterface, TokenType },
bank::BankInterface,
subaccounts::{ SubAccounts, AccountOrigin },
treasury::TreasuryInterface,
};
use scale_info::TypeInfo;
// use sp_runtime::{
// traits::{ AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, Hash, One, Saturating, Zero },
// ArithmeticError,
// TokenError,
// };
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, Hash, One, Saturating, StaticLookup, Zero},
ArithmeticError, DispatchError, TokenError,
};
use sp_std::prelude::*;
use frame_support::{
dispatch::{ DispatchResult },
ensure,
pallet_prelude::DispatchResultWithPostInfo,
storage::KeyPrefixIterator,
traits::{
tokens::{ fungibles, DepositConsequence, WithdrawConsequence },
Currency,
EnsureOriginWithArg,
ReservableCurrency,
StoredMap,
UnixTime,
},
};
use frame_system::Config as SystemConfig;
pub use pallet::*;
const LOG_TARGET: &str = "runtime::assets";
/// Trait with callbacks that are executed after successfull asset creation or destruction.
pub trait AssetsCallback<AssetId, AccountId> {
/// Indicates that asset with `id` was successfully created by the `owner`
fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> {
Ok(())
}
/// Indicates that asset with `id` has just been destroyed
fn destroyed(_id: &AssetId) -> Result<(), ()> {
Ok(())
}
}
/// Empty implementation in case no callbacks are required.
impl<AssetId, AccountId> AssetsCallback<AssetId, AccountId> for () {}
pub type Decimals = u8;
pub type TransferId = u64;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{ pallet_prelude::{ ValueQuery, * }, PalletId };
use frame_system::pallet_prelude::*;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(_);
#[cfg(feature = "runtime-benchmarks")]
pub trait BenchmarkHelper<AssetIdParameter> {
fn create_asset_id_parameter(id: u32) -> AssetIdParameter;
}
#[cfg(feature = "runtime-benchmarks")]
impl<AssetIdParameter: From<u32>> BenchmarkHelper<AssetIdParameter> for () {
fn create_asset_id_parameter(id: u32) -> AssetIdParameter {
id.into()
}
}
#[pallet::config]
/// The module configuration trait.
pub trait Config<I: 'static = ()>: frame_system::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self, I>> +
IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The units in which we record balances.
type Balance: Member +
Parameter +
AtLeast32BitUnsigned +
Default +
Copy +
MaybeSerializeDeserialize +
MaxEncodedLen +
TypeInfo +
From<u32> +
Zero +
Saturating;
/// Max number of items to destroy per `destroy_accounts` and `destroy_approvals` call.
///
/// Must be configured to result in a weight that makes each call fit in a block.
#[pallet::constant]
type RemoveItemsLimit: Get<u32>;
/// Identifier for the class of asset.
type AssetId: Member +
Parameter +
Default +
Saturating +
MaybeSerializeDeserialize +
MaxEncodedLen +
Zero +
One +
Copy +
From<u32>;
/// Wrapper around `Self::AssetId` to use in dispatchable call signatures. Allows the use
/// of compact encoding in instances of the pallet, which will prevent breaking changes
/// resulting from the removal of `HasCompact` from `Self::AssetId`.
///
/// This type includes the `From<Self::AssetId>` bound, since tightly coupled pallets may
/// want to convert an `AssetId` into a parameter for calling dispatchable functions
/// directly.
type AssetIdParameter: Parameter +
Copy +
From<Self::AssetId> +
Into<Self::AssetId> +
MaxEncodedLen;
/// The currency mechanism.
type Currency: ReservableCurrency<Self::AccountId>;
/// Standard asset class creation is only allowed if the origin attempting it and the
/// asset class are in this set.
type CreateOrigin: EnsureOriginWithArg<
Self::RuntimeOrigin,
Self::AssetId,
Success = Self::AccountId
>;
/// The origin which may forcibly create or destroy an asset or otherwise alter privileged
/// attributes.
type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The basic amount of funds that must be reserved for an asset.
#[pallet::constant]
type AssetDeposit: Get<DepositBalanceOf<Self, I>>;
/// The amount of funds that must be reserved for a non-provider asset account to be
/// maintained.
#[pallet::constant]
type AssetAccountDeposit: Get<DepositBalanceOf<Self, I>>;
/// The basic amount of funds that must be reserved when adding metadata to your asset.
#[pallet::constant]
type MetadataDepositBase: Get<DepositBalanceOf<Self, I>>;
/// The additional funds that must be reserved for the number of bytes you store in your
/// metadata.
#[pallet::constant]
type MetadataDepositPerByte: Get<DepositBalanceOf<Self, I>>;
/// The amount of funds that must be reserved when creating a new approval.
#[pallet::constant]
type ApprovalDeposit: Get<DepositBalanceOf<Self, I>>;
/// The maximum length of a name or symbol stored on-chain.
#[pallet::constant]
type StringLimit: Get<u32>;
/// A hook to allow a per-asset, per-account minimum balance to be enforced. This must be
/// respected in all permissionless operations.
type Freezer: FrozenBalance<Self::AssetId, Self::AccountId, Self::Balance>;
/// Additional data to be stored with an account's asset balance.
type Extra: Member + Parameter + Default + MaxEncodedLen;
/// Callback methods for asset state change (e.g. asset created or destroyed)
type CallbackHandle: AssetsCallback<Self::AssetId, Self::AccountId>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
/// The default asset supply
#[pallet::constant]
type DefaultSupply: Get<Self::Balance>;
/// The minimum asset's maximum supply
#[pallet::constant]
type MinSupply: Get<Self::Balance>;
/// The asset's default number of decimals
#[pallet::constant]
type DefaultDecimals: Get<u8>;
/// The asset's name maximum length
#[pallet::constant]
type NameMaxLength: Get<u32>;
/// The asset's symbol maximum length
#[pallet::constant]
type SymbolMaxLength: Get<u32>;
/// The pallet's id
#[pallet::constant]
type AssetPalletId: Get<PalletId>;
// Provides actual time
type TimeProvider: UnixTime;
// Interface to interact with bank pallet
type Bank: BankInterface<Self::AssetId, Self::Balance, Self::AccountId>;
// Interface to intereact with treasury pallet
type Treasury: TreasuryInterface<Self::AssetId, Self::Balance, Self::AccountId>;
/// Type to access the sub account pallet
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>;
//**Approval**: The act of allowing an account the permission to transfer some balance of
//**Approval**: asset
// Helper trait for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper: BenchmarkHelper<Self::AssetIdParameter>;
#[cfg(feature = "runtime-benchmarks")]
type ProfileBenchmarkHelper;
#[cfg(feature = "runtime-benchmarks")]
type ProfileStringLimit: Get<u32>;
}
#[pallet::storage]
#[pallet::getter(fn get_asset_details)]
/// Details of an asset.
pub type Asset<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AssetId,
AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>
>;
#[pallet::storage]
#[pallet::getter(fn get_account)]
/// The holdings of a specific account for a specific asset.
pub type Account<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AssetId,
Blake2_128Concat,
T::AccountId,
AssetAccountOf<T, I>
>;
#[pallet::storage]
#[pallet::getter(fn get_approval)]
/// Approved balance transfers. First balance is the amount approved for transfer. Second
/// is the amount of `T::Currency` reserved for storing this.
/// First key is the asset ID, second key is the owner and third key is the delegate.
pub(super) type Approvals<T: Config<I>, I: 'static = ()> = StorageNMap<
_,
(
NMapKey<Blake2_128Concat, T::AssetId>,
NMapKey<Blake2_128Concat, T::AccountId>, // owner
NMapKey<Blake2_128Concat, T::AccountId>, // delegate
),
Approval<T::Balance, DepositBalanceOf<T, I>>
>;
#[pallet::storage]
#[pallet::getter(fn get_metadata)]
/// Metadata of an asset.
pub(super) type Metadata<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AssetId,
AssetMetadata<
DepositBalanceOf<T, I>,
BoundedVec<u8, T::StringLimit>,
BoundedVec<u8, T::SymbolMaxLength>,
BoundedVec<u8, T::NameMaxLength>,
TokenType
>,
ValueQuery
>;
#[pallet::storage]
/// Store a tuple where the boolean indicates if the symbol is used and the asset id of the
/// asset. symbol_hash -> (bool, asset_id)
pub(super) type Symbols<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::Hash,
(bool, T::AssetId)
>;
/// Store the next asset id.
#[pallet::storage]
#[pallet::getter(fn get_next_id)]
pub(super) type NextAssetId<T: Config<I>, I: 'static = ()> = StorageValue<
_,
T::AssetId,
ValueQuery
>;
/// Store the UNIT asset id.
#[pallet::storage]
#[pallet::getter(fn get_unit_asset_id)]
pub(super) type UnitAssetId<T: Config<I>, I: 'static = ()> = StorageValue<
_,
T::AssetId,
OptionQuery
>;
/// Store the USDU asset id.
#[pallet::storage]
#[pallet::getter(fn get_usdu_asset_id)]
pub(super) type UsduAssetId<T: Config<I>, I: 'static = ()> = StorageValue<
_,
T::AssetId,
OptionQuery
>;
/// Store the transfer counter.
#[pallet::storage]
#[pallet::getter(fn get_next_transfer_id)]
pub(super) type NextTransferId<T: Config<I>, I: 'static = ()> = StorageValue<
_,
TransferId,
ValueQuery
>;
/// Store the transfers id within each user account.
#[pallet::storage]
#[pallet::getter(fn get_transfer_id)]
pub(super) type TransfersId<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
Vec<TransferId>,
ValueQuery
>;
/// Store the transfer
#[pallet::storage]
#[pallet::getter(fn get_transfer)]
pub(super) type Transfers<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
TransferId,
Transfer<T::AccountId, T::AssetId, T::Balance>,
OptionQuery
>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
/// Genesis assets: id, owner, is_sufficient, min_balance, token_type
pub assets: Vec<(T::AccountId, bool, T::Balance, u8)>,
/// Genesis metadata: id, name, symbol, decimals, is froze, description, is changeable, is
/// ecosystem asset website, location, telegram, instagram, linkedin, twitter, token type,
/// youtube, whatsapp, industry symbol, city symbol, slide deck, tiktok, asset picture
/// color, asset picture details, created time
pub metadata: Vec<(T::AssetId, Vec<u8>, Vec<u8>, u8, u8)>,
/// Genesis accounts: id, account_id, balance
pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>,
}
impl<T: Config<I>, I: 'static> core::default::Default for GenesisConfig<T, I> {
fn default() -> Self {
Self {
assets: Default::default(),
metadata: Default::default(),
accounts: Default::default(),
}
}
}
#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
fn build(&self) {
let mut id = T::AssetId::zero();
let unit_symbol: BoundedVec<u8, T::SymbolMaxLength> = BoundedVec::try_from(
"UNIT".as_bytes().to_vec()
).unwrap();
let usdu_symbol: BoundedVec<u8, T::SymbolMaxLength> = BoundedVec::try_from(
"USDU".as_bytes().to_vec()
).unwrap();
for (owner, is_sufficient, min_balance, token_type_u8) in &self.assets {
assert!(!min_balance.is_zero(), "Min balance should not be zero");
// Only the UNIT token begins with maximum supply of 1 billion.
let token_type: TokenType = match token_type_u8 {
0 => TokenType::Stable,
1 => TokenType::Crypto,
2 => TokenType::WrappedCrypto,
3 => TokenType::City,
4 => TokenType::Industry,
_ => TokenType::Crypto,
};
let max_supply: T::Balance = if id == Zero::zero() {
(10_000_000_000_000_000_000u128).try_into().ok().unwrap()
} else if token_type == TokenType::City || token_type == TokenType::Industry {
(1_000_000_000_000_000_000u128).try_into().ok().unwrap()
} else {
Zero::zero()
};
Asset::<T, I>::insert(id, AssetDetails {
owner: owner.clone(),
issuer: owner.clone(),
admin: owner.clone(),
freezer: owner.clone(),
max_supply,
supply: Zero::zero(),
deposit: Zero::zero(),
min_balance: *min_balance,
is_sufficient: *is_sufficient,
accounts: 0,
sufficients: 0,
approvals: 0,
status: AssetStatus::Live,
});
id = id.saturating_add(T::AssetId::one());
}
NextAssetId::<T, I>::put(id);
for (id, name, symbol, token_type_u8, decimals) in &self.metadata {
assert!(Asset::<T, I>::contains_key(id), "Asset does not exist");
let mut good_symbol = true;
let mut symbol_hash = symbol.clone();
// Due to symbol being a vector of u8, I'll use ASCII Table to see if
// it's not alphanumeric and doesn't have space.
for letter in &mut symbol_hash {
if (65 <= *letter && *letter <= 90) || (*letter >= 48 && *letter <= 57) {
continue;
} else if 97 <= *letter && *letter <= 122 {
*letter -= 32;
} else {
good_symbol = false;
break;
}
}
// Symbol should have max length of 6 and not alphanumeric without spaces
assert!(good_symbol, "Symbol is wrong!");
let hash = T::Hashing::hash(&symbol_hash);
assert!(!Symbols::<T, I>::contains_key(hash), "Symbol is already in use!");
Symbols::<T, I>::insert(hash, (true, id));
let name = BoundedVec::<u8, T::NameMaxLength>::try_from(name.clone()).unwrap();
let symbol_to_bounded_vec = BoundedVec::<u8, T::SymbolMaxLength>
::try_from(symbol.clone())
.unwrap();
if symbol_to_bounded_vec == unit_symbol {
UnitAssetId::<T, I>::mutate(|unit_asset_id| {
*unit_asset_id = Some(*id);
});
} else if symbol_to_bounded_vec == usdu_symbol {
UsduAssetId::<T, I>::mutate(|usdu_asset_id| {
*usdu_asset_id = Some(*id);
});
}
let token_type: TokenType = match token_type_u8 {
0 => TokenType::Stable,
1 => TokenType::Crypto,
2 => TokenType::WrappedCrypto,
3 => TokenType::City,
4 => TokenType::Industry,
_ => TokenType::Crypto,
};
let metadata = AssetMetadata {
deposit: Zero::zero(),
name,
symbol: symbol_to_bounded_vec,
decimals: *decimals,
is_frozen: false,
description: ""
.as_bytes()
.to_vec()
.try_into()
.expect("description is too long"),
is_deletable: if token_type == TokenType::Crypto && id != &T::AssetId::zero() {
true
} else {
false
},
is_changeable: true,
is_ecosystem_asset: if token_type == TokenType::City || token_type == TokenType::Industry {
true
} else {
false
},
token_type,
website: "".as_bytes().to_vec().try_into().expect("website is too long"),
location: "".as_bytes().to_vec().try_into().expect("location is too long"),
telegram_url: ""
.as_bytes()
.to_vec()
.try_into()
.expect("telegram url is too long"),
instagram_url: ""
.as_bytes()
.to_vec()
.try_into()
.expect("instagram url is too long"),
linkedin_link: ""
.as_bytes()
.to_vec()
.try_into()
.expect("linkedin link is too long"),
twitter_link: ""
.as_bytes()
.to_vec()
.try_into()
.expect("twitter link is too long"),
youtube_url: ""
.as_bytes()
.to_vec()
.try_into()
.expect("youtube url is too long"),
whatsapp_contact_number: ""
.as_bytes()
.to_vec()
.try_into()
.expect("whatsapp link is too long"),
industry_token_symbol: ""
.as_bytes()
.to_vec()
.try_into()
.expect("industry token symbol is too long"),
city_token_symbol: ""
.as_bytes()
.to_vec()
.try_into()
.expect("city token symbol is too long"),
slide_deck_link: ""
.as_bytes()
.to_vec()
.try_into()
.expect("slide deck link is too long"),
tiktok_link: ""
.as_bytes()
.to_vec()
.try_into()
.expect("tiktok link is too long"),
asset_picture_color: "#28A745"
.as_bytes()
.to_vec()
.try_into()
.expect("asset picture color link is too long"),
asset_picture_initials: BoundedVec::<u8, T::StringLimit>
::try_from(symbol.clone())
.unwrap(),
created_time: None,
};
Metadata::<T, I>::insert(id, metadata);
}
for (id, account_id, amount) in &self.accounts {
let result = <Pallet<T, I>>::increase_balance(
*id,
account_id,
*amount,
|details| -> DispatchResult {
debug_assert!(
T::Balance::max_value() - details.supply >= *amount,
"checked in prep; qed"
);
let metadata = Metadata::<T, I>::get(id);
if metadata.token_type != TokenType::Crypto {
details.max_supply = details.max_supply.saturating_add(*amount);
}
details.supply = details.supply.saturating_add(*amount);
Ok(())
}
);
assert!(result.is_ok());
}
let mut id = T::AssetId::zero();
let last_id = NextAssetId::<T, I>::get();
let bank_account = T::Bank::get_bank_account();
while id != last_id {
let metadata = Metadata::<T, I>::get(id);
// Sends the difference between the maximum supply (`max_supply`) and the current supply (`supply`)
// only to tokens with the token type "CRYPTO".
if metadata.token_type == TokenType::Crypto {
let asset = Asset::<T, I>::get(id).unwrap();
let asset_max_supply = asset.max_supply;
let asset_supply = asset.supply;
let asset_min_balance = asset.min_balance;
let bank_amount = asset_max_supply
.checked_sub(&asset_supply)
.unwrap_or(Zero::zero());
let result = <Pallet<T, I>>::increase_balance(
id,
&bank_account,
bank_amount + asset_min_balance,
|_details| -> DispatchResult {
T::Bank::insert_bank_balance(id, bank_amount);
T::Bank::insert_create_transaction(id, bank_amount);
Ok(())
}
);
assert!(result.is_ok());
}
id = id.saturating_add(T::AssetId::one());
}
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
/// Some asset class was created.
Created {
asset_id: T::AssetId,
creator: T::AccountId,
owner: T::AccountId,
},
/// Some assets were issued.
Issued {
asset_id: T::AssetId,
owner: T::AccountId,
amount: T::Balance,
},
/// Some assets were transferred.
Transferred {
asset_id: T::AssetId,
from: T::AccountId,
to: T::AccountId,
amount: T::Balance,
},
/// Some assets were destroyed.
Burned {
asset_id: T::AssetId,
owner: T::AccountId,
balance: T::Balance,
},
/// The management team changed.
TeamChanged {
asset_id: T::AssetId,
issuer: T::AccountId,
admin: T::AccountId,
freezer: T::AccountId,
},
/// The owner changed.
OwnerChanged {
asset_id: T::AssetId,
owner: T::AccountId,
},
/// Some account `who` was frozen.
Frozen {
asset_id: T::AssetId,
who: T::AccountId,
},
/// Some account `who` was thawed.
Thawed {
asset_id: T::AssetId,
who: T::AccountId,
},
/// Some asset `asset_id` was frozen.
AssetFrozen {
asset_id: T::AssetId,
},
/// Some asset `asset_id` was thawed.
AssetThawed {
asset_id: T::AssetId,
},
/// Accounts were destroyed for given asset.
AccountsDestroyed {
asset_id: T::AssetId,
accounts_destroyed: u32,
accounts_remaining: u32,
},
/// Approvals were destroyed for given asset.
ApprovalsDestroyed {
asset_id: T::AssetId,
approvals_destroyed: u32,
approvals_remaining: u32,
},
/// An asset class is in the process of being destroyed.
DestructionStarted {
asset_id: T::AssetId,
},
/// An asset class was destroyed.
Destroyed {
asset_id: T::AssetId,
},
/// Some asset class was force-created.
ForceCreated {
asset_id: T::AssetId,
owner: T::AccountId,
},
/// New metadata has been set for an asset.
MetadataSet {
asset_id: T::AssetId,
name: BoundedVec<u8, T::NameMaxLength>,
symbol: BoundedVec<u8, T::SymbolMaxLength>,
decimals: u8,
},
/// Metadata has been cleared for an asset.
MetadataCleared {
asset_id: T::AssetId,
},
/// (Additional) funds have been approved for transfer to a destination account.
ApprovedTransfer {
asset_id: T::AssetId,
source: T::AccountId,
delegate: T::AccountId,
amount: T::Balance,
},
/// An approval for account `delegate` was cancelled by `owner`.
ApprovalCancelled {
asset_id: T::AssetId,
owner: T::AccountId,
delegate: T::AccountId,
},
/// An `amount` was transferred in its entirety from `owner` to `destination` by
/// the approved `delegate`.
TransferredApproved {
asset_id: T::AssetId,
owner: T::AccountId,
delegate: T::AccountId,
destination: T::AccountId,
amount: T::Balance,
},
/// An asset has had its attributes changed by the `Force` origin.
AssetStatusChanged {
asset_id: T::AssetId,
},
/// Some account `who` was created with a deposit from `depositor`.
Touched {
asset_id: T::AssetId,
who: T::AccountId,
depositor: T::AccountId,
},
/// Some account `who` was blocked.
Blocked {
asset_id: T::AssetId,
who: T::AccountId,
},
/// An asset was set to be an ecosystem asset.
SetEcosystemAsset {
who: T::AccountId,
asset_id: T::AssetId,
},
/// An asset was deleted.
DeletedAsset {
who: T::AccountId,
asset_id: T::AssetId,
},
}
#[pallet::error]
pub enum Error<T, I = ()> {
/// Account balance must be greater than or equal to the transfer amount.
BalanceLow,
/// The account to alter is not registered in the account storage.
NoAccount,
/// The signing account has no permission to do the operation.
NoPermission,
/// The given asset ID is unknown.
Unknown,
/// The origin account is frozen.
Frozen,
/// The asset ID is already taken.
InUse,
/// Minimum balance should be non-zero.
MinBalanceZero,
/// Unable to increment the consumer reference counters on the account. Either no provider
/// reference exists to allow a non-zero balance of a non-self-sufficient asset, or one
/// fewer then the maximum number of consumers has been reached.
UnavailableConsumer,
/// Invalid metadata given.
BadMetadata,
/// No approval exists that would allow the transfer.
Unapproved,
/// The source account would not survive the transfer and it needs to stay alive.
WouldDie,
/// The asset-account already exists.
AlreadyExists,
/// The asset-account doesn't have an associated deposit.
NoDeposit,
/// The operation would result in funds being burned.
WouldBurn,
/// The asset is a live asset and is actively being used. Usually emit for operations such
/// as `start_destroy` which require the asset to be in a destroying state.
LiveAsset,
/// The asset is not live, and likely being destroyed.
AssetNotLive,
/// The asset status is not the expected status.
IncorrectStatus,
/// The asset should be frozen before the given operation.
NotFrozen,
/// The asset maximum supply should be greater than the minimum.
MaxSupplyLow,
/// The symbol is already being used in this token.
SymbolInUse,
/// The symbol doesn't fill all the requirements.
SymbolNotGood,
/// The asset trying to be minted plus the supply is over the maximum supply
OutOfTokensToMint,
/// The asset symbol modification cannot happen due to a transaction already occured
CannotChangeSymbol,
/// The asset maximum supply modification cannot happen due to a transaction already
/// occured
CannotChangeMaxSupply,
/// The asset name can't have 0 length
NameTooSmall,
/// The asset symbol can't have 0 length
SymbolTooSmall,
/// The asset cannot be set to ecosystem asset because it's already.
AlreadyEcosystemAsset,
/// The picture initials length is equal to zero or greater than 4.
PictureInitialsNotGood,
/// Callback action resulted in error
CallbackFailed,
/// Vector is full
VectorError,
/// A transaction already happened using this asset, so the it cannot be deleted.
AssetCannotBeDeleted,
/// Updates are allowed only for tokens with the token type "CRYPTO" and that are not the UNIT token.
OnlyCryptoTokenCanBeUpdated,
}
#[pallet::call(weight(<T as Config<I>>::WeightInfo))]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// The origin can create a new asset.
///
/// Parameters:
/// - `name`: The name of the new asset.
///
/// Emits `Created` event when successful.
#[pallet::call_index(0)]
pub fn create(
origin: OriginFor<T>,
name: BoundedVec<u8, T::NameMaxLength>
) -> DispatchResult {
let id = NextAssetId::<T, I>::get();
let sender = T::CreateOrigin::ensure_origin(origin, &id)?;
// We modify the info of the main account
let owner = T::SubAccounts::get_main_account(sender)?;
let min_balance = (100u32).into();
Self::do_create(id, &owner, &owner, name, min_balance)
}
/// The origin can move some assets from the sender account to another.
///
/// Parameters:
/// - `id`: The identifier of the asset to have some amount transferred.
/// - `target`: The account to be credited.
/// - `amount`: The amount by which the sender's balance of assets should be reduced and
/// `target`'s balance increased. The amount actually transferred may be slightly greater in
/// the case that the transfer would otherwise take the sender balance above zero but below
/// the minimum balance. Must be greater than zero.
///
/// Emits `Transferred` event when successful.
#[pallet::call_index(1)]
pub fn transfer(
origin: OriginFor<T>,
id: T::AssetId,
to: T::AccountId,
amount: T::Balance
) -> 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 dest = T::SubAccounts::get_main_account(to)?;
let f = TransferFlags { keep_alive: true, best_effort: false, burn_dust: false };
Self::do_transfer(id, &origin, &dest, amount, None, f).map(|_| ())?;
let transfer_id = NextTransferId::<T, I>::get();
let transfer = Transfer {
from: origin.clone(),
to: dest.clone(),
token_id: id,
amount,
time: T::TimeProvider::now().as_secs(),
};
TransfersId::<T, I>::mutate(origin, |transfers_id_vector|
transfers_id_vector.push(transfer_id)
);
TransfersId::<T, I>::mutate(dest, |transfers_id_vector|
transfers_id_vector.push(transfer_id)
);
Transfers::<T, I>::insert(transfer_id, transfer);
let next_transfer_id = transfer_id.checked_add(1).ok_or(ArithmeticError::Overflow)?;
NextTransferId::<T, I>::set(next_transfer_id);
Ok(())
}
/// The origin can set the metadata for an asset.
///
/// Parameters:
/// - `id`: The token ID of the asset,
/// - `name`: The name of the asset,
/// - `symbol`: The symbol of the asset,
/// - `max_supply`: The maximum supply of the asset,
/// - `description`: The description of the asset,
/// - `website`: The website of the asset,
/// - `industry_token_symbol`: The industry token symbol of the asset,
/// - `city_token_symbol`: The city token symbol of the asset,
/// - `telegram_url`: The Telegram group url of the asset,
/// - `instagram_url`: The Instagram account url of the asset,
/// - `linkedin_link`: The LinkedIn page link of the asset,
/// - `twitter_link`: The Twitter account link of the asset,
/// - `whatsapp_contact_number`: The WhatsApp contact number of the asset,
/// - `youtube_url`: The Youtube channel url of the asset,
/// - `slide_deck_link`: The slide deck link of the asset,
/// - `tiktok_link`: The TikTok account link of the asset,
/// - `asset_picture_color`: The asset picture color(hexadecimal) of the asset,
/// - `asset_picture_initials`: The asset picture initials of the asset
///
/// Emits `MetadataSet` event when successful.
#[pallet::call_index(2)]
pub fn set_metadata(
origin: OriginFor<T>,
id: T::AssetId,
name: BoundedVec<u8, T::NameMaxLength>,
symbol: BoundedVec<u8, T::SymbolMaxLength>,
max_supply: T::Balance,
description: BoundedVec<u8, T::StringLimit>,
website: BoundedVec<u8, T::StringLimit>,
industry_token_symbol: BoundedVec<u8, T::StringLimit>,
city_token_symbol: BoundedVec<u8, T::StringLimit>,
telegram_url: BoundedVec<u8, T::StringLimit>,
instagram_url: BoundedVec<u8, T::StringLimit>,
linkedin_link: BoundedVec<u8, T::StringLimit>,
twitter_link: BoundedVec<u8, T::StringLimit>,
whatsapp_contact_number: BoundedVec<u8, T::StringLimit>,
youtube_url: BoundedVec<u8, T::StringLimit>,
slide_deck_link: BoundedVec<u8, T::StringLimit>,
tiktok_link: BoundedVec<u8, T::StringLimit>,
asset_picture_color: BoundedVec<u8, T::StringLimit>,
asset_picture_initials: BoundedVec<u8, T::StringLimit>
) -> DispatchResult {
let mut origin = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
origin = T::SubAccounts::get_main_account(origin)?;
// Ensure that only tokens with the token type "CRYPTO" and not the UNIT token are updateable.
ensure!(
Self::is_crypto(id) &&
id != Self::get_unit_asset_id().ok_or(Error::<T, I>::Unknown)?,
Error::<T, I>::OnlyCryptoTokenCanBeUpdated
);
Self::do_set_metadata(
&origin,
id,
name,
symbol,
max_supply,
description,
website,
industry_token_symbol,
city_token_symbol,
telegram_url,
instagram_url,
linkedin_link,
twitter_link,
whatsapp_contact_number,
youtube_url,
slide_deck_link,
tiktok_link,
asset_picture_color,
asset_picture_initials
)
}
/// The origin can set the asset to be an ecosystem asset.
///
/// Parameters:
/// - `id`: The token ID of the asset,
///
/// Emits `SetEcosystemAsset` event when successful.
#[pallet::call_index(3)]
pub fn set_ecosystem_asset(origin: OriginFor<T>, id: T::AssetId) -> DispatchResult {
let mut origin = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
origin = T::SubAccounts::get_main_account(origin)?;
// Ensure that only tokens with the token type "CRYPTO" and not the UNIT token are eligible to be set as an ecosystem asset.
ensure!(
Self::is_crypto(id) &&
id != Self::get_unit_asset_id().ok_or(Error::<T, I>::Unknown)?,
Error::<T, I>::OnlyCryptoTokenCanBeUpdated
);
// ensure that the sender is the owner
let asset = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
ensure!(origin == asset.owner, Error::<T, I>::NoPermission);
ensure!(Asset::<T, I>::contains_key(id), Error::<T, I>::Unknown);
Metadata::<T, I>::mutate_exists(id, |metadata| -> DispatchResult {
if let Some(metadata) = metadata {
ensure!(!metadata.is_ecosystem_asset, Error::<T, I>::AlreadyEcosystemAsset);
metadata.is_ecosystem_asset = true;
metadata.is_changeable = false;
}
Ok(())
})?;
T::Bank::transfer_all_tokens_from_bank_ecosystem_to_treasury(origin.clone(), id)?;
Self::deposit_event(Event::SetEcosystemAsset {
who: origin,
asset_id: id,
});
Ok(())
}
// This function is only for testing purposes. SHOULD BE REMOVED
#[pallet::call_index(4)]
#[pallet::weight((0, Pays::No))]
pub fn mint_faucet(
origin: OriginFor<T>,
id: T::AssetId,
amount: T::Balance
) -> DispatchResult {
let mut origin = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
origin = T::SubAccounts::get_main_account(origin)?;
Self::do_mint(id, &origin, amount, None, true)
}
/// The origin can delete an asset if they are the owner and any transaction happened using it.
///
/// Parameters:
/// - `id`: The token ID of the asset,
///
/// Emits `DeleteAsset` event when successful.
#[pallet::call_index(5)]
pub fn delete_asset(origin: OriginFor<T>, id: T::AssetId) -> DispatchResult {
let mut origin = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
origin = T::SubAccounts::get_main_account(origin)?;
// Ensure that only tokens with the token type "CRYPTO" (excluding the UNIT token) are eligible for deletion.
ensure!(
Self::is_crypto(id) &&
id != Self::get_unit_asset_id().ok_or(Error::<T, I>::Unknown)?,
Error::<T, I>::OnlyCryptoTokenCanBeUpdated
);
Asset::<T, I>::try_mutate(id, |maybe_details| -> DispatchResult {
if let Some(details) = maybe_details {
ensure!(origin == details.owner, Error::<T, I>::NoPermission);
let metadata = Metadata::<T, I>::get(id);
ensure!(
metadata.is_changeable && metadata.is_deletable,
Error::<T, I>::AssetCannotBeDeleted
);
let bank_account = T::Bank::get_bank_account();
Account::<T, I>::remove(id, &bank_account);
T::Bank::delete_all_data_from_asset(id);
Metadata::<T, I>::remove(id);
Ok(())
} else {
Err(Error::<T, I>::Unknown.into())
}
})?;
Asset::<T, I>::remove(id);
Self::deposit_event(Event::DeletedAsset {
who: origin,
asset_id: id,
});
Ok(())
}
}
}
sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);