Skip to content
This repository has been archived by the owner on Oct 22, 2024. It is now read-only.

Commit

Permalink
Complete Control Pallet Implementation (paritytech#917)
Browse files Browse the repository at this point in the history
* Add create_channel & some refactor

* Fix test

* Not allow origin as relaychain?

* handle error

* Fix breaking tests

* Create/Update channel & Disable inbound channel

* Revamp tests remove the relaychain case

* Update weight with cumulus

* Revert "Create/Update channel & Disable inbound channel"

* Add smoke tests for xcm based control extrinsic

* Scripts and tests for set_operation_mode

* Use template node for xcm tests

* Update cumulus

* Revamp test with customize xcm call

* Add constraint only allow origin from Parachain

* Fix lint error

* Some polish

* Update parachain/pallets/control/src/lib.rs

Co-authored-by: Vincent Geddes <vincent@snowfork.com>

* Some improvement

* Explicit only allow ParaId as origin for all extrinsic

* Update cumulus(ControlOrigin allow from sibling only)

* Revert "Explicit only allow ParaId as origin for all extrinsic"

---------

Co-authored-by: Vincent Geddes <vincent@snowfork.com>
  • Loading branch information
yrong and vgeddes authored Aug 22, 2023
1 parent 60a82e0 commit d1590d0
Show file tree
Hide file tree
Showing 29 changed files with 923 additions and 94 deletions.
1 change: 1 addition & 0 deletions contracts/src/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ contract Gateway is IGateway, IInitializable {
CoreStorage.Layout storage $ = CoreStorage.layout();
SetOperatingModeParams memory params = abi.decode(data, (SetOperatingModeParams));
$.mode = params.mode;
emit OperatingModeChanged(params.mode);
}

struct TransferNativeFromAgentParams {
Expand Down
1 change: 1 addition & 0 deletions parachain/Cargo.lock

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

1 change: 1 addition & 0 deletions parachain/pallets/control/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ xcm-executor = { git = "https://github.com/paritytech/polkadot.git", branch = "m
ethabi = { git = "https://github.com/Snowfork/ethabi-decode.git", package = "ethabi-decode", branch = "master", default-features = false }

[dev-dependencies]
hex = "0.4.1"
hex-literal = { version = "0.4.1" }

[features]
Expand Down
55 changes: 53 additions & 2 deletions parachain/pallets/control/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
use super::*;

#[allow(unused)]
use crate::Pallet as Template;
use crate::Pallet as SnowbridgeControl;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
use snowbridge_core::outbound::OperatingMode;
use sp_core::Get;

#[benchmarks]
Expand Down Expand Up @@ -35,5 +36,55 @@ mod benchmarks {
Ok(())
}

impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test);
#[benchmark]
fn create_channel() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
frame_support::assert_ok!(SnowbridgeControl::<T>::create_agent(
RawOrigin::Signed(caller.clone()).into()
));

#[extrinsic_call]
_(RawOrigin::Signed(caller));

Ok(())
}

#[benchmark]
fn update_channel() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
frame_support::assert_ok!(SnowbridgeControl::<T>::create_agent(
RawOrigin::Signed(caller.clone()).into()
));
frame_support::assert_ok!(SnowbridgeControl::<T>::create_channel(
RawOrigin::Signed(caller.clone()).into()
));

#[extrinsic_call]
_(RawOrigin::Signed(caller), OperatingMode::RejectingOutboundMessages, 1, 1);

Ok(())
}

#[benchmark]
fn set_operating_mode() -> Result<(), BenchmarkError> {
#[extrinsic_call]
_(RawOrigin::Root, OperatingMode::RejectingOutboundMessages);

Ok(())
}

#[benchmark]
fn transfer_native_from_agent() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
frame_support::assert_ok!(SnowbridgeControl::<T>::create_agent(
RawOrigin::Signed(caller.clone()).into()
));

#[extrinsic_call]
_(RawOrigin::Signed(caller), H160::default(), 1);

Ok(())
}

impl_benchmark_test_suite!(SnowbridgeControl, crate::mock::new_test_ext(), crate::mock::Test);
}
204 changes: 184 additions & 20 deletions parachain/pallets/control/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub mod weights;
pub use weights::*;

use snowbridge_core::{
outbound::{Command, Message, OutboundQueue as OutboundQueueTrait, ParaId},
outbound::{Command, Message, OperatingMode, OutboundQueue as OutboundQueueTrait, ParaId},
AgentId,
};
use sp_core::{H160, H256};
Expand All @@ -29,10 +29,12 @@ use xcm_executor::traits::ConvertLocation;

pub use pallet::*;

pub const LOG_TARGET: &str = "snowbridge-control";

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{pallet_prelude::*, traits::EnsureOrigin};
use frame_support::{log, pallet_prelude::*, traits::EnsureOrigin};
use frame_system::pallet_prelude::*;

