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

Connectors v0.1.0 #1160

Merged
merged 55 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a1c8210
connectors: Test Domain encoding
NunoAlexandre Jan 5, 2023
23cba95
Merge branch 'main' into connectors-v0.1.0
NunoAlexandre Jan 6, 2023
74b2d30
Merge branch 'main' into connectors-v0.1.0
NunoAlexandre Jan 11, 2023
7e4f491
Merge branch 'main' into connectors-v0.1.0
NunoAlexandre Jan 19, 2023
fc3ec9e
dev: Bump spec_version
NunoAlexandre Jan 19, 2023
65c252d
tmp: Enable full CI for this branch
NunoAlexandre Jan 20, 2023
d02bf35
Test ethereum mainnet and avalanche domain encoding
NunoAlexandre Jan 24, 2023
ea3f98b
Merge branch 'main' into connectors-v0.1.0
NunoAlexandre Jan 24, 2023
3f4c8b7
Merge branch 'main' into connectors-v0.1.0
NunoAlexandre Jan 25, 2023
f126cea
Un-skip and fix transfer message encoding test
NunoAlexandre Jan 25, 2023
252efa6
fix encoded_ethereum_xcm_add_pool
NunoAlexandre Jan 25, 2023
8814129
Add `max_gas_limit` to XcmDomain
NunoAlexandre Jan 25, 2023
001795f
dev: Bump spec_version to 1009
NunoAlexandre Jan 25, 2023
c82ed7f
Derive transact_required_weight_at_most from gas_limit
NunoAlexandre Jan 25, 2023
3ad77ff
fmt
NunoAlexandre Jan 25, 2023
ad71b3b
dev: Bump spec_version to 1010
NunoAlexandre Jan 25, 2023
79be0de
Test update_member_that_failed
NunoAlexandre Jan 26, 2023
fde1c8a
fix: Align valid_until encoding to solidity counterpart
NunoAlexandre Jan 30, 2023
c9549f7
Add price to `AddTranche`
NunoAlexandre Jan 30, 2023
1e0e8bb
Encode AddTranche.price as little endian
NunoAlexandre Jan 30, 2023
1a3f64b
Encode UpdateTokenPrice.price as le
NunoAlexandre Jan 31, 2023
c0c32d4
Fix add_tranche test
NunoAlexandre Jan 31, 2023
45550a7
Dry tranche_id_from_hex
NunoAlexandre Feb 1, 2023
06ed7a4
Test transfer to xcm and evm domains
NunoAlexandre Feb 1, 2023
2bbfd8c
Merge branch 'main' into connectors-v0.1.0
NunoAlexandre Feb 1, 2023
2143f85
Merge branch 'main' into connectors-v0.1.0
NunoAlexandre Feb 3, 2023
eb78555
Fix MissingTranchePrice error
NunoAlexandre Feb 7, 2023
eea37ae
Redesign DomainAddress type
NunoAlexandre Feb 7, 2023
493969e
Add trait ConnectorEncode + update tests
NunoAlexandre Feb 7, 2023
520d313
Fix transfer encoding/decoding
NunoAlexandre Feb 7, 2023
3a04c84
fmt
NunoAlexandre Feb 7, 2023
d414c14
Merge branch 'main' into connectors-v0.1.0
NunoAlexandre Feb 7, 2023
17411af
dev: Bump spec_version to 1011
NunoAlexandre Feb 8, 2023
c6e8ece
Encode valid_until directly as be instead of mut reverse
NunoAlexandre Feb 10, 2023
d591200
Clean some code
NunoAlexandre Feb 10, 2023
0051a3c
Merge branch 'main' into connectors-v0.1.0
NunoAlexandre Feb 13, 2023
2903371
Add TrancheInvestor if not already one
NunoAlexandre Feb 13, 2023
e75bfea
Test DomainAddress account derivation
NunoAlexandre Feb 13, 2023
efbae75
e2e: Test update_member
NunoAlexandre Feb 13, 2023
a023d5c
Fix type inference issue
NunoAlexandre Feb 13, 2023
fce7c8a
e2e: Test update_token_price
NunoAlexandre Feb 13, 2023
f120eba
extend update_member test
NunoAlexandre Feb 13, 2023
eec6ea1
Revert temporary CI changes
NunoAlexandre Feb 13, 2023
b6da4de
Clean up Message type param type bounds
NunoAlexandre Feb 13, 2023
ee4fedf
Re-order Transfer fields
NunoAlexandre Feb 14, 2023
57ab298
Merge remote-tracking branch 'origin/main' into connectors-v0.1.0
NunoAlexandre Feb 14, 2023
717a339
Clean up big-endian custom encoding
NunoAlexandre Feb 14, 2023
3ab269c
Fix clippy
NunoAlexandre Feb 14, 2023
353d007
Document max_gas_limit to transact max weight
NunoAlexandre Feb 14, 2023
695f2b7
Fix clippy
NunoAlexandre Feb 14, 2023
92327a7
Update pallets/connectors/src/message.rs
NunoAlexandre Feb 15, 2023
9840154
Clean up message encoding tests
NunoAlexandre Feb 15, 2023
16c85bc
Extend domain_address_account_derivation
NunoAlexandre Feb 15, 2023
b18e330
Document DomainAddress variants
NunoAlexandre Feb 15, 2023
1879ab5
Drop ConnectorEncode and impl Encode+Decode for Domain
NunoAlexandre Feb 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 158 additions & 35 deletions pallets/connectors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use core::convert::TryFrom;

