Skip to content

Commit e94b5b1

Browse files
feat(namespace): cost calculation rpc (#106)
Adds a new RPC call, `torus0_namespacePathCreationCost`, that calculates the total fee and deposit needed to create a new namespace entry.
1 parent 240d29b commit e94b5b1

File tree

13 files changed

+502
-34
lines changed

13 files changed

+502
-34
lines changed

Cargo.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pallet-emission0-api = { path = "./pallets/emission0/api", default-features = fa
3535
pallet-faucet = { path = "./pallets/faucet", default-features = false }
3636
pallet-torus0 = { path = "./pallets/torus0", default-features = false }
3737
pallet-torus0-api = { path = "./pallets/torus0/api", default-features = false }
38+
pallet-torus0-rpc = { path = "./pallets/torus0/rpc", default-features = false }
3839
pallet-permission0 = { path = "./pallets/permission0", default-features = false }
3940
pallet-permission0-api = { path = "./pallets/permission0/api", default-features = false }
4041
pallet-permission0-rpc = { path = "./pallets/permission0/rpc", default-features = false }

node/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ fp-rpc = { workspace = true, features = ["default"] }
107107
# Local
108108
torus-runtime.workspace = true
109109
pallet-permission0-rpc.workspace = true
110+
pallet-torus0-rpc.workspace = true
110111

111112
[build-dependencies]
112113
polkadot-sdk = { workspace = true, features = ["substrate-build-script-utils"] }

node/src/rpc.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use eth::EthDeps;
2828
use futures::channel::mpsc;
2929
use jsonrpsee::RpcModule;
3030
use pallet_permission0_rpc::{Permission0Rpc, Permission0StreamApiServer};
31+
use pallet_torus0_rpc::{Torus0ApiServer, Torus0Rpc};
3132
use polkadot_sdk::{
3233
pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer},
3334
sc_consensus_manual_seal::{
@@ -86,7 +87,8 @@ where
8687

8788
io.merge(System::new(client.clone(), pool).into_rpc())?;
8889
io.merge(TransactionPayment::new(client.clone()).into_rpc())?;
89-
io.merge(Permission0Rpc::new(client).into_rpc())?;
90+
io.merge(Permission0Rpc::new(client.clone()).into_rpc())?;
91+
io.merge(Torus0Rpc::new(client.clone()).into_rpc())?;
9092

9193
if let Some(command_sink) = command_sink {
9294
io.merge(

pallets/torus0/api/src/api.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![allow(clippy::multiple_bound_locations)]
2+
3+
use codec::{Decode, Encode};
4+
use polkadot_sdk::sp_runtime::DispatchError;
5+
6+
polkadot_sdk::sp_api::decl_runtime_apis! {
7+
/// RPC related to Torus0.
8+
pub trait Torus0RuntimeApi<AccountId: Encode, Balance: Decode> {
9+
/// Calculates the total creation cost of a namespace: (Fee, Deposit).
10+
fn namespace_path_creation_cost(account_id: AccountId, path: crate::NamespacePathInner) -> Result<(Balance, Balance), DispatchError>;
11+
}
12+
}

pallets/torus0/api/src/lib.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
extern crate alloc;
44
extern crate polkadot_sdk;
55

6-
use core::str::FromStr;
6+
use core::{
7+
fmt::{Debug, Display},
8+
str::FromStr,
9+
};
710

811
use codec::{Decode, Encode, MaxEncodedLen};
912
use polkadot_sdk::{
@@ -13,6 +16,8 @@ use polkadot_sdk::{
1316
};
1417
use scale_info::TypeInfo;
1518

19+
pub mod api;
20+
1621
/// The Torus0 pallet API.
1722
pub trait Torus0Api<AccountId, Balance> {
1823
/// Interval of blocks in which rewards are distributed.
@@ -66,7 +71,7 @@ pub const NAMESPACE_SEPARATOR: u8 = b'.';
6671

6772
pub type NamespacePathInner = BoundedVec<u8, ConstU32<{ MAX_NAMESPACE_PATH_LENGTH as u32 }>>;
6873

69-
#[derive(Encode, Decode, Clone, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen, Debug)]
74+
#[derive(Encode, Decode, Clone, PartialEq, Eq, PartialOrd, Ord, TypeInfo, MaxEncodedLen)]
7075
pub struct NamespacePath(NamespacePathInner);
7176

7277
impl NamespacePath {
@@ -171,6 +176,20 @@ impl NamespacePath {
171176
}
172177
}
173178

179+
impl Debug for NamespacePath {
180+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
181+
f.debug_tuple("NamespacePath")
182+
.field(&core::str::from_utf8(&self.0))
183+
.finish()
184+
}
185+
}
186+
187+
impl Display for NamespacePath {
188+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
189+
f.write_str(core::str::from_utf8(&self.0).unwrap_or("invalid path"))
190+
}
191+
}
192+
174193
impl FromStr for NamespacePath {
175194
type Err = &'static str;
176195

pallets/torus0/rpc/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "pallet-torus0-rpc"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lints]
7+
workspace = true
8+
9+
[dependencies]
10+
async-trait.workspace = true
11+
jsonrpsee.workspace = true
12+
pallet-torus0-api = { workspace = true, features = ["std"] }
13+
polkadot-sdk = { workspace = true, features = ["sp-api", "sp-runtime", "std"] }
14+
torus-runtime = { workspace = true, features = ["std"] }

pallets/torus0/rpc/src/lib.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::sync::Arc;
2+
3+
use jsonrpsee::{core::RpcResult, types::ErrorObject};
4+
use pallet_torus0_api::{api::Torus0RuntimeApi, NamespacePathInner};
5+
use polkadot_sdk::{
6+
sp_api::ProvideRuntimeApi,
7+
sp_blockchain::HeaderBackend,
8+
sp_runtime::{
9+
traits::{IdentifyAccount, Verify},
10+
MultiSignature,
11+
},
12+
};
13+
use torus_runtime::opaque::Block;
14+
15+
type Signature = MultiSignature;
16+
type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId;
17+
type Balance = u128;
18+
19+
#[jsonrpsee::proc_macros::rpc(client, server)]
20+
pub trait Torus0Api {
21+
#[method(name = "torus0_namespacePathCreationCost")]
22+
async fn namespace_path_creation_cost(
23+
&self,
24+
account_id: AccountId,
25+
path: NamespacePathInner,
26+
) -> RpcResult<(Balance, Balance)>;
27+
}
28+
29+
pub struct Torus0Rpc<Client> {
30+
client: Arc<Client>,
31+
}
32+
33+
impl<Client> Torus0Rpc<Client> {
34+
pub fn new(client: Arc<Client>) -> Self {
35+
Self { client }
36+
}
37+
}
38+
39+
#[async_trait::async_trait]
40+
impl<Client> Torus0ApiServer for Torus0Rpc<Client>
41+
where
42+
Client: ProvideRuntimeApi<Block> + HeaderBackend<Block> + Send + Sync + 'static,
43+
Client::Api: Torus0RuntimeApi<Block, AccountId, Balance>,
44+
{
45+
async fn namespace_path_creation_cost(
46+
&self,
47+
account_id: AccountId,
48+
path: NamespacePathInner,
49+
) -> RpcResult<(Balance, Balance)> {
50+
let runtime = &*self.client.runtime_api();
51+
let at = self.client.info().best_hash;
52+
53+
runtime
54+
.namespace_path_creation_cost(at, account_id, path)
55+
.map(|res| {
56+
res.map_err(|err| {
57+
ErrorObject::owned(
58+
1,
59+
"namespace cost calculation failed",
60+
Some(format!("{err:?}")),
61+
)
62+
})
63+
})
64+
.map_err(|err| {
65+
ErrorObject::owned(1, "Runtime execution failed", Some(err.to_string()))
66+
})?
67+
}
68+
}

pallets/torus0/src/lib.rs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -394,33 +394,9 @@ pub mod pallet {
394394
Error::<T>::NamespaceAlreadyExists
395395
);
396396

397-
let mut paths_to_create = namespace_path.parents();
398-
paths_to_create.insert(0, namespace_path.clone());
399-
400-
let mut missing_paths = paths_to_create.as_slice();
401-
for (i, segment) in paths_to_create.iter().skip(1).enumerate().rev() {
402-
if !Namespaces::<T>::contains_key(&owner, segment) {
403-
missing_paths = paths_to_create
404-
.get(..i.saturating_add(1))
405-
.unwrap_or(&paths_to_create);
406-
break;
407-
}
408-
}
409-
410-
let current_count = NamespaceCount::<T>::get(&owner);
411-
412-
let pricing_config = NamespacePricingConfig::<T>::get();
413-
let mut total_fee = BalanceOf::<T>::zero();
414-
let mut total_deposit = BalanceOf::<T>::zero();
415-
416-
for (index, path) in missing_paths.iter().enumerate() {
417-
let count = current_count.saturating_add(index as u32);
418-
let fee = pricing_config.namespace_fee(count)?;
419-
let deposit = pricing_config.namespace_deposit(path);
420-
421-
total_fee = total_fee.saturating_add(fee);
422-
total_deposit = total_deposit.saturating_add(deposit);
423-
}
397+
let missing_paths = namespace::find_missing_paths::<T>(&owner, &namespace_path);
398+
let (total_fee, total_deposit) =
399+
namespace::calculate_cost::<T>(&owner, &missing_paths)?;
424400

425401
T::Currency::reserve(&owner, total_deposit)?;
426402

@@ -433,6 +409,7 @@ pub mod pallet {
433409
.map_err(|_| crate::Error::<T>::NotEnoughBalanceToRegisterAgent)?;
434410

435411
let current_block = <frame_system::Pallet<T>>::block_number();
412+
let pricing_config = crate::NamespacePricingConfig::<T>::get();
436413

437414
for path in missing_paths.iter() {
438415
let deposit = pricing_config.namespace_deposit(path);

pallets/torus0/src/namespace.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use polkadot_sdk::{
33
frame_support::{CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound},
44
frame_system::pallet_prelude::BlockNumberFor,
55
sp_runtime::{
6-
traits::{One, Saturating},
6+
traits::{One, Saturating, Zero},
77
FixedPointNumber, FixedU128,
88
},
99
};
@@ -112,3 +112,42 @@ pub struct NamespaceMetadata<T: Config> {
112112
/// Storage deposit paid for this namespace
113113
pub deposit: BalanceOf<T>,
114114
}
115+
116+
pub fn find_missing_paths<T: Config>(
117+
owner: &T::AccountId,
118+
path: &NamespacePath,
119+
) -> Vec<NamespacePath> {
120+
let mut paths_to_create = path.parents();
121+
paths_to_create.insert(0, path.clone());
122+
123+
for (i, segment) in paths_to_create.iter().enumerate().rev() {
124+
if !Namespaces::<T>::contains_key(owner, segment) {
125+
return paths_to_create.get(..=i).unwrap_or_default().to_vec();
126+
}
127+
}
128+
129+
Default::default()
130+
}
131+
132+
/// Calculates the total cost for registering, (Fee, Deposit)
133+
pub fn calculate_cost<T: Config>(
134+
owner: &T::AccountId,
135+
missing_paths: &[NamespacePath],
136+
) -> Result<(BalanceOf<T>, BalanceOf<T>), DispatchError> {
137+
let current_count = NamespaceCount::<T>::get(owner);
138+
139+
let pricing_config = crate::NamespacePricingConfig::<T>::get();
140+
let mut total_fee = BalanceOf::<T>::zero();
141+
let mut total_deposit = BalanceOf::<T>::zero();
142+
143+
for (index, path) in missing_paths.iter().enumerate() {
144+
let count = current_count.saturating_add(index as u32);
145+
let fee = pricing_config.namespace_fee(count)?;
146+
let deposit = pricing_config.namespace_deposit(path);
147+
148+
total_fee = total_fee.saturating_add(fee);
149+
total_deposit = total_deposit.saturating_add(deposit);
150+
}
151+
152+
Ok((total_fee, total_deposit))
153+
}

0 commit comments

Comments
 (0)