#[pallet::pallet]
Expand All @@ -54,8 +56,11 @@ pub mod pallet {
/// Max size of params passed to initializer of the new implementation contract
type MaxUpgradeDataSize: Get<u32>;

/// Origin check for `create_agent`
type CreateAgentOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = MultiLocation>;
/// Implementation that ensures origin is an XCM location for agent operations
type AgentOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = MultiLocation>;

/// Implementation that ensures origin is an XCM location for channel operations
type ChannelOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = MultiLocation>;

/// Converts MultiLocation to H256 in a way that is stable across multiple versions of XCM
type AgentHashedDescription: ConvertLocation<H256>;
Expand All @@ -76,19 +81,41 @@ pub mod pallet {
Upgrade { impl_address: H160, impl_code_hash: H256, params_hash: Option<H256> },
/// An CreateAgent message was sent to the Gateway
CreateAgent { location: Box<MultiLocation>, agent_id: AgentId },
/// An CreateChannel message was sent to the Gateway
CreateChannel { para_id: ParaId, agent_id: AgentId },
/// An UpdateChannel message was sent to the Gateway
UpdateChannel {
para_id: ParaId,
agent_id: AgentId,
mode: OperatingMode,
fee: u128,
reward: u128,
},
/// An SetOperatingMode message was sent to the Gateway
SetOperatingMode { mode: OperatingMode },
/// An TransferNativeFromAgent message was sent to the Gateway
TransferNativeFromAgent { agent_id: AgentId, recipient: H160, amount: u128 },
}

#[pallet::error]
pub enum Error<T> {
UpgradeDataTooLarge,
SubmissionFailed,
LocationConversionFailed,
LocationReanchorFailed,
LocationToParaIdConversionFailed,
LocationToAgentIdConversionFailed,
AgentAlreadyCreated,
AgentNotExist,
ChannelAlreadyCreated,
ChannelNotExist,
}

#[pallet::storage]
pub type Agents<T: Config> = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>;

#[pallet::storage]
pub type Channels<T: Config> = StorageMap<_, Twox64Concat, ParaId, (), OptionQuery>;

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Sends a message to the Gateway contract to upgrade itself.
Expand Down Expand Up @@ -131,33 +158,147 @@ pub mod pallet {
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::create_agent())]
pub fn create_agent(origin: OriginFor<T>) -> DispatchResult {
let mut location: MultiLocation = T::CreateAgentOrigin::ensure_origin(origin)?;
let origin_location: MultiLocation = T::AgentOrigin::ensure_origin(origin)?;

// Normalize all locations relative to the relay chain unless its the relay itself.
let relay_location = T::RelayLocation::get();
if location != relay_location {
location
.reanchor(&relay_location, T::UniversalLocation::get())
.or(Err(Error::<T>::LocationConversionFailed))?;
}
let (agent_id, _, location) = Self::convert_location(origin_location)?;

// Hash the location to produce an agent id
let agent_id = T::AgentHashedDescription::convert_location(&location)
.ok_or(Error::<T>::LocationConversionFailed)?;
log::debug!(
target: LOG_TARGET,
"💫 Create Agent request with agent_id {:?}, origin_location at {:?}, location at {:?}",
agent_id,
origin_location,
location
);

// Record the agent id or fail if it has already been created
if Agents::<T>::get(agent_id).is_some() {
return Err(Error::<T>::AgentAlreadyCreated.into())
}
ensure!(!Agents::<T>::contains_key(agent_id), Error::<T>::AgentAlreadyCreated);

Agents::<T>::insert(agent_id, ());

let message =
Message { origin: T::OwnParaId::get(), command: Command::CreateAgent { agent_id } };
Self::submit_outbound(message)?;
Self::submit_outbound(message.clone())?;

log::debug!(
target: LOG_TARGET,
"💫 Create Agent request processed with outbound message {:?}",
message
);

Self::deposit_event(Event::<T>::CreateAgent { location: Box::new(location), agent_id });
Ok(())
}

/// Sends a message to the Gateway contract to create a new Channel representing `origin`
///
/// - `origin`: Must be `MultiLocation`
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::create_channel())]
pub fn create_channel(origin: OriginFor<T>) -> DispatchResult {
let location: MultiLocation = T::ChannelOrigin::ensure_origin(origin)?;

let (agent_id, some_para_id, _) = Self::convert_location(location)?;

ensure!(Agents::<T>::contains_key(agent_id), Error::<T>::AgentNotExist);

let para_id = some_para_id.ok_or(Error::<T>::LocationToParaIdConversionFailed)?;

ensure!(!Channels::<T>::contains_key(para_id), Error::<T>::ChannelAlreadyCreated);

Channels::<T>::insert(para_id, ());

let message = Message {
origin: T::OwnParaId::get(),
command: Command::CreateChannel { agent_id, para_id },
};
Self::submit_outbound(message)?;

Self::deposit_event(Event::<T>::CreateChannel { para_id, agent_id });

Ok(())
}

