diff --git a/Cargo.lock b/Cargo.lock index 5e2bcf8..a5c3d56 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -9252,6 +9252,7 @@ dependencies = [ "parity-scale-codec", "polkadot-sdk", "scale-info", + "substrate-fixed", "test-utils", ] @@ -16292,6 +16293,17 @@ name = "substrate-build-script-utils" version = "11.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?branch=stable2409#981d6c0fa87a00b72bb3b6211d1e71deed21f0cc" +[[package]] +name = "substrate-fixed" +version = "0.5.9" +source = "git+https://github.com/encointer/substrate-fixed#ddaa922892d1565f02c9c5702f0aacd17da53ce2" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "substrate-typenum", +] + [[package]] name = "substrate-frame-rpc-support" version = "40.0.0" @@ -16369,6 +16381,16 @@ dependencies = [ "trie-db", ] +[[package]] +name = "substrate-typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f0091e93c2c75b233ae39424c52cb8a662c0811fb68add149e20e5d7e8a788" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + [[package]] name = "substrate-wasm-builder" version = "24.0.1" diff --git a/Cargo.toml b/Cargo.toml index 0d2c227..89c368f 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,8 @@ pallet-evm-precompile-modexp = { git = "https://github.com/paritytech/frontier.g pallet-evm-precompile-sha3fips = { git = "https://github.com/paritytech/frontier.git", rev = "b9b1c620c8b418bdeeadc79725f9cfa4703c0333", default-features = false } pallet-evm-precompile-simple = { git = "https://github.com/paritytech/frontier.git", rev = "b9b1c620c8b418bdeeadc79725f9cfa4703c0333", default-features = false } +substrate-fixed = { git = "https://github.com/encointer/substrate-fixed", default-features = false } + # CLI-specific dependencies clap = { version = "4.5.22", features = ["derive"] } serde_json = { version = "1.0", default-features = false } diff --git a/pallets/torus0/Cargo.toml b/pallets/torus0/Cargo.toml index 6e0ab91..95c8f2b 100644 --- a/pallets/torus0/Cargo.toml +++ b/pallets/torus0/Cargo.toml @@ -8,13 +8,14 @@ edition.workspace = true [features] default = ["std"] -std = ["codec/std", "polkadot-sdk/std", "scale-info/std"] +std = ["codec/std", "polkadot-sdk/std", "scale-info/std", "substrate-fixed/std"] runtime-benchmarks = ["polkadot-sdk/runtime-benchmarks"] try-runtime = ["polkadot-sdk/try-runtime"] [dependencies] codec = { workspace = true, features = ["derive"] } scale-info = { workspace = true, features = ["derive"] } +substrate-fixed = { workspace = true } polkadot-sdk = { workspace = true, features = ["experimental", "runtime"] } [dev-dependencies] diff --git a/pallets/torus0/src/agent.rs b/pallets/torus0/src/agent.rs index 3bc54f5..1c4f92c 100644 --- a/pallets/torus0/src/agent.rs +++ b/pallets/torus0/src/agent.rs @@ -1,8 +1,9 @@ use crate::AccountIdOf; use codec::{Decode, Encode, MaxEncodedLen}; +use polkadot_sdk::frame_election_provider_support::Get; +use polkadot_sdk::sp_runtime::DispatchError; use polkadot_sdk::{ - frame_support::{dispatch::DispatchResult, CloneNoBound}, - polkadot_sdk_frame::prelude::OriginFor, + frame_support::{dispatch::DispatchResult, ensure, CloneNoBound}, sp_runtime::{BoundedVec, Percent}, }; use scale_info::prelude::vec::Vec; @@ -13,31 +14,199 @@ use scale_info::TypeInfo; pub struct Agent { pub key: AccountIdOf, pub name: BoundedVec, - pub address: BoundedVec, + pub url: BoundedVec, + pub metadata: BoundedVec, pub weight_factor: Percent, } pub fn register( - _origin: OriginFor, - _name: Vec, - _address: Vec, - _agent_key: AccountIdOf, - _metadata: Option>, + agent_key: AccountIdOf, + name: Vec, + url: Vec, + metadata: Vec, ) -> DispatchResult { - todo!() + ensure!( + !exists::(&agent_key), + crate::Error::::AgentAlreadyRegistered + ); + + ensure!( + crate::RegistrationsThisBlock::::get() < crate::MaxRegistrationsPerBlock::::get(), + crate::Error::::TooManyAgentRegistrationsThisBlock + ); + + let burn_config = crate::BurnConfig::::get(); + ensure!( + crate::RegistrationsThisInterval::::get() < burn_config.max_registrations_per_interval, + crate::Error::::TooManyAgentRegistrationsThisBlock + ); + + validate_agent_name::(&name[..])?; + validate_agent_url::(&url[..])?; + validate_agent_metadata::(&metadata[..])?; + + // TODO: check if agent is on whitelist + + crate::Agents::::insert( + agent_key.clone(), + Agent { + key: agent_key.clone(), + name: BoundedVec::truncate_from(name), + url: BoundedVec::truncate_from(url), + metadata: BoundedVec::truncate_from(metadata), + weight_factor: Percent::from_percent(100), + }, + ); + + crate::Pallet::::deposit_event(crate::Event::::AgentRegistered(agent_key)); + + Ok(()) } -pub fn deregister(_origin: OriginFor) -> DispatchResult { - todo!() +pub fn unregister(agent_key: AccountIdOf) -> DispatchResult { + ensure!( + exists::(&agent_key), + crate::Error::::AgentDoesNotExist + ); + + crate::Agents::::remove(&agent_key); + + crate::Pallet::::deposit_event(crate::Event::::AgentUnregistered(agent_key)); + + Ok(()) } pub fn update( - _origin: OriginFor, - _name: Vec, - _address: Vec, - _metadata: Option>, - _staking_fee: Option, - _weight_control_fee: Option, + agent_key: AccountIdOf, + name: Vec, + url: Vec, + metadata: Option>, + staking_fee: Option, + weight_control_fee: Option, ) -> DispatchResult { - todo!() + ensure!( + exists::(&agent_key), + crate::Error::::AgentDoesNotExist + ); + + crate::Agents::::try_mutate(&agent_key, |agent| { + let Some(agent) = agent else { + return Err(crate::Error::::AgentDoesNotExist.into()); + }; + + validate_agent_name::(&name[..])?; + agent.name = BoundedVec::truncate_from(name); + + validate_agent_url::(&url[..])?; + agent.url = BoundedVec::truncate_from(url); + + if let Some(metadata) = metadata { + validate_agent_metadata::(&metadata[..])?; + agent.metadata = BoundedVec::truncate_from(metadata); + } + + Ok::<(), DispatchError>(()) + })?; + + if staking_fee.is_none() && weight_control_fee.is_none() { + return Ok(()); + } + + crate::Fee::::try_mutate(&agent_key, |fee| { + let constraints = crate::FeeConstraints::::get(); + + if let Some(staking_fee) = staking_fee { + ensure!( + staking_fee >= constraints.min_staking_fee, + crate::Error::::InvalidStakingFee + ); + + fee.staking_fee = staking_fee; + } + + if let Some(weight_control_fee) = weight_control_fee { + ensure!( + weight_control_fee >= constraints.min_weight_control_fee, + crate::Error::::InvalidWeightControlFee + ); + + fee.weight_control_fee = weight_control_fee; + } + + Ok::<(), DispatchError>(()) + })?; + + Ok(()) +} + +pub fn exists(key: &AccountIdOf) -> bool { + crate::Agents::::contains_key(key) +} + +fn validate_agent_name(bytes: &[u8]) -> DispatchResult { + let len: u32 = bytes + .len() + .try_into() + .map_err(|_| crate::Error::::AgentNameTooLong)?; + + ensure!( + len >= crate::MinNameLength::::get() as u32, + crate::Error::::AgentNameTooShort + ); + + ensure!( + len <= (crate::MaxNameLength::::get() as u32) + .min(T::MaxAgentNameLengthConstraint::get()), + crate::Error::::AgentNameTooShort + ); + + ensure!( + core::str::from_utf8(bytes).is_ok(), + crate::Error::::InvalidAgentName + ); + + Ok(()) +} + +fn validate_agent_url(bytes: &[u8]) -> DispatchResult { + let len: u32 = bytes + .len() + .try_into() + .map_err(|_| crate::Error::::AgentUrlTooLong)?; + + ensure!(len > 0, crate::Error::::AgentNameTooShort); + + ensure!( + len <= (crate::MaxNameLength::::get() as u32) + .min(T::MaxAgentNameLengthConstraint::get()), + crate::Error::::AgentUrlTooShort + ); + + ensure!( + core::str::from_utf8(bytes).is_ok(), + crate::Error::::InvalidAgentUrl + ); + + Ok(()) +} + +fn validate_agent_metadata(bytes: &[u8]) -> DispatchResult { + let len: u32 = bytes + .len() + .try_into() + .map_err(|_| crate::Error::::AgentMetadataTooLong)?; + + ensure!(len > 0, crate::Error::::AgentMetadataTooShort); + + ensure!( + len <= T::MaxAgentMetadataLengthConstraint::get(), + crate::Error::::AgentMetadataTooLong + ); + + ensure!( + core::str::from_utf8(bytes).is_ok(), + crate::Error::::InvalidAgentMetadata + ); + + Ok(()) } diff --git a/pallets/torus0/src/balance.rs b/pallets/torus0/src/balance.rs index 9bd0693..0065384 100644 --- a/pallets/torus0/src/balance.rs +++ b/pallets/torus0/src/balance.rs @@ -1,13 +1,21 @@ -use polkadot_sdk::{ - frame_support::dispatch::DispatchResult, polkadot_sdk_frame::prelude::OriginFor, -}; - use crate::{AccountIdOf, BalanceOf}; +use polkadot_sdk::frame_support::traits::{Currency, ExistenceRequirement}; +use polkadot_sdk::frame_support::{dispatch::DispatchResult, ensure}; -pub fn transfer_balance_multiple( - _origin: OriginFor, - _destination: AccountIdOf, - _amount: BalanceOf, +pub fn transfer_balance( + key: AccountIdOf, + destination: AccountIdOf, + amount: BalanceOf, ) -> DispatchResult { - todo!() + ensure!(amount > 0, crate::Error::::InvalidAmount); + + ::Currency::transfer( + &key, + &destination, + amount, + ExistenceRequirement::KeepAlive, + ) + .map_err(|_| crate::Error::::NotEnoughBalanceToTransfer)?; + + Ok(()) } diff --git a/pallets/torus0/src/burn.rs b/pallets/torus0/src/burn.rs new file mode 100644 index 0000000..7272fae --- /dev/null +++ b/pallets/torus0/src/burn.rs @@ -0,0 +1,94 @@ +use crate::{BalanceOf, BlockAmount}; +use codec::{Decode, Encode, MaxEncodedLen}; +use polkadot_sdk::frame_election_provider_support::Get; +use polkadot_sdk::frame_support::DebugNoBound; +use scale_info::prelude::marker::PhantomData; +use scale_info::TypeInfo; +use substrate_fixed::types::I110F18; + +#[derive(Clone, TypeInfo, Decode, Encode, PartialEq, Eq, DebugNoBound, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +pub struct BurnConfiguration { + pub min_burn: BalanceOf, + pub max_burn: BalanceOf, + pub adjustment_alpha: u64, + pub target_registrations_interval: BlockAmount, + pub target_registrations_per_interval: u16, + pub max_registrations_per_interval: u16, + pub _pd: PhantomData, +} + +impl Default for BurnConfiguration +where + T: crate::Config, +{ + fn default() -> Self { + Self { + min_burn: T::DefaultMinBurn::get(), + max_burn: T::DefaultMaxBurn::get(), + adjustment_alpha: T::DefaultAdjustmentAlpha::get(), + target_registrations_interval: T::DefaultTargetRegistrationsInterval::get(), + target_registrations_per_interval: T::DefaultTargetRegistrationsPerInterval::get(), + max_registrations_per_interval: T::DefaultMaxRegistrationsPerInterval::get(), + _pd: Default::default(), + } + } +} + +#[allow(dead_code)] +pub fn adjust_burn(current_block: u64) { + let BurnConfiguration { + min_burn, + max_burn, + adjustment_alpha, + target_registrations_interval, + target_registrations_per_interval, + .. + } = crate::BurnConfig::::get(); + + let current_burn = crate::Burn::::get(); + let registrations_this_interval = crate::RegistrationsThisInterval::::get(); + + let reached_interval = current_block + .checked_rem(target_registrations_interval) + .is_some_and(|r| r == 0); + + if !reached_interval { + return; + } + + let updated_burn: I110F18 = I110F18::from_num(current_burn) + .checked_mul(I110F18::from_num( + registrations_this_interval.saturating_add(target_registrations_per_interval), + )) + .unwrap_or_default() + .checked_div(I110F18::from_num( + target_registrations_per_interval.saturating_add(target_registrations_per_interval), + )) + .unwrap_or_default(); + + let alpha: I110F18 = I110F18::from_num(adjustment_alpha) + .checked_div(I110F18::from_num(u64::MAX)) + .unwrap_or_else(|| I110F18::from_num(0)); + + let next_value: I110F18 = alpha + .checked_mul(I110F18::from_num(current_burn)) + .unwrap_or_else(|| I110F18::from_num(0)) + .saturating_add( + I110F18::from_num(1.0) + .saturating_sub(alpha) + .checked_mul(updated_burn) + .unwrap_or_else(|| I110F18::from_num(0)), + ); + + let new_burn = if next_value >= I110F18::from_num(max_burn) { + max_burn + } else if next_value <= I110F18::from_num(min_burn) { + min_burn + } else { + next_value.to_num::>() + }; + + crate::Burn::::set(new_burn); + crate::RegistrationsThisInterval::::set(0); +} diff --git a/pallets/torus0/src/fee.rs b/pallets/torus0/src/fee.rs index 634633c..7faa800 100644 --- a/pallets/torus0/src/fee.rs +++ b/pallets/torus0/src/fee.rs @@ -8,7 +8,7 @@ use scale_info::TypeInfo; #[derive(DebugNoBound, Decode, Encode, MaxEncodedLen, PartialEq, Eq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct ValidatorFeeConstraints { - pub min_stake_delegation_fee: Percent, + pub min_staking_fee: Percent, pub min_weight_control_fee: Percent, pub _pd: PhantomData, } @@ -16,7 +16,7 @@ pub struct ValidatorFeeConstraints { impl Default for ValidatorFeeConstraints { fn default() -> Self { Self { - min_stake_delegation_fee: Percent::from_percent(T::DefaultMinStakingFee::get()), + min_staking_fee: Percent::from_percent(T::DefaultMinStakingFee::get()), min_weight_control_fee: Percent::from_percent(T::DefaultMinWeightControlFee::get()), _pd: PhantomData, } @@ -26,7 +26,7 @@ impl Default for ValidatorFeeConstraints { #[derive(DebugNoBound, Decode, Encode, MaxEncodedLen, PartialEq, Eq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct ValidatorFee { - pub stake_delegation_fee: Percent, + pub staking_fee: Percent, pub weight_control_fee: Percent, pub _pd: PhantomData, } @@ -34,7 +34,7 @@ pub struct ValidatorFee { impl Default for ValidatorFee { fn default() -> Self { Self { - stake_delegation_fee: Percent::from_percent(T::DefaultMinStakingFee::get()), + staking_fee: Percent::from_percent(T::DefaultMinStakingFee::get()), weight_control_fee: Percent::from_percent(T::DefaultMinWeightControlFee::get()), _pd: PhantomData, } diff --git a/pallets/torus0/src/lib.rs b/pallets/torus0/src/lib.rs index 1e491c2..04a0df4 100644 --- a/pallets/torus0/src/lib.rs +++ b/pallets/torus0/src/lib.rs @@ -2,15 +2,18 @@ mod agent; mod balance; +mod burn; mod ext; mod fee; mod stake; use crate::agent::Agent; +use crate::burn::BurnConfiguration; use crate::fee::ValidatorFee; use crate::fee::ValidatorFeeConstraints; pub(crate) use ext::*; use frame::arithmetic::Percent; +use frame::prelude::ensure_signed; pub use pallet::*; use polkadot_sdk::frame_support::{ dispatch::DispatchResult, @@ -66,7 +69,7 @@ pub mod pallet { StorageValue<_, u16, ValueQuery, T::DefaultMaxAllowedWeights>; #[pallet::storage] - pub type Tempo = StorageValue<_, u16, ValueQuery, T::DefaultTempo>; + pub type RewardInterval = StorageValue<_, u16, ValueQuery, T::DefaultRewardInterval>; #[pallet::storage] pub type Agents = StorageMap<_, Identity, AccountIdOf, Agent>; @@ -91,7 +94,7 @@ pub mod pallet { pub type MinWeightStake = StorageValue<_, BalanceOf, ValueQuery>; #[pallet::storage] - pub type RegistrationsPerBlock = StorageValue<_, u16, ValueQuery>; + pub type RegistrationsThisBlock = StorageValue<_, u16, ValueQuery>; #[pallet::storage] pub type MaxRegistrationsPerBlock = @@ -118,12 +121,35 @@ pub mod pallet { #[pallet::storage] pub type Fee = StorageMap<_, Identity, T::AccountId, ValidatorFee, ValueQuery>; - #[pallet::config] + #[pallet::storage] + pub type BurnConfig = StorageValue<_, BurnConfiguration, ValueQuery>; + + #[pallet_section] + pub mod hooks { + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(block_number: BlockNumberFor) -> Weight { + let current_block: u64 = block_number + .try_into() + .ok() + .expect("blockchain won't pass 2 ^ 64 blocks"); + + burn::adjust_burn::(current_block); + + RegistrationsThisBlock::::set(0); + + Weight::default() + } + } + } + + #[pallet::config(with_default)] pub trait Config: polkadot_sdk::frame_system::Config { #[pallet::constant] type DefaultMaxAllowedValidators: Get; #[pallet::constant] + #[pallet::no_default_bounds] type DefaultMinValidatorStake: Get>; #[pallet::constant] @@ -139,7 +165,7 @@ pub mod pallet { type DefaultMaxAllowedWeights: Get; #[pallet::constant] - type DefaultTempo: Get; + type DefaultRewardInterval: Get; #[pallet::constant] type DefaultMinNameLength: Get; @@ -154,6 +180,7 @@ pub mod pallet { type DefaultMaxRegistrationsPerBlock: Get; #[pallet::constant] + #[pallet::no_default_bounds] type DefaultMinimumAllowedStake: Get>; #[pallet::constant] @@ -162,6 +189,28 @@ pub mod pallet { #[pallet::constant] type DefaultMinWeightControlFee: Get; + #[pallet::constant] + #[pallet::no_default_bounds] + type DefaultMinBurn: Get>; + + #[pallet::constant] + #[pallet::no_default_bounds] + type DefaultMaxBurn: Get>; + + #[pallet::constant] + type DefaultAdjustmentAlpha: Get; + + #[pallet::constant] + type DefaultTargetRegistrationsInterval: Get; + + #[pallet::constant] + #[pallet::no_default_bounds] + type DefaultTargetRegistrationsPerInterval: Get; + + #[pallet::constant] + #[pallet::no_default_bounds] + type DefaultMaxRegistrationsPerInterval: Get; + /// The storage MaxNameLength should be constrained to be no more than the value of this. /// This is needed on agent::Agent to set the `name` field BoundedVec max length. #[pallet::constant] @@ -169,7 +218,14 @@ pub mod pallet { /// This is needed on agent::Agent to set the `address` field BoundedVec max length. #[pallet::constant] - type MaxAgentAddressLengthConstraint: Get; + type MaxAgentUrlLengthConstraint: Get; + + #[pallet::constant] + type MaxAgentMetadataLengthConstraint: Get; + + #[pallet::no_default_bounds] + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; type Currency: Currency + Send + Sync; } @@ -186,7 +242,8 @@ pub mod pallet { agent_key: AccountIdOf, amount: BalanceOf, ) -> DispatchResult { - stake::add_stake::(origin, agent_key, amount) + let key = ensure_signed(origin)?; + stake::add_stake::(key, agent_key, amount) } #[pallet::call_index(1)] @@ -196,7 +253,8 @@ pub mod pallet { agent_key: AccountIdOf, amount: BalanceOf, ) -> DispatchResult { - stake::remove_stake::(origin, agent_key, amount) + let key = ensure_signed(origin)?; + stake::remove_stake::(key, agent_key, amount) } #[pallet::call_index(2)] @@ -207,7 +265,8 @@ pub mod pallet { new_agent_key: AccountIdOf, amount: BalanceOf, ) -> DispatchResult { - stake::transfer_stake::(origin, agent_key, new_agent_key, amount) + let key = ensure_signed(origin)?; + stake::transfer_stake::(key, agent_key, new_agent_key, amount) } #[pallet::call_index(3)] @@ -217,25 +276,28 @@ pub mod pallet { destination: AccountIdOf, amount: BalanceOf, ) -> DispatchResult { - balance::transfer_balance_multiple::(origin, destination, amount) + let key = ensure_signed(origin)?; + balance::transfer_balance::(key, destination, amount) } #[pallet::call_index(4)] #[pallet::weight(0)] pub fn register_agent( origin: OriginFor, - name: Vec, - address: Vec, agent_key: T::AccountId, - metadata: Option>, + name: Vec, + url: Vec, + metadata: Vec, ) -> DispatchResult { - agent::register::(origin, name, address, agent_key, metadata) + ensure_signed(origin)?; + agent::register::(agent_key, name, url, metadata) } #[pallet::call_index(5)] #[pallet::weight(0)] - pub fn deregister_agent(origin: OriginFor) -> DispatchResult { - agent::deregister::(origin) + pub fn unregister_agent(origin: OriginFor) -> DispatchResult { + let agent_key = ensure_signed(origin)?; + agent::unregister::(agent_key) } #[pallet::call_index(6)] @@ -248,8 +310,9 @@ pub mod pallet { staking_fee: Option, weight_control_fee: Option, ) -> DispatchResult { + let agent_key = ensure_signed(origin)?; agent::update::( - origin, + agent_key, name, address, metadata, @@ -259,18 +322,35 @@ pub mod pallet { } } + #[pallet::event] + #[pallet::generate_deposit(pub fn deposit_event)] + pub enum Event { + /// Event created when stake has been transferred from the coldkey account onto the key + /// staking account + StakeAdded(AccountIdOf, AccountIdOf, BalanceOf), + /// Event created when stake has been removed from the key staking account onto the coldkey + /// account + StakeRemoved(AccountIdOf, AccountIdOf, BalanceOf), + /// Event created when a new agent account has been registered to the chain + AgentRegistered(AccountIdOf), + /// Event created when a agent account has been deregistered from the chain + AgentUnregistered(AccountIdOf), + /// Event created when the agent's updated information is added to the network + AgentUpdated(AccountIdOf), + } + #[pallet::error] pub enum Error { - /// The specified module does not exist. + /// The specified agent does not exist. AgentDoesNotExist, /// Insufficient stake to withdraw the requested amount. NotEnoughStakeToWithdraw, /// Insufficient balance in the cold key account to stake the requested amount. NotEnoughBalanceToStake, /// The number of agent registrations in this block exceeds the allowed limit. - TooManyAgentRegistrationsPerBlock, + TooManyAgentRegistrationsThisBlock, /// The number of agent registrations in this interval exceeds the allowed limit. - TooManyAgentRegistrationsPerInterval, + TooManyAgentRegistrationsThisInterval, /// The agent is already registered in the active set. AgentAlreadyRegistered, /// Failed to convert between u128 and T::Balance. @@ -279,8 +359,6 @@ pub mod pallet { BalanceNotAdded, /// Failed to remove stake from the account. StakeNotRemoved, - /// The key is already registered. - KeyAlreadyRegistered, /// Invalid shares distribution. InvalidShares, /// Insufficient balance to register. @@ -295,30 +373,34 @@ pub mod pallet { NotEnoughStakeToRegister, /// The entity is still registered and cannot be modified. StillRegistered, - /// Attempted to set max allowed modules to a value less than the current number of - /// registered modules. - MaxAllowedModules, + /// Attempted to set max allowed agents to a value less than the current number of + /// registered agents. + MaxAllowedAgents, /// Insufficient balance to transfer. NotEnoughBalanceToTransfer, - /// The module metadata is invalid. + /// The agent metadata is invalid. InvalidAgentMetadata, - /// The module metadata is too long. + /// The agent metadata is too long. AgentMetadataTooLong, + /// The agent metadata is too long. + AgentMetadataTooShort, /// The minimum burn value is invalid, likely too small. InvalidMinBurn, /// The maximum burn value is invalid. InvalidMaxBurn, - /// The module name is too long. + /// The agent name is too long. AgentNameTooLong, - /// The module name is too short. + /// The agent name is too short. AgentNameTooShort, - /// The module name is invalid. It must be a UTF-8 encoded string. + /// The agent name is invalid. It must be a UTF-8 encoded string. InvalidAgentName, - /// The module address is too long. - AgentAddressTooLong, - /// The module address is invalid. - InvalidAgentAddress, - /// A module with this name already exists in the subnet. + /// The agent url is too long. + AgentUrlTooLong, + /// The agent url is too short. + AgentUrlTooShort, + /// The agent ur; is invalid. + InvalidAgentUrl, + /// A agent with this name already exists in the subnet. AgentNameAlreadyExists, /// An arithmetic error occurred during calculation. ArithmeticError, @@ -330,5 +412,11 @@ pub mod pallet { StakeTooSmall, /// Key is not present in Whitelist, it needs to be whitelisted by a Curator AgentKeyNotWhitelisted, + /// The amount given is 0 + InvalidAmount, + /// The staking fee given is lower than the minimum fee + InvalidStakingFee, + /// The weight control fee given is lower than the minimum fee + InvalidWeightControlFee, } } diff --git a/pallets/torus0/src/stake.rs b/pallets/torus0/src/stake.rs index fb38064..1dc9c87 100644 --- a/pallets/torus0/src/stake.rs +++ b/pallets/torus0/src/stake.rs @@ -1,30 +1,88 @@ -use polkadot_sdk::{ - frame_support::dispatch::DispatchResult, polkadot_sdk_frame::prelude::OriginFor, -}; - +use crate::agent; use crate::{AccountIdOf, BalanceOf}; +use polkadot_sdk::frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons}; +use polkadot_sdk::frame_support::{dispatch::DispatchResult, ensure}; pub fn add_stake( - _origin: OriginFor, - _agent_key: AccountIdOf, - _amount: BalanceOf, + key: AccountIdOf, + agent_key: AccountIdOf, + amount: BalanceOf, ) -> DispatchResult { - todo!() + ensure!( + amount >= crate::MinimumAllowedStake::::get(), + crate::Error::::StakeTooSmall + ); + + ensure!( + agent::exists::(&agent_key), + crate::Error::::AgentDoesNotExist + ); + + let _ = ::Currency::withdraw( + &key, + amount, + WithdrawReasons::TRANSFER, + ExistenceRequirement::KeepAlive, + ) + .map_err(|_| crate::Error::::NotEnoughBalanceToStake)?; + + crate::StakingTo::::mutate(&key, &agent_key, |stake| { + *stake = Some(stake.unwrap_or(0).saturating_add(amount)) + }); + + crate::StakedBy::::mutate(&agent_key, &key, |stake| { + *stake = Some(stake.unwrap_or(0).saturating_add(amount)) + }); + + crate::TotalStake::::mutate(|total_stake| *total_stake = total_stake.saturating_add(amount)); + + crate::Pallet::::deposit_event(crate::Event::::StakeAdded(key, agent_key, amount)); + + Ok(()) } pub fn remove_stake( - _origin: OriginFor, - _agent_key: AccountIdOf, - _amount: BalanceOf, + key: AccountIdOf, + agent_key: AccountIdOf, + amount: BalanceOf, ) -> DispatchResult { - todo!() + ensure!( + amount >= crate::MinimumAllowedStake::::get(), + crate::Error::::StakeTooSmall + ); + + ensure!( + agent::exists::(&agent_key), + crate::Error::::AgentDoesNotExist + ); + + ensure!( + crate::StakingTo::::get(&key, &agent_key).unwrap_or(0) >= amount, + crate::Error::::NotEnoughStakeToWithdraw + ); + + crate::StakingTo::::mutate(&key, &agent_key, |stake| { + *stake = Some(stake.unwrap_or(0).saturating_sub(amount)) + }); + + crate::StakedBy::::mutate(&agent_key, &key, |stake| { + *stake = Some(stake.unwrap_or(0).saturating_sub(amount)) + }); + + crate::TotalStake::::mutate(|total_stake| *total_stake = total_stake.saturating_sub(amount)); + + let _ = ::Currency::deposit_creating(&key, amount); + + Ok(()) } pub fn transfer_stake( - _origin: OriginFor, - _agent_key: AccountIdOf, - _new_agent_key: AccountIdOf, - _amount: BalanceOf, + key: AccountIdOf, + agent_key: AccountIdOf, + new_agent_key: AccountIdOf, + amount: BalanceOf, ) -> DispatchResult { - todo!() + remove_stake::(key.clone(), agent_key, amount)?; + add_stake::(key, new_agent_key, amount)?; + Ok(()) } diff --git a/runtime/src/configs.rs b/runtime/src/configs.rs index 59e3265..c771583 100644 --- a/runtime/src/configs.rs +++ b/runtime/src/configs.rs @@ -288,8 +288,6 @@ impl pallet_grandpa::Config for Runtime { // --- Torus --- impl pallet_torus0::Config for Runtime { - type Currency = Balances; - type DefaultMinValidatorStake = ConstU128<50_000_000_000_000>; type DefaultImmunityPeriod = ConstU16<0>; @@ -300,7 +298,7 @@ impl pallet_torus0::Config for Runtime { type DefaultMaxAllowedWeights = ConstU16<420>; - type DefaultTempo = ConstU16<100>; + type DefaultRewardInterval = ConstU16<100>; type DefaultMinNameLength = ConstU16<2>; @@ -318,12 +316,30 @@ impl pallet_torus0::Config for Runtime { type DefaultMinWeightControlFee = ConstU8<4>; + type DefaultMinBurn = ConstU128<10_000_000_000>; + + type DefaultMaxBurn = ConstU128<150_000_000_000>; + + type DefaultAdjustmentAlpha = ConstU64<{ u64::MAX / 2 }>; + + type DefaultTargetRegistrationsInterval = ConstU64<142>; + + type DefaultTargetRegistrationsPerInterval = ConstU16<3>; + + type DefaultMaxRegistrationsPerInterval = ConstU16<32>; + #[doc = " The storage MaxNameLength should be constrained to be no more than the value of this."] #[doc = " This is needed on agent::Agent to set the `name` field BoundedVec max length."] type MaxAgentNameLengthConstraint = ConstU32<256>; #[doc = " This is needed on agent::Agent to set the `address` field BoundedVec max length."] - type MaxAgentAddressLengthConstraint = ConstU32<256>; + type MaxAgentUrlLengthConstraint = ConstU32<256>; + + type MaxAgentMetadataLengthConstraint = ConstU32<256>; + + type RuntimeEvent = RuntimeEvent; + + type Currency = Balances; } parameter_types! { diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index adad249..548df37 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -67,7 +67,7 @@ impl pallet_torus0::Config for Test { type DefaultMaxAllowedWeights = ConstU16<420>; - type DefaultTempo = ConstU16<100>; + type DefaultRewardInterval = ConstU16<100>; type DefaultMinNameLength = ConstU16<2>; @@ -85,12 +85,28 @@ impl pallet_torus0::Config for Test { type DefaultMinWeightControlFee = ConstU8<4>; + type DefaultMinBurn = ConstU128<10_000_000_000>; + + type DefaultMaxBurn = ConstU128<150_000_000_000>; + + type DefaultAdjustmentAlpha = ConstU64<{ u64::MAX / 2 }>; + + type DefaultTargetRegistrationsInterval = ConstU64<142>; + + type DefaultTargetRegistrationsPerInterval = ConstU16<3>; + + type DefaultMaxRegistrationsPerInterval = ConstU16<32>; + #[doc = " The storage MaxNameLength should be constrained to be no more than the value of this."] #[doc = " This is needed on agent::Agent to set the `name` field BoundedVec max length."] type MaxAgentNameLengthConstraint = ConstU32<256>; #[doc = " This is needed on agent::Agent to set the `address` field BoundedVec max length."] - type MaxAgentAddressLengthConstraint = ConstU32<256>; + type MaxAgentUrlLengthConstraint = ConstU32<256>; + + type MaxAgentMetadataLengthConstraint = ConstU32<256>; + + type RuntimeEvent = RuntimeEvent; type Currency = Balances; }