Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Connectors v2 message PoC #1292

Merged
merged 35 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
65a3d17
feat: update Message enum
wischli Mar 28, 2023
690430e
feat: add (dummy) Into<u128> for CurrencyId
wischli Mar 28, 2023
b5eb58c
tests: apply changes to existing
wischli Mar 29, 2023
4588b91
refactor: add consts in tests
wischli Mar 29, 2023
0839851
tests: apply new messages
wischli Mar 30, 2023
394fd4e
refactor: apply suggestions from soft review
wischli Mar 30, 2023
6a86151
fix: clippy
wischli Mar 30, 2023
3ba9230
tests: fix integration
wischli Mar 31, 2023
9c84f3a
refactor: add LocalCurrencyId storage
wischli Apr 3, 2023
ccc22b0
Merge remote-tracking branch 'origin/main' into feat/connectors-v2-me…
wischli Apr 19, 2023
af67315
feat: add general index mapping for CurrencyId
wischli Apr 19, 2023
492fdb3
tests: fix integration for connectors
wischli Apr 19, 2023
7726985
fix: clippy warning
wischli Apr 19, 2023
93662a1
refactor: apply suggestions from review
wischli Apr 20, 2023
3db1233
refactor: remove unused code
wischli Apr 20, 2023
6cc8863
refactor: apply latest spec changes
wischli Apr 20, 2023
7cacb2b
refactor: remove CollectFor* msgs
wischli Apr 20, 2023
7f60bd4
tests: cover uts for all message cases
wischli Apr 20, 2023
c064dca
refactor: use TryInto instead of custom GeneralCurrencyIndex trait
wischli Apr 20, 2023
dd2356e
refactor: update message fields
wischli Apr 21, 2023
51542c5
feat: complete GeneralCurrencyIndex
wischli Apr 21, 2023
af3ea8e
style: fix indentation
wischli Apr 21, 2023
572fb34
Apply suggestion from code review by @NunoAlexandre
wischli May 2, 2023
4ecddf9
chore: add message docs, fix order
wischli May 2, 2023
2ab8593
Apply suggestion from review by @offerijns
wischli May 2, 2023
618f7c2
Update pallets/connectors/src/message.rs
wischli May 2, 2023
bdbf65d
docs: improve AssetNotFound
wischli May 2, 2023
14b3f09
docs: apply normalized directionality to Message
wischli May 2, 2023
6a06ba4
Merge branch 'main' into feat/connectors-v2-message-poc
wischli May 3, 2023
d774926
refactor: hashed GeneralCurrencyPrefix
wischli May 4, 2023
05dd755
fix: block local domain transfers
wischli May 4, 2023
6b06a9d
refactor: rm unneeded trait bounding
wischli May 4, 2023
e045d0e
Merge remote-tracking branch 'origin/main' into feat/connectors-v2-me…
wischli May 4, 2023
787cc53
fmt: apply comment wrapping
wischli May 4, 2023
2f22dae
fix: remove new extrinsics
wischli May 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions libs/primitives/src/lib.rs
Expand Up @@ -249,6 +249,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
}

/// The prefix for tokens managed by Connectors.
pub const GENERAL_CURRENCY_INDEX_PREFIX: [u8; 12] = *b"CfgCnctTknId";
wischli marked this conversation as resolved.
Show resolved Hide resolved
}

