ghost-node/pallets/networks/src/lib.rs
Uncle Stretch c55d9a05d9
auto nullification during finalization and appropriate tests
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-06-03 19:06:50 +03:00

701 lines
23 KiB
Rust

#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::large_enum_variant)]
#![allow(clippy::too_many_arguments)]
use frame_support::{
pallet_prelude::*,
storage::PrefixIterator, traits::{tokens::fungible::Inspect, EnsureOrigin},
};
use frame_system::pallet_prelude::*;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{CheckedSub, CheckedAdd, AtLeast32BitUnsigned, Member},
curve::PiecewiseLinear,
DispatchResult,
};
use sp_std::{prelude::*, convert::TryInto};
pub use ghost_traits::networks::{
NetworkDataBasicHandler, NetworkDataInspectHandler,
NetworkDataMutateHandler,
};
mod weights;
pub use module::*;
pub use crate::weights::WeightInfo;
#[cfg(any(feature = "runtime-benchmarks", test))]
mod benchmarking;
#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;
pub type BalanceOf<T> = <<T as Config>::Currency as Inspect<
<T as frame_system::Config>::AccountId,
>>::Balance;
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum NetworkType {
Evm = 0,
Utxo = 1,
Undefined = 2,
}
impl Default for NetworkType {
fn default() -> Self { NetworkType::Evm }
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct NetworkData {
pub chain_name: Vec<u8>,
pub default_endpoint: Vec<u8>,
pub gatekeeper: Vec<u8>,
pub topic_name: Vec<u8>,
pub finality_delay: Option<u64>,
pub release_delay: Option<u64>,
pub network_type: NetworkType,
pub incoming_fee: u32,
pub outgoing_fee: u32,
}
#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct BridgeAdjustment<Balance> {
pub bridged_out: Balance,
pub bridged_in: Balance,
}
pub struct BridgedInflationCurve<RewardCurve, T>(core::marker::PhantomData<(RewardCurve, T)>);
impl<Balance, RewardCurve, T> pallet_staking::EraPayout<Balance> for BridgedInflationCurve<RewardCurve, T>
where
Balance: Default + AtLeast32BitUnsigned + Clone + Copy + From<u128>,
RewardCurve: Get<&'static PiecewiseLinear<'static>>,
T: Config,
{
fn era_payout(
total_staked: Balance,
total_issuance: Balance,
_era_duration_in_millis: u64,
) -> (Balance, Balance) {
let piecewise_linear = RewardCurve::get();
let bridge_adjustment = BridgedImbalance::<T>::get();
let accumulated_commission = AccumulatedCommission::<T>::get();
let bridged_out: u128 = bridge_adjustment.bridged_out.try_into().unwrap_or_default();
let bridged_in: u128 = bridge_adjustment.bridged_in.try_into().unwrap_or_default();
let accumulated_commission: u128 = accumulated_commission.try_into().unwrap_or_default();
let accumulated_balance = Balance::from(accumulated_commission);
let adjusted_issuance = match bridged_out > bridged_in {
true => total_issuance.saturating_add(Balance::from(bridged_out - bridged_in)),
false => total_issuance.saturating_sub(Balance::from(bridged_in - bridged_out)),
};
NullifyNeeded::<T>::set(true);
match piecewise_linear
.calculate_for_fraction_times_denominator(total_staked, adjusted_issuance)
.checked_mul(&accumulated_balance)
.and_then(|product| product.checked_div(&adjusted_issuance)) {
Some(payout) => (payout, accumulated_balance.saturating_sub(payout)),
None => (Balance::default(), Balance::default()),
}
}
}
#[frame_support::pallet]
pub mod module {
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// The type used for the internal balance storage.
type Currency: Inspect<Self::AccountId>;
/// The type used as a unique network id.
type NetworkId: Parameter
+ Member
+ Parameter
+ AtLeast32BitUnsigned
+ Default
+ Copy
+ Ord
+ TypeInfo
+ MaybeSerializeDeserialize
+ MaxEncodedLen;
/// The origin required to register new network.
type RegisterOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The origin required to update network information.
type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The origin required to remove network.
type RemoveOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Weight information for extrinsics in this module.
type WeightInfo: WeightInfo;
}
#[pallet::error]
pub enum Error<T> {
/// Network already registered.
NetworkAlreadyRegistered,
/// Network does not exist.
NetworkDoesNotExist,
/// Gatekeeper address length not 42 or prefix `0x` missed.
WrongGatekeeperAddress,
/// Topic name length not 66 or prefix `0x` missed.
WrongTopicName,
}
#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
NetworkRegistered { chain_id: T::NetworkId, network: NetworkData },
NetworkNameUpdated { chain_id: T::NetworkId, chain_name: Vec<u8> },
NetworkEndpointUpdated { chain_id: T::NetworkId, default_endpoint: Vec<u8> },
NetworkFinalityDelayUpdated { chain_id: T::NetworkId, finality_delay: Option<u64> },
NetworkReleaseDelayUpdated { chain_id: T::NetworkId, release_delay: Option<u64> },
NetworkTypeUpdated { chain_id: T::NetworkId, network_type: NetworkType },
NetworkGatekeeperUpdated { chain_id: T::NetworkId, gatekeeper: Vec<u8> },
NetworkTopicNameUpdated { chain_id: T::NetworkId, topic_name: Vec<u8> },
NetworkIncomingFeeUpdated { chain_id: T::NetworkId, incoming_fee: u32 },
NetworkOutgoingFeeUpdated { chain_id: T::NetworkId, outgoing_fee: u32 },
NetworkRemoved { chain_id: T::NetworkId },
}
#[pallet::storage]
#[pallet::getter(fn nullify_needed)]
pub type NullifyNeeded<T: Config> = StorageValue<_, bool, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn bridged_imbalance)]
pub type BridgedImbalance<T: Config> =
StorageValue<_, BridgeAdjustment<BalanceOf<T>>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn accumulated_commission)]
pub type AccumulatedCommission<T: Config> =
StorageValue<_, BalanceOf<T>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn networks)]
pub type Networks<T: Config> =
StorageMap<_, Twox64Concat, T::NetworkId, NetworkData, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn gatekeeper_amount)]
pub type GatekeeperAmount<T: Config> = StorageMap<
_,
Twox64Concat,
T::NetworkId,
BalanceOf<T>,
ValueQuery,
>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub networks: Vec<(T::NetworkId, Vec<u8>)>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self { networks: vec![] }
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
if !self.networks.is_empty() {
self.networks.iter().for_each(|(chain_id, network_metadata)| {
let network =
NetworkData::decode(&mut &network_metadata[..])
.expect("Error decoding NetworkData");
Pallet::<T>::do_register_network(chain_id.clone(), network)
.expect("Error registering network");
});
}
}
}
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
T::DbWeight::get().reads_writes(1, 1)
}
fn on_finalize(_: BlockNumberFor<T>) {
if Self::nullify_needed() {
Self::nullify_commission();
NullifyNeeded::<T>::put(false);
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::register_network(
network.chain_name.len() as u32,
network.default_endpoint.len() as u32,
))]
pub fn register_network(
origin: OriginFor<T>,
chain_id: T::NetworkId,
network: NetworkData,
) -> DispatchResult {
T::RegisterOrigin::ensure_origin_or_root(origin)?;
Self::do_register_network(chain_id, network)
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::update_network_name(
chain_name.len() as u32,
))]
pub fn update_network_name(
origin: OriginFor<T>,
chain_id: T::NetworkId,
chain_name: Vec<u8>,
) -> DispatchResult {
T::UpdateOrigin::ensure_origin_or_root(origin)?;
Self::do_update_network_name(
chain_id,
chain_name,
)
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::update_network_endpoint(
default_endpoint.len() as u32
))]
pub fn update_network_endpoint(
origin: OriginFor<T>,
chain_id: T::NetworkId,
default_endpoint: Vec<u8>,
) -> DispatchResult {
T::UpdateOrigin::ensure_origin_or_root(origin)?;
Self::do_update_network_endpoint(
chain_id,
default_endpoint,
)
}
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::update_network_finality_delay())]
pub fn update_network_finality_delay(
origin: OriginFor<T>,
chain_id: T::NetworkId,
finality_delay: Option<u64>,
) -> DispatchResult {
T::UpdateOrigin::ensure_origin_or_root(origin)?;
Self::do_update_network_finality_delay(
chain_id,
finality_delay,
)
}
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::update_network_release_delay())]
pub fn update_network_release_delay(
origin: OriginFor<T>,
chain_id: T::NetworkId,
release_delay: Option<u64>,
) -> DispatchResult {
T::UpdateOrigin::ensure_origin_or_root(origin)?;
Self::do_update_network_release_delay(
chain_id,
release_delay,
)
}
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::update_network_type())]
pub fn update_network_type(
origin: OriginFor<T>,
chain_id: T::NetworkId,
network_type: NetworkType,
) -> DispatchResult {
T::UpdateOrigin::ensure_origin_or_root(origin)?;
Self::do_update_network_type(
chain_id,
network_type,
)
}
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::update_network_gatekeeper())]
pub fn update_network_gatekeeper(
origin: OriginFor<T>,
chain_id: T::NetworkId,
gatekeeper: Vec<u8>,
) -> DispatchResult {
T::UpdateOrigin::ensure_origin_or_root(origin)?;
Self::do_update_network_gatekeeper(
chain_id,
gatekeeper,
)
}
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::update_network_topic_name())]
pub fn update_network_topic_name(
origin: OriginFor<T>,
chain_id: T::NetworkId,
topic_name: Vec<u8>,
) -> DispatchResult {
T::UpdateOrigin::ensure_origin_or_root(origin)?;
Self::do_update_network_topic_name(
chain_id,
topic_name,
)
}
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::update_incoming_network_fee())]
pub fn update_incoming_network_fee(
origin: OriginFor<T>,
chain_id: T::NetworkId,
incoming_fee: u32,
) -> DispatchResult {
T::UpdateOrigin::ensure_origin_or_root(origin)?;
Self::do_update_incoming_network_fee(
chain_id,
incoming_fee,
)
}
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::update_outgoing_network_fee())]
pub fn update_outgoing_network_fee(
origin: OriginFor<T>,
chain_id: T::NetworkId,
outgoing_fee: u32,
) -> DispatchResult {
T::UpdateOrigin::ensure_origin_or_root(origin)?;
Self::do_update_outgoing_network_fee(
chain_id,
outgoing_fee,
)
}
#[pallet::call_index(10)]
#[pallet::weight(T::WeightInfo::remove_network())]
pub fn remove_network(
origin: OriginFor<T>,
chain_id: T::NetworkId,
) -> DispatchResult {
T::RemoveOrigin::ensure_origin_or_root(origin)?;
Self::do_remove_network(chain_id)
}
}
}
impl<T: Config> Pallet<T> {
/// Register a new network.
pub fn do_register_network(
chain_id: T::NetworkId,
network: NetworkData,
) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_none(), Error::<T>::NetworkAlreadyRegistered);
*maybe_network = Some(network.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkRegistered { chain_id, network });
Ok(())
}
/// Remove existent network.
pub fn do_remove_network(chain_id: T::NetworkId) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
*maybe_network = None;
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkRemoved { chain_id });
Ok(())
}
/// Update existent network name.
pub fn do_update_network_name(
chain_id: T::NetworkId,
chain_name: Vec<u8>,
) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
let net = maybe_network.as_mut().unwrap();
net.chain_name = chain_name.clone();
*maybe_network = Some(net.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkNameUpdated {
chain_id,
chain_name,
});
Ok(())
}
/// Update existent network default endpoint.
pub fn do_update_network_endpoint(
chain_id: T::NetworkId,
default_endpoint: Vec<u8>,
) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
let net = maybe_network.as_mut().unwrap();
net.default_endpoint = default_endpoint.clone();
*maybe_network = Some(net.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkEndpointUpdated {
chain_id,
default_endpoint,
});
Ok(())
}
/// Update existent network default endpoint.
pub fn do_update_network_finality_delay(
chain_id: T::NetworkId,
finality_delay: Option<u64>,
) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
let net = maybe_network.as_mut().unwrap();
net.finality_delay = finality_delay;
*maybe_network = Some(net.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkFinalityDelayUpdated {
chain_id,
finality_delay,
});
Ok(())
}
/// Update existent network default endpoint.
pub fn do_update_network_release_delay(
chain_id: T::NetworkId,
release_delay: Option<u64>,
) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
let net = maybe_network.as_mut().unwrap();
net.release_delay = release_delay;
*maybe_network = Some(net.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkReleaseDelayUpdated {
chain_id,
release_delay,
});
Ok(())
}
/// Update existent network type.
pub fn do_update_network_type(
chain_id: T::NetworkId,
network_type: NetworkType,
) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
let net = maybe_network.as_mut().unwrap();
net.network_type = network_type.clone();
*maybe_network = Some(net.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkTypeUpdated {
chain_id,
network_type,
});
Ok(())
}
/// Update existent network gatekeeper.
pub fn do_update_network_gatekeeper(
chain_id: T::NetworkId,
gatekeeper: Vec<u8>,
) -> DispatchResult {
ensure!(gatekeeper.len() == 42 && gatekeeper[0] == 48 && gatekeeper[1] == 120,
Error::<T>::WrongGatekeeperAddress);
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
let net = maybe_network.as_mut().unwrap();
net.gatekeeper = gatekeeper.clone();
*maybe_network = Some(net.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkGatekeeperUpdated {
chain_id,
gatekeeper,
});
Ok(())
}
/// Update existent network gatekeeper's topic name.
pub fn do_update_network_topic_name(
chain_id: T::NetworkId,
topic_name: Vec<u8>,
) -> DispatchResult {
ensure!(topic_name.len() == 66 && topic_name[0] == 48 && topic_name[1] == 120,
Error::<T>::WrongTopicName);
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
let net = maybe_network.as_mut().unwrap();
net.topic_name = topic_name.clone();
*maybe_network = Some(net.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkTopicNameUpdated {
chain_id,
topic_name,
});
Ok(())
}
pub fn do_update_incoming_network_fee(
chain_id: T::NetworkId,
incoming_fee: u32,
) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
let net = maybe_network.as_mut().unwrap();
net.incoming_fee = incoming_fee.clone();
*maybe_network = Some(net.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkIncomingFeeUpdated {
chain_id,
incoming_fee,
});
Ok(())
}
pub fn do_update_outgoing_network_fee(
chain_id: T::NetworkId,
outgoing_fee: u32,
) -> DispatchResult {
Networks::<T>::try_mutate(&chain_id, |maybe_network| -> DispatchResult {
ensure!(maybe_network.is_some(), Error::<T>::NetworkDoesNotExist);
let net = maybe_network.as_mut().unwrap();
net.outgoing_fee = outgoing_fee.clone();
*maybe_network = Some(net.clone());
Ok(())
})?;
Self::deposit_event(Event::<T>::NetworkOutgoingFeeUpdated {
chain_id,
outgoing_fee,
});
Ok(())
}
}
impl<T: Config> NetworkDataBasicHandler for Pallet<T> {
type NetworkId = T::NetworkId;
}
impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
fn get(n: &Self::NetworkId) -> Option<NetworkData> {
Networks::<T>::get(n)
}
fn iter() -> PrefixIterator<(Self::NetworkId, NetworkData)> {
Networks::<T>::iter()
}
fn is_nullification_period() -> bool {
NullifyNeeded::<T>::get()
}
}
impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> {
fn register(chain_id: Self::NetworkId, network: NetworkData) -> DispatchResult {
Self::do_register_network(chain_id, network)
}
fn remove(chain_id: Self::NetworkId) -> DispatchResult {
Self::do_remove_network(chain_id)
}
fn increase_gatekeeper_amount(
network_id: &T::NetworkId,
amount: &BalanceOf<T>,
) -> Result<(BalanceOf<T>, BalanceOf<T>), ()> {
let new_bridged_in_amount = BridgedImbalance::<T>::mutate(|bridged_imbalance| {
match bridged_imbalance.bridged_in.checked_add(amount) {
Some(value) => {
(*bridged_imbalance).bridged_in = value;
Ok(value)
},
None => Err(())
}
})?;
let new_gatekeeper_amount = GatekeeperAmount::<T>::mutate(network_id, |gatekeeper_amount| {
match gatekeeper_amount.checked_add(amount) {
Some(value) => {
*gatekeeper_amount = value;
Ok(value)
},
None => Err(())
}
})?;
Ok((new_gatekeeper_amount, new_bridged_in_amount))
}
fn decrease_gatekeeper_amount(
network_id: &T::NetworkId,
amount: &BalanceOf<T>,
) -> Result<(BalanceOf<T>, BalanceOf<T>), ()> {
let new_gatekeeper_amount = GatekeeperAmount::<T>::mutate(network_id, |gatekeeper_amount| {
match gatekeeper_amount.checked_sub(amount) {
Some(value) => {
*gatekeeper_amount = value;
Ok(value)
},
None => Err(())
}
})?;
let new_bridged_out_amount = BridgedImbalance::<T>::mutate(|bridged_imbalance| {
match bridged_imbalance.bridged_out.checked_add(amount) {
Some(value) => {
(*bridged_imbalance).bridged_out = value;
Ok(value)
},
None => Err(())
}
})?;
Ok((new_gatekeeper_amount, new_bridged_out_amount))
}
fn accumulate_commission(commission: &BalanceOf<T>) -> Result<BalanceOf<T>, ()> {
AccumulatedCommission::<T>::mutate(|accumulated| {
match accumulated.checked_add(commission) {
Some(value) => {
*accumulated = value;
Ok(value)
},
None => Err(()),
}
})
}
fn nullify_commission() {
AccumulatedCommission::<T>::set(Default::default());
}
}