use core::marker::PhantomData;
use cfg_primitives::{
types::{PoolId, TrancheId},
Balance,
};
use cfg_traits::HasLocalAssetRepresentation;
use orml_traits::asset_registry;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_runtime::{traits::Get, DispatchError, TokenError};
use crate::{xcm::XcmMetadata, EVMChainId};
pub const MAX_ASSET_STRING_LIMIT: u32 = 128;
frame_support::parameter_types! {
#[derive(TypeInfo, Eq, PartialEq, Debug, Clone, Copy )]
pub const AssetStringLimit: u32 = MAX_ASSET_STRING_LIMIT;
}
pub type AssetMetadata = asset_registry::AssetMetadata<Balance, CustomMetadata, AssetStringLimit>;
#[derive(
Clone,
Copy,
Default,
PartialOrd,
Ord,
PartialEq,
Eq,
Debug,
Encode,
Decode,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub enum CurrencyId {
#[default]
#[codec(index = 0)]
Native,
#[codec(index = 1)]
Tranche(PoolId, TrancheId),
#[codec(index = 3)]
AUSD,
#[codec(index = 4)]
ForeignAsset(ForeignAssetId),
#[codec(index = 5)]
Staking(StakingCurrency),
#[codec(index = 6)]
LocalAsset(LocalAssetId),
}
#[derive(
Clone,
Copy,
PartialOrd,
Ord,
PartialEq,
Eq,
Debug,
Encode,
Decode,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub struct LocalAssetId(pub u32);
impl From<LocalAssetId> for CurrencyId {
fn from(value: LocalAssetId) -> Self {
Self::LocalAsset(value)
}
}
impl TryFrom<CurrencyId> for LocalAssetId {
type Error = ();
fn try_from(value: CurrencyId) -> Result<Self, Self::Error> {
if let CurrencyId::LocalAsset(local) = value {
Ok(local)
} else {
Err(())
}
}
}
impl From<(PoolId, TrancheId)> for CurrencyId {
fn from((pool_id, tranche_id): (PoolId, TrancheId)) -> Self {
CurrencyId::Tranche(pool_id, tranche_id)
}
}
#[derive(
Clone,
Copy,
PartialOrd,
Ord,
PartialEq,
Eq,
Debug,
Encode,
Decode,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub enum StakingCurrency {
BlockRewards,
}
pub type ForeignAssetId = u32;
impl From<u32> for CurrencyId {
fn from(value: u32) -> Self {
CurrencyId::ForeignAsset(value)
}
}
impl From<StakingCurrency> for CurrencyId {
fn from(inner: StakingCurrency) -> Self {
CurrencyId::Staking(inner)
}
}
impl cfg_traits::CurrencyInspect for CurrencyId {
type CurrencyId = CurrencyId;
fn is_tranche_token(currency: Self::CurrencyId) -> bool {
matches!(currency, CurrencyId::Tranche(_, _))
}
}
pub struct GeneralCurrencyIndex<Index, Prefix> {
pub index: Index,
_phantom: PhantomData<Prefix>,
}
impl<Index, Prefix> TryFrom<CurrencyId> for GeneralCurrencyIndex<Index, Prefix>
where
Index: From<u128>,
Prefix: Get<[u8; 12]>,
{
type Error = DispatchError;
fn try_from(value: CurrencyId) -> Result<GeneralCurrencyIndex<Index, Prefix>, Self::Error> {
let mut bytes = [0u8; 16];
bytes[..12].copy_from_slice(&Prefix::get());
let currency_bytes: [u8; 4] = match &value {
CurrencyId::ForeignAsset(id32) => Ok(id32.to_be_bytes()),
_ => Err(DispatchError::Token(TokenError::Unsupported)),
}?;
bytes[12..].copy_from_slice(¤cy_bytes[..]);
Ok(GeneralCurrencyIndex {
index: u128::from_be_bytes(bytes).into(),
_phantom: Default::default(),
})
}
}
impl<Index, Prefix> TryFrom<GeneralCurrencyIndex<Index, Prefix>> for CurrencyId
where
Index: Into<u128>,
Prefix: Get<[u8; 12]>,
{
type Error = DispatchError;
fn try_from(value: GeneralCurrencyIndex<Index, Prefix>) -> Result<Self, Self::Error> {
let bytes: [u8; 16] = value.index.into().to_be_bytes();
let currency_bytes: [u8; 4] = bytes[12..]
.try_into()
.map_err(|_| DispatchError::Corruption)?;
Ok(CurrencyId::ForeignAsset(u32::from_be_bytes(currency_bytes)))
}
}
impl<Index, Prefix> From<u128> for GeneralCurrencyIndex<Index, Prefix>
where
Index: From<u128>,
Prefix: Get<[u8; 12]>,
{
fn from(value: u128) -> Self {
GeneralCurrencyIndex {
index: value.into(),
_phantom: Default::default(),
}
}
}
#[derive(
Serialize,
Deserialize,
Clone,
Copy,
Default,
PartialOrd,
Ord,
PartialEq,
Eq,
Debug,
Encode,
Decode,
TypeInfo,
MaxEncodedLen,
)]
pub struct CustomMetadata {
pub transferability: CrossChainTransferability,
pub mintable: bool,
pub permissioned: bool,
pub pool_currency: bool,
pub local_representation: Option<LocalAssetId>,
}
#[cfg(feature = "std")]
pub fn default_metadata() -> AssetMetadata {
AssetMetadata {
decimals: 0,
name: Default::default(),
symbol: Default::default(),
existential_deposit: 0,
location: None,
additional: Default::default(),
}
}
#[derive(
Clone,
Copy,
Default,
PartialOrd,
Ord,
PartialEq,
Eq,
Debug,
Encode,
Decode,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub enum CrossChainTransferability {
#[default]
None,
Xcm(XcmMetadata),
LiquidityPools,
}
impl CrossChainTransferability {
pub fn includes_xcm(self) -> bool {
matches!(self, Self::Xcm(..))
}
pub fn includes_liquidity_pools(self) -> bool {
matches!(self, Self::LiquidityPools)
}
#[cfg(feature = "std")]
pub fn xcm_default() -> Self {
Self::Xcm(XcmMetadata {
fee_per_second: None,
})
}
#[cfg(feature = "std")]
pub fn xcm_with_fees(value: Balance) -> Self {
Self::Xcm(XcmMetadata {
fee_per_second: Some(value),
})
}
}
#[derive(
Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen,
)]
pub enum FilterCurrency {
All,
Specific(CurrencyId),
}
impl From<CurrencyId> for FilterCurrency {
fn from(value: CurrencyId) -> Self {
Self::Specific(value)
}
}
impl<AssetInspect> HasLocalAssetRepresentation<AssetInspect> for CurrencyId
where
AssetInspect: orml_traits::asset_registry::Inspect<
AssetId = CurrencyId,
Balance = Balance,
CustomMetadata = CustomMetadata,
>,
{
fn is_local_representation_of(&self, variant_currency: &Self) -> Result<bool, DispatchError> {
let meta_local = AssetInspect::metadata(self).ok_or(DispatchError::CannotLookup)?;
let meta_variant =
AssetInspect::metadata(variant_currency).ok_or(DispatchError::CannotLookup)?;
if let Some(local) = meta_variant.additional.local_representation {
frame_support::ensure!(
meta_local.decimals == meta_variant.decimals,
DispatchError::Other("Mismatching decimals")
);
Ok(self == &local.into())
} else {
Ok(false)
}
}
}
pub mod before {
use cfg_primitives::{PoolId, TrancheId};
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use crate::tokens::{ForeignAssetId, StakingCurrency};
#[derive(
Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen,
)]
pub enum CurrencyId {
#[codec(index = 0)]
Native,
#[codec(index = 1)]
Tranche(PoolId, TrancheId),
#[codec(index = 2)]
KSM,
#[codec(index = 3)]
AUSD,
#[codec(index = 4)]
ForeignAsset(ForeignAssetId),
#[codec(index = 5)]
Staking(StakingCurrency),
}
}
pub mod usdc {
use super::*;
pub const MIN_SWAP_ORDER_AMOUNT: Balance = 10_000_000;
pub const DECIMALS: u32 = 6;
pub const EXISTENTIAL_DEPOSIT: Balance = 1000;
pub const CURRENCY_ID_AXELAR: CurrencyId = CurrencyId::ForeignAsset(2);
pub const CURRENCY_ID_DOT_NATIVE: CurrencyId = CurrencyId::ForeignAsset(6);
pub const CURRENCY_ID_LP_ETH: CurrencyId = CurrencyId::ForeignAsset(100_001);
pub const CURRENCY_ID_LP_ETH_GOERLI: CurrencyId = CurrencyId::ForeignAsset(100_001);
pub const CURRENCY_ID_LP_BASE: CurrencyId = CurrencyId::ForeignAsset(100_002);
pub const CURRENCY_ID_LP_ARB: CurrencyId = CurrencyId::ForeignAsset(100_003);
pub const CURRENCY_ID_LP_CELO_WORMHOLE: CurrencyId = CurrencyId::ForeignAsset(100_004);
pub const CURRENCY_ID_LP_CELO: CurrencyId = CurrencyId::ForeignAsset(100_006);
pub const LOCAL_ASSET_ID: LocalAssetId = LocalAssetId(1u32);
pub const CURRENCY_ID_LOCAL: CurrencyId = CurrencyId::LocalAsset(LOCAL_ASSET_ID);
pub const CHAIN_ID_ETHEREUM_MAINNET: EVMChainId = 1;
pub const CHAIN_ID_ETH_GOERLI_TESTNET: EVMChainId = 5;
pub const CHAIN_ID_BASE_MAINNET: EVMChainId = 8453;
pub const CHAIN_ID_ARBITRUM_MAINNET: EVMChainId = 42_161;
pub const CHAIN_ID_CELO_MAINNET: EVMChainId = 42_220;
pub const CONTRACT_ETHEREUM: [u8; 20] =
hex_literal::hex!("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48");
pub const CONTRACT_ETH_GOERLI: [u8; 20] =
hex_literal::hex!("07865c6e87b9f70255377e024ace6630c1eaa37f");
pub const CONTRACT_BASE: [u8; 20] =
hex_literal::hex!("833589fCD6eDb6E08f4c7C32D4f71b54bdA02913");
pub const CONTRACT_ARBITRUM: [u8; 20] =
hex_literal::hex!("af88d065e77c8cC2239327C5EDb3A432268e5831");
pub const CONTRACT_CELO: [u8; 20] =
hex_literal::hex!("37f750B7cC259A2f741AF45294f6a16572CF5cAd");
}
#[cfg(test)]
mod tests {
use frame_support::parameter_types;
use super::*;
use crate::tokens::CurrencyId::{ForeignAsset, LocalAsset, Native, Staking, Tranche, AUSD};
const FOREIGN: CurrencyId = ForeignAsset(1u32);
parameter_types! {
pub const ZeroPrefix: [u8; 12] = [0u8; 12];
pub const NonZeroPrefix: [u8; 12] = *b"TestPrefix12";
}
#[test]
fn zero_prefix_general_index_conversion() {
let general_index: GeneralCurrencyIndex<u128, ZeroPrefix> = FOREIGN.try_into().unwrap();
assert_eq!(general_index.index, 1u128);
let reconvert = CurrencyId::try_from(general_index).unwrap();
assert_eq!(reconvert, CurrencyId::ForeignAsset(1u32));
}
#[test]
fn non_zero_prefix_general_index_conversion() {
let general_index: GeneralCurrencyIndex<u128, NonZeroPrefix> = FOREIGN.try_into().unwrap();
assert_eq!(
general_index.index,
112181915321113319688489505016241979393u128
);
let reconvert = CurrencyId::try_from(general_index).unwrap();
assert_eq!(reconvert, CurrencyId::ForeignAsset(1u32));
}
#[test]
fn non_foreign_asset_general_index_conversion() {
assert!(
TryInto::<GeneralCurrencyIndex<u128, ZeroPrefix>>::try_into(CurrencyId::Native)
.is_err()
);
assert!(
TryInto::<GeneralCurrencyIndex<u128, ZeroPrefix>>::try_into(CurrencyId::Tranche(
2, [1u8; 16]
))
.is_err()
);
assert!(
TryInto::<GeneralCurrencyIndex<u128, ZeroPrefix>>::try_into(CurrencyId::Staking(
StakingCurrency::BlockRewards
))
.is_err()
);
}
#[cfg(test)]
mod tests {
use cfg_primitives::TrancheId;
use hex::FromHex;
use parity_scale_codec::Encode;
use super::StakingCurrency;
use crate::{tokens as after, tokens::before};
#[test]
fn currency_id_refactor_encode_equality() {
assert_eq!(
before::CurrencyId::Native.encode(),
after::CurrencyId::Native.encode()
);
assert_eq!(after::CurrencyId::Native.encode(), vec![0]);
assert_eq!(
before::CurrencyId::Tranche(33, default_tranche_id()).encode(),
after::CurrencyId::Tranche(33, default_tranche_id()).encode()
);
assert_eq!(
after::CurrencyId::Tranche(33, default_tranche_id()).encode(),
vec![
1, 33, 0, 0, 0, 0, 0, 0, 0, 129, 26, 205, 91, 63, 23, 192, 104, 65, 199, 228,
30, 158, 4, 203, 27
]
);
assert_eq!(before::CurrencyId::KSM.encode(), vec![2]);
assert_eq!(before::CurrencyId::AUSD.encode(), vec![3]);
assert_eq!(
before::CurrencyId::ForeignAsset(91).encode(),
after::CurrencyId::ForeignAsset(91).encode()
);
assert_eq!(
after::CurrencyId::ForeignAsset(91).encode(),
vec![4, 91, 0, 0, 0]
);
assert_eq!(
before::CurrencyId::Staking(StakingCurrency::BlockRewards).encode(),
after::CurrencyId::Staking(StakingCurrency::BlockRewards).encode()
);
assert_eq!(
after::CurrencyId::Staking(StakingCurrency::BlockRewards).encode(),
vec![5, 0]
);
}
fn default_tranche_id() -> TrancheId {
<[u8; 16]>::from_hex("811acd5b3f17c06841c7e41e9e04cb1b")
.expect("Should be valid tranche id")
}
}
#[test]
fn currency_id_encode_sanity() {
vec![
Native,
Tranche(42, [42; 16]),
AUSD,
ForeignAsset(89),
Staking(StakingCurrency::BlockRewards),
LocalAsset(LocalAssetId(103)),
]
.into_iter()
.for_each(|x| assert_eq!(x.encode(), expected_encoded_value(x)));
fn expected_encoded_value(id: CurrencyId) -> Vec<u8> {
match id {
Native => vec![0],
Tranche(pool_id, tranche_id) => {
let mut r: Vec<u8> = vec![1];
r.append(&mut pool_id.encode());
r.append(&mut tranche_id.to_vec());
r
}
AUSD => vec![3],
ForeignAsset(id) => {
let mut r: Vec<u8> = vec![4];
r.append(&mut id.encode());
r
}
Staking(StakingCurrency::BlockRewards) => vec![5, 0],
LocalAsset(LocalAssetId(id)) => {
let mut r: Vec<u8> = vec![6];
r.append(&mut id.encode());
r
}
}
}
}
}