/// Listing of parachains we integrate with.
Expand Down
115 changes: 115 additions & 0 deletions libs/types/src/tokens.rs
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,55 @@ 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, TranchId)
Expand Down Expand Up @@ -151,3 +203,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()
);
}
}
130 changes: 121 additions & 9 deletions pallets/connectors/src/lib.rs
Expand Up @@ -94,15 +94,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 @@ -162,6 +162,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 @@ -196,6 +214,8 @@ pub mod pallet {

#[pallet::error]
pub enum Error<T> {
/// Failed to map the asset to its u128 representation
wischli marked this conversation as resolved.
Show resolved Hide resolved
AssetNotFound,
/// A pool could not be found
PoolNotFound,
/// A tranche could not be found
Expand Down Expand Up @@ -276,6 +296,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 @@ -301,6 +322,7 @@ pub mod pallet {
Message::AddTranche {
pool_id,
tranche_id,
decimals,
token_name,
token_symbol,
price,
Expand Down Expand Up @@ -328,7 +350,7 @@ pub mod pallet {

Self::do_send_message(
who,
Message::UpdateTokenPrice {
Message::UpdateTrancheTokenPrice {
pool_id,
tranche_id,
price,
Expand Down Expand Up @@ -382,7 +404,7 @@ pub mod pallet {
pool_id,
tranche_id,
valid_until,
address: domain_address.address(),
member: domain_address.address(),
},
domain_address.domain(),
)?;
Expand All @@ -393,7 +415,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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block local domain!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! 05dd755

origin: OriginFor<T>,
pool_id: PoolIdOf<T>,
tranche_id: TrancheIdOf<T>,
Expand Down Expand Up @@ -427,13 +449,83 @@ 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(),
receiver: domain_address.address(),
sender: who
wischli marked this conversation as resolved.
Show resolved Hide resolved
.encode()
.try_into()
.map_err(|_| DispatchError::Other("Conversion to 32 bytes failed"))?,
wischli marked this conversation as resolved.
Show resolved Hide resolved
},
domain_address.domain(),
)?;

Ok(())
}

/// Transfer non-tranche tokens to a given address
#[pallet::weight(< T as Config >::WeightInfo::transfer())]
#[pallet::call_index(7)]
pub fn transfer(
origin: OriginFor<T>,
asset_id: CurrencyIdOf<T>,
domain_address: DomainAddress,
amount: <T as pallet::Config>::Balance,
) -> DispatchResult {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block local domain!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! 05dd755

let who = ensure_signed(origin.clone())?;

ensure!(!amount.is_zero(), Error::<T>::InvalidTransferAmount);

// Transfer to the domain account for bookkeeping
T::Tokens::transfer(
asset_id,
&who,
&DomainLocator::<Domain> {
domain: domain_address.domain(),
}
.into_account_truncating(),
amount,
false,
)?;

Self::do_send_message(
who.clone(),
Message::Transfer {
amount,
currency: Self::try_get_general_index(asset_id)?,
sender: who
.encode()
.try_into()
.map_err(|_| DispatchError::Other("Conversion to 32 bytes failed"))?,
receiver: domain_address.address(),
},
domain_address.domain(),
)?;

Ok(())
}

/// Add a `CurrencyId` to the set of known currencies on a given Domain.
// TODO: Replace weight after benchmarking
#[pallet::weight(< T as Config >::WeightInfo::add_connector())]
#[pallet::call_index(8)]
pub fn add_currency(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have an issue for tracking that. This would be really important!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the FIXME

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not have an existing Github issue but this is part of the asset metadata update which is owned by @NunoAlexandre

Copy link
Contributor Author

@wischli wischli May 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improved message in 05dd755

origin: OriginFor<T>,
currency_id: CurrencyIdOf<T>,
domain_address: DomainAddress,
) -> DispatchResult {
let who = ensure_signed(origin)?;

Self::do_send_message(
who,
Message::AddCurrency {
currency: Self::try_get_general_index(currency_id)?,
// FIXME: In PR which adds evm_address to AssetRegistry
evm_address: [0u8; 20],
},
domain_address.domain(),
)?;
Expand Down Expand Up @@ -491,7 +583,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 @@ -550,6 +642,26 @@ 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: <T as pallet::Config>::CurrencyId,
) -> Result<u128, DispatchError> {
ensure!(
<T as Config>::AssetRegistry::metadata(&currency).is_some(),
wischli marked this conversation as resolved.
Show resolved Hide resolved
Error::<T>::AssetNotFound
);

let general_index: GeneralCurrencyIndex<
u128,
<T as pallet::Config>::GeneralCurrencyPrefix,
wischli marked this conversation as resolved.
Show resolved Hide resolved
> = CurrencyIdOf::<T>::try_into(currency)?;

Ok(general_index.index)
}
}
}

Expand Down