Files
pezkuwi-sdk/pezbridges/pezsnowbridge/pezpallets/system-v2/src/lib.rs
T
pezkuwichain 3ddf58cef9 fix: EnsureOrigin try_successful_origin and snowbridge rename
- Fix pezpallet-welati EnsureOrigin implementations (3 fixes)
  - Remove incorrect #[cfg(not(feature = "runtime-benchmarks"))] blocks
  - Affects EnsureSerok, EnsureParlementer, EnsureDiwan

- Fix asset-hub-zagros governance origins macros (2 fixes)
  - Remove non-benchmark try_successful_origin from decl_unit_ensures!
  - Remove non-benchmark try_successful_origin from decl_ensure!

- Rename snowbridge -> pezsnowbridge for consistency

- Update WORKFLOW_PLAN.md with build status and package names
  - Correct package names: pezkuwi-teyrchain-bin, pezstaging-node-cli
  - Mark completed builds: pezkuwi, pezkuwi-teyrchain-bin,
    pezstaging-node-cli, teyrchain-template-node
2025-12-25 01:26:18 +03:00

323 lines
11 KiB
Rust

// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
//! Governance API for controlling the Ethereum side of the bridge
//!
//! # Extrinsics
//!
//! ## Governance
//!
//! * [`Call::upgrade`]: Upgrade the Gateway contract on Ethereum.
//! * [`Call::set_operating_mode`]: Set the operating mode of the Gateway contract
//!
//! ## Pezkuwi-native tokens on Ethereum
//!
//! Tokens deposited on AssetHub pezpallet can be bridged to Ethereum as wrapped ERC20 tokens. As a
//! prerequisite, the token should be registered first.
//!
//! * [`Call::register_token`]: Register a token location as a wrapped ERC20 contract on Ethereum.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod api;
pub mod weights;
pub use weights::*;
use pezframe_support::{pezpallet_prelude::*, traits::EnsureOrigin};
use pezframe_system::pezpallet_prelude::*;
pub use pezpallet::*;
use pezsnowbridge_core::{
reward::{
AddTip, MessageId,
MessageId::{Inbound, Outbound},
},
AgentIdOf as LocationHashOf, AssetMetadata, TokenId, TokenIdOf,
};
use pezsnowbridge_outbound_queue_primitives::{
v2::{Command, Initializer, Message, SendMessage},
OperatingMode, SendError,
};
use pezsnowbridge_pezpallet_system::ForeignToNativeId;
use pezsp_core::{H160, H256};
use pezsp_io::hashing::blake2_256;
use pezsp_runtime::traits::MaybeConvert;
use pezsp_std::prelude::*;
use xcm::prelude::*;
use xcm_executor::traits::ConvertLocation;
#[cfg(feature = "runtime-benchmarks")]
use pezframe_support::traits::OriginTrait;
pub const LOG_TARGET: &str = "snowbridge-system-v2";
pub type AccountIdOf<T> = <T as pezframe_system::Config>::AccountId;
#[cfg(feature = "runtime-benchmarks")]
pub trait BenchmarkHelper<O>
where
O: OriginTrait,
{
fn make_xcm_origin(location: Location) -> O;
}
#[pezframe_support::pezpallet]
pub mod pezpallet {
use super::*;
#[pezpallet::pezpallet]
pub struct Pezpallet<T>(_);
#[pezpallet::config]
pub trait Config: pezframe_system::Config + pezsnowbridge_pezpallet_system::Config {
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
/// Send messages to Ethereum and add additional relayer rewards if deposited
type OutboundQueue: SendMessage + AddTip;
/// Add to the relayer reward for a specific message
type InboundQueue: AddTip;
/// Origin check for XCM locations that transact with this pezpallet
type FrontendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
/// Origin for governance calls
type GovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Location>;
type WeightInfo: WeightInfo;
#[cfg(feature = "runtime-benchmarks")]
type Helper: BenchmarkHelper<Self::RuntimeOrigin>;
}
#[pezpallet::event]
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// An Upgrade message was sent to the Gateway
Upgrade { impl_address: H160, impl_code_hash: H256, initializer_params_hash: H256 },
/// An SetOperatingMode message was sent to the Gateway
SetOperatingMode { mode: OperatingMode },
/// Register Pezkuwi-native token as a wrapped ERC20 token on Ethereum
RegisterToken {
/// Location of Pezkuwi-native token
location: VersionedLocation,
/// ID of Pezkuwi-native token on Ethereum
foreign_token_id: H256,
},
/// A tip was processed for an inbound or outbound message, for relayer incentivization.
/// It could have succeeded or failed (and then added to LostTips).
TipProcessed {
/// The original sender of the tip (who deposited the funds).
sender: AccountIdOf<T>,
/// The Inbound/Outbound message nonce
message_id: MessageId,
/// The tip amount in ether.
amount: u128,
/// Whether the tip was added successfully. If the tip was added for a nonce
/// that was already consumed, the tip will be added to LostTips.
success: bool,
},
}
#[pezpallet::error]
pub enum Error<T> {
/// Location could not be reachored
LocationReanchorFailed,
/// A token location could not be converted to a token ID.
LocationConversionFailed,
/// A `VersionedLocation` could not be converted into a `Location`.
UnsupportedLocationVersion,
/// An XCM could not be sent, due to a `SendError`.
Send(SendError),
/// The gateway contract upgrade message could not be sent due to invalid upgrade
/// parameters.
InvalidUpgradeParameters,
}
/// Relayer reward tips that were paid by the user to incentivize the processing of their
/// message, but then could not be added to their message reward (e.g. the nonce was already
/// processed or their order could not be found). Capturing the lost tips here supports
/// implementing a recovery method in the future.
#[pezpallet::storage]
pub type LostTips<T: Config> =
StorageMap<_, Blake2_128Concat, AccountIdOf<T>, u128, ValueQuery>;
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
/// Sends command to the Gateway contract to upgrade itself with a new implementation
/// contract
///
/// Fee required: No
///
/// - `origin`: Must be `Root`.
/// - `impl_address`: The address of the implementation contract.
/// - `impl_code_hash`: The codehash of the implementation contract.
/// - `initializer`: Optionally call an initializer on the implementation contract.
#[pezpallet::call_index(0)]
#[pezpallet::weight((<T as pezpallet::Config>::WeightInfo::upgrade(), DispatchClass::Operational))]
pub fn upgrade(
origin: OriginFor<T>,
impl_address: H160,
impl_code_hash: H256,
initializer: Initializer,
) -> DispatchResult {
let origin_location = T::GovernanceOrigin::ensure_origin(origin)?;
let origin = Self::location_to_message_origin(origin_location)?;
ensure!(
!impl_address.eq(&H160::zero()) && !impl_code_hash.eq(&H256::zero()),
Error::<T>::InvalidUpgradeParameters
);
let initializer_params_hash: H256 = blake2_256(initializer.params.as_ref()).into();
let command = Command::Upgrade { impl_address, impl_code_hash, initializer };
Self::send(origin, command, 0)?;
Self::deposit_event(Event::<T>::Upgrade {
impl_address,
impl_code_hash,
initializer_params_hash,
});
Ok(())
}
/// Sends a message to the Gateway contract to change its operating mode
///
/// Fee required: No
///
/// - `origin`: Must be `GovernanceOrigin`
#[pezpallet::call_index(1)]
#[pezpallet::weight((<T as pezpallet::Config>::WeightInfo::set_operating_mode(), DispatchClass::Operational))]
pub fn set_operating_mode(origin: OriginFor<T>, mode: OperatingMode) -> DispatchResult {
let origin_location = T::GovernanceOrigin::ensure_origin(origin)?;
let origin = Self::location_to_message_origin(origin_location)?;
let command = Command::SetOperatingMode { mode };
Self::send(origin, command, 0)?;
Self::deposit_event(Event::<T>::SetOperatingMode { mode });
Ok(())
}
/// Registers a Pezkuwi-native token as a wrapped ERC20 token on Ethereum.
///
/// The system frontend pezpallet on AH proxies this call to BH.
///
/// - `sender`: The original sender initiating the call on AH
/// - `asset_id`: Location of the asset (relative to this chain)
/// - `metadata`: Metadata to include in the instantiated ERC20 contract on Ethereum
#[pezpallet::call_index(2)]
#[pezpallet::weight(<T as pezpallet::Config>::WeightInfo::register_token())]
pub fn register_token(
origin: OriginFor<T>,
sender: Box<VersionedLocation>,
asset_id: Box<VersionedLocation>,
metadata: AssetMetadata,
amount: u128,
) -> DispatchResult {
T::FrontendOrigin::ensure_origin(origin)?;
let sender_location: Location =
(*sender).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
let asset_location: Location =
(*asset_id).try_into().map_err(|_| Error::<T>::UnsupportedLocationVersion)?;
let location = Self::reanchor(asset_location)?;
let token_id = TokenIdOf::convert_location(&location)
.ok_or(Error::<T>::LocationConversionFailed)?;
if !ForeignToNativeId::<T>::contains_key(token_id) {
ForeignToNativeId::<T>::insert(token_id, location.clone());
}
let command = Command::RegisterForeignToken {
token_id,
name: metadata.name.into_inner(),
symbol: metadata.symbol.into_inner(),
decimals: metadata.decimals,
};
let message_origin = Self::location_to_message_origin(sender_location)?;
Self::send(message_origin, command, amount)?;
Self::deposit_event(Event::<T>::RegisterToken {
location: location.into(),
foreign_token_id: token_id,
});
Ok(())
}
#[pezpallet::call_index(3)]
#[pezpallet::weight(<T as pezpallet::Config>::WeightInfo::add_tip())]
pub fn add_tip(
origin: OriginFor<T>,
sender: AccountIdOf<T>,
message_id: MessageId,
amount: u128,
) -> DispatchResult {
T::FrontendOrigin::ensure_origin(origin)?;
let result = match message_id {
Inbound(nonce) => <T as pezpallet::Config>::InboundQueue::add_tip(nonce, amount),
Outbound(nonce) => <T as pezpallet::Config>::OutboundQueue::add_tip(nonce, amount),
};
if let Err(ref e) = result {
tracing::debug!(target: LOG_TARGET, ?e, ?message_id, ?amount, "error adding tip");
LostTips::<T>::mutate(&sender, |lost_tip| {
*lost_tip = lost_tip.saturating_add(amount);
});
}
Self::deposit_event(Event::<T>::TipProcessed {
sender,
message_id,
amount,
success: result.is_ok(),
});
Ok(())
}
}
impl<T: Config> Pezpallet<T> {
/// Send `command` to the Gateway from a specific origin/agent
fn send(origin: H256, command: Command, fee: u128) -> DispatchResult {
let message = Message {
origin,
id: pezframe_system::unique((origin, &command, fee)).into(),
fee,
commands: BoundedVec::try_from(vec![command]).unwrap(),
};
let ticket = <T as pezpallet::Config>::OutboundQueue::validate(&message)
.map_err(|err| Error::<T>::Send(err))?;
<T as pezpallet::Config>::OutboundQueue::deliver(ticket)
.map_err(|err| Error::<T>::Send(err))?;
Ok(())
}
/// Reanchor the `location` in context of ethereum
pub fn reanchor(location: Location) -> Result<Location, Error<T>> {
location
.reanchored(&T::EthereumLocation::get(), &T::UniversalLocation::get())
.map_err(|_| Error::<T>::LocationReanchorFailed)
}
pub fn location_to_message_origin(location: Location) -> Result<H256, Error<T>> {
let reanchored_location = Self::reanchor(location)?;
LocationHashOf::convert_location(&reanchored_location)
.ok_or(Error::<T>::LocationConversionFailed)
}
}
impl<T: Config> MaybeConvert<TokenId, Location> for Pezpallet<T> {
fn maybe_convert(foreign_id: TokenId) -> Option<Location> {
pezsnowbridge_pezpallet_system::Pezpallet::<T>::maybe_convert(foreign_id)
}
}
}