Skip to content

Commit

Permalink
Connectors v1++ (centrifuge#1211)
Browse files Browse the repository at this point in the history
* connectors: Add POC `handle` extrinsic

* xcm: Add AccountIdHash origin convert + dev 1013

* Fix Barrier, change handle API + dev spec 1015

* fixup

* Add allowlist logic guarding `handle`

* Implement and test Message::Transfer decoding

* Clean up Message decoding

* fixup! Add doc

* Impl decoding for all message types

* Test identity decode . encode property for all messages

* Clean and improve all encoding / decoding tests

* Dry message encoding with encoded_message

* Use decode_be_bytes in Decode for Domain

* Clean up imports

* Decode message in handle

* fmt

* nix

* Rename to_be to encode_be

* Add weight fns for add_connector and handle

* type alias for Message type in message tests

* Doc Message enum

* Use Account32Hash and drop custom AccountIdHash

* Document Account32Hash

* fix clippy

* dev: spec_version 1016

* Option A

* dev: spec_version 1017

* Bump development runtime cargo version

* nix

* Fix Domain codec issues with custom Codec trait

* dev: spec_version 1018

* fmt

* fix encoded_ethereum_xcm_add_pool

* fmt
  • Loading branch information
NunoAlexandre authored Mar 3, 2023
1 parent 6b46c39 commit d1f436e
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 219 deletions.
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
};

# This is a hash of all the Cargo dependencies, for reproducibility.
cargoSha256 = "sha256-FgnJKi1LfSw+JX/2dx7uXClb3hkdCwoWTefZ83x6Tf4=";
cargoSha256 = "sha256-qFvobNDYPRfjHTyfF0KsqBCp+vhls0d8d6BcqKlhiek=";

