Skip to content
This repository was archived by the owner on Apr 25, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions node/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub fn create_benchmark_extrinsic(
frame_system::CheckWeight::<runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0),
frame_metadata_hash_extension::CheckMetadataHash::<runtime::Runtime>::new(false),
tangle_runtime::extension::CheckNominatedRestaked::<runtime::Runtime>::new(),
);

let raw_payload = runtime::SignedPayload::from_raw(
Expand All @@ -137,6 +138,7 @@ pub fn create_benchmark_extrinsic(
(),
(),
None,
(),
),
);
let signature = raw_payload.using_encoded(|e| sender.sign(e));
Expand Down
6 changes: 3 additions & 3 deletions node/tests/evm_restaking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ where
new_config:
api::runtime_types::pallet_rewards::types::RewardConfigForAssetVault {
apy: api::runtime_types::sp_arithmetic::per_things::Perbill(
MOCK_APY * 10000,
MOCK_APY * 1000000,
), // convert percent to perbill
deposit_cap: MOCK_DEPOSIT_CAP,
incentive_cap: 1,
Expand Down Expand Up @@ -1281,7 +1281,7 @@ fn mad_rewards() {
let bob_new_balance = bob_balance.data.free;
assert!(bob_new_balance > bob_original_balance);
let change_in_bob_balance = bob_new_balance - bob_original_balance;
assert!(change_in_bob_balance <= original_user_rewards); // account for some fee loss
assert!(change_in_bob_balance >= original_user_rewards); // account for some fee loss

// finally lets check that the rewards claimed are not shown again in rpc
let user_rewards = t.subxt.runtime_api().at_latest().await?.call(rewards_addr).await?;
Expand Down Expand Up @@ -1404,7 +1404,7 @@ fn lrt_rewards_erc20() {
let bob = TestAccount::Bob;
let bob_provider = alloy_provider_with_wallet(&t.provider, bob.evm_wallet());
// Mint WETH for Bob
let weth_amount = U256::from(200 * EIGHTEEN_DECIMALS);
let weth_amount = U256::from(MOCK_DEPOSIT * 2);
let weth = MockERC20::new(t.weth, &bob_provider);
weth.mint(bob.address(), weth_amount).send().await?.get_receipt().await?;
info!("Minted {} WETH for Bob", format_ether(weth_amount));
Expand Down
132 changes: 132 additions & 0 deletions runtime/mainnet/src/extension.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// This file is part of Tangle.
// Copyright (C) 2022-2024 Tangle Foundation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Runtime extension implementations for mainnet.

use frame_support::pallet_prelude::*;
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_runtime::traits::{DispatchInfoOf, SignedExtension};

use crate::Balance;
use crate::Runtime;

/// Extension that checks for nominated tokens that are being restaked.
/// Prevents unbonding when tokens are delegated through the multi-asset-delegation system.
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct CheckNominatedRestaked<T>(core::marker::PhantomData<T>);

impl<T> sp_std::fmt::Debug for CheckNominatedRestaked<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "CheckNominatedRestaked")
}

#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}

impl<T> CheckNominatedRestaked<T> {
pub fn new() -> Self {
CheckNominatedRestaked(core::marker::PhantomData)
}
}

impl CheckNominatedRestaked<Runtime> {
/// Checks if unbonding is allowed based on delegated nominations
pub fn can_unbound(
who: &<Runtime as frame_system::Config>::AccountId,
amount: Balance,
) -> bool {
pallet_multi_asset_delegation::Pallet::<Runtime>::can_unbound(who, amount)
}
}

impl<T> Default for CheckNominatedRestaked<T> {
fn default() -> Self {
CheckNominatedRestaked(core::marker::PhantomData)
}
}

impl SignedExtension for CheckNominatedRestaked<Runtime> {
const IDENTIFIER: &'static str = "CheckNominatedRestaked";

type AccountId = <Runtime as frame_system::Config>::AccountId;

type Call = <Runtime as frame_system::Config>::RuntimeCall;

type AdditionalSigned = ();

type Pre = ();

fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(())
}

fn validate(
&self,
who: &Self::AccountId,
call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> TransactionValidity {
use crate::RuntimeCall;

match call {
// Match on Staking unbond calls
RuntimeCall::Staking(pallet_staking::Call::unbond { value }) => {
if Self::can_unbound(who, *value) {
Ok(ValidTransaction::default())
} else {
Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(1)))
}
},
// Match on Proxy calls
RuntimeCall::Proxy(pallet_proxy::Call::proxy { ref call, real, .. }) => {
// Convert MultiAddress to AccountId
if let sp_runtime::MultiAddress::Id(account_id) = real {
self.validate(account_id, call, _info, _len)
} else {
// If not an Id type, we allow it by default
Ok(ValidTransaction::default())
}
},
// Match on various Utility batch calls
RuntimeCall::Utility(pallet_utility::Call::batch { ref calls })
| RuntimeCall::Utility(pallet_utility::Call::batch_all { ref calls })
| RuntimeCall::Utility(pallet_utility::Call::force_batch { ref calls }) => {
for call in calls {
self.validate(who, call, _info, _len)?;
}
Ok(ValidTransaction::default())
},
// Default case for all other calls
_ => Ok(ValidTransaction::default()),
}
}

fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
self.validate(who, call, info, len).map(|_| ())
}
}
3 changes: 3 additions & 0 deletions runtime/mainnet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));

pub mod extension;
mod filters;
pub mod frontier_evm;
pub mod impls;
Expand Down Expand Up @@ -825,6 +826,7 @@ where
frame_system::CheckWeight::<Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(true),
extension::CheckNominatedRestaked::<Runtime>::new(),
);
let raw_payload = SignedPayload::new(call, extra)
.map_err(|e| {
Expand Down Expand Up @@ -1457,6 +1459,7 @@ pub type SignedExtra = (
frame_system::CheckWeight<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
frame_metadata_hash_extension::CheckMetadataHash<Runtime>,
extension::CheckNominatedRestaked<Runtime>,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
Expand Down
132 changes: 132 additions & 0 deletions runtime/testnet/src/extension.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// This file is part of Tangle.
// Copyright (C) 2022-2024 Tangle Foundation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Runtime extension implementations for testnet.

use frame_support::pallet_prelude::*;
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_runtime::traits::{DispatchInfoOf, SignedExtension};

use crate::Balance;
use crate::Runtime;

/// Extension that checks for nominated tokens that are being restaked.
/// Prevents unbonding when tokens are delegated through the multi-asset-delegation system.
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct CheckNominatedRestaked<T>(core::marker::PhantomData<T>);

impl<T> sp_std::fmt::Debug for CheckNominatedRestaked<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "CheckNominatedRestaked")
}

#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}

impl<T> CheckNominatedRestaked<T> {
pub fn new() -> Self {
CheckNominatedRestaked(core::marker::PhantomData)
}
}

impl CheckNominatedRestaked<Runtime> {
/// Checks if unbonding is allowed based on delegated nominations
pub fn can_unbound(
who: &<Runtime as frame_system::Config>::AccountId,
amount: Balance,
) -> bool {
pallet_multi_asset_delegation::Pallet::<Runtime>::can_unbound(who, amount)
}
}

impl<T> Default for CheckNominatedRestaked<T> {
fn default() -> Self {
CheckNominatedRestaked(core::marker::PhantomData)
}
}

impl SignedExtension for CheckNominatedRestaked<Runtime> {
const IDENTIFIER: &'static str = "CheckNominatedRestaked";

type AccountId = <Runtime as frame_system::Config>::AccountId;

type Call = <Runtime as frame_system::Config>::RuntimeCall;

type AdditionalSigned = ();

type Pre = ();

fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(())
}

fn validate(
&self,
who: &Self::AccountId,
call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> TransactionValidity {
use crate::RuntimeCall;

match call {
// Match on Staking unbond calls
RuntimeCall::Staking(pallet_staking::Call::unbond { value }) => {
if Self::can_unbound(who, *value) {
Ok(ValidTransaction::default())
} else {
Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(1)))
}
},
// Match on Proxy calls
RuntimeCall::Proxy(pallet_proxy::Call::proxy { ref call, real, .. }) => {
// Convert MultiAddress to AccountId
if let sp_runtime::MultiAddress::Id(account_id) = real {
self.validate(account_id, call, _info, _len)
} else {
// If not an Id type, we allow it by default
Ok(ValidTransaction::default())
}
},
// Match on various Utility batch calls
RuntimeCall::Utility(pallet_utility::Call::batch { ref calls })
| RuntimeCall::Utility(pallet_utility::Call::batch_all { ref calls })
| RuntimeCall::Utility(pallet_utility::Call::force_batch { ref calls }) => {
for call in calls {
self.validate(who, call, _info, _len)?;
}
Ok(ValidTransaction::default())
},
// Default case for all other calls
_ => Ok(ValidTransaction::default()),
}
}

fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
self.validate(who, call, info, len).map(|_| ())
}
}
3 changes: 3 additions & 0 deletions runtime/testnet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));

pub mod extension;
mod filters;
pub mod frontier_evm;
pub mod hyperbridge;
Expand Down Expand Up @@ -835,6 +836,7 @@ where
frame_system::CheckWeight::<Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(true),
extension::CheckNominatedRestaked::<Runtime>::new(),
);
let raw_payload = SignedPayload::new(call, extra)
.map_err(|e| {
Expand Down Expand Up @@ -1365,6 +1367,7 @@ pub type SignedExtra = (
frame_system::CheckWeight<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
frame_metadata_hash_extension::CheckMetadataHash<Runtime>,
extension::CheckNominatedRestaked<Runtime>,
);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
Expand Down