//! # Unit Grants Pallet
//! <!-- Original author of paragraph: @matthieu
//!
//! ## Overview
//!
//! Pallet that allows a user to make a grant request for Unit Network.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * Create a grant.
//! * Send a grant
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! - `create_grant`: Make a grant request.
//! - `sendgrant` : Send a grand.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
#![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_std::{vec};
use traits::subaccounts::{ SubAccounts, AccountOrigin };
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::WeightInfo;
#[cfg(any(feature = "runtime-benchmarks"))]
use traits::profile::ProfileInterface;
mod types;
pub use types::Grant;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{
pallet_prelude::*,
traits::{fungibles, fungibles::*, tokens::Preservation::Expendable, UnixTime},
};
use frame_system::pallet_prelude::*;
use sp_runtime::{traits::Saturating, ArithmeticError, SaturatedConversion};
#[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 to access the Assets Pallet.
type Fungibles: fungibles::Inspect<Self::AccountId>
+ //, AssetId = u32> // Hash this
fungibles::Mutate<Self::AccountId>
+ fungibles::metadata::Inspect<Self::AccountId>
+ fungibles::Create<Self::AccountId>;
/// Type to access the sub account pallet
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>;
/// Trading fee rate
#[pallet::constant]
type GetExchangeFee: Get<(u32, u32)>;
#[pallet::constant]
type MaxMessageLength: Get<u32>;
#[pallet::constant]
type ProfileStringLimit: Get<u32>;
/// Helper trait for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type ProfileBenchmarkHelper: ProfileInterface<Self::AccountId, Self::ProfileStringLimit>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
/// Provides an interface to get the actual time in Unix Time.
type TimeProvider: UnixTime;
}
// Balance type
pub type BalanceOf<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
// AssetId type
pub type AssetId<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;
// Grant id type
pub type GrantId = u128;
/// Storage of all the grants
#[pallet::storage]
#[pallet::getter(fn grants)]
pub type AllGrants<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
AssetId<T>,
Twox64Concat,
GrantId,
Grant<T, T::AccountId, AssetId<T>, BalanceOf<T>>,
OptionQuery,
>;
/// Store the next grant id for each asset id.
/// asset_id -> grant_id
#[pallet::storage]
#[pallet::getter(fn get_next_grant_id)]
pub type NextGrantId<T: Config> = StorageMap<_, Twox64Concat, AssetId<T>, GrantId, 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> {
// When a grant request has been created
GrantCreated {
grant_id: GrantId,
who: T::AccountId,
token_id: AssetId<T>,
amount: BalanceOf<T>,
},
// When someone send a grant
GrantSent {
grant_id: GrantId,
who: T::AccountId,
token_id: AssetId<T>,
amount: BalanceOf<T>,
},
}
// Errors inform users that something went wrong.
#[pallet::error]
#[derive(PartialEq)]
pub enum Error<T> {
//Grant not found
GrantNotFound,
RequestedGrantAmountCannotBeZero,
ContributionAmountCannotBeZero,
GrantFulfilledNoMoreContributions,
}
// 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> {
// Request for a grant
/// The origin can create a grant for a particular asset.
///
/// The origin must be Signed.
///
/// The amount should be parsed with the USDU decimals
///
/// Parameters:
/// - `token_id`: The asset id of the grant request.
/// - `amount`: The amount in USD that the origin asks.
/// - `title`: The description of why the origin is asking a grant.
///
/// Emits `GrantCreated` event when successful.
#[pallet::call_index(6)]
pub fn create_grant(
origin: OriginFor<T>,
token_id: AssetId<T>,
amount: BalanceOf<T>,
title: BoundedVec<u8, T::MaxMessageLength>,
) -> DispatchResult {
let mut who = ensure_signed(origin.clone())?;
// Mutate origin so main account of user creates grant.
who = T::SubAccounts::get_main_account(who)?;
ensure!(
amount > (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::RequestedGrantAmountCannotBeZero
);
// Get the id of the new grant
let grant_id = NextGrantId::<T>::get(&token_id);
// Create the request for a grant
let grant: Grant<T, T::AccountId, AssetId<T>, BalanceOf<T>> = Grant {
account: who.clone(),
token_id: token_id.clone(),
total_amount: amount.clone(),
current_amount: Self::u32_to_asset_balance(0),
id: grant_id,
title: title.clone(),
contributions_account: Default::default(),
contributions_amount: Default::default(),
contributions_time: Default::default(),
};
// Add new grant to storage
AllGrants::<T>::insert(&token_id, grant_id, grant);
// Update next grant id
let next_grant_id = grant_id.checked_add(1).ok_or(ArithmeticError::Overflow)?;
NextGrantId::<T>::insert(&token_id, next_grant_id);
// Make an event of this new grant
Self::deposit_event(Event::GrantCreated { grant_id, who, token_id, amount });
Ok(())
}
/// Send a grant
/// The origin can send a grant for a particular asset.
///
/// The origin must be Signed.
///
/// The amount should be parsed with the USDU decimals
///
/// Parameters:
/// - `token_id`: The asset id of the grant request.
/// - `amount`: The amount in USD that the origin will transfer.
/// - `grant_id`: The id of the grant we want to send.
/// - `time` : Time of the transaction, handled by the frond end. Modify it to use
/// Timestamp.
///
/// Emits `GrantSent` event when successful.
#[pallet::call_index(7)]
pub fn sendgrant(
origin: OriginFor<T>,
token_id: AssetId<T>,
grant_id: GrantId,
amount: BalanceOf<T>,
) -> DispatchResult {
let mut who = ensure_signed(origin.clone())?;
// Mutate origin to transfer funds from main account.
who = T::SubAccounts::get_main_account(who)?;
// Getting time of contribution.
let time: u64 = T::TimeProvider::now().as_secs();
ensure!(
amount > (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::ContributionAmountCannotBeZero
);
let mut grant =
AllGrants::<T>::get(&token_id, grant_id).ok_or(Error::<T>::GrantNotFound)?;
// Calculate the remaining amount requested
let remaining_amount = grant.total_amount.saturating_sub(grant.current_amount);
// If the amount of the user is more than the remaining, then we only send the
// remaining, or else we send what the user set
let transfer_amount = if amount > remaining_amount { remaining_amount } else { amount };
ensure!(
transfer_amount > (0u128).saturated_into::<BalanceOf<T>>(),
Error::<T>::GrantFulfilledNoMoreContributions
);
// Update the amounts
grant.current_amount = grant.current_amount.saturating_add(transfer_amount);
grant.contributions_account.push(who.clone());
grant.contributions_amount.push(transfer_amount.clone());
grant.contributions_time.push(time);
// We make the transfer
T::Fungibles::transfer(
token_id.clone(),
&who,
&grant.account,
transfer_amount.clone(),
Expendable,
)?;
// We update the storage
AllGrants::<T>::insert(&token_id, grant_id, grant);
// We make an event
Self::deposit_event(Event::GrantSent {
grant_id,
who,
token_id,
amount: transfer_amount,
});
Ok(())
}
}
impl<T: Config> Pallet<T> {
pub fn u32_to_asset_balance(input: u32) -> BalanceOf<T> {
input.try_into().ok().unwrap()
}
pub fn asset_balance_to_u64(input: BalanceOf<T>) -> u64 {
TryInto::<u64>::try_into(input).ok().unwrap()
}
pub fn u64_to_asset_balance(input: u64) -> BalanceOf<T> {
input.try_into().ok().unwrap()
}
pub fn u64_to_fungible_balance(input: u64) -> BalanceOf<T> {
input.try_into().ok().unwrap()
}
}
}