nativeBuildInputs = with pkgs; [ clang git-mock pkg-config ];
buildInputs = with pkgs; [ openssl ] ++ (
Expand Down
31 changes: 30 additions & 1 deletion libs/utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// Ensure we're `no_std` when compiling for WebAssembly.
#![cfg_attr(not(feature = "std"), no_std)]

use codec::{Decode, Encode, Input};
use sp_std::{cmp::min, vec::Vec};

/// Build a fixed-size array using as many elements from `src` as possible without
Expand All @@ -26,13 +27,41 @@ pub fn vec_to_fixed_array<const S: usize>(src: Vec<u8>) -> [u8; S] {
dest
}

/// Encode a value in its big-endian representation of which all we know is that it
/// implements Encode. We use this for number types to make sure they are encoded
/// the way they are expected to be decoded on the Solidity side.
pub fn encode_be(x: impl Encode) -> Vec<u8> {
let mut output = x.encode();
output.reverse();
output
}

/// Decode a type O by reading S bytes from I. Those bytes are expected to be encoded
/// as big-endian and thus needs reversing to little-endian before decoding to O.
pub fn decode_be_bytes<const S: usize, O: Decode, I: Input>(
input: &mut I,
) -> Result<O, codec::Error> {
let mut bytes = [0; S];
input.read(&mut bytes[..])?;
bytes.reverse();

O::decode(&mut bytes.as_slice())
}

/// Decode a type 0 by reading S bytes from I.
pub fn decode<const S: usize, O: Decode, I: Input>(input: &mut I) -> Result<O, codec::Error> {
let mut bytes = [0; S];
input.read(&mut bytes[..])?;

O::decode(&mut bytes.as_slice())
}

/// Function that initializes the frame system & Aura, so a timestamp can be set and pass validation
#[cfg(any(feature = "runtime-benchmarks", feature = "std"))]
pub fn set_block_number_timestamp<T>(block_number: T::BlockNumber, timestamp: T::Moment)
where
T: pallet_aura::Config + frame_system::Config + pallet_timestamp::Config,
{
use codec::Encode;
use frame_support::traits::Hooks;
use sp_consensus_aura::AURA_ENGINE_ID;
use sp_runtime::{Digest, DigestItem};
Expand Down
78 changes: 63 additions & 15 deletions pallets/connectors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
use core::convert::TryFrom;

use cfg_traits::PoolInspect;
use cfg_utils::vec_to_fixed_array;
use codec::{Decode, Encode, EncodeLike, Input, MaxEncodedLen};
use cfg_utils::{decode_be_bytes, vec_to_fixed_array};
use codec::{Decode, Encode, Input, MaxEncodedLen};
use frame_support::traits::{
fungibles::{Inspect, Mutate, Transfer},
OriginTrait,
Expand Down Expand Up @@ -49,7 +49,7 @@ pub enum ParachainId {
/// 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(Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
#[derive(Encode, Decode, 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.
Expand All @@ -60,8 +60,15 @@ pub enum Domain {
EVM(EVMChainId),
}

impl Encode for Domain {
fn encode(&self) -> Vec<u8> {
/// An encoding & decoding trait for the purpose of meeting the
/// Connectors General Message Passing Format
pub trait Codec: Sized {
fn serialize(&self) -> Vec<u8>;
fn deserialize<I: Input>(input: &mut I) -> Result<Self, codec::Error>;
}

impl Codec for Domain {
fn serialize(&self) -> Vec<u8> {
match self {
Self::Centrifuge => vec![0; 9],
Self::EVM(chain_id) => {
Expand All @@ -72,21 +79,14 @@ impl Encode for Domain {
}
}
}
}

impl EncodeLike for Domain {}

impl Decode for Domain {
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
fn deserialize<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);
let chain_id = decode_be_bytes::<8, _, _>(input)?;
Ok(Self::EVM(chain_id))
}
_ => Err(codec::Error::from("Unknown Domain variant")),
Expand Down Expand Up @@ -242,12 +242,22 @@ pub mod pallet {
domain: Domain,
router: Router<CurrencyIdOf<T>>,
},

IncomingMessage {
sender: T::AccountId,
message: Vec<u8>,
},
}

#[pallet::storage]
pub(crate) type DomainRouter<T: Config> =
StorageMap<_, Blake2_128Concat, Domain, Router<CurrencyIdOf<T>>>;

/// The set of known connectors. This set is used as an allow-list when authorizing
/// the origin of incoming messages through the `handle` extrinsic.
#[pallet::storage]
pub(crate) type KnownConnectors<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, ()>;

#[pallet::error]
pub enum Error<T> {
/// A pool could not be found
Expand All @@ -268,6 +278,10 @@ pub mod pallet {
UnauthorizedTransfer,
/// Failed to build Ethereum_Xcm call
FailedToBuildEthereumXcmCall,
/// The origin of an incoming message is not in the allow-list
InvalidIncomingMessageOrigin,
/// Failed to decode an incoming message
InvalidIncomingMessage,
}

#[pallet::call]
Expand All @@ -287,6 +301,16 @@ pub mod pallet {
Ok(())
}

/// Add an AccountId to the set of known connectors, allowing that origin
/// to send incoming messages.
#[pallet::weight(< T as Config >::WeightInfo::add_connector())]
pub fn add_connector(origin: OriginFor<T>, connector: T::AccountId) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
<KnownConnectors<T>>::insert(connector, ());

Ok(())
}

/// Add a pool to a given domain
#[pallet::weight(< T as Config >::WeightInfo::add_pool())]
pub fn add_pool(
Expand Down Expand Up @@ -473,6 +497,30 @@ pub mod pallet {

Ok(())
}

/// Handle an incoming message
/// TODO(nuno): we probably need a custom origin type for these messages to ensure they have
/// come in through XCM. Probably even handle it in a separate pallet? For now, let's have a
/// POC here to test the pipeline Ethereum ---> Moonbeam ---> Centrifuge::connectors
#[pallet::call_index(99)]
#[pallet::weight(< T as Config >::WeightInfo::handle())]
pub fn handle(origin: OriginFor<T>, bytes: Vec<u8>) -> DispatchResult {
let sender = ensure_signed(origin.clone())?;
ensure!(
<KnownConnectors<T>>::contains_key(&sender),
Error::<T>::InvalidIncomingMessageOrigin
);

Self::deposit_event(Event::IncomingMessage {
sender,
message: bytes.clone(),
});
// todo: do someting with the decoded message later on
let _: MessageOf<T> = Message::deserialize(&mut bytes.as_slice())
.map_err(|_| Error::<T>::InvalidIncomingMessage)?;

Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand All @@ -489,7 +537,7 @@ pub mod pallet {
let Router::Xcm(xcm_domain) =
<DomainRouter<T>>::get(domain.clone()).ok_or(Error::<T>::MissingRouter)?;

let contract_call = contract::encoded_contract_call(message.encode());
let contract_call = contract::encoded_contract_call(message.serialize());
let ethereum_xcm_call =
Self::encoded_ethereum_xcm_call(xcm_domain.clone(), contract_call);

Expand Down
Loading

0 comments on commit d1f436e

Please sign in to comment.