diff --git a/Cargo.lock b/Cargo.lock index b4b365008c2df..7ec02a35a8ff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1766,6 +1766,7 @@ dependencies = [ "pallet-utility", "pallet-verify-signature", "pallet-vesting", + "pallet-vesting-precompiles", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", @@ -14772,6 +14773,25 @@ dependencies = [ "sp-runtime 31.0.1", ] +[[package]] +name = "pallet-vesting-precompiles" +version = "0.1.0" +dependencies = [ + "alloy-core", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-revive", + "pallet-timestamp", + "pallet-vesting", + "parity-scale-codec", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", +] + [[package]] name = "pallet-whitelist" version = "27.0.0" @@ -17135,6 +17155,7 @@ dependencies = [ "pallet-utility", "pallet-verify-signature", "pallet-vesting", + "pallet-vesting-precompiles", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", diff --git a/Cargo.toml b/Cargo.toml index e17595035d744..5b5eee75f2de0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -479,6 +479,7 @@ members = [ "substrate/frame/utility", "substrate/frame/verify-signature", "substrate/frame/vesting", + "substrate/frame/vesting/precompiles", "substrate/frame/whitelist", "substrate/primitives/api", "substrate/primitives/api/proc-macro", @@ -1096,6 +1097,7 @@ pallet-uniques = { path = "substrate/frame/uniques", default-features = false } pallet-utility = { path = "substrate/frame/utility", default-features = false } pallet-verify-signature = { path = "substrate/frame/verify-signature", default-features = false } pallet-vesting = { path = "substrate/frame/vesting", default-features = false } +pallet-vesting-precompiles = { path = "substrate/frame/vesting/precompiles", default-features = false } pallet-whitelist = { path = "substrate/frame/whitelist", default-features = false } pallet-xcm = { path = "polkadot/xcm/pallet-xcm", default-features = false } pallet-xcm-benchmarks = { path = "polkadot/xcm/pallet-xcm-benchmarks", default-features = false } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index abaf631e388c4..3b932414e964f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -24,7 +24,7 @@ frame-benchmarking = { optional = true, workspace = true } frame-election-provider-support = { workspace = true } frame-executive = { workspace = true } frame-metadata-hash-extension = { workspace = true } -frame-support = { workspace = true } +frame-support = { features = ["tuples-96"], workspace = true } frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } @@ -76,6 +76,7 @@ pallet-uniques = { workspace = true } pallet-utility = { workspace = true } pallet-verify-signature = { workspace = true } pallet-vesting = { workspace = true } +pallet-vesting-precompiles = { workspace = true } pallet-whitelist = { workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } @@ -217,6 +218,7 @@ runtime-benchmarks = [ "pallet-uniques/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-verify-signature/runtime-benchmarks", + "pallet-vesting-precompiles/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", "pallet-whitelist/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", @@ -296,6 +298,7 @@ try-runtime = [ "pallet-uniques/try-runtime", "pallet-utility/try-runtime", "pallet-verify-signature/try-runtime", + "pallet-vesting-precompiles/try-runtime", "pallet-vesting/try-runtime", "pallet-whitelist/try-runtime", "pallet-xcm-bridge-hub-router/try-runtime", @@ -385,6 +388,7 @@ std = [ "pallet-uniques/std", "pallet-utility/std", "pallet-verify-signature/std", + "pallet-vesting-precompiles/std", "pallet-vesting/std", "pallet-whitelist/std", "pallet-xcm-benchmarks?/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 7a0fad900af84..cbde7f6a1d9e6 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -75,6 +75,7 @@ use pallet_assets_precompiles::{ForeignAssetId, ForeignIdConfig, InlineIdConfig, use pallet_nfts::{DestroyWitness, PalletFeatures}; use pallet_nomination_pools::PoolId; use pallet_revive::evm::runtime::EthExtra; +use pallet_vesting_precompiles::Vesting as VestingPrecompile; use pallet_xcm::EnsureXcm; use pallet_xcm_precompiles::XcmPrecompile; use parachains_common::{ @@ -1261,6 +1262,7 @@ impl pallet_revive::Config for Runtime { ERC20, PoolAssetsInstance>, ERC20, ForeignAssetsInstance>, XcmPrecompile, + VestingPrecompile, ); type AddressMapper = pallet_revive::AccountId32Mapper; type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; @@ -1280,6 +1282,10 @@ impl pallet_revive::Config for Runtime { type OnBurn = Dap; } +impl pallet_vesting_precompiles::pallet::Config for Runtime { + type WeightInfo = pallet_vesting_precompiles::weights::SubstrateWeight; +} + parameter_types! { pub MbmServiceWeight: Weight = Perbill::from_percent(80) * RuntimeBlockWeights::get().max_block; pub FastUnstakeName: &'static str = "FastUnstake"; @@ -1474,6 +1480,7 @@ construct_runtime!( AssetRewards: pallet_asset_rewards = 61, AssetsPrecompiles: pallet_assets_precompiles::pallet = 62, AssetsPrecompilesPermit: pallet_assets_precompiles::permit::pallet = 63, + VestingPrecompiles: pallet_vesting_precompiles::pallet = 64, StateTrieMigration: pallet_state_trie_migration = 70, @@ -1848,6 +1855,7 @@ mod benches { [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_treasury, Treasury] [pallet_vesting, Vesting] + [pallet_vesting_precompiles, VestingPrecompiles] [pallet_whitelist, Whitelist] [pallet_xcm_bridge_hub_router, ToRococo] [pallet_asset_conversion_ops, AssetConversionMigration] diff --git a/prdoc/pr_11398.prdoc b/prdoc/pr_11398.prdoc new file mode 100644 index 0000000000000..99c6c2500f470 --- /dev/null +++ b/prdoc/pr_11398.prdoc @@ -0,0 +1,26 @@ +title: '[pallet-revive] Add vesting precompile' +doc: +- audience: Runtime Dev + description: |- + ## Summary + + - Add a new built-in precompile exposing Substrate's vesting pallet to EVM contracts + - Implement `IVesting.sol` Solidity interface with methods for `vest`, `vestOther`, and `vestingBalance` + - Wire up the precompile in pallet-revive's builtin precompile registry and execution context + + ## Changed files + - **`IVesting.sol`** / **`precompiles/vesting.rs`**: Solidity interface and Rust implementation for vesting operations + - **`precompiles/builtin.rs`** / **`precompiles.rs`**: Register the new vesting precompile + - **`exec.rs`**: Expose vesting functionality to the execution context + - **`tests.rs`**: Add tests for the vesting precompile + + ## Test plan + - [ ] New vesting precompile tests pass (`vest`, `vestOther`, `vestingBalance`) + - [ ] Existing pallet-revive tests unaffected +crates: +- name: pallet-vesting-precompiles + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 835b56f82394b..cf6e02e69b680 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -93,6 +93,7 @@ use pallet_session::historical as pallet_session_historical; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; pub use pallet_transaction_payment::{FungibleAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_tx_pause::RuntimeCallNameOf; +use pallet_vesting_precompiles::Vesting as VestingPrecompile; use sp_api::impl_runtime_apis; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_beefy::{ @@ -1550,8 +1551,11 @@ impl pallet_revive::Config for Runtime { type DepositPerChildTrieItem = DepositPerChildTrieItem; type DepositPerByte = DepositPerByte; type WeightInfo = pallet_revive::weights::SubstrateWeight; - type Precompiles = - (ERC20, Instance1>, ERC20, Instance2>); + type Precompiles = ( + ERC20, Instance1>, + ERC20, Instance2>, + VestingPrecompile, + ); type AddressMapper = pallet_revive::AccountId32Mapper; type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; @@ -1570,6 +1574,10 @@ impl pallet_revive::Config for Runtime { type OnBurn = (); } +impl pallet_vesting_precompiles::pallet::Config for Runtime { + type WeightInfo = pallet_vesting_precompiles::weights::SubstrateWeight; +} + impl pallet_sudo::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -2892,6 +2900,9 @@ mod runtime { #[runtime::pallet_index(92)] pub type AssetsPrecompilesPermit = pallet_assets_precompiles::permit::pallet::Pallet; + + #[runtime::pallet_index(93)] + pub type VestingPrecompiles = pallet_vesting_precompiles::pallet::Pallet; } /// The address format for describing accounts. @@ -3171,6 +3182,7 @@ mod benches { [pallet_mmr, Mmr] [pallet_multi_asset_bounties, MultiAssetBounties] [pallet_assets_precompiles, AssetsPrecompiles] + [pallet_vesting_precompiles, VestingPrecompiles] [pallet_multisig, Multisig] [pallet_nomination_pools, NominationPoolsBench::] [pallet_offences, OffencesBench::] diff --git a/substrate/frame/revive/src/call_builder.rs b/substrate/frame/revive/src/call_builder.rs index d92eb32877295..9176e669b1b49 100644 --- a/substrate/frame/revive/src/call_builder.rs +++ b/substrate/frame/revive/src/call_builder.rs @@ -55,6 +55,8 @@ pub struct CallSetup { data: Vec, transient_storage_size: u32, exec_config: ExecConfig, + read_only: bool, + delegate_call: bool, } impl Default for CallSetup @@ -106,6 +108,8 @@ where data: vec![], transient_storage_size: 0, exec_config: ExecConfig::new_substrate_tx(), + read_only: false, + delegate_call: false, } } @@ -138,6 +142,16 @@ where self.transient_storage_size = size; } + /// Set the read-only flag on the call stack frame. + pub fn set_read_only(&mut self, read_only: bool) { + self.read_only = read_only; + } + + /// Mark the call as a delegate call. + pub fn set_delegate_call(&mut self, delegate: bool) { + self.delegate_call = delegate; + } + /// Get the call's input data. pub fn data(&self) -> Vec { self.data.clone() @@ -156,6 +170,8 @@ where &mut self.transaction_meter, self.value, &self.exec_config, + self.read_only, + self.delegate_call, ); if self.transient_storage_size > 0 { Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap(); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 951ca473bd972..f26735ee28d00 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -954,6 +954,8 @@ where transaction_meter: &'a mut TransactionMeter, value: BalanceOf, exec_config: &'a ExecConfig, + read_only: bool, + delegate_call: bool, ) -> (Self, E) { let call = Self::new( FrameArgs::Call { @@ -969,7 +971,18 @@ where ) .unwrap() .unwrap(); - (call.0, call.1.into_executable().unwrap()) + let mut stack = call.0; + if read_only { + stack.top_frame_mut().read_only = true; + } + if delegate_call { + let frame = stack.top_frame_mut(); + frame.delegate = Some(DelegateInfo { + caller: Origin::from_account_id(frame.account_id.clone()), + callee: H160::zero(), + }); + } + (stack, call.1.into_executable().unwrap()) } /// Create a new call stack. diff --git a/substrate/frame/vesting/precompiles/Cargo.toml b/substrate/frame/vesting/precompiles/Cargo.toml new file mode 100644 index 0000000000000..3e5d1db39cf3e --- /dev/null +++ b/substrate/frame/vesting/precompiles/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "pallet-vesting-precompiles" +version = "0.1.0" +authors.workspace = true +edition = "2024" +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "Vesting precompile exposing pallet-vesting to EVM contracts via pallet-revive." + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +alloy-core = { workspace = true, features = ["sol-types"] } +codec = { workspace = true } +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { workspace = true } +pallet-revive = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-vesting = { workspace = true } +scale-info = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } + +[features] +default = ["std"] +std = [ + "alloy-core/std", + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-revive/std", + "pallet-timestamp/std", + "pallet-vesting/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-revive/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-vesting/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-revive/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-vesting/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/vesting/precompiles/IVesting.sol b/substrate/frame/vesting/precompiles/IVesting.sol new file mode 100644 index 0000000000000..dae8d453b7359 --- /dev/null +++ b/substrate/frame/vesting/precompiles/IVesting.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant VESTING_ADDR = 0x0000000000000000000000000000000000000902; + +interface IVesting { + /// Unlock any vested funds of the caller account. + /// + /// The caller must have funds still locked under the vesting pallet. + /// On success the vesting lock is reduced in line with the amount "vested" so far. + /// + /// Reverts if the caller has no vesting schedule or if the origin is not signed. + function vest() external; + + /// Unlock any vested funds of another account. + /// + /// The `target` account must have funds still locked under the vesting pallet. + /// On success the vesting lock is reduced in line with the amount "vested" so far. + /// The caller pays the fee but the vesting schedule of `target` is updated. + /// + /// Reverts if `target` has no vesting schedule or if the origin is not signed. + function vestOther(address target) external; + + /// Returns the amount of funds still locked (to be vested) for the caller. + /// + /// The returned value is in native (Substrate) denomination. + /// Returns 0 in two cases: the caller has no vesting schedule, or the caller + /// has a schedule but all funds are already unlocked (fully vested). Both cases + /// mean there is nothing left to vest; calling vest() in either case will revert. + function vestingBalance() external view returns (uint256); + + /// Returns the amount of funds still locked (to be vested) for `target`. + /// + /// Identical semantics to vestingBalance() but queries an arbitrary account + /// rather than the caller. Useful for contracts that need to pre-check whether + /// vestOther(target) would do any work before dispatching it. + /// + /// The returned value is in native (Substrate) denomination. + /// Returns 0 if `target` has no vesting schedule or is fully vested. + function vestingBalanceOf(address target) external view returns (uint256); +} diff --git a/substrate/frame/vesting/precompiles/src/benchmarking.rs b/substrate/frame/vesting/precompiles/src/benchmarking.rs new file mode 100644 index 0000000000000..84fbdfedb0e3b --- /dev/null +++ b/substrate/frame/vesting/precompiles/src/benchmarking.rs @@ -0,0 +1,136 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Benchmarks for `pallet-vesting-precompiles`. +//! +//! Only the view functions (`vestingBalance`, `vestingBalanceOf`) are benchmarked here. +//! The mutating calls (`vest`, `vestOther`) delegate to `pallet-vesting` dispatchables +//! and charge the pallet's own benchmarked dispatch weight via +//! `get_dispatch_info().call_weight`, so no separate benchmark is needed. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::{ + IVesting, Vesting, VestingBalance, + pallet::{Config, Pallet}, +}; +use alloy_core::sol_types::SolValue; +use frame_benchmarking::v2::*; +use frame_support::traits::{Get, VestingSchedule, fungible::Mutate}; +use pallet_revive::{ + AddressMapper, + precompiles::{Precompile, U256}, +}; +use sp_runtime::traits::Zero; + +type FungibleOf = ::Currency; + +/// Set up a vesting schedule for `who`. +/// +/// Uses `MinVestedTransfer` as the locked amount to satisfy runtime constraints, +/// and funds the account with enough balance to cover the lock. +fn setup_vesting(who: &T::AccountId) -> VestingBalance +where + VestingBalance: Into, + VestingBalance: From<::Balance>, + ::Balance: From>, +{ + let locked = T::MinVestedTransfer::get(); + // Fund the account with 10x the locked amount. + let fund_amount: U256 = (locked * 10u32.into()).into(); + let balance = fund_amount.try_into().ok().expect("balance fits"); + FungibleOf::::set_balance(who, balance); + + let per_block = (locked / 20u32.into()).max(1u32.into()); + let starting_block = Zero::zero(); + as VestingSchedule>::add_vesting_schedule( + who, + locked, + per_block, + starting_block, + ) + .expect("adding vesting schedule should succeed"); + + locked +} + +#[benchmarks( + where + VestingBalance: Into, + VestingBalance: From<::Balance>, + ::Balance: From>, +)] +mod benchmarks { + use super::*; + fn precompile_address() -> [u8; 20] + where + VestingBalance: Into, + VestingBalance: From<::Balance>, + ::Balance: From>, + { + Vesting::::MATCHER.base_address() + } + + /// Benchmark `vestingBalance()`: query locked balance for the caller (with schedule). + #[benchmark] + fn vesting_balance() { + let mut call_setup = pallet_revive::call_builder::CallSetup::::default(); + let caller_account = call_setup.contract().caller.clone(); + + setup_vesting::(&caller_account); + + let input = IVesting::IVestingCalls::vestingBalance(IVesting::vestingBalanceCall {}); + let address = precompile_address::(); + let (mut ext, _) = call_setup.ext(); + + let result; + #[block] + { + result = Vesting::::call(&address, &input, &mut ext); + } + let raw_data = result.unwrap(); + let balance = U256::from_big_endian(&<[u8; 32]>::abi_decode(&raw_data).unwrap()); + assert!(balance > U256::zero(), "locked balance should be non-zero"); + } + + /// Benchmark `vestingBalanceOf(target)`: query locked balance for another account. + #[benchmark] + fn vesting_balance_of() { + let mut call_setup = pallet_revive::call_builder::CallSetup::::default(); + + let target_addr = pallet_revive::precompiles::H160::from_low_u64_be(0xBEEF); + let target_account = T::AddressMapper::to_account_id(&target_addr); + setup_vesting::(&target_account); + + let input = IVesting::IVestingCalls::vestingBalanceOf(IVesting::vestingBalanceOfCall { + target: alloy_core::primitives::Address::from_slice(target_addr.as_bytes()), + }); + let address = precompile_address::(); + let (mut ext, _) = call_setup.ext(); + + let result; + #[block] + { + result = Vesting::::call(&address, &input, &mut ext); + } + let raw_data = result.unwrap(); + let balance = U256::from_big_endian(&<[u8; 32]>::abi_decode(&raw_data).unwrap()); + assert!(balance > U256::zero(), "locked balance should be non-zero"); + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/substrate/frame/vesting/precompiles/src/lib.rs b/substrate/frame/vesting/precompiles/src/lib.rs new file mode 100644 index 0000000000000..17edbb94dc26c --- /dev/null +++ b/substrate/frame/vesting/precompiles/src/lib.rs @@ -0,0 +1,183 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use alloy_core::sol_types::SolValue; +use core::{marker::PhantomData, num::NonZero}; +use frame_support::{dispatch::GetDispatchInfo, traits::VestingSchedule}; +use pallet_revive::{ + Config, + precompiles::{AddressMatcher, Error, Ext, H160, Precompile, RuntimeCosts, U256}, +}; +use sp_runtime::traits::StaticLookup; + +alloy_core::sol!("IVesting.sol"); + +pub use pallet::Pallet; +pub mod weights; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; + +#[cfg(all(test, feature = "runtime-benchmarks"))] +pub mod mock; + +#[cfg(all(test, feature = "runtime-benchmarks"))] +mod tests; + +fn ensure_mutable(env: &impl Ext) -> Result<(), Error> { + if env.is_read_only() { + return Err(pallet_revive::Error::::StateChangeDenied.into()); + } + if env.is_delegate_call() { + return Err(pallet_revive::Error::::PrecompileDelegateDenied.into()); + } + Ok(()) +} + +fn caller_account_id( + env: &impl Ext, + context: &str, +) -> Result { + env.caller() + .account_id() + .map_err(|e| { + Error::Revert(alloc::format!("{context}: caller has no account id: {e:?}").into()) + }) + .cloned() +} + +/// Minimal pallet providing a `Pallet` type for the FRAME benchmarking machinery. +#[frame_support::pallet] +pub mod pallet { + #[pallet::config] + pub trait Config: + frame_system::Config + pallet_revive::Config + pallet_vesting::Config + { + /// Weight information for the precompile operations. + type WeightInfo: crate::weights::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(_); +} + +pub struct Vesting(PhantomData); + +/// The balance type used by `pallet-vesting`'s currency. +type VestingBalance = + <::Currency as frame_support::traits::Currency< + ::AccountId, + >>::Balance; + +impl Precompile for Vesting +where + VestingBalance: Into, + VestingBalance: From<::Balance>, + ::Balance: From>, +{ + type T = T; + type Interface = IVesting::IVestingCalls; + const MATCHER: AddressMatcher = AddressMatcher::Fixed(NonZero::new(0x0902).unwrap()); + const HAS_CONTRACT_INFO: bool = false; + + fn call( + _address: &[u8; 20], + input: &Self::Interface, + env: &mut impl Ext, + ) -> Result, Error> { + use IVesting::IVestingCalls; + match input { + IVestingCalls::vest(IVesting::vestCall {}) => { + ensure_mutable::(env)?; + // Derive the beneficiary from the immediate caller (not the tx origin). + let account_id = caller_account_id(env, "vest")?; + + // Charge the pallet's own benchmarked dispatch weight. + let dispatch_weight = + pallet_vesting::Call::::vest {}.get_dispatch_info().call_weight; + env.frame_meter_mut() + .charge_weight_token(RuntimeCosts::Precompile(dispatch_weight))?; + + // Construct a signed RuntimeOrigin and dispatch vest(). + let origin = frame_system::RawOrigin::Signed(account_id).into(); + pallet_vesting::Pallet::::vest(origin) + .map_err(|e| Error::Revert(alloc::format!("vest failed: {:?}", e).into()))?; + Ok(Vec::new()) + }, + IVestingCalls::vestOther(IVesting::vestOtherCall { target }) => { + ensure_mutable::(env)?; + let caller_account = caller_account_id(env, "vestOther")?; + + let target_account = env.to_account_id(&H160::from_slice(target.as_slice())); + let target_lookup = T::Lookup::unlookup(target_account); + + let dispatch_weight = + pallet_vesting::Call::::vest_other { target: target_lookup.clone() } + .get_dispatch_info() + .call_weight; + env.frame_meter_mut() + .charge_weight_token(RuntimeCosts::Precompile(dispatch_weight))?; + + let origin = frame_system::RawOrigin::Signed(caller_account).into(); + pallet_vesting::Pallet::::vest_other(origin, target_lookup).map_err(|e| { + Error::Revert(alloc::format!("vestOther failed: {:?}", e).into()) + })?; + Ok(Vec::new()) + }, + // View function to query the currently locked (unvested) balance for the caller. + // vesting_balance() returns Option: None means no schedule exists, + // Some(0) means a schedule exists but all funds are already unlocked. Both + // collapse to 0 here — in either case there is nothing left to vest. + IVestingCalls::vestingBalance(IVesting::vestingBalanceCall {}) => { + let account_id = caller_account_id(env, "vestingBalance")?; + + env.frame_meter_mut().charge_weight_token(RuntimeCosts::Precompile( + <::WeightInfo as weights::WeightInfo>::vesting_balance(), + ))?; + + let maybe_locked = + as VestingSchedule>::vesting_balance( + &account_id, + ); + + let locked = maybe_locked.unwrap_or_default(); + Ok(U256::from(locked.into()).to_big_endian().abi_encode()) + }, + IVestingCalls::vestingBalanceOf(IVesting::vestingBalanceOfCall { target }) => { + let account_id = env.to_account_id(&H160::from_slice(target.as_slice())); + + env.frame_meter_mut().charge_weight_token(RuntimeCosts::Precompile( + <::WeightInfo as weights::WeightInfo>::vesting_balance_of( + ), + ))?; + + let maybe_locked = + as VestingSchedule>::vesting_balance( + &account_id, + ); + + let locked = maybe_locked.unwrap_or_default(); + Ok(U256::from(locked.into()).to_big_endian().abi_encode()) + }, + } + } +} diff --git a/substrate/frame/vesting/precompiles/src/mock.rs b/substrate/frame/vesting/precompiles/src/mock.rs new file mode 100644 index 0000000000000..dfcf9116e5766 --- /dev/null +++ b/substrate/frame/vesting/precompiles/src/mock.rs @@ -0,0 +1,119 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Mock runtime for benchmarking the vesting precompile. + +use frame_support::{derive_impl, parameter_types, traits::WithdrawReasons}; +use sp_runtime::BuildStorage; + +#[frame_support::runtime] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeTask, + RuntimeHoldReason, + RuntimeFreezeReason + )] + pub struct Test; + + #[runtime::pallet_index(0)] + pub type System = frame_system; + #[runtime::pallet_index(10)] + pub type Balances = pallet_balances; + #[runtime::pallet_index(11)] + pub type Timestamp = pallet_timestamp; + #[runtime::pallet_index(20)] + pub type Vesting = pallet_vesting; + #[runtime::pallet_index(30)] + pub type Revive = pallet_revive; + #[runtime::pallet_index(40)] + pub type VestingPrecompile = crate::pallet; +} + +type Block = frame_system::mocking::MockBlock; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const MinVestedTransfer: u64 = 256 * 2; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting::Config for Test { + type BlockNumberToBalance = sp_runtime::traits::Identity; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + const MAX_VESTING_SCHEDULES: u32 = 3; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = (); + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; +} + +#[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] +impl pallet_revive::Config for Test { + type AddressMapper = pallet_revive::TestAccountMapper; + type Balance = u64; + type Currency = Balances; + type Precompiles = (crate::Vesting,); +} + +impl crate::pallet::Config for Test { + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = RuntimeGenesisConfig { + system: Default::default(), + balances: Default::default(), + revive: Default::default(), + ..Default::default() + } + .build_storage() + .unwrap(); + let mut ext: sp_io::TestExternalities = t.into(); + ext.execute_with(|| { + System::set_block_number(1); + pallet_timestamp::Pallet::::set_timestamp(1704067200000); + }); + ext +} diff --git a/substrate/frame/vesting/precompiles/src/tests.rs b/substrate/frame/vesting/precompiles/src/tests.rs new file mode 100644 index 0000000000000..84dcca3a6a6c1 --- /dev/null +++ b/substrate/frame/vesting/precompiles/src/tests.rs @@ -0,0 +1,348 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Unit tests for the vesting precompile. + +use crate::{IVesting, Vesting, VestingBalance, mock::*}; +use alloy_core::sol_types::{SolInterface, SolValue}; +use frame_support::traits::{Currency, VestingSchedule}; +use pallet_revive::{ + AddressMapper, ExecReturnValue, + precompiles::{Precompile, U256}, + test_utils::builder::BareCallBuilder, +}; +use sp_core::H160; + +type CurrencyOf = ::Currency; + +const ALICE: u64 = 1; +const TARGET: u64 = 0xBEEF; + +fn precompile_address() -> H160 { + H160(Vesting::::MATCHER.base_address()) +} + +fn target_h160() -> H160 { + ::AddressMapper::to_address(&TARGET) +} + +fn target_alloy() -> alloy_core::primitives::Address { + alloy_core::primitives::Address::from_slice(target_h160().as_bytes()) +} + +fn add_vesting_schedule(who: &u64, locked: u64) { + let per_block = locked / 20; + as VestingSchedule>::add_vesting_schedule( + who, locked, per_block, 0, + ) + .expect("adding vesting schedule should succeed"); +} + +fn bare_call(input: &IVesting::IVestingCalls) -> BareCallBuilder { + BareCallBuilder::::bare_call(RuntimeOrigin::signed(ALICE), precompile_address()) + .data(input.abi_encode()) +} + +fn decode_balance(result: &ExecReturnValue) -> U256 { + assert!(!result.did_revert(), "unexpected revert: {:?}", result.data); + U256::from_big_endian(&<[u8; 32]>::abi_decode(&result.data).unwrap()) +} + +// --------------------------------------------------------------------------- +// vest() +// --------------------------------------------------------------------------- + +#[test] +fn vest_succeeds_with_active_schedule() { + new_test_ext().execute_with(|| { + let locked: VestingBalance = 10_000; + CurrencyOf::::make_free_balance_be(&ALICE, locked * 10); + add_vesting_schedule(&ALICE, locked); + + let input = IVesting::IVestingCalls::vest(IVesting::vestCall {}); + let result = bare_call(&input).build_and_unwrap_result(); + assert!(!result.did_revert()); + assert!(result.data.is_empty()); + }); +} + +#[test] +fn vest_reverts_with_no_schedule() { + new_test_ext().execute_with(|| { + let input = IVesting::IVestingCalls::vest(IVesting::vestCall {}); + let result = bare_call(&input).build_and_unwrap_result(); + assert!(result.did_revert(), "expected revert when no schedule exists"); + }); +} + +#[test] +fn vest_actually_unlocks_funds() { + new_test_ext().execute_with(|| { + let locked: VestingBalance = 10_000; + CurrencyOf::::make_free_balance_be(&ALICE, locked * 10); + add_vesting_schedule(&ALICE, locked); + + // Advance past the full schedule so vest() has something to unlock. + frame_system::Pallet::::set_block_number(21); + + let input = IVesting::IVestingCalls::vest(IVesting::vestCall {}); + let result = bare_call(&input).build_and_unwrap_result(); + assert!(!result.did_revert()); + + // After vest(), the vesting schedule should be removed entirely. + let schedule = + as VestingSchedule>::vesting_balance(&ALICE); + assert_eq!(schedule, None, "vesting schedule should be removed after full vest"); + }); +} + +// --------------------------------------------------------------------------- +// vestOther() +// --------------------------------------------------------------------------- + +#[test] +fn vest_other_succeeds_with_active_schedule() { + new_test_ext().execute_with(|| { + let locked: VestingBalance = 10_000; + CurrencyOf::::make_free_balance_be(&TARGET, locked * 10); + add_vesting_schedule(&TARGET, locked); + + let input = + IVesting::IVestingCalls::vestOther(IVesting::vestOtherCall { target: target_alloy() }); + let result = bare_call(&input).build_and_unwrap_result(); + assert!(!result.did_revert()); + assert!(result.data.is_empty()); + }); +} + +#[test] +fn vest_other_actually_unlocks_funds_for_target() { + new_test_ext().execute_with(|| { + let locked: VestingBalance = 10_000; + CurrencyOf::::make_free_balance_be(&TARGET, locked * 10); + add_vesting_schedule(&TARGET, locked); + + frame_system::Pallet::::set_block_number(21); + + let input = + IVesting::IVestingCalls::vestOther(IVesting::vestOtherCall { target: target_alloy() }); + let result = bare_call(&input).build_and_unwrap_result(); + assert!(!result.did_revert()); + + let schedule = + as VestingSchedule>::vesting_balance(&TARGET); + assert_eq!(schedule, None, "target's vesting schedule should be removed after full vest"); + }); +} + +#[test] +fn vest_other_reverts_with_no_schedule_on_target() { + new_test_ext().execute_with(|| { + let input = + IVesting::IVestingCalls::vestOther(IVesting::vestOtherCall { target: target_alloy() }); + let result = bare_call(&input).build_and_unwrap_result(); + assert!(result.did_revert(), "expected revert when target has no schedule"); + }); +} + +// --------------------------------------------------------------------------- +// vestingBalance() & vestingBalanceOf() +// --------------------------------------------------------------------------- + +#[test] +fn vesting_balance_returns_correct_locked_amount() { + new_test_ext().execute_with(|| { + let locked: VestingBalance = 10_000; + CurrencyOf::::make_free_balance_be(&ALICE, locked * 10); + add_vesting_schedule(&ALICE, locked); + + let input = IVesting::IVestingCalls::vestingBalance(IVesting::vestingBalanceCall {}); + + // At block 1 with per_block=500 (10_000/20), one block has vested: 10_000 - 500 = 9_500. + let result = bare_call(&input).build_and_unwrap_result(); + assert_eq!(decode_balance(&result), U256::from(9_500u64)); + + // Advance to block 10: half the 20-block schedule has elapsed. + frame_system::Pallet::::set_block_number(10); + let result = bare_call(&input).build_and_unwrap_result(); + assert_eq!(decode_balance(&result), U256::from(5_000u64)); + }); +} + +#[test] +fn vesting_balance_returns_zero_when_no_schedule() { + new_test_ext().execute_with(|| { + let input = IVesting::IVestingCalls::vestingBalance(IVesting::vestingBalanceCall {}); + let result = bare_call(&input).build_and_unwrap_result(); + assert_eq!(decode_balance(&result), U256::zero()); + }); +} + +#[test] +fn vesting_balance_returns_zero_when_fully_vested() { + new_test_ext().execute_with(|| { + let locked: VestingBalance = 10_000; + CurrencyOf::::make_free_balance_be(&ALICE, locked * 10); + add_vesting_schedule(&ALICE, locked); + + frame_system::Pallet::::set_block_number(21); + + let input = IVesting::IVestingCalls::vestingBalance(IVesting::vestingBalanceCall {}); + let result = bare_call(&input).build_and_unwrap_result(); + assert_eq!(decode_balance(&result), U256::zero()); + }); +} + +#[test] +fn vesting_balance_of_returns_correct_locked_amount() { + new_test_ext().execute_with(|| { + let locked: VestingBalance = 10_000; + CurrencyOf::::make_free_balance_be(&TARGET, locked * 10); + add_vesting_schedule(&TARGET, locked); + + let input = IVesting::IVestingCalls::vestingBalanceOf(IVesting::vestingBalanceOfCall { + target: target_alloy(), + }); + let result = bare_call(&input).build_and_unwrap_result(); + assert_eq!(decode_balance(&result), U256::from(9_500u64)); + }); +} + +#[test] +fn vesting_balance_of_returns_zero_when_no_schedule() { + new_test_ext().execute_with(|| { + let input = IVesting::IVestingCalls::vestingBalanceOf(IVesting::vestingBalanceOfCall { + target: target_alloy(), + }); + let result = bare_call(&input).build_and_unwrap_result(); + assert_eq!(decode_balance(&result), U256::zero()); + }); +} + +#[test] +fn vesting_balance_aggregates_multiple_schedules() { + new_test_ext().execute_with(|| { + CurrencyOf::::make_free_balance_be(&ALICE, 500_000); + add_vesting_schedule(&ALICE, 10_000); + add_vesting_schedule(&ALICE, 20_000); + + let input = IVesting::IVestingCalls::vestingBalance(IVesting::vestingBalanceCall {}); + let result = bare_call(&input).build_and_unwrap_result(); + // At block 1: schedule 1 locked = 10_000 - 500 = 9_500, + // schedule 2 locked = 20_000 - 1_000 = 19_000. + // Total = 28_500. + assert_eq!(decode_balance(&result), U256::from(28_500u64)); + }); +} + +// --------------------------------------------------------------------------- +// Read-only & delegate-call guards (test vectors) +// --------------------------------------------------------------------------- + +struct GuardTestCase { + name: &'static str, + input: IVesting::IVestingCalls, + reject_read_only: bool, + reject_delegate: bool, +} + +fn guard_test_cases() -> Vec { + vec![ + GuardTestCase { + name: "vest", + input: IVesting::IVestingCalls::vest(IVesting::vestCall {}), + reject_read_only: true, + reject_delegate: true, + }, + GuardTestCase { + name: "vestOther", + input: IVesting::IVestingCalls::vestOther(IVesting::vestOtherCall { + target: target_alloy(), + }), + reject_read_only: true, + reject_delegate: true, + }, + GuardTestCase { + name: "vestingBalance", + input: IVesting::IVestingCalls::vestingBalance(IVesting::vestingBalanceCall {}), + reject_read_only: false, + reject_delegate: false, + }, + GuardTestCase { + name: "vestingBalanceOf", + input: IVesting::IVestingCalls::vestingBalanceOf(IVesting::vestingBalanceOfCall { + target: target_alloy(), + }), + reject_read_only: false, + reject_delegate: false, + }, + ] +} + +fn call_with_flags( + input: &IVesting::IVestingCalls, + read_only: bool, + delegate: bool, +) -> Result, pallet_revive::precompiles::Error> { + let mut call_setup = pallet_revive::call_builder::CallSetup::::default(); + call_setup.set_read_only(read_only); + call_setup.set_delegate_call(delegate); + let (mut ext, _) = call_setup.ext(); + Vesting::::call(&Vesting::::MATCHER.base_address(), input, &mut ext) +} + +fn assert_guard( + case: &GuardTestCase, + read_only: bool, + delegate: bool, + should_reject: bool, + expected_error: sp_runtime::DispatchError, +) { + let context = if read_only { "read-only" } else { "delegate call" }; + let result = call_with_flags(&case.input, read_only, delegate); + if should_reject { + match result { + Err(pallet_revive::precompiles::Error::Error(err)) => { + assert_eq!(err.error, expected_error, "{}: wrong error in {context}", case.name); + }, + Err(other) => panic!("{}: unexpected error type in {context}: {other:?}", case.name), + Ok(_) => panic!("{}: should be rejected in {context}", case.name), + } + } else { + assert!(result.is_ok(), "{}: should succeed in {context}, got: {:?}", case.name, result); + } +} + +#[test] +fn read_only_guards() { + new_test_ext().execute_with(|| { + let error = pallet_revive::Error::::StateChangeDenied.into(); + for case in guard_test_cases() { + assert_guard(&case, true, false, case.reject_read_only, error); + } + }); +} + +#[test] +fn delegate_call_guards() { + new_test_ext().execute_with(|| { + let error = pallet_revive::Error::::PrecompileDelegateDenied.into(); + for case in guard_test_cases() { + assert_guard(&case, false, true, case.reject_delegate, error); + } + }); +} diff --git a/substrate/frame/vesting/precompiles/src/weights.rs b/substrate/frame/vesting/precompiles/src/weights.rs new file mode 100644 index 0000000000000..08993c4fd85d9 --- /dev/null +++ b/substrate/frame/vesting/precompiles/src/weights.rs @@ -0,0 +1,133 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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. + +//! Autogenerated weights for `pallet_vesting_precompiles` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2026-04-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `9f9d748cbad6`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` + +// Executed Command: +// frame-omni-bencher +// v1 +// benchmark +// pallet +// --extrinsic=* +// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm +// --pallet=pallet_vesting_precompiles +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/vesting/precompiles/src/weights.rs +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --template=substrate/.maintain/frame-weight-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes +// --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] +#![allow(dead_code)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_vesting_precompiles`. +pub trait WeightInfo { + fn vesting_balance() -> Weight; + fn vesting_balance_of() -> Weight; +} + +/// Weights for `pallet_vesting_precompiles` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Vesting::Vesting` (r:1 w:0) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1057), added: 3532, mode: `MaxEncodedLen`) + fn vesting_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `520` + // Estimated: `4522` + // Minimum execution time: 15_399_000 picoseconds. + Weight::from_parts(16_490_000, 4522) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `Revive::OriginalAccount` (r:1 w:0) + /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `Vesting::Vesting` (r:1 w:0) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1057), added: 3532, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn vesting_balance_of() -> Weight { + // Proof Size summary in bytes: + // Measured: `1087` + // Estimated: `4522` + // Minimum execution time: 21_383_000 picoseconds. + Weight::from_parts(22_938_000, 4522) + .saturating_add(T::DbWeight::get().reads(3_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Vesting::Vesting` (r:1 w:0) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1057), added: 3532, mode: `MaxEncodedLen`) + fn vesting_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `520` + // Estimated: `4522` + // Minimum execution time: 15_399_000 picoseconds. + Weight::from_parts(16_490_000, 4522) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `Revive::OriginalAccount` (r:1 w:0) + /// Proof: `Revive::OriginalAccount` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `Vesting::Vesting` (r:1 w:0) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1057), added: 3532, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn vesting_balance_of() -> Weight { + // Proof Size summary in bytes: + // Measured: `1087` + // Estimated: `4522` + // Minimum execution time: 21_383_000 picoseconds. + Weight::from_parts(22_938_000, 4522) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + } +} diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index a45cbb14343c5..b99af699be3e3 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -165,6 +165,7 @@ std = [ "pallet-uniques?/std", "pallet-utility?/std", "pallet-verify-signature?/std", + "pallet-vesting-precompiles?/std", "pallet-vesting?/std", "pallet-whitelist?/std", "pallet-xcm-benchmarks?/std", @@ -349,6 +350,7 @@ runtime-benchmarks = [ "pallet-uniques?/runtime-benchmarks", "pallet-utility?/runtime-benchmarks", "pallet-verify-signature?/runtime-benchmarks", + "pallet-vesting-precompiles?/runtime-benchmarks", "pallet-vesting?/runtime-benchmarks", "pallet-whitelist?/runtime-benchmarks", "pallet-xcm-benchmarks?/runtime-benchmarks", @@ -495,6 +497,7 @@ try-runtime = [ "pallet-uniques?/try-runtime", "pallet-utility?/try-runtime", "pallet-verify-signature?/try-runtime", + "pallet-vesting-precompiles?/try-runtime", "pallet-vesting?/try-runtime", "pallet-whitelist?/try-runtime", "pallet-xcm-bridge-hub-router?/try-runtime", @@ -725,6 +728,7 @@ runtime-full = [ "pallet-utility", "pallet-verify-signature", "pallet-vesting", + "pallet-vesting-precompiles", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", @@ -1841,6 +1845,11 @@ default-features = false optional = true path = "../substrate/frame/vesting" +[dependencies.pallet-vesting-precompiles] +default-features = false +optional = true +path = "../substrate/frame/vesting/precompiles" + [dependencies.pallet-whitelist] default-features = false optional = true diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index be821fb478352..c07c62a7ae280 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -787,6 +787,10 @@ pub use pallet_verify_signature; #[cfg(feature = "pallet-vesting")] pub use pallet_vesting; +/// Vesting precompile exposing pallet-vesting to EVM contracts via pallet-revive. +#[cfg(feature = "pallet-vesting-precompiles")] +pub use pallet_vesting_precompiles; + /// FRAME pallet for whitelisting calls, and dispatching from a specific origin. #[cfg(feature = "pallet-whitelist")] pub use pallet_whitelist;