use cfg_traits::PoolInspect;
use cfg_utils::vec_to_fixed_array;
use codec::{Decode, Encode, MaxEncodedLen};
use codec::{Decode, Encode, EncodeLike, Input, MaxEncodedLen};
use frame_support::traits::{
fungibles::{Inspect, Mutate, Transfer},
OriginTrait,
Expand All @@ -25,7 +25,7 @@ pub use pallet::*;
use scale_info::TypeInfo;
use sp_core::{TypeId, U256};
use sp_runtime::{traits::AtLeast32BitUnsigned, FixedPointNumber};
use sp_std::{boxed::Box, convert::TryInto, vec::Vec};
use sp_std::{boxed::Box, convert::TryInto, vec, vec::Vec};
pub mod weights;

mod message;
Expand All @@ -45,24 +45,60 @@ pub enum ParachainId {
Moonbeam,
}

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

/// 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
/// need to pass the Centrifuge domain to send tranche tokens from the other
/// domain here. Therefore, DO NOT remove or move variants around.
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
#[derive(Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum Domain {
/// Referring to the Centrifuge Parachain. Will be used for handling incoming messages.
/// NOTE: Connectors messages CAN NOT be sent directly from the Centrifuge chain to the
/// Centrifuge chain itself.
Centrifuge,
/// An EVM domain, identified by its EVM Chain Id
EVM(EVMChainId),
/// A Polkadot Parachain domain
Parachain(ParachainId),
}

#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
impl Encode for Domain {
fn encode(&self) -> Vec<u8> {
match self {
Self::Centrifuge => vec![0; 9],
Self::EVM(chain_id) => {
let mut output: Vec<u8> = 1u8.encode();
output.append(&mut chain_id.to_be_bytes().to_vec());

output
}
}
}
}

impl EncodeLike for Domain {}

impl Decode for Domain {
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
let variant = input.read_byte()?;

match variant {
0 => Ok(Self::Centrifuge),
1 => {
let mut chain_id_be_bytes = [0; 8];
input.read(&mut chain_id_be_bytes[..])?;

let chain_id = EVMChainId::from_be_bytes(chain_id_be_bytes);
Ok(Self::EVM(chain_id))
}
_ => Err(codec::Error::from("Unknown Domain variant")),
}
}
}

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

