From b7a682fbf376778b03f5dc2db22ca0fd65f8b559 Mon Sep 17 00:00:00 2001 From: gpestana Date: Sat, 7 Jan 2023 23:36:11 +0100 Subject: [PATCH 1/3] Adds custom RPC to staking related calls --- Cargo.lock | 28 ++++++ Cargo.toml | 2 + bin/node/rpc/Cargo.toml | 1 + bin/node/rpc/src/lib.rs | 3 + bin/node/runtime/Cargo.toml | 1 + bin/node/runtime/src/lib.rs | 11 +++ frame/nomination-pools/src/lib.rs | 6 ++ frame/staking/rpc/Cargo.toml | 24 +++++ frame/staking/rpc/README.md | 3 + frame/staking/rpc/runtime-api/Cargo.toml | 30 +++++++ frame/staking/rpc/runtime-api/README.md | 3 + frame/staking/rpc/runtime-api/src/lib.rs | 33 +++++++ frame/staking/rpc/src/lib.rs | 107 +++++++++++++++++++++++ frame/staking/src/pallet/impls.rs | 5 ++ 14 files changed, 257 insertions(+) create mode 100644 frame/staking/rpc/Cargo.toml create mode 100644 frame/staking/rpc/README.md create mode 100644 frame/staking/rpc/runtime-api/Cargo.toml create mode 100644 frame/staking/rpc/runtime-api/README.md create mode 100644 frame/staking/rpc/runtime-api/src/lib.rs create mode 100644 frame/staking/rpc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index eb644202ae382..b081bd26a54fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3479,6 +3479,7 @@ dependencies = [ "pallet-society", "pallet-staking", "pallet-staking-reward-curve", + "pallet-staking-rpc-runtime-api", "pallet-state-trie-migration", "pallet-sudo", "pallet-timestamp", @@ -4826,6 +4827,7 @@ dependencies = [ "jsonrpsee", "mmr-rpc", "node-primitives", + "pallet-staking-rpc", "pallet-transaction-payment-rpc", "sc-chain-spec", "sc-client-api", @@ -6395,6 +6397,32 @@ dependencies = [ "sp-arithmetic", ] +[[package]] +name = "pallet-staking-rpc" +version = "4.0.0-dev" +dependencies = [ + "jsonrpsee", + "pallet-staking-rpc-runtime-api", + "parity-scale-codec", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", + "sp-weights", +] + +[[package]] +name = "pallet-staking-rpc-runtime-api" +version = "4.0.0-dev" +dependencies = [ + "pallet-staking", + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-weights", +] + [[package]] name = "pallet-state-trie-migration" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 8f55d8e527ecd..4ab4e4cf340ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,6 +141,8 @@ members = [ "frame/staking", "frame/staking/reward-curve", "frame/staking/reward-fn", + "frame/staking/rpc", + "frame/staking/rpc/runtime-api", "frame/state-trie-migration", "frame/sudo", "frame/root-offences", diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index f34922a287dfe..48252c722ddb7 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] jsonrpsee = { version = "0.16.2", features = ["server"] } node-primitives = { version = "2.0.0", path = "../primitives" } pallet-transaction-payment-rpc = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/rpc/" } +pallet-staking-rpc = { version = "4.0.0-dev", path = "../../../frame/staking/rpc/" } mmr-rpc = { version = "4.0.0-dev", path = "../../../client/merkle-mountain-range/rpc/" } sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } diff --git a/bin/node/rpc/src/lib.rs b/bin/node/rpc/src/lib.rs index 0dc5ba4039b00..1bd2b26445a60 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -110,6 +110,7 @@ where C::Api: substrate_frame_rpc_system::AccountNonceApi, C::Api: mmr_rpc::MmrRuntimeApi::Hash, BlockNumber>, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, + C::Api: pallet_staking_rpc::StakingRuntimeApi, C::Api: BabeApi, C::Api: BlockBuilder, P: TransactionPool + 'static, @@ -118,6 +119,7 @@ where B::State: sc_client_api::backend::StateBackend>, { use mmr_rpc::{Mmr, MmrApiServer}; + use pallet_staking_rpc::{Staking, StakingApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use sc_consensus_babe_rpc::{Babe, BabeApiServer}; use sc_finality_grandpa_rpc::{Grandpa, GrandpaApiServer}; @@ -150,6 +152,7 @@ where // These RPCs should use an asynchronous caller instead. io.merge(Mmr::new(client.clone()).into_rpc())?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; + io.merge(Staking::new(client.clone()).into_rpc())?; io.merge( Babe::new( client.clone(), diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 7037be9d7c54d..56f4b42abc92e 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -96,6 +96,7 @@ pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = ". pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/reward-curve" } +pallet-staking-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/rpc/runtime-api/" } pallet-state-trie-migration = { version = "4.0.0-dev", default-features = false, path = "../../../frame/state-trie-migration" } pallet-scheduler = { version = "4.0.0-dev", default-features = false, path = "../../../frame/scheduler" } pallet-society = { version = "4.0.0-dev", default-features = false, path = "../../../frame/society" } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index a9e93a16f0713..8e4fcd615464b 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -59,6 +59,7 @@ use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use pallet_nfts::PalletFeatures; use pallet_nis::WithMaximumOf; use pallet_session::historical::{self as pallet_session_historical}; +use pallet_staking::Nominators; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; @@ -2132,6 +2133,16 @@ impl_runtime_apis! { } } + impl pallet_staking_rpc_runtime_api::StakingApi for Runtime { + fn query_nominations_quota() -> u32 { + Staking::query_nominations_quota() + } + + fn query_points_to_balance(pool_id: u32) -> Result { + NominationPools::query_points_to_balance(pool_id) + } + } + impl pallet_mmr::primitives::MmrApi< Block, mmr::Hash, diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 99bfc8b8c36a5..66feb1dba95fb 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -2140,6 +2140,12 @@ pub mod pallet { } impl Pallet { + // Query points to balance for a given pool. Used for custom RPC call. + pub fn query_points_to_balance(pool_id: u32) -> Result { + // TODO(gpestana): finish + Ok(0) + } + /// Returns the pending rewards for the specified `member_account`. /// /// In the case of error, `None` is returned. diff --git a/frame/staking/rpc/Cargo.toml b/frame/staking/rpc/Cargo.toml new file mode 100644 index 0000000000000..cf9c192456355 --- /dev/null +++ b/frame/staking/rpc/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "pallet-staking-rpc" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "RPC interface for the staking pallet." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0" } +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +pallet-staking-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-rpc = { version = "6.0.0", path = "../../../primitives/rpc" } +sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-weights = { version = "4.0.0", path = "../../../primitives/weights" } diff --git a/frame/staking/rpc/README.md b/frame/staking/rpc/README.md new file mode 100644 index 0000000000000..8a5a617d1a370 --- /dev/null +++ b/frame/staking/rpc/README.md @@ -0,0 +1,3 @@ +RPC interface for the staking pallet. + +License: Apache-2.0 diff --git a/frame/staking/rpc/runtime-api/Cargo.toml b/frame/staking/rpc/runtime-api/Cargo.toml new file mode 100644 index 0000000000000..351cd37b0991c --- /dev/null +++ b/frame/staking/rpc/runtime-api/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "pallet-staking-rpc-runtime-api" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "RPC runtime API for transaction payment FRAME pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../staking" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../../../primitives/weights" } + +[features] +default = ["std"] +std = [ + "codec/std", + "pallet-staking/std", + "sp-api/std", + "sp-runtime/std", + "sp-weights/std", +] diff --git a/frame/staking/rpc/runtime-api/README.md b/frame/staking/rpc/runtime-api/README.md new file mode 100644 index 0000000000000..a999e519f8cbf --- /dev/null +++ b/frame/staking/rpc/runtime-api/README.md @@ -0,0 +1,3 @@ +Runtime API definition for the staking pallet. + +License: Apache-2.0 diff --git a/frame/staking/rpc/runtime-api/src/lib.rs b/frame/staking/rpc/runtime-api/src/lib.rs new file mode 100644 index 0000000000000..8d13ba460bfc4 --- /dev/null +++ b/frame/staking/rpc/runtime-api/src/lib.rs @@ -0,0 +1,33 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Runtime API definition for the staking pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; +use sp_runtime::traits::MaybeDisplay; + +sp_api::decl_runtime_apis! { + #[api_version(1)] + pub trait StakingApi where + Balance: Codec + MaybeDisplay, + { + fn query_nominations_quota() -> u32; + fn query_points_to_balance(pool_id: u32) -> Result; + } +} diff --git a/frame/staking/rpc/src/lib.rs b/frame/staking/rpc/src/lib.rs new file mode 100644 index 0000000000000..1cecf8c5a224a --- /dev/null +++ b/frame/staking/rpc/src/lib.rs @@ -0,0 +1,107 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! RPC interface for the staking pallet. + +use std::sync::Arc; + +use codec::Codec; +use jsonrpsee::{ + core::RpcResult, + proc_macros::rpc, + types::error::{CallError, ErrorObject}, +}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_rpc::number::NumberOrHex; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, MaybeDisplay}, +}; + +pub use pallet_staking_rpc_runtime_api::StakingApi as StakingRuntimeApi; + +#[rpc(client, server)] +pub trait StakingApi { + #[method(name = "staking_nominationsQuota")] + fn query_nominations_quota(&self) -> RpcResult; + #[method(name = "nominationPools_pointsToBalance")] + fn query_points_to_balance(&self, pool_id: u32) -> RpcResult; +} + +pub struct Staking { + client: Arc, + _marker: std::marker::PhantomData

, +} + +impl Staking { + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } + } +} + +/// Error type of this RPC api. +pub enum Error { + /// The call to runtime failed. + RuntimeError, +} + +impl From for i32 { + fn from(e: Error) -> i32 { + match e { + Error::RuntimeError => 1, + } + } +} + +impl StakingApiServer for Staking +where + Block: BlockT, + C: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, + C::Api: StakingRuntimeApi, + Balance: Codec + MaybeDisplay + Copy + TryInto + Send + Sync + 'static, +{ + fn query_nominations_quota(&self) -> RpcResult { + let api = self.client.runtime_api(); + let at = BlockId::hash(self.client.info().best_hash); + + let runtime_api_result = api.query_nominations_quota(&at); + + Ok(runtime_api_result.map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query the nominations quota.", + Some(e.to_string()), + )) + })?) + } + + fn query_points_to_balance(&self, pool_id: u32) -> RpcResult { + let api = self.client.runtime_api(); + let at = BlockId::hash(self.client.info().best_hash); + + let runtime_api_result = api.query_points_to_balance(&at, pool_id); + + runtime_api_result.map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query the points to balance conversion for pool.", + Some(e.to_string()), + )) + })? + } +} diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index db9aeba6fb58e..67efdb9ebfc36 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -59,6 +59,11 @@ use super::{pallet::*, STAKING_ID}; const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { + /// Query nominations quota for a given balance. Used for custom RPC call. + pub fn query_nominations_quota() -> u32 { + T::MaxNominations::get() + } + /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash. From a0e21bdbf1af751e0b3908816414c0ad45bb641a Mon Sep 17 00:00:00 2001 From: gpestana Date: Mon, 9 Jan 2023 00:48:52 +0100 Subject: [PATCH 2/3] error handling --- frame/staking/rpc/src/lib.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frame/staking/rpc/src/lib.rs b/frame/staking/rpc/src/lib.rs index 1cecf8c5a224a..ed65ac1000083 100644 --- a/frame/staking/rpc/src/lib.rs +++ b/frame/staking/rpc/src/lib.rs @@ -94,14 +94,20 @@ where let api = self.client.runtime_api(); let at = BlockId::hash(self.client.info().best_hash); - let runtime_api_result = api.query_points_to_balance(&at, pool_id); - - runtime_api_result.map_err(|e| { + api.query_points_to_balance(&at, pool_id).map_err(|e| { CallError::Custom(ErrorObject::owned( Error::RuntimeError.into(), "Unable to query the points to balance conversion for pool.", Some(e.to_string()), )) - })? + }).and_then(|r| { + r.map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query the points to balance conversion for pool.", + Some(e.to_string()), + )) + })? + }) } } From d8ea2643b097429e5ad348b61d1a2df043ce614d Mon Sep 17 00:00:00 2001 From: gpestana Date: Mon, 9 Jan 2023 14:34:03 +0100 Subject: [PATCH 3/3] Fixes --- bin/node/runtime/src/lib.rs | 7 ++-- frame/nomination-pools/src/lib.rs | 13 +++++--- frame/staking/rpc/runtime-api/src/lib.rs | 2 +- frame/staking/rpc/src/lib.rs | 42 ++++++++++++------------ 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8e4fcd615464b..8df59e0129fd9 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -59,7 +59,6 @@ use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use pallet_nfts::PalletFeatures; use pallet_nis::WithMaximumOf; use pallet_session::historical::{self as pallet_session_historical}; -use pallet_staking::Nominators; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; @@ -2138,9 +2137,9 @@ impl_runtime_apis! { Staking::query_nominations_quota() } - fn query_points_to_balance(pool_id: u32) -> Result { - NominationPools::query_points_to_balance(pool_id) - } + fn query_points_to_balance(points: Balance, pool_id: u32) -> Result { + NominationPools::query_points_to_balance(points, pool_id) + } } impl pallet_mmr::primitives::MmrApi< diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 66feb1dba95fb..a83fddc0ccf38 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -2140,11 +2140,14 @@ pub mod pallet { } impl Pallet { - // Query points to balance for a given pool. Used for custom RPC call. - pub fn query_points_to_balance(pool_id: u32) -> Result { - // TODO(gpestana): finish - Ok(0) - } + // Query points to balance for a given pool. Used for custom RPC call. + pub fn query_points_to_balance(points: BalanceOf, pool_id: u32) -> Result, ()> { + if let Some(pool) = BondedPool::::get(pool_id).defensive() { + Ok(pool.points_to_balance(points)) + } else { + Err(()) + } + } /// Returns the pending rewards for the specified `member_account`. /// diff --git a/frame/staking/rpc/runtime-api/src/lib.rs b/frame/staking/rpc/runtime-api/src/lib.rs index 8d13ba460bfc4..81bc11e10b974 100644 --- a/frame/staking/rpc/runtime-api/src/lib.rs +++ b/frame/staking/rpc/runtime-api/src/lib.rs @@ -28,6 +28,6 @@ sp_api::decl_runtime_apis! { Balance: Codec + MaybeDisplay, { fn query_nominations_quota() -> u32; - fn query_points_to_balance(pool_id: u32) -> Result; + fn query_points_to_balance(points: Balance, pool_id: u32) -> Result; } } diff --git a/frame/staking/rpc/src/lib.rs b/frame/staking/rpc/src/lib.rs index ed65ac1000083..5a76cf0d9ba7c 100644 --- a/frame/staking/rpc/src/lib.rs +++ b/frame/staking/rpc/src/lib.rs @@ -39,8 +39,8 @@ pub use pallet_staking_rpc_runtime_api::StakingApi as StakingRuntimeApi; pub trait StakingApi { #[method(name = "staking_nominationsQuota")] fn query_nominations_quota(&self) -> RpcResult; - #[method(name = "nominationPools_pointsToBalance")] - fn query_points_to_balance(&self, pool_id: u32) -> RpcResult; + #[method(name = "nominationPools_pointsToBalance")] + fn query_points_to_balance(&self, balance: Balance, pool_id: u32) -> RpcResult; } pub struct Staking { @@ -55,15 +55,15 @@ impl Staking { } /// Error type of this RPC api. -pub enum Error { +pub enum ApiError { /// The call to runtime failed. RuntimeError, } -impl From for i32 { - fn from(e: Error) -> i32 { +impl From for i32 { + fn from(e: ApiError) -> i32 { match e { - Error::RuntimeError => 1, + ApiError::RuntimeError => 1, } } } @@ -83,31 +83,31 @@ where Ok(runtime_api_result.map_err(|e| { CallError::Custom(ErrorObject::owned( - Error::RuntimeError.into(), + ApiError::RuntimeError.into(), "Unable to query the nominations quota.", Some(e.to_string()), )) })?) } - fn query_points_to_balance(&self, pool_id: u32) -> RpcResult { - let api = self.client.runtime_api(); + fn query_points_to_balance(&self, balance: Balance, pool_id: u32) -> RpcResult { + let api = self.client.runtime_api(); let at = BlockId::hash(self.client.info().best_hash); - api.query_points_to_balance(&at, pool_id).map_err(|e| { + let result = api.query_points_to_balance(&at, balance, pool_id).map_err(|e| { CallError::Custom(ErrorObject::owned( - Error::RuntimeError.into(), + ApiError::RuntimeError.into(), "Unable to query the points to balance conversion for pool.", Some(e.to_string()), )) - }).and_then(|r| { - r.map_err(|e| { - CallError::Custom(ErrorObject::owned( - Error::RuntimeError.into(), - "Unable to query the points to balance conversion for pool.", - Some(e.to_string()), - )) - })? - }) - } + })?; + + Ok(result.map_err(|_| { + CallError::Custom(ErrorObject::owned( + ApiError::RuntimeError.into(), + "Pool ID not found", + None::, + )) + })?) + } }