3139ffa25e
- snowbridge-pezpallet-* → pezsnowbridge-pezpallet-* (201 refs) - pallet/ directories → pezpallet/ (4 locations) - Fixed pezpallet.rs self-include recursion bug - Fixed sc-chain-spec hardcoded crate name in derive macro - Reverted .pezpallet_by_name() to .pallet_by_name() (subxt API) - Added BizinikiwiConfig type alias for zombienet tests - Deleted obsolete session state files Verified: pezsnowbridge-pezpallet-*, pezpallet-staking, pezpallet-staking-async, pezframe-benchmarking-cli all pass cargo check
402 lines
9.8 KiB
Rust
402 lines
9.8 KiB
Rust
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
|
|
|
|
extern crate alloc;
|
|
|
|
use crate::reward::RewardPaymentError::{ChargeFeesFailure, XcmSendFailure};
|
|
use bp_relayers::PaymentProcedure;
|
|
use codec::DecodeWithMemTracking;
|
|
use pezframe_support::{dispatch::GetDispatchInfo, PalletError};
|
|
use scale_info::TypeInfo;
|
|
use pezsp_runtime::{
|
|
codec::{Decode, Encode},
|
|
traits::Get,
|
|
DispatchError,
|
|
};
|
|
use pezsp_std::{fmt::Debug, marker::PhantomData};
|
|
use xcm::{
|
|
opaque::latest::prelude::Xcm,
|
|
prelude::{ExecuteXcm, Junction::*, Location, SendXcm, *},
|
|
};
|
|
|
|
/// Describes the message that the tip should be added to (either Inbound or Outbound message) and
|
|
/// the message nonce.
|
|
#[derive(Debug, Clone, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
|
|
pub enum MessageId {
|
|
/// Message from Ethereum
|
|
Inbound(u64),
|
|
/// Message to Ethereum
|
|
Outbound(u64),
|
|
}
|
|
|
|
#[derive(Debug, Encode, PartialEq, DecodeWithMemTracking, Decode, TypeInfo, PalletError)]
|
|
pub enum AddTipError {
|
|
NonceConsumed,
|
|
UnknownMessage,
|
|
AmountZero,
|
|
}
|
|
|
|
/// Trait to add a tip for a nonce.
|
|
pub trait AddTip {
|
|
/// Add a relayer reward tip to a pezpallet.
|
|
fn add_tip(nonce: u64, amount: u128) -> Result<(), AddTipError>;
|
|
}
|
|
|
|
/// Error related to paying out relayer rewards.
|
|
#[derive(Debug, Encode, Decode)]
|
|
pub enum RewardPaymentError {
|
|
/// The XCM to mint the reward on AssetHub could not be sent.
|
|
XcmSendFailure,
|
|
/// The delivery fee to send the XCM could not be charged.
|
|
ChargeFeesFailure,
|
|
}
|
|
|
|
impl From<RewardPaymentError> for DispatchError {
|
|
fn from(e: RewardPaymentError) -> DispatchError {
|
|
match e {
|
|
XcmSendFailure => DispatchError::Other("xcm send failure"),
|
|
ChargeFeesFailure => DispatchError::Other("charge fees error"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Reward payment procedure that sends a XCM to AssetHub to mint the reward (foreign asset)
|
|
/// into the provided beneficiary account.
|
|
pub struct PayAccountOnLocation<
|
|
Relayer,
|
|
RewardBalance,
|
|
EthereumNetwork,
|
|
AssetHubLocation,
|
|
InboundQueueLocation,
|
|
XcmSender,
|
|
XcmExecutor,
|
|
Call,
|
|
>(
|
|
PhantomData<(
|
|
Relayer,
|
|
RewardBalance,
|
|
EthereumNetwork,
|
|
AssetHubLocation,
|
|
InboundQueueLocation,
|
|
XcmSender,
|
|
XcmExecutor,
|
|
Call,
|
|
)>,
|
|
);
|
|
|
|
impl<
|
|
Relayer,
|
|
RewardBalance,
|
|
EthereumNetwork,
|
|
AssetHubLocation,
|
|
InboundQueueLocation,
|
|
XcmSender,
|
|
XcmExecutor,
|
|
Call,
|
|
> PaymentProcedure<Relayer, (), RewardBalance>
|
|
for PayAccountOnLocation<
|
|
Relayer,
|
|
RewardBalance,
|
|
EthereumNetwork,
|
|
AssetHubLocation,
|
|
InboundQueueLocation,
|
|
XcmSender,
|
|
XcmExecutor,
|
|
Call,
|
|
>
|
|
where
|
|
Relayer: Clone
|
|
+ Debug
|
|
+ Decode
|
|
+ Encode
|
|
+ Eq
|
|
+ TypeInfo
|
|
+ Into<pezsp_runtime::AccountId32>
|
|
+ Into<Location>,
|
|
EthereumNetwork: Get<NetworkId>,
|
|
InboundQueueLocation: Get<InteriorLocation>,
|
|
AssetHubLocation: Get<Location>,
|
|
XcmSender: SendXcm,
|
|
RewardBalance: Into<u128> + Clone,
|
|
XcmExecutor: ExecuteXcm<Call>,
|
|
Call: Decode + GetDispatchInfo,
|
|
{
|
|
type Error = DispatchError;
|
|
type Beneficiary = Location;
|
|
|
|
fn pay_reward(
|
|
relayer: &Relayer,
|
|
_: (),
|
|
reward: RewardBalance,
|
|
beneficiary: Self::Beneficiary,
|
|
) -> Result<(), Self::Error> {
|
|
let ethereum_location = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]);
|
|
let assets: Asset = (ethereum_location.clone(), reward.into()).into();
|
|
|
|
let xcm: Xcm<()> = alloc::vec![
|
|
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
|
DescendOrigin(InboundQueueLocation::get().into()),
|
|
UniversalOrigin(GlobalConsensus(EthereumNetwork::get())),
|
|
ReserveAssetDeposited(assets.into()),
|
|
DepositAsset { assets: AllCounted(1).into(), beneficiary },
|
|
]
|
|
.into();
|
|
|
|
let (ticket, fee) =
|
|
validate_send::<XcmSender>(AssetHubLocation::get(), xcm).map_err(|_| XcmSendFailure)?;
|
|
XcmExecutor::charge_fees(relayer.clone(), fee).map_err(|_| ChargeFeesFailure)?;
|
|
XcmSender::deliver(ticket).map_err(|_| XcmSendFailure)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use pezframe_support::parameter_types;
|
|
use pezsp_runtime::AccountId32;
|
|
|
|
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
|
|
pub struct MockRelayer(pub AccountId32);
|
|
|
|
impl From<MockRelayer> for AccountId32 {
|
|
fn from(m: MockRelayer) -> Self {
|
|
m.0
|
|
}
|
|
}
|
|
|
|
impl From<MockRelayer> for Location {
|
|
fn from(_m: MockRelayer) -> Self {
|
|
// For simplicity, return a dummy location
|
|
Location::new(1, Here)
|
|
}
|
|
}
|
|
|
|
pub enum BridgeReward {
|
|
#[allow(dead_code)]
|
|
Snowbridge,
|
|
}
|
|
|
|
parameter_types! {
|
|
pub AssetHubLocation: Location = Location::new(1,[Teyrchain(1000)]);
|
|
pub InboundQueueLocation: InteriorLocation = [PalletInstance(84)].into();
|
|
pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 };
|
|
pub const DefaultMyRewardKind: BridgeReward = BridgeReward::Snowbridge;
|
|
}
|
|
|
|
pub enum Weightless {}
|
|
impl PreparedMessage for Weightless {
|
|
fn weight_of(&self) -> Weight {
|
|
unreachable!();
|
|
}
|
|
}
|
|
|
|
pub struct MockXcmExecutor;
|
|
impl<C> ExecuteXcm<C> for MockXcmExecutor {
|
|
type Prepared = Weightless;
|
|
fn prepare(_: Xcm<C>, _: Weight) -> Result<Self::Prepared, InstructionError> {
|
|
Err(InstructionError { index: 0, error: XcmError::Unimplemented })
|
|
}
|
|
fn execute(
|
|
_: impl Into<Location>,
|
|
_: Self::Prepared,
|
|
_: &mut XcmHash,
|
|
_: Weight,
|
|
) -> Outcome {
|
|
unreachable!()
|
|
}
|
|
fn charge_fees(_: impl Into<Location>, _: Assets) -> xcm::latest::Result {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Decode, Default)]
|
|
pub struct MockCall;
|
|
impl GetDispatchInfo for MockCall {
|
|
fn get_dispatch_info(&self) -> pezframe_support::dispatch::DispatchInfo {
|
|
Default::default()
|
|
}
|
|
}
|
|
|
|
pub struct MockXcmSender;
|
|
impl SendXcm for MockXcmSender {
|
|
type Ticket = Xcm<()>;
|
|
|
|
fn validate(
|
|
dest: &mut Option<Location>,
|
|
xcm: &mut Option<Xcm<()>>,
|
|
) -> SendResult<Self::Ticket> {
|
|
if let Some(location) = dest {
|
|
match location.unpack() {
|
|
(_, [Teyrchain(1001)]) => return Err(SendError::NotApplicable),
|
|
_ => Ok((xcm.clone().unwrap(), Assets::default())),
|
|
}
|
|
} else {
|
|
Ok((xcm.clone().unwrap(), Assets::default()))
|
|
}
|
|
}
|
|
|
|
fn deliver(xcm: Self::Ticket) -> core::result::Result<XcmHash, SendError> {
|
|
let hash = xcm.using_encoded(pezsp_io::hashing::blake2_256);
|
|
Ok(hash)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn pay_reward_success() {
|
|
let relayer = MockRelayer(AccountId32::new([1u8; 32]));
|
|
let beneficiary = Location::new(1, Here);
|
|
let reward = 1_000u128;
|
|
|
|
type TestedPayAccountOnLocation = PayAccountOnLocation<
|
|
MockRelayer,
|
|
u128,
|
|
EthereumNetwork,
|
|
AssetHubLocation,
|
|
InboundQueueLocation,
|
|
MockXcmSender,
|
|
MockXcmExecutor,
|
|
MockCall,
|
|
>;
|
|
|
|
let result = TestedPayAccountOnLocation::pay_reward(&relayer, (), reward, beneficiary);
|
|
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn pay_reward_fails_on_xcm_validate_xcm() {
|
|
struct FailingXcmValidator;
|
|
impl SendXcm for FailingXcmValidator {
|
|
type Ticket = ();
|
|
|
|
fn validate(
|
|
_dest: &mut Option<Location>,
|
|
_xcm: &mut Option<Xcm<()>>,
|
|
) -> SendResult<Self::Ticket> {
|
|
Err(SendError::NotApplicable)
|
|
}
|
|
|
|
fn deliver(xcm: Self::Ticket) -> core::result::Result<XcmHash, SendError> {
|
|
let hash = xcm.using_encoded(pezsp_io::hashing::blake2_256);
|
|
Ok(hash)
|
|
}
|
|
}
|
|
|
|
type FailingSenderPayAccount = PayAccountOnLocation<
|
|
MockRelayer,
|
|
u128,
|
|
EthereumNetwork,
|
|
AssetHubLocation,
|
|
InboundQueueLocation,
|
|
FailingXcmValidator,
|
|
MockXcmExecutor,
|
|
MockCall,
|
|
>;
|
|
|
|
let relayer = MockRelayer(AccountId32::new([1u8; 32]));
|
|
let reward = 1_000u128;
|
|
let beneficiary = Location::new(1, Here);
|
|
let result = FailingSenderPayAccount::pay_reward(&relayer, (), reward, beneficiary);
|
|
|
|
assert!(result.is_err());
|
|
let err_str = format!("{:?}", result.err().unwrap());
|
|
assert!(
|
|
err_str.contains("xcm send failure"),
|
|
"Expected xcm send failure error, got {:?}",
|
|
err_str
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pay_reward_fails_on_charge_fees() {
|
|
struct FailingXcmExecutor;
|
|
impl<C> ExecuteXcm<C> for FailingXcmExecutor {
|
|
type Prepared = Weightless;
|
|
fn prepare(_: Xcm<C>, _: Weight) -> Result<Self::Prepared, InstructionError> {
|
|
Err(InstructionError { index: 0, error: XcmError::Unimplemented })
|
|
}
|
|
fn execute(
|
|
_: impl Into<Location>,
|
|
_: Self::Prepared,
|
|
_: &mut XcmHash,
|
|
_: Weight,
|
|
) -> Outcome {
|
|
unreachable!()
|
|
}
|
|
fn charge_fees(_: impl Into<Location>, _: Assets) -> xcm::latest::Result {
|
|
Err(crate::reward::SendError::Fees.into())
|
|
}
|
|
}
|
|
|
|
type FailingExecutorPayAccount = PayAccountOnLocation<
|
|
MockRelayer,
|
|
u128,
|
|
EthereumNetwork,
|
|
AssetHubLocation,
|
|
InboundQueueLocation,
|
|
MockXcmSender,
|
|
FailingXcmExecutor,
|
|
MockCall,
|
|
>;
|
|
|
|
let relayer = MockRelayer(AccountId32::new([3u8; 32]));
|
|
let beneficiary = Location::new(1, Here);
|
|
let reward = 500u128;
|
|
let result = FailingExecutorPayAccount::pay_reward(&relayer, (), reward, beneficiary);
|
|
|
|
assert!(result.is_err());
|
|
let err_str = format!("{:?}", result.err().unwrap());
|
|
assert!(
|
|
err_str.contains("charge fees error"),
|
|
"Expected 'charge fees error', got {:?}",
|
|
err_str
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pay_reward_fails_on_delivery() {
|
|
#[derive(Default)]
|
|
struct FailingDeliveryXcmSender;
|
|
impl SendXcm for FailingDeliveryXcmSender {
|
|
type Ticket = ();
|
|
|
|
fn validate(
|
|
_dest: &mut Option<Location>,
|
|
_xcm: &mut Option<Xcm<()>>,
|
|
) -> SendResult<Self::Ticket> {
|
|
Ok(((), Assets::from(vec![])))
|
|
}
|
|
|
|
fn deliver(_xcm: Self::Ticket) -> core::result::Result<XcmHash, SendError> {
|
|
Err(SendError::NotApplicable)
|
|
}
|
|
}
|
|
|
|
type FailingDeliveryPayAccount = PayAccountOnLocation<
|
|
MockRelayer,
|
|
u128,
|
|
EthereumNetwork,
|
|
AssetHubLocation,
|
|
InboundQueueLocation,
|
|
FailingDeliveryXcmSender,
|
|
MockXcmExecutor,
|
|
MockCall,
|
|
>;
|
|
|
|
let relayer = MockRelayer(AccountId32::new([4u8; 32]));
|
|
let beneficiary = Location::new(1, Here);
|
|
let reward = 123u128;
|
|
let result = FailingDeliveryPayAccount::pay_reward(&relayer, (), reward, beneficiary);
|
|
|
|
assert!(result.is_err());
|
|
let err_str = format!("{:?}", result.err().unwrap());
|
|
assert!(
|
|
err_str.contains("xcm send failure"),
|
|
"Expected 'xcm delivery failure', got {:?}",
|
|
err_str
|
|
);
|
|
}
|
|
}
|