#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::pallet_prelude::*;
use codec::{Decode, Encode};
use frame_support::{
traits::{
Contains, ExistenceRequirement, Get, MultiTokenCurrency, MultiTokenVestingLocks,
StorageVersion,
},
transactional, PalletId,
};
use frame_system::{ensure_root, ensure_signed, pallet_prelude::*};
use mangata_support::traits::{
AssetRegistryApi, GetMaintenanceStatusTrait, PoolCreateApi, ProofOfStakeRewardsApi,
};
use mangata_types::multipurpose_liquidity::ActivateKind;
use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency};
use scale_info::TypeInfo;
use sp_arithmetic::{helpers_128bit::multiply_by_rational_with_rounding, per_things::Rounding};
use sp_core::U256;
use sp_io::KillStorageResult;
use sp_runtime::traits::{
AccountIdConversion, Bounded, CheckedAdd, One, SaturatedConversion, Saturating, Zero,
};
use sp_std::{convert::TryInto, prelude::*};
#[cfg(test)]
mod mock;
mod benchmarking;
#[cfg(test)]
mod tests;
pub mod weights;
pub use weights::WeightInfo;
pub use pallet::*;
const PALLET_ID: PalletId = PalletId(*b"bootstrp");
#[macro_export]
macro_rules! log {
($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
log::$level!(
target: "bootstrap",
concat!("[{:?}] 💸 ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
)
};
}
type BalanceOf<T> = <<T as Config>::Currency as MultiTokenCurrency<
<T as frame_system::Config>::AccountId,
>>::Balance;
type CurrencyIdOf<T> = <<T as Config>::Currency as MultiTokenCurrency<
<T as frame_system::Config>::AccountId,
>>::CurrencyId;
type BlockNrAsBalance<T> = BalanceOf<T>;
#[frame_support::pallet]
pub mod pallet {
use super::*;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
#[pallet::pallet]
#[pallet::without_storage_info]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
let phase = Phase::<T>::get(); if phase == BootstrapPhase::Finished {
return T::DbWeight::get().reads(1)
}
if let Some((start, whitelist_length, public_length, _)) = BootstrapSchedule::<T>::get()
{
let whitelist_start = start;
let public_start = start + whitelist_length.into();
let finished = start + whitelist_length.into() + public_length.into();
if n >= finished {
Phase::<T>::put(BootstrapPhase::Finished); log!(info, "bootstrap event finished");
let (second_token_valuation, first_token_valuation) = Valuations::<T>::get();
if !T::AssetRegistryApi::enable_pool_creation((
Self::first_token_id(),
Self::second_token_id(),
)) {
log!(error, "cannot modify asset registry!");
}
if let Some((liq_asset_id, issuance)) = T::PoolCreateApi::pool_create(
Self::vault_address(),
Self::first_token_id(),
first_token_valuation,
Self::second_token_id(),
second_token_valuation,
) {
MintedLiquidity::<T>::put((liq_asset_id, issuance)); if PromoteBootstrapPool::<T>::get() {
T::RewardsApi::enable(
liq_asset_id,
T::DefaultBootstrapPromotedPoolWeight::get(),
);
}
} else {
log!(error, "cannot create pool!");
}
T::DbWeight::get().reads_writes(21, 18)
} else if n >= public_start {
if phase != BootstrapPhase::Public {
Phase::<T>::put(BootstrapPhase::Public);
log!(info, "starting public phase");
T::DbWeight::get().reads_writes(2, 1)
} else {
T::DbWeight::get().reads(2)
}
} else if n >= whitelist_start {
if phase != BootstrapPhase::Whitelist {
log!(info, "starting whitelist phase");
Phase::<T>::put(BootstrapPhase::Whitelist);
T::DbWeight::get().reads_writes(2, 1)
} else {
T::DbWeight::get().reads(2)
}
} else {
T::DbWeight::get().reads(2)
}
} else {
T::DbWeight::get().reads(2)
}
}
}
#[cfg(feature = "runtime-benchmarks")]
pub trait BootstrapBenchmarkingConfig {}
#[cfg(not(feature = "runtime-benchmarks"))]
pub trait BootstrapBenchmarkingConfig {}
#[pallet::config]
pub trait Config: frame_system::Config + BootstrapBenchmarkingConfig {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type MaintenanceStatusProvider: GetMaintenanceStatusTrait;
type Currency: MultiTokenCurrencyExtended<Self::AccountId>
+ MultiTokenReservableCurrency<Self::AccountId>;
type PoolCreateApi: PoolCreateApi<Self::AccountId, BalanceOf<Self>, CurrencyIdOf<Self>>;
#[pallet::constant]
type DefaultBootstrapPromotedPoolWeight: Get<u8>;
#[pallet::constant]
type BootstrapUpdateBuffer: Get<BlockNumberFor<Self>>;
#[pallet::constant]
type TreasuryPalletId: Get<PalletId>;
type VestingProvider: MultiTokenVestingLocks<
Self::AccountId,
Currency = Self::Currency,
Moment = BlockNumberFor<Self>,
>;
type ClearStorageLimit: Get<u32>;
type WeightInfo: WeightInfo;
type RewardsApi: ProofOfStakeRewardsApi<
Self::AccountId,
BalanceOf<Self>,
CurrencyIdOf<Self>,
>;
type AssetRegistryApi: AssetRegistryApi<CurrencyIdOf<Self>>;
}
#[pallet::storage]
#[pallet::getter(fn provisions)]
pub type Provisions<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
T::AccountId,
Twox64Concat,
CurrencyIdOf<T>,
BalanceOf<T>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn vested_provisions)]
pub type VestedProvisions<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
T::AccountId,
Twox64Concat,
CurrencyIdOf<T>,
(BalanceOf<T>, BlockNrAsBalance<T>, BlockNrAsBalance<T>),
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn whitelisted_accounts)]
pub type WhitelistedAccount<T: Config> =
StorageMap<_, Twox64Concat, T::AccountId, (), ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn phase)]
pub type Phase<T: Config> = StorageValue<_, BootstrapPhase, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn valuations)]
pub type Valuations<T: Config> = StorageValue<_, (BalanceOf<T>, BalanceOf<T>), ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn config)]
pub type BootstrapSchedule<T: Config> =
StorageValue<_, (BlockNumberFor<T>, u32, u32, (BalanceOf<T>, BalanceOf<T>)), OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn minted_liquidity)]
pub type MintedLiquidity<T: Config> =
StorageValue<_, (CurrencyIdOf<T>, BalanceOf<T>), ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn claimed_rewards)]
pub type ClaimedRewards<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
T::AccountId,
Twox64Concat,
CurrencyIdOf<T>,
BalanceOf<T>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn provision_accounts)]
pub type ProvisionAccounts<T: Config> =
StorageMap<_, Twox64Concat, T::AccountId, (), OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn pair)]
pub type ActivePair<T: Config> =
StorageValue<_, (CurrencyIdOf<T>, CurrencyIdOf<T>), OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn get_promote_bootstrap_pool)]
pub type PromoteBootstrapPool<T: Config> = StorageValue<_, bool, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn archived)]
pub type ArchivedBootstrap<T: Config> = StorageValue<
_,
Vec<(BlockNumberFor<T>, u32, u32, (BalanceOf<T>, BalanceOf<T>))>,
ValueQuery,
>;
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<<T as Config>::WeightInfo>::provision())]
#[transactional]
pub fn provision(
origin: OriginFor<T>,
token_id: CurrencyIdOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
let sender = ensure_signed(origin)?;
ensure!(
!T::MaintenanceStatusProvider::is_maintenance(),
Error::<T>::ProvisioningBlockedByMaintenanceMode
);
Self::do_provision(&sender, token_id, amount)?;
ProvisionAccounts::<T>::insert(&sender, ());
Self::deposit_event(Event::Provisioned(token_id, amount));
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(T::DbWeight::get().writes(1) * (accounts.len() as u64))]
#[transactional]
pub fn whitelist_accounts(
origin: OriginFor<T>,
accounts: Vec<T::AccountId>,
) -> DispatchResult {
ensure_root(origin)?;
for account in accounts {
WhitelistedAccount::<T>::insert(&account, ());
}
Self::deposit_event(Event::AccountsWhitelisted);
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight(<<T as Config>::WeightInfo>::schedule_bootstrap())]
#[transactional]
pub fn schedule_bootstrap(
origin: OriginFor<T>,
first_token_id: CurrencyIdOf<T>,
second_token_id: CurrencyIdOf<T>,
ido_start: BlockNumberFor<T>,
whitelist_phase_length: Option<u32>,
public_phase_length: u32,
max_first_to_second_ratio: Option<(BalanceOf<T>, BalanceOf<T>)>,
promote_bootstrap_pool: bool,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(Phase::<T>::get() == BootstrapPhase::BeforeStart, Error::<T>::AlreadyStarted);
if let Some((scheduled_ido_start, _, _, _)) = BootstrapSchedule::<T>::get() {
let now = <frame_system::Pallet<T>>::block_number();
ensure!(
now.saturating_add(T::BootstrapUpdateBuffer::get()) < scheduled_ido_start,
Error::<T>::TooLateToUpdateBootstrap
);
}
ensure!(first_token_id != second_token_id, Error::<T>::SameToken);
ensure!(T::Currency::exists(first_token_id.into()), Error::<T>::TokenIdDoesNotExists);
ensure!(T::Currency::exists(second_token_id.into()), Error::<T>::TokenIdDoesNotExists);
ensure!(
ido_start > frame_system::Pallet::<T>::block_number(),
Error::<T>::BootstrapStartInThePast
);
let whitelist_phase_length = whitelist_phase_length.unwrap_or_default();
let max_first_to_second_ratio = max_first_to_second_ratio
.unwrap_or((BalanceOf::<T>::max_value(), BalanceOf::<T>::one()));
ensure!(max_first_to_second_ratio.0 != BalanceOf::<T>::zero(), Error::<T>::WrongRatio);
ensure!(max_first_to_second_ratio.1 != BalanceOf::<T>::zero(), Error::<T>::WrongRatio);
ensure!(public_phase_length > 0, Error::<T>::PhaseLengthCannotBeZero);
ensure!(
ido_start
.checked_add(&whitelist_phase_length.into())
.and_then(|whiteslist_start| whiteslist_start
.checked_add(&public_phase_length.into()))
.is_some(),
Error::<T>::MathOverflow
);
ensure!(
ido_start.checked_add(&whitelist_phase_length.into()).is_some(),
Error::<T>::MathOverflow
);
ensure!(
!T::PoolCreateApi::pool_exists(first_token_id, second_token_id),
Error::<T>::PoolAlreadyExists
);
ActivePair::<T>::put((first_token_id, second_token_id));
BootstrapSchedule::<T>::put((
ido_start,
whitelist_phase_length,
public_phase_length,
max_first_to_second_ratio,
));
PromoteBootstrapPool::<T>::put(promote_bootstrap_pool);
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight(T::DbWeight::get().reads_writes(3, 4).saturating_add(Weight::from_parts(1_000_000, 0)))]
#[transactional]
pub fn cancel_bootstrap(origin: OriginFor<T>) -> DispatchResult {
ensure_root(origin)?;
let now = <frame_system::Pallet<T>>::block_number();
let (ido_start, _, _, _) =
BootstrapSchedule::<T>::get().ok_or(Error::<T>::BootstrapNotSchduled)?;
ensure!(Phase::<T>::get() == BootstrapPhase::BeforeStart, Error::<T>::AlreadyStarted);
ensure!(
now.saturating_add(T::BootstrapUpdateBuffer::get()) < ido_start,
Error::<T>::TooLateToUpdateBootstrap
);
ActivePair::<T>::kill();
BootstrapSchedule::<T>::kill();
PromoteBootstrapPool::<T>::kill();
Phase::<T>::put(BootstrapPhase::BeforeStart);
Ok(())
}
#[pallet::call_index(4)]
#[pallet::weight(T::DbWeight::get().reads_writes(2, 1).saturating_add(Weight::from_parts(1_000_000, 0)))]
#[transactional]
pub fn update_promote_bootstrap_pool(
origin: OriginFor<T>,
promote_bootstrap_pool: bool,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(BootstrapSchedule::<T>::get().is_some(), Error::<T>::BootstrapNotSchduled);
ensure!(Phase::<T>::get() != BootstrapPhase::Finished, Error::<T>::BootstrapFinished);
PromoteBootstrapPool::<T>::put(promote_bootstrap_pool);
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight(<<T as Config>::WeightInfo>::claim_and_activate_liquidity_tokens())]
#[transactional]
pub fn claim_liquidity_tokens(origin: OriginFor<T>) -> DispatchResult {
let sender = ensure_signed(origin)?;
Self::do_claim_liquidity_tokens(&sender, false)
}
#[pallet::call_index(6)]
#[pallet::weight(<<T as Config>::WeightInfo>::claim_and_activate_liquidity_tokens())]
#[transactional]
pub fn claim_and_activate_liquidity_tokens(origin: OriginFor<T>) -> DispatchResult {
let sender = ensure_signed(origin)?;
Self::do_claim_liquidity_tokens(&sender, true)
}
#[pallet::call_index(7)]
#[pallet::weight(Weight::from_parts(40_000_000, 0)
.saturating_add(T::DbWeight::get().reads_writes(6, 0)
.saturating_add(T::DbWeight::get().reads_writes(1, 1).saturating_mul(Into::<u64>::into(T::ClearStorageLimit::get())))))]
#[transactional]
pub fn pre_finalize(origin: OriginFor<T>) -> DispatchResult {
let _ = ensure_signed(origin)?;
ensure!(Self::phase() == BootstrapPhase::Finished, Error::<T>::NotFinishedYet);
ensure!(
ProvisionAccounts::<T>::iter_keys().next().is_none(),
Error::<T>::BootstrapNotReadyToBeFinished
);
let mut limit = T::ClearStorageLimit::get();
match VestedProvisions::<T>::clear(limit, None).into() {
KillStorageResult::AllRemoved(num_iter) => limit = limit.saturating_sub(num_iter),
KillStorageResult::SomeRemaining(_) => {
Self::deposit_event(Event::BootstrapParitallyPreFinalized);
return Ok(())
},
}
match WhitelistedAccount::<T>::clear(limit, None).into() {
KillStorageResult::AllRemoved(num_iter) => limit = limit.saturating_sub(num_iter),
KillStorageResult::SomeRemaining(_) => {
Self::deposit_event(Event::BootstrapParitallyPreFinalized);
return Ok(())
},
}
match ClaimedRewards::<T>::clear(limit, None).into() {
KillStorageResult::AllRemoved(num_iter) => limit = limit.saturating_sub(num_iter),
KillStorageResult::SomeRemaining(_) => {
Self::deposit_event(Event::BootstrapParitallyPreFinalized);
return Ok(())
},
}
match Provisions::<T>::clear(limit, None).into() {
KillStorageResult::AllRemoved(num_iter) => limit = limit.saturating_sub(num_iter),
KillStorageResult::SomeRemaining(_) => {
Self::deposit_event(Event::BootstrapParitallyPreFinalized);
return Ok(())
},
}
Self::deposit_event(Event::BootstrapReadyToBeFinalized);
Ok(())
}
#[pallet::call_index(8)]
#[pallet::weight(<<T as Config>::WeightInfo>::finalize())]
#[transactional]
pub fn finalize(origin: OriginFor<T>) -> DispatchResult {
let _ = ensure_signed(origin)?;
ensure!(Self::phase() == BootstrapPhase::Finished, Error::<T>::NotFinishedYet);
ensure!(
ProvisionAccounts::<T>::iter_keys().next().is_none(),
Error::<T>::BootstrapNotReadyToBeFinished
);
ensure!(
VestedProvisions::<T>::iter_keys().next().is_none(),
Error::<T>::BootstrapMustBePreFinalized
);
ensure!(
WhitelistedAccount::<T>::iter_keys().next().is_none(),
Error::<T>::BootstrapMustBePreFinalized
);
ensure!(
ClaimedRewards::<T>::iter_keys().next().is_none(),
Error::<T>::BootstrapMustBePreFinalized
);
ensure!(
Provisions::<T>::iter_keys().next().is_none(),
Error::<T>::BootstrapMustBePreFinalized
);
Phase::<T>::put(BootstrapPhase::BeforeStart);
let (liq_token_id, _) = MintedLiquidity::<T>::take();
let balance = T::Currency::free_balance(liq_token_id.into(), &Self::vault_address());
if balance > 0_u32.into() {
T::Currency::transfer(
liq_token_id.into(),
&Self::vault_address(),
&T::TreasuryPalletId::get().into_account_truncating(),
balance,
ExistenceRequirement::AllowDeath,
)?;
}
Valuations::<T>::kill();
ActivePair::<T>::kill();
PromoteBootstrapPool::<T>::kill();
if let Some(bootstrap) = BootstrapSchedule::<T>::take() {
ArchivedBootstrap::<T>::mutate(|v| {
v.push(bootstrap);
});
}
Self::deposit_event(Event::BootstrapFinalized);
Ok(())
}
#[pallet::call_index(9)]
#[pallet::weight(<<T as Config>::WeightInfo>::claim_and_activate_liquidity_tokens())]
#[transactional]
pub fn claim_liquidity_tokens_for_account(
origin: OriginFor<T>,
account: T::AccountId,
activate_rewards: bool,
) -> DispatchResult {
let _ = ensure_signed(origin)?;
Self::do_claim_liquidity_tokens(&account, activate_rewards)
}
}
#[pallet::error]
pub enum Error<T> {
UnsupportedTokenId,
NotEnoughAssets,
NotEnoughVestedAssets,
MathOverflow,
Unauthorized,
BootstrapStartInThePast,
PhaseLengthCannotBeZero,
AlreadyStarted,
ValuationRatio,
FirstProvisionInSecondTokenId,
PoolAlreadyExists,
NotFinishedYet,
NothingToClaim,
WrongRatio,
BootstrapNotReadyToBeFinished,
SameToken,
TokenIdDoesNotExists,
TokensActivationFailed,
BootstrapNotSchduled,
BootstrapFinished,
TooLateToUpdateBootstrap,
ProvisioningBlockedByMaintenanceMode,
BootstrapMustBePreFinalized,
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Provisioned(CurrencyIdOf<T>, BalanceOf<T>),
VestedProvisioned(CurrencyIdOf<T>, BalanceOf<T>),
RewardsLiquidityAcitvationFailed(T::AccountId, CurrencyIdOf<T>, BalanceOf<T>),
RewardsClaimed(CurrencyIdOf<T>, BalanceOf<T>),
AccountsWhitelisted,
BootstrapParitallyPreFinalized,
BootstrapReadyToBeFinalized,
BootstrapFinalized,
}
}
#[derive(Eq, PartialEq, Encode, Decode, TypeInfo, Debug)]
pub enum BootstrapPhase {
BeforeStart,
Whitelist,
Public,
Finished,
}
impl Default for BootstrapPhase {
fn default() -> Self {
BootstrapPhase::BeforeStart
}
}
impl<T: Config> Pallet<T> {
fn is_whitelisted(account: &T::AccountId) -> bool {
WhitelistedAccount::<T>::try_get(account).is_ok()
}
fn vault_address() -> T::AccountId {
PALLET_ID.into_account_truncating()
}
fn claim_liquidity_tokens_from_single_currency(
who: &T::AccountId,
provision_token_id: &CurrencyIdOf<T>,
rewards: BalanceOf<T>,
rewards_vested: BalanceOf<T>,
lock: (BlockNrAsBalance<T>, BlockNrAsBalance<T>),
) -> DispatchResult {
let (liq_token_id, _) = Self::minted_liquidity();
let total_rewards = rewards.checked_add(&rewards_vested).ok_or(Error::<T>::MathOverflow)?;
if total_rewards == BalanceOf::<T>::zero() {
return Ok(())
}
T::Currency::transfer(
liq_token_id.into(),
&Self::vault_address(),
who,
total_rewards,
ExistenceRequirement::KeepAlive,
)?;
ClaimedRewards::<T>::try_mutate(who, provision_token_id, |rewards| {
if let Some(val) = rewards.checked_add(&total_rewards) {
*rewards = val;
Ok(())
} else {
Err(Error::<T>::MathOverflow)
}
})?;
if rewards_vested > BalanceOf::<T>::zero() {
T::VestingProvider::lock_tokens(
who,
liq_token_id,
rewards_vested,
Some(lock.0.into().saturated_into()),
lock.1,
)?;
}
Ok(())
}
fn is_ratio_kept(ratio_nominator: BalanceOf<T>, ratio_denominator: BalanceOf<T>) -> bool {
let (second_token_valuation, first_token_valuation) = Valuations::<T>::get();
let left = U256::from(first_token_valuation.into()) * U256::from(ratio_denominator.into());
let right = U256::from(ratio_nominator.into()) * U256::from(second_token_valuation.into());
left <= right
}
pub fn do_provision(
sender: &T::AccountId,
token_id: CurrencyIdOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
let is_first_token = token_id == Self::first_token_id();
let is_second_token = token_id == Self::second_token_id();
let is_public_phase = Phase::<T>::get() == BootstrapPhase::Public;
let is_whitelist_phase = Phase::<T>::get() == BootstrapPhase::Whitelist;
let am_i_whitelisted = Self::is_whitelisted(sender);
ensure!(is_first_token || is_second_token, Error::<T>::UnsupportedTokenId);
ensure!(
is_public_phase || (is_whitelist_phase && (am_i_whitelisted || is_second_token)),
Error::<T>::Unauthorized
);
let schedule = BootstrapSchedule::<T>::get();
ensure!(schedule.is_some(), Error::<T>::Unauthorized);
let (_, _, _, (ratio_nominator, ratio_denominator)) = schedule.unwrap();
<T as Config>::Currency::transfer(
token_id.into(),
sender,
&Self::vault_address(),
amount,
ExistenceRequirement::KeepAlive,
)
.or(Err(Error::<T>::NotEnoughAssets))?;
ensure!(
Provisions::<T>::try_mutate(sender, token_id, |provision| {
if let Some(val) = provision.checked_add(&amount) {
*provision = val;
Ok(())
} else {
Err(())
}
})
.is_ok(),
Error::<T>::MathOverflow
);
let (pre_second_token_valuation, _) = Valuations::<T>::get();
ensure!(
token_id != Self::first_token_id() ||
pre_second_token_valuation != BalanceOf::<T>::zero(),
Error::<T>::FirstProvisionInSecondTokenId
);
ensure!(
Valuations::<T>::try_mutate(
|(second_token_valuation, first_token_valuation)| -> Result<(), ()> {
if token_id == Self::second_token_id() {
*second_token_valuation =
second_token_valuation.checked_add(&amount).ok_or(())?;
}
if token_id == Self::first_token_id() {
*first_token_valuation =
first_token_valuation.checked_add(&amount).ok_or(())?;
}
Ok(())
}
)
.is_ok(),
Error::<T>::MathOverflow
);
if token_id == Self::first_token_id() {
ensure!(
Self::is_ratio_kept(ratio_nominator, ratio_denominator),
Error::<T>::ValuationRatio
);
}
Ok(())
}
fn get_valuation(token_id: &CurrencyIdOf<T>) -> BalanceOf<T> {
if *token_id == Self::first_token_id() {
Self::valuations().1
} else if *token_id == Self::second_token_id() {
Self::valuations().0
} else {
BalanceOf::<T>::zero()
}
}
fn calculate_rewards(
who: &T::AccountId,
token_id: &CurrencyIdOf<T>,
) -> Result<(BalanceOf<T>, BalanceOf<T>, (BlockNrAsBalance<T>, BlockNrAsBalance<T>)), Error<T>>
{
let valuation = Self::get_valuation(token_id);
let provision = Self::provisions(who, token_id);
let (vested_provision, lock_start, lock_end) = Self::vested_provisions(who, token_id);
let (_, liquidity) = Self::minted_liquidity();
let rewards = multiply_by_rational_with_rounding(
liquidity.into() / 2,
provision.into(),
valuation.into(),
Rounding::Down,
)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
let vested_rewards = multiply_by_rational_with_rounding(
liquidity.into() / 2,
vested_provision.into(),
valuation.into(),
Rounding::Down,
)
.ok_or(Error::<T>::MathOverflow)?
.try_into()
.map_err(|_| Error::<T>::MathOverflow)?;
Ok((rewards, vested_rewards, (lock_start, lock_end)))
}
fn do_claim_liquidity_tokens(who: &T::AccountId, activate_rewards: bool) -> DispatchResult {
ensure!(Self::phase() == BootstrapPhase::Finished, Error::<T>::NotFinishedYet);
let (liq_token_id, _) = Self::minted_liquidity();
if !Self::archived().is_empty() {
ensure!(ProvisionAccounts::<T>::get(who).is_some(), Error::<T>::NothingToClaim);
} else {
ensure!(
!ClaimedRewards::<T>::contains_key(&who, &Self::first_token_id()),
Error::<T>::NothingToClaim
);
ensure!(
!ClaimedRewards::<T>::contains_key(&who, &Self::second_token_id()),
Error::<T>::NothingToClaim
);
}
let (first_token_rewards, first_token_rewards_vested, first_token_lock) =
Self::calculate_rewards(who, &Self::first_token_id())?;
let (second_token_rewards, second_token_rewards_vested, second_token_lock) =
Self::calculate_rewards(who, &Self::second_token_id())?;
let total_rewards_claimed = second_token_rewards
.checked_add(&second_token_rewards_vested)
.ok_or(Error::<T>::MathOverflow)?
.checked_add(&first_token_rewards)
.ok_or(Error::<T>::MathOverflow)?
.checked_add(&first_token_rewards_vested)
.ok_or(Error::<T>::MathOverflow)?;
Self::claim_liquidity_tokens_from_single_currency(
who,
&Self::second_token_id(),
second_token_rewards,
second_token_rewards_vested,
second_token_lock,
)?;
log!(
info,
"Second token rewards (non-vested, vested) = ({:?}, {:?})",
second_token_rewards,
second_token_rewards_vested,
);
Self::claim_liquidity_tokens_from_single_currency(
who,
&Self::first_token_id(),
first_token_rewards,
first_token_rewards_vested,
first_token_lock,
)?;
log!(
info,
"First token rewards (non-vested, vested) = ({:?}, {:?})",
first_token_rewards,
first_token_rewards_vested,
);
ProvisionAccounts::<T>::remove(who);
if activate_rewards && <T as Config>::RewardsApi::is_enabled(liq_token_id) {
let non_vested_rewards = second_token_rewards
.checked_add(&first_token_rewards)
.ok_or(Error::<T>::MathOverflow)?;
if non_vested_rewards > BalanceOf::<T>::zero() {
let activate_result = <T as Config>::RewardsApi::activate_liquidity(
who.clone(),
liq_token_id,
non_vested_rewards,
Some(ActivateKind::AvailableBalance),
);
if let Err(err) = activate_result {
log!(
error,
"Activating liquidity tokens failed upon bootstrap claim rewards = ({:?}, {:?}, {:?}, {:?})",
who,
liq_token_id,
non_vested_rewards,
err
);
Self::deposit_event(Event::RewardsLiquidityAcitvationFailed(
who.clone(),
liq_token_id,
non_vested_rewards,
));
};
}
}
Self::deposit_event(Event::RewardsClaimed(liq_token_id, total_rewards_claimed));
Ok(())
}
fn first_token_id() -> CurrencyIdOf<T> {
ActivePair::<T>::get().map(|(first, _)| first).unwrap_or(4_u32.into())
}
fn second_token_id() -> CurrencyIdOf<T> {
ActivePair::<T>::get().map(|(_, second)| second).unwrap_or(0_u32.into())
}
}
impl<T: Config> Contains<(CurrencyIdOf<T>, CurrencyIdOf<T>)> for Pallet<T> {
fn contains(pair: &(CurrencyIdOf<T>, CurrencyIdOf<T>)) -> bool {
pair == &(Self::first_token_id(), Self::second_token_id()) ||
pair == &(Self::second_token_id(), Self::first_token_id())
}
}