From 0dfcd646ccd46e9b446cf5d5f9539a0d68e9ed83 Mon Sep 17 00:00:00 2001 From: devwckd Date: Wed, 22 Oct 2025 20:43:18 -0300 Subject: [PATCH] feat: add permission delegate command to cli --- cli/src/action/permission.rs | 923 +++++++++++++++++++++++++++++++++++ cli/src/cli.rs | 194 +++++++- cli/src/util.rs | 201 ++++++++ 3 files changed, 1316 insertions(+), 2 deletions(-) diff --git a/cli/src/action/permission.rs b/cli/src/action/permission.rs index 245f8f0..8128565 100644 --- a/cli/src/action/permission.rs +++ b/cli/src/action/permission.rs @@ -8,6 +8,929 @@ use crate::{ keypair::Keypair, store::{get_account, get_key}, }; + +pub enum Allocation { + Streams(Vec<(H256, u8)>), + FixedAmount(u128), +} + +pub enum DistributionControl { + Manual, + Automatic(u128), + AtBlock(u64), + Interval(u64), +} + +pub enum Duration { + UntilBlock(u64), + Indefinite, +} + +pub enum RevocationTerms { + Irrevocable, + RevocableByDelegator, + RevocableByArbiters { + accounts: Vec, + required_votes: u32, + }, + RevocableAfter(u64), +} + +pub enum EnforcementAuthority { + None, + ControlledBy { + controllers: Vec, + required_votes: u32, + }, +} + +pub struct DelegateStreamPermissionAction { + key: Keypair, + recipients: Vec<(AccountId32, u16)>, + allocation: Allocation, + distribution: DistributionControl, + duration: Duration, + revocation: RevocationTerms, + enforcement: EnforcementAuthority, + recipient_manager: Option, + weight_setter: Option, +} + +impl Action for DelegateStreamPermissionAction { + type Params = ( + String, + Vec<(AccountId32, u16)>, + Allocation, + DistributionControl, + Duration, + RevocationTerms, + EnforcementAuthority, + Option, + Option, + ); + type ResponseData = DelegateStreamPermissionActionResponse; + + async fn create( + ctx: &mut impl ActionContext, + ( + key, + recipients, + allocation, + distribution, + duration, + revocation, + enforcement, + recipient_manager, + weight_setter, + ): Self::Params, + ) -> anyhow::Result { + let key = get_key(&key)?; + let (_, keypair) = ctx.decrypt(&key)?; + + Ok(Self { + key: keypair, + recipients, + allocation, + distribution, + duration, + revocation, + enforcement, + recipient_manager, + weight_setter, + }) + } + + async fn execute(&self, ctx: &mut impl ActionContext) -> anyhow::Result { + if ctx.is_mainnet() { + let client = TorusClient::for_mainnet().await?; + + let allocation = match &self.allocation { + Allocation::Streams(items) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::StreamAllocation::Streams( + torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(items.iter().map(|(i, j)| + (*i, torus_client::interfaces::mainnet::api::runtime_types::sp_arithmetic::per_things::Percent(*j))).collect()) + ), + Allocation::FixedAmount(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::StreamAllocation::FixedAmount(*value) + }; + + let distribution = match &self.distribution { + DistributionControl::Manual => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Manual, + DistributionControl::Automatic(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Automatic(*value), + DistributionControl::AtBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::AtBlock(*value), + DistributionControl::Interval(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Interval(*value), + }; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + let enforcement = match &self.enforcement { + EnforcementAuthority::None => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::EnforcementAuthority::None, + EnforcementAuthority::ControlledBy { controllers, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::EnforcementAuthority::ControlledBy { + controllers: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(controllers.clone()), + required_votes: *required_votes, + }, + }; + + client + .permission0() + .calls() + .delegate_stream_permission_wait( + torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(self.recipients.clone()), allocation, distribution, duration, revocation, enforcement, self.recipient_manager.clone(), self.weight_setter.clone(), self.key.clone()) + .await? + } else { + let client = TorusClient::for_testnet().await?; + let allocation = match &self.allocation { + Allocation::Streams(items) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::StreamAllocation::Streams( + torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(items.iter().map(|(i, j)| + (*i, torus_client::interfaces::testnet::api::runtime_types::sp_arithmetic::per_things::Percent(*j))).collect()) + ), + Allocation::FixedAmount(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::StreamAllocation::FixedAmount(*value) + }; + + let distribution = match &self.distribution { + DistributionControl::Manual => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Manual, + DistributionControl::Automatic(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Automatic(*value), + DistributionControl::AtBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::AtBlock(*value), + DistributionControl::Interval(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Interval(*value), + }; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + let enforcement = match &self.enforcement { + EnforcementAuthority::None => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::EnforcementAuthority::None, + EnforcementAuthority::ControlledBy { controllers, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::EnforcementAuthority::ControlledBy { + controllers: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(controllers.clone()), + required_votes: *required_votes, + }, + }; + + client + .permission0() + .calls() + .delegate_stream_permission_wait( + torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(self.recipients.clone()), allocation, distribution, duration, revocation, enforcement, self.recipient_manager.clone(), self.weight_setter.clone(), self.key.clone()) + .await? + } + + Ok(DelegateStreamPermissionActionResponse) + } + + async fn estimate_fee(&self, ctx: &mut impl ActionContext) -> anyhow::Result { + let fee = if ctx.is_mainnet() { + let client = TorusClient::for_mainnet().await?; + + let allocation = match &self.allocation { + Allocation::Streams(items) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::StreamAllocation::Streams( + torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(items.iter().map(|(i, j)| + (*i, torus_client::interfaces::mainnet::api::runtime_types::sp_arithmetic::per_things::Percent(*j))).collect()) + ), + Allocation::FixedAmount(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::StreamAllocation::FixedAmount(*value) + }; + + let distribution = match &self.distribution { + DistributionControl::Manual => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Manual, + DistributionControl::Automatic(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Automatic(*value), + DistributionControl::AtBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::AtBlock(*value), + DistributionControl::Interval(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Interval(*value), + }; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + let enforcement = match &self.enforcement { + EnforcementAuthority::None => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::EnforcementAuthority::None, + EnforcementAuthority::ControlledBy { controllers, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::EnforcementAuthority::ControlledBy { + controllers: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(controllers.clone()), + required_votes: *required_votes, + }, + }; + + client + .permission0() + .calls() + .delegate_stream_permission_fee( + torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(self.recipients.clone()), allocation, distribution, duration, revocation, enforcement, self.recipient_manager.clone(), self.weight_setter.clone(), self.key.clone()) + .await? + } else { + let client = TorusClient::for_testnet().await?; + let allocation = match &self.allocation { + Allocation::Streams(items) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::StreamAllocation::Streams( + torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(items.iter().map(|(i, j)| + (*i, torus_client::interfaces::testnet::api::runtime_types::sp_arithmetic::per_things::Percent(*j))).collect()) + ), + Allocation::FixedAmount(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::StreamAllocation::FixedAmount(*value) + }; + + let distribution = match &self.distribution { + DistributionControl::Manual => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Manual, + DistributionControl::Automatic(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Automatic(*value), + DistributionControl::AtBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::AtBlock(*value), + DistributionControl::Interval(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::stream::DistributionControl::Interval(*value), + }; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + let enforcement = match &self.enforcement { + EnforcementAuthority::None => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::EnforcementAuthority::None, + EnforcementAuthority::ControlledBy { controllers, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::EnforcementAuthority::ControlledBy { + controllers: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(controllers.clone()), + required_votes: *required_votes, + }, + }; + + client + .permission0() + .calls() + .delegate_stream_permission_fee( + torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(self.recipients.clone()), allocation, distribution, duration, revocation, enforcement, self.recipient_manager.clone(), self.weight_setter.clone(), self.key.clone()) + .await? + }; + + Ok(fee) + } + + async fn get_changes(&self, ctx: &mut impl ActionContext) -> anyhow::Result> { + let fee = self.estimate_fee(ctx).await?; + Ok(Some(Changes { + changes: vec![format!( + "Delegate stream permission to {} recipients", + self.recipients.len() + )], + fee: Some(fee), + })) + } +} + +#[derive(serde::Serialize)] +pub struct DelegateStreamPermissionActionResponse; + +impl Display for DelegateStreamPermissionActionResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Stream permission delegated successfully") + } +} + +pub struct DelegateNamespacePermissionAction { + pub key: Keypair, + pub recipient: AccountId32, + pub paths: Vec<(Option, Vec)>, + pub duration: Duration, + pub revocation: RevocationTerms, + pub instances: u32, +} + +impl Action for DelegateNamespacePermissionAction { + type Params = ( + String, + String, + Vec<(Option, Vec)>, + Duration, + RevocationTerms, + u32, + ); + type ResponseData = DelegateNamespacePermissionActionResponse; + + async fn create( + ctx: &mut impl ActionContext, + (key, recipient, paths, duration, revocation, instances): Self::Params, + ) -> anyhow::Result { + let key = get_key(&key)?; + let (_, keypair) = ctx.decrypt(&key)?; + + let recipient = get_account(&recipient)?; + + Ok(Self { + key: keypair, + recipient, + paths, + duration, + revocation, + instances, + }) + } + + async fn execute(&self, ctx: &mut impl ActionContext) -> anyhow::Result { + if ctx.is_mainnet() { + let client = TorusClient::for_mainnet().await?; + + let paths = torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap( + self.paths.iter().cloned() + .map(|(id, paths)| (id, torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_set::BoundedBTreeSet( + paths.into_iter().map(|path| torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(path.as_bytes().to_vec())).collect() + ))).collect() + ); + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_namespace_permission_wait( + self.recipient.clone(), + paths, + duration, + revocation, + self.instances, + self.key.clone(), + ) + .await?; + } else { + let client = TorusClient::for_testnet().await?; + + let paths = torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap( + self.paths.iter().cloned() + .map(|(id, paths)| (id, torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_set::BoundedBTreeSet( + paths.into_iter().map(|path| torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(path.as_bytes().to_vec())).collect() + ))).collect() + ); + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_namespace_permission_wait( + self.recipient.clone(), + paths, + duration, + revocation, + self.instances, + self.key.clone(), + ) + .await?; + } + + Ok(DelegateNamespacePermissionActionResponse) + } + + async fn estimate_fee(&self, ctx: &mut impl ActionContext) -> anyhow::Result { + let fee = if ctx.is_mainnet() { + let client = TorusClient::for_mainnet().await?; + + let paths = torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap( + self.paths.iter().cloned() + .map(|(id, paths)| (id, torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_set::BoundedBTreeSet( + paths.into_iter().map(|path| torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(path.as_bytes().to_vec())).collect() + ))).collect() + ); + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_namespace_permission_fee( + self.recipient.clone(), + paths, + duration, + revocation, + self.instances, + self.key.clone(), + ) + .await? + } else { + let client = TorusClient::for_testnet().await?; + + let paths = torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap( + self.paths.iter().cloned() + .map(|(id, paths)| (id, torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_set::BoundedBTreeSet( + paths.into_iter().map(|path| torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(path.as_bytes().to_vec())).collect() + ))).collect() + ); + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_namespace_permission_fee( + self.recipient.clone(), + paths, + duration, + revocation, + self.instances, + self.key.clone(), + ) + .await? + }; + + Ok(fee) + } + + async fn get_changes(&self, ctx: &mut impl ActionContext) -> anyhow::Result> { + let fee = self.estimate_fee(ctx).await?; + Ok(Some(Changes { + changes: vec![format!( + "Delegate namespace permission to {}", + self.recipient + )], + fee: Some(fee), + })) + } +} + +#[derive(serde::Serialize)] +pub struct DelegateNamespacePermissionActionResponse; + +impl Display for DelegateNamespacePermissionActionResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Namespace permission delegated successfully") + } +} + +pub struct DelegateWalletPermissionAction { + pub key: Keypair, + pub recipient: AccountId32, + pub can_transfer_stake: bool, + pub exclusive_stake_access: bool, + pub duration: Duration, + pub revocation: RevocationTerms, +} + +impl Action for DelegateWalletPermissionAction { + type Params = (String, String, bool, bool, Duration, RevocationTerms); + type ResponseData = DelegateWalletPermissionActionResponse; + + async fn create( + ctx: &mut impl ActionContext, + (key, recipient, can_transfer_stake, exclusive_stake_access, duration, revocation): Self::Params, + ) -> anyhow::Result { + let key = get_key(&key)?; + let (_, keypair) = ctx.decrypt(&key)?; + + let recipient = get_account(&recipient)?; + + Ok(Self { + key: keypair, + recipient, + can_transfer_stake, + exclusive_stake_access, + duration, + revocation, + }) + } + + async fn execute(&self, ctx: &mut impl ActionContext) -> anyhow::Result { + if ctx.is_mainnet() { + let client = TorusClient::for_mainnet().await?; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_wallet_stake_permission_wait( + self.recipient.clone(), + torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::wallet::WalletStake { + can_transfer_stake: self.can_transfer_stake, + exclusive_stake_access: self.exclusive_stake_access + }, + duration, + revocation, + self.key.clone() + ) + .await?; + } else { + let client = TorusClient::for_testnet().await?; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_wallet_stake_permission_wait( + self.recipient.clone(), + torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::wallet::WalletStake { + can_transfer_stake: self.can_transfer_stake, + exclusive_stake_access: self.exclusive_stake_access + }, + duration, + revocation, + self.key.clone() + ) + .await?; + } + + Ok(DelegateWalletPermissionActionResponse) + } + + async fn estimate_fee(&self, ctx: &mut impl ActionContext) -> anyhow::Result { + let fee = if ctx.is_mainnet() { + let client = TorusClient::for_mainnet().await?; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_wallet_stake_permission_fee( + self.recipient.clone(), + torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::wallet::WalletStake { + can_transfer_stake: self.can_transfer_stake, + exclusive_stake_access: self.exclusive_stake_access + }, + duration, + revocation, + self.key.clone() + ) + .await? + } else { + let client = TorusClient::for_testnet().await?; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_wallet_stake_permission_fee( + self.recipient.clone(), + torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::wallet::WalletStake { + can_transfer_stake: self.can_transfer_stake, + exclusive_stake_access: self.exclusive_stake_access + }, + duration, + revocation, + self.key.clone() + ) + .await? + }; + + Ok(fee) + } + + async fn get_changes(&self, ctx: &mut impl ActionContext) -> anyhow::Result> { + let fee = self.estimate_fee(ctx).await?; + Ok(Some(Changes { + changes: vec![format!( + "Delegate wallet stake permission to {}", + self.recipient + )], + fee: Some(fee), + })) + } +} + +#[derive(serde::Serialize)] +pub struct DelegateWalletPermissionActionResponse; + +impl Display for DelegateWalletPermissionActionResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Wallet stake permission delegated successfully") + } +} + +pub struct DelegateCuratorPermissionAction { + pub key: Keypair, + pub recipient: AccountId32, + pub flags: Vec<(Option, u32)>, + pub cooldown: Option, + pub duration: Duration, + pub instances: u32, + pub revocation: RevocationTerms, +} + +impl Action for DelegateCuratorPermissionAction { + type Params = ( + String, + String, + Vec<(Option, u32)>, + Option, + Duration, + u32, + RevocationTerms, + ); + type ResponseData = DelegateCuratorPermissionActionResponse; + + async fn create( + ctx: &mut impl ActionContext, + (key, recipient, flags, cooldown, duration, instances, revocation): Self::Params, + ) -> anyhow::Result { + let key = get_key(&key)?; + let (_, keypair) = ctx.decrypt(&key)?; + + let recipient = get_account(&recipient)?; + + Ok(Self { + key: keypair, + recipient, + flags, + cooldown, + duration, + instances, + revocation, + }) + } + + async fn execute(&self, ctx: &mut impl ActionContext) -> anyhow::Result { + if ctx.is_mainnet() { + let client = TorusClient::for_mainnet().await?; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_curator_permission_wait( + self.recipient.clone(), + torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(self.flags.clone()), + self.cooldown, + duration, + revocation, + self.instances, + self.key.clone(), + ) + .await?; + } else { + let client = TorusClient::for_testnet().await?; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_curator_permission_wait( + self.recipient.clone(), + torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(self.flags.clone()), + self.cooldown, + duration, + revocation, + self.instances, + self.key.clone(), + ) + .await?; + } + + Ok(DelegateCuratorPermissionActionResponse) + } + + async fn estimate_fee(&self, ctx: &mut impl ActionContext) -> anyhow::Result { + let fee = if ctx.is_mainnet() { + let client = TorusClient::for_mainnet().await?; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::mainnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_curator_permission_fee( + self.recipient.clone(), + torus_client::interfaces::mainnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(self.flags.clone()), + self.cooldown, + duration, + revocation, + self.instances, + self.key.clone(), + ) + .await? + } else { + let client = TorusClient::for_testnet().await?; + + let duration = match &self.duration { + Duration::UntilBlock(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::UntilBlock(*value), + Duration::Indefinite => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::PermissionDuration::Indefinite, + }; + + let revocation = match &self.revocation { + RevocationTerms::Irrevocable => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::Irrevocable, + RevocationTerms::RevocableByDelegator => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByDelegator, + RevocationTerms::RevocableByArbiters { accounts, required_votes } => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableByArbiters { + accounts: torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_vec::BoundedVec(accounts.clone()), + required_votes: *required_votes + }, + RevocationTerms::RevocableAfter(value) => torus_client::interfaces::testnet::api::runtime_types::pallet_permission0::permission::RevocationTerms::RevocableAfter(*value), + }; + + client + .permission0() + .calls() + .delegate_curator_permission_fee( + self.recipient.clone(), + torus_client::interfaces::testnet::api::runtime_types::bounded_collections::bounded_btree_map::BoundedBTreeMap(self.flags.clone()), + self.cooldown, + duration, + revocation, + self.instances, + self.key.clone(), + ) + .await? + }; + + Ok(fee) + } + + async fn get_changes(&self, ctx: &mut impl ActionContext) -> anyhow::Result> { + let fee = self.estimate_fee(ctx).await?; + Ok(Some(Changes { + changes: vec![format!( + "Delegate {} instances of curator permission to {}", + self.instances, self.recipient + )], + fee: Some(fee), + })) + } +} + +#[derive(serde::Serialize)] +pub struct DelegateCuratorPermissionActionResponse; + +impl Display for DelegateCuratorPermissionActionResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Curator permission delegated successfully") + } +} + pub struct RevokePermissionAction { key: Keypair, permission_id: H256, diff --git a/cli/src/cli.rs b/cli/src/cli.rs index e0a2384..d5c65af 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -13,6 +13,8 @@ use crate::{ namespace::{NamespaceInfoAction, RegisterNamespaceAction, UnregisterNamespaceAction}, network::{PrintNetworkInfoAction, PrintNetworkSupplyAction, PrintTreasuryAddressAction}, permission::{ + DelegateCuratorPermissionAction, DelegateNamespacePermissionAction, + DelegateStreamPermissionAction, DelegateWalletPermissionAction, ExecutePermissionAction, RevokePermissionAction, SetPermissionAccumulationAction, SetPermissionEnforcementAuthorityAction, }, @@ -24,8 +26,12 @@ use crate::{ Action, ActionContext, }, keypair::Keypair, - store::decrypt_key, - util::format_torus, + store::{decrypt_key, get_account}, + util::{ + format_torus, parse_allocation, parse_distribution, parse_duration, + parse_enforcement_authority, parse_flags, parse_paths, parse_recipients, + parse_revocation_terms, + }, }; pub(super) async fn run() -> anyhow::Result<()> { @@ -167,6 +173,100 @@ pub(super) async fn run() -> anyhow::Result<()> { }, CliSubCommand::Permission(permission_cli_command) => { match permission_cli_command.sub_command { + PermissionCliSubCommand::Delegate { sub_command } => match sub_command { + PermissionDelegateCliSubCommand::Stream { + key, + recipients, + allocation, + distribution_control, + duration, + revocation_terms, + enforcement_authority, + recipient_manager, + weight_setter, + } => { + execute::( + &mut ctx, + ( + key, + parse_recipients(&recipients)?, + parse_allocation(&allocation)?, + parse_distribution(&distribution_control)?, + parse_duration(&duration)?, + parse_revocation_terms(&revocation_terms)?, + parse_enforcement_authority(&enforcement_authority)?, + recipient_manager.map(|acc| get_account(&acc)).transpose()?, + weight_setter.map(|acc| get_account(&acc)).transpose()?, + ), + ) + .await? + } + PermissionDelegateCliSubCommand::Namespace { + key, + recipient, + paths, + duration, + revocation_terms, + instances, + } => { + execute::( + &mut ctx, + ( + key, + recipient, + parse_paths(&paths)?, + parse_duration(&duration)?, + parse_revocation_terms(&revocation_terms)?, + instances, + ), + ) + .await? + } + PermissionDelegateCliSubCommand::Wallet { + key, + recipient, + can_transfer_stake, + exclusive_stake_access, + duration, + revocation_terms: revocation, + } => { + execute::( + &mut ctx, + ( + key, + recipient, + can_transfer_stake, + exclusive_stake_access, + parse_duration(&duration)?, + parse_revocation_terms(&revocation)?, + ), + ) + .await? + } + PermissionDelegateCliSubCommand::Curator { + key, + recipient, + flags, + cooldown, + instances, + duration, + revocation_terms: revocation, + } => { + execute::( + &mut ctx, + ( + key, + recipient, + parse_flags(&flags)?, + cooldown, + parse_duration(&duration)?, + instances, + parse_revocation_terms(&revocation)?, + ), + ) + .await? + } + }, PermissionCliSubCommand::Revoke { key, permission_id } => { execute::(&mut ctx, (key, permission_id)).await? } @@ -724,8 +824,13 @@ pub struct PermissionCliCommand { pub sub_command: PermissionCliSubCommand, } +#[allow(clippy::large_enum_variant)] #[derive(clap::Subcommand, Clone)] pub enum PermissionCliSubCommand { + Delegate { + #[command(subcommand)] + sub_command: PermissionDelegateCliSubCommand, + }, /// Revokes a permission. Revoke { key: String, permission_id: String }, /// Executes a permission. @@ -748,6 +853,91 @@ pub enum PermissionCliSubCommand { }, } +#[derive(clap::Args, Clone)] +#[command(group = ArgGroup::default().id("distribution-control-params").args(&["manual_distribution", "automatic_distribution", "at_block_distribution", "interval_distribution"]))] +pub struct DistributionControlParams { + #[arg(long)] + pub manual_distribution: Option, + #[arg(long)] + pub automatic_distribution: Option, + #[arg(long)] + pub at_block_distribution: Option, + #[arg(long)] + pub interval_distribution: Option, +} + +#[derive(clap::Args, Clone)] +#[command(group = ArgGroup::default().id("revocation-terms-params").args(&["irrevocable", "revocable_by_delegator", "revocable_by_arbiters", "revocable_after"]))] +pub struct RevocationTermsParams { + #[arg(long)] + pub irrevocable: Option, + #[arg(long)] + pub revocable_by_delegator: Option, + #[arg(long)] + pub revocable_by_arbiters: Option, + #[arg(long)] + pub revocable_after: Option, +} + +#[derive(clap::Subcommand, Clone)] +pub enum PermissionDelegateCliSubCommand { + Stream { + key: String, + recipients: String, + allocation: String, + #[command(flatten)] + distribution_control: DistributionControlParams, + #[arg(long)] + duration: Option, + #[command(flatten)] + revocation_terms: RevocationTermsParams, + #[arg(long)] + enforcement_authority: Option, + #[arg(long)] + recipient_manager: Option, + #[arg(long)] + weight_setter: Option, + }, + Namespace { + key: String, + recipient: String, + #[arg(long)] + paths: Vec, + #[arg(long)] + duration: Option, + #[command(flatten)] + revocation_terms: RevocationTermsParams, + #[arg(long, default_value_t = 1)] + instances: u32, + }, + Wallet { + key: String, + recipient: String, + #[arg(long)] + can_transfer_stake: bool, + #[arg(long)] + exclusive_stake_access: bool, + #[arg(long)] + duration: Option, + #[command(flatten)] + revocation_terms: RevocationTermsParams, + }, + Curator { + key: String, + recipient: String, + #[arg(long)] + flags: Option>, + #[arg(long)] + cooldown: Option, + #[arg(long, default_value_t = 1)] + instances: u32, + #[arg(long)] + duration: Option, + #[command(flatten)] + revocation_terms: RevocationTermsParams, + }, +} + #[derive(clap::Subcommand, Clone)] pub enum PermissionEnforcementAuthorityCliSubCommand { /// Removes the current enforcement authority from the given permission. diff --git a/cli/src/util.rs b/cli/src/util.rs index 6b6a3e0..416ca86 100644 --- a/cli/src/util.rs +++ b/cli/src/util.rs @@ -1,3 +1,17 @@ +use std::str::FromStr; + +use anyhow::bail; +use sp_core::H256; +use torus_client::subxt::utils::AccountId32; + +use crate::{ + action::permission::{ + Allocation, DistributionControl, Duration, EnforcementAuthority, RevocationTerms, + }, + cli::{DistributionControlParams, RevocationTermsParams}, + store::get_account, +}; + pub fn format_torus(amount: u128) -> String { format!("{:.5}", amount as f64 / 10f64.powf(18.0)) } @@ -9,3 +23,190 @@ pub fn to_percent_u8(amount: u32) -> anyhow::Result { Ok(amount.try_into()?) } + +pub fn parse_recipients(string: &str) -> anyhow::Result> { + let mut vec = Vec::new(); + let split = string.split(","); + + for ele in split { + let split = ele.split(":").collect::>(); + if split.len() != 2 { + bail!("Invalid recipients format, use `id1:weight1,id2:weight2`"); + } + + let recipient = get_account(split[0])?; + let weight: u16 = split[1].parse()?; + vec.push((recipient, weight)); + } + + Ok(vec) +} + +pub fn parse_allocation(string: &str) -> anyhow::Result { + if !string.contains(",") && !string.contains(":") { + return Ok(Allocation::FixedAmount(string.parse()?)); + } + + let mut vec = Vec::new(); + + for ele in string.split(",") { + let split = ele.split(":").collect::>(); + if split.len() != 2 { + bail!("Invalid allocation format, use `stream1:percentage1,stream2:percentage2`"); + } + + let stream = H256::from_str(split[0])?; + let weight: u8 = split[1].parse()?; + + vec.push((stream, weight)); + } + + Ok(Allocation::Streams(vec)) +} + +pub fn parse_distribution( + params: &DistributionControlParams, +) -> anyhow::Result { + if let Some(value) = params.automatic_distribution { + return Ok(DistributionControl::Automatic(value)); + } + + if let Some(value) = params.at_block_distribution { + return Ok(DistributionControl::AtBlock(value)); + } + + if let Some(value) = params.interval_distribution { + return Ok(DistributionControl::Interval(value)); + } + + Ok(DistributionControl::Manual) +} + +pub fn parse_duration(duration: &Option) -> anyhow::Result { + match duration { + Some(value) => Ok(Duration::UntilBlock(*value)), + None => Ok(Duration::Indefinite), + } +} + +pub fn parse_revocation_terms(params: &RevocationTermsParams) -> anyhow::Result { + if params.revocable_by_delegator.is_some_and(|b| b) { + return Ok(RevocationTerms::RevocableByDelegator); + } + + if let Some(value) = params.revocable_after { + return Ok(RevocationTerms::RevocableAfter(value)); + } + + if let Some(string) = ¶ms.revocable_by_arbiters { + if !string.contains(",") { + bail!("Invalid revocation terms arbiter format, use `arbiter1,arbiter2...,votes`"); + } + + let split = string.split(",").collect::>(); + let mut accounts = Vec::new(); + let mut required_votes = 0; + for (idx, ele) in split.iter().enumerate() { + if idx + 1 == split.len() { + required_votes = ele.parse()?; + } else { + accounts.push(get_account(ele)?); + } + } + + return Ok(RevocationTerms::RevocableByArbiters { + accounts, + required_votes, + }); + } + + Ok(RevocationTerms::Irrevocable) +} + +pub fn parse_enforcement_authority( + params: &Option, +) -> anyhow::Result { + if let Some(string) = ¶ms { + if !string.contains(",") { + bail!("Invalid enforcement authority format, use `controller1,controller2...,votes`"); + } + + let split = string.split(",").collect::>(); + let mut accounts = Vec::new(); + let mut required_votes = 0; + for (idx, ele) in split.iter().enumerate() { + if idx + 1 == split.len() { + required_votes = ele.parse()?; + } else { + accounts.push(get_account(ele)?); + } + } + + return Ok(EnforcementAuthority::ControlledBy { + controllers: accounts, + required_votes, + }); + } + + Ok(EnforcementAuthority::None) +} + +pub fn parse_paths(paths: &Vec) -> anyhow::Result, Vec)>> { + let mut vec = Vec::new(); + for paths in paths { + let mut paths = paths.clone(); + + let permission_id = if paths.contains(":") { + let split = paths + .split(":") + .map(|path| path.to_string()) + .collect::>(); + if split.len() > 2 || split.is_empty() { + bail!("Invalid paths format, use `(permissionid:)path1,path2...`"); + } + paths = split[1].clone(); + Some(H256::from_str(&split[0])?) + } else { + None + }; + + let paths = paths + .split(",") + .map(|path| path.to_string()) + .collect::>(); + vec.push((permission_id, paths)); + } + + Ok(vec) +} + +pub fn parse_flags(paths: &Option>) -> anyhow::Result, u32)>> { + match paths { + None => Ok(vec![]), + Some(strings) => { + let mut vec = Vec::new(); + for ele in strings { + let mut ele = ele.clone(); + + let id = if ele.contains(":") { + let split = ele + .split(":") + .map(|path| path.to_string()) + .collect::>(); + if split.len() > 2 || split.is_empty() { + bail!("Invalid paths format, use `(permissionid:)path1,path2...`"); + } + ele = split[1].clone(); + Some(H256::from_str(&split[0])?) + } else { + None + }; + + let val = ele.parse()?; + vec.push((id, val)); + } + + Ok(vec) + } + } +}