Skip to content

Commit

Permalink
feat: Connectors v2 message PoC (#1292)
Browse files Browse the repository at this point in the history
* feat: update Message enum

* feat: add (dummy) Into<u128> for CurrencyId

* tests: apply changes to existing

* refactor: add consts in tests

* tests: apply new messages

* refactor: apply suggestions from soft review

* fix: clippy

* tests: fix integration

* refactor: add LocalCurrencyId storage

* feat: add general index mapping for CurrencyId

* tests: fix integration for connectors

* fix: clippy warning

* refactor: apply suggestions from review

* refactor: remove unused code

* refactor: apply latest spec changes

* refactor: remove CollectFor* msgs

* tests: cover uts for all message cases

* refactor: use TryInto instead of custom GeneralCurrencyIndex trait

* refactor: update message fields

* feat: complete GeneralCurrencyIndex

* style: fix indentation

* Apply suggestion from code review by @NunoAlexandre

* chore: add message docs, fix order

* Apply suggestion from review by @offerijns

* Update pallets/connectors/src/message.rs

Co-authored-by: Jeroen Offerijns <Offerijns@users.noreply.github.com>

* docs: improve AssetNotFound

* docs: apply normalized directionality to Message

* refactor: hashed GeneralCurrencyPrefix

* fix: block local domain transfers

* refactor: rm unneeded trait bounding

* fmt: apply comment wrapping

* fix: remove new extrinsics

---------

Co-authored-by: Jeroen Offerijns <Offerijns@users.noreply.github.com>
  • Loading branch information
wischli and Jeroen Offerijns committed May 4, 2023
1 parent 189b221 commit bd4a96b
Show file tree
Hide file tree
Showing 6 changed files with 748 additions and 74 deletions.
21 changes: 21 additions & 0 deletions libs/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ pub mod constants {
pub const fn deposit(items: u32, bytes: u32) -> Balance {
items as Balance * 15 * CENTI_CFG + (bytes as Balance) * 6 * CENTI_CFG
}

/// Unhashed 36-bytes prefix for currencies managed by Connectors.
pub const GENERAL_CURRENCY_INDEX_PREFIX: [u8; 36] = *b"CentrifugeGeneralCurrencyIndexPrefix";
}

/// Listing of parachains we integrate with.
Expand Down Expand Up @@ -304,3 +307,21 @@ pub mod parachains {
}
}
}

pub mod connectors {
/// The hashed prefix for currencies managed by Connectors.
pub struct GeneralCurrencyPrefix;

impl sp_core::Get<[u8; 12]> for GeneralCurrencyPrefix {
fn get() -> [u8; 12] {
let hash: [u8; 16] = frame_support::sp_io::hashing::blake2_128(
&crate::constants::GENERAL_CURRENCY_INDEX_PREFIX,
);
let (trimmed, _) = hash.split_at(12);

trimmed
.try_into()
.expect("Should not fail to trim 16-length byte array to length 12")
}
}
}
116 changes: 116 additions & 0 deletions libs/types/src/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

use core::marker::PhantomData;

use cfg_primitives::types::{PoolId, TrancheId};
use cfg_traits::TrancheCurrency as TrancheCurrencyT;
use codec::{Decode, Encode, MaxEncodedLen};
pub use orml_asset_registry::AssetMetadata;
use scale_info::TypeInfo;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use sp_runtime::{traits::Get, DispatchError, TokenError};

use crate::xcm::XcmMetadata;

Expand Down Expand Up @@ -79,6 +82,56 @@ impl From<StakingCurrency> for CurrencyId {
}
}

/// A general index wrapper for a given currency representation which is the
/// concatenation of the generic prefix and the identifier of the respective
/// currency.
pub struct GeneralCurrencyIndex<Index, Prefix> {
pub index: Index,
_phantom: PhantomData<Prefix>,
}

impl<Index, Prefix> TryInto<GeneralCurrencyIndex<Index, Prefix>> for CurrencyId
where
Index: From<u128>,
Prefix: Get<[u8; 12]>,
{
type Error = DispatchError;

fn try_into(self) -> Result<GeneralCurrencyIndex<Index, Prefix>, Self::Error> {
let mut bytes = [0u8; 16];
bytes[..12].copy_from_slice(&Prefix::get());

let currency_bytes: [u8; 4] = match &self {
CurrencyId::ForeignAsset(id32) => Ok(id32.to_be_bytes()),
_ => Err(DispatchError::Token(TokenError::Unsupported)),
}?;
bytes[12..].copy_from_slice(&currency_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()
// should never throw but lets be safe
.map_err(|_| DispatchError::Corruption)?;

Ok(CurrencyId::ForeignAsset(u32::from_be_bytes(currency_bytes)))
}
}

/// A Currency that is solely used by tranches.
///
/// We distinguish here between the enum variant CurrencyId::Tranche(PoolId,
Expand Down Expand Up @@ -152,3 +205,66 @@ pub struct CustomMetadata {
/// Whether an asset can be used as a currency to fund Centrifuge Pools.
pub pool_currency: bool,
}

#[cfg(test)]
mod tests {
use frame_support::parameter_types;

use super::*;

const FOREIGN: CurrencyId = 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);

// check identity condition on reverse conversion
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
);

// check identity condition on reverse conversion
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::KSM).is_err()
);
assert!(
TryInto::<GeneralCurrencyIndex<u128, ZeroPrefix>>::try_into(CurrencyId::AUSD).is_err()
);
assert!(
TryInto::<GeneralCurrencyIndex<u128, ZeroPrefix>>::try_into(CurrencyId::Staking(
StakingCurrency::BlockRewards
))
.is_err()
);
}
}
69 changes: 60 additions & 9 deletions pallets/connectors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ pub type TrancheIdOf<T> = <<T as Config>::PoolInspect as PoolInspect<
pub type MessageOf<T> =
Message<Domain, PoolIdOf<T>, TrancheIdOf<T>, <T as Config>::Balance, <T as Config>::Rate>;

