Skip to content

Commit

Permalink
connectors: Wrapped token lookup (#1393)
Browse files Browse the repository at this point in the history
* asset-registry: Add `transferability` property

We also add migragitons to set the appropriate value depending on the
type of currency.

* Move xcm metadata to Transferability::Xcm and All

* Add CrossChainTransferability::None and set as default

* wip: ConnectorsWrappedTokenConvert

* Impl Token to MultiLocation conversion

* Test ConnectorsWrappedToken conversions
  • Loading branch information
NunoAlexandre committed Jun 14, 2023
1 parent 7c67d0f commit f5db44e
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 5 deletions.
3 changes: 3 additions & 0 deletions libs/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ pub mod types {

/// A representation of a loan identifier
pub type LoanId = u64;

/// The type for indexing pallets on a Substrate runtime
pub type PalletIndex = u8;
}

/// Common constants for all runtimes
Expand Down
2 changes: 1 addition & 1 deletion libs/traits/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ pub trait TransferAllowance<AccountId> {
type CurrencyId;
type Location: Member + Debug + Eq + PartialEq + TypeInfo + Encode + Decode + MaxEncodedLen;
/// Determines whether the `send` account is allowed to make a transfer to
/// the `recieve` loocation with `currency` type currency. Returns result
/// the `receive` location with `currency` type currency. Returns result
/// wrapped bool for whether allowance is allowed.
fn allowance(
send: AccountId,
Expand Down
4 changes: 1 addition & 3 deletions libs/types/src/domain_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_core::TypeId;

/// The EVM Chain ID
/// The type should accomodate all chain ids listed on https://chainlist.org/.
type EVMChainId = u64;
use crate::EVMChainId;

/// A Domain is a chain or network we can send a Connectors message to.
/// The domain indices need to match those used in the EVM contracts and these
Expand Down
4 changes: 4 additions & 0 deletions libs/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ pub mod permissions;
pub mod time;
pub mod tokens;
pub mod xcm;

/// The EVM Chain ID
/// The type should accomodate all chain ids listed on https://chainlist.org/.
type EVMChainId = u64;
21 changes: 20 additions & 1 deletion libs/types/src/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_runtime::{traits::Get, DispatchError, TokenError};

use crate::xcm::XcmMetadata;
use crate::{xcm::XcmMetadata, EVMChainId};

#[derive(
Clone,
Expand Down Expand Up @@ -255,6 +255,25 @@ impl CrossChainTransferability {
}
}

/// Connectors-wrapped tokens
///
/// Currently, Connectors are only deployed on EVM-based chains and therefore
/// we only support EVM tokens. In the far future, we might support wrapped
/// tokens from other chains such as Cosmos based ones.
#[derive(
Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen,
)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum ConnectorsWrappedToken {
/// An EVM-native token
EVM {
/// The EVM chain id where the token is deployed
chain_id: EVMChainId,
/// The token contract address
address: [u8; 20],
},
}

#[cfg(test)]
mod tests {
use frame_support::parameter_types;
Expand Down
149 changes: 149 additions & 0 deletions runtime/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,152 @@ pub mod changes {
}
}
}

pub mod connectors {
use cfg_primitives::types::PalletIndex;
use cfg_types::tokens::ConnectorsWrappedToken;
use sp_core::Get;
use sp_runtime::traits::Convert;
use sp_std::marker::PhantomData;
use xcm::{
latest::{MultiLocation, NetworkId},
prelude::{AccountKey20, GlobalConsensus, PalletInstance, X3},
};

/// This type offers conversions between the xcm MultiLocation and our
/// ConnectorsWrappedToken types. This conversion is runtime-dependant, as
/// it needs to include the Connectors pallet index on the target runtime.
/// Therefore, we have `Index` as a generic type param that we use to unwrap
/// said pallet index and correctly convert between the two types.
pub struct ConnectorsWrappedTokenConvert<Index: Get<PalletIndex>>(PhantomData<Index>);

impl<Index: Get<PalletIndex>> Convert<MultiLocation, Result<ConnectorsWrappedToken, ()>>
for ConnectorsWrappedTokenConvert<Index>
{
fn convert(location: MultiLocation) -> Result<ConnectorsWrappedToken, ()> {
match location {
MultiLocation {
parents: 0,
interior:
X3(
PalletInstance(pallet_instance),
GlobalConsensus(NetworkId::Ethereum { chain_id }),
AccountKey20 {
network: None,
key: address,
},
),
} if pallet_instance == Index::get() => Ok(ConnectorsWrappedToken::EVM { chain_id, address }),
_ => Err(()),
}
}
}

impl<Index: Get<PalletIndex>> Convert<ConnectorsWrappedToken, MultiLocation>
for ConnectorsWrappedTokenConvert<Index>
{
fn convert(token: ConnectorsWrappedToken) -> MultiLocation {
match token {
ConnectorsWrappedToken::EVM { chain_id, address } => MultiLocation {
parents: 0,
interior: X3(
PalletInstance(Index::get()),
GlobalConsensus(NetworkId::Ethereum { chain_id }),
AccountKey20 {
network: None,
key: address,
},
),
},
}
}
}

#[cfg(test)]
mod test {
use sp_core::parameter_types;
use sp_runtime::traits::Convert;

use super::*;

#[test]
/// Verify that converting a ConnectorsWrappedToken to MultiLocation and
/// back results in the same original value.
fn connectors_wrapped_token_convert_identity() {
parameter_types! {
const Index: PalletIndex = 108;
}

const CHAIN_ID: u64 = 123;
const ADDRESS: [u8; 20] = [9; 20];

let wrapped_token = ConnectorsWrappedToken::EVM {
chain_id: CHAIN_ID,
address: ADDRESS,
};

let location = MultiLocation {
parents: 0,
interior: X3(
PalletInstance(Index::get()),
GlobalConsensus(NetworkId::Ethereum { chain_id: CHAIN_ID }),
AccountKey20 {
network: None,
key: ADDRESS,
},
),
};

assert_eq!(
<ConnectorsWrappedTokenConvert<Index> as Convert<
ConnectorsWrappedToken,
MultiLocation,
>>::convert(wrapped_token),
location
);

assert_eq!(
<ConnectorsWrappedTokenConvert<Index> as Convert<
MultiLocation,
Result<ConnectorsWrappedToken, ()>,
>>::convert(location),
Ok(wrapped_token)
);
}

/// Verify that ConnectorsWrappedTokenConvert will fail to convert a
/// location to a ConnectorsWrappedToken if the PalletInstance value
/// doesn't match the Index generic param, i.e, fail if the token
/// doesn't appear to be under the Connectors pallet and thus be a
/// connectors wrapped token.
#[test]
fn connectors_wrapped_token_convert_fail() {
parameter_types! {
const Index: PalletIndex = 108;
const WrongIndex: PalletIndex = 72;
}
const CHAIN_ID: u64 = 123;
const ADDRESS: [u8; 20] = [9; 20];

let location = MultiLocation {
parents: 0,
interior: X3(
PalletInstance(WrongIndex::get()),
GlobalConsensus(NetworkId::Ethereum { chain_id: CHAIN_ID }),
AccountKey20 {
network: None,
key: ADDRESS,
},
),
};

assert_eq!(
<ConnectorsWrappedTokenConvert<Index> as Convert<
MultiLocation,
Result<ConnectorsWrappedToken, ()>,
>>::convert(location),
Err(())
);
}
}
}

0 comments on commit f5db44e

Please sign in to comment.