/// Sends a message to the Gateway contract to update channel
///
/// - `origin`: Must be `MultiLocation`
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::update_channel())]
pub fn update_channel(
origin: OriginFor<T>,
mode: OperatingMode,
fee: u128,
reward: u128,
) -> DispatchResult {
let location: MultiLocation = T::ChannelOrigin::ensure_origin(origin)?;

let (agent_id, some_para_id, _) = Self::convert_location(location)?;

ensure!(Agents::<T>::contains_key(agent_id), Error::<T>::AgentNotExist);

let para_id = some_para_id.ok_or(Error::<T>::LocationToParaIdConversionFailed)?;

ensure!(Channels::<T>::contains_key(para_id), Error::<T>::ChannelNotExist);

let message = Message {
origin: T::OwnParaId::get(),
command: Command::UpdateChannel { para_id, mode, fee, reward },
};
Self::submit_outbound(message)?;

Self::deposit_event(Event::<T>::UpdateChannel { para_id, agent_id, mode, fee, reward });

Ok(())
}

/// Sends a message to the Gateway contract to set OperationMode
///
/// - `origin`: Must be `MultiLocation`
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::set_operating_mode())]
pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
ensure_root(origin)?;

let message = Message {
origin: T::OwnParaId::get(),
command: Command::SetOperatingMode { mode },
};
Self::submit_outbound(message)?;

Self::deposit_event(Event::<T>::SetOperatingMode { mode });

Ok(())
}

/// Sends a message to the Gateway contract to transfer asset from agent
///
/// - `origin`: Must be `MultiLocation`
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::transfer_native_from_agent())]
pub fn transfer_native_from_agent(
origin: OriginFor<T>,
recipient: H160,
amount: u128,
) -> DispatchResult {
let location: MultiLocation = T::AgentOrigin::ensure_origin(origin)?;

let (agent_id, _, _) = Self::convert_location(location)?;

ensure!(Agents::<T>::contains_key(agent_id), Error::<T>::AgentNotExist);

let message = Message {
origin: T::OwnParaId::get(),
command: Command::TransferNativeFromAgent { agent_id, recipient, amount },
};
Self::submit_outbound(message)?;

Self::deposit_event(Event::<T>::TransferNativeFromAgent {
agent_id,
recipient,
amount,
});

Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand All @@ -167,5 +308,28 @@ pub mod pallet {
T::OutboundQueue::submit(ticket).map_err(|_| Error::<T>::SubmissionFailed)?;
Ok(())
}

pub fn convert_location(
mut location: MultiLocation,
) -> Result<(H256, Option<ParaId>, MultiLocation), DispatchError> {
// Normalize all locations relative to the relay chain.
let relay_location = T::RelayLocation::get();
location
.reanchor(&relay_location, T::UniversalLocation::get())
.map_err(|_| Error::<T>::LocationReanchorFailed)?;

// Only allow Parachain as origin location
let para_id = match location {
MultiLocation { parents: 0, interior: X1(Parachain(index)) } =>
Some((index).into()),
_ => None,
};

// Hash the location to produce an agent id
let agent_id = T::AgentHashedDescription::convert_location(&location)
.ok_or(Error::<T>::LocationToAgentIdConversionFailed)?;

Ok((agent_id, para_id, location))
}
}
}
9 changes: 5 additions & 4 deletions parachain/pallets/control/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ parameter_types! {
}

static ORIGIN_TABLE: &[([u8; 32], MultiLocation)] = &[
// Case 1: Relay chain
([1; 32], MultiLocation { parents: 1, interior: Here }),
// Case 1: Bridge hub
([1; 32], MultiLocation { parents: 0, interior: Here }),
// Case 2: Local AccountId32
(
[2; 32],
Expand Down Expand Up @@ -152,7 +152,7 @@ impl EnsureOrigin<RuntimeOrigin> for EnsureOriginFromTable {
#[cfg(feature = "runtime-benchmarks")]
{
if account == whitelisted_caller() {
return Ok(MultiLocation::new(1, Here))
return Ok(MultiLocation::new(0, Here))
}
}

Expand Down Expand Up @@ -191,7 +191,8 @@ impl snowbridge_control::Config for Test {
type OutboundQueue = MockOutboundQueue;
type MessageHasher = BlakeTwo256;
type MaxUpgradeDataSize = MaxUpgradeDataSize;
type CreateAgentOrigin = EnsureOriginFromTable;
type AgentOrigin = EnsureOriginFromTable;
type ChannelOrigin = EnsureOriginFromTable;
type UniversalLocation = UniversalLocation;
type RelayLocation = RelayLocation;
type AgentHashedDescription = HashedDescription<H256, DescribeFamily<DescribeAllTerminal>>;
Expand Down
Loading

0 comments on commit d1590d0

Please sign in to comment.