#![cfg_attr(not(feature = "std"), no_std)]
use core::convert::TryFrom;
use cfg_traits::{liquidity_pools::OutboundMessageHandler, swaps::TokenSwaps, PreConditions};
use cfg_types::{
domain_address::{Domain, DomainAddress},
tokens::GeneralCurrencyIndex,
};
use cfg_utils::vec_to_fixed_array;
use frame_support::{
traits::{
fungibles::{Inspect, Mutate},
PalletInfo,
},
transactional,
};
use orml_traits::{
asset_registry::{self, Inspect as _},
GetByKey,
};
pub use pallet::*;
use sp_core::{crypto::AccountId32, H160};
use sp_runtime::{
traits::{AtLeast32BitUnsigned, EnsureMul},
FixedPointNumber, SaturatedConversion,
};
use sp_std::{convert::TryInto, vec};
use staging_xcm::{
v4::{Junction::*, NetworkId},
VersionedLocation,
};
use crate::message::UpdateRestrictionMessage;
pub mod defensive_weights;
mod gmpf {
mod de;
mod error;
mod ser;
pub use de::from_slice;
#[cfg(test)]
pub use error::Error;
pub use ser::to_vec;
}
mod message;
pub use message::Message;
pub mod hooks;
mod inbound;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub type GeneralCurrencyIndexType = u128;
pub type GeneralCurrencyIndexOf<T> =
GeneralCurrencyIndex<GeneralCurrencyIndexType, <T as pallet::Config>::GeneralCurrencyPrefix>;
#[frame_support::pallet]
pub mod pallet {
use cfg_traits::{
investments::ForeignInvestment, liquidity_pools::InboundMessageHandler, CurrencyInspect,
Permissions, PoolInspect, Seconds, TimeAsSecs, TrancheTokenPrice,
};
use cfg_types::{
permissions::{PermissionScope, PoolRole, Role},
tokens::CustomMetadata,
EVMChainId,
};
use frame_support::{
pallet_prelude::*,
traits::tokens::{Fortitude, Precision, Preservation},
};
use frame_system::pallet_prelude::*;
use parity_scale_codec::HasCompact;
use sp_core::U256;
use sp_runtime::{traits::Zero, DispatchError};
use super::*;
use crate::defensive_weights::WeightInfo;
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config<AccountId = AccountId32> {
type WeightInfo: WeightInfo;
type Balance: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ MaxEncodedLen
+ Into<u128>
+ From<u128>;
type PoolId: Member
+ Parameter
+ Default
+ Copy
+ HasCompact
+ MaxEncodedLen
+ core::fmt::Debug
+ Into<u64>
+ From<u64>;
type TrancheId: Member
+ Parameter
+ Default
+ Copy
+ MaxEncodedLen
+ TypeInfo
+ From<[u8; 16]>
+ Into<[u8; 16]>;
type BalanceRatio: Parameter
+ Member
+ MaybeSerializeDeserialize
+ FixedPointNumber<Inner = u128>
+ TypeInfo;
type PoolInspect: PoolInspect<
Self::AccountId,
Self::CurrencyId,
PoolId = Self::PoolId,
TrancheId = Self::TrancheId,
>;
type TrancheTokenPrice: TrancheTokenPrice<
Self::AccountId,
Self::CurrencyId,
BalanceRatio = Self::BalanceRatio,
PoolId = Self::PoolId,
TrancheId = Self::TrancheId,
Moment = Seconds,
>;
type Permission: Permissions<
Self::AccountId,
Scope = PermissionScope<Self::PoolId, Self::CurrencyId>,
Role = Role<Self::TrancheId>,
Error = DispatchError,
>;
type Time: TimeAsSecs;
type Tokens: Mutate<Self::AccountId>
+ Inspect<Self::AccountId, AssetId = Self::CurrencyId, Balance = Self::Balance>;
type ForeignInvestment: ForeignInvestment<
Self::AccountId,
Amount = Self::Balance,
TrancheAmount = Self::Balance,
CurrencyId = Self::CurrencyId,
InvestmentId = (Self::PoolId, Self::TrancheId),
>;
type AssetRegistry: asset_registry::Inspect<
AssetId = Self::CurrencyId,
Balance = <Self as Config>::Balance,
CustomMetadata = CustomMetadata,
>;
type CurrencyId: Parameter
+ Member
+ Copy
+ MaybeSerializeDeserialize
+ Ord
+ TypeInfo
+ MaxEncodedLen
+ TryInto<GeneralCurrencyIndexOf<Self>, Error = DispatchError>
+ TryFrom<GeneralCurrencyIndexOf<Self>, Error = DispatchError>
+ CurrencyInspect<CurrencyId = Self::CurrencyId>
+ From<(Self::PoolId, Self::TrancheId)>;
type OutboundMessageHandler: OutboundMessageHandler<
Sender = Self::AccountId,
Message = Message,
Destination = Domain,
> + GetByKey<Domain, Option<[u8; 20]>>;
#[pallet::constant]
type GeneralCurrencyPrefix: Get<[u8; 12]>;
#[pallet::constant]
type TreasuryAccount: Get<Self::AccountId>;
type PreTransferFilter: PreConditions<
(Self::AccountId, DomainAddress, Self::CurrencyId),
Result = DispatchResult,
>;
type MarketRatio: TokenSwaps<
Self::AccountId,
CurrencyId = Self::CurrencyId,
Ratio = Self::BalanceRatio,
>;
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
#[allow(clippy::large_enum_variant)]
pub enum Event<T: Config> {
IncomingMessage {
sender: DomainAddress,
message: Message,
},
}
#[pallet::error]
pub enum Error<T> {
AssetNotFound,
AssetMetadataNotPoolCurrency,
AssetNotLiquidityPoolsTransferable,
AssetNotLiquidityPoolsWrappedToken,
PoolNotFound,
TrancheNotFound,
TrancheMetadataNotFound,
MissingTranchePrice,
InvalidTransferAmount,
BalanceTooLow,
UnauthorizedTransfer,
InvalidIncomingMessage,
InvalidDomain,
InvalidTrancheInvestorValidity,
InvalidTransferCurrency,
InvestorDomainAddressNotAMember,
InvestorDomainAddressFrozen,
InvestorDomainAddressNotFrozen,
NotPoolAdmin,
DomainHookAddressNotFound,
UnsupportedBatchMessage,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(T::WeightInfo::add_pool())]
#[pallet::call_index(2)]
pub fn add_pool(
origin: OriginFor<T>,
pool_id: T::PoolId,
domain: Domain,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
ensure!(
T::PoolInspect::pool_exists(pool_id),
Error::<T>::PoolNotFound
);
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who.clone(),
Role::PoolRole(PoolRole::PoolAdmin)
),
Error::<T>::NotPoolAdmin
);
T::OutboundMessageHandler::handle(
who,
domain,
Message::AddPool {
pool_id: pool_id.into(),
},
)?;
Ok(())
}
#[pallet::weight(T::WeightInfo::add_tranche())]
#[pallet::call_index(3)]
pub fn add_tranche(
origin: OriginFor<T>,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
domain: Domain,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who.clone(),
Role::PoolRole(PoolRole::PoolAdmin)
),
Error::<T>::NotPoolAdmin
);
let investment_id = Self::derive_invest_id(pool_id, tranche_id)?;
let metadata = T::AssetRegistry::metadata(&investment_id.into())
.ok_or(Error::<T>::TrancheMetadataNotFound)?;
let token_name = vec_to_fixed_array(metadata.name);
let token_symbol = vec_to_fixed_array(metadata.symbol);
let hook_bytes = T::OutboundMessageHandler::get(&domain)
.ok_or(Error::<T>::DomainHookAddressNotFound)?;
let evm_chain_id = match domain {
Domain::Evm(id) => Ok(id),
_ => Err(Error::<T>::InvalidDomain),
}?;
T::OutboundMessageHandler::handle(
who,
domain,
Message::AddTranche {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
decimals: metadata.decimals.saturated_into(),
token_name,
token_symbol,
hook: DomainAddress::Evm(evm_chain_id, hook_bytes.into()).bytes(),
},
)?;
Ok(())
}
#[pallet::weight(T::WeightInfo::update_token_price())]
#[pallet::call_index(4)]
pub fn update_token_price(
origin: OriginFor<T>,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
currency_id: T::CurrencyId,
destination: Domain,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
let (price, computed_at) = T::TrancheTokenPrice::get_price(pool_id, tranche_id)
.ok_or(Error::<T>::MissingTranchePrice)?;
let foreign_price = T::MarketRatio::market_ratio(
currency_id,
T::PoolInspect::currency_for(pool_id).ok_or(Error::<T>::PoolNotFound)?,
)?
.ensure_mul(price)?;
let (chain_id, ..) = Self::try_get_wrapped_token(¤cy_id)?;
ensure!(
Domain::Evm(chain_id) == destination,
Error::<T>::InvalidDomain
);
let currency = Self::try_get_general_index(currency_id)?;
T::OutboundMessageHandler::handle(
who,
destination,
Message::UpdateTranchePrice {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
currency,
price: foreign_price.into_inner(),
computed_at,
},
)?;
Ok(())
}
#[pallet::weight(T::WeightInfo::update_member())]
#[pallet::call_index(5)]
pub fn update_member(
origin: OriginFor<T>,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
domain_address: DomainAddress,
valid_until: Seconds,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
ensure!(
T::PoolInspect::pool_exists(pool_id),
Error::<T>::PoolNotFound
);
ensure!(
T::PoolInspect::tranche_exists(pool_id, tranche_id),
Error::<T>::TrancheNotFound
);
ensure!(
valid_until > T::Time::now(),
Error::<T>::InvalidTrancheInvestorValidity
);
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
domain_address.account(),
Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, valid_until))
),
Error::<T>::InvestorDomainAddressNotAMember
);
T::OutboundMessageHandler::handle(
who,
domain_address.domain(),
Message::UpdateRestriction {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
update: UpdateRestrictionMessage::UpdateMember {
member: domain_address.bytes(),
valid_until,
},
},
)?;
Ok(())
}
#[pallet::weight(T::WeightInfo::transfer())]
#[pallet::call_index(6)]
pub fn transfer_tranche_tokens(
origin: OriginFor<T>,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
domain_address: DomainAddress,
amount: T::Balance,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
ensure!(!amount.is_zero(), Error::<T>::InvalidTransferAmount);
Self::validate_investor_can_transfer(domain_address.account(), pool_id, tranche_id)?;
let invest_id = Self::derive_invest_id(pool_id, tranche_id)?;
T::PreTransferFilter::check((who.clone(), domain_address.clone(), invest_id.into()))?;
T::Tokens::transfer(
invest_id.into(),
&who,
&domain_address.domain().into_account(),
amount,
Preservation::Expendable,
)?;
T::OutboundMessageHandler::handle(
who.clone(),
domain_address.domain(),
Message::TransferTrancheTokens {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
amount: amount.into(),
domain: domain_address.domain().into(),
receiver: domain_address.bytes(),
},
)?;
Ok(())
}
#[pallet::weight(T::WeightInfo::transfer())]
#[pallet::call_index(7)]
pub fn transfer(
origin: OriginFor<T>,
currency_id: T::CurrencyId,
receiver: DomainAddress,
amount: T::Balance,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
ensure!(!amount.is_zero(), Error::<T>::InvalidTransferAmount);
ensure!(
!T::CurrencyId::is_tranche_token(currency_id),
Error::<T>::InvalidTransferCurrency
);
let currency = Self::try_get_general_index(currency_id)?;
let (chain_id, ..) = Self::try_get_wrapped_token(¤cy_id)?;
ensure!(
Domain::Evm(chain_id) == receiver.domain(),
Error::<T>::InvalidDomain
);
T::PreTransferFilter::check((who.clone(), receiver.clone(), currency_id))?;
ensure!(
T::Tokens::reducible_balance(
currency_id,
&who,
Preservation::Expendable,
Fortitude::Polite
) >= amount,
Error::<T>::BalanceTooLow
);
T::Tokens::burn_from(
currency_id,
&who,
amount,
Precision::Exact,
Fortitude::Polite,
)?;
T::OutboundMessageHandler::handle(
who.clone(),
receiver.domain(),
Message::TransferAssets {
amount: amount.into(),
currency,
receiver: receiver.bytes(),
},
)?;
Ok(())
}
#[pallet::weight(T::WeightInfo::add_currency())]
#[pallet::call_index(8)]
pub fn add_currency(origin: OriginFor<T>, currency_id: T::CurrencyId) -> DispatchResult {
let who = ensure_signed(origin)?;
let currency = Self::try_get_general_index(currency_id)?;
let (chain_id, evm_address) = Self::try_get_wrapped_token(¤cy_id)?;
T::OutboundMessageHandler::handle(
who,
Domain::Evm(chain_id),
Message::AddAsset {
currency,
evm_address: evm_address.0,
},
)?;
Ok(())
}
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::allow_investment_currency())]
pub fn allow_investment_currency(
origin: OriginFor<T>,
pool_id: T::PoolId,
currency_id: T::CurrencyId,
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who.clone(),
Role::PoolRole(PoolRole::PoolAdmin)
),
Error::<T>::NotPoolAdmin
);
let (currency, chain_id) = Self::validate_investment_currency(currency_id)?;
T::OutboundMessageHandler::handle(
who,
Domain::Evm(chain_id),
Message::AllowAsset {
pool_id: pool_id.into(),
currency,
},
)?;
Ok(())
}
#[pallet::weight(T::WeightInfo::schedule_upgrade())]
#[pallet::call_index(10)]
pub fn schedule_upgrade(
origin: OriginFor<T>,
evm_chain_id: EVMChainId,
contract: [u8; 20],
) -> DispatchResult {
ensure_root(origin)?;
T::OutboundMessageHandler::handle(
T::TreasuryAccount::get(),
Domain::Evm(evm_chain_id),
Message::ScheduleUpgrade { contract },
)
}
#[pallet::weight(T::WeightInfo::cancel_upgrade())]
#[pallet::call_index(11)]
pub fn cancel_upgrade(
origin: OriginFor<T>,
evm_chain_id: EVMChainId,
contract: [u8; 20],
) -> DispatchResult {
ensure_root(origin)?;
T::OutboundMessageHandler::handle(
T::TreasuryAccount::get(),
Domain::Evm(evm_chain_id),
Message::CancelUpgrade { contract },
)
}
#[pallet::weight(T::WeightInfo::update_tranche_token_metadata())]
#[pallet::call_index(12)]
pub fn update_tranche_token_metadata(
origin: OriginFor<T>,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
domain: Domain,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
let investment_id = Self::derive_invest_id(pool_id, tranche_id)?;
let metadata = T::AssetRegistry::metadata(&investment_id.into())
.ok_or(Error::<T>::TrancheMetadataNotFound)?;
let token_name = vec_to_fixed_array(metadata.name);
let token_symbol = vec_to_fixed_array(metadata.symbol);
T::OutboundMessageHandler::handle(
who,
domain,
Message::UpdateTrancheMetadata {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
token_name,
token_symbol,
},
)
}
#[pallet::call_index(13)]
#[pallet::weight(T::WeightInfo::disallow_investment_currency())]
pub fn disallow_investment_currency(
origin: OriginFor<T>,
pool_id: T::PoolId,
currency_id: T::CurrencyId,
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who.clone(),
Role::PoolRole(PoolRole::PoolAdmin)
),
Error::<T>::NotPoolAdmin
);
let (currency, chain_id) = Self::validate_investment_currency(currency_id)?;
T::OutboundMessageHandler::handle(
who,
Domain::Evm(chain_id),
Message::DisallowAsset {
pool_id: pool_id.into(),
currency,
},
)?;
Ok(())
}
#[pallet::call_index(14)]
#[pallet::weight(T::WeightInfo::freeze_investor())]
pub fn freeze_investor(
origin: OriginFor<T>,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
domain_address: DomainAddress,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
ensure!(
T::PoolInspect::pool_exists(pool_id),
Error::<T>::PoolNotFound
);
ensure!(
T::PoolInspect::tranche_exists(pool_id, tranche_id),
Error::<T>::TrancheNotFound
);
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who.clone(),
Role::PoolRole(PoolRole::PoolAdmin)
),
Error::<T>::NotPoolAdmin
);
Self::validate_investor_status(
domain_address.account(),
pool_id,
tranche_id,
T::Time::now(),
true,
)?;
T::OutboundMessageHandler::handle(
who,
domain_address.domain(),
Message::UpdateRestriction {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
update: UpdateRestrictionMessage::Freeze {
address: domain_address.bytes(),
},
},
)?;
Ok(())
}
#[pallet::call_index(15)]
#[pallet::weight(T::WeightInfo::unfreeze_investor())]
pub fn unfreeze_investor(
origin: OriginFor<T>,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
domain_address: DomainAddress,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
ensure!(
T::PoolInspect::pool_exists(pool_id),
Error::<T>::PoolNotFound
);
ensure!(
T::PoolInspect::tranche_exists(pool_id, tranche_id),
Error::<T>::TrancheNotFound
);
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who.clone(),
Role::PoolRole(PoolRole::PoolAdmin)
),
Error::<T>::NotPoolAdmin
);
Self::validate_investor_status(
domain_address.account(),
pool_id,
tranche_id,
T::Time::now(),
false,
)?;
T::OutboundMessageHandler::handle(
who,
domain_address.domain(),
Message::UpdateRestriction {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
update: UpdateRestrictionMessage::Unfreeze {
address: domain_address.bytes(),
},
},
)?;
Ok(())
}
#[pallet::call_index(16)]
#[pallet::weight(T::WeightInfo::update_tranche_hook())]
pub fn update_tranche_hook(
origin: OriginFor<T>,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
domain: Domain,
hook: [u8; 20],
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
ensure!(
T::PoolInspect::pool_exists(pool_id),
Error::<T>::PoolNotFound
);
ensure!(
T::PoolInspect::tranche_exists(pool_id, tranche_id),
Error::<T>::TrancheNotFound
);
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who.clone(),
Role::PoolRole(PoolRole::PoolAdmin)
),
Error::<T>::NotPoolAdmin
);
let evm_chain_id = match domain {
Domain::Evm(id) => Ok(id),
_ => Err(Error::<T>::InvalidDomain),
}?;
T::OutboundMessageHandler::handle(
who,
domain,
Message::UpdateTrancheHook {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
hook: DomainAddress::Evm(evm_chain_id, hook.into()).bytes(),
},
)?;
Ok(())
}
#[pallet::call_index(17)]
#[pallet::weight(T::WeightInfo::update_tranche_hook())]
pub fn recover_assets(
origin: OriginFor<T>,
domain_address: DomainAddress,
incorrect_contract: [u8; 32],
asset: [u8; 32],
amount: U256,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(
matches!(domain_address.domain(), Domain::Evm(_)),
Error::<T>::InvalidDomain
);
T::OutboundMessageHandler::handle(
T::TreasuryAccount::get(),
domain_address.domain(),
Message::RecoverAssets {
contract: incorrect_contract,
asset,
recipient: domain_address.bytes(),
amount: amount.into(),
},
)?;
Ok(())
}
}
impl<T: Config> Pallet<T> {
pub fn try_get_general_index(currency: T::CurrencyId) -> Result<u128, DispatchError> {
ensure!(
T::AssetRegistry::metadata(¤cy).is_some(),
Error::<T>::AssetNotFound
);
let general_index: GeneralCurrencyIndexOf<T> = T::CurrencyId::try_into(currency)?;
Ok(general_index.index)
}
pub fn try_get_currency_id(
index: GeneralCurrencyIndexOf<T>,
) -> Result<T::CurrencyId, DispatchError> {
let currency = T::CurrencyId::try_from(index)?;
ensure!(
T::AssetRegistry::metadata(¤cy).is_some(),
Error::<T>::AssetNotFound
);
Ok(currency)
}
pub fn try_get_wrapped_token(
currency_id: &T::CurrencyId,
) -> Result<(EVMChainId, H160), DispatchError> {
let meta = T::AssetRegistry::metadata(currency_id).ok_or(Error::<T>::AssetNotFound)?;
ensure!(
meta.additional.transferability.includes_liquidity_pools(),
Error::<T>::AssetNotLiquidityPoolsTransferable
);
let location = match meta.location {
Some(VersionedLocation::V3(location)) => location.try_into().map_err(|_| {
DispatchError::Other("v3 is isometric to v4 and should not fail")
})?,
Some(VersionedLocation::V4(location)) => location,
_ => Err(Error::<T>::AssetNotLiquidityPoolsWrappedToken)?,
};
let pallet_index = <T as frame_system::Config>::PalletInfo::index::<Pallet<T>>();
match location.unpack() {
(
0,
&[PalletInstance(pallet_instance), GlobalConsensus(NetworkId::Ethereum { chain_id }), AccountKey20 {
network: None,
key: address,
}],
) if Some(pallet_instance.into()) == pallet_index => Ok((chain_id, address.into())),
_ => Err(Error::<T>::AssetNotLiquidityPoolsWrappedToken.into()),
}
}
pub fn derive_invest_id(
pool_id: T::PoolId,
tranche_id: T::TrancheId,
) -> Result<(T::PoolId, T::TrancheId), DispatchError> {
ensure!(
T::PoolInspect::pool_exists(pool_id),
Error::<T>::PoolNotFound
);
ensure!(
T::PoolInspect::tranche_exists(pool_id, tranche_id),
Error::<T>::TrancheNotFound
);
Ok((pool_id, tranche_id))
}
pub fn validate_investment_currency(
currency_id: T::CurrencyId,
) -> Result<(u128, EVMChainId), DispatchError> {
let currency = Self::try_get_general_index(currency_id)?;
let (chain_id, ..) = Self::try_get_wrapped_token(¤cy_id)?;
let metadata =
T::AssetRegistry::metadata(¤cy_id).ok_or(Error::<T>::AssetNotFound)?;
ensure!(
metadata.additional.pool_currency,
Error::<T>::AssetMetadataNotPoolCurrency
);
Ok((currency, chain_id))
}
pub fn validate_investor_status(
investor: T::AccountId,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
valid_until: Seconds,
is_frozen: bool,
) -> DispatchResult {
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
investor.clone(),
Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, valid_until))
),
Error::<T>::InvestorDomainAddressNotAMember
);
ensure!(
is_frozen
== T::Permission::has(
PermissionScope::Pool(pool_id),
investor,
Role::PoolRole(PoolRole::FrozenTrancheInvestor(tranche_id))
),
Error::<T>::InvestorDomainAddressFrozen
);
Ok(())
}
pub fn validate_investor_can_transfer(
investor: T::AccountId,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
) -> DispatchResult {
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
investor.clone(),
Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, T::Time::now()))
),
Error::<T>::UnauthorizedTransfer
);
ensure!(
!T::Permission::has(
PermissionScope::Pool(pool_id),
investor,
Role::PoolRole(PoolRole::FrozenTrancheInvestor(tranche_id))
),
Error::<T>::InvestorDomainAddressFrozen
);
Ok(())
}
}
impl<T: Config> InboundMessageHandler for Pallet<T> {
type Message = Message;
type Sender = DomainAddress;
#[transactional]
fn handle(sender: DomainAddress, msg: Message) -> DispatchResult {
Self::deposit_event(Event::<T>::IncomingMessage {
sender: sender.clone(),
message: msg.clone(),
});
match msg {
Message::TransferAssets {
currency,
receiver,
amount,
..
} => Self::handle_transfer(currency.into(), receiver.into(), amount.into()),
Message::TransferTrancheTokens {
pool_id,
tranche_id,
domain,
receiver,
amount,
..
} => Self::handle_tranche_tokens_transfer(
pool_id.into(),
tranche_id.into(),
sender.domain(),
DomainAddress::new(domain.try_into()?, receiver),
amount.into(),
),
Message::DepositRequest {
pool_id,
tranche_id,
investor,
currency,
amount,
} => Self::handle_deposit_request(
pool_id.into(),
tranche_id.into(),
DomainAddress::new(sender.domain(), investor).account(),
currency.into(),
amount.into(),
),
Message::RedeemRequest {
pool_id,
tranche_id,
investor,
amount,
currency,
} => Self::handle_redeem_request(
pool_id.into(),
tranche_id.into(),
DomainAddress::new(sender.domain(), investor).account(),
amount.into(),
currency.into(),
sender,
),
Message::CancelDepositRequest {
pool_id,
tranche_id,
investor,
currency,
} => Self::handle_cancel_deposit_request(
pool_id.into(),
tranche_id.into(),
DomainAddress::new(sender.domain(), investor).account(),
currency.into(),
),
Message::CancelRedeemRequest {
pool_id,
tranche_id,
investor,
currency,
} => Self::handle_cancel_redeem_request(
pool_id.into(),
tranche_id.into(),
DomainAddress::new(sender.domain(), investor).account(),
currency.into(),
sender,
),
Message::Batch(_) => Err(Error::<T>::UnsupportedBatchMessage.into()),
_ => Err(Error::<T>::InvalidIncomingMessage.into()),
}?;
Ok(())
}
}
}