pub type CurrencyIdOf<T> = <T as pallet_xcm_transactor::Config>::CurrencyId;
pub type CurrencyIdOf<T> = <T as Config>::CurrencyId;

#[frame_support::pallet]
pub mod pallet {
use cfg_primitives::Moment;
use cfg_traits::{Permissions, PoolInspect, TrancheCurrency};
use cfg_types::{
permissions::{PermissionScope, PoolRole, Role},
tokens::CustomMetadata,
tokens::{CustomMetadata, GeneralCurrencyIndex},
};
use frame_support::{error::BadOrigin, pallet_prelude::*, traits::UnixTime};
use frame_system::pallet_prelude::*;
Expand Down Expand Up @@ -164,6 +164,24 @@ pub mod pallet {
Balance = <Self as Config>::Balance,
CustomMetadata = CustomMetadata,
>;

/// The currency type of transferrable token.
type CurrencyId: Parameter
+ Member
+ Copy
+ MaybeSerializeDeserialize
+ Ord
+ TypeInfo
+ MaxEncodedLen
+ Into<<Self as pallet_xcm_transactor::Config>::CurrencyId>
+ TryInto<
GeneralCurrencyIndex<u128, <Self as Config>::GeneralCurrencyPrefix>,
Error = DispatchError,
>;

/// The prefix for currencies added via Connectors.
#[pallet::constant]
type GeneralCurrencyPrefix: Get<[u8; 12]>;
}

#[pallet::event]
Expand Down Expand Up @@ -199,6 +217,9 @@ pub mod pallet {

#[pallet::error]
pub enum Error<T> {
/// Failed to map the asset to the corresponding Connector's General
/// Index representation
AssetNotFound,
/// A pool could not be found
PoolNotFound,
/// A tranche could not be found
Expand All @@ -221,6 +242,8 @@ pub mod pallet {
InvalidIncomingMessageOrigin,
/// Failed to decode an incoming message
InvalidIncomingMessage,
/// A transfer attempt from the local to the local domain
InvalidTransferDomain,
}

#[pallet::call]
Expand Down Expand Up @@ -279,6 +302,7 @@ pub mod pallet {
origin: OriginFor<T>,
pool_id: PoolIdOf<T>,
tranche_id: TrancheIdOf<T>,
decimals: u8,
domain: Domain,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
Expand All @@ -304,6 +328,7 @@ pub mod pallet {
Message::AddTranche {
pool_id,
tranche_id,
decimals,
token_name,
token_symbol,
price,
Expand Down Expand Up @@ -331,7 +356,7 @@ pub mod pallet {

Self::do_send_message(
who,
Message::UpdateTokenPrice {
Message::UpdateTrancheTokenPrice {
pool_id,
tranche_id,
price,
Expand Down Expand Up @@ -386,7 +411,7 @@ pub mod pallet {
pool_id,
tranche_id,
valid_until,
address: domain_address.address(),
member: domain_address.address(),
},
domain_address.domain(),
)?;
Expand All @@ -397,7 +422,7 @@ pub mod pallet {
/// Transfer tranche tokens to a given address
#[pallet::weight(< T as Config >::WeightInfo::transfer())]
#[pallet::call_index(6)]
pub fn transfer(
pub fn transfer_tranche_tokens(
origin: OriginFor<T>,
pool_id: PoolIdOf<T>,
tranche_id: TrancheIdOf<T>,
Expand All @@ -406,6 +431,11 @@ pub mod pallet {
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;

// Check that the destination is not the local domain
ensure!(
domain_address.domain() != Domain::Centrifuge,
Error::<T>::InvalidTransferDomain
);
// Check that the destination is a member of this tranche token
ensure!(
T::Permission::has(
Expand All @@ -431,13 +461,17 @@ pub mod pallet {
)?;

Self::do_send_message(
who,
Message::Transfer {
who.clone(),
Message::TransferTrancheTokens {
pool_id,
tranche_id,
amount,
domain: domain_address.domain(),
address: domain_address.address(),
sender: who
.encode()
.try_into()
.map_err(|_| DispatchError::Other("Conversion to 32 bytes failed"))?,
receiver: domain_address.address(),
},
domain_address.domain(),
)?;
Expand Down Expand Up @@ -496,7 +530,7 @@ pub mod pallet {
fee_payer,
// The currency in which we want to pay fees
CurrencyPayment {
currency: Currency::AsCurrencyId(xcm_domain.fee_currency),
currency: Currency::AsCurrencyId(xcm_domain.fee_currency.into()),
fee_amount: None,
},
// The call to be executed in the destination chain
Expand Down Expand Up @@ -558,6 +592,23 @@ pub mod pallet {

encoded
}

/// Returns the `u128` general index of a currency as the concatenation
/// of the configured `GeneralCurrencyPrefix` and its local currency
/// identifier.
///
/// Assumes the currency to be registered in the `AssetRegistry`.
pub fn try_get_general_index(currency: CurrencyIdOf<T>) -> Result<u128, DispatchError> {
ensure!(
T::AssetRegistry::metadata(&currency).is_some(),
Error::<T>::AssetNotFound
);

let general_index: GeneralCurrencyIndex<u128, T::GeneralCurrencyPrefix> =
CurrencyIdOf::<T>::try_into(currency)?;

Ok(general_index.index)
}
}
}

Expand Down

0 comments on commit bd4a96b

Please sign in to comment.