#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
pub struct DomainLocator<Domain> {
pub domain: Domain,
}
Expand All @@ -71,14 +107,40 @@ impl<Domain> TypeId for DomainLocator<Domain> {
const TYPE_ID: [u8; 4] = *b"domn";
}

#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, TypeInfo)]
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct DomainAddress<Domain> {
pub domain: Domain,
pub address: [u8; 32],
pub enum DomainAddress {
/// A Centrifuge-Chain based account address, 32-bytes long
Centrifuge([u8; 32]),
/// An EVM chain address, 20-bytes long
EVM(EVMChainId, [u8; 20]),
wischli marked this conversation as resolved.
Show resolved Hide resolved
}

impl From<DomainAddress> for Domain {
fn from(x: DomainAddress) -> Self {
match x {
DomainAddress::Centrifuge(_) => Domain::Centrifuge,
DomainAddress::EVM(chain_id, _) => Domain::EVM(chain_id),
}
}
}

impl DomainAddress {
/// Get the address in a 32-byte long representation.
/// For EVM addresses, append 12 zeros.
fn address(&self) -> [u8; 32] {
match self.clone() {
Self::Centrifuge(x) => x,
Self::EVM(_, x) => vec_to_fixed_array(x.to_vec()),
}
}

fn domain(&self) -> Domain {
self.clone().into()
}
}

impl<Domain> TypeId for DomainAddress<Domain> {
impl TypeId for DomainAddress {
const TYPE_ID: [u8; 4] = *b"dadr";
}

Expand Down Expand Up @@ -265,6 +327,9 @@ pub mod pallet {
.ok_or(Error::<T>::TrancheMetadataNotFound)?;
let token_name = vec_to_fixed_array(metadata.name);
let token_symbol = vec_to_fixed_array(metadata.symbol);
let price = T::PoolInspect::get_tranche_token_price(pool_id, tranche_id)
.ok_or(Error::<T>::MissingTranchePrice)?
.price;

// Send the message to the domain
Self::do_send_message(
Expand All @@ -274,6 +339,7 @@ pub mod pallet {
tranche_id,
token_name,
token_symbol,
price,
},
domain,
)?;
Expand All @@ -291,15 +357,16 @@ pub mod pallet {
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;

let latest_price = T::PoolInspect::get_tranche_token_price(pool_id, tranche_id)
.ok_or(Error::<T>::MissingTranchePrice)?;
let price = T::PoolInspect::get_tranche_token_price(pool_id, tranche_id)
.ok_or(Error::<T>::MissingTranchePrice)?
.price;

Self::do_send_message(
who,
Message::UpdateTokenPrice {
pool_id,
tranche_id,
price: latest_price.price,
price,
},
domain,
)?;
Expand All @@ -311,7 +378,7 @@ pub mod pallet {
#[pallet::weight(< T as Config >::WeightInfo::update_member())]
pub fn update_member(
origin: OriginFor<T>,
address: DomainAddress<Domain>,
domain_address: DomainAddress,
pool_id: PoolIdOf<T>,
tranche_id: TrancheIdOf<T>,
valid_until: Moment,
Expand All @@ -328,22 +395,30 @@ pub mod pallet {
BadOrigin
);

// Now add the destination address as a TrancheInvestor of the given tranche
T::Permission::add(
// Now add the destination address as a TrancheInvestor of the given tranche if
// not already one. This check is necessary shall a user have called `update_member`
// already but the call has failed on the EVM side and needs to be retried.
if !T::Permission::has(
PermissionScope::Pool(pool_id),
address.into_account_truncating(),
domain_address.into_account_truncating(),
Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, valid_until)),
)?;
) {
T::Permission::add(
PermissionScope::Pool(pool_id),
domain_address.into_account_truncating(),
Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, valid_until)),
)?;
}
lemunozm marked this conversation as resolved.
Show resolved Hide resolved

Self::do_send_message(
who,
Message::UpdateMember {
pool_id,
tranche_id,
valid_until,
address: address.address,
address: domain_address.address(),
},
address.domain,
domain_address.domain(),
)?;

Ok(())
Expand All @@ -355,7 +430,7 @@ pub mod pallet {
origin: OriginFor<T>,
pool_id: PoolIdOf<T>,
tranche_id: TrancheIdOf<T>,
address: DomainAddress<Domain>,
domain_address: DomainAddress,
amount: <T as pallet::Config>::Balance,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
Expand All @@ -364,7 +439,7 @@ pub mod pallet {
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
address.into_account_truncating(),
domain_address.into_account_truncating(),
Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, Self::now()))
),
Error::<T>::UnauthorizedTransfer
Expand All @@ -376,8 +451,8 @@ pub mod pallet {
T::Tokens::transfer(
T::TrancheCurrency::generate(pool_id, tranche_id).into(),
&who,
&DomainLocator {
domain: address.domain.clone(),
&DomainLocator::<Domain> {
domain: domain_address.domain(),
}
.into_account_truncating(),
amount,
Expand All @@ -390,10 +465,10 @@ pub mod pallet {
pool_id,
tranche_id,
amount,
domain: address.clone().domain,
destination: address.clone().address,
domain: domain_address.domain(),
address: domain_address.address(),
},
address.domain,
domain_address.domain(),
)?;

Ok(())
Expand Down Expand Up @@ -432,8 +507,9 @@ pub mod pallet {
ethereum_xcm_call,
OriginKind::SovereignAccount,
TransactWeights {
// Specify a conservative max weight
transact_required_weight_at_most: 8_000_000_000,
// Convert the max gas_limit into a max transact weight following Moonbeam's formula.
transact_required_weight_at_most: xcm_domain.max_gas_limit * 25_000
+ 100_000_000,
NunoAlexandre marked this conversation as resolved.
Show resolved Hide resolved
overall_weight: None,
},
)?;
Expand Down Expand Up @@ -464,7 +540,7 @@ pub mod pallet {
encoded.append(
&mut xcm_primitives::EthereumXcmTransaction::V1(
xcm_primitives::EthereumXcmTransactionV1 {
gas_limit: U256::from(80_000),
gas_limit: U256::from(xcm_domain.max_gas_limit),
fee_payment: xcm_primitives::EthereumXcmFee::Auto,
action: pallet_ethereum::TransactionAction::Call(
xcm_domain.contract_address,
Expand All @@ -485,3 +561,50 @@ pub mod pallet {
}
}
}

#[cfg(test)]
mod tests {
use cfg_primitives::AccountId;
use codec::{Decode, Encode};
use sp_runtime::traits::AccountIdConversion;

use super::DomainAddress;
use crate::Domain;

#[test]
fn test_domain_encode_decode() {
test_domain_identity(Domain::Centrifuge);
test_domain_identity(Domain::EVM(1284));
test_domain_identity(Domain::EVM(1));
}

/// Test that decode . encode results in the original value
fn test_domain_identity(domain: Domain) {
let encoded = domain.encode();
let decoded: Domain = Domain::decode(&mut encoded.as_slice()).expect("");

assert_eq!(domain, decoded);
}

#[test]
fn domain_address_account_derivation() {
assert_eq!(
account_from(DomainAddress::EVM(1284, [9; 20])),
NunoAlexandre marked this conversation as resolved.
Show resolved Hide resolved
account_from(DomainAddress::EVM(1284, [9; 20])),
);
NunoAlexandre marked this conversation as resolved.
Show resolved Hide resolved

assert_ne!(
account_from(DomainAddress::EVM(1284, [42; 20])),
account_from(DomainAddress::EVM(1284, [24; 20])),
);

assert_ne!(
account_from(DomainAddress::EVM(1284, [9; 20])),
account_from(DomainAddress::EVM(1285, [9; 20])),
);
}

fn account_from(domain_address: DomainAddress) -> AccountId {
domain_address.into_account_truncating()
}
}
Loading