From 77c78e1561bbe5ee0ecf414312bae82396ae6d11 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:50:42 +0200 Subject: [PATCH 1/6] litep2p: Provide partial results to speedup GetRecord queries (#7099) This PR provides the partial results of the `GetRecord` kademlia query. This significantly improves the authority discovery records, from ~37 minutes to ~2/3 minutes. In contrast, libp2p discovers authority records in around ~10 minutes. The authority discovery was slow because litep2p provided the records only after the Kademlia query was completed. A normal Kademlia query completes in around 40 seconds to a few minutes. In this PR, partial records are provided as soon as they are discovered from the network. ### Testing Done Started a node in Kusama with `--validator` and litep2p backend. The node discovered 996/1000 authority records in ~ 1 minute 45 seconds. ![Screenshot 2025-01-09 at 12 26 08](https://github.com/user-attachments/assets/b618bf7c-2bba-43a0-a021-4047e854c075) ### Before & After In this image, on the left side is libp2p, in the middle litep2p without this PR, on the right litep2p with this PR ![Screenshot 2025-01-07 at 17 57 56](https://github.com/user-attachments/assets/a8d467f7-8dc7-461c-bcff-163b94d01ae8) Closes: https://github.com/paritytech/polkadot-sdk/issues/7077 cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile --- Cargo.lock | 4 +- Cargo.toml | 2 +- prdoc/pr_7099.prdoc | 16 ++++ .../client/network/src/litep2p/discovery.rs | 33 +++++-- substrate/client/network/src/litep2p/mod.rs | 87 ++++++++----------- 5 files changed, 79 insertions(+), 63 deletions(-) create mode 100644 prdoc/pr_7099.prdoc diff --git a/Cargo.lock b/Cargo.lock index 7725db743c41..0d71a770d38b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10446,9 +10446,9 @@ dependencies = [ [[package]] name = "litep2p" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0fef34af8847e816003bf7fdeac5ea50b9a7a88441ac927a6166b5e812ab79" +checksum = "6ca6ee50a125dc4fc4e9a3ae3640010796d1d07bc517a0ac715fdf0b24a0b6ac" dependencies = [ "async-trait", "bs58", diff --git a/Cargo.toml b/Cargo.toml index c30a9949e85e..eb99b80e16fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -850,7 +850,7 @@ linked-hash-map = { version = "0.5.4" } linked_hash_set = { version = "0.1.4" } linregress = { version = "0.5.1" } lite-json = { version = "0.2.0", default-features = false } -litep2p = { version = "0.8.4", features = ["websocket"] } +litep2p = { version = "0.9.0", features = ["websocket"] } log = { version = "0.4.22", default-features = false } macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } diff --git a/prdoc/pr_7099.prdoc b/prdoc/pr_7099.prdoc new file mode 100644 index 000000000000..58d809f3c090 --- /dev/null +++ b/prdoc/pr_7099.prdoc @@ -0,0 +1,16 @@ +title: Provide partial results to speedup GetRecord queries + +doc: + - audience: Node Dev + description: | + This PR provides the partial results of the GetRecord kademlia query. + + This significantly improves the authority discovery records, from ~37 minutes to ~2/3 minutes. + In contrast, libp2p discovers authority records in around ~10 minutes. + + The authority discovery was slow because litep2p provided the records only after the Kademlia query was completed. A normal Kademlia query completes in around 40 seconds to a few minutes. + In this PR, partial records are provided as soon as they are discovered from the network. + +crates: + - name: sc-network + bump: patch diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs index b55df374f60e..eb571804f30e 100644 --- a/substrate/client/network/src/litep2p/discovery.rs +++ b/substrate/client/network/src/litep2p/discovery.rs @@ -33,8 +33,8 @@ use litep2p::{ identify::{Config as IdentifyConfig, IdentifyEvent}, kademlia::{ Config as KademliaConfig, ConfigBuilder as KademliaConfigBuilder, ContentProvider, - IncomingRecordValidationMode, KademliaEvent, KademliaHandle, QueryId, Quorum, - Record, RecordKey, RecordsType, + IncomingRecordValidationMode, KademliaEvent, KademliaHandle, PeerRecord, QueryId, + Quorum, Record, RecordKey, }, ping::{Config as PingConfig, PingEvent}, }, @@ -129,13 +129,19 @@ pub enum DiscoveryEvent { address: Multiaddr, }, - /// Record was found from the DHT. + /// `GetRecord` query succeeded. GetRecordSuccess { /// Query ID. query_id: QueryId, + }, - /// Records. - records: RecordsType, + /// Record was found from the DHT. + GetRecordPartialResult { + /// Query ID. + query_id: QueryId, + + /// Record. + record: PeerRecord, }, /// Record was successfully stored on the DHT. @@ -573,13 +579,24 @@ impl Stream for Discovery { peers: peers.into_iter().collect(), })) }, - Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id, records })) => { + Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id })) => { log::trace!( target: LOG_TARGET, - "`GET_RECORD` succeeded for {query_id:?}: {records:?}", + "`GET_RECORD` succeeded for {query_id:?}", ); - return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id, records })); + return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id })); + }, + Poll::Ready(Some(KademliaEvent::GetRecordPartialResult { query_id, record })) => { + log::trace!( + target: LOG_TARGET, + "`GET_RECORD` intermediary succeeded for {query_id:?}: {record:?}", + ); + + return Poll::Ready(Some(DiscoveryEvent::GetRecordPartialResult { + query_id, + record, + })); }, Poll::Ready(Some(KademliaEvent::PutRecordSuccess { query_id, key: _ })) => return Poll::Ready(Some(DiscoveryEvent::PutRecordSuccess { query_id })), diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs index 52b2970525df..fc4cce476283 100644 --- a/substrate/client/network/src/litep2p/mod.rs +++ b/substrate/client/network/src/litep2p/mod.rs @@ -58,7 +58,7 @@ use litep2p::{ protocol::{ libp2p::{ bitswap::Config as BitswapConfig, - kademlia::{QueryId, Record, RecordsType}, + kademlia::{QueryId, Record}, }, request_response::ConfigBuilder as RequestResponseConfigBuilder, }, @@ -836,23 +836,45 @@ impl NetworkBackend for Litep2pNetworkBac self.peerstore_handle.add_known_peer(peer.into()); } } - Some(DiscoveryEvent::GetRecordSuccess { query_id, records }) => { + Some(DiscoveryEvent::GetRecordPartialResult { query_id, record }) => { + if !self.pending_queries.contains_key(&query_id) { + log::error!( + target: LOG_TARGET, + "Missing/invalid pending query for `GET_VALUE` partial result: {query_id:?}" + ); + + continue + } + + let peer_id: sc_network_types::PeerId = record.peer.into(); + let record = PeerRecord { + record: P2PRecord { + key: record.record.key.to_vec().into(), + value: record.record.value, + publisher: record.record.publisher.map(|peer_id| { + let peer_id: sc_network_types::PeerId = peer_id.into(); + peer_id.into() + }), + expires: record.record.expires, + }, + peer: Some(peer_id.into()), + }; + + self.event_streams.send( + Event::Dht( + DhtEvent::ValueFound( + record.into() + ) + ) + ); + } + Some(DiscoveryEvent::GetRecordSuccess { query_id }) => { match self.pending_queries.remove(&query_id) { Some(KadQuery::GetValue(key, started)) => { log::trace!( target: LOG_TARGET, - "`GET_VALUE` for {:?} ({query_id:?}) succeeded", - key, + "`GET_VALUE` for {key:?} ({query_id:?}) succeeded", ); - for record in litep2p_to_libp2p_peer_record(records) { - self.event_streams.send( - Event::Dht( - DhtEvent::ValueFound( - record.into() - ) - ) - ); - } if let Some(ref metrics) = self.metrics { metrics @@ -1165,42 +1187,3 @@ impl NetworkBackend for Litep2pNetworkBac } } } - -// Glue code to convert from a litep2p records type to a libp2p2 PeerRecord. -fn litep2p_to_libp2p_peer_record(records: RecordsType) -> Vec { - match records { - litep2p::protocol::libp2p::kademlia::RecordsType::LocalStore(record) => { - vec![PeerRecord { - record: P2PRecord { - key: record.key.to_vec().into(), - value: record.value, - publisher: record.publisher.map(|peer_id| { - let peer_id: sc_network_types::PeerId = peer_id.into(); - peer_id.into() - }), - expires: record.expires, - }, - peer: None, - }] - }, - litep2p::protocol::libp2p::kademlia::RecordsType::Network(records) => records - .into_iter() - .map(|record| { - let peer_id: sc_network_types::PeerId = record.peer.into(); - - PeerRecord { - record: P2PRecord { - key: record.record.key.to_vec().into(), - value: record.record.value, - publisher: record.record.publisher.map(|peer_id| { - let peer_id: sc_network_types::PeerId = peer_id.into(); - peer_id.into() - }), - expires: record.record.expires, - }, - peer: Some(peer_id.into()), - } - }) - .collect::>(), - } -} From ece32e38a1a37aa354d51b16c07a42c66f23976e Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 15 Jan 2025 18:37:59 +0100 Subject: [PATCH 2/6] [pallet-revive] Remove debug buffer (#7163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the `debug_buffer` feature --------- Co-authored-by: command-bot <> Co-authored-by: Cyrill Leutwiler Co-authored-by: Alexander Theißen --- .../assets/asset-hub-westend/src/lib.rs | 15 +- prdoc/pr_7163.prdoc | 13 + substrate/bin/node/runtime/src/lib.rs | 10 +- substrate/frame/revive/README.md | 23 -- .../contracts/debug_message_invalid_utf8.rs | 33 --- .../debug_message_logging_disabled.rs | 33 --- .../fixtures/contracts/debug_message_works.rs | 33 --- substrate/frame/revive/proc-macro/src/lib.rs | 7 +- .../revive/src/benchmarking/call_builder.rs | 15 +- .../frame/revive/src/benchmarking/mod.rs | 28 --- substrate/frame/revive/src/exec.rs | 228 +----------------- substrate/frame/revive/src/lib.rs | 67 +---- substrate/frame/revive/src/limits.rs | 5 - substrate/frame/revive/src/primitives.rs | 53 +--- .../frame/revive/src/test_utils/builder.rs | 17 +- substrate/frame/revive/src/tests.rs | 146 +---------- .../frame/revive/src/tests/test_debug.rs | 4 - substrate/frame/revive/src/wasm/mod.rs | 2 +- substrate/frame/revive/src/wasm/runtime.rs | 34 +-- substrate/frame/revive/src/weights.rs | 21 -- substrate/frame/revive/uapi/src/host.rs | 20 -- .../frame/revive/uapi/src/host/riscv64.rs | 7 - substrate/frame/revive/uapi/src/lib.rs | 7 +- 23 files changed, 54 insertions(+), 767 deletions(-) create mode 100644 prdoc/pr_7163.prdoc delete mode 100644 substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs delete mode 100644 substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs delete mode 100644 substrate/frame/revive/fixtures/contracts/debug_message_works.rs 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 7844b0d885ec..5966dd01f18f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -952,11 +952,6 @@ parameter_types! { pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } -type EventRecord = frame_system::EventRecord< - ::RuntimeEvent, - ::Hash, ->; - impl pallet_revive::Config for Runtime { type Time = Timestamp; type Currency = Balances; @@ -2073,7 +2068,7 @@ impl_runtime_apis! { } } - impl pallet_revive::ReviveApi for Runtime + impl pallet_revive::ReviveApi for Runtime { fn balance(address: H160) -> U256 { Revive::evm_balance(&address) @@ -2108,7 +2103,7 @@ impl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_revive::ContractResult { + ) -> pallet_revive::ContractResult { let blockweights= ::BlockWeights::get(); Revive::bare_call( RuntimeOrigin::signed(origin), @@ -2117,8 +2112,6 @@ impl_runtime_apis! { gas_limit.unwrap_or(blockweights.max_block), pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -2130,7 +2123,7 @@ impl_runtime_apis! { code: pallet_revive::Code, data: Vec, salt: Option<[u8; 32]>, - ) -> pallet_revive::ContractResult + ) -> pallet_revive::ContractResult { let blockweights= ::BlockWeights::get(); Revive::bare_instantiate( @@ -2141,8 +2134,6 @@ impl_runtime_apis! { code, data, salt, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } diff --git a/prdoc/pr_7163.prdoc b/prdoc/pr_7163.prdoc new file mode 100644 index 000000000000..669c480b835b --- /dev/null +++ b/prdoc/pr_7163.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] Remove debug buffer' +doc: +- audience: Runtime Dev + description: Remove the `debug_buffer` feature +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive + bump: major +- name: pallet-revive-proc-macro + 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 e11a009c1c3f..97728f12f5f9 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -3212,7 +3212,7 @@ impl_runtime_apis! { } } - impl pallet_revive::ReviveApi for Runtime + impl pallet_revive::ReviveApi for Runtime { fn balance(address: H160) -> U256 { Revive::evm_balance(&address) @@ -3247,7 +3247,7 @@ impl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_revive::ContractResult { + ) -> pallet_revive::ContractResult { Revive::bare_call( RuntimeOrigin::signed(origin), dest, @@ -3255,8 +3255,6 @@ impl_runtime_apis! { gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -3268,7 +3266,7 @@ impl_runtime_apis! { code: pallet_revive::Code, data: Vec, salt: Option<[u8; 32]>, - ) -> pallet_revive::ContractResult + ) -> pallet_revive::ContractResult { Revive::bare_instantiate( RuntimeOrigin::signed(origin), @@ -3278,8 +3276,6 @@ impl_runtime_apis! { code, data, salt, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } diff --git a/substrate/frame/revive/README.md b/substrate/frame/revive/README.md index 575920dfaac7..7538f77d10bc 100644 --- a/substrate/frame/revive/README.md +++ b/substrate/frame/revive/README.md @@ -49,29 +49,6 @@ This module executes PolkaVM smart contracts. These can potentially be written i RISC-V. For now, the only officially supported languages are Solidity (via [`revive`](https://github.com/xermicus/revive)) and Rust (check the `fixtures` directory for Rust examples). -## Debugging - -Contracts can emit messages to the client when called as RPC through the -[`debug_message`](https://paritytech.github.io/substrate/master/pallet_revive/trait.SyscallDocs.html#tymethod.debug_message) -API. - -Those messages are gathered into an internal buffer and sent to the RPC client. It is up to the individual client if -and how those messages are presented to the user. - -This buffer is also printed as a debug message. In order to see these messages on the node console the log level for the -`runtime::revive` target needs to be raised to at least the `debug` level. However, those messages are easy to -overlook because of the noise generated by block production. A good starting point for observing them on the console is -using this command line in the root directory of the Substrate repository: - -```bash -cargo run --release -- --dev -lerror,runtime::revive=debug -``` - -This raises the log level of `runtime::revive` to `debug` and all other targets to `error` in order to prevent them -from spamming the console. - -`--dev`: Use a dev chain spec `--tmp`: Use temporary storage for chain data (the chain state is deleted on exit) - ## Host function tracing For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments, diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs b/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs deleted file mode 100644 index 6c850a9ec663..000000000000 --- a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -//! Emit a debug message with an invalid utf-8 code. -#![no_std] -#![no_main] - -extern crate common; -use uapi::{HostFn, HostFnImpl as api}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - api::debug_message(b"\xFC").unwrap(); -} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs b/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs deleted file mode 100644 index 0ce2b6b5628d..000000000000 --- a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -//! Emit a "Hello World!" debug message but assume that logging is disabled. -#![no_std] -#![no_main] - -extern crate common; -use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - assert_eq!(api::debug_message(b"Hello World!"), Err(ReturnErrorCode::LoggingDisabled)); -} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs b/substrate/frame/revive/fixtures/contracts/debug_message_works.rs deleted file mode 100644 index 3a2509509d8f..000000000000 --- a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs +++ /dev/null @@ -1,33 +0,0 @@ -// 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. - -//! Emit a "Hello World!" debug message. -#![no_std] -#![no_main] - -extern crate common; -use uapi::{HostFn, HostFnImpl as api}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - api::debug_message(b"Hello World!").unwrap(); -} diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index b09bdef14632..6e38063d20a6 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -510,12 +510,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { quote! { // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); - if ::log::log_enabled!(target: "runtime::revive::strace", ::log::Level::Trace) { - use core::fmt::Write; - let mut msg = alloc::string::String::default(); - let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result); - self.ext().append_debug_buffer(&msg); - } + ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result); result } }; diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs index 1177d47aadc3..077e18ff5f0b 100644 --- a/substrate/frame/revive/src/benchmarking/call_builder.rs +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -22,7 +22,7 @@ use crate::{ storage::meter::Meter, transient_storage::MeterEntry, wasm::{PreparedCall, Runtime}, - BalanceOf, Config, DebugBuffer, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, + BalanceOf, Config, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, }; use alloc::{vec, vec::Vec}; use frame_benchmarking::benchmarking; @@ -38,7 +38,6 @@ pub struct CallSetup { gas_meter: GasMeter, storage_meter: Meter, value: BalanceOf, - debug_message: Option, data: Vec, transient_storage_size: u32, } @@ -91,7 +90,6 @@ where gas_meter: GasMeter::new(Weight::MAX), storage_meter, value: 0u32.into(), - debug_message: None, data: vec![], transient_storage_size: 0, } @@ -122,16 +120,6 @@ where self.transient_storage_size = size; } - /// Set the debug message. - pub fn enable_debug_message(&mut self) { - self.debug_message = Some(Default::default()); - } - - /// Get the debug message. - pub fn debug_message(&self) -> Option { - self.debug_message.clone() - } - /// Get the call's input data. pub fn data(&self) -> Vec { self.data.clone() @@ -150,7 +138,6 @@ where &mut self.gas_meter, &mut self.storage_meter, self.value, - self.debug_message.as_mut(), ); 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/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 1796348ff321..e23554f21ba8 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -107,8 +107,6 @@ where Code::Upload(module.code), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); let address = outcome.result?.addr; @@ -1047,32 +1045,6 @@ mod benchmarks { ); } - // Benchmark debug_message call - // Whereas this function is used in RPC mode only, it still should be secured - // against an excessive use. - // - // i: size of input in bytes up to maximum allowed contract memory or maximum allowed debug - // buffer size, whichever is less. - #[benchmark] - fn seal_debug_message( - i: Linear<0, { (limits::code::BLOB_BYTES).min(limits::DEBUG_BUFFER_BYTES) }>, - ) { - let mut setup = CallSetup::::default(); - setup.enable_debug_message(); - let (mut ext, _) = setup.ext(); - let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); - // Fill memory with printable ASCII bytes. - let mut memory = (0..i).zip((32..127).cycle()).map(|i| i.1).collect::>(); - - let result; - #[block] - { - result = runtime.bench_debug_message(memory.as_mut_slice(), 0, i); - } - assert_ok!(result); - assert_eq!(setup.debug_message().unwrap().len() as u32, i); - } - #[benchmark(skip_meta, pov_mode = Measured)] fn get_storage_empty() -> Result<(), BenchmarkError> { let max_key_len = limits::STORAGE_KEY_BYTES; diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index c069216d6cc7..e20c5dd7786e 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -24,8 +24,8 @@ use crate::{ runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, storage::{self, meter::Diff, WriteOutcome}, transient_storage::TransientStorage, - BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, DebugBuffer, Error, - Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, LOG_TARGET, + BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event, + ImmutableData, ImmutableDataOf, Pallet as Contracts, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData, mem}; @@ -378,19 +378,6 @@ pub trait Ext: sealing::Sealed { /// Charges `diff` from the meter. fn charge_storage(&mut self, diff: &Diff); - /// Append a string to the debug buffer. - /// - /// It is added as-is without any additional new line. - /// - /// This is a no-op if debug message recording is disabled which is always the case - /// when the code is executing on-chain. - /// - /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn append_debug_buffer(&mut self, msg: &str) -> bool; - - /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn debug_buffer_enabled(&self) -> bool; - /// Call some dispatchable and return the result. fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; @@ -555,11 +542,6 @@ pub struct Stack<'a, T: Config, E> { frames: BoundedVec, ConstU32<{ limits::CALL_STACK_DEPTH }>>, /// Statically guarantee that each call stack has at least one frame. first_frame: Frame, - /// A text buffer used to output human readable information. - /// - /// All the bytes added to this field should be valid UTF-8. The buffer has no defined - /// structure and is intended to be shown to users as-is for debugging purposes. - debug_message: Option<&'a mut DebugBuffer>, /// Transient storage used to store data, which is kept for the duration of a transaction. transient_storage: TransientStorage, /// Whether or not actual transfer of funds should be performed. @@ -765,11 +747,6 @@ where { /// Create and run a new call stack by calling into `dest`. /// - /// # Note - /// - /// `debug_message` should only ever be set to `Some` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - /// /// # Return Value /// /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)> @@ -781,7 +758,6 @@ where value: U256, input_data: Vec, skip_transfer: bool, - debug_message: Option<&'a mut DebugBuffer>, ) -> ExecResult { let dest = T::AddressMapper::to_account_id(&dest); if let Some((mut stack, executable)) = Self::new( @@ -791,7 +767,6 @@ where storage_meter, value, skip_transfer, - debug_message, )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { @@ -801,11 +776,6 @@ where /// Create and run a new call stack by instantiating a new contract. /// - /// # Note - /// - /// `debug_message` should only ever be set to `Some` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - /// /// # Return Value /// /// Result<(NewContractAccountId, ExecReturnValue), ExecError)> @@ -818,7 +788,6 @@ where input_data: Vec, salt: Option<&[u8; 32]>, skip_transfer: bool, - debug_message: Option<&'a mut DebugBuffer>, ) -> Result<(H160, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( FrameArgs::Instantiate { @@ -832,7 +801,6 @@ where storage_meter, value, skip_transfer, - debug_message, )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); let address = T::AddressMapper::to_address(&stack.top_frame().account_id); @@ -848,7 +816,6 @@ where gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, value: BalanceOf, - debug_message: Option<&'a mut DebugBuffer>, ) -> (Self, E) { Self::new( FrameArgs::Call { @@ -861,7 +828,6 @@ where storage_meter, value.into(), false, - debug_message, ) .unwrap() .unwrap() @@ -878,7 +844,6 @@ where storage_meter: &'a mut storage::meter::Meter, value: U256, skip_transfer: bool, - debug_message: Option<&'a mut DebugBuffer>, ) -> Result, ExecError> { origin.ensure_mapped()?; let Some((first_frame, executable)) = Self::new_frame( @@ -903,7 +868,6 @@ where block_number: >::block_number(), first_frame, frames: Default::default(), - debug_message, transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), skip_transfer, _phantom: Default::default(), @@ -1250,13 +1214,6 @@ where } } } else { - if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) { - log::debug!( - target: LOG_TARGET, - "Execution finished with debug buffer: {}", - core::str::from_utf8(msg).unwrap_or(""), - ); - } self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas)); if !persist { return; @@ -1759,28 +1716,6 @@ where self.top_frame_mut().nested_storage.charge(diff) } - fn debug_buffer_enabled(&self) -> bool { - self.debug_message.is_some() - } - - fn append_debug_buffer(&mut self, msg: &str) -> bool { - if let Some(buffer) = &mut self.debug_message { - buffer - .try_extend(&mut msg.bytes()) - .map_err(|_| { - log::debug!( - target: LOG_TARGET, - "Debug buffer (of {} bytes) exhausted!", - limits::DEBUG_BUFFER_BYTES, - ) - }) - .ok(); - true - } else { - false - } - } - fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo { let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.account_id().clone()).into(); origin.add_filter(T::CallFilter::contains); @@ -2103,7 +2038,6 @@ mod tests { value.into(), vec![], false, - None, ), Ok(_) ); @@ -2196,7 +2130,6 @@ mod tests { value.into(), vec![], false, - None, ) .unwrap(); @@ -2237,7 +2170,6 @@ mod tests { value.into(), vec![], false, - None, )); assert_eq!(get_balance(&ALICE), 100 - value); @@ -2274,7 +2206,6 @@ mod tests { U256::zero(), vec![], false, - None, ), ExecError { error: Error::::CodeNotFound.into(), @@ -2292,7 +2223,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -2321,7 +2251,6 @@ mod tests { 55u64.into(), vec![], false, - None, ) .unwrap(); @@ -2371,7 +2300,6 @@ mod tests { U256::zero(), vec![], false, - None, ); let output = result.unwrap(); @@ -2401,7 +2329,6 @@ mod tests { U256::zero(), vec![], false, - None, ); let output = result.unwrap(); @@ -2431,7 +2358,6 @@ mod tests { U256::zero(), vec![1, 2, 3, 4], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2468,7 +2394,6 @@ mod tests { vec![1, 2, 3, 4], Some(&[0; 32]), false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2523,7 +2448,6 @@ mod tests { value.into(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2588,7 +2512,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2654,7 +2577,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2687,7 +2609,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2725,7 +2646,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2752,7 +2672,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2797,7 +2716,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2824,7 +2742,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2851,7 +2768,6 @@ mod tests { 1u64.into(), vec![0], false, - None, ); assert_matches!(result, Err(_)); }); @@ -2896,7 +2812,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2942,7 +2857,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2969,7 +2883,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ), Err(_) ); @@ -3005,7 +2918,6 @@ mod tests { vec![], Some(&[0 ;32]), false, - None, ), Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address ); @@ -3060,7 +2972,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ), Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address ); @@ -3125,7 +3036,6 @@ mod tests { (min_balance * 10).into(), vec![], false, - None, ), Ok(_) ); @@ -3206,7 +3116,6 @@ mod tests { U256::zero(), vec![], false, - None, ), Ok(_) ); @@ -3250,7 +3159,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ), Err(Error::::TerminatedInConstructor.into()) ); @@ -3315,7 +3223,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -3378,7 +3285,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ); assert_matches!(result, Ok(_)); }); @@ -3425,113 +3331,11 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap(); }); } - #[test] - fn printing_works() { - let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); - exec_success() - }); - - let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let min_balance = ::Currency::minimum_balance(); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - vec![], - false, - Some(&mut debug_buffer), - ) - .unwrap(); - }); - - assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); - } - - #[test] - fn printing_works_on_fail() { - let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); - exec_trapped() - }); - - let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let min_balance = ::Currency::minimum_balance(); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - let result = MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - vec![], - false, - Some(&mut debug_buffer), - ); - assert!(result.is_err()); - }); - - assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); - } - - #[test] - fn debug_buffer_is_limited() { - let code_hash = MockLoader::insert(Call, move |ctx, _| { - ctx.ext.append_debug_buffer("overflowing bytes"); - exec_success() - }); - - // Pre-fill the buffer almost up to its limit, leaving not enough space to the message - let debug_buf_before = DebugBuffer::try_from(vec![0u8; DebugBuffer::bound() - 5]).unwrap(); - let mut debug_buf_after = debug_buf_before.clone(); - - ExtBuilder::default().build().execute_with(|| { - let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - vec![], - false, - Some(&mut debug_buf_after), - ) - .unwrap(); - assert_eq!(debug_buf_before, debug_buf_after); - }); - } - #[test] fn call_reentry_direct_recursion() { // call the contract passed as input with disabled reentry @@ -3559,7 +3363,6 @@ mod tests { U256::zero(), CHARLIE_ADDR.as_bytes().to_vec(), false, - None, )); // Calling into oneself fails @@ -3572,7 +3375,6 @@ mod tests { U256::zero(), BOB_ADDR.as_bytes().to_vec(), false, - None, ) .map_err(|e| e.error), >::ReentranceDenied, @@ -3623,7 +3425,6 @@ mod tests { U256::zero(), vec![0], false, - None, ) .map_err(|e| e.error), >::ReentranceDenied, @@ -3658,7 +3459,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap(); @@ -3743,7 +3543,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap(); @@ -3870,7 +3669,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ) .ok(); assert_eq!(System::account_nonce(&ALICE), 0); @@ -3884,7 +3682,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, )); assert_eq!(System::account_nonce(&ALICE), 1); @@ -3897,7 +3694,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, )); assert_eq!(System::account_nonce(&ALICE), 2); @@ -3910,7 +3706,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, )); assert_eq!(System::account_nonce(&ALICE), 3); }); @@ -3979,7 +3774,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4091,7 +3885,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4131,7 +3924,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4171,7 +3963,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4225,7 +4016,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4282,7 +4072,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4358,7 +4147,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4429,7 +4217,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4468,7 +4255,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4531,7 +4317,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4565,7 +4350,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4641,7 +4425,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4710,7 +4493,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4782,7 +4564,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4834,7 +4615,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4904,7 +4684,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4951,7 +4730,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4996,7 +4774,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -5052,7 +4829,6 @@ mod tests { U256::zero(), vec![0], false, - None, ), Ok(_) ); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index bdb4b92edd9e..403598ae136e 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -71,7 +71,7 @@ use frame_support::{ use frame_system::{ ensure_signed, pallet_prelude::{BlockNumberFor, OriginFor}, - EventRecord, Pallet as System, + Pallet as System, }; use pallet_transaction_payment::OnChargeTransaction; use scale_info::TypeInfo; @@ -98,9 +98,6 @@ type BalanceOf = <::Currency as Inspect<::AccountId>>::Balance; type OnChargeTransactionBalanceOf = <::OnChargeTransaction as OnChargeTransaction>::Balance; type CodeVec = BoundedVec>; -type EventRecordOf = - EventRecord<::RuntimeEvent, ::Hash>; -type DebugBuffer = BoundedVec>; type ImmutableData = BoundedVec>; /// Used as a sentinel value when reading and writing contract memory. @@ -258,9 +255,9 @@ pub mod pallet { #[pallet::no_default_bounds] type InstantiateOrigin: EnsureOrigin; - /// For most production chains, it's recommended to use the `()` implementation of this - /// trait. This implementation offers additional logging when the log target - /// "runtime::revive" is set to trace. + /// Debugging utilities for contracts. + /// For production chains, it's recommended to use the `()` implementation of this + /// trait. #[pallet::no_default_bounds] type Debug: Debugger; @@ -810,9 +807,8 @@ pub mod pallet { gas_limit, DepositLimit::Balance(storage_deposit_limit), data, - DebugInfo::Skip, - CollectEvents::Skip, ); + if let Ok(return_value) = &output.result { if return_value.did_revert() { output.result = Err(>::ContractReverted.into()); @@ -848,8 +844,6 @@ pub mod pallet { Code::Existing(code_hash), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -914,8 +908,6 @@ pub mod pallet { Code::Upload(code), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -1085,16 +1077,10 @@ where gas_limit: Weight, storage_deposit_limit: DepositLimit>, data: Vec, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf> { + ) -> ContractResult> { let mut gas_meter = GasMeter::new(gas_limit); let mut storage_deposit = Default::default(); - let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) { - Some(DebugBuffer::default()) - } else { - None - }; + let try_call = || { let origin = Origin::from_runtime_origin(origin)?; let mut storage_meter = match storage_deposit_limit { @@ -1109,7 +1095,6 @@ where Self::convert_native_to_evm(value), data, storage_deposit_limit.is_unchecked(), - debug_message.as_mut(), )?; storage_deposit = storage_meter .try_into_deposit(&origin, storage_deposit_limit.is_unchecked()) @@ -1119,18 +1104,11 @@ where Ok(result) }; let result = Self::run_guarded(try_call); - let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { - Some(System::::read_events_no_consensus().map(|e| *e).collect()) - } else { - None - }; ContractResult { result: result.map_err(|r| r.error), gas_consumed: gas_meter.gas_consumed(), gas_required: gas_meter.gas_required(), storage_deposit, - debug_message: debug_message.unwrap_or_default().to_vec(), - events, } } @@ -1138,8 +1116,7 @@ where /// /// Identical to [`Self::instantiate`] or [`Self::instantiate_with_code`] but tailored towards /// being called by other code within the runtime as opposed to from an extrinsic. It returns - /// more information and allows the enablement of features that are not suitable for an - /// extrinsic (debugging, event collection). + /// more information to the caller useful to estimate the cost of the operation. pub fn bare_instantiate( origin: OriginFor, value: BalanceOf, @@ -1148,14 +1125,9 @@ where code: Code, data: Vec, salt: Option<[u8; 32]>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf> { + ) -> ContractResult> { let mut gas_meter = GasMeter::new(gas_limit); let mut storage_deposit = Default::default(); - let mut debug_message = - if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None }; - let unchecked_deposit_limit = storage_deposit_limit.is_unchecked(); let mut storage_deposit_limit = match storage_deposit_limit { DepositLimit::Balance(limit) => limit, @@ -1195,7 +1167,6 @@ where data, salt.as_ref(), unchecked_deposit_limit, - debug_message.as_mut(), ); storage_deposit = storage_meter .try_into_deposit(&instantiate_origin, unchecked_deposit_limit)? @@ -1203,11 +1174,6 @@ where result }; let output = Self::run_guarded(try_instantiate); - let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { - Some(System::::read_events_no_consensus().map(|e| *e).collect()) - } else { - None - }; ContractResult { result: output .map(|(addr, result)| InstantiateReturnValue { result, addr }) @@ -1215,8 +1181,6 @@ where gas_consumed: gas_meter.gas_consumed(), gas_required: gas_meter.gas_required(), storage_deposit, - debug_message: debug_message.unwrap_or_default().to_vec(), - events, } } @@ -1273,8 +1237,6 @@ where }; let input = tx.input.clone().unwrap_or_default().0; - let debug = DebugInfo::Skip; - let collect_events = CollectEvents::Skip; let extract_error = |err| { if err == Error::::TransferFailed.into() || @@ -1305,8 +1267,6 @@ where gas_limit, storage_deposit_limit, input.clone(), - debug, - collect_events, ); let data = match result.result { @@ -1363,8 +1323,6 @@ where Code::Upload(code.to_vec()), data.to_vec(), None, - debug, - collect_events, ); let returned_data = match result.result { @@ -1535,12 +1493,11 @@ environmental!(executing_contract: bool); sp_api::decl_runtime_apis! { /// The API used to dry-run contract interactions. #[api_version(1)] - pub trait ReviveApi where + pub trait ReviveApi where AccountId: Codec, Balance: Codec, Nonce: Codec, BlockNumber: Codec, - EventRecord: Codec, { /// Returns the free balance of the given `[H160]` address, using EVM decimals. fn balance(address: H160) -> U256; @@ -1558,7 +1515,7 @@ sp_api::decl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> ContractResult; + ) -> ContractResult; /// Instantiate a new contract. /// @@ -1571,7 +1528,7 @@ sp_api::decl_runtime_apis! { code: Code, data: Vec, salt: Option<[u8; 32]>, - ) -> ContractResult; + ) -> ContractResult; /// Perform an Ethereum call. diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 3b55106c67d8..f101abf0ea7e 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -57,11 +57,6 @@ pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024; /// The maximum allowable length in bytes for (transient) storage keys. pub const STORAGE_KEY_BYTES: u32 = 128; -/// The maximum size of the debug buffer contracts can write messages to. -/// -/// The buffer will always be disabled for on-chain execution. -pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024; - /// The page size in which PolkaVM should allocate memory chunks. pub const PAGE_SIZE: u32 = 4 * 1024; diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 452d2c8a3067..9c149c7cc389 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -63,7 +63,7 @@ impl From for DepositLimit { /// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data /// should be ignored to avoid any potential compatibility issues. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct ContractResult { +pub struct ContractResult { /// How much weight was consumed during execution. pub gas_consumed: Weight, /// How much weight is required as gas limit in order to execute this call. @@ -84,26 +84,8 @@ pub struct ContractResult { /// is `Err`. This is because on error all storage changes are rolled back including the /// payment of the deposit. pub storage_deposit: StorageDeposit, - /// An optional debug message. This message is only filled when explicitly requested - /// by the code that calls into the contract. Otherwise it is empty. - /// - /// The contained bytes are valid UTF-8. This is not declared as `String` because - /// this type is not allowed within the runtime. - /// - /// Clients should not make any assumptions about the format of the buffer. - /// They should just display it as-is. It is **not** only a collection of log lines - /// provided by a contract but a formatted buffer with different sections. - /// - /// # Note - /// - /// The debug message is never generated during on-chain execution. It is reserved for - /// RPC calls. - pub debug_message: Vec, /// The execution result of the wasm code. pub result: Result, - /// The events that were emitted during execution. It is an option as event collection is - /// optional. - pub events: Option>, } /// The result of the execution of a `eth_transact` call. @@ -284,36 +266,3 @@ where } } } - -/// Determines whether events should be collected during execution. -#[derive( - Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, -)] -pub enum CollectEvents { - /// Collect events. - /// - /// # Note - /// - /// Events should only be collected when called off-chain, as this would otherwise - /// collect all the Events emitted in the block so far and put them into the PoV. - /// - /// **Never** use this mode for on-chain execution. - UnsafeCollect, - /// Skip event collection. - Skip, -} - -/// Determines whether debug messages will be collected. -#[derive( - Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, -)] -pub enum DebugInfo { - /// Collect debug messages. - /// # Note - /// - /// This should only ever be set to `UnsafeDebug` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - UnsafeDebug, - /// Skip collection of debug messages. - Skip, -} diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index 8ba5e7384070..7fbb5b676439 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -17,9 +17,8 @@ use super::{deposit_limit, GAS_LIMIT}; use crate::{ - address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config, ContractResult, - DebugInfo, DepositLimit, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, - Pallet, Weight, + address::AddressMapper, AccountIdOf, BalanceOf, Code, Config, ContractResult, DepositLimit, + ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, }; use frame_support::pallet_prelude::DispatchResultWithPostInfo; use paste::paste; @@ -138,9 +137,7 @@ builder!( code: Code, data: Vec, salt: Option<[u8; 32]>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf>; + ) -> ContractResult>; /// Build the instantiate call and unwrap the result. pub fn build_and_unwrap_result(self) -> InstantiateReturnValue { @@ -164,8 +161,6 @@ builder!( code, data: vec![], salt: Some([0; 32]), - debug: DebugInfo::UnsafeDebug, - collect_events: CollectEvents::Skip, } } ); @@ -201,9 +196,7 @@ builder!( gas_limit: Weight, storage_deposit_limit: DepositLimit>, data: Vec, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf>; + ) -> ContractResult>; /// Build the call and unwrap the result. pub fn build_and_unwrap_result(self) -> ExecReturnValue { @@ -219,8 +212,6 @@ builder!( gas_limit: GAS_LIMIT, storage_deposit_limit: DepositLimit::Balance(deposit_limit::()), data: vec![], - debug: DebugInfo::UnsafeDebug, - collect_events: CollectEvents::Skip, } } ); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index cf02d17a4d03..e2b30cf07c8d 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -38,9 +38,9 @@ use crate::{ tests::test_utils::{get_contract, get_contract_checked}, wasm::Memory, weights::WeightInfo, - AccountId32Mapper, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo, - ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, EthTransactError, - HoldReason, Origin, Pallet, PristineCode, H160, + AccountId32Mapper, BalanceOf, Code, CodeInfoOf, Config, ContractInfo, ContractInfoOf, + DeletionQueueCounter, DepositLimit, Error, EthTransactError, HoldReason, Origin, Pallet, + PristineCode, H160, }; use crate::test_utils::builder::Contract; @@ -2184,58 +2184,6 @@ fn refcounter() { }); } -#[test] -fn debug_message_works() { - let (wasm, _code_hash) = compile_module("debug_message_works").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); - - assert_matches!(result.result, Ok(_)); - assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); - }); -} - -#[test] -fn debug_message_logging_disabled() { - let (wasm, _code_hash) = compile_module("debug_message_logging_disabled").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - // the dispatchables always run without debugging - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr, - 0, - GAS_LIMIT, - deposit_limit::(), - vec![] - )); - }); -} - -#[test] -fn debug_message_invalid_utf8() { - let (wasm, _code_hash) = compile_module("debug_message_invalid_utf8").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); - assert_ok!(result.result); - assert!(result.debug_message.is_empty()); - }); -} - #[test] fn gas_estimation_for_subcalls() { let (caller_code, _caller_hash) = compile_module("call_with_limit").unwrap(); @@ -2451,79 +2399,6 @@ fn ecdsa_recover() { }) } -#[test] -fn bare_instantiate_returns_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let result = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .collect_events(CollectEvents::UnsafeCollect) - .build(); - - let events = result.events.unwrap(); - assert!(!events.is_empty()); - assert_eq!(events, System::events()); - }); -} - -#[test] -fn bare_instantiate_does_not_return_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let result = builder::bare_instantiate(Code::Upload(wasm)).value(min_balance * 100).build(); - - let events = result.events; - assert!(!System::events().is_empty()); - assert!(events.is_none()); - }); -} - -#[test] -fn bare_call_returns_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr).collect_events(CollectEvents::UnsafeCollect).build(); - - let events = result.events.unwrap(); - assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); - assert!(!events.is_empty()); - assert_eq!(events, System::events()); - }); -} - -#[test] -fn bare_call_does_not_return_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr).build(); - - let events = result.events; - assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); - assert!(!System::events().is_empty()); - assert!(events.is_none()); - }); -} - #[test] fn sr25519_verify() { let (wasm, _code_hash) = compile_module("sr25519_verify").unwrap(); @@ -3327,14 +3202,11 @@ fn set_code_hash() { // First call sets new code_hash and returns 1 let result = builder::bare_call(contract_addr) .data(new_code_hash.as_ref().to_vec()) - .debug(DebugInfo::UnsafeDebug) .build_and_unwrap_result(); assert_return_code!(result, 1); // Second calls new contract code that returns 2 - let result = builder::bare_call(contract_addr) - .debug(DebugInfo::UnsafeDebug) - .build_and_unwrap_result(); + let result = builder::bare_call(contract_addr).build_and_unwrap_result(); assert_return_code!(result, 2); // Checking for the last event only @@ -4810,7 +4682,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, ), EthTransactError::Message(format!( "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" @@ -4825,7 +4697,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, )); let Contract { addr, .. } = @@ -4844,7 +4716,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, ), EthTransactError::Message(format!( "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" @@ -4869,7 +4741,7 @@ fn skip_transfer_works() { assert_ok!(Pallet::::bare_eth_transact( GenericTransaction { from: Some(BOB_ADDR), to: Some(addr), ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, )); // works when calling from a contract when no gas is specified. @@ -4881,7 +4753,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, )); }); } diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs index c9e19e52ace1..b1fdb2d47441 100644 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ b/substrate/frame/revive/src/tests/test_debug.rs @@ -119,8 +119,6 @@ fn debugging_works() { Code::Upload(wasm), vec![], Some([0u8; 32]), - DebugInfo::Skip, - CollectEvents::Skip, ) .result .unwrap() @@ -204,8 +202,6 @@ fn call_interception_works() { vec![], // some salt to ensure that the address of this contract is unique among all tests Some([0x41; 32]), - DebugInfo::Skip, - CollectEvents::Skip, ) .result .unwrap() diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index 3bd4bde5679f..b45d7026ba91 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -288,7 +288,7 @@ impl WasmBlob { } let engine = polkavm::Engine::new(&config).expect( "on-chain (no_std) use of interpreter is hard coded. - interpreter is available on all plattforms; qed", + interpreter is available on all platforms; qed", ); let mut module_config = polkavm::ModuleConfig::new(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 8529c7d9e73b..1ff6a80840a9 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -339,8 +339,6 @@ pub enum RuntimeCosts { Terminate(u32), /// Weight of calling `seal_deposit_event` with the given number of topics and event size. DepositEvent { num_topic: u32, len: u32 }, - /// Weight of calling `seal_debug_message` per byte of passed message. - DebugMessage(u32), /// Weight of calling `seal_set_storage` for the given storage item sizes. SetStorage { old_bytes: u32, new_bytes: u32 }, /// Weight of calling `seal_clear_storage` per cleared byte. @@ -489,7 +487,6 @@ impl Token for RuntimeCosts { WeightToFee => T::WeightInfo::seal_weight_to_fee(), Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies), DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), - DebugMessage(len) => T::WeightInfo::seal_debug_message(len), SetStorage { new_bytes, old_bytes } => { cost_storage!(write, seal_set_storage, new_bytes, old_bytes) }, @@ -669,10 +666,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { match result { Ok(_) => Ok(ReturnErrorCode::Success), Err(e) => { - if self.ext.debug_buffer_enabled() { - self.ext.append_debug_buffer("call failed with: "); - self.ext.append_debug_buffer(e.into()); - }; + log::debug!(target: LOG_TARGET, "call failed with: {e:?}"); Ok(ErrorReturnCode::get()) }, } @@ -1832,27 +1826,6 @@ pub mod env { self.contains_storage(memory, flags, key_ptr, key_len) } - /// Emit a custom debug message. - /// See [`pallet_revive_uapi::HostFn::debug_message`]. - fn debug_message( - &mut self, - memory: &mut M, - str_ptr: u32, - str_len: u32, - ) -> Result { - let str_len = str_len.min(limits::DEBUG_BUFFER_BYTES); - self.charge_gas(RuntimeCosts::DebugMessage(str_len))?; - if self.ext.append_debug_buffer("") { - let data = memory.read(str_ptr, str_len)?; - if let Some(msg) = core::str::from_utf8(&data).ok() { - self.ext.append_debug_buffer(msg); - } - Ok(ReturnErrorCode::Success) - } else { - Ok(ReturnErrorCode::LoggingDisabled) - } - } - /// Recovers the ECDSA public key from the given message hash and signature. /// See [`pallet_revive_uapi::HostFn::ecdsa_recover`]. fn ecdsa_recover( @@ -2162,10 +2135,7 @@ pub mod env { Ok(ReturnErrorCode::Success) }, Err(e) => { - if self.ext.append_debug_buffer("") { - self.ext.append_debug_buffer("seal0::xcm_send failed with: "); - self.ext.append_debug_buffer(e.into()); - }; + log::debug!(target: LOG_TARGET, "seal0::xcm_send failed with: {e:?}"); Ok(ReturnErrorCode::XcmSendFailed) }, } diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index e35ba5ca0766..06495d5d21aa 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -96,7 +96,6 @@ pub trait WeightInfo { fn seal_return(n: u32, ) -> Weight; fn seal_terminate(n: u32, ) -> Weight; fn seal_deposit_event(t: u32, n: u32, ) -> Weight; - fn seal_debug_message(i: u32, ) -> Weight; fn get_storage_empty() -> Weight; fn get_storage_full() -> Weight; fn set_storage_empty() -> Weight; @@ -643,16 +642,6 @@ impl WeightInfo for SubstrateWeight { // Standard Error: 34 .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) } - /// The range of component `i` is `[0, 262144]`. - fn seal_debug_message(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 340_000 picoseconds. - Weight::from_parts(306_527, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into())) - } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { @@ -1539,16 +1528,6 @@ impl WeightInfo for () { // Standard Error: 34 .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) } - /// The range of component `i` is `[0, 262144]`. - fn seal_debug_message(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 340_000 picoseconds. - Weight::from_parts(306_527, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into())) - } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index ba0a63b15c37..b82393826ddf 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -515,26 +515,6 @@ pub trait HostFn: private::Sealed { #[unstable_hostfn] fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option; - /// Emit a custom debug message. - /// - /// No newlines are added to the supplied message. - /// Specifying invalid UTF-8 just drops the message with no trap. - /// - /// This is a no-op if debug message recording is disabled which is always the case - /// when the code is executing on-chain. The message is interpreted as UTF-8 and - /// appended to the debug buffer which is then supplied to the calling RPC client. - /// - /// # Note - /// - /// Even though no action is taken when debug message recording is disabled there is still - /// a non trivial overhead (and weight cost) associated with calling this function. Contract - /// languages should remove calls to this function (either at runtime or compile time) when - /// not being executed as an RPC. For example, they could allow users to disable logging - /// through compile time flags (cargo features) for on-chain deployment. Additionally, the - /// return value of this function can be cached in order to prevent further calls at runtime. - #[unstable_hostfn] - fn debug_message(str: &[u8]) -> Result; - /// Recovers the ECDSA public key from the given message hash and signature. /// /// Writes the public key into the given output buffer. diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 8c40bc9f48ea..0023b8aa721d 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -109,7 +109,6 @@ mod sys { out_ptr: *mut u8, out_len_ptr: *mut u32, ) -> ReturnCode; - pub fn debug_message(str_ptr: *const u8, str_len: u32) -> ReturnCode; pub fn call_runtime(call_ptr: *const u8, call_len: u32) -> ReturnCode; pub fn ecdsa_recover( signature_ptr: *const u8, @@ -519,12 +518,6 @@ impl HostFn for HostFnImpl { ret_code.into() } - #[unstable_hostfn] - fn debug_message(str: &[u8]) -> Result { - let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; - ret_code.into() - } - #[unstable_hostfn] fn ecdsa_recover( signature: &[u8; 65], diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs index ef1798b4bf61..867f35633987 100644 --- a/substrate/frame/revive/uapi/src/lib.rs +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -86,9 +86,8 @@ define_error_codes! { /// Transfer failed for other not further specified reason. Most probably /// reserved or locked balance of the sender that was preventing the transfer. TransferFailed = 4, - /// The call to `debug_message` had no effect because debug message - /// recording was disabled. - LoggingDisabled = 5, + /// The subcall ran out of weight or storage deposit. + OutOfResources = 5, /// The call dispatched by `call_runtime` was executed but returned an error. CallRuntimeFailed = 6, /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. @@ -99,8 +98,6 @@ define_error_codes! { XcmExecutionFailed = 9, /// The `xcm_send` call failed. XcmSendFailed = 10, - /// The subcall ran out of weight or storage deposit. - OutOfResources = 11, } /// The raw return code returned by the host side. From 5be65872188a4ac1bf76333af3958b65f2a9629e Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 15 Jan 2025 20:23:54 +0100 Subject: [PATCH 3/6] [pallet-revive] Remove revive events (#7164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove all pallet::events except for the `ContractEmitted` event that is emitted by contracts --------- Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen --- prdoc/pr_7164.prdoc | 8 + substrate/frame/revive/src/exec.rs | 103 +------ substrate/frame/revive/src/lib.rs | 72 ----- substrate/frame/revive/src/storage/meter.rs | 16 +- substrate/frame/revive/src/tests.rs | 323 +------------------- substrate/frame/revive/src/wasm/mod.rs | 18 +- 6 files changed, 40 insertions(+), 500 deletions(-) create mode 100644 prdoc/pr_7164.prdoc diff --git a/prdoc/pr_7164.prdoc b/prdoc/pr_7164.prdoc new file mode 100644 index 000000000000..cb0410a9de79 --- /dev/null +++ b/prdoc/pr_7164.prdoc @@ -0,0 +1,8 @@ +title: '[pallet-revive] Remove revive events' +doc: +- audience: Runtime Dev + description: Remove all pallet::events except for the `ContractEmitted` event that + is emitted by contracts +crates: +- name: pallet-revive + bump: major diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index e20c5dd7786e..1c6ca435aefb 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1092,34 +1092,11 @@ where .enforce_limit(contract) .map_err(|e| ExecError { error: e, origin: ErrorOrigin::Callee })?; - let account_id = T::AddressMapper::to_address(&frame.account_id); - match (entry_point, delegated_code_hash) { - (ExportedFunction::Constructor, _) => { - // It is not allowed to terminate a contract inside its constructor. - if matches!(frame.contract_info, CachedContract::Terminated) { - return Err(Error::::TerminatedInConstructor.into()); - } - - let caller = T::AddressMapper::to_address(self.caller().account_id()?); - // Deposit an instantiation event. - Contracts::::deposit_event(Event::Instantiated { - deployer: caller, - contract: account_id, - }); - }, - (ExportedFunction::Call, Some(code_hash)) => { - Contracts::::deposit_event(Event::DelegateCalled { - contract: account_id, - code_hash, - }); - }, - (ExportedFunction::Call, None) => { - let caller = self.caller(); - Contracts::::deposit_event(Event::Called { - caller: caller.clone(), - contract: account_id, - }); - }, + // It is not allowed to terminate a contract inside its constructor. + if entry_point == ExportedFunction::Constructor && + matches!(frame.contract_info, CachedContract::Terminated) + { + return Err(Error::::TerminatedInConstructor.into()); } Ok(output) @@ -1526,10 +1503,6 @@ where .charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(*deposit)); } - Contracts::::deposit_event(Event::Terminated { - contract: account_address, - beneficiary: *beneficiary, - }); Ok(()) } @@ -1782,11 +1755,6 @@ where Self::increment_refcount(hash)?; Self::decrement_refcount(prev_hash); - Contracts::::deposit_event(Event::ContractCodeUpdated { - contract: T::AddressMapper::to_address(&frame.account_id), - new_code_hash: hash, - old_code_hash: prev_hash, - }); Ok(()) } @@ -2933,13 +2901,6 @@ mod tests { ContractInfo::::load_code_hash(&instantiated_contract_id).unwrap(), dummy_ch ); - assert_eq!( - &events(), - &[Event::Instantiated { - deployer: ALICE_ADDR, - contract: instantiated_contract_address - }] - ); }); } @@ -3055,19 +3016,6 @@ mod tests { ContractInfo::::load_code_hash(&instantiated_contract_id).unwrap(), dummy_ch ); - assert_eq!( - &events(), - &[ - Event::Instantiated { - deployer: BOB_ADDR, - contract: instantiated_contract_address - }, - Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR - }, - ] - ); }); } @@ -3119,13 +3067,6 @@ mod tests { ), Ok(_) ); - - // The contract wasn't instantiated so we don't expect to see an instantiation - // event here. - assert_eq!( - &events(), - &[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB_ADDR },] - ); }); } @@ -3465,24 +3406,14 @@ mod tests { let remark_hash = ::Hashing::hash(b"Hello World"); assert_eq!( System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::System(frame_system::Event::Remarked { - sender: BOB_FALLBACK, - hash: remark_hash - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR, - }), - topics: vec![], - }, - ] + vec![EventRecord { + phase: Phase::Initialization, + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB_FALLBACK, + hash: remark_hash + }), + topics: vec![], + },] ); }); } @@ -3571,14 +3502,6 @@ mod tests { },), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR, - }), - topics: vec![], - }, ] ); }); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 403598ae136e..a9f2842c35f6 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -379,25 +379,6 @@ pub mod pallet { #[pallet::event] pub enum Event { - /// Contract deployed by address at the specified address. - Instantiated { deployer: H160, contract: H160 }, - - /// Contract has been removed. - /// - /// # Note - /// - /// The only way for a contract to be removed and emitting this event is by calling - /// `seal_terminate`. - Terminated { - /// The contract that was terminated. - contract: H160, - /// The account that received the contracts remaining balance - beneficiary: H160, - }, - - /// Code with the specified hash has been stored. - CodeStored { code_hash: H256, deposit_held: BalanceOf, uploader: H160 }, - /// A custom event emitted by the contract. ContractEmitted { /// The contract that emitted the event. @@ -409,54 +390,6 @@ pub mod pallet { /// Number of topics is capped by [`limits::NUM_EVENT_TOPICS`]. topics: Vec, }, - - /// A code with the specified hash was removed. - CodeRemoved { code_hash: H256, deposit_released: BalanceOf, remover: H160 }, - - /// A contract's code was updated. - ContractCodeUpdated { - /// The contract that has been updated. - contract: H160, - /// New code hash that was set for the contract. - new_code_hash: H256, - /// Previous code hash of the contract. - old_code_hash: H256, - }, - - /// A contract was called either by a plain account or another contract. - /// - /// # Note - /// - /// Please keep in mind that like all events this is only emitted for successful - /// calls. This is because on failure all storage changes including events are - /// rolled back. - Called { - /// The caller of the `contract`. - caller: Origin, - /// The contract that was called. - contract: H160, - }, - - /// A contract delegate called a code hash. - /// - /// # Note - /// - /// Please keep in mind that like all events this is only emitted for successful - /// calls. This is because on failure all storage changes including events are - /// rolled back. - DelegateCalled { - /// The contract that performed the delegate call and hence in whose context - /// the `code_hash` is executed. - contract: H160, - /// The code hash that was delegate called. - code_hash: H256, - }, - - /// Some funds have been transferred and held as storage deposit. - StorageDepositTransferredAndHeld { from: H160, to: H160, amount: BalanceOf }, - - /// Some storage deposit funds have been transferred and released. - StorageDepositTransferredAndReleased { from: H160, to: H160, amount: BalanceOf }, } #[pallet::error] @@ -985,11 +918,6 @@ pub mod pallet { }; >>::increment_refcount(code_hash)?; >>::decrement_refcount(contract.code_hash); - Self::deposit_event(Event::ContractCodeUpdated { - contract: dest, - new_code_hash: code_hash, - old_code_hash: contract.code_hash, - }); contract.code_hash = code_hash; Ok(()) }) diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index 4febcb0c4066..cd390c86f63a 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -18,8 +18,8 @@ //! This module contains functions to meter the storage deposit. use crate::{ - address::AddressMapper, storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, - Event, HoldReason, Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, + storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, HoldReason, Inspect, + Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData}; @@ -516,12 +516,6 @@ impl Ext for ReservingExt { Preservation::Preserve, Fortitude::Polite, )?; - - Pallet::::deposit_event(Event::StorageDepositTransferredAndHeld { - from: T::AddressMapper::to_address(origin), - to: T::AddressMapper::to_address(contract), - amount: *amount, - }); }, Deposit::Refund(amount) => { let transferred = T::Currency::transfer_on_hold( @@ -534,12 +528,6 @@ impl Ext for ReservingExt { Fortitude::Polite, )?; - Pallet::::deposit_event(Event::StorageDepositTransferredAndReleased { - from: T::AddressMapper::to_address(contract), - to: T::AddressMapper::to_address(origin), - amount: transferred, - }); - if transferred < *amount { // This should never happen, if it does it means that there is a bug in the // runtime logic. In the rare case this happens we try to refund as much as we diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index e2b30cf07c8d..35940f544d00 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -713,25 +713,6 @@ fn instantiate_and_call_and_deposit_event() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -1078,14 +1059,6 @@ fn deploy_and_call_other_contract() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: caller_addr, - contract: callee_addr, - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { @@ -1095,33 +1068,6 @@ fn deploy_and_call_other_contract() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(caller_account.clone()), - contract: callee_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: caller_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: callee_addr, - amount: test_utils::contract_info_storage_deposit(&callee_addr), - } - ), - topics: vec![], - }, ] ); }); @@ -1373,8 +1319,6 @@ fn self_destruct_works() { // Check that the BOB contract has been instantiated. let _ = get_contract(&contract.addr); - let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr); - // Drop all previous events initialize_block(2); @@ -1404,33 +1348,6 @@ fn self_destruct_works() { pretty_assertions::assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Terminated { - contract: contract.addr, - beneficiary: DJANGO_ADDR, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract.addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: contract.addr, - to: ALICE_ADDR, - amount: info_deposit, - } - ), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::KilledAccount { @@ -2512,23 +2429,9 @@ fn upload_code_works() { initialize_block(2); assert!(!PristineCode::::contains_key(&code_hash)); - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); - - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - },] - ); + expected_deposit(ensure_stored(code_hash)); }); } @@ -2586,32 +2489,8 @@ fn remove_code_works() { assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); - + expected_deposit(ensure_stored(code_hash)); assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { - code_hash, - deposit_released: deposit_expected, - remover: ALICE_ADDR - }), - topics: vec![], - }, - ] - ); }); } @@ -2627,25 +2506,12 @@ fn remove_code_wrong_origin() { assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); assert_noop!( Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), sp_runtime::traits::BadOrigin, ); - - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - },] - ); }); } @@ -2704,7 +2570,7 @@ fn instantiate_with_zero_balance_works() { builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though no free balance was send assert_eq!(::Currency::free_balance(&account_id), min_balance); @@ -2716,15 +2582,6 @@ fn instantiate_with_zero_balance_works() { assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { @@ -2749,25 +2606,6 @@ fn instantiate_with_zero_balance_works() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -2790,7 +2628,7 @@ fn instantiate_with_below_existential_deposit_works() { .build_and_unwrap_contract(); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though not enough free balance was send assert_eq!(::Currency::free_balance(&account_id), min_balance + value); assert_eq!( @@ -2801,15 +2639,6 @@ fn instantiate_with_below_existential_deposit_works() { assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { @@ -2843,25 +2672,6 @@ fn instantiate_with_below_existential_deposit_works() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -2903,74 +2713,15 @@ fn storage_deposit_works() { assert_eq!( System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: 42, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged0, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged1, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: addr, - to: ALICE_ADDR, - amount: refunded0, - } - ), - topics: vec![], - }, - ] + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: 42, + }), + topics: vec![], + },] ); }); } @@ -3063,18 +2814,6 @@ fn set_code_extrinsic() { assert_eq!(get_contract(&addr).code_hash, new_code_hash); assert_refcount!(&code_hash, 0); assert_refcount!(&new_code_hash, 1); - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(pallet_revive::Event::ContractCodeUpdated { - contract: addr, - new_code_hash, - old_code_hash: code_hash, - }), - topics: vec![], - },] - ); }); } @@ -3180,7 +2919,7 @@ fn contract_reverted() { #[test] fn set_code_hash() { - let (wasm, code_hash) = compile_module("set_code_hash").unwrap(); + let (wasm, _) = compile_module("set_code_hash").unwrap(); let (new_wasm, new_code_hash) = compile_module("new_set_code_hash_contract").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { @@ -3208,38 +2947,6 @@ fn set_code_hash() { // Second calls new contract code that returns 2 let result = builder::bare_call(contract_addr).build_and_unwrap_result(); assert_return_code!(result, 2); - - // Checking for the last event only - assert_eq!( - &System::events(), - &[ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated { - contract: contract_addr, - new_code_hash, - old_code_hash: code_hash, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract_addr, - }), - topics: vec![], - }, - ], - ); }); } diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index b45d7026ba91..527cf1630954 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -29,14 +29,13 @@ pub use crate::wasm::runtime::{ReturnData, TrapReason}; pub use crate::wasm::runtime::{Memory, Runtime, RuntimeCosts}; use crate::{ - address::AddressMapper, exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::{GasMeter, Token}, limits, storage::meter::Diff, weights::WeightInfo, - AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, Event, ExecError, - HoldReason, Pallet, PristineCode, Weight, LOG_TARGET, + AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, ExecError, HoldReason, + PristineCode, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; @@ -157,16 +156,9 @@ where code_info.deposit, BestEffort, ); - let deposit_released = code_info.deposit; - let remover = T::AddressMapper::to_address(&code_info.owner); *existing = None; >::remove(&code_hash); - >::deposit_event(Event::CodeRemoved { - code_hash, - deposit_released, - remover, - }); Ok(()) } else { Err(>::CodeNotFound.into()) @@ -202,12 +194,6 @@ where self.code_info.refcount = 0; >::insert(code_hash, &self.code); *stored_code_info = Some(self.code_info.clone()); - let uploader = T::AddressMapper::to_address(&self.code_info.owner); - >::deposit_event(Event::CodeStored { - code_hash, - deposit_held: deposit, - uploader, - }); Ok(deposit) }, } From 412aca6c48a01f11318228f4d8a79fec544a22bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Wed, 15 Jan 2025 22:58:51 +0100 Subject: [PATCH 4/6] [pallet-revive] Add host function `to_account_id` (#7091) Closes https://github.com/paritytech/polkadot-sdk/issues/6891. cc @athei @xermicus @pgherveou --- prdoc/pr_7091.prdoc | 12 ++++++ .../fixtures/contracts/to_account_id.rs | 40 ++++++++++++++++++ .../frame/revive/src/benchmarking/mod.rs | 32 +++++++++++++++ substrate/frame/revive/src/exec.rs | 41 +++++++++++++++++++ substrate/frame/revive/src/tests.rs | 37 +++++++++++++++++ substrate/frame/revive/src/wasm/runtime.rs | 24 +++++++++++ substrate/frame/revive/src/weights.rs | 21 ++++++++++ substrate/frame/revive/uapi/src/host.rs | 12 ++++++ .../frame/revive/uapi/src/host/riscv64.rs | 6 +++ 9 files changed, 225 insertions(+) create mode 100644 prdoc/pr_7091.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/to_account_id.rs diff --git a/prdoc/pr_7091.prdoc b/prdoc/pr_7091.prdoc new file mode 100644 index 000000000000..badea4e82fdb --- /dev/null +++ b/prdoc/pr_7091.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] Add new host function `to_account_id`' +doc: +- audience: Runtime Dev + description: A new host function `to_account_id` is added. It allows retrieving + the account id for a `H160` address. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/to_account_id.rs b/substrate/frame/revive/fixtures/contracts/to_account_id.rs new file mode 100644 index 000000000000..c2a8fce3ec99 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/to_account_id.rs @@ -0,0 +1,40 @@ +// 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. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + address: &[u8; 20], + expected_account_id: &[u8; 32], + ); + + let mut account_id = [0u8; 32]; + api::to_account_id(address, &mut account_id); + + assert!(&account_id == expected_account_id); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index e23554f21ba8..18d7bb0afc31 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -556,6 +556,38 @@ mod benchmarks { assert_eq!(result.unwrap(), 1); } + #[benchmark(pov_mode = Measured)] + fn seal_to_account_id() { + // use a mapped address for the benchmark, to ensure that we bench the worst + // case (and not the fallback case). + let address = { + let caller = account("seal_to_account_id", 0, 0); + T::Currency::set_balance(&caller, caller_funding::()); + T::AddressMapper::map(&caller).unwrap(); + T::AddressMapper::to_address(&caller) + }; + + let len = ::max_encoded_len(); + build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]); + + let result; + #[block] + { + result = runtime.bench_to_account_id(memory.as_mut_slice(), len as u32, 0); + } + + assert_ok!(result); + assert_ne!( + memory.as_slice()[20..32], + [0xEE; 12], + "fallback suffix found where none should be" + ); + assert_eq!( + T::AccountId::decode(&mut memory.as_slice()), + Ok(runtime.ext().to_account_id(&address)) + ); + } + #[benchmark(pov_mode = Measured)] fn seal_code_hash() { let contract = Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 1c6ca435aefb..f696f75a4a13 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -293,6 +293,9 @@ pub trait Ext: sealing::Sealed { /// Check if a contract lives at the specified `address`. fn is_contract(&self, address: &H160) -> bool; + /// Returns the account id for the given `address`. + fn to_account_id(&self, address: &H160) -> AccountIdOf; + /// Returns the code hash of the contract for the given `address`. /// If not a contract but account exists then `keccak_256([])` is returned, otherwise `zero`. fn code_hash(&self, address: &H160) -> H256; @@ -1572,6 +1575,10 @@ where ContractInfoOf::::contains_key(&address) } + fn to_account_id(&self, address: &H160) -> T::AccountId { + T::AddressMapper::to_account_id(address) + } + fn code_hash(&self, address: &H160) -> H256 { >::get(&address) .map(|contract| contract.code_hash) @@ -2582,6 +2589,40 @@ mod tests { }); } + #[test] + fn to_account_id_returns_proper_values() { + let bob_code_hash = MockLoader::insert(Call, |ctx, _| { + let alice_account_id = ::AddressMapper::to_account_id(&ALICE_ADDR); + assert_eq!(ctx.ext.to_account_id(&ALICE_ADDR), alice_account_id); + + const UNMAPPED_ADDR: H160 = H160([99u8; 20]); + let mut unmapped_fallback_account_id = [0xEE; 32]; + unmapped_fallback_account_id[..20].copy_from_slice(UNMAPPED_ADDR.as_bytes()); + assert_eq!( + ctx.ext.to_account_id(&UNMAPPED_ADDR), + AccountId32::new(unmapped_fallback_account_id) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_code_hash); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); + let result = MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + U256::zero(), + vec![0], + false, + ); + assert_matches!(result, Ok(_)); + }); + } + #[test] fn code_hash_returns_proper_values() { let bob_code_hash = MockLoader::insert(Call, |ctx, _| { diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 35940f544d00..8398bc2cb66f 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4239,6 +4239,43 @@ fn origin_api_works() { }); } +#[test] +fn to_account_id_works() { + let (code_hash_code, _) = compile_module("to_account_id").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&EVE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract(); + + // mapped account + >::map_account(RuntimeOrigin::signed(EVE)).unwrap(); + let expected_mapped_account_id = &::AddressMapper::to_account_id(&EVE_ADDR); + assert_ne!( + expected_mapped_account_id.encode()[20..32], + [0xEE; 12], + "fallback suffix found where none should be" + ); + assert_ok!(builder::call(addr) + .data((EVE_ADDR, expected_mapped_account_id).encode()) + .build()); + + // fallback for unmapped accounts + let expected_fallback_account_id = + &::AddressMapper::to_account_id(&BOB_ADDR); + assert_eq!( + expected_fallback_account_id.encode()[20..32], + [0xEE; 12], + "no fallback suffix found where one should be" + ); + assert_ok!(builder::call(addr) + .data((BOB_ADDR, expected_fallback_account_id).encode()) + .build()); + }); +} + #[test] fn code_hash_works() { let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 1ff6a80840a9..4fbcfe1b47f5 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -293,6 +293,8 @@ pub enum RuntimeCosts { CallDataSize, /// Weight of calling `seal_return_data_size`. ReturnDataSize, + /// Weight of calling `seal_to_account_id`. + ToAccountId, /// Weight of calling `seal_origin`. Origin, /// Weight of calling `seal_is_contract`. @@ -466,6 +468,7 @@ impl Token for RuntimeCosts { Caller => T::WeightInfo::seal_caller(), Origin => T::WeightInfo::seal_origin(), IsContract => T::WeightInfo::seal_is_contract(), + ToAccountId => T::WeightInfo::seal_to_account_id(), CodeHash => T::WeightInfo::seal_code_hash(), CodeSize => T::WeightInfo::seal_code_size(), OwnCodeHash => T::WeightInfo::seal_own_code_hash(), @@ -2140,4 +2143,25 @@ pub mod env { }, } } + + /// Retrieves the account id for a specified contract address. + /// + /// See [`pallet_revive_uapi::HostFn::to_account_id`]. + fn to_account_id( + &mut self, + memory: &mut M, + addr_ptr: u32, + out_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::ToAccountId)?; + let address = memory.read_h160(addr_ptr)?; + let account_id = self.ext.to_account_id(&address); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &account_id.encode(), + false, + already_charged, + )?) + } } diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 06495d5d21aa..52153d74ca75 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -67,6 +67,7 @@ pub trait WeightInfo { fn seal_caller() -> Weight; fn seal_origin() -> Weight; fn seal_is_contract() -> Weight; + fn seal_to_account_id() -> Weight; fn seal_code_hash() -> Weight; fn seal_own_code_hash() -> Weight; fn seal_code_size() -> Weight; @@ -377,6 +378,16 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(10_336_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } + /// Storage: `Revive::AddressSuffix` (r:1 w:0) + /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) + fn seal_to_account_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 3677) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_code_hash() -> Weight { @@ -1263,6 +1274,16 @@ impl WeightInfo for () { Weight::from_parts(10_336_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } + /// Storage: `Revive::AddressSuffix` (r:1 w:0) + /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) + fn seal_to_account_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 3677) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_code_hash() -> Weight { diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index b82393826ddf..3e5cf0eb0c24 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -144,6 +144,18 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the origin's address. fn origin(output: &mut [u8; 20]); + /// Retrieve the account id for a specified address. + /// + /// # Parameters + /// + /// - `addr`: A `H160` address. + /// - `output`: A reference to the output data buffer to write the account id. + /// + /// # Note + /// + /// If no mapping exists for `addr`, the fallback account id will be returned. + fn to_account_id(addr: &[u8; 20], output: &mut [u8]); + /// Retrieve the code hash for a specified contract address. /// /// # Parameters diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 0023b8aa721d..3726564e26eb 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -69,6 +69,7 @@ mod sys { pub fn caller(out_ptr: *mut u8); pub fn origin(out_ptr: *mut u8); pub fn is_contract(account_ptr: *const u8) -> ReturnCode; + pub fn to_account_id(address_ptr: *const u8, out_ptr: *mut u8); pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8); pub fn code_size(address_ptr: *const u8) -> u64; pub fn own_code_hash(out_ptr: *mut u8); @@ -456,6 +457,11 @@ impl HostFn for HostFnImpl { unsafe { sys::ref_time_left() } } + #[unstable_hostfn] + fn to_account_id(address: &[u8; 20], output: &mut [u8]) { + unsafe { sys::to_account_id(address.as_ptr(), output.as_mut_ptr()) } + } + #[unstable_hostfn] fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) { unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) }; From be2404cccd9923c41e2f16bfe655f19574f1ae0e Mon Sep 17 00:00:00 2001 From: liamaharon Date: Thu, 16 Jan 2025 10:26:59 +0400 Subject: [PATCH 5/6] Implement `pallet-asset-rewards` (#3926) Closes #3149 ## Description This PR introduces `pallet-asset-rewards`, which allows accounts to be rewarded for freezing `fungible` tokens. The motivation for creating this pallet is to allow incentivising LPs. See the pallet docs for more info about the pallet. ## Runtime changes The pallet has been added to - `asset-hub-rococo` - `asset-hub-westend` The `NativeAndAssets` `fungibles` Union did not contain `PoolAssets`, so it has been renamed `NativeAndNonPoolAssets` A new `fungibles` Union `NativeAndAllAssets` was created to encompass all assets and the native token. ## TODO - [x] Emulation tests - [x] Fill in Freeze logic (blocked https://github.com/paritytech/polkadot-sdk/issues/3342) and re-run benchmarks --------- Co-authored-by: command-bot <> Co-authored-by: Oliver Tale-Yazdi Co-authored-by: muharem Co-authored-by: Guillaume Thiolliere --- Cargo.lock | 27 + Cargo.toml | 2 + .../assets/asset-hub-rococo/Cargo.toml | 1 + .../emulated/common/src/lib.rs | 2 + .../tests/assets/asset-hub-rococo/Cargo.toml | 1 + .../tests/assets/asset-hub-rococo/src/lib.rs | 3 +- .../assets/asset-hub-rococo/src/tests/mod.rs | 1 + .../asset-hub-rococo/src/tests/reward_pool.rs | 114 ++ .../tests/assets/asset-hub-westend/Cargo.toml | 1 + .../tests/assets/asset-hub-westend/src/lib.rs | 8 +- .../assets/asset-hub-westend/src/tests/mod.rs | 1 + .../src/tests/reward_pool.rs | 113 ++ .../assets/asset-hub-rococo/Cargo.toml | 5 + .../assets/asset-hub-rococo/src/lib.rs | 142 +- .../asset-hub-rococo/src/weights/mod.rs | 1 + .../src/weights/pallet_asset_rewards.rs | 217 +++ .../assets/asset-hub-rococo/src/xcm_config.rs | 10 +- .../assets/asset-hub-westend/Cargo.toml | 5 + .../assets/asset-hub-westend/src/lib.rs | 144 +- .../asset-hub-westend/src/weights/mod.rs | 1 + .../src/weights/pallet_asset_rewards.rs | 217 +++ .../asset-hub-westend/src/xcm_config.rs | 7 +- .../runtimes/assets/common/src/lib.rs | 7 +- polkadot/runtime/rococo/src/xcm_config.rs | 20 +- polkadot/runtime/westend/src/xcm_config.rs | 5 +- prdoc/pr_3926.prdoc | 30 + substrate/bin/node/runtime/src/lib.rs | 77 +- substrate/frame/asset-rewards/Cargo.toml | 71 + .../frame/asset-rewards/src/benchmarking.rs | 355 ++++ substrate/frame/asset-rewards/src/lib.rs | 905 ++++++++++ substrate/frame/asset-rewards/src/mock.rs | 221 +++ substrate/frame/asset-rewards/src/tests.rs | 1457 +++++++++++++++++ substrate/frame/asset-rewards/src/weights.rs | 368 +++++ substrate/frame/support/src/traits.rs | 5 +- substrate/frame/support/src/traits/storage.rs | 12 + umbrella/Cargo.toml | 10 +- umbrella/src/lib.rs | 4 + 37 files changed, 4517 insertions(+), 53 deletions(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs create mode 100644 cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs create mode 100644 prdoc/pr_3926.prdoc create mode 100644 substrate/frame/asset-rewards/Cargo.toml create mode 100644 substrate/frame/asset-rewards/src/benchmarking.rs create mode 100644 substrate/frame/asset-rewards/src/lib.rs create mode 100644 substrate/frame/asset-rewards/src/mock.rs create mode 100644 substrate/frame/asset-rewards/src/tests.rs create mode 100644 substrate/frame/asset-rewards/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 0d71a770d38b..6eba7e651096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,6 +910,7 @@ dependencies = [ "cumulus-primitives-core 0.7.0", "emulated-integration-tests-common", "frame-support 28.0.0", + "pallet-asset-rewards", "parachains-common 7.0.0", "rococo-emulated-chain", "sp-core 28.0.0", @@ -928,6 +929,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support 28.0.0", "pallet-asset-conversion 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-message-queue 31.0.0", @@ -978,6 +980,7 @@ dependencies = [ "pallet-asset-conversion 10.0.0", "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", "pallet-aura 27.0.0", @@ -1063,6 +1066,7 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "pallet-asset-conversion 10.0.0", + "pallet-asset-rewards", "pallet-asset-tx-payment 28.0.0", "pallet-assets 29.1.0", "pallet-balances 28.0.0", @@ -1114,6 +1118,7 @@ dependencies = [ "pallet-asset-conversion 10.0.0", "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", "pallet-aura 27.0.0", @@ -12036,6 +12041,27 @@ dependencies = [ "sp-runtime 39.0.2", ] +[[package]] +name = "pallet-asset-rewards" +version = "0.1.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-assets 29.1.0", + "pallet-assets-freezer 0.1.0", + "pallet-balances 28.0.0", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-info", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", +] + [[package]] name = "pallet-asset-tx-payment" version = "28.0.0" @@ -18715,6 +18741,7 @@ dependencies = [ "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", "pallet-asset-rate 7.0.0", + "pallet-asset-rewards", "pallet-asset-tx-payment 28.0.0", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index eb99b80e16fa..509775fe99e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,6 +315,7 @@ members = [ "substrate/frame/asset-conversion", "substrate/frame/asset-conversion/ops", "substrate/frame/asset-rate", + "substrate/frame/asset-rewards", "substrate/frame/assets", "substrate/frame/assets-freezer", "substrate/frame/atomic-swap", @@ -893,6 +894,7 @@ pallet-asset-conversion = { path = "substrate/frame/asset-conversion", default-f pallet-asset-conversion-ops = { path = "substrate/frame/asset-conversion/ops", default-features = false } pallet-asset-conversion-tx-payment = { path = "substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false } pallet-asset-rate = { path = "substrate/frame/asset-rate", default-features = false } +pallet-asset-rewards = { path = "substrate/frame/asset-rewards", default-features = false } pallet-asset-tx-payment = { path = "substrate/frame/transaction-payment/asset-tx-payment", default-features = false } pallet-assets = { path = "substrate/frame/assets", default-features = false } pallet-assets-freezer = { path = "substrate/frame/assets-freezer", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml index a164a8197f72..c6a8baeff3b3 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # Substrate frame-support = { workspace = true } +pallet-asset-rewards = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index e2757f8b9a35..f5466a63f1f5 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -58,6 +58,8 @@ pub const USDT_ID: u32 = 1984; pub const PENPAL_A_ID: u32 = 2000; pub const PENPAL_B_ID: u32 = 2001; +pub const ASSET_HUB_ROCOCO_ID: u32 = 1000; +pub const ASSET_HUB_WESTEND_ID: u32 = 1000; pub const ASSETS_PALLET_ID: u8 = 50; parameter_types! { diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml index 9e8b8f2a52d7..b53edb39c73b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -17,6 +17,7 @@ codec = { workspace = true } # Substrate frame-support = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index f3a1b3f5bfa2..513ca278a319 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -76,10 +76,11 @@ mod imports { genesis::ED as ROCOCO_ED, rococo_runtime::{ governance as rococo_governance, + governance::pallet_custom_origins::Origin::Treasurer, xcm_config::{ UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig, }, - OriginCaller as RococoOriginCaller, + Dmp, OriginCaller as RococoOriginCaller, }, RococoRelayPallet as RococoPallet, }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs index 88fa379c4072..75714acb07cd 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs @@ -16,6 +16,7 @@ mod claim_assets; mod hybrid_transfers; mod reserve_transfer; +mod reward_pool; mod send; mod set_xcm_versions; mod swap; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs new file mode 100644 index 000000000000..2f3ee536a7b9 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs @@ -0,0 +1,114 @@ +// 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. + +use crate::imports::*; +use codec::Encode; +use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime}; +use xcm_executor::traits::ConvertLocation; + +#[test] +fn treasury_creates_asset_reward_pool() { + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + let treasurer = + Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); + let treasurer_account = + ahr_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap(); + + assert_ok!(Balances::force_set_balance( + ::RuntimeOrigin::root(), + treasurer_account.clone().into(), + ASSET_HUB_ROCOCO_ED * 100_000, + )); + + let events = AssetHubRococo::events(); + match events.iter().last() { + Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) => + assert_eq!(*who, treasurer_account), + _ => panic!("Expected Balances::BalanceSet event"), + } + }); + + Rococo::execute_with(|| { + type AssetHubRococoRuntimeCall = ::RuntimeCall; + type AssetHubRococoRuntime = ::Runtime; + type RococoRuntimeCall = ::RuntimeCall; + type RococoRuntime = ::Runtime; + type RococoRuntimeEvent = ::RuntimeEvent; + type RococoRuntimeOrigin = ::RuntimeOrigin; + + Dmp::make_parachain_reachable(AssetHubRococo::para_id()); + + let staked_asset_id = bx!(RelayLocation::get()); + let reward_asset_id = bx!(RelayLocation::get()); + + let reward_rate_per_block = 1_000_000_000; + let lifetime = 1_000_000_000; + let admin = None; + + let create_pool_call = + RococoRuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::V4( + xcm::v4::Junction::Parachain(AssetHubRococo::para_id().into()).into() + )), + message: bx!(VersionedXcm::V5(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, + call: AssetHubRococoRuntimeCall::AssetRewards( + pallet_asset_rewards::Call::::create_pool { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + expiry: DispatchTime::After(lifetime), + admin + } + ) + .encode() + .into(), + } + ]))), + }); + + let treasury_origin: RococoRuntimeOrigin = Treasurer.into(); + assert_ok!(create_pool_call.dispatch(treasury_origin)); + + assert_expected_events!( + Rococo, + vec![ + RococoRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeEvent = ::RuntimeEvent; + + assert_eq!(1, pallet_asset_rewards::Pools::::iter().count()); + + let events = AssetHubRococo::events(); + match events.iter().last() { + Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: true, + .. + })) => (), + _ => panic!("Expected MessageQueue::Processed event"), + } + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml index 5cd00c239e60..ef68a53c3b18 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -19,6 +19,7 @@ frame-metadata-hash-extension = { workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-asset-tx-payment = { workspace = true } pallet-assets = { workspace = true } pallet-balances = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index 36630e2d2221..68dc87250f76 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -79,8 +79,12 @@ mod imports { }, westend_emulated_chain::{ genesis::ED as WESTEND_ED, - westend_runtime::xcm_config::{ - UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + westend_runtime::{ + governance::pallet_custom_origins::Origin::Treasurer, + xcm_config::{ + UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + }, + Dmp, }, WestendRelayPallet as WestendPallet, }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index 0dfe7a85f4c2..576c44fc542f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -17,6 +17,7 @@ mod claim_assets; mod fellowship_treasury; mod hybrid_transfers; mod reserve_transfer; +mod reward_pool; mod send; mod set_asset_claimer; mod set_xcm_versions; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs new file mode 100644 index 000000000000..4df51abcaceb --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs @@ -0,0 +1,113 @@ +// 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. + +use crate::imports::*; +use codec::Encode; +use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime}; +use xcm_executor::traits::ConvertLocation; + +#[test] +fn treasury_creates_asset_reward_pool() { + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + let treasurer = + Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); + let treasurer_account = + ahw_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap(); + + assert_ok!(Balances::force_set_balance( + ::RuntimeOrigin::root(), + treasurer_account.clone().into(), + ASSET_HUB_WESTEND_ED * 100_000, + )); + + let events = AssetHubWestend::events(); + match events.iter().last() { + Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) => + assert_eq!(*who, treasurer_account), + _ => panic!("Expected Balances::BalanceSet event"), + } + }); + Westend::execute_with(|| { + type AssetHubWestendRuntimeCall = ::RuntimeCall; + type AssetHubWestendRuntime = ::Runtime; + type WestendRuntimeCall = ::RuntimeCall; + type WestendRuntime = ::Runtime; + type WestendRuntimeEvent = ::RuntimeEvent; + type WestendRuntimeOrigin = ::RuntimeOrigin; + + Dmp::make_parachain_reachable(AssetHubWestend::para_id()); + + let staked_asset_id = bx!(RelayLocation::get()); + let reward_asset_id = bx!(RelayLocation::get()); + + let reward_rate_per_block = 1_000_000_000; + let lifetime = 1_000_000_000; + let admin = None; + + let create_pool_call = + WestendRuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::V4( + xcm::v4::Junction::Parachain(AssetHubWestend::para_id().into()).into() + )), + message: bx!(VersionedXcm::V5(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, + call: AssetHubWestendRuntimeCall::AssetRewards( + pallet_asset_rewards::Call::::create_pool { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + expiry: DispatchTime::After(lifetime), + admin + } + ) + .encode() + .into(), + } + ]))), + }); + + let treasury_origin: WestendRuntimeOrigin = Treasurer.into(); + assert_ok!(create_pool_call.dispatch(treasury_origin)); + + assert_expected_events!( + Westend, + vec![ + WestendRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeEvent = ::RuntimeEvent; + + assert_eq!(1, pallet_asset_rewards::Pools::::iter().count()); + + let events = AssetHubWestend::events(); + match events.iter().last() { + Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: true, + .. + })) => (), + _ => panic!("Expected MessageQueue::Processed event"), + } + }); +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index abe59a8439a8..d612dd03c247 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -30,6 +30,7 @@ frame-try-runtime = { optional = true, workspace = true } pallet-asset-conversion = { workspace = true } pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } @@ -61,6 +62,7 @@ sp-storage = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } sp-weights = { workspace = true } + # num-traits feature needed for dex integer sq root: primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } @@ -123,6 +125,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion-tx-payment/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-asset-rewards/runtime-benchmarks", "pallet-assets-freezer/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -162,6 +165,7 @@ try-runtime = [ "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", + "pallet-asset-rewards/try-runtime", "pallet-assets-freezer/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", @@ -212,6 +216,7 @@ std = [ "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", + "pallet-asset-rewards/std", "pallet-assets-freezer/std", "pallet-assets/std", "pallet-aura/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index db9a8201ebbe..43b7bf0ba118 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -35,7 +35,7 @@ use assets_common::{ foreign_creators::ForeignCreators, local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, matching::{FromNetwork, FromSiblingParachain}, - AssetIdForTrustBackedAssetsConvert, + AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert, }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector}; @@ -61,9 +61,9 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, ord_parameter_types, parameter_types, traits::{ - fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool, - ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter, - TransformOrigin, + fungible, fungible::HoldConsideration, fungibles, tokens::imbalance::ResolveAssetTo, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, + ConstantStoragePrice, EitherOfDiverse, Equals, InstanceFilter, TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -84,8 +84,8 @@ use sp_runtime::{Perbill, RuntimeDebug}; use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, GovernanceLocation, LocationToAccountId, - PoolAssetsConvertedConcreteId, TokenLocation, TrustBackedAssetsConvertedConcreteId, - TrustBackedAssetsPalletLocation, + PoolAssetsConvertedConcreteId, PoolAssetsPalletLocation, TokenLocation, + TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, }; #[cfg(test)] @@ -111,6 +111,9 @@ use xcm_runtime_apis::{ fees::Error as XcmPaymentApiError, }; +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::PalletInfoAccess; + use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; impl_opaque_keys! { @@ -217,8 +220,8 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; type DoneSlashHandler = (); } @@ -302,7 +305,7 @@ impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type RemoveItemsLimit = ConstU32<1000>; - type AssetId = u32; + type AssetId = AssetIdForPoolAssets; type AssetIdParameter = u32; type Currency = Balances; type CreateOrigin = @@ -343,8 +346,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< AccountId, >; -/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. -pub type NativeAndAssets = fungible::UnionOf< +/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`. +pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf< + AssetsFreezer, + ForeignAssetsFreezer, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`LocalAndForeignAssets`] and [`Balances`]. +pub type NativeAndNonPoolAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, TargetFromLeft, @@ -352,6 +368,45 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`]. +pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf< + Balances, + LocalAndForeignAssetsFreezer, + TargetFromLeft, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssets = fungibles::UnionOf< + PoolAssets, + NativeAndNonPoolAssets, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssetsFreezer = fungibles::UnionOf< + PoolAssetsFreezer, + NativeAndNonPoolAssetsFreezer, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, (xcm::v5::Location, xcm::v5::Location), @@ -362,7 +417,7 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type AssetKind = xcm::v5::Location; - type Assets = NativeAndAssets; + type Assets = NativeAndNonPoolAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< TokenLocation, @@ -823,9 +878,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime { type AssetId = xcm::v5::Location; type OnChargeAssetTransaction = SwapAssetAdapter< TokenLocation, - NativeAndAssets, + NativeAndNonPoolAssets, AssetConversion, - ResolveAssetTo, + ResolveAssetTo, >; type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -953,6 +1008,55 @@ impl pallet_xcm_bridge_hub_router::Config for Runtim type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId; } +#[cfg(feature = "runtime-benchmarks")] +pub struct PalletAssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper + for PalletAssetRewardsBenchmarkHelper +{ + fn staked_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(100)], + ) + } + fn reward_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(101)], + ) + } +} + +parameter_types! { + pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd"); + pub const RewardsPoolCreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = AssetRewardsPalletId; + type Balance = Balance; + type Assets = NativeAndAllAssets; + type AssetsFreezer = NativeAndAllAssetsFreezer; + type AssetId = xcm::v5::Location; + type CreatePoolOrigin = EnsureSigned; + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + AccountId, + Balances, + RewardsPoolCreationHoldReason, + ConstantStoragePrice, + >; + type WeightInfo = weights::pallet_asset_rewards::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -998,10 +1102,13 @@ construct_runtime!( NftFractionalization: pallet_nft_fractionalization = 54, PoolAssets: pallet_assets:: = 55, AssetConversion: pallet_asset_conversion = 56, + AssetsFreezer: pallet_assets_freezer:: = 57, ForeignAssetsFreezer: pallet_assets_freezer:: = 58, PoolAssetsFreezer: pallet_assets_freezer:: = 59, + AssetRewards: pallet_asset_rewards = 60, + // TODO: the pallet instance should be removed once all pools have migrated // to the new account IDs. AssetConversionMigration: pallet_asset_conversion_ops = 200, @@ -1193,6 +1300,7 @@ mod benches { [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetTxPayment] [pallet_balances, Balances] [pallet_message_queue, MessageQueue] @@ -1503,6 +1611,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { fn core_selector() -> (CoreSelector, ClaimQueueOffset) { ParachainSystem::core_selector() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs index ae78a56d8b3c..6893766ac72d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs @@ -24,6 +24,7 @@ pub mod frame_system_extensions; pub mod pallet_asset_conversion; pub mod pallet_asset_conversion_ops; pub mod pallet_asset_conversion_tx_payment; +pub mod pallet_asset_rewards; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs new file mode 100644 index 000000000000..218c93c51035 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs @@ -0,0 +1,217 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_rewards`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_rewards::WeightInfo for WeightInfo { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `358` + // Estimated: `6360` + // Minimum execution time: 65_882_000 picoseconds. + Weight::from_parts(67_073_000, 0) + .saturating_add(Weight::from_parts(0, 6360)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `872` + // Estimated: `4809` + // Minimum execution time: 56_950_000 picoseconds. + Weight::from_parts(58_088_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `872` + // Estimated: `4809` + // Minimum execution time: 59_509_000 picoseconds. + Weight::from_parts(61_064_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1072` + // Estimated: `6208` + // Minimum execution time: 80_685_000 picoseconds. + Weight::from_parts(83_505_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_032_000 picoseconds. + Weight::from_parts(17_628_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 15_290_000 picoseconds. + Weight::from_parts(16_212_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_721_000 picoseconds. + Weight::from_parts(18_603_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `747` + // Estimated: `6208` + // Minimum execution time: 67_754_000 picoseconds. + Weight::from_parts(69_428_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:0 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1105` + // Estimated: `6208` + // Minimum execution time: 127_524_000 picoseconds. + Weight::from_parts(130_238_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 08b2f520c4b9..0c6ff5e4bfdd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -76,6 +76,10 @@ parameter_types! { pub TrustBackedAssetsPalletLocation: Location = PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); pub TrustBackedAssetsPalletIndex: u8 = ::index() as u8; + pub TrustBackedAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); + pub PoolAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); pub ForeignAssetsPalletLocation: Location = PalletInstance(::index() as u8).into(); pub PoolAssetsPalletLocation: Location = @@ -336,7 +340,7 @@ pub type TrustedTeleporters = ( /// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, @@ -387,7 +391,7 @@ impl xcm_executor::Config for XcmConfig { TokenLocation, crate::AssetConversion, WeightToFee, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, @@ -396,7 +400,7 @@ impl xcm_executor::Config for XcmConfig { >, ForeignAssetsConvertedConcreteId, ), - ResolveAssetTo, + ResolveAssetTo, AccountId, >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index cb10ae9a4800..65ef63a7fb35 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -30,6 +30,7 @@ frame-try-runtime = { optional = true, workspace = true } pallet-asset-conversion = { workspace = true } pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } @@ -62,6 +63,7 @@ sp-std = { workspace = true } sp-storage = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } + # num-traits feature needed for dex integer sq root: primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } @@ -125,6 +127,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion-tx-payment/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-asset-rewards/runtime-benchmarks", "pallet-assets-freezer/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -166,6 +169,7 @@ try-runtime = [ "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", + "pallet-asset-rewards/try-runtime", "pallet-assets-freezer/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", @@ -218,6 +222,7 @@ std = [ "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", + "pallet-asset-rewards/std", "pallet-assets-freezer/std", "pallet-assets/std", "pallet-aura/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 5966dd01f18f..3ef5e87f24c4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -33,7 +33,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use assets_common::{ local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, - AssetIdForTrustBackedAssetsConvert, + AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert, }; use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; @@ -44,10 +44,12 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, ord_parameter_types, parameter_types, traits::{ - fungible, fungibles, + fungible, + fungible::HoldConsideration, + fungibles, tokens::{imbalance::ResolveAssetTo, nonfungibles_v2::Inspect}, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Equals, - InstanceFilter, Nothing, TransformOrigin, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, + ConstantStoragePrice, Equals, InstanceFilter, Nothing, TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -81,8 +83,8 @@ use testnet_parachains_constants::westend::{ }; use xcm_config::{ ForeignAssetsConvertedConcreteId, LocationToAccountId, PoolAssetsConvertedConcreteId, - TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, WestendLocation, - XcmOriginToTransactDispatchOrigin, + PoolAssetsPalletLocation, TrustBackedAssetsConvertedConcreteId, + TrustBackedAssetsPalletLocation, WestendLocation, XcmOriginToTransactDispatchOrigin, }; #[cfg(any(feature = "std", test))] @@ -93,11 +95,15 @@ use assets_common::{ matching::{FromNetwork, FromSiblingParachain}, }; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::{ latest::prelude::AssetId, prelude::{VersionedAsset, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}, }; +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::PalletInfoAccess; + #[cfg(feature = "runtime-benchmarks")] use xcm::latest::prelude::{ Asset, Assets as XcmAssets, Fungible, Here, InteriorLocation, Junction, Junction::*, Location, @@ -109,8 +115,6 @@ use xcm_runtime_apis::{ fees::Error as XcmPaymentApiError, }; -use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; - impl_opaque_keys! { pub struct SessionKeys { pub aura: Aura, @@ -218,8 +222,8 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; type DoneSlashHandler = (); } @@ -341,8 +345,22 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< xcm::v5::Location, AccountId, >; + +/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`. +pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf< + AssetsFreezer, + ForeignAssetsFreezer, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + /// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. -pub type NativeAndAssets = fungible::UnionOf< +pub type NativeAndNonPoolAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, TargetFromLeft, @@ -350,6 +368,45 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`]. +pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf< + Balances, + LocalAndForeignAssetsFreezer, + TargetFromLeft, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssets = fungibles::UnionOf< + PoolAssets, + NativeAndNonPoolAssets, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssetsFreezer = fungibles::UnionOf< + PoolAssetsFreezer, + NativeAndNonPoolAssetsFreezer, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, (xcm::v5::Location, xcm::v5::Location), @@ -360,7 +417,7 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type AssetKind = xcm::v5::Location; - type Assets = NativeAndAssets; + type Assets = NativeAndNonPoolAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< WestendLocation, @@ -388,6 +445,55 @@ impl pallet_asset_conversion::Config for Runtime { >; } +#[cfg(feature = "runtime-benchmarks")] +pub struct PalletAssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper + for PalletAssetRewardsBenchmarkHelper +{ + fn staked_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(100)], + ) + } + fn reward_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(101)], + ) + } +} + +parameter_types! { + pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd"); + pub const RewardsPoolCreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = AssetRewardsPalletId; + type Balance = Balance; + type Assets = NativeAndAllAssets; + type AssetsFreezer = NativeAndAllAssetsFreezer; + type AssetId = xcm::v5::Location; + type CreatePoolOrigin = EnsureSigned; + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + AccountId, + Balances, + RewardsPoolCreationHoldReason, + ConstantStoragePrice, + >; + type WeightInfo = weights::pallet_asset_rewards::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper; +} + impl pallet_asset_conversion_ops::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed< @@ -816,9 +922,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime { type AssetId = xcm::v5::Location; type OnChargeAssetTransaction = SwapAssetAdapter< WestendLocation, - NativeAndAssets, + NativeAndNonPoolAssets, AssetConversion, - ResolveAssetTo, + ResolveAssetTo, >; type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -1035,11 +1141,14 @@ construct_runtime!( NftFractionalization: pallet_nft_fractionalization = 54, PoolAssets: pallet_assets:: = 55, AssetConversion: pallet_asset_conversion = 56, + AssetsFreezer: pallet_assets_freezer:: = 57, ForeignAssetsFreezer: pallet_assets_freezer:: = 58, PoolAssetsFreezer: pallet_assets_freezer:: = 59, Revive: pallet_revive = 60, + AssetRewards: pallet_asset_rewards = 61, + StateTrieMigration: pallet_state_trie_migration = 70, // TODO: the pallet instance should be removed once all pools have migrated @@ -1317,6 +1426,7 @@ mod benches { [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetTxPayment] [pallet_balances, Balances] [pallet_message_queue, MessageQueue] @@ -1674,6 +1784,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { fn core_selector() -> (CoreSelector, ClaimQueueOffset) { ParachainSystem::core_selector() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs index 442b58635f48..d653838ad80e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs @@ -23,6 +23,7 @@ pub mod frame_system_extensions; pub mod pallet_asset_conversion; pub mod pallet_asset_conversion_ops; pub mod pallet_asset_conversion_tx_payment; +pub mod pallet_asset_rewards; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs new file mode 100644 index 000000000000..3bbc289fec7b --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs @@ -0,0 +1,217 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_rewards`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_rewards::WeightInfo for WeightInfo { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `392` + // Estimated: `6360` + // Minimum execution time: 60_734_000 picoseconds. + Weight::from_parts(61_828_000, 0) + .saturating_add(Weight::from_parts(0, 6360)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `906` + // Estimated: `4809` + // Minimum execution time: 56_014_000 picoseconds. + Weight::from_parts(58_487_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `906` + // Estimated: `4809` + // Minimum execution time: 59_071_000 picoseconds. + Weight::from_parts(60_631_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1106` + // Estimated: `6208` + // Minimum execution time: 80_585_000 picoseconds. + Weight::from_parts(82_186_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_083_000 picoseconds. + Weight::from_parts(17_816_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 15_269_000 picoseconds. + Weight::from_parts(15_881_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_482_000 picoseconds. + Weight::from_parts(18_124_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `781` + // Estimated: `6208` + // Minimum execution time: 66_644_000 picoseconds. + Weight::from_parts(67_950_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:0 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1139` + // Estimated: `6208` + // Minimum execution time: 124_136_000 picoseconds. + Weight::from_parts(128_642_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index b4e938f1f8b5..1ea2ce5136ab 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -65,6 +65,7 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); pub const WestendLocation: Location = Location::parent(); + pub const GovernanceLocation: Location = Location::parent(); pub const RelayNetwork: Option = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH)); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = @@ -359,7 +360,7 @@ pub type TrustedTeleporters = ( /// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, @@ -409,7 +410,7 @@ impl xcm_executor::Config for XcmConfig { WestendLocation, crate::AssetConversion, WeightToFee, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, @@ -418,7 +419,7 @@ impl xcm_executor::Config for XcmConfig { >, ForeignAssetsConvertedConcreteId, ), - ResolveAssetTo, + ResolveAssetTo, AccountId, >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 25c2df6b68d1..50b1b63146bc 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -123,10 +123,11 @@ pub type ForeignAssetsConvertedConcreteId< BalanceConverter, >; -type AssetIdForPoolAssets = u32; +pub type AssetIdForPoolAssets = u32; + /// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`. -pub type AssetIdForPoolAssetsConvert = - AsPrefixedGeneralIndex; +pub type AssetIdForPoolAssetsConvert = + AsPrefixedGeneralIndex; /// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets` pub type PoolAssetsConvertedConcreteId = MatchedConvertedConcreteId< diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index bb77ec0000e5..10c3f6c0cbfc 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -18,7 +18,8 @@ use super::{ parachains_origin, AccountId, AllPalletsWithSystem, Balances, Dmp, Fellows, ParaId, Runtime, - RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasury, WeightToFee, XcmPallet, + RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasurer, Treasury, WeightToFee, + XcmPallet, }; use crate::governance::StakingAdmin; @@ -228,11 +229,14 @@ impl xcm_executor::Config for XcmConfig { } parameter_types! { + /// Collective pluralistic body. pub const CollectiveBodyId: BodyId = BodyId::Unit; - // StakingAdmin pluralistic body. + /// StakingAdmin pluralistic body. pub const StakingAdminBodyId: BodyId = BodyId::Defense; - // Fellows pluralistic body. + /// Fellows pluralistic body. pub const FellowsBodyId: BodyId = BodyId::Technical; + /// Treasury pluralistic body. + pub const TreasuryBodyId: BodyId = BodyId::Treasury; } /// Type to convert an `Origin` type value into a `Location` value which represents an interior @@ -249,6 +253,9 @@ pub type StakingAdminToPlurality = /// Type to convert the Fellows origin to a Plurality `Location` value. pub type FellowsToPlurality = OriginToPluralityVoice; +/// Type to convert the Treasury origin to a Plurality `Location` value. +pub type TreasurerToPlurality = OriginToPluralityVoice; + /// Type to convert a pallet `Origin` type value into a `Location` value which represents an /// interior location of this chain for a destination chain. pub type LocalPalletOriginToLocation = ( @@ -256,13 +263,18 @@ pub type LocalPalletOriginToLocation = ( StakingAdminToPlurality, // Fellows origin to be used in XCM as a corresponding Plurality `Location` value. FellowsToPlurality, + // Treasurer origin to be used in XCM as a corresponding Plurality `Location` value. + TreasurerToPlurality, ); impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // Note that this configuration of `SendXcmOrigin` is different from the one present in // production. - type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin< + RuntimeOrigin, + (LocalPalletOriginToLocation, LocalOriginToLocation), + >; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally. type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index 3f6a7304c8a9..4235edf82b24 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -280,7 +280,10 @@ impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // Note that this configuration of `SendXcmOrigin` is different from the one present in // production. - type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin< + RuntimeOrigin, + (LocalPalletOriginToLocation, LocalOriginToLocation), + >; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally. type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; diff --git a/prdoc/pr_3926.prdoc b/prdoc/pr_3926.prdoc new file mode 100644 index 000000000000..7f352f7a45fb --- /dev/null +++ b/prdoc/pr_3926.prdoc @@ -0,0 +1,30 @@ +title: Introduce pallet-asset-rewards + +doc: + - audience: Runtime Dev + description: | + Introduce pallet-asset-rewards, which allows accounts to be rewarded for freezing fungible + tokens. The motivation for creating this pallet is to allow incentivising LPs. + See the pallet docs for more info about the pallet. + +crates: + - name: pallet-asset-rewards + bump: major + - name: polkadot-sdk + bump: minor + - name: kitchensink-runtime + bump: major + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major + - name: assets-common + bump: minor + - name: rococo-runtime + bump: minor + - name: westend-runtime + bump: patch + - name: frame-support + bump: minor + - name: emulated-integration-tests-common + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 97728f12f5f9..de377a55bc88 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -56,10 +56,10 @@ use frame_support::{ imbalance::ResolveAssetTo, nonfungibles_v2::Inspect, pay::PayAssetFromAccount, GetSalary, PayFromAccount, }, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, Contains, - Currency, EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, - InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, - OnUnbalanced, VariantCountOf, WithdrawReasons, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, + ConstantStoragePrice, Contains, Currency, EitherOfDiverse, EnsureOriginWithArg, + EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, KeyOwnerProofSystem, + LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, VariantCountOf, WithdrawReasons, }, weights::{ constants::{ @@ -511,7 +511,8 @@ impl pallet_glutton::Config for Runtime { } parameter_types! { - pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); + pub const PreimageHoldReason: RuntimeHoldReason = + RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } impl pallet_preimage::Config for Runtime { @@ -618,6 +619,12 @@ impl pallet_transaction_payment::Config for Runtime { type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight; } +pub type AssetsFreezerInstance = pallet_assets_freezer::Instance1; +impl pallet_assets_freezer::Config for Runtime { + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeEvent = RuntimeEvent; +} + impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AssetId = NativeOrWithId; @@ -1858,6 +1865,53 @@ impl pallet_asset_conversion::Config for Runtime { type BenchmarkHelper = (); } +pub type NativeAndAssetsFreezer = + UnionOf, AccountId>; + +/// Benchmark Helper +#[cfg(feature = "runtime-benchmarks")] +pub struct AssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper> + for AssetRewardsBenchmarkHelper +{ + fn staked_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(100) + } + fn reward_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(101) + } +} + +parameter_types! { + pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd"); + pub const CreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeFreezeReason = RuntimeFreezeReason; + type AssetId = NativeOrWithId; + type Balance = Balance; + type Assets = NativeAndAssets; + type PalletId = StakingRewardsPalletId; + type CreatePoolOrigin = EnsureSigned; + type WeightInfo = (); + type AssetsFreezer = NativeAndAssetsFreezer; + type Consideration = HoldConsideration< + AccountId, + Balances, + CreationHoldReason, + ConstantStoragePrice, + >; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetRewardsBenchmarkHelper; +} + impl pallet_asset_conversion_ops::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed<( @@ -2636,6 +2690,12 @@ mod runtime { #[runtime::pallet_index(81)] pub type VerifySignature = pallet_verify_signature::Pallet; + + #[runtime::pallet_index(83)] + pub type AssetRewards = pallet_asset_rewards::Pallet; + + #[runtime::pallet_index(84)] + pub type AssetsFreezer = pallet_assets_freezer::Pallet; } impl TryFrom for pallet_revive::Call { @@ -2846,6 +2906,7 @@ mod benches { [pallet_example_tasks, TasksExample] [pallet_democracy, Democracy] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetConversionTxPayment] [pallet_transaction_payment, TransactionPayment] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] @@ -3553,6 +3614,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/substrate/frame/asset-rewards/Cargo.toml b/substrate/frame/asset-rewards/Cargo.toml new file mode 100644 index 000000000000..a03fa17cf0dc --- /dev/null +++ b/substrate/frame/asset-rewards/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "pallet-asset-rewards" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME asset rewards pallet" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true, features = ["experimental"] } +frame-system = { workspace = true } +scale-info = { workspace = true, features = ["derive"] } +sp-api = { workspace = true } +sp-arithmetic = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[dev-dependencies] +pallet-assets = { workspace = true } +pallet-assets-freezer = { workspace = true } +pallet-balances = { workspace = true } +primitive-types = { workspace = true, features = ["codec", "num-traits", "scale-info"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "pallet-assets-freezer/std", + "pallet-assets/std", + "pallet-balances/std", + "primitive-types/std", + "scale-info/std", + "sp-api/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets-freezer/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets-freezer/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/asset-rewards/src/benchmarking.rs b/substrate/frame/asset-rewards/src/benchmarking.rs new file mode 100644 index 000000000000..5605804dd20e --- /dev/null +++ b/substrate/frame/asset-rewards/src/benchmarking.rs @@ -0,0 +1,355 @@ +// 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. + +//! Asset Rewards pallet benchmarking. + +use super::*; +use crate::Pallet as AssetRewards; +use frame_benchmarking::{v2::*, whitelisted_caller, BenchmarkError}; +use frame_support::{ + assert_ok, + traits::{ + fungibles::{Create, Inspect, Mutate}, + Consideration, EnsureOrigin, Footprint, + }, +}; +use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System, RawOrigin}; +use sp_runtime::{traits::One, Saturating}; +use sp_std::prelude::*; + +/// Benchmark Helper +pub trait BenchmarkHelper { + /// Returns the staked asset id. + /// + /// If the asset does not exist, it will be created by the benchmark. + fn staked_asset() -> AssetId; + /// Returns the reward asset id. + /// + /// If the asset does not exist, it will be created by the benchmark. + fn reward_asset() -> AssetId; +} + +fn pool_expire() -> DispatchTime> { + DispatchTime::At(BlockNumberFor::::from(100u32)) +} + +fn create_reward_pool() -> Result +where + T::Assets: Create + Mutate, +{ + let caller_origin = + T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap(); + + let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor)>(); + T::Consideration::ensure_successful(&caller, footprint); + + let staked_asset = T::BenchmarkHelper::staked_asset(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + + let min_staked_balance = + T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(staked_asset.clone()) { + assert_ok!(T::Assets::create( + staked_asset.clone(), + caller.clone(), + true, + min_staked_balance + )); + } + let min_reward_balance = + T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(reward_asset.clone()) { + assert_ok!(T::Assets::create( + reward_asset.clone(), + caller.clone(), + true, + min_reward_balance + )); + } + + assert_ok!(AssetRewards::::create_pool( + caller_origin.clone(), + Box::new(staked_asset), + Box::new(reward_asset), + // reward rate per block + min_reward_balance, + pool_expire::(), + Some(caller), + )); + + Ok(caller_origin) +} + +fn mint_into(caller: &T::AccountId, asset: &T::AssetId) -> T::Balance +where + T::Assets: Mutate, +{ + let min_balance = T::Assets::minimum_balance(asset.clone()); + assert_ok!(T::Assets::mint_into( + asset.clone(), + &caller, + min_balance.saturating_mul(10u32.into()) + )); + min_balance +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + System::::assert_last_event(generic_event.into()); +} + +#[benchmarks(where T::Assets: Create + Mutate)] +mod benchmarks { + use super::*; + + #[benchmark] + fn create_pool() -> Result<(), BenchmarkError> { + let caller_origin = + T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap(); + + let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor)>(); + T::Consideration::ensure_successful(&caller, footprint); + + let staked_asset = T::BenchmarkHelper::staked_asset(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + + let min_balance = T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(staked_asset.clone()) { + assert_ok!(T::Assets::create(staked_asset.clone(), caller.clone(), true, min_balance)); + } + let min_balance = T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(reward_asset.clone()) { + assert_ok!(T::Assets::create(reward_asset.clone(), caller.clone(), true, min_balance)); + } + + #[extrinsic_call] + _( + caller_origin as T::RuntimeOrigin, + Box::new(staked_asset.clone()), + Box::new(reward_asset.clone()), + min_balance, + pool_expire::(), + Some(caller.clone()), + ); + + assert_last_event::( + Event::PoolCreated { + creator: caller.clone(), + admin: caller, + staked_asset_id: staked_asset, + reward_asset_id: reward_asset, + reward_rate_per_block: min_balance, + expiry_block: pool_expire::().evaluate(System::::block_number()), + pool_id: 0, + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn stake() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + // stake first to get worth case benchmark. + assert_ok!(AssetRewards::::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + min_balance + )); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, min_balance); + + assert_last_event::(Event::Staked { staker, pool_id: 0, amount: min_balance }.into()); + + Ok(()) + } + + #[benchmark] + fn unstake() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + min_balance, + )); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, min_balance, None); + + assert_last_event::( + Event::Unstaked { caller: staker.clone(), staker, pool_id: 0, amount: min_balance } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn harvest_rewards() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + + let pool_acc = AssetRewards::::pool_account_id(&0u32); + let min_reward_balance = mint_into::(&pool_acc, &T::BenchmarkHelper::reward_asset()); + + let staker = whitelisted_caller(); + let _ = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + assert_ok!(AssetRewards::::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + T::Balance::one(), + )); + + System::::set_block_number(System::::block_number() + BlockNumberFor::::one()); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, None); + + assert_last_event::( + Event::RewardsHarvested { + caller: staker.clone(), + staker, + pool_id: 0, + amount: min_reward_balance, + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn set_pool_reward_rate_per_block() -> Result<(), BenchmarkError> { + let caller_origin = create_reward_pool::()?; + + // stake first to get worth case benchmark. + { + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::::stake(RawOrigin::Signed(staker).into(), 0, min_balance)); + } + + let new_reward_rate_per_block = + T::Assets::minimum_balance(T::BenchmarkHelper::reward_asset()).max(T::Balance::one()) + + T::Balance::one(); + + #[extrinsic_call] + _(caller_origin as T::RuntimeOrigin, 0, new_reward_rate_per_block); + + assert_last_event::( + Event::PoolRewardRateModified { pool_id: 0, new_reward_rate_per_block }.into(), + ); + Ok(()) + } + + #[benchmark] + fn set_pool_admin() -> Result<(), BenchmarkError> { + let caller_origin = create_reward_pool::()?; + let new_admin: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(caller_origin as T::RuntimeOrigin, 0, new_admin.clone()); + + assert_last_event::(Event::PoolAdminModified { pool_id: 0, new_admin }.into()); + + Ok(()) + } + + #[benchmark] + fn set_pool_expiry_block() -> Result<(), BenchmarkError> { + let create_origin = create_reward_pool::()?; + + // stake first to get worth case benchmark. + { + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::::stake(RawOrigin::Signed(staker).into(), 0, min_balance)); + } + + let new_expiry_block = + pool_expire::().evaluate(System::::block_number()) + BlockNumberFor::::one(); + + #[extrinsic_call] + _(create_origin as T::RuntimeOrigin, 0, DispatchTime::At(new_expiry_block)); + + assert_last_event::( + Event::PoolExpiryBlockModified { pool_id: 0, new_expiry_block }.into(), + ); + + Ok(()) + } + + #[benchmark] + fn deposit_reward_tokens() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + let caller = whitelisted_caller(); + + let reward_asset = T::BenchmarkHelper::reward_asset(); + let pool_acc = AssetRewards::::pool_account_id(&0u32); + let min_balance = mint_into::(&caller, &reward_asset); + + let balance_before = T::Assets::balance(reward_asset.clone(), &pool_acc); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), 0, min_balance); + + let balance_after = T::Assets::balance(reward_asset, &pool_acc); + + assert_eq!(balance_after, balance_before + min_balance); + + Ok(()) + } + + #[benchmark] + fn cleanup_pool() -> Result<(), BenchmarkError> { + let create_origin = create_reward_pool::()?; + let caller = T::CreatePoolOrigin::ensure_origin(create_origin.clone()).unwrap(); + + // deposit rewards tokens to get worth case benchmark. + { + let caller = whitelisted_caller(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + let min_balance = mint_into::(&caller, &reward_asset); + assert_ok!(AssetRewards::::deposit_reward_tokens( + RawOrigin::Signed(caller).into(), + 0, + min_balance + )); + } + + #[extrinsic_call] + _(RawOrigin::Signed(caller), 0); + + assert_last_event::(Event::PoolCleanedUp { pool_id: 0 }.into()); + + Ok(()) + } + + impl_benchmark_test_suite!(AssetRewards, crate::mock::new_test_ext(), crate::mock::MockRuntime); +} diff --git a/substrate/frame/asset-rewards/src/lib.rs b/substrate/frame/asset-rewards/src/lib.rs new file mode 100644 index 000000000000..4ce73e9febf9 --- /dev/null +++ b/substrate/frame/asset-rewards/src/lib.rs @@ -0,0 +1,905 @@ +// 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. + +//! # FRAME Staking Rewards Pallet +//! +//! Allows accounts to be rewarded for holding `fungible` asset/s, for example LP tokens. +//! +//! ## Overview +//! +//! Initiate an incentive program for a fungible asset by creating a new pool. +//! +//! During pool creation, a 'staking asset', 'reward asset', 'reward rate per block', 'expiry +//! block', and 'admin' are specified. +//! +//! Once created, holders of the 'staking asset' can 'stake' them in a corresponding pool, which +//! creates a Freeze on the asset. +//! +//! Once staked, rewards denominated in 'reward asset' begin accumulating to the staker, +//! proportional to their share of the total staked tokens in the pool. +//! +//! Reward assets pending distribution are held in an account unique to each pool. +//! +//! Care should be taken by the pool operator to keep pool accounts adequately funded with the +//! reward asset. +//! +//! The pool admin may increase reward rate per block, increase expiry block, and change admin. +//! +//! ## Disambiguation +//! +//! While this pallet shares some terminology with the `staking-pool` and similar native staking +//! related pallets, it is distinct and is entirely unrelated to native staking. +//! +//! ## Permissioning +//! +//! Currently, pool creation and management restricted to a configured Origin. +//! +//! Future iterations of this pallet may allow permissionless creation and management of pools. +//! +//! Note: The permissioned origin must return an AccountId. This can be achieved for any Origin by +//! wrapping it with `EnsureSuccess`. +//! +//! ## Implementation Notes +//! +//! Internal logic functions such as `update_pool_and_staker_rewards` were deliberately written +//! without side-effects. +//! +//! Storage interaction such as reads and writes are instead all performed in the top level +//! pallet Call method, which while slightly more verbose, makes it easier to understand the +//! code and reason about how storage reads and writes occur in the pallet. +//! +//! ## Rewards Algorithm +//! +//! The rewards algorithm is based on the Synthetix [StakingRewards.sol](https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol) +//! smart contract. +//! +//! Rewards are calculated JIT (just-in-time), and all operations are O(1) making the approach +//! scalable to many pools and stakers. +//! +//! ### Resources +//! +//! - [This video series](https://www.youtube.com/watch?v=6ZO5aYg1GI8), which walks through the math +//! of the algorithm. +//! - [This dev.to article](https://dev.to/heymarkkop/understanding-sushiswaps-masterchef-staking-rewards-1m6f), +//! which explains the algorithm of the SushiSwap MasterChef staking. While not identical to the +//! Synthetix approach, they are quite similar. +#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + traits::{ + fungibles::{Inspect, Mutate}, + schedule::DispatchTime, + tokens::Balance, + }, + PalletId, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use scale_info::TypeInfo; +use sp_core::Get; +use sp_runtime::{ + traits::{MaybeDisplay, Zero}, + DispatchError, +}; +use sp_std::boxed::Box; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +mod weights; + +pub use weights::WeightInfo; + +/// Unique id type for each pool. +pub type PoolId = u32; + +/// Multiplier to maintain precision when calculating rewards. +pub(crate) const PRECISION_SCALING_FACTOR: u16 = 4096; + +/// Convenience alias for `PoolInfo`. +pub type PoolInfoFor = PoolInfo< + ::AccountId, + ::AssetId, + ::Balance, + BlockNumberFor, +>; + +/// The state of a staker in a pool. +#[derive(Debug, Default, Clone, Decode, Encode, MaxEncodedLen, TypeInfo)] +pub struct PoolStakerInfo { + /// Amount of tokens staked. + amount: Balance, + /// Accumulated, unpaid rewards. + rewards: Balance, + /// Reward per token value at the time of the staker's last interaction with the contract. + reward_per_token_paid: Balance, +} + +/// The state and configuration of an incentive pool. +#[derive(Debug, Clone, Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct PoolInfo { + /// The asset staked in this pool. + staked_asset_id: AssetId, + /// The asset distributed as rewards by this pool. + reward_asset_id: AssetId, + /// The amount of tokens rewarded per block. + reward_rate_per_block: Balance, + /// The block the pool will cease distributing rewards. + expiry_block: BlockNumber, + /// The account authorized to manage this pool. + admin: AccountId, + /// The total amount of tokens staked in this pool. + total_tokens_staked: Balance, + /// Total rewards accumulated per token, up to the `last_update_block`. + reward_per_token_stored: Balance, + /// Last block number the pool was updated. + last_update_block: BlockNumber, + /// The account that holds the pool's rewards. + account: AccountId, +} + +sp_api::decl_runtime_apis! { + /// The runtime API for the asset rewards pallet. + pub trait AssetRewards { + /// Get the cost of creating a pool. + /// + /// This is especially useful when the cost is dynamic. + fn pool_creation_cost() -> Cost; + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::{ + fungibles::MutateFreeze, + tokens::{AssetId, Fortitude, Preservation}, + Consideration, Footprint, + }, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::{ + traits::{ + AccountIdConversion, BadOrigin, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureMul, + EnsureSub, EnsureSubAssign, + }, + DispatchResult, + }; + + #[pallet::pallet] + pub struct Pallet(_); + + /// A reason for the pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum FreezeReason { + /// Funds are staked in the pallet. + #[codec(index = 0)] + Staked, + } + + /// A reason for the pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// Cost associated with storing pool information on-chain. + #[codec(index = 0)] + PoolCreation, + } + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The pallet's unique identifier, used to derive the pool's account ID. + /// + /// The account ID is derived once during pool creation and stored in the storage. + #[pallet::constant] + type PalletId: Get; + + /// Identifier for each type of asset. + type AssetId: AssetId + Member + Parameter; + + /// The type in which the assets are measured. + type Balance: Balance + TypeInfo; + + /// The origin with permission to create pools. + /// + /// The Origin must return an AccountId. + type CreatePoolOrigin: EnsureOrigin; + + /// Registry of assets that can be configured to either stake for rewards, or be offered as + /// rewards for staking. + type Assets: Inspect + + Mutate; + + /// Freezer for the Assets. + type AssetsFreezer: MutateFreeze< + Self::AccountId, + Id = Self::RuntimeFreezeReason, + AssetId = Self::AssetId, + Balance = Self::Balance, + >; + + /// The overarching freeze reason. + type RuntimeFreezeReason: From; + + /// Means for associating a cost with the on-chain storage of pool information, which + /// is incurred by the pool creator. + /// + /// The passed `Footprint` specifically accounts for the storage footprint of the pool's + /// information itself, excluding any potential storage footprint related to the stakers. + type Consideration: Consideration; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Helper for benchmarking. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: benchmarking::BenchmarkHelper; + } + + /// State of pool stakers. + #[pallet::storage] + pub type PoolStakers = StorageDoubleMap< + _, + Blake2_128Concat, + PoolId, + Blake2_128Concat, + T::AccountId, + PoolStakerInfo, + >; + + /// State and configuration of each staking pool. + #[pallet::storage] + pub type Pools = StorageMap<_, Blake2_128Concat, PoolId, PoolInfoFor>; + + /// The cost associated with storing pool information on-chain which was incurred by the pool + /// creator. + /// + /// This cost may be [`None`], as determined by [`Config::Consideration`]. + #[pallet::storage] + pub type PoolCost = + StorageMap<_, Blake2_128Concat, PoolId, (T::AccountId, T::Consideration)>; + + /// Stores the [`PoolId`] to use for the next pool. + /// + /// Incremented when a new pool is created. + #[pallet::storage] + pub type NextPoolId = StorageValue<_, PoolId, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An account staked some tokens in a pool. + Staked { + /// The account that staked assets. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The staked asset amount. + amount: T::Balance, + }, + /// An account unstaked some tokens from a pool. + Unstaked { + /// The account that signed transaction. + caller: T::AccountId, + /// The account that unstaked assets. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The unstaked asset amount. + amount: T::Balance, + }, + /// An account harvested some rewards. + RewardsHarvested { + /// The account that signed transaction. + caller: T::AccountId, + /// The staker whos rewards were harvested. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The amount of harvested tokens. + amount: T::Balance, + }, + /// A new reward pool was created. + PoolCreated { + /// The account that created the pool. + creator: T::AccountId, + /// The unique ID for the new pool. + pool_id: PoolId, + /// The staking asset. + staked_asset_id: T::AssetId, + /// The reward asset. + reward_asset_id: T::AssetId, + /// The initial reward rate per block. + reward_rate_per_block: T::Balance, + /// The block the pool will cease to accumulate rewards. + expiry_block: BlockNumberFor, + /// The account allowed to modify the pool. + admin: T::AccountId, + }, + /// A pool reward rate was modified by the admin. + PoolRewardRateModified { + /// The modified pool. + pool_id: PoolId, + /// The new reward rate per block. + new_reward_rate_per_block: T::Balance, + }, + /// A pool admin was modified. + PoolAdminModified { + /// The modified pool. + pool_id: PoolId, + /// The new admin. + new_admin: T::AccountId, + }, + /// A pool expiry block was modified by the admin. + PoolExpiryBlockModified { + /// The modified pool. + pool_id: PoolId, + /// The new expiry block. + new_expiry_block: BlockNumberFor, + }, + /// A pool information was cleared after it's completion. + PoolCleanedUp { + /// The cleared pool. + pool_id: PoolId, + }, + } + + #[pallet::error] + pub enum Error { + /// The staker does not have enough tokens to perform the operation. + NotEnoughTokens, + /// An operation was attempted on a non-existent pool. + NonExistentPool, + /// An operation was attempted for a non-existent staker. + NonExistentStaker, + /// An operation was attempted with a non-existent asset. + NonExistentAsset, + /// There was an error converting a block number. + BlockNumberConversionError, + /// The expiry block must be in the future. + ExpiryBlockMustBeInTheFuture, + /// Insufficient funds to create the freeze. + InsufficientFunds, + /// The expiry block can be only extended. + ExpiryCut, + /// The reward rate per block can be only increased. + RewardRateCut, + /// The pool still has staked tokens or rewards. + NonEmptyPool, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + // The AccountId is at least 16 bytes to contain the unique PalletId. + let pool_id: PoolId = 1; + assert!( + >::try_into_sub_account( + &T::PalletId::get(), pool_id, + ) + .is_some() + ); + } + } + + /// Pallet's callable functions. + #[pallet::call(weight(::WeightInfo))] + impl Pallet { + /// Create a new reward pool. + /// + /// Parameters: + /// - `origin`: must be `Config::CreatePoolOrigin`; + /// - `staked_asset_id`: the asset to be staked in the pool; + /// - `reward_asset_id`: the asset to be distributed as rewards; + /// - `reward_rate_per_block`: the amount of reward tokens distributed per block; + /// - `expiry`: the block number at which the pool will cease to accumulate rewards. The + /// [`DispatchTime::After`] variant evaluated at the execution time. + /// - `admin`: the account allowed to extend the pool expiration, increase the rewards rate + /// and receive the unutilized reward tokens back after the pool completion. If `None`, + /// the caller is set as an admin. + #[pallet::call_index(0)] + pub fn create_pool( + origin: OriginFor, + staked_asset_id: Box, + reward_asset_id: Box, + reward_rate_per_block: T::Balance, + expiry: DispatchTime>, + admin: Option, + ) -> DispatchResult { + // Check the origin. + let creator = T::CreatePoolOrigin::ensure_origin(origin)?; + + // Ensure the assets exist. + ensure!( + T::Assets::asset_exists(*staked_asset_id.clone()), + Error::::NonExistentAsset + ); + ensure!( + T::Assets::asset_exists(*reward_asset_id.clone()), + Error::::NonExistentAsset + ); + + // Check the expiry block. + let expiry_block = expiry.evaluate(frame_system::Pallet::::block_number()); + ensure!( + expiry_block > frame_system::Pallet::::block_number(), + Error::::ExpiryBlockMustBeInTheFuture + ); + + let pool_id = NextPoolId::::get(); + + let footprint = Self::pool_creation_footprint(); + let cost = T::Consideration::new(&creator, footprint)?; + PoolCost::::insert(pool_id, (creator.clone(), cost)); + + let admin = admin.unwrap_or(creator.clone()); + + // Create the pool. + let pool = PoolInfoFor:: { + staked_asset_id: *staked_asset_id.clone(), + reward_asset_id: *reward_asset_id.clone(), + reward_rate_per_block, + total_tokens_staked: 0u32.into(), + reward_per_token_stored: 0u32.into(), + last_update_block: 0u32.into(), + expiry_block, + admin: admin.clone(), + account: Self::pool_account_id(&pool_id), + }; + + // Insert it into storage. + Pools::::insert(pool_id, pool); + + NextPoolId::::put(pool_id.ensure_add(1)?); + + // Emit created event. + Self::deposit_event(Event::PoolCreated { + creator, + pool_id, + staked_asset_id: *staked_asset_id, + reward_asset_id: *reward_asset_id, + reward_rate_per_block, + expiry_block, + admin, + }); + + Ok(()) + } + + /// Stake additional tokens in a pool. + /// + /// A freeze is placed on the staked tokens. + #[pallet::call_index(1)] + pub fn stake(origin: OriginFor, pool_id: PoolId, amount: T::Balance) -> DispatchResult { + let staker = ensure_signed(origin)?; + + // Always start by updating staker and pool rewards. + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + let staker_info = PoolStakers::::get(pool_id, &staker).unwrap_or_default(); + let (mut pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + T::AssetsFreezer::increase_frozen( + pool_info.staked_asset_id.clone(), + &FreezeReason::Staked.into(), + &staker, + amount, + )?; + + // Update Pools. + pool_info.total_tokens_staked.ensure_add_assign(amount)?; + + Pools::::insert(pool_id, pool_info); + + // Update PoolStakers. + staker_info.amount.ensure_add_assign(amount)?; + PoolStakers::::insert(pool_id, &staker, staker_info); + + // Emit event. + Self::deposit_event(Event::Staked { staker, pool_id, amount }); + + Ok(()) + } + + /// Unstake tokens from a pool. + /// + /// Removes the freeze on the staked tokens. + /// + /// Parameters: + /// - origin: must be the `staker` if the pool is still active. Otherwise, any account. + /// - pool_id: the pool to unstake from. + /// - amount: the amount of tokens to unstake. + /// - staker: the account to unstake from. If `None`, the caller is used. + #[pallet::call_index(2)] + pub fn unstake( + origin: OriginFor, + pool_id: PoolId, + amount: T::Balance, + staker: Option, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let staker = staker.unwrap_or(caller.clone()); + + // Always start by updating the pool rewards. + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + let now = frame_system::Pallet::::block_number(); + ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin); + + let staker_info = PoolStakers::::get(pool_id, &staker).unwrap_or_default(); + let (mut pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + // Check the staker has enough staked tokens. + ensure!(staker_info.amount >= amount, Error::::NotEnoughTokens); + + // Unfreeze staker assets. + T::AssetsFreezer::decrease_frozen( + pool_info.staked_asset_id.clone(), + &FreezeReason::Staked.into(), + &staker, + amount, + )?; + + // Update Pools. + pool_info.total_tokens_staked.ensure_sub_assign(amount)?; + Pools::::insert(pool_id, pool_info); + + // Update PoolStakers. + staker_info.amount.ensure_sub_assign(amount)?; + + if staker_info.amount.is_zero() && staker_info.rewards.is_zero() { + PoolStakers::::remove(&pool_id, &staker); + } else { + PoolStakers::::insert(&pool_id, &staker, staker_info); + } + + // Emit event. + Self::deposit_event(Event::Unstaked { caller, staker, pool_id, amount }); + + Ok(()) + } + + /// Harvest unclaimed pool rewards. + /// + /// Parameters: + /// - origin: must be the `staker` if the pool is still active. Otherwise, any account. + /// - pool_id: the pool to harvest from. + /// - staker: the account for which to harvest rewards. If `None`, the caller is used. + #[pallet::call_index(3)] + pub fn harvest_rewards( + origin: OriginFor, + pool_id: PoolId, + staker: Option, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let staker = staker.unwrap_or(caller.clone()); + + // Always start by updating the pool and staker rewards. + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + let now = frame_system::Pallet::::block_number(); + ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin); + + let staker_info = + PoolStakers::::get(pool_id, &staker).ok_or(Error::::NonExistentStaker)?; + let (pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + // Transfer unclaimed rewards from the pool to the staker. + T::Assets::transfer( + pool_info.reward_asset_id, + &pool_info.account, + &staker, + staker_info.rewards, + // Could kill the account, but only if the pool was already almost empty. + Preservation::Expendable, + )?; + + // Emit event. + Self::deposit_event(Event::RewardsHarvested { + caller, + staker: staker.clone(), + pool_id, + amount: staker_info.rewards, + }); + + // Reset staker rewards. + staker_info.rewards = 0u32.into(); + + if staker_info.amount.is_zero() { + PoolStakers::::remove(&pool_id, &staker); + } else { + PoolStakers::::insert(&pool_id, &staker, staker_info); + } + + Ok(()) + } + + /// Modify a pool reward rate. + /// + /// Currently the reward rate can only be increased. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(4)] + pub fn set_pool_reward_rate_per_block( + origin: OriginFor, + pool_id: PoolId, + new_reward_rate_per_block: T::Balance, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + ensure!( + new_reward_rate_per_block > pool_info.reward_rate_per_block, + Error::::RewardRateCut + ); + + // Always start by updating the pool rewards. + let rewards_per_token = Self::reward_per_token(&pool_info)?; + let mut pool_info = Self::update_pool_rewards(&pool_info, rewards_per_token)?; + + pool_info.reward_rate_per_block = new_reward_rate_per_block; + Pools::::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block, + }); + + Ok(()) + } + + /// Modify a pool admin. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(5)] + pub fn set_pool_admin( + origin: OriginFor, + pool_id: PoolId, + new_admin: T::AccountId, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let mut pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + + pool_info.admin = new_admin.clone(); + Pools::::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolAdminModified { pool_id, new_admin }); + + Ok(()) + } + + /// Set when the pool should expire. + /// + /// Currently the expiry block can only be extended. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(6)] + pub fn set_pool_expiry_block( + origin: OriginFor, + pool_id: PoolId, + new_expiry: DispatchTime>, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let new_expiry = new_expiry.evaluate(frame_system::Pallet::::block_number()); + ensure!( + new_expiry > frame_system::Pallet::::block_number(), + Error::::ExpiryBlockMustBeInTheFuture + ); + + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + ensure!(new_expiry > pool_info.expiry_block, Error::::ExpiryCut); + + // Always start by updating the pool rewards. + let reward_per_token = Self::reward_per_token(&pool_info)?; + let mut pool_info = Self::update_pool_rewards(&pool_info, reward_per_token)?; + + pool_info.expiry_block = new_expiry; + Pools::::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolExpiryBlockModified { + pool_id, + new_expiry_block: new_expiry, + }); + + Ok(()) + } + + /// Convenience method to deposit reward tokens into a pool. + /// + /// This method is not strictly necessary (tokens could be transferred directly to the + /// pool pot address), but is provided for convenience so manual derivation of the + /// account id is not required. + #[pallet::call_index(7)] + pub fn deposit_reward_tokens( + origin: OriginFor, + pool_id: PoolId, + amount: T::Balance, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + T::Assets::transfer( + pool_info.reward_asset_id, + &caller, + &pool_info.account, + amount, + Preservation::Preserve, + )?; + Ok(()) + } + + /// Cleanup a pool. + /// + /// Origin must be the pool admin. + /// + /// Cleanup storage, release any associated storage cost and return the remaining reward + /// tokens to the admin. + #[pallet::call_index(8)] + pub fn cleanup_pool(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == who, BadOrigin); + + let stakers = PoolStakers::::iter_key_prefix(pool_id).next(); + ensure!(stakers.is_none(), Error::::NonEmptyPool); + + let pool_balance = T::Assets::reducible_balance( + pool_info.reward_asset_id.clone(), + &pool_info.account, + Preservation::Expendable, + Fortitude::Polite, + ); + T::Assets::transfer( + pool_info.reward_asset_id, + &pool_info.account, + &pool_info.admin, + pool_balance, + Preservation::Expendable, + )?; + + if let Some((who, cost)) = PoolCost::::take(pool_id) { + T::Consideration::drop(cost, &who)?; + } + + Pools::::remove(pool_id); + + Self::deposit_event(Event::PoolCleanedUp { pool_id }); + + Ok(()) + } + } + + impl Pallet { + /// The pool creation footprint. + /// + /// The footprint specifically accounts for the storage footprint of the pool's information + /// itself, excluding any potential storage footprint related to the stakers. + pub fn pool_creation_footprint() -> Footprint { + Footprint::from_mel::<(PoolId, PoolInfoFor)>() + } + + /// Derive a pool account ID from the pool's ID. + pub fn pool_account_id(id: &PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating(id) + } + + /// Computes update pool and staker reward state. + /// + /// Should be called prior to any operation involving a staker. + /// + /// Returns the updated pool and staker info. + /// + /// NOTE: this function has no side-effects. Side-effects such as storage modifications are + /// the responsibility of the caller. + pub fn update_pool_and_staker_rewards( + pool_info: &PoolInfoFor, + staker_info: &PoolStakerInfo, + ) -> Result<(PoolInfoFor, PoolStakerInfo), DispatchError> { + let reward_per_token = Self::reward_per_token(&pool_info)?; + let pool_info = Self::update_pool_rewards(pool_info, reward_per_token)?; + + let mut new_staker_info = staker_info.clone(); + new_staker_info.rewards = Self::derive_rewards(&staker_info, &reward_per_token)?; + new_staker_info.reward_per_token_paid = pool_info.reward_per_token_stored; + return Ok((pool_info, new_staker_info)); + } + + /// Computes update pool reward state. + /// + /// Should be called every time the pool is adjusted, and a staker is not involved. + /// + /// Returns the updated pool and staker info. + /// + /// NOTE: this function has no side-effects. Side-effects such as storage modifications are + /// the responsibility of the caller. + pub fn update_pool_rewards( + pool_info: &PoolInfoFor, + reward_per_token: T::Balance, + ) -> Result, DispatchError> { + let mut new_pool_info = pool_info.clone(); + new_pool_info.last_update_block = frame_system::Pallet::::block_number(); + new_pool_info.reward_per_token_stored = reward_per_token; + + Ok(new_pool_info) + } + + /// Derives the current reward per token for this pool. + fn reward_per_token(pool_info: &PoolInfoFor) -> Result { + if pool_info.total_tokens_staked.is_zero() { + return Ok(pool_info.reward_per_token_stored) + } + + let rewardable_blocks_elapsed: u32 = + match Self::last_block_reward_applicable(pool_info.expiry_block) + .ensure_sub(pool_info.last_update_block)? + .try_into() + { + Ok(b) => b, + Err(_) => return Err(Error::::BlockNumberConversionError.into()), + }; + + Ok(pool_info.reward_per_token_stored.ensure_add( + pool_info + .reward_rate_per_block + .ensure_mul(rewardable_blocks_elapsed.into())? + .ensure_mul(PRECISION_SCALING_FACTOR.into())? + .ensure_div(pool_info.total_tokens_staked)?, + )?) + } + + /// Derives the amount of rewards earned by a staker. + /// + /// This is a helper function for `update_pool_rewards` and should not be called directly. + fn derive_rewards( + staker_info: &PoolStakerInfo, + reward_per_token: &T::Balance, + ) -> Result { + Ok(staker_info + .amount + .ensure_mul(reward_per_token.ensure_sub(staker_info.reward_per_token_paid)?)? + .ensure_div(PRECISION_SCALING_FACTOR.into())? + .ensure_add(staker_info.rewards)?) + } + + fn last_block_reward_applicable(pool_expiry_block: BlockNumberFor) -> BlockNumberFor { + let now = frame_system::Pallet::::block_number(); + if now < pool_expiry_block { + now + } else { + pool_expiry_block + } + } + } +} diff --git a/substrate/frame/asset-rewards/src/mock.rs b/substrate/frame/asset-rewards/src/mock.rs new file mode 100644 index 000000000000..87c8a8a0dea0 --- /dev/null +++ b/substrate/frame/asset-rewards/src/mock.rs @@ -0,0 +1,221 @@ +// 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. + +//! Test environment for Asset Rewards pallet. + +use super::*; +use crate as pallet_asset_rewards; +use core::default::Default; +use frame_support::{ + construct_runtime, derive_impl, + instances::Instance1, + parameter_types, + traits::{ + tokens::fungible::{HoldConsideration, NativeFromLeft, NativeOrWithId, UnionOf}, + AsEnsureOriginWithArg, ConstU128, ConstU32, EnsureOrigin, LinearStoragePrice, + }, + PalletId, +}; +use frame_system::EnsureSigned; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; + +#[cfg(feature = "runtime-benchmarks")] +use self::benchmarking::BenchmarkHelper; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum MockRuntime + { + System: frame_system, + Balances: pallet_balances, + Assets: pallet_assets::, + AssetsFreezer: pallet_assets_freezer::, + StakingRewards: pallet_asset_rewards, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for MockRuntime { + type AccountId = u128; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +impl pallet_balances::Config for MockRuntime { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<100>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type DoneSlashHandler = (); +} + +impl pallet_assets::Config for MockRuntime { + type RuntimeEvent = RuntimeEvent; + type Balance = u128; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = AssetsFreezer; + type Extra = (); + type WeightInfo = (); + type CallbackHandle = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +parameter_types! { + pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd"); + pub const Native: NativeOrWithId = NativeOrWithId::Native; + pub const PermissionedAccountId: u128 = 0; +} + +/// Give Root Origin permission to create pools. +pub struct MockPermissionedOrigin; +impl EnsureOrigin for MockPermissionedOrigin { + type Success = ::AccountId; + + fn try_origin(origin: RuntimeOrigin) -> Result { + match origin.clone().into() { + Ok(frame_system::RawOrigin::Root) => Ok(PermissionedAccountId::get()), + _ => Err(origin), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(RuntimeOrigin::root()) + } +} + +/// Allow Freezes for the `Assets` pallet +impl pallet_assets_freezer::Config for MockRuntime { + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeEvent = RuntimeEvent; +} + +pub type NativeAndAssets = UnionOf, u128>; + +pub type NativeAndAssetsFreezer = + UnionOf, u128>; + +#[cfg(feature = "runtime-benchmarks")] +pub struct AssetRewardsBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper> for AssetRewardsBenchmarkHelper { + fn staked_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(101) + } + fn reward_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(102) + } +} + +parameter_types! { + pub const CreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::StakingRewards(pallet_asset_rewards::HoldReason::PoolCreation); +} + +impl Config for MockRuntime { + type RuntimeEvent = RuntimeEvent; + type AssetId = NativeOrWithId; + type Balance = ::Balance; + type Assets = NativeAndAssets; + type AssetsFreezer = NativeAndAssetsFreezer; + type PalletId = StakingRewardsPalletId; + type CreatePoolOrigin = MockPermissionedOrigin; + type WeightInfo = (); + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + u128, + Balances, + CreationHoldReason, + LinearStoragePrice, ConstU128<0>, u128>, + >; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetRewardsBenchmarkHelper; +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_assets::GenesisConfig:: { + // Genesis assets: id, owner, is_sufficient, min_balance + // pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>, + assets: vec![(1, 1, true, 1), (10, 1, true, 1), (20, 1, true, 1)], + // Genesis metadata: id, name, symbol, decimals + // pub metadata: Vec<(T::AssetId, Vec, Vec, u8)>, + metadata: vec![ + (1, b"test".to_vec(), b"TST".to_vec(), 18), + (10, b"test10".to_vec(), b"T10".to_vec(), 18), + (20, b"test20".to_vec(), b"T20".to_vec(), 18), + ], + // Genesis accounts: id, account_id, balance + // pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>, + accounts: vec![ + (1, 1, 10000), + (1, 2, 20000), + (1, 3, 30000), + (1, 4, 40000), + (1, 10, 40000), + (1, 20, 40000), + ], + next_asset_id: None, + } + .assimilate_storage(&mut t) + .unwrap(); + + let pool_zero_account_id = 31086825966906540362769395565; + pallet_balances::GenesisConfig:: { + balances: vec![ + (0, 10000), + (1, 10000), + (2, 20000), + (3, 30000), + (4, 40000), + (10, 40000), + (20, 40000), + (pool_zero_account_id, 100_000), // Top up the default pool account id + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/substrate/frame/asset-rewards/src/tests.rs b/substrate/frame/asset-rewards/src/tests.rs new file mode 100644 index 000000000000..399d6a54c939 --- /dev/null +++ b/substrate/frame/asset-rewards/src/tests.rs @@ -0,0 +1,1457 @@ +// 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. + +use crate::{mock::*, *}; +use frame_support::{ + assert_err, assert_noop, assert_ok, hypothetically, + traits::{ + fungible, + fungible::NativeOrWithId, + fungibles, + tokens::{Fortitude, Preservation}, + }, +}; +use sp_runtime::{traits::BadOrigin, ArithmeticError, TokenError}; + +const DEFAULT_STAKED_ASSET_ID: NativeOrWithId = NativeOrWithId::::WithId(1); +const DEFAULT_REWARD_ASSET_ID: NativeOrWithId = NativeOrWithId::::Native; +const DEFAULT_REWARD_RATE_PER_BLOCK: u128 = 100; +const DEFAULT_EXPIRE_AFTER: u64 = 200; +const DEFAULT_ADMIN: u128 = 1; + +/// Creates a basic pool with values: +/// - Staking asset: 1 +/// - Reward asset: Native +/// - Reward rate per block: 100 +/// - Lifetime: 100 +/// - Admin: 1 +/// +/// Useful to reduce boilerplate in tests when it's not important to customise or reuse pool +/// params. +pub fn create_default_pool() { + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID.clone()), + Box::new(DEFAULT_REWARD_ASSET_ID.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(DEFAULT_ADMIN) + )); +} + +/// The same as [`create_default_pool`], but with the admin parameter set to the creator. +pub fn create_default_pool_permissioned_admin() { + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID.clone()), + Box::new(DEFAULT_REWARD_ASSET_ID.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()), + )); +} + +fn assert_hypothetically_earned( + staker: u128, + expected_earned: u128, + pool_id: u32, + reward_asset_id: NativeOrWithId, +) { + hypothetically!({ + // Get the pre-harvest balance. + let balance_before: ::Balance = + <::Assets>::balance(reward_asset_id.clone(), &staker); + + // Harvest the rewards. + assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker), pool_id, None),); + + // Sanity check: staker rewards are reset to 0 if some `amount` is still staked, otherwise + // the storage item removed. + if let Some(staker_pool) = PoolStakers::::get(pool_id, staker) { + assert!(staker_pool.rewards == 0); + assert!(staker_pool.amount > 0); + } + + // Check that the staker has earned the expected amount. + let balance_after = + <::Assets>::balance(reward_asset_id.clone(), &staker); + assert_eq!(balance_after - balance_before, expected_earned); + }); +} + +fn events() -> Vec> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let mock::RuntimeEvent::StakingRewards(inner) = e { + Some(inner) + } else { + None + } + }) + .collect(); + + System::reset_events(); + + result +} + +fn pools() -> Vec<(u32, PoolInfo, u128, u64>)> { + Pools::::iter().collect() +} + +mod create_pool { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + // Create a pool with default values, and no admin override so [`PermissionedAccountId`] + // is admin. + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID), + Box::new(DEFAULT_REWARD_ASSET_ID), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::::get(), 1); + assert_eq!( + pools(), + vec![( + 0, + PoolInfo { + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + )] + ); + + // Create another pool with explicit admin and other overrides. + let admin = 2; + let staked_asset_id = NativeOrWithId::::WithId(10); + let reward_asset_id = NativeOrWithId::::WithId(20); + let reward_rate_per_block = 250; + let expiry_block = 500; + let expected_expiry_block = expiry_block + 10; + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(expiry_block), + Some(admin) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 1, + staked_asset_id: staked_asset_id.clone(), + reward_asset_id: reward_asset_id.clone(), + reward_rate_per_block, + admin, + expiry_block: expected_expiry_block, + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::::get(), 2); + assert_eq!( + pools(), + vec![ + ( + 0, + PoolInfo { + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + admin: PermissionedAccountId::get(), + expiry_block: DEFAULT_EXPIRE_AFTER + 10, + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + ), + ( + 1, + PoolInfo { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + admin, + total_tokens_staked: 0, + expiry_block: expected_expiry_block, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&1), + } + ) + ] + ); + }); + } + + #[test] + fn success_same_assets() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + // Create a pool with the same staking and reward asset. + let asset = NativeOrWithId::::Native; + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(asset.clone()), + Box::new(asset.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: asset.clone(), + reward_asset_id: asset.clone(), + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::::get(), 1); + assert_eq!( + pools(), + vec![( + 0, + PoolInfo { + staked_asset_id: asset.clone(), + reward_asset_id: asset, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + )] + ); + }) + } + + #[test] + fn fails_for_non_existent_asset() { + new_test_ext().execute_with(|| { + let valid_asset = NativeOrWithId::::WithId(1); + let invalid_asset = NativeOrWithId::::WithId(200); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(valid_asset.clone()), + Box::new(invalid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::::NonExistentAsset + ); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(invalid_asset.clone()), + Box::new(valid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::::NonExistentAsset + ); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(invalid_asset.clone()), + Box::new(invalid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::::NonExistentAsset + ); + }) + } + + #[test] + fn fails_for_not_permissioned() { + new_test_ext().execute_with(|| { + let user = 100; + let staked_asset_id = NativeOrWithId::::Native; + let reward_asset_id = NativeOrWithId::::WithId(1); + let reward_rate_per_block = 100; + let expiry_block = 100u64; + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::signed(user), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(expiry_block), + None + ), + BadOrigin + ); + }); + } + + #[test] + fn create_pool_with_caller_admin() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID), + Box::new(DEFAULT_REWARD_ASSET_ID), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + None, + )); + + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + assert_eq!(Pools::::get(0).unwrap().admin, PermissionedAccountId::get()); + }); + } +} + +mod stake { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + let initial_balance = >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ); + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::::get(pool_id, user).unwrap().amount, 1000); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Staked { staker: user, amount: 1000, pool_id: 0 } + ); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 1000); + + // Check user's frozen balance is updated + assert_eq!( + >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ), + // - extra 1 for ed + initial_balance - 1000 - 1 + ); + + // User stakes more tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 500)); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Staked { staker: user, amount: 500, pool_id: 0 } + ); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::::get(pool_id, user).unwrap().amount, 1000 + 500); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 1000 + 500); + + assert_eq!( + >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ), + // - extra 1 for ed + initial_balance - 1500 - 1 + ); + + // Event is emitted. + assert_eq!(events(), []); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let user = 1; + assert_err!( + StakingRewards::stake(RuntimeOrigin::signed(user), 999, 1000), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_balance() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + let initial_balance = >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ); + assert_err!( + StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, initial_balance + 1), + TokenError::FundsUnavailable, + ); + }) + } +} + +mod unstake { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // User unstakes tokens + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None)); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Unstaked { + caller: user, + staker: user, + amount: 500, + pool_id: 0 + } + ); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::::get(pool_id, user).unwrap().amount, 500); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 500); + + // User unstakes remaining tokens + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None)); + + // Check that the storage items is removed since stake amount and rewards are zero. + assert!(PoolStakers::::get(pool_id, user).is_none()); + + // Check that the pool's total tokens staked is zero + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 0); + }); + } + + #[test] + fn unstake_for_other() { + new_test_ext().execute_with(|| { + let staker = 1; + let caller = 2; + let pool_id = 0; + let init_block = System::block_number(); + + create_default_pool(); + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + // Fails to unstake for other since pool is still active + assert_noop!( + StakingRewards::unstake(RuntimeOrigin::signed(caller), pool_id, 500, Some(staker)), + BadOrigin, + ); + + System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1); + + assert_ok!(StakingRewards::unstake( + RuntimeOrigin::signed(caller), + pool_id, + 500, + Some(staker) + )); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Unstaked { caller, staker, amount: 500, pool_id: 0 } + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let user = 1; + let non_existent_pool_id = 999; + + // User tries to unstake tokens from a non-existent pool + assert_err!( + StakingRewards::unstake( + RuntimeOrigin::signed(user), + non_existent_pool_id, + 500, + None + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_staked_amount() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // User tries to unstake more tokens than they have staked + assert_err!( + StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 1500, None), + Error::::NotEnoughTokens + ); + }); + } +} + +mod harvest_rewards { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let staker = 1; + let pool_id = 0; + let reward_asset_id = NativeOrWithId::::Native; + create_default_pool(); + + // Stake + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + // Harvest + System::set_block_number(20); + let balance_before: ::Balance = + <::Assets>::balance(reward_asset_id.clone(), &staker); + assert_ok!(StakingRewards::harvest_rewards( + RuntimeOrigin::signed(staker), + pool_id, + None + )); + let balance_after = + <::Assets>::balance(reward_asset_id.clone(), &staker); + + // Assert + assert_eq!( + balance_after - balance_before, + 10 * Pools::::get(pool_id).unwrap().reward_rate_per_block + ); + assert_eq!( + *events().last().unwrap(), + Event::::RewardsHarvested { + caller: staker, + staker, + pool_id, + amount: 10 * Pools::::get(pool_id).unwrap().reward_rate_per_block + } + ); + }); + } + + #[test] + fn harvest_for_other() { + new_test_ext().execute_with(|| { + let caller = 2; + let staker = 1; + let pool_id = 0; + let init_block = System::block_number(); + + create_default_pool(); + + // Stake + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + System::set_block_number(20); + + // Fails to harvest for staker since pool is still active + assert_noop!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(caller), + pool_id, + Some(staker) + ), + BadOrigin + ); + + System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1); + + // Harvest for staker + assert_ok!(StakingRewards::harvest_rewards( + RuntimeOrigin::signed(caller), + pool_id, + Some(staker), + )); + + assert!(matches!( + events().last().unwrap(), + Event::::RewardsHarvested { + caller, + staker, + pool_id, + .. + } if caller == caller && staker == staker && pool_id == pool_id + )); + }); + } + + #[test] + fn fails_for_non_existent_staker() { + new_test_ext().execute_with(|| { + let non_existent_staker = 999; + + create_default_pool(); + assert_err!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(non_existent_staker), + 0, + None + ), + Error::::NonExistentStaker + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let staker = 1; + let non_existent_pool_id = 999; + + assert_err!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(staker), + non_existent_pool_id, + None, + ), + Error::::NonExistentPool + ); + }); + } +} + +mod set_pool_admin { + use super::*; + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let admin = 1; + let new_admin = 2; + let pool_id = 0; + create_default_pool(); + + // Modify the pool admin + assert_ok!(StakingRewards::set_pool_admin( + RuntimeOrigin::signed(admin), + pool_id, + new_admin, + )); + + // Check state + assert_eq!( + *events().last().unwrap(), + Event::::PoolAdminModified { pool_id, new_admin } + ); + assert_eq!(Pools::::get(pool_id).unwrap().admin, new_admin); + }); + } + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let new_admin = 2; + let pool_id = 0; + create_default_pool_permissioned_admin(); + + // Modify the pool admin + assert_ok!(StakingRewards::set_pool_admin(RuntimeOrigin::root(), pool_id, new_admin)); + + // Check state + assert_eq!( + *events().last().unwrap(), + Event::::PoolAdminModified { pool_id, new_admin } + ); + assert_eq!(Pools::::get(pool_id).unwrap().admin, new_admin); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let new_admin = 2; + let non_existent_pool_id = 999; + + assert_err!( + StakingRewards::set_pool_admin( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + new_admin + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let new_admin = 2; + let non_admin = 3; + let pool_id = 0; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_admin( + RuntimeOrigin::signed(non_admin), + pool_id, + new_admin + ), + BadOrigin + ); + }); + } +} + +mod set_pool_expiry_block { + use super::*; + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64; + create_default_pool_permissioned_admin(); + + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::root(), + pool_id, + DispatchTime::At(new_expiry_block), + )); + + // Check state + assert_eq!(Pools::::get(pool_id).unwrap().expiry_block, new_expiry_block); + assert_eq!( + *events().last().unwrap(), + Event::::PoolExpiryBlockModified { pool_id, new_expiry_block } + ); + }); + } + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64; + create_default_pool(); + + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(new_expiry_block) + )); + + // Check state + assert_eq!(Pools::::get(pool_id).unwrap().expiry_block, new_expiry_block); + assert_eq!( + *events().last().unwrap(), + Event::::PoolExpiryBlockModified { pool_id, new_expiry_block } + ); + }); + } + + #[test] + fn extends_reward_accumulation() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker = 2; + let pool_id = 0; + let new_expiry_block = 300u64; + System::set_block_number(10); + create_default_pool(); + + // Regular reward accumulation + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + System::set_block_number(20); + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 10, + pool_id, + NativeOrWithId::::Native, + ); + + // Expiry was block 210, so earned 200 at block 250 + System::set_block_number(250); + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 200, + pool_id, + NativeOrWithId::::Native, + ); + + // Extend expiry 50 more blocks + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(new_expiry_block) + )); + System::set_block_number(350); + + // Staker has been in pool with rewards active for 250 blocks total + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 250, + pool_id, + NativeOrWithId::::Native, + ); + }); + } + + #[test] + fn fails_to_cutback_expiration() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + create_default_pool(); + + assert_noop!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::After(30) + ), + Error::::ExpiryCut + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let non_existent_pool_id = 999; + let new_expiry_block = 200u64; + + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + DispatchTime::After(new_expiry_block) + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let non_admin = 2; + let pool_id = 0; + let new_expiry_block = 200u64; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(non_admin), + pool_id, + DispatchTime::After(new_expiry_block) + ), + BadOrigin + ); + }); + } + + #[test] + fn fails_for_expiry_block_in_the_past() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + create_default_pool(); + System::set_block_number(50); + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(40u64) + ), + Error::::ExpiryBlockMustBeInTheFuture + ); + }); + } +} + +mod set_pool_reward_rate_per_block { + use super::*; + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool(); + + // Pool Admin can modify + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(DEFAULT_ADMIN), + pool_id, + new_reward_rate + )); + + // Check state + assert_eq!( + Pools::::get(pool_id).unwrap().reward_rate_per_block, + new_reward_rate + ); + + // Check event + assert_eq!( + *events().last().unwrap(), + Event::::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block: new_reward_rate + } + ); + }); + } + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool_permissioned_admin(); + + // Root can modify + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::root(), + pool_id, + new_reward_rate + )); + + // Check state + assert_eq!( + Pools::::get(pool_id).unwrap().reward_rate_per_block, + new_reward_rate + ); + + // Check event + assert_eq!( + *events().last().unwrap(), + Event::::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block: new_reward_rate + } + ); + }); + } + + #[test] + fn staker_rewards_are_affected_correctly() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker = 2; + let pool_id = 0; + let new_reward_rate = 150; + create_default_pool(); + + // Stake some tokens, and accumulate 10 blocks of rewards at the default pool rate (100) + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + System::set_block_number(20); + + // Increase the reward rate + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + pool_id, + new_reward_rate + )); + + // Accumulate 10 blocks of rewards at the new rate + System::set_block_number(30); + + // Check that rewards are calculated correctly with the updated rate + assert_hypothetically_earned( + staker, + 10 * 100 + 10 * new_reward_rate, + pool_id, + NativeOrWithId::::Native, + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let non_existent_pool_id = 999; + let new_reward_rate = 200; + + assert_err!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + new_reward_rate + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let non_admin = 2; + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(non_admin), + pool_id, + new_reward_rate + ), + BadOrigin + ); + }); + } + + #[test] + fn fails_to_decrease() { + new_test_ext().execute_with(|| { + create_default_pool_permissioned_admin(); + + assert_noop!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::root(), + 0, + DEFAULT_REWARD_RATE_PER_BLOCK - 1 + ), + Error::::RewardRateCut + ); + }); + } +} + +mod deposit_reward_tokens { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let depositor = 1; + let pool_id = 0; + let amount = 1000; + let reward_asset_id = NativeOrWithId::::Native; + create_default_pool(); + let pool_account_id = StakingRewards::pool_account_id(&pool_id); + + let depositor_balance_before = + <::Assets>::balance(reward_asset_id.clone(), &depositor); + let pool_balance_before = <::Assets>::balance( + reward_asset_id.clone(), + &pool_account_id, + ); + assert_ok!(StakingRewards::deposit_reward_tokens( + RuntimeOrigin::signed(depositor), + pool_id, + amount + )); + let depositor_balance_after = + <::Assets>::balance(reward_asset_id.clone(), &depositor); + let pool_balance_after = + <::Assets>::balance(reward_asset_id, &pool_account_id); + + assert_eq!(pool_balance_after - pool_balance_before, amount); + assert_eq!(depositor_balance_before - depositor_balance_after, amount); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + assert_err!( + StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 999, 100), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_balance() { + new_test_ext().execute_with(|| { + create_default_pool(); + assert_err!( + StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 0, 100_000_000), + ArithmeticError::Underflow + ); + }); + } +} + +mod cleanup_pool { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let admin = DEFAULT_ADMIN; + let admin_balance_before = >::balance(&admin); + + create_default_pool(); + assert!(Pools::::get(pool_id).is_some()); + + assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id)); + + assert_eq!( + >::balance(&admin), + // `100_000` initial pool account balance from Genesis config + admin_balance_before + 100_000, + ); + assert_eq!(Pools::::get(pool_id), None); + assert_eq!(PoolStakers::::iter_prefix_values(pool_id).count(), 0); + assert_eq!(PoolCost::::get(pool_id), None); + }); + } + + #[test] + fn success_only_when_pool_empty() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let staker = 20; + let admin = DEFAULT_ADMIN; + + create_default_pool(); + + // stake to prevent pool cleanup + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 100)); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id), + Error::::NonEmptyPool + ); + + // unstake partially + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None)); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id), + Error::::NonEmptyPool + ); + + // unstake all + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None)); + + assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id),); + + assert_eq!(Pools::::get(pool_id), None); + assert_eq!(PoolStakers::::iter_prefix_values(pool_id).count(), 0); + assert_eq!(PoolCost::::get(pool_id), None); + }); + } + + #[test] + fn fails_on_wrong_origin() { + new_test_ext().execute_with(|| { + let caller = 888; + let pool_id = 0; + create_default_pool(); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(caller), pool_id), + BadOrigin + ); + }); + } +} + +/// This integration test +/// 1. Considers 2 stakers each staking and unstaking at different intervals, asserts their +/// claimable rewards are adjusted as expected, and that harvesting works. +/// 2. Checks that rewards are correctly halted after the pool's expiry block, and resume when the +/// pool is extended. +/// 3. Checks that reward rates adjustment works correctly. +/// +/// Note: There are occasionally off by 1 errors due to rounding. In practice this is +/// insignificant. +#[test] +fn integration() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker1 = 10u128; + let staker2 = 20; + let staked_asset_id = NativeOrWithId::::WithId(1); + let reward_asset_id = NativeOrWithId::::Native; + let reward_rate_per_block = 100; + let lifetime = 24u64.into(); + System::set_block_number(1); + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(lifetime), + Some(admin) + )); + let pool_id = 0; + + // Block 7: Staker 1 stakes 100 tokens. + System::set_block_number(7); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100)); + // At this point + // - Staker 1 has earned 0 tokens. + // - Staker 1 is earning 100 tokens per block. + + // Check that Staker 1 has earned 0 tokens. + assert_hypothetically_earned(staker1, 0, pool_id, reward_asset_id.clone()); + + // Block 9: Staker 2 stakes 100 tokens. + System::set_block_number(9); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker2), pool_id, 100)); + // At this point + // - Staker 1 has earned 200 (100*2) tokens. + // - Staker 2 has earned 0 tokens. + // - Staker 1 is earning 50 tokens per block. + // - Staker 2 is earning 50 tokens per block. + + // Check that Staker 1 has earned 200 tokens and Staker 2 has earned 0 tokens. + assert_hypothetically_earned(staker1, 200, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 0, pool_id, reward_asset_id.clone()); + + // Block 12: Staker 1 stakes an additional 100 tokens. + System::set_block_number(12); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100)); + // At this point + // - Staker 1 has earned 350 (200 + (50 * 3)) tokens. + // - Staker 2 has earned 150 (50 * 3) tokens. + // - Staker 1 is earning 66.66 tokens per block. + // - Staker 2 is earning 33.33 tokens per block. + + // Check that Staker 1 has earned 350 tokens and Staker 2 has earned 150 tokens. + assert_hypothetically_earned(staker1, 350, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 150, pool_id, reward_asset_id.clone()); + + // Block 22: Staker 1 unstakes 100 tokens. + System::set_block_number(22); + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None)); + // - Staker 1 has earned 1016 (350 + 66.66 * 10) tokens. + // - Staker 2 has earned 483 (150 + 33.33 * 10) tokens. + // - Staker 1 is earning 50 tokens per block. + // - Staker 2 is earning 50 tokens per block. + assert_hypothetically_earned(staker1, 1016, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 483, pool_id, reward_asset_id.clone()); + + // Block 23: Staker 1 unstakes 100 tokens. + System::set_block_number(23); + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None)); + // - Staker 1 has earned 1065 (1015 + 50) tokens. + // - Staker 2 has earned 533 (483 + 50) tokens. + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 100 tokens per block. + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 533, pool_id, reward_asset_id.clone()); + + // Block 50: Stakers should only have earned 2 blocks worth of tokens (expiry is 25). + System::set_block_number(50); + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 733 (533 + 2 * 100) tokens. + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 0 tokens per block. + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone()); + + // Block 51: Extend the pool expiry block to 60. + System::set_block_number(51); + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 100 tokens per block. + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(60u64), + )); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone()); + + // Block 53: Check rewards are resumed. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 933 (733 + 2 * 100) tokens. + // - Staker 2 is earning 100 tokens per block. + System::set_block_number(53); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 933, pool_id, reward_asset_id.clone()); + + // Block 55: Increase the block reward. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 1133 (933 + 2 * 100) tokens. + // - Staker 2 is earning 50 tokens per block. + System::set_block_number(55); + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + pool_id, + 150 + )); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 1133, pool_id, reward_asset_id.clone()); + + // Block 57: Staker2 harvests their rewards. + System::set_block_number(57); + // - Staker 2 has earned 1433 (1133 + 2 * 150) tokens. + assert_hypothetically_earned(staker2, 1433, pool_id, reward_asset_id.clone()); + // Get the pre-harvest balance. + let balance_before: ::Balance = + <::Assets>::balance(reward_asset_id.clone(), &staker2); + assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker2), pool_id, None)); + let balance_after = + <::Assets>::balance(reward_asset_id.clone(), &staker2); + assert_eq!(balance_after - balance_before, 1433u128); + + // Block 60: Check rewards were adjusted correctly. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 450 (3 * 150) tokens. + System::set_block_number(60); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 450, pool_id, reward_asset_id.clone()); + + // Finally, check events. + assert_eq!( + events(), + [ + Event::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id, + staked_asset_id, + reward_asset_id, + reward_rate_per_block: 100, + expiry_block: 25, + admin, + }, + Event::Staked { staker: staker1, pool_id, amount: 100 }, + Event::Staked { staker: staker2, pool_id, amount: 100 }, + Event::Staked { staker: staker1, pool_id, amount: 100 }, + Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 }, + Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 }, + Event::PoolExpiryBlockModified { pool_id, new_expiry_block: 60 }, + Event::PoolRewardRateModified { pool_id, new_reward_rate_per_block: 150 }, + Event::RewardsHarvested { caller: staker2, staker: staker2, pool_id, amount: 1433 } + ] + ); + }); +} diff --git a/substrate/frame/asset-rewards/src/weights.rs b/substrate/frame/asset-rewards/src/weights.rs new file mode 100644 index 000000000000..c9e2d0fd251a --- /dev/null +++ b/substrate/frame/asset-rewards/src/weights.rs @@ -0,0 +1,368 @@ +// 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_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/asset-rewards/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_asset_rewards`. +pub trait WeightInfo { + fn create_pool() -> Weight; + fn stake() -> Weight; + fn unstake() -> Weight; + fn harvest_rewards() -> Weight; + fn set_pool_reward_rate_per_block() -> Weight; + fn set_pool_admin() -> Weight; + fn set_pool_expiry_block() -> Weight; + fn deposit_reward_tokens() -> Weight; + fn cleanup_pool() -> Weight; +} + +/// Weights for `pallet_asset_rewards` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `6360` + // Minimum execution time: 62_655_000 picoseconds. + Weight::from_parts(63_723_000, 6360) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 54_463_000 picoseconds. + Weight::from_parts(55_974_000, 3615) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 55_749_000 picoseconds. + Weight::from_parts(57_652_000, 3615) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `6208` + // Minimum execution time: 69_372_000 picoseconds. + Weight::from_parts(70_278_000, 6208) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_284_000 picoseconds. + Weight::from_parts(19_791_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 17_388_000 picoseconds. + Weight::from_parts(18_390_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_780_000 picoseconds. + Weight::from_parts(20_676_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `840` + // Estimated: `6208` + // Minimum execution time: 57_746_000 picoseconds. + Weight::from_parts(59_669_000, 6208) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1236` + // Estimated: `6208` + // Minimum execution time: 110_443_000 picoseconds. + Weight::from_parts(113_149_000, 6208) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `6360` + // Minimum execution time: 62_655_000 picoseconds. + Weight::from_parts(63_723_000, 6360) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 54_463_000 picoseconds. + Weight::from_parts(55_974_000, 3615) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 55_749_000 picoseconds. + Weight::from_parts(57_652_000, 3615) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `6208` + // Minimum execution time: 69_372_000 picoseconds. + Weight::from_parts(70_278_000, 6208) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_284_000 picoseconds. + Weight::from_parts(19_791_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 17_388_000 picoseconds. + Weight::from_parts(18_390_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_780_000 picoseconds. + Weight::from_parts(20_676_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `840` + // Estimated: `6208` + // Minimum execution time: 57_746_000 picoseconds. + Weight::from_parts(59_669_000, 6208) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1236` + // Estimated: `6208` + // Minimum execution time: 110_443_000 picoseconds. + Weight::from_parts(113_149_000, 6208) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + } +} diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 728426cc84c7..4a83c809a6a5 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -96,8 +96,9 @@ mod storage; #[cfg(feature = "experimental")] pub use storage::MaybeConsideration; pub use storage::{ - Consideration, Footprint, Incrementable, Instance, LinearStoragePrice, PartialStorageInfoTrait, - StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, WhitelistedStorageKeys, + Consideration, ConstantStoragePrice, Footprint, Incrementable, Instance, LinearStoragePrice, + PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, + WhitelistedStorageKeys, }; mod dispatch; diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index 2b8e43707389..676b73e03d3c 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -200,6 +200,18 @@ where } } +/// Constant `Price` regardless of the given [`Footprint`]. +pub struct ConstantStoragePrice(PhantomData<(Price, Balance)>); +impl Convert for ConstantStoragePrice +where + Price: Get, + Balance: From + sp_runtime::Saturating, +{ + fn convert(_: Footprint) -> Balance { + Price::get() + } +} + /// Some sort of cost taken from account temporarily in order to offset the cost to the chain of /// holding some data [`Footprint`] in state. /// diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 17a7c02e8259..fc0b2d5a140e 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -57,6 +57,7 @@ std = [ "pallet-asset-conversion-tx-payment?/std", "pallet-asset-conversion?/std", "pallet-asset-rate?/std", + "pallet-asset-rewards?/std", "pallet-asset-tx-payment?/std", "pallet-assets-freezer?/std", "pallet-assets?/std", @@ -256,6 +257,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-tx-payment?/runtime-benchmarks", "pallet-asset-conversion?/runtime-benchmarks", "pallet-asset-rate?/runtime-benchmarks", + "pallet-asset-rewards?/runtime-benchmarks", "pallet-asset-tx-payment?/runtime-benchmarks", "pallet-assets-freezer?/runtime-benchmarks", "pallet-assets?/runtime-benchmarks", @@ -386,6 +388,7 @@ try-runtime = [ "pallet-asset-conversion-tx-payment?/try-runtime", "pallet-asset-conversion?/try-runtime", "pallet-asset-rate?/try-runtime", + "pallet-asset-rewards?/try-runtime", "pallet-asset-tx-payment?/try-runtime", "pallet-assets-freezer?/try-runtime", "pallet-assets?/try-runtime", @@ -543,7 +546,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-rewards", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -870,6 +873,11 @@ default-features = false optional = true path = "../substrate/frame/asset-rate" +[dependencies.pallet-asset-rewards] +default-features = false +optional = true +path = "../substrate/frame/asset-rewards" + [dependencies.pallet-asset-tx-payment] default-features = false optional = true diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 3504f081f295..a132f16a2c33 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -312,6 +312,10 @@ pub use pallet_asset_conversion_tx_payment; #[cfg(feature = "pallet-asset-rate")] pub use pallet_asset_rate; +/// FRAME asset rewards pallet. +#[cfg(feature = "pallet-asset-rewards")] +pub use pallet_asset_rewards; + /// pallet to manage transaction payments in assets. #[cfg(feature = "pallet-asset-tx-payment")] pub use pallet_asset_tx_payment; From 64abc745d9a7e7d6bea471e7bd2e895c503199c2 Mon Sep 17 00:00:00 2001 From: Giuseppe Re Date: Thu, 16 Jan 2025 15:00:59 +0100 Subject: [PATCH 6/6] Update `parity-publish` to v0.10.4 (#7193) The changes from v0.10.3 are only related to dependencies version. This should fix some failing CIs. This PR also updates the Rust cache version in CI. --- .github/workflows/check-semver.yml | 2 +- .github/workflows/publish-check-compile.yml | 6 ++++-- .github/workflows/publish-check-crates.yml | 4 ++-- .github/workflows/publish-claim-crates.yml | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 0da3e54ef60b..43c70d6abc78 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -81,7 +81,7 @@ jobs: - name: install parity-publish if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} # Set the target dir to cache the build. - run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.3 --locked -q + run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.4 --locked -q - name: check semver if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} diff --git a/.github/workflows/publish-check-compile.yml b/.github/workflows/publish-check-compile.yml index ce1b2cb231d0..f20909106a82 100644 --- a/.github/workflows/publish-check-compile.yml +++ b/.github/workflows/publish-check-compile.yml @@ -26,12 +26,14 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 with: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.10.3 --locked -q + run: | + rustup override set 1.82.0 + cargo install parity-publish@0.10.4 --locked -q - name: parity-publish update plan run: parity-publish --color always plan --skip-check --prdoc prdoc/ diff --git a/.github/workflows/publish-check-crates.yml b/.github/workflows/publish-check-crates.yml index 3150cb9dd405..c1b13243ba19 100644 --- a/.github/workflows/publish-check-crates.yml +++ b/.github/workflows/publish-check-crates.yml @@ -19,12 +19,12 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 with: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.10.3 --locked -q + run: cargo install parity-publish@0.10.4 --locked -q - name: parity-publish check run: parity-publish --color always check --allow-unpublished diff --git a/.github/workflows/publish-claim-crates.yml b/.github/workflows/publish-claim-crates.yml index a6efc8a5599e..804baf9ff06c 100644 --- a/.github/workflows/publish-claim-crates.yml +++ b/.github/workflows/publish-claim-crates.yml @@ -13,12 +13,12 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 with: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.10.3 --locked -q + run: cargo install parity-publish@0.10.4 --locked -q - name: parity-publish claim env: