diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index 708e2237..08af32f1 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -46,7 +46,6 @@ use crate::worker::UntilImportedOrTimeout; use codec::{Decode, Encode}; use futures::{Future, StreamExt}; use log::*; -use substrate_prometheus_endpoint::Registry; use sc_client_api::{self, backend::AuxStore, BlockOf, BlockchainEvents}; use sc_consensus::{ BasicQueue, BlockCheckParams, BlockImport, BlockImportParams, BoxBlockImport, @@ -63,6 +62,7 @@ use sp_runtime::{ traits::{Block as BlockT, Header as HeaderT}, }; use std::{cmp::Ordering, marker::PhantomData, sync::Arc, time::Duration}; +use substrate_prometheus_endpoint::Registry; const LOG_TARGET: &str = "pow"; @@ -130,7 +130,11 @@ pub const POW_AUX_PREFIX: [u8; 4] = *b"PoW:"; /// Get the auxiliary storage key used by engine to store total difficulty. fn aux_key>(hash: &T) -> Vec { - POW_AUX_PREFIX.iter().chain(hash.as_ref()).copied().collect() + POW_AUX_PREFIX + .iter() + .chain(hash.as_ref()) + .copied() + .collect() } /// Intermediate value passed to block importer. @@ -218,7 +222,7 @@ pub struct PowBlockImport { } impl Clone -for PowBlockImport + for PowBlockImport { fn clone(&self) -> Self { Self { @@ -268,7 +272,7 @@ where inherent_data_providers: CIDP::InherentDataProviders, ) -> Result<(), Error> { if *block.header().number() < self.check_inherents_after { - return Ok(()) + return Ok(()); } let inherent_data = inherent_data_providers @@ -284,7 +288,10 @@ where if !inherent_res.ok() { for (identifier, error) in inherent_res.into_errors() { - match inherent_data_providers.try_handle_error(&identifier, &error).await { + match inherent_data_providers + .try_handle_error(&identifier, &error) + .await + { Some(res) => res.map_err(Error::CheckInherents)?, None => return Err(Error::CheckInherentsUnknownError(identifier)), } @@ -341,7 +348,7 @@ where .create_inherent_data_providers(parent_hash, ()) .await?, ) - .await?; + .await?; } block.body = Some(check_block.deconstruct().1); @@ -366,7 +373,7 @@ where &inner_seal, difficulty, )? { - return Err(Error::::InvalidSeal.into()) + return Err(Error::::InvalidSeal.into()); } aux.difficulty = difficulty; @@ -384,7 +391,7 @@ where fetch_seal::(best_header.digest().logs.last(), best_hash)?; self.algorithm.break_tie(&best_inner_seal, &inner_seal) - }, + } }, )); } @@ -401,7 +408,10 @@ pub struct PowVerifier { impl PowVerifier { pub fn new(algorithm: Algorithm) -> Self { - Self { algorithm, _marker: PhantomData } + Self { + algorithm, + _marker: PhantomData, + } } fn check_header(&self, mut header: B::Header) -> Result<(B::Header, DigestItem), Error> @@ -411,19 +421,24 @@ impl PowVerifier { let hash = header.hash(); let (seal, inner_seal) = match header.digest_mut().pop() { - Some(DigestItem::Seal(id, seal)) => + Some(DigestItem::Seal(id, seal)) => { if id == POW_ENGINE_ID { (DigestItem::Seal(id, seal.clone()), seal) } else { - return Err(Error::WrongEngine(id)) - }, + return Err(Error::WrongEngine(id)); + } + } _ => return Err(Error::HeaderUnsealed(hash)), }; let pre_hash = header.hash(); - if !self.algorithm.preliminary_verify(&pre_hash, &inner_seal)?.unwrap_or(true) { - return Err(Error::FailedPreliminaryVerify) + if !self + .algorithm + .preliminary_verify(&pre_hash, &inner_seal)? + .unwrap_or(true) + { + return Err(Error::FailedPreliminaryVerify); } Ok((header, seal)) @@ -471,7 +486,13 @@ where { let verifier = PowVerifier::new(algorithm); - Ok(BasicQueue::new(verifier, block_import, justification_import, spawner, registry)) + Ok(BasicQueue::new( + verifier, + block_import, + justification_import, + spawner, + registry, + )) } /// Start the mining worker for PoW. This function provides the necessary helper functions that can @@ -519,31 +540,31 @@ where let task = async move { loop { if timer.next().await.is_none() { - break + break; } if sync_oracle.is_major_syncing() { debug!(target: LOG_TARGET, "Skipping proposal due to sync."); worker.on_major_syncing(); - continue + continue; } let best_header = match select_chain.best_chain().await { Ok(x) => x, Err(err) => { warn!( - target: LOG_TARGET, - "Unable to pull new block for authoring. \ - Select best chain error: {}", - err - ); - continue - }, + target: LOG_TARGET, + "Unable to pull new block for authoring. \ + Select best chain error: {}", + err + ); + continue; + } }; let best_hash = best_header.hash(); if worker.best_hash() == Some(best_hash) { - continue + continue; } // The worker is locked for the duration of the whole proposing period. Within this @@ -553,13 +574,13 @@ where Ok(x) => x, Err(err) => { warn!( - target: LOG_TARGET, - "Unable to propose new block for authoring. \ - Fetch difficulty failed: {}", - err, - ); - continue - }, + target: LOG_TARGET, + "Unable to propose new block for authoring. \ + Fetch difficulty failed: {}", + err, + ); + continue; + } }; let inherent_data_providers = match create_inherent_data_providers @@ -569,26 +590,26 @@ where Ok(x) => x, Err(err) => { warn!( - target: LOG_TARGET, - "Unable to propose new block for authoring. \ - Creating inherent data providers failed: {}", - err, - ); - continue - }, + target: LOG_TARGET, + "Unable to propose new block for authoring. \ + Creating inherent data providers failed: {}", + err, + ); + continue; + } }; let inherent_data = match inherent_data_providers.create_inherent_data().await { Ok(r) => r, Err(e) => { warn!( - target: LOG_TARGET, - "Unable to propose new block for authoring. \ - Creating inherent data failed: {}", - e, - ); - continue - }, + target: LOG_TARGET, + "Unable to propose new block for authoring. \ + Creating inherent data failed: {}", + e, + ); + continue; + } }; let mut inherent_digest = Digest::default(); @@ -602,28 +623,30 @@ where Ok(x) => x, Err(err) => { warn!( - target: LOG_TARGET, - "Unable to propose new block for authoring. \ - Creating proposer failed: {:?}", - err, - ); - continue - }, + target: LOG_TARGET, + "Unable to propose new block for authoring. \ + Creating proposer failed: {:?}", + err, + ); + continue; + } }; - let proposal = - match proposer.propose(inherent_data, inherent_digest, build_time, None).await { - Ok(x) => x, - Err(err) => { - warn!( - target: LOG_TARGET, - "Unable to propose new block for authoring. \ - Creating proposal failed: {}", - err, - ); - continue - }, - }; + let proposal = match proposer + .propose(inherent_data, inherent_digest, build_time, None) + .await + { + Ok(x) => x, + Err(err) => { + warn!( + target: LOG_TARGET, + "Unable to propose new block for authoring. \ + Creating proposal failed: {}", + err, + ); + continue; + } + }; let build = MiningBuild:: { metadata: MiningMetadata { @@ -648,11 +671,12 @@ fn find_pre_digest(header: &B::Header) -> Result>, Err for log in header.digest().logs() { trace!(target: LOG_TARGET, "Checking log {:?}, looking for pre runtime digest", log); match (log, pre_digest.is_some()) { - (DigestItem::PreRuntime(POW_ENGINE_ID, _), true) => - return Err(Error::MultiplePreRuntimeDigests), + (DigestItem::PreRuntime(POW_ENGINE_ID, _), true) => { + return Err(Error::MultiplePreRuntimeDigests) + } (DigestItem::PreRuntime(POW_ENGINE_ID, v), false) => { pre_digest = Some(v.clone()); - }, + } (_, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), } } @@ -663,12 +687,13 @@ fn find_pre_digest(header: &B::Header) -> Result>, Err /// Fetch PoW seal. fn fetch_seal(digest: Option<&DigestItem>, hash: B::Hash) -> Result, Error> { match digest { - Some(DigestItem::Seal(id, seal)) => + Some(DigestItem::Seal(id, seal)) => { if id == &POW_ENGINE_ID { Ok(seal.clone()) } else { Err(Error::::WrongEngine(*id)) - }, + } + } _ => Err(Error::::HeaderUnsealed(hash)), } -} \ No newline at end of file +} diff --git a/client/consensus/pow/src/worker.rs b/client/consensus/pow/src/worker.rs index 7a3b8fef..96be0ddf 100644 --- a/client/consensus/pow/src/worker.rs +++ b/client/consensus/pow/src/worker.rs @@ -151,16 +151,16 @@ where Ok(true) => (), Ok(false) => { warn!(target: LOG_TARGET, "Unable to import mined block: seal is invalid",); - return false - }, + return false; + } Err(err) => { warn!(target: LOG_TARGET, "Unable to import mined block: {}", err,); - return false - }, + return false; + } } } else { warn!(target: LOG_TARGET, "Unable to import mined block: metadata does not exist",); - return false + return false; } let build = if let Some(build) = { @@ -174,7 +174,7 @@ where build } else { warn!(target: LOG_TARGET, "Unable to import mined block: build does not exist",); - return false + return false; }; let seal = DigestItem::Seal(POW_ENGINE_ID, seal); @@ -203,15 +203,15 @@ where ); info!( - target: LOG_TARGET, - "✅ Successfully mined block on top of: {}", build.metadata.best_hash - ); + target: LOG_TARGET, + "✅ Successfully mined block on top of: {}", build.metadata.best_hash + ); true - }, + } Err(err) => { warn!(target: LOG_TARGET, "Unable to import mined block: {}", err,); false - }, + } } } } @@ -243,7 +243,11 @@ pub struct UntilImportedOrTimeout { impl UntilImportedOrTimeout { /// Create a new stream using the given import notification and timeout duration. pub fn new(import_notifications: ImportNotifications, timeout: Duration) -> Self { - Self { import_notifications, timeout, inner_delay: None } + Self { + import_notifications, + timeout, + inner_delay: None, + } } } @@ -258,7 +262,7 @@ impl Stream for UntilImportedOrTimeout { Poll::Pending => break, Poll::Ready(Some(_)) => { fire = true; - }, + } Poll::Ready(None) => return Poll::Ready(None), } } @@ -270,7 +274,7 @@ impl Stream for UntilImportedOrTimeout { Poll::Pending => (), Poll::Ready(()) => { fire = true; - }, + } } if fire { @@ -280,4 +284,4 @@ impl Stream for UntilImportedOrTimeout { Poll::Pending } } -} \ No newline at end of file +} diff --git a/client/consensus/qpow/src/chain_management.rs b/client/consensus/qpow/src/chain_management.rs index 24ce1f98..5eb2c285 100644 --- a/client/consensus/qpow/src/chain_management.rs +++ b/client/consensus/qpow/src/chain_management.rs @@ -1,16 +1,16 @@ -use std::marker::PhantomData; -use std::sync::Arc; +use crate::QPowAlgorithm; use futures::StreamExt; use primitive_types::{H256, U512}; use sc_client_api::{AuxStore, BlockBackend, BlockchainEvents, Finalizer}; use sc_service::TaskManager; -use sp_api::__private::BlockT; use sp_api::ProvideRuntimeApi; +use sp_api::__private::BlockT; use sp_blockchain::{Backend, HeaderBackend}; use sp_consensus::SelectChain; -use sp_runtime::traits::{Header, One, Zero}; use sp_consensus_qpow::QPoWApi; -use crate::QPowAlgorithm; +use sp_runtime::traits::{Header, One, Zero}; +use std::marker::PhantomData; +use std::sync::Arc; const IGNORED_CHAINS_PREFIX: &[u8] = b"QPow:IgnoredChains:"; @@ -49,14 +49,14 @@ where C::Api: QPoWApi, BE: sc_client_api::Backend + 'static, { - pub fn new(backend: Arc, client: Arc, algorithm: QPowAlgorithm) -> Self { + pub fn new(backend: Arc, client: Arc, algorithm: QPowAlgorithm) -> Self { log::debug!("Creating new HeaviestChain instance"); Self { backend, client, algorithm, - _phantom: PhantomData + _phantom: PhantomData, } } @@ -73,12 +73,18 @@ where if best_hash == Default::default() { log::debug!("✓ No blocks to finalize - best hash is default"); - return Ok(()); // No blocks to finalize + return Ok(()); // No blocks to finalize } - let best_header = self.client.header(best_hash) + let best_header = self + .client + .header(best_hash) .map_err(|e| { - log::error!("Failed to get header for best hash: {:?}, error: {:?}", best_hash, e); + log::error!( + "Failed to get header for best hash: {:?}, error: {:?}", + best_hash, + e + ); sp_consensus::Error::Other(format!("Blockchain error: {:?}", e).into()) })? .ok_or_else(|| { @@ -89,7 +95,10 @@ where let best_number = *best_header.number(); log::debug!("Current best block number: {}", best_number); - let max_reorg_depth = self.client.runtime_api().get_max_reorg_depth(best_hash) + let max_reorg_depth = self + .client + .runtime_api() + .get_max_reorg_depth(best_hash) .expect("Failed to get max reorg depth"); // Calculate how far back to finalize @@ -97,9 +106,12 @@ where // Only finalize if we have enough blocks if best_number <= finalize_depth.into() { - log::debug!("✓ Chain not long enough for finalization. Best number: {}, Required: > {}", - best_number, finalize_depth); - return Ok(()); // Chain not long enough yet + log::debug!( + "✓ Chain not long enough for finalization. Best number: {}, Required: > {}", + best_number, + finalize_depth + ); + return Ok(()); // Chain not long enough yet } // Calculate block number to finalize @@ -107,10 +119,14 @@ where log::debug!("Target block number to finalize: {}", finalize_number); // Get the hash for that block number in the current canonical chain - let finalize_hash = self.client.hash(finalize_number) + let finalize_hash = self + .client + .hash(finalize_number) .map_err(|e| { log::error!("Failed to get hash for block #{}: {:?}", finalize_number, e); - sp_consensus::Error::Other(format!("Failed to get hash at #{}: {:?}", finalize_number, e).into()) + sp_consensus::Error::Other( + format!("Failed to get hash at #{}: {:?}", finalize_number, e).into(), + ) })? .ok_or_else(|| { log::error!("No block found at #{} for finalization", finalize_number); @@ -121,13 +137,24 @@ where // Get last finalized block before attempting finalization let last_finalized_before = self.client.info().finalized_number; - log::debug!("Last finalized block before attempt: #{}", last_finalized_before); + log::debug!( + "Last finalized block before attempt: #{}", + last_finalized_before + ); // Finalize the block - self.client.finalize_block(finalize_hash, None, true) + self.client + .finalize_block(finalize_hash, None, true) .map_err(|e| { - log::error!("Failed to finalize block #{} ({:?}): {:?}", finalize_number, finalize_hash, e); - sp_consensus::Error::Other(format!("Failed to finalize block #{}: {:?}", finalize_number, e).into()) + log::error!( + "Failed to finalize block #{} ({:?}): {:?}", + finalize_number, + finalize_hash, + e + ); + sp_consensus::Error::Other( + format!("Failed to finalize block #{}: {:?}", finalize_number, e).into(), + ) })?; // Check if finalization was successful @@ -135,10 +162,17 @@ where log::debug!( "✓ Finalization stats: best={}, finalized={}, finalize_depth={}, target_finalize={}", - best_number, last_finalized_after, finalize_depth, finalize_number + best_number, + last_finalized_after, + finalize_depth, + finalize_number ); - log::info!("✓ Finalized block #{} ({:?})", finalize_number, finalize_hash); + log::info!( + "✓ Finalized block #{} ({:?})", + finalize_number, + finalize_hash + ); Ok(()) } @@ -149,9 +183,16 @@ where let current_hash = chain_head.hash(); let current_number = *chain_head.number(); - let total_work = self.client.runtime_api().get_total_work(current_hash) + let total_work = self + .client + .runtime_api() + .get_total_work(current_hash) .map_err(|e| { - log::error!("Failed to get total work for chain with head #{}: {:?}", current_number, e); + log::error!( + "Failed to get total work for chain with head #{}: {:?}", + current_number, + e + ); sp_consensus::Error::Other(format!("Failed to get total difficulty {:?}", e).into()) })?; @@ -166,18 +207,35 @@ where } /// Method to find best chain when there's no current best header - async fn find_best_chain(&self, leaves: Vec) -> Result { - log::debug!("Finding best chain among {} leaves when no current best exists", leaves.len()); + async fn find_best_chain( + &self, + leaves: Vec, + ) -> Result { + log::debug!( + "Finding best chain among {} leaves when no current best exists", + leaves.len() + ); let mut best_header = None; let mut best_work = U512::zero(); for (idx, leaf_hash) in leaves.iter().enumerate() { - log::debug!("Checking leaf [{}/{}]: {:?}", idx + 1, leaves.len(), leaf_hash); + log::debug!( + "Checking leaf [{}/{}]: {:?}", + idx + 1, + leaves.len(), + leaf_hash + ); - let header = self.client.header(*leaf_hash) + let header = self + .client + .header(*leaf_hash) .map_err(|e| { - log::error!("Blockchain error when getting header for leaf {:?}: {:?}", leaf_hash, e); + log::error!( + "Blockchain error when getting header for leaf {:?}: {:?}", + leaf_hash, + e + ); sp_consensus::Error::Other(format!("Blockchain error: {:?}", e).into()) })? .ok_or_else(|| { @@ -257,8 +315,11 @@ where let mut reorg_depth = 0; // First, move the headers to the same height - log::debug!("Phase 1: Aligning heights - current: {}, competing: {}", - current_height, competing_height); + log::debug!( + "Phase 1: Aligning heights - current: {}, competing: {}", + current_height, + competing_height + ); while current_height > competing_height { // Check if the blocks are identical during descent @@ -280,13 +341,23 @@ where reorg_depth ); - current_best_hash = *self.client.header(current_best_hash) + current_best_hash = *self + .client + .header(current_best_hash) .map_err(|e| { - log::error!("Blockchain error when getting header for #{}: {:?}", current_height, e); + log::error!( + "Blockchain error when getting header for #{}: {:?}", + current_height, + e + ); sp_consensus::Error::Other(format!("Blockchain error: {:?}", e).into()) })? .ok_or_else(|| { - log::error!("Missing header at #{} ({:?})", current_height, current_best_hash); + log::error!( + "Missing header at #{} ({:?})", + current_height, + current_best_hash + ); sp_consensus::Error::Other("Missing header".into()) })? .parent_hash(); @@ -303,8 +374,11 @@ where } // Similarly, if the competing chain is taller, move it down to the same height - log::debug!("Phase 2: Aligning heights if competing chain is taller - current: {}, competing: {}", - current_height, competing_height); + log::debug!( + "Phase 2: Aligning heights if competing chain is taller - current: {}, competing: {}", + current_height, + competing_height + ); while competing_height > current_height { reorg_depth += 1; @@ -314,15 +388,23 @@ where competing_hash ); - competing_hash = *self.client.header(competing_hash) + competing_hash = *self + .client + .header(competing_hash) .map_err(|e| { - log::error!("Blockchain error when getting header for competing chain #{}: {:?}", - competing_height, e); + log::error!( + "Blockchain error when getting header for competing chain #{}: {:?}", + competing_height, + e + ); sp_consensus::Error::Other(format!("Blockchain error: {:?}", e).into()) })? .ok_or_else(|| { - log::error!("Missing header for competing chain at #{} ({:?})", - competing_height, competing_hash); + log::error!( + "Missing header for competing chain at #{} ({:?})", + competing_height, + competing_hash + ); sp_consensus::Error::Other("Missing header".into()) })? .parent_hash(); @@ -336,7 +418,10 @@ where ); } - log::debug!("Phase 3: Both chains now at height {} - finding fork point", current_height); + log::debug!( + "Phase 3: Both chains now at height {} - finding fork point", + current_height + ); // Now both headers are at the same height // Find the fork-point by traversing backwards @@ -344,7 +429,9 @@ where // If we reach genesis and still no match, no common ancestor if current_height.is_zero() { log::error!("Reached genesis block without finding common ancestor"); - return Err(sp_consensus::Error::Other("No common ancestor found".into())); + return Err(sp_consensus::Error::Other( + "No common ancestor found".into(), + )); } log::debug!( @@ -355,27 +442,45 @@ where ); // Move down one block in the current best chain - current_best_hash = *self.client.header(current_best_hash) + current_best_hash = *self + .client + .header(current_best_hash) .map_err(|e| { - log::error!("Blockchain error when getting parent at #{}: {:?}", current_height, e); + log::error!( + "Blockchain error when getting parent at #{}: {:?}", + current_height, + e + ); sp_consensus::Error::Other(format!("Blockchain error: {:?}", e).into()) })? .ok_or_else(|| { - log::error!("Missing header for parent at #{} ({:?})", current_height, current_best_hash); + log::error!( + "Missing header for parent at #{} ({:?})", + current_height, + current_best_hash + ); sp_consensus::Error::Other("Missing header".into()) })? .parent_hash(); // Move down one block in the competing chain - competing_hash = *self.client.header(competing_hash) + competing_hash = *self + .client + .header(competing_hash) .map_err(|e| { - log::error!("Blockchain error when getting parent for competing chain at #{}: {:?}", - current_height, e); + log::error!( + "Blockchain error when getting parent for competing chain at #{}: {:?}", + current_height, + e + ); sp_consensus::Error::Other(format!("Blockchain error: {:?}", e).into()) })? .ok_or_else(|| { - log::error!("Missing header for competing chain parent at #{} ({:?})", - current_height, competing_hash); + log::error!( + "Missing header for competing chain parent at #{} ({:?})", + current_height, + competing_hash + ); sp_consensus::Error::Other("Missing header".into()) })? .parent_hash(); @@ -414,15 +519,21 @@ where Ok(Some(_)) => { log::debug!("Chain with head {:?} is ignored", hash); Ok(true) - }, + } Ok(None) => { log::debug!("Chain with head {:?} is not ignored", hash); Ok(false) - }, + } Err(e) => { - log::error!("Failed to check if chain with head {:?} is ignored: {:?}", hash, e); - Err(sp_consensus::Error::Other(format!("Failed to check ignored chain: {:?}", e).into())) - }, + log::error!( + "Failed to check if chain with head {:?} is ignored: {:?}", + hash, + e + ); + Err(sp_consensus::Error::Other( + format!("Failed to check ignored chain: {:?}", e).into(), + )) + } } } @@ -435,9 +546,14 @@ where let empty_value = vec![]; - self.client.insert_aux(&[(key.as_slice(), empty_value.as_slice())], &[]) + self.client + .insert_aux(&[(key.as_slice(), empty_value.as_slice())], &[]) .map_err(|e| { - log::error!("Failed to add chain with head {:?} to ignored chains: {:?}", hash, e); + log::error!( + "Failed to add chain with head {:?} to ignored chains: {:?}", + hash, + e + ); sp_consensus::Error::Other(format!("Failed to add ignored chain: {:?}", e).into()) }) } @@ -447,11 +563,11 @@ where impl SelectChain for HeaviestChain where B: BlockT, - C: ProvideRuntimeApi + HeaderBackend + BlockBackend +AuxStore + Send + Sync + 'static, + C: ProvideRuntimeApi + HeaderBackend + BlockBackend + AuxStore + Send + Sync + 'static, C::Api: QPoWApi, BE: sc_client_api::Backend + 'static, { - async fn leaves(&self) -> Result, sp_consensus::Error>{ + async fn leaves(&self) -> Result, sp_consensus::Error> { log::debug!("Getting blockchain leaves"); let leaves = self.backend.blockchain().leaves().map_err(|e| { @@ -476,7 +592,9 @@ where if leaves.is_empty() { log::error!("☝️ Blockchain has no leaves"); - return Err(sp_consensus::Error::Other("Blockchain has no leaves".into())); + return Err(sp_consensus::Error::Other( + "Blockchain has no leaves".into(), + )); } // Get info about last finalized block @@ -488,33 +606,55 @@ where hash if hash != Default::default() => { log::debug!("☝️ Current best hash: {:?}", hash); - self.client.header(hash) + self.client + .header(hash) .map_err(|e| { - log::error!("☝️ Blockchain error when getting header for best hash: {:?}", e); + log::error!( + "☝️ Blockchain error when getting header for best hash: {:?}", + e + ); sp_consensus::Error::Other(format!("Blockchain error: {:?}", e).into()) })? .ok_or_else(|| { log::error!("☝️ Missing header for current best hash: {:?}", hash); sp_consensus::Error::Other("Missing current best header".into()) })? - }, + } _ => { // If there's no current best, we don't need to find reorg depth - log::debug!("☝️ No current best hash, finding best chain without reorg constraints"); + log::debug!( + "☝️ No current best hash, finding best chain without reorg constraints" + ); return self.find_best_chain(leaves).await; } }; let current_best_number = *current_best.number(); - log::debug!("☝️ Current best block: #{} ({:?})", current_best_number, current_best.hash()); + log::debug!( + "☝️ Current best block: #{} ({:?})", + current_best_number, + current_best.hash() + ); let mut best_header = current_best.clone(); let mut best_work = self.calculate_chain_work(¤t_best)?; - log::debug!("☝️ Current best chain: {:?} with work: {:?}", best_header.hash(), best_work); + log::debug!( + "☝️ Current best chain: {:?} with work: {:?}", + best_header.hash(), + best_work + ); - log::debug!("☝️ Evaluating {} leaves for potential best chain", leaves.len()); + log::debug!( + "☝️ Evaluating {} leaves for potential best chain", + leaves.len() + ); for (idx, leaf_hash) in leaves.iter().enumerate() { - log::debug!("☝️ Evaluating leaf [{}/{}]: {:?}", idx + 1, leaves.len(), leaf_hash); + log::debug!( + "☝️ Evaluating leaf [{}/{}]: {:?}", + idx + 1, + leaves.len(), + leaf_hash + ); // Skip if it's the current best or already ignored if *leaf_hash == best_header.hash() { @@ -523,11 +663,16 @@ where } if self.is_chain_ignored(leaf_hash)? { - log::debug!("☝️ Skipping leaf {:?} - it's in the ignored list", leaf_hash); + log::debug!( + "☝️ Skipping leaf {:?} - it's in the ignored list", + leaf_hash + ); continue; } - let header = self.client.header(*leaf_hash) + let header = self + .client + .header(*leaf_hash) .map_err(|e| { log::error!("☝️ Blockchain error when getting header for leaf: {:?}", e); sp_consensus::Error::Other(format!("Blockchain error: {:?}", e).into()) @@ -543,7 +688,10 @@ where let chain_work = self.calculate_chain_work(&header)?; log::debug!("☝️ Chain work for leaf #{}: {}", header_number, chain_work); - let max_reorg_depth = self.client.runtime_api().get_max_reorg_depth(best_header.hash()) + let max_reorg_depth = self + .client + .runtime_api() + .get_max_reorg_depth(best_header.hash()) .expect("Failed to get max reorg depth"); log::debug!("☝️ Max reorg depth from runtime: {}", max_reorg_depth); @@ -557,7 +705,8 @@ where best_work ); - let (fork_point, reorg_depth) = self.find_common_ancestor_and_depth(¤t_best, &header)?; + let (fork_point, reorg_depth) = + self.find_common_ancestor_and_depth(¤t_best, &header)?; log::debug!( "☝️ Found common ancestor with hash {:?} with reorg depth: {}", fork_point, @@ -633,7 +782,8 @@ where best_work ); - let (fork_point, reorg_depth) = self.find_common_ancestor_and_depth(¤t_best, &header)?; + let (fork_point, reorg_depth) = + self.find_common_ancestor_and_depth(¤t_best, &header)?; log::debug!( "☝️ Found common ancestor with hash {:?} with reorg depth: {}", fork_point, @@ -677,7 +827,11 @@ where } fn ignored_chain_key>(hash: &T) -> Vec { - IGNORED_CHAINS_PREFIX.iter().chain(hash.as_ref()).copied().collect() + IGNORED_CHAINS_PREFIX + .iter() + .chain(hash.as_ref()) + .copied() + .collect() } pub struct ChainManagement; @@ -689,35 +843,43 @@ impl ChainManagement { task_manager: &TaskManager, ) where B: BlockT, - C: ProvideRuntimeApi + HeaderBackend + BlockBackend + AuxStore + BlockchainEvents + Finalizer + Send + Sync + 'static, + C: ProvideRuntimeApi + + HeaderBackend + + BlockBackend + + AuxStore + + BlockchainEvents + + Finalizer + + Send + + Sync + + 'static, C::Api: QPoWApi, BE: sc_client_api::Backend + 'static, { log::info!("⛓️ Spawning chain finalization task"); - task_manager.spawn_essential_handle().spawn( - "chain_finalization", - None, - async move { + task_manager + .spawn_essential_handle() + .spawn("chain_finalization", None, async move { log::info!("⛓️ Chain finalization task spawned"); - let mut import_notification_stream = select_chain.client.import_notification_stream(); + let mut import_notification_stream = + select_chain.client.import_notification_stream(); log::debug!("⛓️ Listening for block import notifications"); while let Some(notification) = import_notification_stream.next().await { - if let Err(e) = select_chain.finalize_canonical_at_depth() { log::error!("⛓️ Failed to finalize blocks: {:?}", e); } else { - log::debug!("⛓️ Successfully processed finalization after import of block #{}", - notification.header.number()); + log::debug!( + "⛓️ Successfully processed finalization after import of block #{}", + notification.header.number() + ); } } log::info!("Block import notification stream ended"); - } - ); + }); log::info!("Chain finalization task has been spawned"); } -} \ No newline at end of file +} diff --git a/client/consensus/qpow/src/lib.rs b/client/consensus/qpow/src/lib.rs index 74451a9c..3b5c40a7 100644 --- a/client/consensus/qpow/src/lib.rs +++ b/client/consensus/qpow/src/lib.rs @@ -1,32 +1,30 @@ -mod miner; mod chain_management; +mod miner; -use std::marker::PhantomData; -use std::sync::Arc; +pub use chain_management::ChainManagement; +pub use chain_management::HeaviestChain; use codec::{Decode, Encode}; +pub use miner::QPoWMiner; use primitive_types::{H256, U512}; +use sc_client_api::BlockBackend; use sc_consensus_pow::{Error, PowAlgorithm}; -use sp_consensus_pow::{Seal as RawSeal}; -use sp_api::__private::BlockT; use sp_api::ProvideRuntimeApi; -use sp_runtime::generic::BlockId; +use sp_api::__private::BlockT; +use sp_consensus_pow::Seal as RawSeal; use sp_consensus_qpow::QPoWApi; -use sc_client_api::BlockBackend; -pub use miner::QPoWMiner; -pub use chain_management::HeaviestChain; -pub use chain_management::ChainManagement; - - +use sp_runtime::generic::BlockId; +use std::marker::PhantomData; +use std::sync::Arc; #[derive(Clone, Debug, Encode, Decode, PartialEq)] pub struct QPoWSeal { pub nonce: [u8; 64], } -pub struct QPowAlgorithm +pub struct QPowAlgorithm where B: BlockT, - C: ProvideRuntimeApi + C: ProvideRuntimeApi, { pub client: Arc, pub _phantom: PhantomData, @@ -46,13 +44,12 @@ where } // Here we implement the general PowAlgorithm trait for our concrete Sha3Algorithm -impl PowAlgorithm for QPowAlgorithm +impl PowAlgorithm for QPowAlgorithm where B: BlockT, C: ProvideRuntimeApi + BlockBackend + Send + Sync + 'static, C::Api: QPoWApi, { - type Difficulty = U512; fn difficulty(&self, parent: B::Hash) -> Result> { @@ -71,7 +68,6 @@ where seal: &RawSeal, _difficulty: Self::Difficulty, ) -> Result> { - //Executed for mined and imported blocks /* @@ -108,9 +104,12 @@ where let pre_hash = pre_hash.as_ref().try_into().unwrap_or([0u8; 32]); // Verify the nonce using QPoW - if !self.client.runtime_api() + if !self + .client + .runtime_api() .verify_for_import(parent_hash, pre_hash, nonce) - .map_err(|e| Error::Runtime(format!("API error in verify_nonce: {:?}", e)))? { + .map_err(|e| Error::Runtime(format!("API error in verify_nonce: {:?}", e)))? + { return Ok(false); } @@ -118,10 +117,11 @@ where } } - pub fn extract_block_hash>(parent: &BlockId) -> Result> { match parent { BlockId::Hash(hash) => Ok(*hash), - BlockId::Number(_) => Err(Error::Runtime("Expected BlockId::Hash, but got BlockId::Number".into())), + BlockId::Number(_) => Err(Error::Runtime( + "Expected BlockId::Hash, but got BlockId::Number".into(), + )), } -} \ No newline at end of file +} diff --git a/client/consensus/qpow/src/miner.rs b/client/consensus/qpow/src/miner.rs index bfd2bdc8..c0622c72 100644 --- a/client/consensus/qpow/src/miner.rs +++ b/client/consensus/qpow/src/miner.rs @@ -1,29 +1,27 @@ -use std::marker::PhantomData; -use std::sync::Arc; +use crate::QPoWSeal; use primitive_types::H256; use sc_client_api::BlockBackend; use sp_api::ProvideRuntimeApi; -use sp_runtime::traits::Block as BlockT; use sp_consensus_qpow::QPoWApi; -use crate::QPoWSeal; +use sp_runtime::traits::Block as BlockT; +use std::marker::PhantomData; +use std::sync::Arc; -pub struct QPoWMiner +pub struct QPoWMiner where B: BlockT, - C: ProvideRuntimeApi + C: ProvideRuntimeApi, { pub client: Arc, pub _phantom: PhantomData, } - impl QPoWMiner where B: BlockT, C: ProvideRuntimeApi + BlockBackend + Send + Sync + 'static, C::Api: QPoWApi, { - pub fn new(client: Arc) -> Self { Self { client, @@ -37,24 +35,22 @@ where pre_hash: BA::Hash, nonce: [u8; 64], ) -> Result { - // Convert pre_hash to [u8; 32] for verification // TODO normalize all the different ways we do calculations let block_hash = pre_hash.as_ref().try_into().unwrap_or([0u8; 32]); // Verify the nonce using runtime api - match self.client.runtime_api().submit_nonce(parent_hash, block_hash, nonce) { - Ok(true) => { - Ok(QPoWSeal { nonce }) - } - Ok(false) => { - Err(()) - } + match self + .client + .runtime_api() + .submit_nonce(parent_hash, block_hash, nonce) + { + Ok(true) => Ok(QPoWSeal { nonce }), + Ok(false) => Err(()), Err(e) => { log::error!("API error in verify_nonce: {:?}", e); Err(()) } } - } -} \ No newline at end of file +} diff --git a/dilithium-crypto/hdwallet/src/lib.rs b/dilithium-crypto/hdwallet/src/lib.rs index 11701eca..ff9b5730 100644 --- a/dilithium-crypto/hdwallet/src/lib.rs +++ b/dilithium-crypto/hdwallet/src/lib.rs @@ -1,6 +1,9 @@ #![no_std] -use rusty_crystals_dilithium::{ml_dsa_87::{Keypair, SecretKey, PublicKey}, params::SEEDBYTES}; +use rusty_crystals_dilithium::{ + ml_dsa_87::{Keypair, PublicKey, SecretKey}, + params::SEEDBYTES, +}; pub fn generate(entropy: Option<&[u8]>) -> Result { if entropy.is_some() && entropy.unwrap().len() < SEEDBYTES { @@ -12,11 +15,7 @@ pub fn generate(entropy: Option<&[u8]>) -> Result { pub fn create_keypair(public_key: &[u8], secret_key: &[u8]) -> Result { let keypair = Keypair { secret: SecretKey::from_bytes(secret_key), - public: PublicKey::from_bytes(public_key) + public: PublicKey::from_bytes(public_key), }; Ok(keypair) } - - - - diff --git a/dilithium-crypto/sign/src/lib.rs b/dilithium-crypto/sign/src/lib.rs index 200ac07b..70b5e082 100644 --- a/dilithium-crypto/sign/src/lib.rs +++ b/dilithium-crypto/sign/src/lib.rs @@ -1,4 +1,3 @@ - // Option D // use dilithium_crypto::ml_dsa_87; diff --git a/dilithium-crypto/src/crypto.rs b/dilithium-crypto/src/crypto.rs index 38a17b8b..c1a23dcd 100644 --- a/dilithium-crypto/src/crypto.rs +++ b/dilithium-crypto/src/crypto.rs @@ -2,4 +2,4 @@ use rusty_crystals_dilithium::ml_dsa_87; pub const PUB_KEY_BYTES: usize = ml_dsa_87::PUBLICKEYBYTES; pub const SECRET_KEY_BYTES: usize = ml_dsa_87::SECRETKEYBYTES; -pub const SIGNATURE_BYTES: usize = ml_dsa_87::SIGNBYTES; \ No newline at end of file +pub const SIGNATURE_BYTES: usize = ml_dsa_87::SIGNBYTES; diff --git a/dilithium-crypto/src/lib.rs b/dilithium-crypto/src/lib.rs index 45d7f7c1..e0a1f8f9 100644 --- a/dilithium-crypto/src/lib.rs +++ b/dilithium-crypto/src/lib.rs @@ -1,10 +1,13 @@ #![no_std] pub mod crypto; -pub mod types; -pub mod traits; pub mod pair; +pub mod traits; +pub mod types; -pub use types::{ResonancePublic, ResonanceSignature, ResonanceSignatureWithPublic, ResonancePair, ResonanceSignatureScheme, ResonanceSigner, WrappedPublicBytes, WrappedSignatureBytes}; pub use crypto::{PUB_KEY_BYTES, SECRET_KEY_BYTES, SIGNATURE_BYTES}; -pub use pair::{crystal_alice, dilithium_bob, crystal_charlie}; \ No newline at end of file +pub use pair::{crystal_alice, crystal_charlie, dilithium_bob}; +pub use types::{ + ResonancePair, ResonancePublic, ResonanceSignature, ResonanceSignatureScheme, + ResonanceSignatureWithPublic, ResonanceSigner, WrappedPublicBytes, WrappedSignatureBytes, +}; diff --git a/dilithium-crypto/src/pair.rs b/dilithium-crypto/src/pair.rs index 5de4e1a7..153df1cb 100644 --- a/dilithium-crypto/src/pair.rs +++ b/dilithium-crypto/src/pair.rs @@ -2,9 +2,13 @@ use crate::{ResonanceSignatureScheme, ResonanceSignatureWithPublic, ResonanceSig use super::types::{ResonancePair, ResonancePublic}; use sp_core::{ - crypto::{DeriveError, DeriveJunction, SecretStringError}, ByteArray, Pair + crypto::{DeriveError, DeriveJunction, SecretStringError}, + ByteArray, Pair, +}; +use sp_runtime::{ + traits::{IdentifyAccount, Verify}, + AccountId32, }; -use sp_runtime::{traits::{IdentifyAccount, Verify}, AccountId32}; use sp_std::vec::Vec; pub fn crystal_alice() -> ResonancePair { @@ -23,11 +27,10 @@ pub fn crystal_charlie() -> ResonancePair { impl IdentifyAccount for ResonancePair { type AccountId = AccountId32; fn into_account(self) -> AccountId32 { - self.public().into_account() + self.public().into_account() } } - impl Pair for ResonancePair { type Public = ResonancePublic; type Seed = Vec; @@ -51,20 +54,25 @@ impl Pair for ResonancePair { // Create keypair struct use crate::types::ResonanceSignature; - let keypair = hdwallet::create_keypair(&self.public, &self.secret).expect("Failed to create keypair"); + let keypair = + hdwallet::create_keypair(&self.public, &self.secret).expect("Failed to create keypair"); // Sign the message let signature = keypair .sign(message, None, false) .expect("Signing should succeed"); - let signature = ResonanceSignature::try_from(signature.as_ref()).expect("Wrap doesn't fail"); - + let signature = + ResonanceSignature::try_from(signature.as_ref()).expect("Wrap doesn't fail"); ResonanceSignatureWithPublic::new(signature, self.public()) } - fn verify>(sig: &ResonanceSignatureWithPublic, message: M, pubkey: &ResonancePublic) -> bool { + fn verify>( + sig: &ResonanceSignatureWithPublic, + message: M, + pubkey: &ResonancePublic, + ) -> bool { let sig_scheme = ResonanceSignatureScheme::Resonance(sig.clone()); let signer = ResonanceSigner::Resonance(pubkey.clone()); sig_scheme.verify(message.as_ref(), &signer.into_account()) @@ -85,7 +93,6 @@ impl Pair for ResonancePair { } } - #[cfg(test)] mod tests { use sp_std::vec; @@ -106,7 +113,7 @@ mod tests { let pair = ResonancePair::from_seed_slice(&seed).expect("Failed to create pair"); let message: Vec = b"Hello, world!".to_vec(); - + log::info!("Signing message: {:?}", &message[..10]); let signature = pair.sign(&message); @@ -120,7 +127,7 @@ mod tests { // let keypair = hdwallet::generate(Some(&seed)).expect("Failed to generate keypair"); // let sig_bytes = keypair.sign(&message, None, false).expect("Signing failed"); // assert_eq!(signature.as_ref(), sig_bytes, "Signatures should match"); - + let public = pair.public(); let result = ResonancePair::verify(&signature, message, &public); @@ -134,10 +141,10 @@ mod tests { let pair = ResonancePair::from_seed(&seed).expect("Failed to create pair"); let message = b"Hello, world!"; let wrong_message = b"Goodbye, world!"; - + let signature = pair.sign(message); let public = pair.public(); - + assert!( !ResonancePair::verify(&signature, wrong_message, &public), "Signature should not verify with wrong message" @@ -149,16 +156,17 @@ mod tests { let seed = [0u8; 32]; let pair = ResonancePair::from_seed(&seed).expect("Failed to create pair"); let message = b"Hello, world!"; - + let mut signature = pair.sign(message); let signature_bytes = signature.as_mut(); // Corrupt the signature by flipping a bit if let Some(byte) = signature_bytes.get_mut(0) { *byte ^= 1; } - let false_signature = ResonanceSignatureWithPublic::from_slice(signature_bytes).expect("Failed to create signature"); + let false_signature = ResonanceSignatureWithPublic::from_slice(signature_bytes) + .expect("Failed to create signature"); let public = pair.public(); - + assert!( !ResonancePair::verify(&false_signature, message, &public), "Corrupted signature should not verify" @@ -171,11 +179,14 @@ mod tests { let seed2 = vec![1u8; 32]; let pair1 = ResonancePair::from_seed(&seed1).expect("Failed to create pair"); let pair2 = ResonancePair::from_seed(&seed2).expect("Failed to create pair"); - + let pub1 = pair1.public(); let pub2 = pair2.public(); - - assert_ne!(pub1.as_ref(), pub2.as_ref(), "Different seeds should produce different public keys"); + + assert_ne!( + pub1.as_ref(), + pub2.as_ref(), + "Different seeds should produce different public keys" + ); } } - diff --git a/dilithium-crypto/src/traits.rs b/dilithium-crypto/src/traits.rs index b3946ad7..103fc24d 100644 --- a/dilithium-crypto/src/traits.rs +++ b/dilithium-crypto/src/traits.rs @@ -1,40 +1,58 @@ use super::types::{ - ResonancePublic, Error, WrappedPublicBytes, WrappedSignatureBytes, - ResonancePair, ResonanceSignatureScheme, ResonanceSigner + Error, ResonancePair, ResonancePublic, ResonanceSignatureScheme, ResonanceSigner, + WrappedPublicBytes, WrappedSignatureBytes, }; -use sp_core::{ByteArray, crypto::{Derive, Signature, Public, PublicBytes, SignatureBytes}}; -use sp_runtime::{AccountId32, CryptoType, traits::{IdentifyAccount, Verify}}; -use sp_std::vec::Vec; +use crate::{ResonanceSignature, ResonanceSignatureWithPublic}; +use poseidon_resonance::PoseidonHasher; +use sp_core::{ + crypto::{Derive, Public, PublicBytes, Signature, SignatureBytes}, + ByteArray, +}; use sp_core::{ecdsa, ed25519, sr25519}; use sp_runtime::traits::Hash; +use sp_runtime::{ + traits::{IdentifyAccount, Verify}, + AccountId32, CryptoType, +}; +use sp_std::vec::Vec; use verify::verify; -use poseidon_resonance::PoseidonHasher; -use crate::{ResonanceSignature, ResonanceSignatureWithPublic}; // // WrappedPublicBytes -// +// impl Derive for WrappedPublicBytes {} impl AsMut<[u8]> for WrappedPublicBytes { - fn as_mut(&mut self) -> &mut [u8] { self.0.as_mut() } + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } } impl AsRef<[u8]> for WrappedPublicBytes { - fn as_ref(&self) -> &[u8] { self.0.as_slice() } + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } } impl TryFrom<&[u8]> for WrappedPublicBytes { type Error = (); fn try_from(data: &[u8]) -> Result { - PublicBytes::from_slice(data).map(|bytes| WrappedPublicBytes(bytes)).map_err(|_| ()) + PublicBytes::from_slice(data) + .map(|bytes| WrappedPublicBytes(bytes)) + .map_err(|_| ()) } } impl ByteArray for WrappedPublicBytes { - fn as_slice(&self) -> &[u8] { self.0.as_slice() } + fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } const LEN: usize = N; fn from_slice(data: &[u8]) -> Result { - PublicBytes::from_slice(data).map(|bytes| WrappedPublicBytes(bytes)).map_err(|_| ()) + PublicBytes::from_slice(data) + .map(|bytes| WrappedPublicBytes(bytes)) + .map_err(|_| ()) + } + fn to_raw_vec(&self) -> Vec { + self.0.as_slice().to_vec() } - fn to_raw_vec(&self) -> Vec { self.0.as_slice().to_vec() } } impl CryptoType for WrappedPublicBytes { type Pair = ResonancePair; @@ -49,7 +67,11 @@ impl Default for WrappedPublicBytes { impl sp_std::fmt::Debug for WrappedPublicBytes { #[cfg(feature = "std")] fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "{}", sp_core::hexdisplay::HexDisplay::from(&self.0.as_ref())) + write!( + f, + "{}", + sp_core::hexdisplay::HexDisplay::from(&self.0.as_ref()) + ) } #[cfg(not(feature = "std"))] @@ -65,29 +87,41 @@ impl IdentifyAccount for ResonancePublic { } } -// +// // WrappedSignatureBytes -// +// impl Derive for WrappedSignatureBytes {} impl AsMut<[u8]> for WrappedSignatureBytes { - fn as_mut(&mut self) -> &mut [u8] { self.0.as_mut() } + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut() + } } impl AsRef<[u8]> for WrappedSignatureBytes { - fn as_ref(&self) -> &[u8] { self.0.as_slice() } + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } } impl TryFrom<&[u8]> for WrappedSignatureBytes { type Error = (); fn try_from(data: &[u8]) -> Result { - SignatureBytes::from_slice(data).map(|bytes| WrappedSignatureBytes(bytes)).map_err(|_| ()) + SignatureBytes::from_slice(data) + .map(|bytes| WrappedSignatureBytes(bytes)) + .map_err(|_| ()) } } impl ByteArray for WrappedSignatureBytes { - fn as_slice(&self) -> &[u8] { self.0.as_slice() } + fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } const LEN: usize = N; fn from_slice(data: &[u8]) -> Result { - SignatureBytes::from_slice(data).map(|bytes| WrappedSignatureBytes(bytes)).map_err(|_| ()) + SignatureBytes::from_slice(data) + .map(|bytes| WrappedSignatureBytes(bytes)) + .map_err(|_| ()) + } + fn to_raw_vec(&self) -> Vec { + self.0.as_slice().to_vec() } - fn to_raw_vec(&self) -> Vec { self.0.as_slice().to_vec() } } impl CryptoType for WrappedSignatureBytes { type Pair = ResonancePair; @@ -103,7 +137,11 @@ impl Default for WrappedSignatureBytes { impl sp_std::fmt::Debug for WrappedSignatureBytes { #[cfg(feature = "std")] fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "{}", sp_core::hexdisplay::HexDisplay::from(&self.0.as_ref())) + write!( + f, + "{}", + sp_core::hexdisplay::HexDisplay::from(&self.0.as_ref()) + ) } #[cfg(not(feature = "std"))] @@ -126,7 +164,11 @@ impl From for ResonanceSignatureScheme { impl TryFrom for ed25519::Signature { type Error = (); fn try_from(m: ResonanceSignatureScheme) -> Result { - if let ResonanceSignatureScheme::Ed25519(x) = m { Ok(x) } else { Err(()) } + if let ResonanceSignatureScheme::Ed25519(x) = m { + Ok(x) + } else { + Err(()) + } } } @@ -139,7 +181,11 @@ impl From for ResonanceSignatureScheme { impl TryFrom for sr25519::Signature { type Error = (); fn try_from(m: ResonanceSignatureScheme) -> Result { - if let ResonanceSignatureScheme::Sr25519(x) = m { Ok(x) } else { Err(()) } + if let ResonanceSignatureScheme::Sr25519(x) = m { + Ok(x) + } else { + Err(()) + } } } @@ -152,7 +198,11 @@ impl From for ResonanceSignatureScheme { impl TryFrom for ecdsa::Signature { type Error = (); fn try_from(m: ResonanceSignatureScheme) -> Result { - if let ResonanceSignatureScheme::Ecdsa(x) = m { Ok(x) } else { Err(()) } + if let ResonanceSignatureScheme::Ecdsa(x) = m { + Ok(x) + } else { + Err(()) + } } } @@ -168,24 +218,33 @@ impl Verify for ResonanceSignatureScheme { Self::Ed25519(sig) => { let pk = ed25519::Public::from_slice(signer.as_ref()).unwrap_or_default(); sig.verify(msg, &pk) - }, + } Self::Sr25519(sig) => { let pk = sr25519::Public::from_slice(signer.as_ref()).unwrap_or_default(); sig.verify(msg, &pk) - }, + } Self::Ecdsa(sig) => { let m = sp_io::hashing::blake2_256(msg.get()); - sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m).is_ok_and(|pubkey| sp_io::hashing::blake2_256(&pubkey) == >::as_ref(signer)) - }, + sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m).is_ok_and( + |pubkey| { + sp_io::hashing::blake2_256(&pubkey) + == >::as_ref(signer) + }, + ) + } Self::Resonance(sig_public) => { let account = sig_public.public().clone().into_account(); if account != *signer { return false; } - let result = verify(sig_public.public().as_ref(), msg.get(), sig_public.signature().as_ref()); + let result = verify( + sig_public.public().as_ref(), + msg.get(), + sig_public.signature().as_ref(), + ); result - }, + } } } } @@ -228,7 +287,7 @@ impl ResonancePair { let keypair = hdwallet::generate(Some(seed)).map_err(|_| Error::KeyGenerationFailed)?; Ok(ResonancePair { secret: keypair.secret.to_bytes(), - public: keypair.public.to_bytes() + public: keypair.public.to_bytes(), }) } pub fn public(&self) -> ResonancePublic { @@ -241,7 +300,9 @@ impl sp_std::fmt::Debug for ResonanceSignatureWithPublic { fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { write!( f, - "ResonanceSignatureWithPublic {{ signature: {:?}, public: {:?} }}", self.signature(), self.public() + "ResonanceSignatureWithPublic {{ signature: {:?}, public: {:?} }}", + self.signature(), + self.public() ) } @@ -263,7 +324,7 @@ impl TryFrom for ResonanceSignatureWithPublic { if let ResonanceSignatureScheme::Resonance(sig_public) = m { Ok(sig_public) } else { - Err(()) + Err(()) } } } @@ -279,7 +340,7 @@ impl TryFrom<&[u8]> for ResonanceSignatureWithPublic { let (sig_bytes, pub_bytes) = data.split_at(::LEN); Ok(Self::new( ResonanceSignature::from_slice(sig_bytes)?, - ResonancePublic::from_slice(pub_bytes)? + ResonancePublic::from_slice(pub_bytes)?, )) } } diff --git a/dilithium-crypto/src/types.rs b/dilithium-crypto/src/types.rs index de42e679..2fbce382 100644 --- a/dilithium-crypto/src/types.rs +++ b/dilithium-crypto/src/types.rs @@ -1,20 +1,22 @@ - use codec::{Decode, Encode, MaxEncodedLen}; use rusty_crystals_dilithium::ml_dsa_87::{PUBLICKEYBYTES, SECRETKEYBYTES}; use scale_info::TypeInfo; -use sp_core::{crypto::{PublicBytes, SignatureBytes}, ByteArray, RuntimeDebug}; -use sp_core::{ecdsa, ed25519, sr25519}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use sp_core::{ + crypto::{PublicBytes, SignatureBytes}, + ByteArray, RuntimeDebug, +}; +use sp_core::{ecdsa, ed25519, sr25519}; use thiserror::Error; /// /// Resonance Crypto Types -/// +/// /// Currently implementing the Dilithum cryprographic scheme for post quantum security -/// +/// /// It is modeled after the Substrate MultiSignature and Signature types such as sr25519. -/// +/// /// For traits implemented see traits.rs /// @@ -22,12 +24,11 @@ use thiserror::Error; pub struct ResonanceCryptoTag; // TODO: Review if we even need Pair - we need some sort of pair trait in order to satisfy crypto bytes -// which is one of the wrapped public key types. But I am not sure we need that either. +// which is one of the wrapped public key types. But I am not sure we need that either. #[derive(Clone, Eq, PartialEq, Debug)] pub struct ResonancePair { pub secret: [u8; SECRETKEYBYTES], - pub public: [u8; PUBLICKEYBYTES] - + pub public: [u8; PUBLICKEYBYTES], } impl Default for ResonancePair { @@ -43,8 +44,9 @@ pub struct WrappedPublicBytes(pub PublicBytes #[derive(Clone, Eq, PartialEq, Hash, Encode, Decode, TypeInfo, MaxEncodedLen, Ord, PartialOrd)] pub struct WrappedSignatureBytes(pub SignatureBytes); -pub type ResonancePublic = WrappedPublicBytes<{super::crypto::PUB_KEY_BYTES}, ResonanceCryptoTag>; -pub type ResonanceSignature = WrappedSignatureBytes<{super::crypto::SIGNATURE_BYTES}, ResonanceCryptoTag>; +pub type ResonancePublic = WrappedPublicBytes<{ super::crypto::PUB_KEY_BYTES }, ResonanceCryptoTag>; +pub type ResonanceSignature = + WrappedSignatureBytes<{ super::crypto::SIGNATURE_BYTES }, ResonanceCryptoTag>; // ResonanceSignatureScheme drop-in replacement for MultiSignature #[derive(Eq, PartialEq, Clone, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] @@ -53,7 +55,7 @@ pub enum ResonanceSignatureScheme { Ed25519(ed25519::Signature), Sr25519(sr25519::Signature), Ecdsa(ecdsa::Signature), - Resonance(ResonanceSignatureWithPublic) + Resonance(ResonanceSignatureWithPublic), } // Replacement for MultiSigner @@ -88,9 +90,7 @@ impl ResonanceSignatureWithPublic { let mut bytes = [0u8; Self::LEN]; bytes[..Self::SIGNATURE_LEN].copy_from_slice(signature.as_ref()); bytes[Self::SIGNATURE_LEN..].copy_from_slice(public.as_ref()); - Self { - bytes, - } + Self { bytes } } pub fn signature(&self) -> ResonanceSignature { @@ -99,8 +99,7 @@ impl ResonanceSignatureWithPublic { } pub fn public(&self) -> ResonancePublic { - ResonancePublic::from_slice(&self.bytes[Self::SIGNATURE_LEN..]) - .expect("Invalid public key") + ResonancePublic::from_slice(&self.bytes[Self::SIGNATURE_LEN..]).expect("Invalid public key") } pub fn to_bytes(&self) -> [u8; Self::TOTAL_LEN] { @@ -111,13 +110,12 @@ impl ResonanceSignatureWithPublic { if bytes.len() != Self::TOTAL_LEN { return Err(Error::InvalidLength); } - + let signature = ResonanceSignature::from_slice(&bytes[..Self::SIGNATURE_LEN]) .map_err(|_| Error::InvalidLength)?; let public = ResonancePublic::from_slice(&bytes[Self::SIGNATURE_LEN..]) .map_err(|_| Error::InvalidLength)?; - + Ok(Self::new(signature, public)) } } - diff --git a/dilithium-crypto/verify/src/lib.rs b/dilithium-crypto/verify/src/lib.rs index dbe2b3ca..7f176286 100644 --- a/dilithium-crypto/verify/src/lib.rs +++ b/dilithium-crypto/verify/src/lib.rs @@ -5,4 +5,4 @@ use rusty_crystals_dilithium::ml_dsa_87::PublicKey; pub fn verify(pub_key: &[u8], msg: &[u8], sig: &[u8]) -> bool { let pk = PublicKey::from_bytes(pub_key); pk.verify(msg, sig, None) -} \ No newline at end of file +} diff --git a/external-miner/src/lib.rs b/external-miner/src/lib.rs index 13638fb0..de8c082a 100644 --- a/external-miner/src/lib.rs +++ b/external-miner/src/lib.rs @@ -1,14 +1,14 @@ // external-miner/src/lib.rs +use codec::{Decode, Encode}; +use primitive_types::U512; +use qpow_math::is_valid_nonce; +use resonance_miner_api::*; use std::collections::HashMap; use std::sync::Arc; +use std::time::Instant; use tokio::sync::Mutex; -use primitive_types::U512; -use codec::{Encode, Decode}; use warp::{Rejection, Reply}; -use qpow_math::is_valid_nonce; -use std::time::Instant; -use resonance_miner_api::*; #[derive(Debug, Clone, Encode, Decode)] pub struct QPoWSeal { @@ -110,12 +110,13 @@ impl MiningState { } // Check if the *new* current_nonce has exceeded the range *before* hashing - if job.current_nonce > job.nonce_end { // Use > comparison now + if job.current_nonce > job.nonce_end { + // Use > comparison now job.status = JobStatus::Failed; log::info!( "Job {} failed (exceeded nonce_end {}). Hashes: {}, Time: {:?}", job_id, - job.nonce_end, // Log the boundary + job.nonce_end, // Log the boundary job.hash_count, // Hash count is before this failed attempt job.start_time.elapsed() ); @@ -138,7 +139,7 @@ impl MiningState { job.hash_count, job.start_time.elapsed() ); - } else if job.current_nonce == job.nonce_end { + } else if job.current_nonce == job.nonce_end { // If it wasn't valid and we are at the end, mark as failed job.status = JobStatus::Failed; log::info!( @@ -152,8 +153,9 @@ impl MiningState { } } drop(jobs_guard); // Release the lock before sleeping - // Adjust sleep time based on performance/CPU usage goals - tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; // Reduced sleep time + // Adjust sleep time based on performance/CPU usage goals + tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; + // Reduced sleep time } }); } @@ -175,7 +177,9 @@ pub fn validate_mining_request(request: &MiningRequest) -> Result<(), String> { if request.nonce_end.len() != 128 || U512::from_str_radix(&request.nonce_end, 16).is_err() { return Err("Invalid nonce_end (must be 128 hex characters)".to_string()); } - if U512::from_str_radix(&request.nonce_start, 16).unwrap() > U512::from_str_radix(&request.nonce_end, 16).unwrap() { + if U512::from_str_radix(&request.nonce_start, 16).unwrap() + > U512::from_str_radix(&request.nonce_end, 16).unwrap() + { return Err("nonce_start cannot be greater than nonce_end".to_string()); } Ok(()) @@ -209,12 +213,7 @@ pub async fn handle_mine_request( let nonce_start = U512::from_str_radix(&request.nonce_start, 16).unwrap(); let nonce_end = U512::from_str_radix(&request.nonce_end, 16).unwrap(); - let job = MiningJob::new( - header_hash, - distance_threshold, - nonce_start, - nonce_end, - ); + let job = MiningJob::new(header_hash, distance_threshold, nonce_start, nonce_end); match state.add_job(request.job_id.clone(), job).await { Ok(_) => { @@ -247,7 +246,7 @@ pub async fn handle_result_request( job_id: String, state: MiningState, ) -> Result { - log::debug!("Received result request for job ID: {}", job_id); + log::debug!("Received result request for job ID: {}", job_id); if let Some(job) = state.get_job(&job_id).await { let api_status = match job.status { JobStatus::Running => ApiResponseStatus::Running, @@ -292,10 +291,10 @@ pub async fn handle_cancel_request( job_id: String, state: MiningState, ) -> Result { - log::debug!("Received cancel request for job ID: {}", job_id); + log::debug!("Received cancel request for job ID: {}", job_id); // Removing the job effectively cancels it if state.remove_job(&job_id).await.is_some() { - log::info!("Cancelled job ID: {}", job_id); + log::info!("Cancelled job ID: {}", job_id); Ok(warp::reply::with_status( warp::reply::json(&MiningResponse { status: ApiResponseStatus::Cancelled, @@ -323,8 +322,8 @@ pub async fn handle_cancel_request( #[cfg(test)] mod tests { use super::*; - use tokio::time::{sleep, Duration}; use std::time::Instant; + use tokio::time::{sleep, Duration}; // --- Keep existing tests --- #[test] @@ -341,43 +340,61 @@ mod tests { // Test empty job ID let invalid_request_job_id = MiningRequest { - job_id: "".to_string(), ..valid_request.clone() }; + job_id: "".to_string(), + ..valid_request.clone() + }; assert!(validate_mining_request(&invalid_request_job_id).is_err()); // Test invalid mining hash length let invalid_request_hash = MiningRequest { - mining_hash: "a".repeat(63), ..valid_request.clone() }; + mining_hash: "a".repeat(63), + ..valid_request.clone() + }; assert!(validate_mining_request(&invalid_request_hash).is_err()); - let invalid_request_hash_hex = MiningRequest { - mining_hash: "g".repeat(64), ..valid_request.clone() }; // Not hex + let invalid_request_hash_hex = MiningRequest { + mining_hash: "g".repeat(64), + ..valid_request.clone() + }; // Not hex assert!(validate_mining_request(&invalid_request_hash_hex).is_err()); - // Test invalid distance_threshold let invalid_request_diff = MiningRequest { - distance_threshold: "not_a_number".to_string(), ..valid_request.clone() }; + distance_threshold: "not_a_number".to_string(), + ..valid_request.clone() + }; assert!(validate_mining_request(&invalid_request_diff).is_err()); // Test invalid nonce length let invalid_request_nonce_start_len = MiningRequest { - nonce_start: "0".repeat(127), ..valid_request.clone() }; + nonce_start: "0".repeat(127), + ..valid_request.clone() + }; assert!(validate_mining_request(&invalid_request_nonce_start_len).is_err()); - let invalid_request_nonce_end_len = MiningRequest { - nonce_end: "f".repeat(127), ..valid_request.clone() }; + let invalid_request_nonce_end_len = MiningRequest { + nonce_end: "f".repeat(127), + ..valid_request.clone() + }; assert!(validate_mining_request(&invalid_request_nonce_end_len).is_err()); - // Test invalid nonce hex - let invalid_request_nonce_start_hex = MiningRequest { - nonce_start: "g".repeat(128), ..valid_request.clone() }; - assert!(validate_mining_request(&invalid_request_nonce_start_hex).is_err()); - let invalid_request_nonce_end_hex = MiningRequest { - nonce_end: "g".repeat(128), ..valid_request.clone() }; - assert!(validate_mining_request(&invalid_request_nonce_end_hex).is_err()); - - // Test nonce_start > nonce_end - let invalid_request_nonce_order = MiningRequest { - nonce_start: "1".repeat(128), nonce_end: "0".repeat(128), ..valid_request.clone() }; - assert!(validate_mining_request(&invalid_request_nonce_order).is_err()); + // Test invalid nonce hex + let invalid_request_nonce_start_hex = MiningRequest { + nonce_start: "g".repeat(128), + ..valid_request.clone() + }; + assert!(validate_mining_request(&invalid_request_nonce_start_hex).is_err()); + let invalid_request_nonce_end_hex = MiningRequest { + nonce_end: "g".repeat(128), + ..valid_request.clone() + }; + assert!(validate_mining_request(&invalid_request_nonce_end_hex).is_err()); + + // Test nonce_start > nonce_end + let invalid_request_nonce_order = MiningRequest { + nonce_start: "1".repeat(128), + nonce_end: "0".repeat(128), + ..valid_request.clone() + }; + assert!(validate_mining_request(&invalid_request_nonce_order).is_err()); } #[tokio::test] @@ -398,7 +415,10 @@ mod tests { assert!(state.add_job("test".to_string(), job.clone()).await.is_ok()); // Test adding duplicate job - assert!(state.add_job("test".to_string(), job.clone()).await.is_err()); + assert!(state + .add_job("test".to_string(), job.clone()) + .await + .is_err()); // Test getting a job let retrieved_job = state.get_job("test").await; @@ -431,9 +451,7 @@ mod tests { hash_count: 0, start_time: std::time::Instant::now(), }; - let handle = tokio::spawn(async move { - state.add_job(format!("job{}", i), job).await - }); + let handle = tokio::spawn(async move { state.add_job(format!("job{}", i), job).await }); handles.push(handle); } @@ -492,7 +510,7 @@ mod tests { state.start_mining_loop().await; // Start the actual loop let job = MiningJob { - header_hash: [1; 32], // Use a non-zero hash + header_hash: [1; 32], // Use a non-zero hash distance_threshold: U512::MAX, // Maximum threshold, which corresponds to the lowest mining difficulty nonce_start: U512::from(0), // Small nonce range to ensure it finishes if no solution found @@ -513,65 +531,99 @@ mod tests { let job = updated_job.unwrap(); // Check that the status is no longer "running" - assert_ne!(job.status, JobStatus::Running, "Job status should have changed from running"); + assert_ne!( + job.status, + JobStatus::Running, + "Job status should have changed from running" + ); // It could be "completed" or "failed" - assert!(job.status == JobStatus::Completed || job.status == JobStatus::Failed, "Job status should be completed or failed"); - assert!(job.hash_count > 0, "Should have attempted at least one hash"); - println!("Mining loop test final status: {:?}, hash_count: {}", job.status, job.hash_count); + assert!( + job.status == JobStatus::Completed || job.status == JobStatus::Failed, + "Job status should be completed or failed" + ); + assert!( + job.hash_count > 0, + "Should have attempted at least one hash" + ); + println!( + "Mining loop test final status: {:?}, hash_count: {}", + job.status, job.hash_count + ); } // Keep concurrent mining test, focusing on adding jobs and seeing status change - #[tokio::test] - async fn test_concurrent_mining_status_change() { - let state = MiningState::new(); - state.start_mining_loop().await; - - let mut handles = vec![]; - let num_jobs = 5; - - for i in 0..num_jobs { - let state = state.clone(); - let job = MiningJob { - header_hash: [i as u8; 32], // Different hash per job - distance_threshold: U512::MAX, // Low difficulty - nonce_start: U512::from(0), - nonce_end: U512::from(500), // Small range - current_nonce: U512::from(0), - status: JobStatus::Running, - hash_count: 0, - start_time: Instant::now(), - }; - let handle = tokio::spawn(async move { - state.add_job(format!("job{}", i), job).await - }); - handles.push(handle); - } - - // Wait for all jobs to be added - for handle in handles { - assert!(handle.await.unwrap().is_ok()); - } - - // Wait for mining loop to process - sleep(Duration::from_millis(500)).await; // Increase wait time slightly - - // Verify all jobs were processed (status changed) - let mut completed_count = 0; - let mut failed_count = 0; - for i in 0..num_jobs { - let job_id = format!("job{}", i); - let job_opt = state.get_job(&job_id).await; - assert!(job_opt.is_some(), "Job {} should exist", job_id); - let job = job_opt.unwrap(); - assert_ne!(job.status, JobStatus::Running, "Job {} status should have changed", job_id); - assert!(job.status == JobStatus::Completed || job.status == JobStatus::Failed, "Job {} status invalid", job_id); - if job.status == JobStatus::Completed { completed_count += 1; } - if job.status == JobStatus::Failed { failed_count += 1; } - println!("Concurrent test - Job {}: Status={:?}, Hashes={}", job_id, job.status, job.hash_count); - } - println!("Concurrent test results: Completed={}, Failed={}", completed_count, failed_count); - assert_eq!(completed_count + failed_count, num_jobs, "All jobs should be either completed or failed"); - } + #[tokio::test] + async fn test_concurrent_mining_status_change() { + let state = MiningState::new(); + state.start_mining_loop().await; + + let mut handles = vec![]; + let num_jobs = 5; + + for i in 0..num_jobs { + let state = state.clone(); + let job = MiningJob { + header_hash: [i as u8; 32], // Different hash per job + distance_threshold: U512::MAX, // Low difficulty + nonce_start: U512::from(0), + nonce_end: U512::from(500), // Small range + current_nonce: U512::from(0), + status: JobStatus::Running, + hash_count: 0, + start_time: Instant::now(), + }; + let handle = tokio::spawn(async move { state.add_job(format!("job{}", i), job).await }); + handles.push(handle); + } + + // Wait for all jobs to be added + for handle in handles { + assert!(handle.await.unwrap().is_ok()); + } + + // Wait for mining loop to process + sleep(Duration::from_millis(500)).await; // Increase wait time slightly + + // Verify all jobs were processed (status changed) + let mut completed_count = 0; + let mut failed_count = 0; + for i in 0..num_jobs { + let job_id = format!("job{}", i); + let job_opt = state.get_job(&job_id).await; + assert!(job_opt.is_some(), "Job {} should exist", job_id); + let job = job_opt.unwrap(); + assert_ne!( + job.status, + JobStatus::Running, + "Job {} status should have changed", + job_id + ); + assert!( + job.status == JobStatus::Completed || job.status == JobStatus::Failed, + "Job {} status invalid", + job_id + ); + if job.status == JobStatus::Completed { + completed_count += 1; + } + if job.status == JobStatus::Failed { + failed_count += 1; + } + println!( + "Concurrent test - Job {}: Status={:?}, Hashes={}", + job_id, job.status, job.hash_count + ); + } + println!( + "Concurrent test results: Completed={}, Failed={}", + completed_count, failed_count + ); + assert_eq!( + completed_count + failed_count, + num_jobs, + "All jobs should be either completed or failed" + ); + } // Remove the test_hash_and_difficulty_check test // #[test] @@ -619,7 +671,7 @@ mod tests { // It's possible it fails if no nonce works, but we hope for completed if result_job.status != JobStatus::Completed { - println!("WARN: test_mining_job_completes did not complete, ended as {:?}. This might be due to qpow_math complexity.", result_job.status); + println!("WARN: test_mining_job_completes did not complete, ended as {:?}. This might be due to qpow_math complexity.", result_job.status); } assert_ne!(result_job.status, JobStatus::Running); assert!(result_job.hash_count > 0); @@ -636,20 +688,33 @@ mod tests { let start_nonce = 10; // Start above 0 to avoid the nonce==0 edge case in is_valid_nonce let end_nonce = 20; - let (job_id, job) = create_test_job("fail_test", impossible_distance, start_nonce, end_nonce); + let (job_id, job) = + create_test_job("fail_test", impossible_distance, start_nonce, end_nonce); state.add_job(job_id.clone(), job).await.unwrap(); // Wait long enough for the loop to iterate through the small range sleep(Duration::from_millis(250)).await; // Increased sleep time let result_job = state.get_job(&job_id).await.unwrap(); - assert_eq!(result_job.status, JobStatus::Failed, "Job should have failed by reaching nonce_end"); - assert_eq!(result_job.current_nonce, U512::from(end_nonce), "Current nonce should be at nonce_end"); + assert_eq!( + result_job.status, + JobStatus::Failed, + "Job should have failed by reaching nonce_end" + ); + assert_eq!( + result_job.current_nonce, + U512::from(end_nonce), + "Current nonce should be at nonce_end" + ); // Expect hash count to be exactly (end_nonce - start_nonce + 1) nonces checked - assert_eq!(result_job.hash_count, (end_nonce - start_nonce + 1), "Hash count should match the range size"); + assert_eq!( + result_job.hash_count, + (end_nonce - start_nonce + 1), + "Hash count should match the range size" + ); } - #[tokio::test] + #[tokio::test] async fn test_mining_job_result_work_field() { // Similar to test_mining_job_completes, hoping it finds a solution. let state = MiningState::new(); @@ -663,7 +728,10 @@ mod tests { // Get the final job state directly instead of processing HTTP reply let final_job_state_opt = state.get_job(&job_id).await; - assert!(final_job_state_opt.is_some(), "Job should exist after attempting work"); + assert!( + final_job_state_opt.is_some(), + "Job should exist after attempting work" + ); let final_job_state = final_job_state_opt.unwrap(); // Construct the expected result based on final job state @@ -678,16 +746,37 @@ mod tests { if final_job_state.status == JobStatus::Completed { assert_eq!(expected_status, JobStatus::Completed); - assert!(expected_work_hex.is_some(), "Work field should be present for completed job"); + assert!( + expected_work_hex.is_some(), + "Work field should be present for completed job" + ); let expected_nonce_bytes = final_job_state.current_nonce.to_big_endian(); - assert_eq!(expected_work_hex, Some(hex::encode(expected_nonce_bytes)), "Work field should contain the hex of the winning nonce bytes"); - assert_eq!(expected_nonce_hex, Some(format!("{:x}", final_job_state.current_nonce)), "Nonce field should contain the U512 hex"); + assert_eq!( + expected_work_hex, + Some(hex::encode(expected_nonce_bytes)), + "Work field should contain the hex of the winning nonce bytes" + ); + assert_eq!( + expected_nonce_hex, + Some(format!("{:x}", final_job_state.current_nonce)), + "Nonce field should contain the U512 hex" + ); assert_eq!(expected_hash_count, final_job_state.hash_count); } else { - println!("WARN: test_mining_job_result_work_field did not complete, ended as {:?}.", final_job_state.status); + println!( + "WARN: test_mining_job_result_work_field did not complete, ended as {:?}.", + final_job_state.status + ); assert_ne!(expected_status, JobStatus::Running); - assert!(expected_work_hex.is_none(), "Work field should be None for non-completed job"); - assert_eq!(expected_nonce_hex, Some(format!("{:x}", final_job_state.current_nonce)), "Nonce field should contain the U512 hex even if failed"); + assert!( + expected_work_hex.is_none(), + "Work field should be None for non-completed job" + ); + assert_eq!( + expected_nonce_hex, + Some(format!("{:x}", final_job_state.current_nonce)), + "Nonce field should contain the U512 hex even if failed" + ); } } @@ -705,23 +794,30 @@ mod tests { // Get current hash count let job_before_cancel = state.get_job(&job_id).await; - assert!(job_before_cancel.is_some(), "Job should exist before cancel"); + assert!( + job_before_cancel.is_some(), + "Job should exist before cancel" + ); let hash_count_before = job_before_cancel.unwrap().hash_count; assert!(hash_count_before > 0, "Job should have started hashing"); - // Cancel the job by removing it let removed_job = state.remove_job(&job_id).await; assert!(removed_job.is_some(), "Job should be removed successfully"); - println!("Cancelled job {} after {} hashes", job_id, hash_count_before); - + println!( + "Cancelled job {} after {} hashes", + job_id, hash_count_before + ); // Wait a bit longer to ensure the loop iterates more sleep(Duration::from_millis(50)).await; // Try to get the job again - it should be gone let job_after_cancel = state.get_job(&job_id).await; - assert!(job_after_cancel.is_none(), "Job should not exist after cancel"); + assert!( + job_after_cancel.is_none(), + "Job should not exist after cancel" + ); // Check logs or potentially add internal counters (if needed) to be *absolutely* sure // the loop isn't still somehow processing the removed job ID, but removing it @@ -729,7 +825,7 @@ mod tests { // We rely on the loop checking the map each iteration. } - #[tokio::test] + #[tokio::test] async fn test_mining_job_nonce_start_equals_end() { let state = MiningState::new(); state.start_mining_loop().await; @@ -737,7 +833,8 @@ mod tests { let nonce_value = 50; let distance = U512::one(); // Use impossible difficulty - let (job_id, job) = create_test_job("single_nonce_test", distance, nonce_value, nonce_value); + let (job_id, job) = + create_test_job("single_nonce_test", distance, nonce_value, nonce_value); state.add_job(job_id.clone(), job).await.unwrap(); // Wait just long enough for one check @@ -746,31 +843,47 @@ mod tests { let result_job = state.get_job(&job_id).await.unwrap(); // Since difficulty is impossible, it must fail - assert_eq!(result_job.status, JobStatus::Failed, "Job should fail as the single nonce is invalid"); + assert_eq!( + result_job.status, + JobStatus::Failed, + "Job should fail as the single nonce is invalid" + ); // Hash count should be exactly 1 because only one nonce was checked - assert_eq!(result_job.hash_count, 1, "Should have checked exactly one nonce"); + assert_eq!( + result_job.hash_count, 1, + "Should have checked exactly one nonce" + ); assert_eq!(result_job.current_nonce, U512::from(nonce_value)); - // --- Test completion case for single nonce --- - let state_complete = MiningState::new(); - state_complete.start_mining_loop().await; - let difficulty_easy = U512::one(); // Easy difficulty - let (job_id_c, job_c) = create_test_job("single_nonce_complete", difficulty_easy, nonce_value, nonce_value); - state_complete.add_job(job_id_c.clone(), job_c).await.unwrap(); - - sleep(Duration::from_millis(50)).await; - let result_job_c = state_complete.get_job(&job_id_c).await.unwrap(); - - // It should be completed or failed, status should not be running - assert_ne!(result_job_c.status, JobStatus::Running); - assert_eq!(result_job_c.hash_count, 1, "Should have checked exactly one nonce"); - assert_eq!(result_job_c.current_nonce, U512::from(nonce_value)); - if result_job_c.status == JobStatus::Completed { - println!("Single nonce test completed successfully."); - } else { - println!("WARN: Single nonce test failed (status={:?}). qpow_math might not accept nonce {}.", result_job_c.status, nonce_value); - } + // --- Test completion case for single nonce --- + let state_complete = MiningState::new(); + state_complete.start_mining_loop().await; + let difficulty_easy = U512::one(); // Easy difficulty + let (job_id_c, job_c) = create_test_job( + "single_nonce_complete", + difficulty_easy, + nonce_value, + nonce_value, + ); + state_complete + .add_job(job_id_c.clone(), job_c) + .await + .unwrap(); + sleep(Duration::from_millis(50)).await; + let result_job_c = state_complete.get_job(&job_id_c).await.unwrap(); + + // It should be completed or failed, status should not be running + assert_ne!(result_job_c.status, JobStatus::Running); + assert_eq!( + result_job_c.hash_count, 1, + "Should have checked exactly one nonce" + ); + assert_eq!(result_job_c.current_nonce, U512::from(nonce_value)); + if result_job_c.status == JobStatus::Completed { + println!("Single nonce test completed successfully."); + } else { + println!("WARN: Single nonce test failed (status={:?}). qpow_math might not accept nonce {}.", result_job_c.status, nonce_value); + } } - } diff --git a/external-miner/src/main.rs b/external-miner/src/main.rs index 35b0140c..86b1a9a5 100644 --- a/external-miner/src/main.rs +++ b/external-miner/src/main.rs @@ -1,8 +1,8 @@ -use warp::Filter; -use log::info; +use clap::Parser; use external_miner::*; // Import everything from lib.rs +use log::info; use std::net::SocketAddr; -use clap::Parser; +use warp::Filter; /// Resonance External Miner Service #[derive(Parser, Debug)] @@ -22,10 +22,10 @@ async fn main() { // Use MiningState from lib.rs let state = MiningState::new(); - // --- Start the mining loop --- + // --- Start the mining loop --- state.start_mining_loop().await; - // --- Set up Warp filters --- + // --- Set up Warp filters --- let state_clone = state.clone(); // Clone state for the filter closure let state_filter = warp::any().map(move || state_clone.clone()); diff --git a/external-miner/tests/integration_tests.rs b/external-miner/tests/integration_tests.rs index f9358397..3f712316 100644 --- a/external-miner/tests/integration_tests.rs +++ b/external-miner/tests/integration_tests.rs @@ -1,9 +1,9 @@ use external_miner::*; -use warp::test::request; -use warp::Filter; use primitive_types::U512; +use resonance_miner_api::*; use std::time::Instant; -use resonance_miner_api::*; // Import shared API types +use warp::test::request; +use warp::Filter; // Import shared API types #[tokio::test] async fn test_mine_endpoint() { @@ -46,7 +46,7 @@ async fn test_mine_endpoint() { .reply(&mine_route) .await; - assert_eq!(resp.status(), 409); + assert_eq!(resp.status(), 409); let body: MiningResponse = serde_json::from_slice(resp.body()).unwrap(); assert_eq!(body.status, ApiResponseStatus::Error); assert!(body.message.is_some()); @@ -85,7 +85,7 @@ async fn test_result_endpoint() { nonce_end: U512::from(1000), current_nonce: U512::from(0), status: JobStatus::Running, // Use enum variant - hash_count: 0, + hash_count: 0, start_time: Instant::now(), }; state.add_job("test".to_string(), job).await.unwrap(); @@ -134,7 +134,7 @@ async fn test_cancel_endpoint() { nonce_end: U512::from(1000), current_nonce: U512::from(0), status: JobStatus::Running, // Use enum variant - hash_count: 0, + hash_count: 0, start_time: Instant::now(), }; state.add_job("test".to_string(), job).await.unwrap(); @@ -187,7 +187,7 @@ async fn test_concurrent_access() { nonce_end: U512::from(1000), current_nonce: U512::from(0), status: JobStatus::Running, // Use enum variant - hash_count: 0, + hash_count: 0, start_time: Instant::now(), }; state.add_job(format!("test{}", i), job).await @@ -232,4 +232,4 @@ async fn test_concurrent_access() { let body: MiningResult = serde_json::from_slice(resp.body()).unwrap(); assert_eq!(body.status, ApiResponseStatus::Running); } -} \ No newline at end of file +} diff --git a/external-miner/tests/spec_tests.rs b/external-miner/tests/spec_tests.rs index 94e763d0..54ebe729 100644 --- a/external-miner/tests/spec_tests.rs +++ b/external-miner/tests/spec_tests.rs @@ -2,14 +2,17 @@ // Manually generated from the OpenAPI spec but it should still flag when we get out of sync and need to update. use external_miner::*; -use warp::test::request; -use warp::Filter; -use resonance_miner_api::*; // Use the shared API types use primitive_types::U512; -use serde_json::json; // For sending custom/invalid JSON +use resonance_miner_api::*; // Use the shared API types +use serde_json::json; +use warp::test::request; +use warp::Filter; // For sending custom/invalid JSON // Helper function to setup the routes with a fresh state for each test -fn setup_routes() -> (MiningState, impl Filter + Clone) { +fn setup_routes() -> ( + MiningState, + impl Filter + Clone, +) { let state = MiningState::new(); let state_clone = state.clone(); let state_filter = warp::any().map(move || state_clone.clone()); @@ -36,7 +39,7 @@ fn setup_routes() -> (MiningState, impl Filter= 0.0); + assert!(body.work.is_none()); // No work yet + // hash_count and elapsed_time should be present + // assert_eq!(body.hash_count, 0); // Initial state + // assert!(body.elapsed_time >= 0.0); } // Add tests for spec_result_job_completed, spec_result_job_failed (need to manipulate state) @@ -209,7 +246,8 @@ async fn spec_result_job_not_found() { // Spec: 404 Not Found, response matches MiningResultNotFound assert_eq!(resp.status(), 404); - let body: MiningResult = serde_json::from_slice(resp.body()).expect("Should deserialize to MiningResult (NotFound variant)"); + let body: MiningResult = serde_json::from_slice(resp.body()) + .expect("Should deserialize to MiningResult (NotFound variant)"); assert_eq!(body.status, ApiResponseStatus::NotFound); assert_eq!(body.job_id, job_id); assert!(body.nonce.is_none()); @@ -218,8 +256,7 @@ async fn spec_result_job_not_found() { assert_eq!(body.elapsed_time, 0.0); } - -// --- /cancel/{job_id} Endpoint Tests --- +// --- /cancel/{job_id} Endpoint Tests --- #[tokio::test] async fn spec_cancel_existing_job() { @@ -237,7 +274,8 @@ async fn spec_cancel_existing_job() { // Spec: 200 OK, response matches MiningResponseCancelled assert_eq!(resp.status(), 200); - let body: MiningResponse = serde_json::from_slice(resp.body()).expect("Should deserialize to MiningResponse"); + let body: MiningResponse = + serde_json::from_slice(resp.body()).expect("Should deserialize to MiningResponse"); assert_eq!(body.status, ApiResponseStatus::Cancelled); assert_eq!(body.job_id, job_id); assert!(body.message.is_none()); @@ -259,10 +297,11 @@ async fn spec_cancel_non_existent_job() { // Spec: 404 Not Found, response matches MiningResponseNotFound assert_eq!(resp.status(), 404); - let body: MiningResponse = serde_json::from_slice(resp.body()).expect("Should deserialize to MiningResponse (NotFound variant)"); + let body: MiningResponse = serde_json::from_slice(resp.body()) + .expect("Should deserialize to MiningResponse (NotFound variant)"); assert_eq!(body.status, ApiResponseStatus::NotFound); assert_eq!(body.job_id, job_id); assert!(body.message.is_none()); } -// Removed placeholder test \ No newline at end of file +// Removed placeholder test diff --git a/node/build.rs b/node/build.rs index e3bfe311..f9d839f9 100644 --- a/node/build.rs +++ b/node/build.rs @@ -1,7 +1,7 @@ use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; fn main() { - generate_cargo_keys(); + generate_cargo_keys(); - rerun_if_git_head_changed(); + rerun_if_git_head_changed(); } diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 043f9799..367c5d1a 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -1,78 +1,83 @@ +use resonance_runtime::genesis_config_presets::LIVE_TESTNET_RUNTIME_PRESET; use resonance_runtime::WASM_BINARY; use sc_service::{ChainType, Properties}; use sc_telemetry::TelemetryEndpoints; use serde_json::json; -use resonance_runtime::genesis_config_presets::LIVE_TESTNET_RUNTIME_PRESET; /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. pub type ChainSpec = sc_service::GenericChainSpec; pub fn development_chain_spec() -> Result { + let mut properties = Properties::new(); + properties.insert("tokenDecimals".into(), json!(9)); + properties.insert("tokenSymbol".into(), json!("DEV")); - let mut properties = Properties::new(); - properties.insert("tokenDecimals".into(), json!(9)); - properties.insert("tokenSymbol".into(), json!("DEV")); - - Ok(ChainSpec::builder( - WASM_BINARY.ok_or_else(|| "Quantus DevNet wasm not available".to_string())?, - None, - ) - .with_name("Quantus DevNet") - .with_id("dev") - .with_protocol_id("quantus-devnet") - .with_chain_type(ChainType::Development) - .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) - .with_properties(properties) - .build()) + Ok(ChainSpec::builder( + WASM_BINARY.ok_or_else(|| "Quantus DevNet wasm not available".to_string())?, + None, + ) + .with_name("Quantus DevNet") + .with_id("dev") + .with_protocol_id("quantus-devnet") + .with_chain_type(ChainType::Development) + .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) + .with_properties(properties) + .build()) } pub fn local_chain_spec() -> Result { + let mut properties = Properties::new(); + properties.insert("tokenDecimals".into(), json!(9)); + properties.insert("tokenSymbol".into(), json!("RESL")); - let mut properties = Properties::new(); - properties.insert("tokenDecimals".into(), json!(9)); - properties.insert("tokenSymbol".into(), json!("RESL")); - - Ok(ChainSpec::builder( - WASM_BINARY.ok_or_else(|| "Local Resonance wasm not available".to_string())?, - None, - ) - .with_name("Local Resonance") - .with_id("local_resonance") - .with_protocol_id("local-resonance") - .with_chain_type(ChainType::Local) - .with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) - .with_properties(properties) - .build()) + Ok(ChainSpec::builder( + WASM_BINARY.ok_or_else(|| "Local Resonance wasm not available".to_string())?, + None, + ) + .with_name("Local Resonance") + .with_id("local_resonance") + .with_protocol_id("local-resonance") + .with_chain_type(ChainType::Local) + .with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET) + .with_properties(properties) + .build()) } /// Configure a new chain spec for the live testnet. pub fn live_testnet_chain_spec() -> Result { + let mut properties = Properties::new(); + properties.insert("tokenDecimals".into(), json!(9)); + properties.insert("tokenSymbol".into(), json!("RES")); - let mut properties = Properties::new(); - properties.insert("tokenDecimals".into(), json!(9)); - properties.insert("tokenSymbol".into(), json!("RES")); - - let telemetry_endpoints = TelemetryEndpoints::new(vec![ - ("/dns/telemetry.res.fm/tcp/443/x-parity-wss/%2Fsubmit%2F".to_string(), 0) - ]).expect("Telemetry endpoints config is valid; qed"); + let telemetry_endpoints = TelemetryEndpoints::new(vec![( + "/dns/telemetry.res.fm/tcp/443/x-parity-wss/%2Fsubmit%2F".to_string(), + 0, + )]) + .expect("Telemetry endpoints config is valid; qed"); - let boot_nodes = vec![ - "/dns/a1.t.res.fm/tcp/30201/p2p/12D3KooWGmDZ95J13cggsv56mSepAj3WiVPR3foqqh728umZrhPr".parse().unwrap(), - "/dns/a2.t.res.fm/tcp/30203/p2p/12D3KooWPPv8nrVEN5mjcMruDnAEdcpfppSfSbij2A7FXWNGt8JL".parse().unwrap(), - "/dns/a3.t.res.fm/tcp/30202/p2p/12D3KooWMpmEQmCB31Dz84YdnxL48aiSFQydEiq5MZv6VtZouXRd".parse().unwrap(), - ]; + let boot_nodes = vec![ + "/dns/a1.t.res.fm/tcp/30201/p2p/12D3KooWGmDZ95J13cggsv56mSepAj3WiVPR3foqqh728umZrhPr" + .parse() + .unwrap(), + "/dns/a2.t.res.fm/tcp/30203/p2p/12D3KooWPPv8nrVEN5mjcMruDnAEdcpfppSfSbij2A7FXWNGt8JL" + .parse() + .unwrap(), + "/dns/a3.t.res.fm/tcp/30202/p2p/12D3KooWMpmEQmCB31Dz84YdnxL48aiSFQydEiq5MZv6VtZouXRd" + .parse() + .unwrap(), + ]; - Ok(ChainSpec::builder( - WASM_BINARY.ok_or_else(|| "Resonance wasm not available".to_string())?, - None, - ) - .with_name("Resonance") - .with_id("resonance") - .with_protocol_id("resonance") - .with_boot_nodes(boot_nodes) - .with_telemetry_endpoints(telemetry_endpoints) - .with_chain_type(ChainType::Live) - .with_genesis_config_preset_name(LIVE_TESTNET_RUNTIME_PRESET) - .with_properties(properties) - .build()) + Ok(ChainSpec::builder( + WASM_BINARY.ok_or_else(|| "Resonance wasm not available".to_string())?, + None, + ) + .with_name("Resonance") + .with_id("resonance") + .with_protocol_id("resonance") + .with_boot_nodes(boot_nodes) + .with_telemetry_endpoints(telemetry_endpoints) + .with_chain_type(ChainType::Live) + .with_genesis_config_preset_name(LIVE_TESTNET_RUNTIME_PRESET) + .with_properties(properties) + .build()) } diff --git a/node/src/external_miner_client.rs b/node/src/external_miner_client.rs index ff21f314..22a93671 100644 --- a/node/src/external_miner_client.rs +++ b/node/src/external_miner_client.rs @@ -1,8 +1,7 @@ +use primitive_types::{H256, U512}; /// Functions to interact with the external miner service - use reqwest::Client; -use primitive_types::{H256, U512}; -use resonance_miner_api::{MiningRequest, MiningResponse, MiningResult, ApiResponseStatus}; +use resonance_miner_api::{ApiResponseStatus, MiningRequest, MiningResponse, MiningResult}; use sc_consensus_qpow::QPoWSeal; // Assuming QPoWSeal is here // Make functions pub(crate) or pub as needed @@ -66,9 +65,12 @@ pub(crate) async fn check_mining_result( if nonce_bytes.len() == 64 { let mut nonce = [0u8; 64]; nonce.copy_from_slice(&nonce_bytes); - Ok(Some(QPoWSeal { nonce })) + Ok(Some(QPoWSeal { nonce })) } else { - Err(format!("Invalid decoded work length: {} bytes (expected 64)", nonce_bytes.len())) + Err(format!( + "Invalid decoded work length: {} bytes (expected 64)", + nonce_bytes.len() + )) } } else { Err("Missing 'work' field in completed mining result".to_string()) @@ -77,13 +79,21 @@ pub(crate) async fn check_mining_result( ApiResponseStatus::Running => Ok(None), ApiResponseStatus::NotFound => Err("Mining job not found".to_string()), ApiResponseStatus::Failed => Err("Mining job failed (miner reported)".to_string()), - ApiResponseStatus::Cancelled => Err("Mining job was cancelled (miner reported)".to_string()), - ApiResponseStatus::Error => Err("Miner reported an unspecified error".to_string()), - ApiResponseStatus::Accepted => Err("Unexpected 'Accepted' status received from result endpoint".to_string()), + ApiResponseStatus::Cancelled => { + Err("Mining job was cancelled (miner reported)".to_string()) + } + ApiResponseStatus::Error => Err("Miner reported an unspecified error".to_string()), + ApiResponseStatus::Accepted => { + Err("Unexpected 'Accepted' status received from result endpoint".to_string()) + } } } -pub(crate) async fn cancel_mining_job(client: &Client, miner_url: &str, job_id: &str) -> Result<(), String> { +pub(crate) async fn cancel_mining_job( + client: &Client, + miner_url: &str, + job_id: &str, +) -> Result<(), String> { let response = client .post(format!("{}/cancel/{}", miner_url, job_id)) .send() @@ -95,9 +105,13 @@ pub(crate) async fn cancel_mining_job(client: &Client, miner_url: &str, job_id: .await .map_err(|e| format!("Failed to parse cancel response: {}", e))?; - if result.status == ApiResponseStatus::Cancelled || result.status == ApiResponseStatus::NotFound { + if result.status == ApiResponseStatus::Cancelled || result.status == ApiResponseStatus::NotFound + { Ok(()) } else { - Err(format!("Failed to cancel mining job (unexpected status): {:?}", result.status)) + Err(format!( + "Failed to cancel mining job (unexpected status): {:?}", + result.status + )) } -} \ No newline at end of file +} diff --git a/node/src/faucet.rs b/node/src/faucet.rs index d18c0872..75a0d889 100644 --- a/node/src/faucet.rs +++ b/node/src/faucet.rs @@ -1,25 +1,21 @@ use codec::{Decode, Encode}; -use jsonrpsee::{ - core::RpcResult, - proc_macros::rpc, -}; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use resonance_runtime::opaque::Block; +use resonance_runtime::{AccountId, Balance, Nonce, RuntimeCall, UncheckedExtrinsic}; use sc_transaction_pool_api::TransactionPool; +use serde::{Deserialize, Serialize}; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::crypto::Ss58Codec; -use sp_runtime::MultiAddress; +use sp_faucet::FaucetApi; use sp_runtime::traits::Block as BlockT; use sp_runtime::transaction_validity::TransactionSource; +use sp_runtime::MultiAddress; +use std::sync::Arc; use substrate_frame_rpc_system::AccountNonceApi; -use resonance_runtime::{AccountId, Balance, Nonce, RuntimeCall, UncheckedExtrinsic}; -use resonance_runtime::opaque::Block; -use sp_faucet::FaucetApi; -#[rpc(client,server)] +#[rpc(client, server)] pub trait FaucetApi { - #[method(name = "faucet_getAccountInfo")] fn get_account_info(&self, address: String, at: Option) -> RpcResult; @@ -42,13 +38,12 @@ pub struct Faucet { impl Faucet { /// Create new Faucet RPC handler pub fn new(client: Arc, pool: Arc

) -> Self { - Self { - client, - pool, - } + Self { client, pool } } - fn parse_address(address: String) -> Result> { + fn parse_address( + address: String, + ) -> Result> { if let Some(hex_str) = address.strip_prefix("0x") { match hex::decode(hex_str) { Ok(bytes) => { @@ -56,32 +51,28 @@ impl Faucet { return Err(jsonrpsee::types::error::ErrorObject::owned( 4001, "Invalid hex address length, expected 32 bytes", - None::<()> + None::<()>, )); } let mut array = [0u8; 32]; array.copy_from_slice(&bytes); Ok(AccountId::from(array)) - }, - Err(_) => { - Err(jsonrpsee::types::error::ErrorObject::owned( - 4001, - "Invalid hex address format", - None::<()> - )) } + Err(_) => Err(jsonrpsee::types::error::ErrorObject::owned( + 4001, + "Invalid hex address format", + None::<()>, + )), } } else { // Format SS58 match resonance_runtime::AccountId::from_string(&address) { Ok(account) => Ok(account), - Err(_) => { - Err(jsonrpsee::types::error::ErrorObject::owned( - 4001, - "Invalid address format, expected 0x-prefixed hex or valid SS58", - None::<()> - )) - } + Err(_) => Err(jsonrpsee::types::error::ErrorObject::owned( + 4001, + "Invalid address format, expected 0x-prefixed hex or valid SS58", + None::<()>, + )), } } } @@ -89,25 +80,33 @@ impl Faucet { impl FaucetApiServer<::Hash> for Faucet where - C: ProvideRuntimeApi - + HeaderBackend - + Send - + Sync - + 'static, + C: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, C::Api: AccountNonceApi, C::Api: FaucetApi, P: TransactionPool + 'static, { - fn get_account_info(&self, address: String, at: Option<::Hash>) -> RpcResult { + fn get_account_info( + &self, + address: String, + at: Option<::Hash>, + ) -> RpcResult { let at = at.unwrap_or_else(|| self.client.info().best_hash); let account_id = Self::parse_address(address)?; - let (free_balance, reserved_balance) = match self.client.runtime_api().account_balance(at, account_id.clone()) { + let (free_balance, reserved_balance) = match self + .client + .runtime_api() + .account_balance(at, account_id.clone()) + { Ok((free, reserved)) => { - log::info!("Successfully retrieved balances - free: {}, reserved: {}", free, reserved); + log::info!( + "Successfully retrieved balances - free: {}, reserved: {}", + free, + reserved + ); (free, reserved) - }, + } Err(err) => { log::error!("Failed to get account balances: {:?}", err); (0, 0) @@ -120,7 +119,11 @@ where }) } - fn request_tokens(&self, address: String, at: Option<::Hash>) -> RpcResult { + fn request_tokens( + &self, + address: String, + at: Option<::Hash>, + ) -> RpcResult { let at = at.unwrap_or_else(|| self.client.info().best_hash); let account_id = Self::parse_address(address)?; @@ -129,7 +132,7 @@ where let call = RuntimeCall::Faucet(pallet_faucet::Call::mint_new_tokens { dest: MultiAddress::Id(account_id.clone()), - seed + seed, }); let extrinsic = UncheckedExtrinsic::new_bare(call); @@ -142,11 +145,11 @@ where Ok(tx_hash) => { log::info!("Successfully submitted faucet transaction: {:?}", tx_hash); Ok(true) - }, + } Err(e) => { log::error!("Failed to submit faucet transaction: {:?}", e); Ok(false) } } } -} \ No newline at end of file +} diff --git a/node/src/prometheus.rs b/node/src/prometheus.rs index cb367d04..ddb4dbc7 100644 --- a/node/src/prometheus.rs +++ b/node/src/prometheus.rs @@ -1,17 +1,14 @@ -use prometheus::{Registry, Opts, GaugeVec}; -use sp_api::ProvideRuntimeApi; -use resonance_runtime::opaque::Block; use futures::StreamExt; +use primitive_types::U512; +use prometheus::{GaugeVec, Opts, Registry}; +use resonance_runtime::opaque::Block; use sc_client_api::BlockchainEvents; -use std::sync::Arc; -use primitive_types::{U512}; +use sp_api::ProvideRuntimeApi; use sp_consensus_qpow::QPoWApi; +use std::sync::Arc; pub struct ResonanceBusinessMetrics; - - - impl ResonanceBusinessMetrics { /// Pack a U512 into an f64 by taking the highest-order 64 bits (8 bytes). fn pack_u512_to_f64(value: U512) -> f64 { @@ -31,10 +28,8 @@ impl ResonanceBusinessMetrics { /// Register QPoW metrics gauge vector with Prometheus pub fn register_gauge_vec(registry: &Registry) -> GaugeVec { - let gauge_vec = GaugeVec::new( - Opts::new("qpow_metrics", "QPOW Metrics"), - &["data_group"] - ).unwrap(); + let gauge_vec = + GaugeVec::new(Opts::new("qpow_metrics", "QPOW Metrics"), &["data_group"]).unwrap(); registry.register(Box::new(gauge_vec.clone())).unwrap(); gauge_vec @@ -50,49 +45,56 @@ impl ResonanceBusinessMetrics { C::Api: sp_consensus_qpow::QPoWApi, { // Get values via the runtime API - we'll handle potential errors gracefully - let block_time_sum = client.runtime_api() + let block_time_sum = client + .runtime_api() .get_block_time_sum(block_hash) .unwrap_or_else(|e| { log::warn!("Failed to get median_block_time: {:?}", e); 0 }); - let median_block_time = client.runtime_api() + let median_block_time = client + .runtime_api() .get_median_block_time(block_hash) .unwrap_or_else(|e| { log::warn!("Failed to get median_block_time: {:?}", e); 0 }); - let distance_threshold = client.runtime_api() + let distance_threshold = client + .runtime_api() .get_distance_threshold(block_hash) .unwrap_or_else(|e| { log::warn!("Failed to get distance_threshold: {:?}", e); U512::zero() }); - let last_block_time = client.runtime_api() + let last_block_time = client + .runtime_api() .get_last_block_time(block_hash) .unwrap_or_else(|e| { log::warn!("Failed to get last_block_time: {:?}", e); 0 }); - let last_block_duration = client.runtime_api() + let last_block_duration = client + .runtime_api() .get_last_block_duration(block_hash) .unwrap_or_else(|e| { log::warn!("Failed to get last_block_duration: {:?}", e); 0 }); - let chain_height = client.runtime_api() + let chain_height = client + .runtime_api() .get_chain_height(block_hash) .unwrap_or_else(|e| { log::warn!("Failed to get chain_height: {:?}", e); 0 }); - let difficulty = client.runtime_api() + let difficulty = client + .runtime_api() .get_difficulty(block_hash) .unwrap_or_else(|e| { log::warn!("Failed to get difficulty: {:?}", e); @@ -102,13 +104,27 @@ impl ResonanceBusinessMetrics { log::warn!("😵 difficulty reported: {:?}", difficulty.low_u64() as f64); // Update the metrics with the values we retrieved - gauge.with_label_values(&["chain_height"]).set(chain_height as f64); - gauge.with_label_values(&["block_time_sum"]).set(block_time_sum as f64); - gauge.with_label_values(&["median_block_time"]).set(median_block_time as f64); - gauge.with_label_values(&["distance_threshold"]).set(Self::pack_u512_to_f64(distance_threshold)); - gauge.with_label_values(&["difficulty"]).set(difficulty.low_u64() as f64); - gauge.with_label_values(&["last_block_time"]).set(last_block_time as f64); - gauge.with_label_values(&["last_block_duration"]).set(last_block_duration as f64); + gauge + .with_label_values(&["chain_height"]) + .set(chain_height as f64); + gauge + .with_label_values(&["block_time_sum"]) + .set(block_time_sum as f64); + gauge + .with_label_values(&["median_block_time"]) + .set(median_block_time as f64); + gauge + .with_label_values(&["distance_threshold"]) + .set(Self::pack_u512_to_f64(distance_threshold)); + gauge + .with_label_values(&["difficulty"]) + .set(difficulty.low_u64() as f64); + gauge + .with_label_values(&["last_block_time"]) + .set(last_block_time as f64); + gauge + .with_label_values(&["last_block_duration"]) + .set(last_block_duration as f64); } /// Start a monitoring task for QPoW metrics @@ -121,15 +137,12 @@ impl ResonanceBusinessMetrics { C::Api: sp_consensus_qpow::QPoWApi, { // Get or create the gauge vector from the registry - let prometheus_gauge_vec = prometheus_registry - .as_ref() - .map(Self::register_gauge_vec); + let prometheus_gauge_vec = prometheus_registry.as_ref().map(Self::register_gauge_vec); // Spawn the monitoring task - task_manager.spawn_essential_handle().spawn( - "monitoring_qpow", - None, - async move { + task_manager + .spawn_essential_handle() + .spawn("monitoring_qpow", None, async move { log::info!("⚙️ QPoW Monitoring task spawned"); let mut sub = client.import_notification_stream(); @@ -141,7 +154,6 @@ impl ResonanceBusinessMetrics { log::warn!("QPoW Monitoring: Prometheus registry not found"); } } - } - ); + }); } -} \ No newline at end of file +} diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 5d2daa53..f7fec11e 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -7,46 +7,46 @@ use std::sync::Arc; +use crate::faucet::{Faucet, FaucetApiServer}; use jsonrpsee::RpcModule; -use sc_transaction_pool_api::TransactionPool; use resonance_runtime::{opaque::Block, AccountId, Balance, Nonce}; +use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use sp_faucet::FaucetApi; -use crate::faucet::{Faucet, FaucetApiServer}; /// Full client dependencies. pub struct FullDeps { - /// The client instance to use. - pub client: Arc, - /// Transaction pool instance. - pub pool: Arc

, + /// The client instance to use. + pub client: Arc, + /// Transaction pool instance. + pub pool: Arc

, } /// Instantiate all full RPC extensions. pub fn create_full( - deps: FullDeps, + deps: FullDeps, ) -> Result, Box> where - C: ProvideRuntimeApi, - C: HeaderBackend + HeaderMetadata + 'static, - C: Send + Sync + 'static, - C::Api: substrate_frame_rpc_system::AccountNonceApi, - C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, - C::Api: FaucetApi, - C::Api: BlockBuilder, - P: TransactionPool + 'static, + C: ProvideRuntimeApi, + C: HeaderBackend + HeaderMetadata + 'static, + C: Send + Sync + 'static, + C::Api: substrate_frame_rpc_system::AccountNonceApi, + C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, + C::Api: FaucetApi, + C::Api: BlockBuilder, + P: TransactionPool + 'static, { - use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; - use substrate_frame_rpc_system::{System, SystemApiServer}; + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use substrate_frame_rpc_system::{System, SystemApiServer}; - let mut module = RpcModule::new(()); - let FullDeps { client, pool } = deps; + let mut module = RpcModule::new(()); + let FullDeps { client, pool } = deps; - module.merge(System::new(client.clone(), pool.clone()).into_rpc())?; - module.merge(TransactionPayment::new(client.clone()).into_rpc())?; - module.merge(Faucet::new(client, pool).into_rpc())?; + module.merge(System::new(client.clone(), pool.clone()).into_rpc())?; + module.merge(TransactionPayment::new(client.clone()).into_rpc())?; + module.merge(Faucet::new(client, pool).into_rpc())?; - Ok(module) + Ok(module) } diff --git a/node/src/service.rs b/node/src/service.rs index 442e8225..bfe729ea 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -1,28 +1,28 @@ //! Service and ServiceFactory implementation. Specialized wrapper over substrate service. use futures::{FutureExt, StreamExt}; -use sc_consensus_qpow::{ChainManagement, QPoWMiner, QPoWSeal, QPowAlgorithm}; +use resonance_runtime::{self, apis::RuntimeApi, opaque::Block}; use sc_client_api::Backend; +use sc_consensus_qpow::{ChainManagement, QPoWMiner, QPoWSeal, QPowAlgorithm}; use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; use sc_telemetry::{Telemetry, TelemetryWorker}; use sc_transaction_pool_api::{InPoolTransaction, OffchainTransactionPoolFactory, TransactionPool}; -use resonance_runtime::{self, apis::RuntimeApi, opaque::Block}; -use std::{sync::Arc, time::Duration}; +use crate::external_miner_client; +use crate::prometheus::ResonanceBusinessMetrics; +use async_trait::async_trait; use codec::Encode; use jsonrpsee::tokio; +use reqwest::Client; +use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult}; +use sp_api::ProvideRuntimeApi; use sp_api::__private::BlockT; +use sp_consensus_qpow::QPoWApi; +use sp_core::crypto::AccountId32; use sp_core::{RuntimeDebug, U512}; -use async_trait::async_trait; -use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult}; use sp_runtime::traits::Header; -use crate::prometheus::ResonanceBusinessMetrics; -use sp_core::crypto::AccountId32; -use reqwest::Client; +use std::{sync::Arc, time::Duration}; use uuid::Uuid; -use sp_api::ProvideRuntimeApi; -use sp_consensus_qpow::QPoWApi; -use crate::external_miner_client; pub(crate) type FullClient = sc_service::TFullClient< Block, @@ -37,7 +37,13 @@ pub type PowBlockImport = sc_consensus_pow::PowBlockImport< FullClient, FullSelectChain, QPowAlgorithm, - Box>, + Box< + dyn sp_inherents::CreateInherentDataProviders< + Block, + (), + InherentDataProviders = sp_timestamp::InherentDataProvider, + >, + >, >; #[derive(PartialEq, Eq, Clone, RuntimeDebug)] @@ -56,8 +62,7 @@ impl LoggingBlockImport { } #[async_trait] -impl + Sync> BlockImport for LoggingBlockImport -{ +impl + Sync> BlockImport for LoggingBlockImport { type Error = I::Error; async fn check_block(&self, block: BlockCheckParams) -> Result { @@ -85,8 +90,16 @@ pub type Service = sc_service::PartialComponents< (LoggingBlockImport, Option), >; //TODO Question - for what is this method? -pub fn build_inherent_data_providers( -) -> Result>, ServiceError> { +pub fn build_inherent_data_providers() -> Result< + Box< + dyn sp_inherents::CreateInherentDataProviders< + Block, + (), + InherentDataProviders = sp_timestamp::InherentDataProvider, + >, + >, + ServiceError, +> { struct Provider; #[async_trait::async_trait] impl sp_inherents::CreateInherentDataProviders for Provider { @@ -138,7 +151,11 @@ pub fn new_partial(config: &Configuration) -> Result { _phantom: Default::default(), }; - let select_chain = sc_consensus_qpow::HeaviestChain::new(backend.clone(), Arc::clone(&client), pow_algorithm.clone()); + let select_chain = sc_consensus_qpow::HeaviestChain::new( + backend.clone(), + Arc::clone(&client), + pow_algorithm.clone(), + ); let transaction_pool = Arc::from( sc_transaction_pool::Builder::new( @@ -146,9 +163,9 @@ pub fn new_partial(config: &Configuration) -> Result { client.clone(), config.role.is_authority().into(), ) - .with_options(config.transaction_pool.clone()) - .with_prometheus(config.prometheus_registry()) - .build(), + .with_options(config.transaction_pool.clone()) + .with_prometheus(config.prometheus_registry()) + .build(), ); let inherent_data_providers = build_inherent_data_providers()?; @@ -175,16 +192,16 @@ pub fn new_partial(config: &Configuration) -> Result { config.prometheus_registry(), )?; - Ok(sc_service::PartialComponents { - client, - backend, - task_manager, - import_queue, - keystore_container, - select_chain, - transaction_pool, - other: (logging_block_import, telemetry), - }) + Ok(sc_service::PartialComponents { + client, + backend, + task_manager, + import_queue, + keystore_container, + select_chain, + transaction_pool, + other: (logging_block_import, telemetry), + }) } /// Builds a new service for a full client. @@ -229,26 +246,28 @@ pub fn new_full< metrics, })?; - if config.offchain_worker.enabled { - let offchain_workers = - sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { - runtime_api_provider: client.clone(), - is_validator: config.role.is_authority(), - keystore: Some(keystore_container.keystore()), - offchain_db: backend.offchain_storage(), - transaction_pool: Some(OffchainTransactionPoolFactory::new( - transaction_pool.clone(), - )), - network_provider: Arc::new(network.clone()), - enable_http_requests: true, - custom_extensions: |_| vec![], - })?; - task_manager.spawn_handle().spawn( - "offchain-workers-runner", - "offchain-worker", - offchain_workers.run(client.clone(), task_manager.spawn_handle()).boxed(), - ); - } + if config.offchain_worker.enabled { + let offchain_workers = + sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { + runtime_api_provider: client.clone(), + is_validator: config.role.is_authority(), + keystore: Some(keystore_container.keystore()), + offchain_db: backend.offchain_storage(), + transaction_pool: Some(OffchainTransactionPoolFactory::new( + transaction_pool.clone(), + )), + network_provider: Arc::new(network.clone()), + enable_http_requests: true, + custom_extensions: |_| vec![], + })?; + task_manager.spawn_handle().spawn( + "offchain-workers-runner", + "offchain-worker", + offchain_workers + .run(client.clone(), task_manager.spawn_handle()) + .boxed(), + ); + } let role = config.role; let prometheus_registry = config.prometheus_registry().cloned(); @@ -305,7 +324,7 @@ pub fn new_full< Ok(account) => { log::info!("Using provided rewards address: {:?}", account); Some(account.encode()) - }, + } Err(_) => { log::warn!("Invalid rewards address format: {}", addr_str); None @@ -336,13 +355,10 @@ pub fn new_full< ResonanceBusinessMetrics::start_monitoring_task( client.clone(), prometheus_registry.clone(), - &task_manager + &task_manager, ); - ChainManagement::spawn_finalization_task( - Arc::new(select_chain.clone()), - &task_manager - ); + ChainManagement::spawn_finalization_task(Arc::new(select_chain.clone()), &task_manager); task_manager.spawn_essential_handle().spawn( "qpow-mining", @@ -465,21 +481,23 @@ pub fn new_full< }, ); - task_manager.spawn_handle().spawn("tx-logger", None, async move { - while let Some(tx_hash) = tx_stream.next().await { - if let Some(tx) = transaction_pool.ready_transaction(&tx_hash) { - log::info!("New transaction: Hash = {:?}", tx_hash); - let extrinsic = tx.data(); - log::info!("Payload: {:?}", extrinsic); - } else { - log::warn!("Transaction {:?} not found in pool", tx_hash); + task_manager + .spawn_handle() + .spawn("tx-logger", None, async move { + while let Some(tx_hash) = tx_stream.next().await { + if let Some(tx) = transaction_pool.ready_transaction(&tx_hash) { + log::info!("New transaction: Hash = {:?}", tx_hash); + let extrinsic = tx.data(); + log::info!("Payload: {:?}", extrinsic); + } else { + log::warn!("Transaction {:?} not found in pool", tx_hash); + } } - } - }); + }); log::info!("⛏️ Pow miner spawned"); } network_starter.start_network(); Ok(task_manager) -} \ No newline at end of file +} diff --git a/pallets/balances/src/benchmarking.rs b/pallets/balances/src/benchmarking.rs index 06f3d24d..6f5ab3aa 100644 --- a/pallets/balances/src/benchmarking.rs +++ b/pallets/balances/src/benchmarking.rs @@ -32,319 +32,347 @@ const SEED: u32 = 0; const ED_MULTIPLIER: u32 = 10; fn minimum_balance, I: 'static>() -> T::Balance { - if cfg!(feature = "insecure_zero_ed") { - 100u32.into() - } else { - T::ExistentialDeposit::get() - } + if cfg!(feature = "insecure_zero_ed") { + 100u32.into() + } else { + T::ExistentialDeposit::get() + } } #[instance_benchmarks] mod benchmarks { - use super::*; - - // Benchmark `transfer` extrinsic with the worst possible conditions: - // * Transfer will kill the sender account. - // * Transfer will create the recipient account. - #[benchmark] - fn transfer_allow_death() { - let existential_deposit: T::Balance = minimum_balance::(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()).max(1u32.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, - // and reap this user. - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - let transfer_amount = - existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); - - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - - assert_eq!(Balances::::free_balance(&caller), Zero::zero()); - assert_eq!(Balances::::free_balance(&recipient), transfer_amount); - } - - // Benchmark `transfer` with the best possible condition: - // * Both accounts exist and will continue to exist. - #[benchmark(extra)] - fn transfer_best_case() { - let caller = whitelisted_caller(); - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - - // Give the sender account max funds for transfer (their account will never reasonably be - // killed). - let _ = - as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); - - // Give the recipient account existential deposit (thus their account already exists). - let existential_deposit: T::Balance = minimum_balance::(); - let _ = - as Currency<_>>::make_free_balance_be(&recipient, existential_deposit); - let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - - #[extrinsic_call] - transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - - assert!(!Balances::::free_balance(&caller).is_zero()); - assert!(!Balances::::free_balance(&recipient).is_zero()); - } - - // Benchmark `transfer_keep_alive` with the worst possible condition: - // * The recipient account is created. - #[benchmark] - fn transfer_keep_alive() { - let caller = whitelisted_caller(); - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - - // Give the sender account max funds, thus a transfer will not kill account. - let _ = - as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); - let existential_deposit: T::Balance = minimum_balance::(); - let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - - assert!(!Balances::::free_balance(&caller).is_zero()); - assert_eq!(Balances::::free_balance(&recipient), transfer_amount); - } - - // Benchmark `force_set_balance` coming from ROOT account. This always creates an account. - #[benchmark] - fn force_set_balance_creating() { - let user: T::AccountId = account("user", 0, SEED); - let user_lookup = T::Lookup::unlookup(user.clone()); - - // Give the user some initial balance. - let existential_deposit: T::Balance = minimum_balance::(); - let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); - - #[extrinsic_call] - force_set_balance(RawOrigin::Root, user_lookup, balance_amount); - - assert_eq!(Balances::::free_balance(&user), balance_amount); - } - - // Benchmark `force_set_balance` coming from ROOT account. This always kills an account. - #[benchmark] - fn force_set_balance_killing() { - let user: T::AccountId = account("user", 0, SEED); - let user_lookup = T::Lookup::unlookup(user.clone()); - - // Give the user some initial balance. - let existential_deposit: T::Balance = minimum_balance::(); - let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); - - #[extrinsic_call] - force_set_balance(RawOrigin::Root, user_lookup, Zero::zero()); - - assert!(Balances::::free_balance(&user).is_zero()); - } - - // Benchmark `force_transfer` extrinsic with the worst possible conditions: - // * Transfer will kill the sender account. - // * Transfer will create the recipient account. - #[benchmark] - fn force_transfer() { - let existential_deposit: T::Balance = minimum_balance::(); - let source: T::AccountId = account("source", 0, SEED); - let source_lookup = T::Lookup::unlookup(source.clone()); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&source, balance); - - // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, - // and reap this user. - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - let transfer_amount = - existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); - - #[extrinsic_call] - _(RawOrigin::Root, source_lookup, recipient_lookup, transfer_amount); - - assert_eq!(Balances::::free_balance(&source), Zero::zero()); - assert_eq!(Balances::::free_balance(&recipient), transfer_amount); - } - - // This benchmark performs the same operation as `transfer` in the worst case scenario, - // but additionally introduces many new users into the storage, increasing the the merkle - // trie and PoV size. - #[benchmark(extra)] - fn transfer_increasing_users(u: Linear<0, 1_000>) { - // 1_000 is not very much, but this upper bound can be controlled by the CLI. - let existential_deposit: T::Balance = minimum_balance::(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, - // and reap this user. - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - let transfer_amount = - existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); - - // Create a bunch of users in storage. - for i in 0..u { - // The `account` function uses `blake2_256` to generate unique accounts, so these - // should be quite random and evenly distributed in the trie. - let new_user: T::AccountId = account("new_user", i, SEED); - let _ = as Currency<_>>::make_free_balance_be(&new_user, balance); - } - - #[extrinsic_call] - transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - - assert_eq!(Balances::::free_balance(&caller), Zero::zero()); - assert_eq!(Balances::::free_balance(&recipient), transfer_amount); - } - - // Benchmark `transfer_all` with the worst possible condition: - // * The recipient account is created - // * The sender is killed - #[benchmark] - fn transfer_all() { - let caller = whitelisted_caller(); - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - - // Give some multiple of the existential deposit - let existential_deposit: T::Balance = minimum_balance::(); - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), recipient_lookup, false); - - assert!(Balances::::free_balance(&caller).is_zero()); - assert_eq!(Balances::::free_balance(&recipient), balance); - } - - #[benchmark] - fn force_unreserve() -> Result<(), BenchmarkError> { - let user: T::AccountId = account("user", 0, SEED); - let user_lookup = T::Lookup::unlookup(user.clone()); - - // Give some multiple of the existential deposit - let ed = minimum_balance::(); - let balance = ed + ed; - let _ = as Currency<_>>::make_free_balance_be(&user, balance); - - // Reserve the balance - as ReservableCurrency<_>>::reserve(&user, ed)?; - assert_eq!(Balances::::reserved_balance(&user), ed); - assert_eq!(Balances::::free_balance(&user), ed); - - #[extrinsic_call] - _(RawOrigin::Root, user_lookup, balance); - - assert!(Balances::::reserved_balance(&user).is_zero()); - assert_eq!(Balances::::free_balance(&user), ed + ed); - - Ok(()) - } - - #[benchmark] - fn upgrade_accounts(u: Linear<1, 1_000>) { - let caller: T::AccountId = whitelisted_caller(); - let who = (0..u) - .map(|i| -> T::AccountId { - let user = account("old_user", i, SEED); - let account = AccountData { - free: minimum_balance::(), - reserved: minimum_balance::(), - frozen: Zero::zero(), - flags: ExtraFlags::old_logic(), - }; - frame_system::Pallet::::inc_providers(&user); - assert!(T::AccountStore::try_mutate_exists(&user, |a| -> DispatchResult { - *a = Some(account); - Ok(()) - }) - .is_ok()); - assert!(!Balances::::account(&user).flags.is_new_logic()); - assert_eq!(frame_system::Pallet::::providers(&user), 1); - assert_eq!(frame_system::Pallet::::consumers(&user), 0); - user - }) - .collect(); - - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), who); - - for i in 0..u { - let user: T::AccountId = account("old_user", i, SEED); - assert!(Balances::::account(&user).flags.is_new_logic()); - assert_eq!(frame_system::Pallet::::providers(&user), 1); - assert_eq!(frame_system::Pallet::::consumers(&user), 1); - } - } - - #[benchmark] - fn force_adjust_total_issuance() { - let ti = TotalIssuance::::get(); - let delta = 123u32.into(); - - #[extrinsic_call] - _(RawOrigin::Root, AdjustmentDirection::Increase, delta); - - assert_eq!(TotalIssuance::::get(), ti + delta); - } - - /// Benchmark `burn` extrinsic with the worst possible condition - burn kills the account. - #[benchmark] - fn burn_allow_death() { - let existential_deposit = T::ExistentialDeposit::get(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - // Burn enough to kill the account. - let burn_amount = balance - existential_deposit + 1u32.into(); - - #[extrinsic_call] - burn(RawOrigin::Signed(caller.clone()), burn_amount, false); - - assert_eq!(Balances::::free_balance(&caller), Zero::zero()); - } - - // Benchmark `burn` extrinsic with the case where account is kept alive. - #[benchmark] - fn burn_keep_alive() { - let existential_deposit = T::ExistentialDeposit::get(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - // Burn minimum possible amount which should not kill the account. - let burn_amount = 1u32.into(); - - #[extrinsic_call] - burn(RawOrigin::Signed(caller.clone()), burn_amount, true); - - assert_eq!(Balances::::free_balance(&caller), balance - burn_amount); - } - - impl_benchmark_test_suite! { - Balances, - crate::tests::ExtBuilder::default().build(), - crate::tests::Test, - } + use super::*; + + // Benchmark `transfer` extrinsic with the worst possible conditions: + // * Transfer will kill the sender account. + // * Transfer will create the recipient account. + #[benchmark] + fn transfer_allow_death() { + let existential_deposit: T::Balance = minimum_balance::(); + let caller = whitelisted_caller(); + + // Give some multiple of the existential deposit + let balance = existential_deposit + .saturating_mul(ED_MULTIPLIER.into()) + .max(1u32.into()); + let _ = as Currency<_>>::make_free_balance_be(&caller, balance); + + // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, + // and reap this user. + let recipient: T::AccountId = account("recipient", 0, SEED); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); + let transfer_amount = + existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + recipient_lookup, + transfer_amount, + ); + + assert_eq!(Balances::::free_balance(&caller), Zero::zero()); + assert_eq!(Balances::::free_balance(&recipient), transfer_amount); + } + + // Benchmark `transfer` with the best possible condition: + // * Both accounts exist and will continue to exist. + #[benchmark(extra)] + fn transfer_best_case() { + let caller = whitelisted_caller(); + let recipient: T::AccountId = account("recipient", 0, SEED); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); + + // Give the sender account max funds for transfer (their account will never reasonably be + // killed). + let _ = + as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); + + // Give the recipient account existential deposit (thus their account already exists). + let existential_deposit: T::Balance = minimum_balance::(); + let _ = + as Currency<_>>::make_free_balance_be(&recipient, existential_deposit); + let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + + #[extrinsic_call] + transfer_allow_death( + RawOrigin::Signed(caller.clone()), + recipient_lookup, + transfer_amount, + ); + + assert!(!Balances::::free_balance(&caller).is_zero()); + assert!(!Balances::::free_balance(&recipient).is_zero()); + } + + // Benchmark `transfer_keep_alive` with the worst possible condition: + // * The recipient account is created. + #[benchmark] + fn transfer_keep_alive() { + let caller = whitelisted_caller(); + let recipient: T::AccountId = account("recipient", 0, SEED); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); + + // Give the sender account max funds, thus a transfer will not kill account. + let _ = + as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); + let existential_deposit: T::Balance = minimum_balance::(); + let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + recipient_lookup, + transfer_amount, + ); + + assert!(!Balances::::free_balance(&caller).is_zero()); + assert_eq!(Balances::::free_balance(&recipient), transfer_amount); + } + + // Benchmark `force_set_balance` coming from ROOT account. This always creates an account. + #[benchmark] + fn force_set_balance_creating() { + let user: T::AccountId = account("user", 0, SEED); + let user_lookup = T::Lookup::unlookup(user.clone()); + + // Give the user some initial balance. + let existential_deposit: T::Balance = minimum_balance::(); + let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); + + #[extrinsic_call] + force_set_balance(RawOrigin::Root, user_lookup, balance_amount); + + assert_eq!(Balances::::free_balance(&user), balance_amount); + } + + // Benchmark `force_set_balance` coming from ROOT account. This always kills an account. + #[benchmark] + fn force_set_balance_killing() { + let user: T::AccountId = account("user", 0, SEED); + let user_lookup = T::Lookup::unlookup(user.clone()); + + // Give the user some initial balance. + let existential_deposit: T::Balance = minimum_balance::(); + let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); + + #[extrinsic_call] + force_set_balance(RawOrigin::Root, user_lookup, Zero::zero()); + + assert!(Balances::::free_balance(&user).is_zero()); + } + + // Benchmark `force_transfer` extrinsic with the worst possible conditions: + // * Transfer will kill the sender account. + // * Transfer will create the recipient account. + #[benchmark] + fn force_transfer() { + let existential_deposit: T::Balance = minimum_balance::(); + let source: T::AccountId = account("source", 0, SEED); + let source_lookup = T::Lookup::unlookup(source.clone()); + + // Give some multiple of the existential deposit + let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + let _ = as Currency<_>>::make_free_balance_be(&source, balance); + + // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, + // and reap this user. + let recipient: T::AccountId = account("recipient", 0, SEED); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); + let transfer_amount = + existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); + + #[extrinsic_call] + _( + RawOrigin::Root, + source_lookup, + recipient_lookup, + transfer_amount, + ); + + assert_eq!(Balances::::free_balance(&source), Zero::zero()); + assert_eq!(Balances::::free_balance(&recipient), transfer_amount); + } + + // This benchmark performs the same operation as `transfer` in the worst case scenario, + // but additionally introduces many new users into the storage, increasing the the merkle + // trie and PoV size. + #[benchmark(extra)] + fn transfer_increasing_users(u: Linear<0, 1_000>) { + // 1_000 is not very much, but this upper bound can be controlled by the CLI. + let existential_deposit: T::Balance = minimum_balance::(); + let caller = whitelisted_caller(); + + // Give some multiple of the existential deposit + let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + let _ = as Currency<_>>::make_free_balance_be(&caller, balance); + + // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, + // and reap this user. + let recipient: T::AccountId = account("recipient", 0, SEED); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); + let transfer_amount = + existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); + + // Create a bunch of users in storage. + for i in 0..u { + // The `account` function uses `blake2_256` to generate unique accounts, so these + // should be quite random and evenly distributed in the trie. + let new_user: T::AccountId = account("new_user", i, SEED); + let _ = as Currency<_>>::make_free_balance_be(&new_user, balance); + } + + #[extrinsic_call] + transfer_allow_death( + RawOrigin::Signed(caller.clone()), + recipient_lookup, + transfer_amount, + ); + + assert_eq!(Balances::::free_balance(&caller), Zero::zero()); + assert_eq!(Balances::::free_balance(&recipient), transfer_amount); + } + + // Benchmark `transfer_all` with the worst possible condition: + // * The recipient account is created + // * The sender is killed + #[benchmark] + fn transfer_all() { + let caller = whitelisted_caller(); + let recipient: T::AccountId = account("recipient", 0, SEED); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); + + // Give some multiple of the existential deposit + let existential_deposit: T::Balance = minimum_balance::(); + let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + let _ = as Currency<_>>::make_free_balance_be(&caller, balance); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), recipient_lookup, false); + + assert!(Balances::::free_balance(&caller).is_zero()); + assert_eq!(Balances::::free_balance(&recipient), balance); + } + + #[benchmark] + fn force_unreserve() -> Result<(), BenchmarkError> { + let user: T::AccountId = account("user", 0, SEED); + let user_lookup = T::Lookup::unlookup(user.clone()); + + // Give some multiple of the existential deposit + let ed = minimum_balance::(); + let balance = ed + ed; + let _ = as Currency<_>>::make_free_balance_be(&user, balance); + + // Reserve the balance + as ReservableCurrency<_>>::reserve(&user, ed)?; + assert_eq!(Balances::::reserved_balance(&user), ed); + assert_eq!(Balances::::free_balance(&user), ed); + + #[extrinsic_call] + _(RawOrigin::Root, user_lookup, balance); + + assert!(Balances::::reserved_balance(&user).is_zero()); + assert_eq!(Balances::::free_balance(&user), ed + ed); + + Ok(()) + } + + #[benchmark] + fn upgrade_accounts(u: Linear<1, 1_000>) { + let caller: T::AccountId = whitelisted_caller(); + let who = (0..u) + .map(|i| -> T::AccountId { + let user = account("old_user", i, SEED); + let account = AccountData { + free: minimum_balance::(), + reserved: minimum_balance::(), + frozen: Zero::zero(), + flags: ExtraFlags::old_logic(), + }; + frame_system::Pallet::::inc_providers(&user); + assert!( + T::AccountStore::try_mutate_exists(&user, |a| -> DispatchResult { + *a = Some(account); + Ok(()) + }) + .is_ok() + ); + assert!(!Balances::::account(&user).flags.is_new_logic()); + assert_eq!(frame_system::Pallet::::providers(&user), 1); + assert_eq!(frame_system::Pallet::::consumers(&user), 0); + user + }) + .collect(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), who); + + for i in 0..u { + let user: T::AccountId = account("old_user", i, SEED); + assert!(Balances::::account(&user).flags.is_new_logic()); + assert_eq!(frame_system::Pallet::::providers(&user), 1); + assert_eq!(frame_system::Pallet::::consumers(&user), 1); + } + } + + #[benchmark] + fn force_adjust_total_issuance() { + let ti = TotalIssuance::::get(); + let delta = 123u32.into(); + + #[extrinsic_call] + _(RawOrigin::Root, AdjustmentDirection::Increase, delta); + + assert_eq!(TotalIssuance::::get(), ti + delta); + } + + /// Benchmark `burn` extrinsic with the worst possible condition - burn kills the account. + #[benchmark] + fn burn_allow_death() { + let existential_deposit = T::ExistentialDeposit::get(); + let caller = whitelisted_caller(); + + // Give some multiple of the existential deposit + let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + let _ = as Currency<_>>::make_free_balance_be(&caller, balance); + + // Burn enough to kill the account. + let burn_amount = balance - existential_deposit + 1u32.into(); + + #[extrinsic_call] + burn(RawOrigin::Signed(caller.clone()), burn_amount, false); + + assert_eq!(Balances::::free_balance(&caller), Zero::zero()); + } + + // Benchmark `burn` extrinsic with the case where account is kept alive. + #[benchmark] + fn burn_keep_alive() { + let existential_deposit = T::ExistentialDeposit::get(); + let caller = whitelisted_caller(); + + // Give some multiple of the existential deposit + let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + let _ = as Currency<_>>::make_free_balance_be(&caller, balance); + + // Burn minimum possible amount which should not kill the account. + let burn_amount = 1u32.into(); + + #[extrinsic_call] + burn(RawOrigin::Signed(caller.clone()), burn_amount, true); + + assert_eq!( + Balances::::free_balance(&caller), + balance - burn_amount + ); + } + + impl_benchmark_test_suite! { + Balances, + crate::tests::ExtBuilder::default().build(), + crate::tests::Test, + } } diff --git a/pallets/balances/src/impl_currency.rs b/pallets/balances/src/impl_currency.rs index a7af976d..64b66b31 100644 --- a/pallets/balances/src/impl_currency.rs +++ b/pallets/balances/src/impl_currency.rs @@ -21,466 +21,485 @@ //! are expected to be removed in the near future, once migration to `fungible::*` traits is done. use super::*; +use core::cmp::Ordering; use frame_support::{ - ensure, - pallet_prelude::DispatchResult, - traits::{ - tokens::{fungible, BalanceStatus as Status, Fortitude::Polite, Precision::BestEffort}, - Currency, DefensiveSaturating, ExistenceRequirement, - ExistenceRequirement::AllowDeath, - Get, Imbalance, InspectLockableCurrency, LockIdentifier, LockableCurrency, - NamedReservableCurrency, ReservableCurrency, SignedImbalance, TryDrop, WithdrawReasons, - }, + ensure, + pallet_prelude::DispatchResult, + traits::{ + tokens::{fungible, BalanceStatus as Status, Fortitude::Polite, Precision::BestEffort}, + Currency, DefensiveSaturating, ExistenceRequirement, + ExistenceRequirement::AllowDeath, + Get, Imbalance, InspectLockableCurrency, LockIdentifier, LockableCurrency, + NamedReservableCurrency, ReservableCurrency, SignedImbalance, TryDrop, WithdrawReasons, + }, }; use frame_system::pallet_prelude::BlockNumberFor; pub use imbalances::{NegativeImbalance, PositiveImbalance}; use sp_runtime::traits::Bounded; -use core::cmp::Ordering; // wrapping these imbalances in a private module is necessary to ensure absolute privacy // of the inner member. mod imbalances { - use super::*; - use core::mem; - use frame_support::traits::{tokens::imbalance::TryMerge, SameOrOther}; - - /// Opaque, move-only struct with private fields that serves as a token denoting that - /// funds have been created without any equal and opposite accounting. - #[must_use] - #[derive(RuntimeDebug, PartialEq, Eq)] - pub struct PositiveImbalance, I: 'static = ()>(T::Balance); - - impl, I: 'static> PositiveImbalance { - /// Create a new positive imbalance from a balance. - pub fn new(amount: T::Balance) -> Self { - PositiveImbalance(amount) - } - } - - /// Opaque, move-only struct with private fields that serves as a token denoting that - /// funds have been destroyed without any equal and opposite accounting. - #[must_use] - #[derive(RuntimeDebug, PartialEq, Eq)] - pub struct NegativeImbalance, I: 'static = ()>(T::Balance); - - impl, I: 'static> NegativeImbalance { - /// Create a new negative imbalance from a balance. - pub fn new(amount: T::Balance) -> Self { - NegativeImbalance(amount) - } - } - - impl, I: 'static> TryDrop for PositiveImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } - } - - impl, I: 'static> Default for PositiveImbalance { - fn default() -> Self { - Self::zero() - } - } - - impl, I: 'static> Imbalance for PositiveImbalance { - type Opposite = NegativeImbalance; - - fn zero() -> Self { - Self(Zero::zero()) - } - fn drop_zero(self) -> result::Result<(), Self> { - if self.0.is_zero() { - Ok(()) - } else { - Err(self) - } - } - fn split(self, amount: T::Balance) -> (Self, Self) { - let first = self.0.min(amount); - let second = self.0 - first; - - mem::forget(self); - (Self(first), Self(second)) - } - fn extract(&mut self, amount: T::Balance) -> Self { - let new = self.0.min(amount); - self.0 -= new; - Self(new) - } - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - - self - } - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - } - fn offset(self, other: Self::Opposite) -> SameOrOther { - let (a, b) = (self.0, other.0); - mem::forget((self, other)); - - match a.cmp(&b) { - Ordering::Greater => SameOrOther::Same(Self(a - b)), - Ordering::Less => SameOrOther::Other(NegativeImbalance::new(b - a)), - Ordering::Equal => SameOrOther::None, - } - } - fn peek(&self) -> T::Balance { - self.0 - } - } - - impl, I: 'static> TryMerge for PositiveImbalance { - fn try_merge(self, other: Self) -> Result { - Ok(self.merge(other)) - } - } - - impl, I: 'static> TryDrop for NegativeImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } - } - - impl, I: 'static> Default for NegativeImbalance { - fn default() -> Self { - Self::zero() - } - } - - impl, I: 'static> Imbalance for NegativeImbalance { - type Opposite = PositiveImbalance; - - fn zero() -> Self { - Self(Zero::zero()) - } - fn drop_zero(self) -> result::Result<(), Self> { - if self.0.is_zero() { - Ok(()) - } else { - Err(self) - } - } - fn split(self, amount: T::Balance) -> (Self, Self) { - let first = self.0.min(amount); - let second = self.0 - first; - - mem::forget(self); - (Self(first), Self(second)) - } - fn extract(&mut self, amount: T::Balance) -> Self { - let new = self.0.min(amount); - self.0 -= new; - Self(new) - } - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - - self - } - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - } - fn offset(self, other: Self::Opposite) -> SameOrOther { - let (a, b) = (self.0, other.0); - mem::forget((self, other)); - - match a.cmp(&b) { - Ordering::Greater => SameOrOther::Same(Self(a - b)), - Ordering::Less => SameOrOther::Other(PositiveImbalance::new(b - a)), - Ordering::Equal => SameOrOther::None, - } - } - fn peek(&self) -> T::Balance { - self.0 - } - } - - impl, I: 'static> TryMerge for NegativeImbalance { - fn try_merge(self, other: Self) -> Result { - Ok(self.merge(other)) - } - } - - impl, I: 'static> Drop for PositiveImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - if !self.0.is_zero() { - >::mutate(|v| *v = v.saturating_add(self.0)); - Pallet::::deposit_event(Event::::Issued { amount: self.0 }); - } - } - } - - impl, I: 'static> Drop for NegativeImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - if !self.0.is_zero() { - >::mutate(|v| *v = v.saturating_sub(self.0)); - Pallet::::deposit_event(Event::::Rescinded { amount: self.0 }); - } - } - } + use super::*; + use core::mem; + use frame_support::traits::{tokens::imbalance::TryMerge, SameOrOther}; + + /// Opaque, move-only struct with private fields that serves as a token denoting that + /// funds have been created without any equal and opposite accounting. + #[must_use] + #[derive(RuntimeDebug, PartialEq, Eq)] + pub struct PositiveImbalance, I: 'static = ()>(T::Balance); + + impl, I: 'static> PositiveImbalance { + /// Create a new positive imbalance from a balance. + pub fn new(amount: T::Balance) -> Self { + PositiveImbalance(amount) + } + } + + /// Opaque, move-only struct with private fields that serves as a token denoting that + /// funds have been destroyed without any equal and opposite accounting. + #[must_use] + #[derive(RuntimeDebug, PartialEq, Eq)] + pub struct NegativeImbalance, I: 'static = ()>(T::Balance); + + impl, I: 'static> NegativeImbalance { + /// Create a new negative imbalance from a balance. + pub fn new(amount: T::Balance) -> Self { + NegativeImbalance(amount) + } + } + + impl, I: 'static> TryDrop for PositiveImbalance { + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } + } + + impl, I: 'static> Default for PositiveImbalance { + fn default() -> Self { + Self::zero() + } + } + + impl, I: 'static> Imbalance for PositiveImbalance { + type Opposite = NegativeImbalance; + + fn zero() -> Self { + Self(Zero::zero()) + } + fn drop_zero(self) -> result::Result<(), Self> { + if self.0.is_zero() { + Ok(()) + } else { + Err(self) + } + } + fn split(self, amount: T::Balance) -> (Self, Self) { + let first = self.0.min(amount); + let second = self.0 - first; + + mem::forget(self); + (Self(first), Self(second)) + } + fn extract(&mut self, amount: T::Balance) -> Self { + let new = self.0.min(amount); + self.0 -= new; + Self(new) + } + fn merge(mut self, other: Self) -> Self { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + + self + } + fn subsume(&mut self, other: Self) { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + } + fn offset(self, other: Self::Opposite) -> SameOrOther { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + match a.cmp(&b) { + Ordering::Greater => SameOrOther::Same(Self(a - b)), + Ordering::Less => SameOrOther::Other(NegativeImbalance::new(b - a)), + Ordering::Equal => SameOrOther::None, + } + } + fn peek(&self) -> T::Balance { + self.0 + } + } + + impl, I: 'static> TryMerge for PositiveImbalance { + fn try_merge(self, other: Self) -> Result { + Ok(self.merge(other)) + } + } + + impl, I: 'static> TryDrop for NegativeImbalance { + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } + } + + impl, I: 'static> Default for NegativeImbalance { + fn default() -> Self { + Self::zero() + } + } + + impl, I: 'static> Imbalance for NegativeImbalance { + type Opposite = PositiveImbalance; + + fn zero() -> Self { + Self(Zero::zero()) + } + fn drop_zero(self) -> result::Result<(), Self> { + if self.0.is_zero() { + Ok(()) + } else { + Err(self) + } + } + fn split(self, amount: T::Balance) -> (Self, Self) { + let first = self.0.min(amount); + let second = self.0 - first; + + mem::forget(self); + (Self(first), Self(second)) + } + fn extract(&mut self, amount: T::Balance) -> Self { + let new = self.0.min(amount); + self.0 -= new; + Self(new) + } + fn merge(mut self, other: Self) -> Self { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + + self + } + fn subsume(&mut self, other: Self) { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + } + fn offset(self, other: Self::Opposite) -> SameOrOther { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + match a.cmp(&b) { + Ordering::Greater => SameOrOther::Same(Self(a - b)), + Ordering::Less => SameOrOther::Other(PositiveImbalance::new(b - a)), + Ordering::Equal => SameOrOther::None, + } + } + fn peek(&self) -> T::Balance { + self.0 + } + } + + impl, I: 'static> TryMerge for NegativeImbalance { + fn try_merge(self, other: Self) -> Result { + Ok(self.merge(other)) + } + } + + impl, I: 'static> Drop for PositiveImbalance { + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + if !self.0.is_zero() { + >::mutate(|v| *v = v.saturating_add(self.0)); + Pallet::::deposit_event(Event::::Issued { amount: self.0 }); + } + } + } + + impl, I: 'static> Drop for NegativeImbalance { + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + if !self.0.is_zero() { + >::mutate(|v| *v = v.saturating_sub(self.0)); + Pallet::::deposit_event(Event::::Rescinded { amount: self.0 }); + } + } + } } impl, I: 'static> Currency for Pallet where - T::Balance: MaybeSerializeDeserialize + Debug, + T::Balance: MaybeSerializeDeserialize + Debug, { - type Balance = T::Balance; - type PositiveImbalance = PositiveImbalance; - type NegativeImbalance = NegativeImbalance; - - fn total_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).total() - } - - // Check if `value` amount of free balance can be slashed from `who`. - fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { - if value.is_zero() { - return true - } - Self::free_balance(who) >= value - } - - fn total_issuance() -> Self::Balance { - TotalIssuance::::get() - } - - fn active_issuance() -> Self::Balance { - >::active_issuance() - } - - fn deactivate(amount: Self::Balance) { - >::deactivate(amount); - } - - fn reactivate(amount: Self::Balance) { - >::reactivate(amount); - } - - fn minimum_balance() -> Self::Balance { - T::ExistentialDeposit::get() - } - - // Burn funds from the total issuance, returning a positive imbalance for the amount burned. - // Is a no-op if amount to be burned is zero. - fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { - if amount.is_zero() { - return PositiveImbalance::zero() - } - >::mutate(|issued| { - *issued = issued.checked_sub(&amount).unwrap_or_else(|| { - amount = *issued; - Zero::zero() - }); - }); - - Pallet::::deposit_event(Event::::Rescinded { amount }); - PositiveImbalance::new(amount) - } - - // Create new funds into the total issuance, returning a negative imbalance - // for the amount issued. - // Is a no-op if amount to be issued it zero. - fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { - if amount.is_zero() { - return NegativeImbalance::zero() - } - >::mutate(|issued| { - *issued = issued.checked_add(&amount).unwrap_or_else(|| { - amount = Self::Balance::max_value() - *issued; - Self::Balance::max_value() - }) - }); - - Pallet::::deposit_event(Event::::Issued { amount }); - NegativeImbalance::new(amount) - } - - fn free_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).free - } - - // Ensure that an account can withdraw from their free balance given any existing withdrawal - // restrictions like locks and vesting balance. - // Is a no-op if amount to be withdrawn is zero. - fn ensure_can_withdraw( - who: &T::AccountId, - amount: T::Balance, - _reasons: WithdrawReasons, - new_balance: T::Balance, - ) -> DispatchResult { - if amount.is_zero() { - return Ok(()) - } - ensure!(new_balance >= Self::account(who).frozen, Error::::LiquidityRestrictions); - Ok(()) - } - - // Transfer some free balance from `transactor` to `dest`, respecting existence requirements. - // Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`. - fn transfer( - transactor: &T::AccountId, - dest: &T::AccountId, - value: Self::Balance, - existence_requirement: ExistenceRequirement, - ) -> DispatchResult { - if value.is_zero() || transactor == dest { - return Ok(()) - } - let keep_alive = match existence_requirement { - ExistenceRequirement::KeepAlive => Preserve, - ExistenceRequirement::AllowDeath => Expendable, - }; - >::transfer(transactor, dest, value, keep_alive)?; - Ok(()) - } - - /// Slash a target account `who`, returning the negative imbalance created and any left over - /// amount that could not be slashed. - /// - /// Is a no-op if `value` to be slashed is zero or the account does not exist. - /// - /// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn - /// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid - /// having to draw from reserved funds, however we err on the side of punishment if things are - /// inconsistent or `can_slash` wasn't used appropriately. - fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()) - } - if Self::total_balance(who).is_zero() { - return (NegativeImbalance::zero(), value) - } - - - match Self::try_mutate_account_handling_dust( - who, - |account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> { - // Best value is the most amount we can slash following liveness rules. - let ed = T::ExistentialDeposit::get(); - let actual = match system::Pallet::::can_dec_provider(who) { - true => value.min(account.free), - false => value.min(account.free.saturating_sub(ed)), - }; - account.free.saturating_reduce(actual); - let remaining = value.saturating_sub(actual); - Ok((NegativeImbalance::new(actual), remaining)) - }, - ) { - Ok((imbalance, remaining)) => { - Self::deposit_event(Event::Slashed { - who: who.clone(), - amount: value.saturating_sub(remaining), - }); - (imbalance, remaining) - }, - Err(_) => (Self::NegativeImbalance::zero(), value), - } - } - - /// Deposit some `value` into the free balance of an existing target account `who`. - /// - /// Is a no-op if the `value` to be deposited is zero. - fn deposit_into_existing( - who: &T::AccountId, - value: Self::Balance, - ) -> Result { - if value.is_zero() { - return Ok(PositiveImbalance::zero()) - } - - Self::try_mutate_account_handling_dust( - who, - |account, is_new| -> Result { - ensure!(!is_new, Error::::DeadAccount); - account.free = account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); - Ok(PositiveImbalance::new(value)) - }, - ) - } - - /// Deposit some `value` into the free balance of `who`, possibly creating a new account. - /// - /// This function is a no-op if: - /// - the `value` to be deposited is zero; or - /// - the `value` to be deposited is less than the required ED and the account does not yet - /// exist; or - /// - the deposit would necessitate the account to exist and there are no provider references; - /// or - /// - `value` is so large it would cause the balance of `who` to overflow. - fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance { - if value.is_zero() { - return Self::PositiveImbalance::zero() - } - - Self::try_mutate_account_handling_dust( - who, - |account, is_new| -> Result { - let ed = T::ExistentialDeposit::get(); - ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); - - // defensive only: overflow should never happen, however in case it does, then this - // operation is a no-op. - account.free = match account.free.checked_add(&value) { - Some(x) => x, - None => return Ok(Self::PositiveImbalance::zero()), - }; - - Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); - Ok(PositiveImbalance::new(value)) - }, - ) - .unwrap_or_else(|_| Self::PositiveImbalance::zero()) - } - - /// Withdraw some free balance from an account, respecting existence requirements. - /// - /// Is a no-op if value to be withdrawn is zero. - fn withdraw( - who: &T::AccountId, - value: Self::Balance, - reasons: WithdrawReasons, - liveness: ExistenceRequirement, - ) -> result::Result { - if value.is_zero() { - return Ok(NegativeImbalance::zero()) - } - - Self::try_mutate_account_handling_dust( - who, - |account, _| -> Result { - let new_free_account = - account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; - - // bail if we need to keep the account alive and this would kill it. - let ed = T::ExistentialDeposit::get(); - let would_be_dead = new_free_account < ed; - let would_kill = would_be_dead && account.free >= ed; - ensure!(liveness == AllowDeath || !would_kill, Error::::Expendability); - - Self::ensure_can_withdraw(who, value, reasons, new_free_account)?; - - account.free = new_free_account; - - Self::deposit_event(Event::Withdraw { who: who.clone(), amount: value }); - Ok(NegativeImbalance::new(value)) - }, - ) - } - - /// Force the new free balance of a target account `who` to some new value `balance`. - fn make_free_balance_be( - who: &T::AccountId, - value: Self::Balance, - ) -> SignedImbalance { - Self::try_mutate_account_handling_dust( + type Balance = T::Balance; + type PositiveImbalance = PositiveImbalance; + type NegativeImbalance = NegativeImbalance; + + fn total_balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).total() + } + + // Check if `value` amount of free balance can be slashed from `who`. + fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { + if value.is_zero() { + return true; + } + Self::free_balance(who) >= value + } + + fn total_issuance() -> Self::Balance { + TotalIssuance::::get() + } + + fn active_issuance() -> Self::Balance { + >::active_issuance() + } + + fn deactivate(amount: Self::Balance) { + >::deactivate(amount); + } + + fn reactivate(amount: Self::Balance) { + >::reactivate(amount); + } + + fn minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + + // Burn funds from the total issuance, returning a positive imbalance for the amount burned. + // Is a no-op if amount to be burned is zero. + fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { + if amount.is_zero() { + return PositiveImbalance::zero(); + } + >::mutate(|issued| { + *issued = issued.checked_sub(&amount).unwrap_or_else(|| { + amount = *issued; + Zero::zero() + }); + }); + + Pallet::::deposit_event(Event::::Rescinded { amount }); + PositiveImbalance::new(amount) + } + + // Create new funds into the total issuance, returning a negative imbalance + // for the amount issued. + // Is a no-op if amount to be issued it zero. + fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { + if amount.is_zero() { + return NegativeImbalance::zero(); + } + >::mutate(|issued| { + *issued = issued.checked_add(&amount).unwrap_or_else(|| { + amount = Self::Balance::max_value() - *issued; + Self::Balance::max_value() + }) + }); + + Pallet::::deposit_event(Event::::Issued { amount }); + NegativeImbalance::new(amount) + } + + fn free_balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).free + } + + // Ensure that an account can withdraw from their free balance given any existing withdrawal + // restrictions like locks and vesting balance. + // Is a no-op if amount to be withdrawn is zero. + fn ensure_can_withdraw( + who: &T::AccountId, + amount: T::Balance, + _reasons: WithdrawReasons, + new_balance: T::Balance, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + ensure!( + new_balance >= Self::account(who).frozen, + Error::::LiquidityRestrictions + ); + Ok(()) + } + + // Transfer some free balance from `transactor` to `dest`, respecting existence requirements. + // Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`. + fn transfer( + transactor: &T::AccountId, + dest: &T::AccountId, + value: Self::Balance, + existence_requirement: ExistenceRequirement, + ) -> DispatchResult { + if value.is_zero() || transactor == dest { + return Ok(()); + } + let keep_alive = match existence_requirement { + ExistenceRequirement::KeepAlive => Preserve, + ExistenceRequirement::AllowDeath => Expendable, + }; + >::transfer(transactor, dest, value, keep_alive)?; + Ok(()) + } + + /// Slash a target account `who`, returning the negative imbalance created and any left over + /// amount that could not be slashed. + /// + /// Is a no-op if `value` to be slashed is zero or the account does not exist. + /// + /// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn + /// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid + /// having to draw from reserved funds, however we err on the side of punishment if things are + /// inconsistent or `can_slash` wasn't used appropriately. + fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { + if value.is_zero() { + return (NegativeImbalance::zero(), Zero::zero()); + } + if Self::total_balance(who).is_zero() { + return (NegativeImbalance::zero(), value); + } + + match Self::try_mutate_account_handling_dust( + who, + |account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> { + // Best value is the most amount we can slash following liveness rules. + let ed = T::ExistentialDeposit::get(); + let actual = match system::Pallet::::can_dec_provider(who) { + true => value.min(account.free), + false => value.min(account.free.saturating_sub(ed)), + }; + account.free.saturating_reduce(actual); + let remaining = value.saturating_sub(actual); + Ok((NegativeImbalance::new(actual), remaining)) + }, + ) { + Ok((imbalance, remaining)) => { + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: value.saturating_sub(remaining), + }); + (imbalance, remaining) + } + Err(_) => (Self::NegativeImbalance::zero(), value), + } + } + + /// Deposit some `value` into the free balance of an existing target account `who`. + /// + /// Is a no-op if the `value` to be deposited is zero. + fn deposit_into_existing( + who: &T::AccountId, + value: Self::Balance, + ) -> Result { + if value.is_zero() { + return Ok(PositiveImbalance::zero()); + } + + Self::try_mutate_account_handling_dust( + who, + |account, is_new| -> Result { + ensure!(!is_new, Error::::DeadAccount); + account.free = account + .free + .checked_add(&value) + .ok_or(ArithmeticError::Overflow)?; + Self::deposit_event(Event::Deposit { + who: who.clone(), + amount: value, + }); + Ok(PositiveImbalance::new(value)) + }, + ) + } + + /// Deposit some `value` into the free balance of `who`, possibly creating a new account. + /// + /// This function is a no-op if: + /// - the `value` to be deposited is zero; or + /// - the `value` to be deposited is less than the required ED and the account does not yet + /// exist; or + /// - the deposit would necessitate the account to exist and there are no provider references; + /// or + /// - `value` is so large it would cause the balance of `who` to overflow. + fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance { + if value.is_zero() { + return Self::PositiveImbalance::zero(); + } + + Self::try_mutate_account_handling_dust( + who, + |account, is_new| -> Result { + let ed = T::ExistentialDeposit::get(); + ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); + + // defensive only: overflow should never happen, however in case it does, then this + // operation is a no-op. + account.free = match account.free.checked_add(&value) { + Some(x) => x, + None => return Ok(Self::PositiveImbalance::zero()), + }; + + Self::deposit_event(Event::Deposit { + who: who.clone(), + amount: value, + }); + Ok(PositiveImbalance::new(value)) + }, + ) + .unwrap_or_else(|_| Self::PositiveImbalance::zero()) + } + + /// Withdraw some free balance from an account, respecting existence requirements. + /// + /// Is a no-op if value to be withdrawn is zero. + fn withdraw( + who: &T::AccountId, + value: Self::Balance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> result::Result { + if value.is_zero() { + return Ok(NegativeImbalance::zero()); + } + + Self::try_mutate_account_handling_dust( + who, + |account, _| -> Result { + let new_free_account = account + .free + .checked_sub(&value) + .ok_or(Error::::InsufficientBalance)?; + + // bail if we need to keep the account alive and this would kill it. + let ed = T::ExistentialDeposit::get(); + let would_be_dead = new_free_account < ed; + let would_kill = would_be_dead && account.free >= ed; + ensure!( + liveness == AllowDeath || !would_kill, + Error::::Expendability + ); + + Self::ensure_can_withdraw(who, value, reasons, new_free_account)?; + + account.free = new_free_account; + + Self::deposit_event(Event::Withdraw { + who: who.clone(), + amount: value, + }); + Ok(NegativeImbalance::new(value)) + }, + ) + } + + /// Force the new free balance of a target account `who` to some new value `balance`. + fn make_free_balance_be( + who: &T::AccountId, + value: Self::Balance, + ) -> SignedImbalance { + Self::try_mutate_account_handling_dust( who, |account, is_new| @@ -506,306 +525,334 @@ where }, ) .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) - } + } } impl, I: 'static> ReservableCurrency for Pallet where - T::Balance: MaybeSerializeDeserialize + Debug, + T::Balance: MaybeSerializeDeserialize + Debug, { - /// Check if `who` can reserve `value` from their free balance. - /// - /// Always `true` if value to be reserved is zero. - fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { - if value.is_zero() { - return true - } - Self::account(who).free.checked_sub(&value).is_some_and(|new_balance| { - new_balance >= T::ExistentialDeposit::get() && - Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance) - .is_ok() - }) - } - - fn reserved_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).reserved - } - - /// Move `value` from the free balance from `who` to their reserved balance. - /// - /// Is a no-op if value to be reserved is zero. - fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult { - if value.is_zero() { - return Ok(()) - } - - Self::try_mutate_account_handling_dust(who, |account, _| -> DispatchResult { - account.free = - account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; - account.reserved = - account.reserved.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, account.free) - })?; - - Self::deposit_event(Event::Reserved { who: who.clone(), amount: value }); - Ok(()) - } - - /// Unreserve some funds, returning any amount that was unable to be unreserved. - /// - /// Is a no-op if the value to be unreserved is zero or the account does not exist. - /// - /// NOTE: returns amount value which wasn't successfully unreserved. - fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { - if value.is_zero() { - return Zero::zero() - } - if Self::total_balance(who).is_zero() { - return value - } - - let actual = match Self::mutate_account_handling_dust(who, |account| { - let actual = cmp::min(account.reserved, value); - account.reserved -= actual; - // defensive only: this can never fail since total issuance which is at least - // free+reserved fits into the same data type. - account.free = account.free.defensive_saturating_add(actual); - actual - }) { - Ok(x) => x, - Err(_) => { - // This should never happen since we don't alter the total amount in the account. - // If it ever does, then we should fail gracefully though, indicating that nothing - // could be done. - return value - }, - }; - - Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual }); - value - actual - } - - /// Slash from reserved balance, returning the negative imbalance created, - /// and any amount that was unable to be slashed. - /// - /// Is a no-op if the value to be slashed is zero or the account does not exist. - fn slash_reserved( - who: &T::AccountId, - value: Self::Balance, - ) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()) - } - if Self::total_balance(who).is_zero() { - return (NegativeImbalance::zero(), value) - } - - // NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an - // account is attempted to be illegally destroyed. - - match Self::mutate_account_handling_dust(who, |account| { - let actual = value.min(account.reserved); - account.reserved.saturating_reduce(actual); - - // underflow should never happen, but it if does, there's nothing to be done here. - (NegativeImbalance::new(actual), value.saturating_sub(actual)) - }) { - Ok((imbalance, not_slashed)) => { - Self::deposit_event(Event::Slashed { - who: who.clone(), - amount: value.saturating_sub(not_slashed), - }); - (imbalance, not_slashed) - }, - Err(_) => (Self::NegativeImbalance::zero(), value), - } - } - - /// Move the reserved balance of one account into the balance of another, according to `status`. - /// - /// Is a no-op if: - /// - the value to be moved is zero; or - /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. - /// - /// This is `Polite` and thus will not repatriate any funds which would lead the total balance - /// to be less than the frozen amount. Returns `Ok` with the actual amount of funds moved, - /// which may be less than `value` since the operation is done an a `BestEffort` basis. - fn repatriate_reserved( - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: Self::Balance, - status: Status, - ) -> Result { - let actual = - Self::do_transfer_reserved(slashed, beneficiary, value, BestEffort, Polite, status)?; - Ok(value.saturating_sub(actual)) - } + /// Check if `who` can reserve `value` from their free balance. + /// + /// Always `true` if value to be reserved is zero. + fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { + if value.is_zero() { + return true; + } + Self::account(who) + .free + .checked_sub(&value) + .is_some_and(|new_balance| { + new_balance >= T::ExistentialDeposit::get() + && Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance) + .is_ok() + }) + } + + fn reserved_balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).reserved + } + + /// Move `value` from the free balance from `who` to their reserved balance. + /// + /// Is a no-op if value to be reserved is zero. + fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult { + if value.is_zero() { + return Ok(()); + } + + Self::try_mutate_account_handling_dust(who, |account, _| -> DispatchResult { + account.free = account + .free + .checked_sub(&value) + .ok_or(Error::::InsufficientBalance)?; + account.reserved = account + .reserved + .checked_add(&value) + .ok_or(ArithmeticError::Overflow)?; + Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, account.free) + })?; + + Self::deposit_event(Event::Reserved { + who: who.clone(), + amount: value, + }); + Ok(()) + } + + /// Unreserve some funds, returning any amount that was unable to be unreserved. + /// + /// Is a no-op if the value to be unreserved is zero or the account does not exist. + /// + /// NOTE: returns amount value which wasn't successfully unreserved. + fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { + if value.is_zero() { + return Zero::zero(); + } + if Self::total_balance(who).is_zero() { + return value; + } + + let actual = match Self::mutate_account_handling_dust(who, |account| { + let actual = cmp::min(account.reserved, value); + account.reserved -= actual; + // defensive only: this can never fail since total issuance which is at least + // free+reserved fits into the same data type. + account.free = account.free.defensive_saturating_add(actual); + actual + }) { + Ok(x) => x, + Err(_) => { + // This should never happen since we don't alter the total amount in the account. + // If it ever does, then we should fail gracefully though, indicating that nothing + // could be done. + return value; + } + }; + + Self::deposit_event(Event::Unreserved { + who: who.clone(), + amount: actual, + }); + value - actual + } + + /// Slash from reserved balance, returning the negative imbalance created, + /// and any amount that was unable to be slashed. + /// + /// Is a no-op if the value to be slashed is zero or the account does not exist. + fn slash_reserved( + who: &T::AccountId, + value: Self::Balance, + ) -> (Self::NegativeImbalance, Self::Balance) { + if value.is_zero() { + return (NegativeImbalance::zero(), Zero::zero()); + } + if Self::total_balance(who).is_zero() { + return (NegativeImbalance::zero(), value); + } + + // NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an + // account is attempted to be illegally destroyed. + + match Self::mutate_account_handling_dust(who, |account| { + let actual = value.min(account.reserved); + account.reserved.saturating_reduce(actual); + + // underflow should never happen, but it if does, there's nothing to be done here. + (NegativeImbalance::new(actual), value.saturating_sub(actual)) + }) { + Ok((imbalance, not_slashed)) => { + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: value.saturating_sub(not_slashed), + }); + (imbalance, not_slashed) + } + Err(_) => (Self::NegativeImbalance::zero(), value), + } + } + + /// Move the reserved balance of one account into the balance of another, according to `status`. + /// + /// Is a no-op if: + /// - the value to be moved is zero; or + /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. + /// + /// This is `Polite` and thus will not repatriate any funds which would lead the total balance + /// to be less than the frozen amount. Returns `Ok` with the actual amount of funds moved, + /// which may be less than `value` since the operation is done an a `BestEffort` basis. + fn repatriate_reserved( + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: Status, + ) -> Result { + let actual = + Self::do_transfer_reserved(slashed, beneficiary, value, BestEffort, Polite, status)?; + Ok(value.saturating_sub(actual)) + } } impl, I: 'static> NamedReservableCurrency for Pallet where - T::Balance: MaybeSerializeDeserialize + Debug, + T::Balance: MaybeSerializeDeserialize + Debug, { - type ReserveIdentifier = T::ReserveIdentifier; - - fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance { - let reserves = Self::reserves(who); - reserves - .binary_search_by_key(id, |data| data.id) - .map(|index| reserves[index].amount) - .unwrap_or_default() - } - - /// Move `value` from the free balance from `who` to a named reserve balance. - /// - /// Is a no-op if value to be reserved is zero. - fn reserve_named( - id: &Self::ReserveIdentifier, - who: &T::AccountId, - value: Self::Balance, - ) -> DispatchResult { - if value.is_zero() { - return Ok(()) - } - - Reserves::::try_mutate(who, |reserves| -> DispatchResult { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - reserves[index].amount = reserves[index] - .amount - .checked_add(&value) - .ok_or(ArithmeticError::Overflow)?; - }, - Err(index) => { - reserves - .try_insert(index, ReserveData { id: *id, amount: value }) - .map_err(|_| Error::::TooManyReserves)?; - }, - }; - >::reserve(who, value)?; - Ok(()) - }) - } - - /// Unreserve some funds, returning any amount that was unable to be unreserved. - /// - /// Is a no-op if the value to be unreserved is zero. - fn unreserve_named( - id: &Self::ReserveIdentifier, - who: &T::AccountId, - value: Self::Balance, - ) -> Self::Balance { - if value.is_zero() { - return Zero::zero() - } - - Reserves::::mutate_exists(who, |maybe_reserves| -> Self::Balance { - if let Some(reserves) = maybe_reserves.as_mut() { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let to_change = cmp::min(reserves[index].amount, value); - - let remain = >::unreserve(who, to_change); - - // remain should always be zero but just to be defensive here. - let actual = to_change.defensive_saturating_sub(remain); - - // `actual <= to_change` and `to_change <= amount`; qed; - reserves[index].amount -= actual; - - if reserves[index].amount.is_zero() { - if reserves.len() == 1 { - // no more named reserves - *maybe_reserves = None; - } else { - // remove this named reserve - reserves.remove(index); - } - } - - value - actual - }, - Err(_) => value, - } - } else { - value - } - }) - } - - /// Slash from reserved balance, returning the negative imbalance created, - /// and any amount that was unable to be slashed. - /// - /// Is a no-op if the value to be slashed is zero. - fn slash_reserved_named( - id: &Self::ReserveIdentifier, - who: &T::AccountId, - value: Self::Balance, - ) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()) - } - - Reserves::::mutate(who, |reserves| -> (Self::NegativeImbalance, Self::Balance) { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let to_change = cmp::min(reserves[index].amount, value); - - let (imb, remain) = - >::slash_reserved(who, to_change); - - // remain should always be zero but just to be defensive here. - let actual = to_change.defensive_saturating_sub(remain); - - // `actual <= to_change` and `to_change <= amount`; qed; - reserves[index].amount -= actual; - - Self::deposit_event(Event::Slashed { who: who.clone(), amount: actual }); - (imb, value - actual) - }, - Err(_) => (NegativeImbalance::zero(), value), - } - }) - } - - /// Move the reserved balance of one account into the balance of another, according to `status`. - /// If `status` is `Reserved`, the balance will be reserved with given `id`. - /// - /// Is a no-op if: - /// - the value to be moved is zero; or - /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. - fn repatriate_reserved_named( - id: &Self::ReserveIdentifier, - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: Self::Balance, - status: Status, - ) -> Result { - if value.is_zero() { - return Ok(Zero::zero()) - } - - if slashed == beneficiary { - return match status { - Status::Free => Ok(Self::unreserve_named(id, slashed, value)), - Status::Reserved => - Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))), - } - } - - Reserves::::try_mutate(slashed, |reserves| -> Result { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let to_change = cmp::min(reserves[index].amount, value); - - let actual = if status == Status::Reserved { - // make it the reserved under same identifier - Reserves::::try_mutate( - beneficiary, - |reserves| -> Result { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let remain = + type ReserveIdentifier = T::ReserveIdentifier; + + fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance { + let reserves = Self::reserves(who); + reserves + .binary_search_by_key(id, |data| data.id) + .map(|index| reserves[index].amount) + .unwrap_or_default() + } + + /// Move `value` from the free balance from `who` to a named reserve balance. + /// + /// Is a no-op if value to be reserved is zero. + fn reserve_named( + id: &Self::ReserveIdentifier, + who: &T::AccountId, + value: Self::Balance, + ) -> DispatchResult { + if value.is_zero() { + return Ok(()); + } + + Reserves::::try_mutate(who, |reserves| -> DispatchResult { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + reserves[index].amount = reserves[index] + .amount + .checked_add(&value) + .ok_or(ArithmeticError::Overflow)?; + } + Err(index) => { + reserves + .try_insert( + index, + ReserveData { + id: *id, + amount: value, + }, + ) + .map_err(|_| Error::::TooManyReserves)?; + } + }; + >::reserve(who, value)?; + Ok(()) + }) + } + + /// Unreserve some funds, returning any amount that was unable to be unreserved. + /// + /// Is a no-op if the value to be unreserved is zero. + fn unreserve_named( + id: &Self::ReserveIdentifier, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + if value.is_zero() { + return Zero::zero(); + } + + Reserves::::mutate_exists(who, |maybe_reserves| -> Self::Balance { + if let Some(reserves) = maybe_reserves.as_mut() { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + let to_change = cmp::min(reserves[index].amount, value); + + let remain = >::unreserve(who, to_change); + + // remain should always be zero but just to be defensive here. + let actual = to_change.defensive_saturating_sub(remain); + + // `actual <= to_change` and `to_change <= amount`; qed; + reserves[index].amount -= actual; + + if reserves[index].amount.is_zero() { + if reserves.len() == 1 { + // no more named reserves + *maybe_reserves = None; + } else { + // remove this named reserve + reserves.remove(index); + } + } + + value - actual + } + Err(_) => value, + } + } else { + value + } + }) + } + + /// Slash from reserved balance, returning the negative imbalance created, + /// and any amount that was unable to be slashed. + /// + /// Is a no-op if the value to be slashed is zero. + fn slash_reserved_named( + id: &Self::ReserveIdentifier, + who: &T::AccountId, + value: Self::Balance, + ) -> (Self::NegativeImbalance, Self::Balance) { + if value.is_zero() { + return (NegativeImbalance::zero(), Zero::zero()); + } + + Reserves::::mutate( + who, + |reserves| -> (Self::NegativeImbalance, Self::Balance) { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + let to_change = cmp::min(reserves[index].amount, value); + + let (imb, remain) = + >::slash_reserved(who, to_change); + + // remain should always be zero but just to be defensive here. + let actual = to_change.defensive_saturating_sub(remain); + + // `actual <= to_change` and `to_change <= amount`; qed; + reserves[index].amount -= actual; + + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: actual, + }); + (imb, value - actual) + } + Err(_) => (NegativeImbalance::zero(), value), + } + }, + ) + } + + /// Move the reserved balance of one account into the balance of another, according to `status`. + /// If `status` is `Reserved`, the balance will be reserved with given `id`. + /// + /// Is a no-op if: + /// - the value to be moved is zero; or + /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. + fn repatriate_reserved_named( + id: &Self::ReserveIdentifier, + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: Status, + ) -> Result { + if value.is_zero() { + return Ok(Zero::zero()); + } + + if slashed == beneficiary { + return match status { + Status::Free => Ok(Self::unreserve_named(id, slashed, value)), + Status::Reserved => { + Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))) + } + }; + } + + Reserves::::try_mutate( + slashed, + |reserves| -> Result { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + let to_change = cmp::min(reserves[index].amount, value); + + let actual = if status == Status::Reserved { + // make it the reserved under same identifier + Reserves::::try_mutate( + beneficiary, + |reserves| -> Result { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + let remain = >::repatriate_reserved( slashed, beneficiary, @@ -813,18 +860,19 @@ where status, )?; - // remain should always be zero but just to be defensive - // here. - let actual = to_change.defensive_saturating_sub(remain); + // remain should always be zero but just to be defensive + // here. + let actual = to_change.defensive_saturating_sub(remain); - // this add can't overflow but just to be defensive. - reserves[index].amount = - reserves[index].amount.defensive_saturating_add(actual); + // this add can't overflow but just to be defensive. + reserves[index].amount = reserves[index] + .amount + .defensive_saturating_add(actual); - Ok(actual) - }, - Err(index) => { - let remain = + Ok(actual) + } + Err(index) => { + let remain = >::repatriate_reserved( slashed, beneficiary, @@ -832,120 +880,132 @@ where status, )?; - // remain should always be zero but just to be defensive - // here - let actual = to_change.defensive_saturating_sub(remain); - - reserves - .try_insert( - index, - ReserveData { id: *id, amount: actual }, - ) - .map_err(|_| Error::::TooManyReserves)?; - - Ok(actual) - }, - } - }, - )? - } else { - let remain = >::repatriate_reserved( - slashed, - beneficiary, - to_change, - status, - )?; - - // remain should always be zero but just to be defensive here - to_change.defensive_saturating_sub(remain) - }; - - // `actual <= to_change` and `to_change <= amount`; qed; - reserves[index].amount -= actual; - - Ok(value - actual) - }, - Err(_) => Ok(value), - } - }) - } + // remain should always be zero but just to be defensive + // here + let actual = to_change.defensive_saturating_sub(remain); + + reserves + .try_insert( + index, + ReserveData { + id: *id, + amount: actual, + }, + ) + .map_err(|_| Error::::TooManyReserves)?; + + Ok(actual) + } + } + }, + )? + } else { + let remain = >::repatriate_reserved( + slashed, + beneficiary, + to_change, + status, + )?; + + // remain should always be zero but just to be defensive here + to_change.defensive_saturating_sub(remain) + }; + + // `actual <= to_change` and `to_change <= amount`; qed; + reserves[index].amount -= actual; + + Ok(value - actual) + } + Err(_) => Ok(value), + } + }, + ) + } } impl, I: 'static> LockableCurrency for Pallet where - T::Balance: MaybeSerializeDeserialize + Debug, + T::Balance: MaybeSerializeDeserialize + Debug, { - type Moment = BlockNumberFor; - - type MaxLocks = T::MaxLocks; - - // Set or alter a lock on the balance of `who`. - fn set_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - reasons: WithdrawReasons, - ) { - if reasons.is_empty() || amount.is_zero() { - Self::remove_lock(id, who); - return - } - - let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); - let mut locks = Self::locks(who) - .into_iter() - .filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock) - } - Self::update_locks(who, &locks[..]); - } - - // Extend a lock on the balance of `who`. - // Is a no-op if lock amount is zero or `reasons` `is_none()`. - fn extend_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - reasons: WithdrawReasons, - ) { - if amount.is_zero() || reasons.is_empty() { - return - } - let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); - let mut locks = Self::locks(who) - .into_iter() - .filter_map(|l| { - if l.id == id { - new_lock.take().map(|nl| BalanceLock { - id: l.id, - amount: l.amount.max(nl.amount), - reasons: l.reasons | nl.reasons, - }) - } else { - Some(l) - } - }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock) - } - Self::update_locks(who, &locks[..]); - } - - fn remove_lock(id: LockIdentifier, who: &T::AccountId) { - let mut locks = Self::locks(who); - locks.retain(|l| l.id != id); - Self::update_locks(who, &locks[..]); - } + type Moment = BlockNumberFor; + + type MaxLocks = T::MaxLocks; + + // Set or alter a lock on the balance of `who`. + fn set_lock( + id: LockIdentifier, + who: &T::AccountId, + amount: T::Balance, + reasons: WithdrawReasons, + ) { + if reasons.is_empty() || amount.is_zero() { + Self::remove_lock(id, who); + return; + } + + let mut new_lock = Some(BalanceLock { + id, + amount, + reasons: reasons.into(), + }); + let mut locks = Self::locks(who) + .into_iter() + .filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) }) + .collect::>(); + if let Some(lock) = new_lock { + locks.push(lock) + } + Self::update_locks(who, &locks[..]); + } + + // Extend a lock on the balance of `who`. + // Is a no-op if lock amount is zero or `reasons` `is_none()`. + fn extend_lock( + id: LockIdentifier, + who: &T::AccountId, + amount: T::Balance, + reasons: WithdrawReasons, + ) { + if amount.is_zero() || reasons.is_empty() { + return; + } + let mut new_lock = Some(BalanceLock { + id, + amount, + reasons: reasons.into(), + }); + let mut locks = Self::locks(who) + .into_iter() + .filter_map(|l| { + if l.id == id { + new_lock.take().map(|nl| BalanceLock { + id: l.id, + amount: l.amount.max(nl.amount), + reasons: l.reasons | nl.reasons, + }) + } else { + Some(l) + } + }) + .collect::>(); + if let Some(lock) = new_lock { + locks.push(lock) + } + Self::update_locks(who, &locks[..]); + } + + fn remove_lock(id: LockIdentifier, who: &T::AccountId) { + let mut locks = Self::locks(who); + locks.retain(|l| l.id != id); + Self::update_locks(who, &locks[..]); + } } impl, I: 'static> InspectLockableCurrency for Pallet { - fn balance_locked(id: LockIdentifier, who: &T::AccountId) -> Self::Balance { - Self::locks(who) - .into_iter() - .filter(|l| l.id == id) - .fold(Zero::zero(), |acc, l| acc + l.amount) - } + fn balance_locked(id: LockIdentifier, who: &T::AccountId) -> Self::Balance { + Self::locks(who) + .into_iter() + .filter(|l| l.id == id) + .fold(Zero::zero(), |acc, l| acc + l.amount) + } } diff --git a/pallets/balances/src/impl_fungible.rs b/pallets/balances/src/impl_fungible.rs index a53cce07..35f4b8df 100644 --- a/pallets/balances/src/impl_fungible.rs +++ b/pallets/balances/src/impl_fungible.rs @@ -18,368 +18,401 @@ //! Implementation of `fungible` traits for Balances pallet. use super::*; use frame_support::traits::{ - tokens::{ - Fortitude, - Preservation::{self, Preserve, Protect}, - Provenance::{self, Minted}, - }, - AccountTouch, + tokens::{ + Fortitude, + Preservation::{self, Preserve, Protect}, + Provenance::{self, Minted}, + }, + AccountTouch, }; impl, I: 'static> fungible::Inspect for Pallet { - type Balance = T::Balance; - - fn total_issuance() -> Self::Balance { - TotalIssuance::::get() - } - fn active_issuance() -> Self::Balance { - TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) - } - fn minimum_balance() -> Self::Balance { - T::ExistentialDeposit::get() - } - fn total_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).total() - } - fn balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).free - } - fn reducible_balance( - who: &T::AccountId, - preservation: Preservation, - force: Fortitude, - ) -> Self::Balance { - let a = Self::account(who); - let mut untouchable = Zero::zero(); - if force == Polite { - // Frozen balance applies to total. Anything on hold therefore gets discounted from the - // limit given by the freezes. - untouchable = a.frozen.saturating_sub(a.reserved); - } - // If we want to keep our provider ref.. - if preservation == Preserve + type Balance = T::Balance; + + fn total_issuance() -> Self::Balance { + TotalIssuance::::get() + } + fn active_issuance() -> Self::Balance { + TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) + } + fn minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + fn total_balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).total() + } + fn balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).free + } + fn reducible_balance( + who: &T::AccountId, + preservation: Preservation, + force: Fortitude, + ) -> Self::Balance { + let a = Self::account(who); + let mut untouchable = Zero::zero(); + if force == Polite { + // Frozen balance applies to total. Anything on hold therefore gets discounted from the + // limit given by the freezes. + untouchable = a.frozen.saturating_sub(a.reserved); + } + // If we want to keep our provider ref.. + if preservation == Preserve // ..or we don't want the account to die and our provider ref is needed for it to live.. || preservation == Protect && !a.free.is_zero() && frame_system::Pallet::::providers(who) == 1 // ..or we don't care about the account dying but our provider ref is required.. || preservation == Expendable && !a.free.is_zero() && !frame_system::Pallet::::can_dec_provider(who) - { - // ..then the ED needed.. - untouchable = untouchable.max(T::ExistentialDeposit::get()); - } - // Liquid balance is what is neither on hold nor frozen/required for provider. - a.free.saturating_sub(untouchable) - } - fn can_deposit( - who: &T::AccountId, - amount: Self::Balance, - provenance: Provenance, - ) -> DepositConsequence { - if amount.is_zero() { - return DepositConsequence::Success - } - - if provenance == Minted && TotalIssuance::::get().checked_add(&amount).is_none() { - return DepositConsequence::Overflow - } - - let account = Self::account(who); - let new_free = match account.free.checked_add(&amount) { - None => return DepositConsequence::Overflow, - Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum, - Some(x) => x, - }; - - match account.reserved.checked_add(&new_free) { - Some(_) => {}, - None => return DepositConsequence::Overflow, - }; - - // NOTE: We assume that we are a provider, so don't need to do any checks in the - // case of account creation. - - DepositConsequence::Success - } - fn can_withdraw( - who: &T::AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence { - if amount.is_zero() { - return WithdrawConsequence::Success - } - - if TotalIssuance::::get().checked_sub(&amount).is_none() { - return WithdrawConsequence::Underflow - } - - let account = Self::account(who); - let new_free_balance = match account.free.checked_sub(&amount) { - Some(x) => x, - None => return WithdrawConsequence::BalanceLow, - }; - - let liquid = Self::reducible_balance(who, Expendable, Polite); - if amount > liquid { - return WithdrawConsequence::Frozen - } - - // Provider restriction - total account balance cannot be reduced to zero if it cannot - // sustain the loss of a provider reference. - // NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes, - // then this will need to adapt accordingly. - let ed = T::ExistentialDeposit::get(); - let success = if new_free_balance < ed { - if frame_system::Pallet::::can_dec_provider(who) { - WithdrawConsequence::ReducedToZero(new_free_balance) - } else { - return WithdrawConsequence::WouldDie - } - } else { - WithdrawConsequence::Success - }; - - let new_total_balance = new_free_balance.saturating_add(account.reserved); - - // Eventual free funds must be no less than the frozen balance. - if new_total_balance < account.frozen { - return WithdrawConsequence::Frozen - } - - success - } + { + // ..then the ED needed.. + untouchable = untouchable.max(T::ExistentialDeposit::get()); + } + // Liquid balance is what is neither on hold nor frozen/required for provider. + a.free.saturating_sub(untouchable) + } + fn can_deposit( + who: &T::AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence { + if amount.is_zero() { + return DepositConsequence::Success; + } + + if provenance == Minted && TotalIssuance::::get().checked_add(&amount).is_none() { + return DepositConsequence::Overflow; + } + + let account = Self::account(who); + let new_free = match account.free.checked_add(&amount) { + None => return DepositConsequence::Overflow, + Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum, + Some(x) => x, + }; + + match account.reserved.checked_add(&new_free) { + Some(_) => {} + None => return DepositConsequence::Overflow, + }; + + // NOTE: We assume that we are a provider, so don't need to do any checks in the + // case of account creation. + + DepositConsequence::Success + } + fn can_withdraw( + who: &T::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + if amount.is_zero() { + return WithdrawConsequence::Success; + } + + if TotalIssuance::::get().checked_sub(&amount).is_none() { + return WithdrawConsequence::Underflow; + } + + let account = Self::account(who); + let new_free_balance = match account.free.checked_sub(&amount) { + Some(x) => x, + None => return WithdrawConsequence::BalanceLow, + }; + + let liquid = Self::reducible_balance(who, Expendable, Polite); + if amount > liquid { + return WithdrawConsequence::Frozen; + } + + // Provider restriction - total account balance cannot be reduced to zero if it cannot + // sustain the loss of a provider reference. + // NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes, + // then this will need to adapt accordingly. + let ed = T::ExistentialDeposit::get(); + let success = if new_free_balance < ed { + if frame_system::Pallet::::can_dec_provider(who) { + WithdrawConsequence::ReducedToZero(new_free_balance) + } else { + return WithdrawConsequence::WouldDie; + } + } else { + WithdrawConsequence::Success + }; + + let new_total_balance = new_free_balance.saturating_add(account.reserved); + + // Eventual free funds must be no less than the frozen balance. + if new_total_balance < account.frozen { + return WithdrawConsequence::Frozen; + } + + success + } } impl, I: 'static> fungible::Unbalanced for Pallet { - fn handle_dust(dust: fungible::Dust) { - T::DustRemoval::on_unbalanced(dust.into_credit()); - } - fn write_balance( - who: &T::AccountId, - amount: Self::Balance, - ) -> Result, DispatchError> { - let max_reduction = - >::reducible_balance(who, Expendable, Force); - let (result, maybe_dust) = Self::mutate_account(who, |account| -> DispatchResult { - // Make sure the reduction (if there is one) is no more than the maximum allowed. - let reduction = account.free.saturating_sub(amount); - ensure!(reduction <= max_reduction, Error::::InsufficientBalance); - - account.free = amount; - Ok(()) - })?; - result?; - Ok(maybe_dust) - } - - fn set_total_issuance(amount: Self::Balance) { - TotalIssuance::::mutate(|t| *t = amount); - } - - fn deactivate(amount: Self::Balance) { - InactiveIssuance::::mutate(|b| { - // InactiveIssuance cannot be greater than TotalIssuance. - *b = b.saturating_add(amount).min(TotalIssuance::::get()); - }); - } - - fn reactivate(amount: Self::Balance) { - InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); - } + fn handle_dust(dust: fungible::Dust) { + T::DustRemoval::on_unbalanced(dust.into_credit()); + } + fn write_balance( + who: &T::AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + let max_reduction = + >::reducible_balance(who, Expendable, Force); + let (result, maybe_dust) = Self::mutate_account(who, |account| -> DispatchResult { + // Make sure the reduction (if there is one) is no more than the maximum allowed. + let reduction = account.free.saturating_sub(amount); + ensure!( + reduction <= max_reduction, + Error::::InsufficientBalance + ); + + account.free = amount; + Ok(()) + })?; + result?; + Ok(maybe_dust) + } + + fn set_total_issuance(amount: Self::Balance) { + TotalIssuance::::mutate(|t| *t = amount); + } + + fn deactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| { + // InactiveIssuance cannot be greater than TotalIssuance. + *b = b.saturating_add(amount).min(TotalIssuance::::get()); + }); + } + + fn reactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); + } } impl, I: 'static> fungible::Mutate for Pallet { - fn done_mint_into(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Minted { who: who.clone(), amount }); - } - fn done_burn_from(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Burned { who: who.clone(), amount }); - } - fn done_shelve(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Suspended { who: who.clone(), amount }); - } - fn done_restore(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Restored { who: who.clone(), amount }); - } - fn done_transfer(source: &T::AccountId, dest: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Transfer { - from: source.clone(), - to: dest.clone(), - amount, - }); - } + fn done_mint_into(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Minted { + who: who.clone(), + amount, + }); + } + fn done_burn_from(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Burned { + who: who.clone(), + amount, + }); + } + fn done_shelve(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Suspended { + who: who.clone(), + amount, + }); + } + fn done_restore(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Restored { + who: who.clone(), + amount, + }); + } + fn done_transfer(source: &T::AccountId, dest: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Transfer { + from: source.clone(), + to: dest.clone(), + amount, + }); + } } impl, I: 'static> fungible::MutateHold for Pallet {} impl, I: 'static> fungible::InspectHold for Pallet { - type Reason = T::RuntimeHoldReason; - - fn total_balance_on_hold(who: &T::AccountId) -> T::Balance { - Self::account(who).reserved - } - fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance { - // The total balance must never drop below the freeze requirements if we're not forcing: - let a = Self::account(who); - let unavailable = if force == Force { - Self::Balance::zero() - } else { - // The freeze lock applies to the total balance, so we can discount the free balance - // from the amount which the total reserved balance must provide to satisfy it. - a.frozen.saturating_sub(a.free) - }; - a.reserved.saturating_sub(unavailable) - } - fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> T::Balance { - Holds::::get(who) - .iter() - .find(|x| &x.id == reason) - .map_or_else(Zero::zero, |x| x.amount) - } - fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool { - if frame_system::Pallet::::providers(who) == 0 { - return false - } - let holds = Holds::::get(who); - if holds.is_full() && !holds.iter().any(|x| &x.id == reason) { - return false - } - true - } + type Reason = T::RuntimeHoldReason; + + fn total_balance_on_hold(who: &T::AccountId) -> T::Balance { + Self::account(who).reserved + } + fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance { + // The total balance must never drop below the freeze requirements if we're not forcing: + let a = Self::account(who); + let unavailable = if force == Force { + Self::Balance::zero() + } else { + // The freeze lock applies to the total balance, so we can discount the free balance + // from the amount which the total reserved balance must provide to satisfy it. + a.frozen.saturating_sub(a.free) + }; + a.reserved.saturating_sub(unavailable) + } + fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> T::Balance { + Holds::::get(who) + .iter() + .find(|x| &x.id == reason) + .map_or_else(Zero::zero, |x| x.amount) + } + fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool { + if frame_system::Pallet::::providers(who) == 0 { + return false; + } + let holds = Holds::::get(who); + if holds.is_full() && !holds.iter().any(|x| &x.id == reason) { + return false; + } + true + } } impl, I: 'static> fungible::UnbalancedHold for Pallet { - fn set_balance_on_hold( - reason: &Self::Reason, - who: &T::AccountId, - amount: Self::Balance, - ) -> DispatchResult { - let mut new_account = Self::account(who); - let mut holds = Holds::::get(who); - let mut increase = true; - let mut delta = amount; - - if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) { - delta = item.amount.max(amount) - item.amount.min(amount); - increase = amount > item.amount; - item.amount = amount; - holds.retain(|x| !x.amount.is_zero()); - } else if !amount.is_zero() { - holds - .try_push(IdAmount { id: *reason, amount }) - .map_err(|_| Error::::TooManyHolds)?; - } - - new_account.reserved = if increase { - new_account.reserved.checked_add(&delta).ok_or(ArithmeticError::Overflow)? - } else { - new_account.reserved.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? - }; - - let (result, maybe_dust) = Self::try_mutate_account(who, |a, _| -> DispatchResult { - *a = new_account; - Ok(()) - })?; - debug_assert!( - maybe_dust.is_none(), - "Does not alter main balance; dust only happens when it is altered; qed" - ); - Holds::::insert(who, holds); - Ok(result) - } + fn set_balance_on_hold( + reason: &Self::Reason, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + let mut new_account = Self::account(who); + let mut holds = Holds::::get(who); + let mut increase = true; + let mut delta = amount; + + if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) { + delta = item.amount.max(amount) - item.amount.min(amount); + increase = amount > item.amount; + item.amount = amount; + holds.retain(|x| !x.amount.is_zero()); + } else if !amount.is_zero() { + holds + .try_push(IdAmount { + id: *reason, + amount, + }) + .map_err(|_| Error::::TooManyHolds)?; + } + + new_account.reserved = if increase { + new_account + .reserved + .checked_add(&delta) + .ok_or(ArithmeticError::Overflow)? + } else { + new_account + .reserved + .checked_sub(&delta) + .ok_or(ArithmeticError::Underflow)? + }; + + let (result, maybe_dust) = Self::try_mutate_account(who, |a, _| -> DispatchResult { + *a = new_account; + Ok(()) + })?; + debug_assert!( + maybe_dust.is_none(), + "Does not alter main balance; dust only happens when it is altered; qed" + ); + Holds::::insert(who, holds); + Ok(result) + } } impl, I: 'static> fungible::InspectFreeze for Pallet { - type Id = T::FreezeIdentifier; - - fn balance_frozen(id: &Self::Id, who: &T::AccountId) -> Self::Balance { - let locks = Freezes::::get(who); - locks.into_iter().find(|l| &l.id == id).map_or(Zero::zero(), |l| l.amount) - } - - fn can_freeze(id: &Self::Id, who: &T::AccountId) -> bool { - let l = Freezes::::get(who); - !l.is_full() || l.iter().any(|x| &x.id == id) - } + type Id = T::FreezeIdentifier; + + fn balance_frozen(id: &Self::Id, who: &T::AccountId) -> Self::Balance { + let locks = Freezes::::get(who); + locks + .into_iter() + .find(|l| &l.id == id) + .map_or(Zero::zero(), |l| l.amount) + } + + fn can_freeze(id: &Self::Id, who: &T::AccountId) -> bool { + let l = Freezes::::get(who); + !l.is_full() || l.iter().any(|x| &x.id == id) + } } impl, I: 'static> fungible::MutateFreeze for Pallet { - fn set_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Self::thaw(id, who) - } - let mut locks = Freezes::::get(who); - if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { - i.amount = amount; - } else { - locks - .try_push(IdAmount { id: *id, amount }) - .map_err(|_| Error::::TooManyFreezes)?; - } - Self::update_freezes(who, locks.as_bounded_slice()) - } - - fn extend_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()) - } - let mut locks = Freezes::::get(who); - if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { - i.amount = i.amount.max(amount); - } else { - locks - .try_push(IdAmount { id: *id, amount }) - .map_err(|_| Error::::TooManyFreezes)?; - } - Self::update_freezes(who, locks.as_bounded_slice()) - } - - fn thaw(id: &Self::Id, who: &T::AccountId) -> DispatchResult { - let mut locks = Freezes::::get(who); - locks.retain(|l| &l.id != id); - Self::update_freezes(who, locks.as_bounded_slice()) - } + fn set_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { + if amount.is_zero() { + return Self::thaw(id, who); + } + let mut locks = Freezes::::get(who); + if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { + i.amount = amount; + } else { + locks + .try_push(IdAmount { id: *id, amount }) + .map_err(|_| Error::::TooManyFreezes)?; + } + Self::update_freezes(who, locks.as_bounded_slice()) + } + + fn extend_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + let mut locks = Freezes::::get(who); + if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { + i.amount = i.amount.max(amount); + } else { + locks + .try_push(IdAmount { id: *id, amount }) + .map_err(|_| Error::::TooManyFreezes)?; + } + Self::update_freezes(who, locks.as_bounded_slice()) + } + + fn thaw(id: &Self::Id, who: &T::AccountId) -> DispatchResult { + let mut locks = Freezes::::get(who); + locks.retain(|l| &l.id != id); + Self::update_freezes(who, locks.as_bounded_slice()) + } } impl, I: 'static> fungible::Balanced for Pallet { - type OnDropCredit = fungible::DecreaseIssuance; - type OnDropDebt = fungible::IncreaseIssuance; - - fn done_deposit(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Deposit { who: who.clone(), amount }); - } - fn done_withdraw(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Withdraw { who: who.clone(), amount }); - } - fn done_issue(amount: Self::Balance) { - if !amount.is_zero() { - Self::deposit_event(Event::::Issued { amount }); - } - } - fn done_rescind(amount: Self::Balance) { - Self::deposit_event(Event::::Rescinded { amount }); - } + type OnDropCredit = fungible::DecreaseIssuance; + type OnDropDebt = fungible::IncreaseIssuance; + + fn done_deposit(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Deposit { + who: who.clone(), + amount, + }); + } + fn done_withdraw(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Withdraw { + who: who.clone(), + amount, + }); + } + fn done_issue(amount: Self::Balance) { + if !amount.is_zero() { + Self::deposit_event(Event::::Issued { amount }); + } + } + fn done_rescind(amount: Self::Balance) { + Self::deposit_event(Event::::Rescinded { amount }); + } } impl, I: 'static> fungible::BalancedHold for Pallet {} impl, I: 'static> - fungible::hold::DoneSlash for Pallet + fungible::hold::DoneSlash for Pallet { - fn done_slash(reason: &T::RuntimeHoldReason, who: &T::AccountId, amount: T::Balance) { - T::DoneSlashHandler::done_slash(reason, who, amount); - } + fn done_slash(reason: &T::RuntimeHoldReason, who: &T::AccountId, amount: T::Balance) { + T::DoneSlashHandler::done_slash(reason, who, amount); + } } impl, I: 'static> AccountTouch<(), T::AccountId> for Pallet { - type Balance = T::Balance; - fn deposit_required(_: ()) -> Self::Balance { - Self::Balance::zero() - } - fn should_touch(_: (), _: &T::AccountId) -> bool { - false - } - fn touch(_: (), _: &T::AccountId, _: &T::AccountId) -> DispatchResult { - Ok(()) - } + type Balance = T::Balance; + fn deposit_required(_: ()) -> Self::Balance { + Self::Balance::zero() + } + fn should_touch(_: (), _: &T::AccountId) -> bool { + false + } + fn touch(_: (), _: &T::AccountId, _: &T::AccountId) -> DispatchResult { + Ok(()) + } } diff --git a/pallets/balances/src/lib.rs b/pallets/balances/src/lib.rs index e098af53..bd447ce6 100644 --- a/pallets/balances/src/lib.rs +++ b/pallets/balances/src/lib.rs @@ -195,7 +195,9 @@ type AccountIdLookupOf = <::Lookup as StaticLookup pub struct PoseidonStorageHasher(PhantomData); -impl StorageHasher for PoseidonStorageHasher { +impl StorageHasher + for PoseidonStorageHasher +{ // We are lying here, but maybe it's ok because it's just metadata const METADATA: StorageHasherIR = StorageHasherIR::Identity; type Output = [u8; 32]; diff --git a/pallets/balances/src/migration.rs b/pallets/balances/src/migration.rs index ac2f7e2f..04dc2d80 100644 --- a/pallets/balances/src/migration.rs +++ b/pallets/balances/src/migration.rs @@ -16,88 +16,88 @@ use super::*; use frame_support::{ - pallet_prelude::*, - traits::{OnRuntimeUpgrade, PalletInfoAccess}, - weights::Weight, + pallet_prelude::*, + traits::{OnRuntimeUpgrade, PalletInfoAccess}, + weights::Weight, }; fn migrate_v0_to_v1, I: 'static>(accounts: &[T::AccountId]) -> Weight { - let on_chain_version = Pallet::::on_chain_storage_version(); - - if on_chain_version == 0 { - let total = accounts - .iter() - .map(Pallet::::total_balance) - .fold(T::Balance::zero(), |a, e| a.saturating_add(e)); - Pallet::::deactivate(total); - - // Remove the old `StorageVersion` type. - frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( - Pallet::::name().as_bytes(), - "StorageVersion".as_bytes(), - )); - - // Set storage version to `1`. - StorageVersion::new(1).put::>(); - - log::info!(target: LOG_TARGET, "Storage to version 1"); - T::DbWeight::get().reads_writes(2 + accounts.len() as u64, 3) - } else { - log::info!( - target: LOG_TARGET, - "Migration did not execute. This probably should be removed" - ); - T::DbWeight::get().reads(1) - } + let on_chain_version = Pallet::::on_chain_storage_version(); + + if on_chain_version == 0 { + let total = accounts + .iter() + .map(Pallet::::total_balance) + .fold(T::Balance::zero(), |a, e| a.saturating_add(e)); + Pallet::::deactivate(total); + + // Remove the old `StorageVersion` type. + frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( + Pallet::::name().as_bytes(), + "StorageVersion".as_bytes(), + )); + + // Set storage version to `1`. + StorageVersion::new(1).put::>(); + + log::info!(target: LOG_TARGET, "Storage to version 1"); + T::DbWeight::get().reads_writes(2 + accounts.len() as u64, 3) + } else { + log::info!( + target: LOG_TARGET, + "Migration did not execute. This probably should be removed" + ); + T::DbWeight::get().reads(1) + } } // NOTE: This must be used alongside the account whose balance is expected to be inactive. // Generally this will be used for the XCM teleport checking account. pub struct MigrateToTrackInactive(PhantomData<(T, A, I)>); impl, A: Get, I: 'static> OnRuntimeUpgrade - for MigrateToTrackInactive + for MigrateToTrackInactive { - fn on_runtime_upgrade() -> Weight { - migrate_v0_to_v1::(&[A::get()]) - } + fn on_runtime_upgrade() -> Weight { + migrate_v0_to_v1::(&[A::get()]) + } } // NOTE: This must be used alongside the accounts whose balance is expected to be inactive. // Generally this will be used for the XCM teleport checking accounts. pub struct MigrateManyToTrackInactive(PhantomData<(T, A, I)>); impl, A: Get>, I: 'static> OnRuntimeUpgrade - for MigrateManyToTrackInactive + for MigrateManyToTrackInactive { - fn on_runtime_upgrade() -> Weight { - migrate_v0_to_v1::(&A::get()) - } + fn on_runtime_upgrade() -> Weight { + migrate_v0_to_v1::(&A::get()) + } } pub struct ResetInactive(PhantomData<(T, I)>); impl, I: 'static> OnRuntimeUpgrade for ResetInactive { - fn on_runtime_upgrade() -> Weight { - let on_chain_version = Pallet::::on_chain_storage_version(); - - if on_chain_version == 1 { - // Remove the old `StorageVersion` type. - frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( - Pallet::::name().as_bytes(), - "StorageVersion".as_bytes(), - )); - - InactiveIssuance::::kill(); - - // Set storage version to `0`. - StorageVersion::new(0).put::>(); - - log::info!(target: LOG_TARGET, "Storage to version 0"); - T::DbWeight::get().reads_writes(1, 3) - } else { - log::info!( - target: LOG_TARGET, - "Migration did not execute. This probably should be removed" - ); - T::DbWeight::get().reads(1) - } - } + fn on_runtime_upgrade() -> Weight { + let on_chain_version = Pallet::::on_chain_storage_version(); + + if on_chain_version == 1 { + // Remove the old `StorageVersion` type. + frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( + Pallet::::name().as_bytes(), + "StorageVersion".as_bytes(), + )); + + InactiveIssuance::::kill(); + + // Set storage version to `0`. + StorageVersion::new(0).put::>(); + + log::info!(target: LOG_TARGET, "Storage to version 0"); + T::DbWeight::get().reads_writes(1, 3) + } else { + log::info!( + target: LOG_TARGET, + "Migration did not execute. This probably should be removed" + ); + T::DbWeight::get().reads(1) + } + } } diff --git a/pallets/balances/src/tests/currency_tests.rs b/pallets/balances/src/tests/currency_tests.rs index 5e35608b..c858783b 100644 --- a/pallets/balances/src/tests/currency_tests.rs +++ b/pallets/balances/src/tests/currency_tests.rs @@ -20,14 +20,14 @@ use super::*; use crate::{Event, NegativeImbalance}; use frame_support::{ - traits::{ - BalanceStatus::{Free, Reserved}, - Currency, - ExistenceRequirement::{self, AllowDeath, KeepAlive}, - Hooks, InspectLockableCurrency, LockIdentifier, LockableCurrency, NamedReservableCurrency, - ReservableCurrency, WithdrawReasons, - }, - StorageNoopGuard, + traits::{ + BalanceStatus::{Free, Reserved}, + Currency, + ExistenceRequirement::{self, AllowDeath, KeepAlive}, + Hooks, InspectLockableCurrency, LockIdentifier, LockableCurrency, NamedReservableCurrency, + ReservableCurrency, WithdrawReasons, + }, + StorageNoopGuard, }; use frame_system::Event as SysEvent; use sp_runtime::traits::DispatchTransaction; @@ -36,1405 +36,1636 @@ const ID_1: LockIdentifier = *b"1 "; const ID_2: LockIdentifier = *b"2 "; pub const CALL: &::RuntimeCall = - &RuntimeCall::Balances(crate::Call::transfer_allow_death { dest: 0, value: 0 }); + &RuntimeCall::Balances(crate::Call::transfer_allow_death { dest: 0, value: 0 }); #[test] fn ed_should_work() { - ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 1000)); - assert_noop!( - >::transfer(&1, &10, 1000, KeepAlive), - TokenError::NotExpendable - ); - assert_ok!(>::transfer(&1, &10, 1000, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 1000)); + assert_noop!( + >::transfer(&1, &10, 1000, KeepAlive), + TokenError::NotExpendable + ); + assert_ok!(>::transfer( + &1, &10, 1000, AllowDeath + )); + }); } #[test] fn set_lock_with_amount_zero_removes_lock() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::all()); - Balances::set_lock(ID_1, &1, 0, WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::all()); + Balances::set_lock(ID_1, &1, 0, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn set_lock_with_withdraw_reasons_empty_removes_lock() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::all()); - Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::empty()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::all()); + Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::empty()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn basic_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_eq!(Balances::free_balance(1), 10); - Balances::set_lock(ID_1, &1, 9, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 5, AllowDeath), - TokenError::Frozen - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + Balances::set_lock(ID_1, &1, 9, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 5, AllowDeath), + TokenError::Frozen + ); + }); } #[test] fn inspect_lock_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, 10, WithdrawReasons::all()); - Balances::set_lock(ID_2, &1, 10, WithdrawReasons::all()); - Balances::set_lock(ID_1, &2, 20, WithdrawReasons::all()); - - assert_eq!(>::balance_locked(ID_1, &1), 10); - assert_eq!(>::balance_locked(ID_2, &1), 10); - assert_eq!(>::balance_locked(ID_1, &2), 20); - assert_eq!(>::balance_locked(ID_2, &2), 0); - assert_eq!(>::balance_locked(ID_1, &3), 0); - }) + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::all()); + Balances::set_lock(ID_2, &1, 10, WithdrawReasons::all()); + Balances::set_lock(ID_1, &2, 20, WithdrawReasons::all()); + + assert_eq!( + >::balance_locked(ID_1, &1), + 10 + ); + assert_eq!( + >::balance_locked(ID_2, &1), + 10 + ); + assert_eq!( + >::balance_locked(ID_1, &2), + 20 + ); + assert_eq!( + >::balance_locked(ID_2, &2), + 0 + ); + assert_eq!( + >::balance_locked(ID_1, &3), + 0 + ); + }) } #[test] fn account_should_be_reaped() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_eq!(Balances::free_balance(1), 10); - assert_ok!(>::transfer(&1, &2, 10, AllowDeath)); - assert_eq!(System::providers(&1), 0); - assert_eq!(System::consumers(&1), 0); - // Check that the account is dead. - assert!(!frame_system::Account::::contains_key(1)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_ok!(>::transfer(&1, &2, 10, AllowDeath)); + assert_eq!(System::providers(&1), 0); + assert_eq!(System::consumers(&1), 0); + // Check that the account is dead. + assert!(!frame_system::Account::::contains_key(1)); + }); } #[test] fn reap_failed_due_to_provider_and_consumer() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // SCENARIO: only one provider and there are remaining consumers. - assert_ok!(System::inc_consumers(&1)); - assert!(!System::can_dec_provider(&1)); - assert_noop!( - >::transfer(&1, &2, 10, AllowDeath), - TokenError::Frozen - ); - assert!(System::account_exists(&1)); - assert_eq!(Balances::free_balance(1), 10); - - // SCENARIO: more than one provider, but will not kill account due to other provider. - assert_eq!(System::inc_providers(&1), frame_system::IncRefStatus::Existed); - assert_eq!(System::providers(&1), 2); - assert!(System::can_dec_provider(&1)); - assert_ok!(>::transfer(&1, &2, 10, AllowDeath)); - assert_eq!(System::providers(&1), 1); - assert!(System::account_exists(&1)); - assert_eq!(Balances::free_balance(1), 0); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + // SCENARIO: only one provider and there are remaining consumers. + assert_ok!(System::inc_consumers(&1)); + assert!(!System::can_dec_provider(&1)); + assert_noop!( + >::transfer(&1, &2, 10, AllowDeath), + TokenError::Frozen + ); + assert!(System::account_exists(&1)); + assert_eq!(Balances::free_balance(1), 10); + + // SCENARIO: more than one provider, but will not kill account due to other provider. + assert_eq!( + System::inc_providers(&1), + frame_system::IncRefStatus::Existed + ); + assert_eq!(System::providers(&1), 2); + assert!(System::can_dec_provider(&1)); + assert_ok!(>::transfer(&1, &2, 10, AllowDeath)); + assert_eq!(System::providers(&1), 1); + assert!(System::account_exists(&1)); + assert_eq!(Balances::free_balance(1), 0); + }); } #[test] fn partial_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn lock_removal_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 1); - Balances::remove_lock(ID_1, &1); - assert_eq!(System::consumers(&1), 0); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 1); + Balances::remove_lock(ID_1, &1); + assert_eq!(System::consumers(&1), 0); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn lock_replacement_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 1); - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 1); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 1); + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 1); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn double_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 1); - Balances::set_lock(ID_2, &1, 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 1); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 1); + Balances::set_lock(ID_2, &1, 5, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 1); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn combination_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_eq!(System::consumers(&1), 0); - Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::empty()); - assert_eq!(System::consumers(&1), 0); - Balances::set_lock(ID_2, &1, 0, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 0); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_eq!(System::consumers(&1), 0); + Balances::set_lock(ID_1, &1, Balance::MAX, WithdrawReasons::empty()); + assert_eq!(System::consumers(&1), 0); + Balances::set_lock(ID_2, &1, 0, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 0); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn lock_value_extension_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 1); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &1, 2, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 1); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &1, 8, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 1); - assert_noop!( - >::transfer(&1, &2, 3, AllowDeath), - TokenError::Frozen - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 1); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 2, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 1); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 8, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 1); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + TokenError::Frozen + ); + }); } #[test] fn lock_should_work_reserve() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - pallet_transaction_payment::NextFeeMultiplier::::put( - Multiplier::saturating_from_integer(1), - ); - Balances::set_lock(ID_1, &1, 10, WithdrawReasons::RESERVE); - assert_noop!( - >::transfer(&1, &2, 1, AllowDeath), - TokenError::Frozen - ); - assert_noop!(Balances::reserve(&1, 1), Error::::LiquidityRestrictions,); - assert!(ChargeTransactionPayment::::validate_and_prepare( - ChargeTransactionPayment::from(1), - Some(1).into(), - CALL, - &info_from_weight(Weight::from_parts(1, 0)), - 1, - 0, - ) - .is_err()); - assert!(ChargeTransactionPayment::::validate_and_prepare( - ChargeTransactionPayment::from(0), - Some(1).into(), - CALL, - &info_from_weight(Weight::from_parts(1, 0)), - 1, - 0, - ) - .is_err()); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + pallet_transaction_payment::NextFeeMultiplier::::put( + Multiplier::saturating_from_integer(1), + ); + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::RESERVE); + assert_noop!( + >::transfer(&1, &2, 1, AllowDeath), + TokenError::Frozen + ); + assert_noop!( + Balances::reserve(&1, 1), + Error::::LiquidityRestrictions, + ); + assert!(ChargeTransactionPayment::::validate_and_prepare( + ChargeTransactionPayment::from(1), + Some(1).into(), + CALL, + &info_from_weight(Weight::from_parts(1, 0)), + 1, + 0, + ) + .is_err()); + assert!(ChargeTransactionPayment::::validate_and_prepare( + ChargeTransactionPayment::from(0), + Some(1).into(), + CALL, + &info_from_weight(Weight::from_parts(1, 0)), + 1, + 0, + ) + .is_err()); + }); } #[test] fn lock_should_work_tx_fee() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, 10, WithdrawReasons::TRANSACTION_PAYMENT); - assert_noop!( - >::transfer(&1, &2, 1, AllowDeath), - TokenError::Frozen - ); - assert_noop!(Balances::reserve(&1, 1), Error::::LiquidityRestrictions,); - assert!(ChargeTransactionPayment::::validate_and_prepare( - ChargeTransactionPayment::from(1), - Some(1).into(), - CALL, - &info_from_weight(Weight::from_parts(1, 0)), - 1, - 0, - ) - .is_err()); - assert!(ChargeTransactionPayment::::validate_and_prepare( - ChargeTransactionPayment::from(0), - Some(1).into(), - CALL, - &info_from_weight(Weight::from_parts(1, 0)), - 1, - 0, - ) - .is_err()); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::TRANSACTION_PAYMENT); + assert_noop!( + >::transfer(&1, &2, 1, AllowDeath), + TokenError::Frozen + ); + assert_noop!( + Balances::reserve(&1, 1), + Error::::LiquidityRestrictions, + ); + assert!(ChargeTransactionPayment::::validate_and_prepare( + ChargeTransactionPayment::from(1), + Some(1).into(), + CALL, + &info_from_weight(Weight::from_parts(1, 0)), + 1, + 0, + ) + .is_err()); + assert!(ChargeTransactionPayment::::validate_and_prepare( + ChargeTransactionPayment::from(0), + Some(1).into(), + CALL, + &info_from_weight(Weight::from_parts(1, 0)), + 1, + 0, + ) + .is_err()); + }); } #[test] fn lock_block_number_extension_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, 10, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - TokenError::Frozen - ); - System::set_block_number(2); - Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 3, AllowDeath), - TokenError::Frozen - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + System::set_block_number(2); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + TokenError::Frozen + ); + }); } #[test] fn lock_reasons_extension_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &1, 10, WithdrawReasons::TRANSFER); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::empty()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::RESERVE); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - TokenError::Frozen - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::TRANSFER); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::empty()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::RESERVE); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + }); } #[test] fn reserved_balance_should_prevent_reclaim_count() { - ExtBuilder::default() - .existential_deposit(256) - .monied(true) - .build_and_execute_with(|| { - System::inc_account_nonce(2); - assert_eq!(Balances::total_balance(&2), 256 * 20); - assert_eq!(System::providers(&2), 1); - System::inc_providers(&2); - assert_eq!(System::providers(&2), 2); - - assert_ok!(Balances::reserve(&2, 256 * 19 + 1)); // account 2 becomes mostly reserved - assert_eq!(System::providers(&2), 1); - assert_eq!(Balances::free_balance(2), 255); // "free" account would be deleted. - assert_eq!(Balances::total_balance(&2), 256 * 20); // reserve still exists. - assert_eq!(System::account_nonce(2), 1); - - // account 4 tries to take index 1 for account 5. - assert_ok!(Balances::transfer_allow_death(Some(4).into(), 5, 256 + 0x69)); - assert_eq!(Balances::total_balance(&5), 256 + 0x69); - - assert!(Balances::slash_reserved(&2, 256 * 19 + 1).1.is_zero()); // account 2 gets slashed - - // "reserve" account reduced to 255 (below ED) so account no longer consuming - assert_ok!(System::dec_providers(&2)); - assert_eq!(System::providers(&2), 0); - // account deleted - assert_eq!(System::account_nonce(2), 0); // nonce zero - assert_eq!(Balances::total_balance(&2), 0); - - // account 4 tries to take index 1 again for account 6. - assert_ok!(Balances::transfer_allow_death(Some(4).into(), 6, 256 + 0x69)); - assert_eq!(Balances::total_balance(&6), 256 + 0x69); - }); + ExtBuilder::default() + .existential_deposit(256) + .monied(true) + .build_and_execute_with(|| { + System::inc_account_nonce(2); + assert_eq!(Balances::total_balance(&2), 256 * 20); + assert_eq!(System::providers(&2), 1); + System::inc_providers(&2); + assert_eq!(System::providers(&2), 2); + + assert_ok!(Balances::reserve(&2, 256 * 19 + 1)); // account 2 becomes mostly reserved + assert_eq!(System::providers(&2), 1); + assert_eq!(Balances::free_balance(2), 255); // "free" account would be deleted. + assert_eq!(Balances::total_balance(&2), 256 * 20); // reserve still exists. + assert_eq!(System::account_nonce(2), 1); + + // account 4 tries to take index 1 for account 5. + assert_ok!(Balances::transfer_allow_death( + Some(4).into(), + 5, + 256 + 0x69 + )); + assert_eq!(Balances::total_balance(&5), 256 + 0x69); + + assert!(Balances::slash_reserved(&2, 256 * 19 + 1).1.is_zero()); // account 2 gets slashed + + // "reserve" account reduced to 255 (below ED) so account no longer consuming + assert_ok!(System::dec_providers(&2)); + assert_eq!(System::providers(&2), 0); + // account deleted + assert_eq!(System::account_nonce(2), 0); // nonce zero + assert_eq!(Balances::total_balance(&2), 0); + + // account 4 tries to take index 1 again for account 6. + assert_ok!(Balances::transfer_allow_death( + Some(4).into(), + 6, + 256 + 0x69 + )); + assert_eq!(Balances::total_balance(&6), 256 + 0x69); + }); } #[test] fn reward_should_work() { - ExtBuilder::default().monied(true).build_and_execute_with(|| { - assert_eq!(Balances::total_balance(&1), 10); - assert_ok!(Balances::deposit_into_existing(&1, 10).map(drop)); - assert_eq!( - events(), - [ - RuntimeEvent::Balances(crate::Event::Deposit { who: 1, amount: 10 }), - RuntimeEvent::Balances(crate::Event::Issued { amount: 10 }), - ] - ); - assert_eq!(Balances::total_balance(&1), 20); - assert_eq!(pallet_balances::TotalIssuance::::get(), 120); - }); + ExtBuilder::default() + .monied(true) + .build_and_execute_with(|| { + assert_eq!(Balances::total_balance(&1), 10); + assert_ok!(Balances::deposit_into_existing(&1, 10).map(drop)); + assert_eq!( + events(), + [ + RuntimeEvent::Balances(crate::Event::Deposit { who: 1, amount: 10 }), + RuntimeEvent::Balances(crate::Event::Issued { amount: 10 }), + ] + ); + assert_eq!(Balances::total_balance(&1), 20); + assert_eq!(pallet_balances::TotalIssuance::::get(), 120); + }); } #[test] fn balance_works() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 42); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: 1, - amount: 42, - })); - assert_eq!(Balances::free_balance(1), 42); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::total_balance(&1), 42); - assert_eq!(Balances::free_balance(2), 0); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::total_balance(&2), 0); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 42, + })); + assert_eq!(Balances::free_balance(1), 42); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::total_balance(&1), 42); + assert_eq!(Balances::free_balance(2), 0); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::total_balance(&2), 0); + }); } #[test] fn reserving_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); - assert_eq!(Balances::total_balance(&1), 111); - assert_eq!(Balances::free_balance(1), 111); - assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 111); + assert_eq!(Balances::reserved_balance(1), 0); - assert_ok!(Balances::reserve(&1, 69)); + assert_ok!(Balances::reserve(&1, 69)); - assert_eq!(Balances::total_balance(&1), 111); - assert_eq!(Balances::free_balance(1), 42); - assert_eq!(Balances::reserved_balance(1), 69); - }); + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 42); + assert_eq!(Balances::reserved_balance(1), 69); + }); } #[test] fn deducting_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 69)); - assert_eq!(Balances::free_balance(1), 42); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 69)); + assert_eq!(Balances::free_balance(1), 42); + }); } #[test] fn refunding_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 42); - assert_ok!(Balances::mutate_account(&1, |a| a.reserved = 69)); - Balances::unreserve(&1, 69); - assert_eq!(Balances::free_balance(1), 111); - assert_eq!(Balances::reserved_balance(1), 0); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + assert_ok!(Balances::mutate_account(&1, |a| a.reserved = 69)); + Balances::unreserve(&1, 69); + assert_eq!(Balances::free_balance(1), 111); + assert_eq!(Balances::reserved_balance(1), 0); + }); } #[test] fn slashing_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 112); - assert_ok!(Balances::reserve(&1, 69)); - assert!(Balances::slash(&1, 42).1.is_zero()); - assert_eq!(Balances::free_balance(1), 1); - assert_eq!(Balances::reserved_balance(1), 69); - assert_eq!(pallet_balances::TotalIssuance::::get(), 70); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 112); + assert_ok!(Balances::reserve(&1, 69)); + assert!(Balances::slash(&1, 42).1.is_zero()); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(pallet_balances::TotalIssuance::::get(), 70); + }); } #[test] fn withdrawing_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&2, 111); - let _ = - Balances::withdraw(&2, 11, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Withdraw { - who: 2, - amount: 11, - })); - assert_eq!(Balances::free_balance(2), 100); - assert_eq!(pallet_balances::TotalIssuance::::get(), 100); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&2, 111); + let _ = Balances::withdraw( + &2, + 11, + WithdrawReasons::TRANSFER, + ExistenceRequirement::KeepAlive, + ); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Withdraw { + who: 2, + amount: 11, + })); + assert_eq!(Balances::free_balance(2), 100); + assert_eq!(pallet_balances::TotalIssuance::::get(), 100); + }); } #[test] fn withdrawing_balance_should_fail_when_not_expendable() { - ExtBuilder::default().build_and_execute_with(|| { - ExistentialDeposit::set(10); - let _ = Balances::deposit_creating(&2, 20); - assert_ok!(Balances::reserve(&2, 5)); - assert_noop!( - Balances::withdraw(&2, 6, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive), - Error::::Expendability, - ); - assert_ok!(Balances::withdraw( - &2, - 5, - WithdrawReasons::TRANSFER, - ExistenceRequirement::KeepAlive - ),); - }); + ExtBuilder::default().build_and_execute_with(|| { + ExistentialDeposit::set(10); + let _ = Balances::deposit_creating(&2, 20); + assert_ok!(Balances::reserve(&2, 5)); + assert_noop!( + Balances::withdraw( + &2, + 6, + WithdrawReasons::TRANSFER, + ExistenceRequirement::KeepAlive + ), + Error::::Expendability, + ); + assert_ok!(Balances::withdraw( + &2, + 5, + WithdrawReasons::TRANSFER, + ExistenceRequirement::KeepAlive + ),); + }); } #[test] fn slashing_incomplete_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 42); - assert_ok!(Balances::reserve(&1, 21)); - assert_eq!(Balances::slash(&1, 69).1, 49); - assert_eq!(Balances::free_balance(1), 1); - assert_eq!(Balances::reserved_balance(1), 21); - assert_eq!(pallet_balances::TotalIssuance::::get(), 22); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + assert_ok!(Balances::reserve(&1, 21)); + assert_eq!(Balances::slash(&1, 69).1, 49); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::reserved_balance(1), 21); + assert_eq!(pallet_balances::TotalIssuance::::get(), 22); + }); } #[test] fn unreserving_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 110)); - Balances::unreserve(&1, 41); - assert_eq!(Balances::reserved_balance(1), 69); - assert_eq!(Balances::free_balance(1), 42); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 110)); + Balances::unreserve(&1, 41); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 42); + }); } #[test] fn slashing_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 112); - assert_ok!(Balances::reserve(&1, 111)); - assert_eq!(Balances::slash_reserved(&1, 42).1, 0); - assert_eq!(Balances::reserved_balance(1), 69); - assert_eq!(Balances::free_balance(1), 1); - assert_eq!(pallet_balances::TotalIssuance::::get(), 70); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 112); + assert_ok!(Balances::reserve(&1, 111)); + assert_eq!(Balances::slash_reserved(&1, 42).1, 0); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(pallet_balances::TotalIssuance::::get(), 70); + }); } #[test] fn slashing_incomplete_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 42)); - assert_eq!(Balances::slash_reserved(&1, 69).1, 27); - assert_eq!(Balances::free_balance(1), 69); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(pallet_balances::TotalIssuance::::get(), 69); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 42)); + assert_eq!(Balances::slash_reserved(&1, 69).1, 27); + assert_eq!(Balances::free_balance(1), 69); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(pallet_balances::TotalIssuance::::get(), 69); + }); } #[test] fn repatriating_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - let _ = Balances::deposit_creating(&2, 1); - assert_ok!(Balances::reserve(&1, 110)); - assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Free), 0); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::ReserveRepatriated { - from: 1, - to: 2, - amount: 41, - destination_status: Free, - })); - assert_eq!(Balances::reserved_balance(1), 69); - assert_eq!(Balances::free_balance(1), 1); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::free_balance(2), 42); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + let _ = Balances::deposit_creating(&2, 1); + assert_ok!(Balances::reserve(&1, 110)); + assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Free), 0); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::ReserveRepatriated { + from: 1, + to: 2, + amount: 41, + destination_status: Free, + })); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::free_balance(2), 42); + }); } #[test] fn transferring_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - let _ = Balances::deposit_creating(&2, 1); - assert_ok!(Balances::reserve(&1, 110)); - assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Reserved), 0); - assert_eq!(Balances::reserved_balance(1), 69); - assert_eq!(Balances::free_balance(1), 1); - assert_eq!(Balances::reserved_balance(2), 41); - assert_eq!(Balances::free_balance(2), 1); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + let _ = Balances::deposit_creating(&2, 1); + assert_ok!(Balances::reserve(&1, 110)); + assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Reserved), 0); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::reserved_balance(2), 41); + assert_eq!(Balances::free_balance(2), 1); + }); } #[test] fn transferring_reserved_balance_to_yourself_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 110); - assert_ok!(Balances::reserve(&1, 50)); - assert_ok!(Balances::repatriate_reserved(&1, &1, 50, Free), 0); - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::reserved_balance(1), 0); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + assert_ok!(Balances::reserve(&1, 50)); + assert_ok!(Balances::repatriate_reserved(&1, &1, 50, Free), 0); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance(1), 0); - assert_ok!(Balances::reserve(&1, 50)); - assert_ok!(Balances::repatriate_reserved(&1, &1, 60, Free), 10); - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::reserved_balance(1), 0); - }); + assert_ok!(Balances::reserve(&1, 50)); + assert_ok!(Balances::repatriate_reserved(&1, &1, 60, Free), 10); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance(1), 0); + }); } #[test] fn transferring_reserved_balance_to_nonexistent_should_fail() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 110)); - assert_noop!( - Balances::repatriate_reserved(&1, &2, 42, Free), - Error::::DeadAccount - ); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 110)); + assert_noop!( + Balances::repatriate_reserved(&1, &2, 42, Free), + Error::::DeadAccount + ); + }); } #[test] fn transferring_incomplete_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 110); - let _ = Balances::deposit_creating(&2, 1); - assert_ok!(Balances::reserve(&1, 41)); - assert_ok!(Balances::repatriate_reserved(&1, &2, 69, Free), 28); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::free_balance(1), 69); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::free_balance(2), 42); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + let _ = Balances::deposit_creating(&2, 1); + assert_ok!(Balances::reserve(&1, 41)); + assert_ok!(Balances::repatriate_reserved(&1, &2, 69, Free), 28); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::free_balance(1), 69); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::free_balance(2), 42); + }); } #[test] fn transferring_too_high_value_should_not_panic() { - ExtBuilder::default().build_and_execute_with(|| { - Balances::make_free_balance_be(&1, Balance::MAX); - Balances::make_free_balance_be(&2, 1); + ExtBuilder::default().build_and_execute_with(|| { + Balances::make_free_balance_be(&1, Balance::MAX); + Balances::make_free_balance_be(&2, 1); - assert_err!( - >::transfer(&1, &2, Balance::MAX, AllowDeath), - ArithmeticError::Overflow, - ); + assert_err!( + >::transfer(&1, &2, Balance::MAX, AllowDeath), + ArithmeticError::Overflow, + ); - assert_eq!(Balances::free_balance(1), Balance::MAX); - assert_eq!(Balances::free_balance(2), 1); - }); + assert_eq!(Balances::free_balance(1), Balance::MAX); + assert_eq!(Balances::free_balance(2), 1); + }); } #[test] fn account_create_on_free_too_low_with_other() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 100); - assert_eq!(pallet_balances::TotalIssuance::::get(), 100); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); + assert_eq!(pallet_balances::TotalIssuance::::get(), 100); - // No-op. - let _ = Balances::deposit_creating(&2, 50); - assert_eq!(Balances::free_balance(2), 0); - assert_eq!(pallet_balances::TotalIssuance::::get(), 100); - }) + // No-op. + let _ = Balances::deposit_creating(&2, 50); + assert_eq!(Balances::free_balance(2), 0); + assert_eq!(pallet_balances::TotalIssuance::::get(), 100); + }) } #[test] fn account_create_on_free_too_low() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // No-op. - let _ = Balances::deposit_creating(&2, 50); - assert_eq!(Balances::free_balance(2), 0); - assert_eq!(pallet_balances::TotalIssuance::::get(), 0); - }) + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + // No-op. + let _ = Balances::deposit_creating(&2, 50); + assert_eq!(Balances::free_balance(2), 0); + assert_eq!(pallet_balances::TotalIssuance::::get(), 0); + }) } #[test] fn account_removal_on_free_too_low() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - assert_eq!(pallet_balances::TotalIssuance::::get(), 0); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + assert_eq!(pallet_balances::TotalIssuance::::get(), 0); - // Setup two accounts with free balance above the existential threshold. - let _ = Balances::deposit_creating(&1, 110); - let _ = Balances::deposit_creating(&2, 110); + // Setup two accounts with free balance above the existential threshold. + let _ = Balances::deposit_creating(&1, 110); + let _ = Balances::deposit_creating(&2, 110); - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::free_balance(2), 110); - assert_eq!(pallet_balances::TotalIssuance::::get(), 220); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::free_balance(2), 110); + assert_eq!(pallet_balances::TotalIssuance::::get(), 220); - // Transfer funds from account 1 of such amount that after this transfer - // the balance of account 1 will be below the existential threshold. - // This should lead to the removal of all balance of this account. - assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 20)); + // Transfer funds from account 1 of such amount that after this transfer + // the balance of account 1 will be below the existential threshold. + // This should lead to the removal of all balance of this account. + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 20)); - // Verify free balance removal of account 1. - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::free_balance(2), 130); + // Verify free balance removal of account 1. + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 130); - // Verify that TotalIssuance tracks balance removal when free balance is too low. - assert_eq!(pallet_balances::TotalIssuance::::get(), 130); - }); + // Verify that TotalIssuance tracks balance removal when free balance is too low. + assert_eq!(pallet_balances::TotalIssuance::::get(), 130); + }); } #[test] fn burn_must_work() { - ExtBuilder::default().monied(true).build_and_execute_with(|| { - let init_total_issuance = pallet_balances::TotalIssuance::::get(); - let imbalance = >::burn(10); - assert_eq!(pallet_balances::TotalIssuance::::get(), init_total_issuance - 10); - drop(imbalance); - assert_eq!(pallet_balances::TotalIssuance::::get(), init_total_issuance); - }); + ExtBuilder::default() + .monied(true) + .build_and_execute_with(|| { + let init_total_issuance = pallet_balances::TotalIssuance::::get(); + let imbalance = >::burn(10); + assert_eq!( + pallet_balances::TotalIssuance::::get(), + init_total_issuance - 10 + ); + drop(imbalance); + assert_eq!( + pallet_balances::TotalIssuance::::get(), + init_total_issuance + ); + }); } #[test] #[should_panic = "the balance of any account should always be at least the existential deposit."] fn cannot_set_genesis_value_below_ed() { - EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = 11); - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - crate::GenesisConfig:: { balances: vec![(1, 10)] } - .assimilate_storage(&mut t) - .unwrap(); + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = 11); + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + crate::GenesisConfig:: { + balances: vec![(1, 10)], + } + .assimilate_storage(&mut t) + .unwrap(); } #[test] #[should_panic = "duplicate balances in genesis."] fn cannot_set_genesis_value_twice() { - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - crate::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (1, 15)] } - .assimilate_storage(&mut t) - .unwrap(); + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + crate::GenesisConfig:: { + balances: vec![(1, 10), (2, 20), (1, 15)], + } + .assimilate_storage(&mut t) + .unwrap(); } #[test] fn existential_deposit_respected_when_reserving() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // Set balance to free and reserved at the existential deposit - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 101)); - // Check balance - assert_eq!(Balances::free_balance(1), 101); - assert_eq!(Balances::reserved_balance(1), 0); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + // Set balance to free and reserved at the existential deposit + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 101)); + // Check balance + assert_eq!(Balances::free_balance(1), 101); + assert_eq!(Balances::reserved_balance(1), 0); - // Reserve some free balance - assert_ok!(Balances::reserve(&1, 1)); - // Check balance, the account should be ok. - assert_eq!(Balances::free_balance(1), 100); - assert_eq!(Balances::reserved_balance(1), 1); + // Reserve some free balance + assert_ok!(Balances::reserve(&1, 1)); + // Check balance, the account should be ok. + assert_eq!(Balances::free_balance(1), 100); + assert_eq!(Balances::reserved_balance(1), 1); - // Cannot reserve any more of the free balance. - assert_noop!(Balances::reserve(&1, 1), DispatchError::ConsumerRemaining); - }); + // Cannot reserve any more of the free balance. + assert_noop!(Balances::reserve(&1, 1), DispatchError::ConsumerRemaining); + }); } #[test] fn slash_fails_when_account_needed() { - ExtBuilder::default().existential_deposit(50).build_and_execute_with(|| { - // Set balance to free and reserved at the existential deposit - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 52)); - assert_ok!(Balances::reserve(&1, 1)); - // Check balance - assert_eq!(Balances::free_balance(1), 51); - assert_eq!(Balances::reserved_balance(1), 1); + ExtBuilder::default() + .existential_deposit(50) + .build_and_execute_with(|| { + // Set balance to free and reserved at the existential deposit + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 52)); + assert_ok!(Balances::reserve(&1, 1)); + // Check balance + assert_eq!(Balances::free_balance(1), 51); + assert_eq!(Balances::reserved_balance(1), 1); - // Slash a small amount - let res = Balances::slash(&1, 1); - assert_eq!(res, (NegativeImbalance::new(1), 0)); + // Slash a small amount + let res = Balances::slash(&1, 1); + assert_eq!(res, (NegativeImbalance::new(1), 0)); - // The account should be dead. - assert_eq!(Balances::free_balance(1), 50); - assert_eq!(Balances::reserved_balance(1), 1); + // The account should be dead. + assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Balances::reserved_balance(1), 1); - // Slashing again doesn't work since we require the ED - let res = Balances::slash(&1, 1); - assert_eq!(res, (NegativeImbalance::new(0), 1)); + // Slashing again doesn't work since we require the ED + let res = Balances::slash(&1, 1); + assert_eq!(res, (NegativeImbalance::new(0), 1)); - // The account should be dead. - assert_eq!(Balances::free_balance(1), 50); - assert_eq!(Balances::reserved_balance(1), 1); - }); + // The account should be dead. + assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Balances::reserved_balance(1), 1); + }); } #[test] fn account_deleted_when_just_dust() { - ExtBuilder::default().existential_deposit(50).build_and_execute_with(|| { - // Set balance to free and reserved at the existential deposit - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 50)); - // Check balance - assert_eq!(Balances::free_balance(1), 50); + ExtBuilder::default() + .existential_deposit(50) + .build_and_execute_with(|| { + // Set balance to free and reserved at the existential deposit + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 50)); + // Check balance + assert_eq!(Balances::free_balance(1), 50); - // Slash a small amount - let res = Balances::slash(&1, 1); - assert_eq!(res, (NegativeImbalance::new(1), 0)); + // Slash a small amount + let res = Balances::slash(&1, 1); + assert_eq!(res, (NegativeImbalance::new(1), 0)); - // The account should be dead. - assert_eq!(Balances::free_balance(1), 0); - }); + // The account should be dead. + assert_eq!(Balances::free_balance(1), 0); + }); } #[test] fn emit_events_with_reserve_and_unreserve() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 100); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); - System::set_block_number(2); - assert_ok!(Balances::reserve(&1, 10)); + System::set_block_number(2); + assert_ok!(Balances::reserve(&1, 10)); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Reserved { - who: 1, - amount: 10, - })); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Reserved { + who: 1, + amount: 10, + })); - System::set_block_number(3); - assert!(Balances::unreserve(&1, 5).is_zero()); + System::set_block_number(3); + assert!(Balances::unreserve(&1, 5).is_zero()); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { - who: 1, - amount: 5, - })); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { + who: 1, + amount: 5, + })); - System::set_block_number(4); - assert_eq!(Balances::unreserve(&1, 6), 1); + System::set_block_number(4); + assert_eq!(Balances::unreserve(&1, 6), 1); - // should only unreserve 5 - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { - who: 1, - amount: 5, - })); - }); + // should only unreserve 5 + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { + who: 1, + amount: 5, + })); + }); } #[test] fn emit_events_with_changing_locks() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 100); - System::reset_events(); - - // Locks = [] --> [10] - Balances::set_lock(*b"LOCK_000", &1, 10, WithdrawReasons::TRANSFER); - assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Locked { who: 1, amount: 10 })]); - - // Locks = [10] --> [15] - Balances::set_lock(*b"LOCK_000", &1, 15, WithdrawReasons::TRANSFER); - assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Locked { who: 1, amount: 5 })]); - - // Locks = [15] --> [15, 20] - Balances::set_lock(*b"LOCK_001", &1, 20, WithdrawReasons::TRANSACTION_PAYMENT); - assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Locked { who: 1, amount: 5 })]); - - // Locks = [15, 20] --> [17, 20] - Balances::set_lock(*b"LOCK_000", &1, 17, WithdrawReasons::TRANSACTION_PAYMENT); - for event in events() { - match event { - RuntimeEvent::Balances(crate::Event::Locked { .. }) => { - assert!(false, "unexpected lock event") - }, - RuntimeEvent::Balances(crate::Event::Unlocked { .. }) => { - assert!(false, "unexpected unlock event") - }, - _ => continue, - } - } - - // Locks = [17, 20] --> [17, 15] - Balances::set_lock(*b"LOCK_001", &1, 15, WithdrawReasons::TRANSFER); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Unlocked { who: 1, amount: 3 })] - ); - - // Locks = [17, 15] --> [15] - Balances::remove_lock(*b"LOCK_000", &1); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Unlocked { who: 1, amount: 2 })] - ); - - // Locks = [15] --> [] - Balances::remove_lock(*b"LOCK_001", &1); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Unlocked { who: 1, amount: 15 })] - ); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); + System::reset_events(); + + // Locks = [] --> [10] + Balances::set_lock(*b"LOCK_000", &1, 10, WithdrawReasons::TRANSFER); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Locked { + who: 1, + amount: 10 + })] + ); + + // Locks = [10] --> [15] + Balances::set_lock(*b"LOCK_000", &1, 15, WithdrawReasons::TRANSFER); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Locked { + who: 1, + amount: 5 + })] + ); + + // Locks = [15] --> [15, 20] + Balances::set_lock(*b"LOCK_001", &1, 20, WithdrawReasons::TRANSACTION_PAYMENT); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Locked { + who: 1, + amount: 5 + })] + ); + + // Locks = [15, 20] --> [17, 20] + Balances::set_lock(*b"LOCK_000", &1, 17, WithdrawReasons::TRANSACTION_PAYMENT); + for event in events() { + match event { + RuntimeEvent::Balances(crate::Event::Locked { .. }) => { + assert!(false, "unexpected lock event") + } + RuntimeEvent::Balances(crate::Event::Unlocked { .. }) => { + assert!(false, "unexpected unlock event") + } + _ => continue, + } + } + + // Locks = [17, 20] --> [17, 15] + Balances::set_lock(*b"LOCK_001", &1, 15, WithdrawReasons::TRANSFER); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Unlocked { + who: 1, + amount: 3 + })] + ); + + // Locks = [17, 15] --> [15] + Balances::remove_lock(*b"LOCK_000", &1); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Unlocked { + who: 1, + amount: 2 + })] + ); + + // Locks = [15] --> [] + Balances::remove_lock(*b"LOCK_001", &1); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Unlocked { + who: 1, + amount: 15 + })] + ); + }); } #[test] fn emit_events_with_existential_deposit() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 100)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::NewAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), - RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), - RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), - ] - ); - - let res = Balances::slash(&1, 1); - assert_eq!(res, (NegativeImbalance::new(1), 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 99 }), - RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 }), - RuntimeEvent::Balances(crate::Event::Rescinded { amount: 1 }), - ] - ); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 100)); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::NewAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Endowed { + account: 1, + free_balance: 100 + }), + RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), + RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), + ] + ); + + let res = Balances::slash(&1, 1); + assert_eq!(res, (NegativeImbalance::new(1), 0)); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::DustLost { + account: 1, + amount: 99 + }), + RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 }), + RuntimeEvent::Balances(crate::Event::Rescinded { amount: 1 }), + ] + ); + }); } #[test] fn emit_events_with_no_existential_deposit_suicide() { - ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 100); - - assert_eq!( - events(), - [ - RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), - RuntimeEvent::System(system::Event::NewAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), - RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), - ] - ); - - let res = Balances::slash(&1, 100); - assert_eq!(res, (NegativeImbalance::new(100), 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 100 }), - RuntimeEvent::Balances(crate::Event::Rescinded { amount: 100 }), - ] - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_eq!( + events(), + [ + RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), + RuntimeEvent::System(system::Event::NewAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Endowed { + account: 1, + free_balance: 100 + }), + RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), + ] + ); + + let res = Balances::slash(&1, 100); + assert_eq!(res, (NegativeImbalance::new(100), 0)); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Slashed { + who: 1, + amount: 100 + }), + RuntimeEvent::Balances(crate::Event::Rescinded { amount: 100 }), + ] + ); + }); } #[test] fn slash_over_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // SCENARIO: Over-slash will kill account, and report missing slash amount. - Balances::make_free_balance_be(&1, 1_000); - // Slashed full free_balance, and reports 300 not slashed - assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1000), 300)); - // Account is dead - assert!(!System::account_exists(&1)); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + // SCENARIO: Over-slash will kill account, and report missing slash amount. + Balances::make_free_balance_be(&1, 1_000); + // Slashed full free_balance, and reports 300 not slashed + assert_eq!( + Balances::slash(&1, 1_300), + (NegativeImbalance::new(1000), 300) + ); + // Account is dead + assert!(!System::account_exists(&1)); + }); } #[test] fn slash_full_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 1_000); - // Slashed completed in full - assert_eq!(Balances::slash(&1, 1_000), (NegativeImbalance::new(1000), 0)); - // Account is still alive - assert!(!System::account_exists(&1)); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { - who: 1, - amount: 1000, - })); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + // Slashed completed in full + assert_eq!( + Balances::slash(&1, 1_000), + (NegativeImbalance::new(1000), 0) + ); + // Account is still alive + assert!(!System::account_exists(&1)); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { + who: 1, + amount: 1000, + })); + }); } #[test] fn slash_partial_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 1_000); - // Slashed completed in full - assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); - // Account is still alive - assert!(System::account_exists(&1)); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { - who: 1, - amount: 900, - })); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + // Slashed completed in full + assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); + // Account is still alive + assert!(System::account_exists(&1)); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { + who: 1, + amount: 900, + })); + }); } #[test] fn slash_dusting_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 1_000); - // Slashed completed in full - assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(950), 0)); - assert!(!System::account_exists(&1)); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { - who: 1, - amount: 950, - })); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + // Slashed completed in full + assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(950), 0)); + assert!(!System::account_exists(&1)); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { + who: 1, + amount: 950, + })); + }); } #[test] fn slash_does_not_take_from_reserve() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 1_000); - assert_ok!(Balances::reserve(&1, 100)); - // Slashed completed in full - assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(800), 100)); - assert_eq!(Balances::reserved_balance(1), 100); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { - who: 1, - amount: 800, - })); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(Balances::reserve(&1, 100)); + // Slashed completed in full + assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(800), 100)); + assert_eq!(Balances::reserved_balance(1), 100); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { + who: 1, + amount: 800, + })); + }); } #[test] fn slash_consumed_slash_full_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 1_000); - assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full - assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); - // Account is still alive - assert!(System::account_exists(&1)); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests + // Slashed completed in full + assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); + // Account is still alive + assert!(System::account_exists(&1)); + }); } #[test] fn slash_consumed_slash_over_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 1_000); - assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full - assert_eq!(Balances::slash(&1, 1_000), (NegativeImbalance::new(900), 100)); - // Account is still alive - assert!(System::account_exists(&1)); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests + // Slashed completed in full + assert_eq!( + Balances::slash(&1, 1_000), + (NegativeImbalance::new(900), 100) + ); + // Account is still alive + assert!(System::account_exists(&1)); + }); } #[test] fn slash_consumed_slash_partial_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 1_000); - assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full - assert_eq!(Balances::slash(&1, 800), (NegativeImbalance::new(800), 0)); - // Account is still alive - assert!(System::account_exists(&1)); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests + // Slashed completed in full + assert_eq!(Balances::slash(&1, 800), (NegativeImbalance::new(800), 0)); + // Account is still alive + assert!(System::account_exists(&1)); + }); } #[test] fn slash_on_non_existent_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // Slash on non-existent account is okay. - assert_eq!(Balances::slash(&12345, 1_300), (NegativeImbalance::new(0), 1300)); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + // Slash on non-existent account is okay. + assert_eq!( + Balances::slash(&12345, 1_300), + (NegativeImbalance::new(0), 1300) + ); + }); } #[test] fn slash_reserved_slash_partial_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 1_000); - assert_ok!(Balances::reserve(&1, 900)); - // Slashed completed in full - assert_eq!(Balances::slash_reserved(&1, 800), (NegativeImbalance::new(800), 0)); - assert_eq!(System::consumers(&1), 1); - assert_eq!(Balances::reserved_balance(1), 100); - assert_eq!(Balances::free_balance(1), 100); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(Balances::reserve(&1, 900)); + // Slashed completed in full + assert_eq!( + Balances::slash_reserved(&1, 800), + (NegativeImbalance::new(800), 0) + ); + assert_eq!(System::consumers(&1), 1); + assert_eq!(Balances::reserved_balance(1), 100); + assert_eq!(Balances::free_balance(1), 100); + }); } #[test] fn slash_reserved_slash_everything_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&1, 1_000); - assert_ok!(Balances::reserve(&1, 900)); - assert_eq!(System::consumers(&1), 1); - // Slashed completed in full - assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(900), 0)); - assert_eq!(System::consumers(&1), 0); - // Account is still alive - assert!(System::account_exists(&1)); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(Balances::reserve(&1, 900)); + assert_eq!(System::consumers(&1), 1); + // Slashed completed in full + assert_eq!( + Balances::slash_reserved(&1, 900), + (NegativeImbalance::new(900), 0) + ); + assert_eq!(System::consumers(&1), 0); + // Account is still alive + assert!(System::account_exists(&1)); + }); } #[test] fn slash_reserved_overslash_does_not_touch_free_balance() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // SCENARIO: Over-slash doesn't touch free balance. - Balances::make_free_balance_be(&1, 1_000); - assert_ok!(Balances::reserve(&1, 800)); - // Slashed done - assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(800), 100)); - assert_eq!(Balances::free_balance(1), 200); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + // SCENARIO: Over-slash doesn't touch free balance. + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(Balances::reserve(&1, 800)); + // Slashed done + assert_eq!( + Balances::slash_reserved(&1, 900), + (NegativeImbalance::new(800), 100) + ); + assert_eq!(Balances::free_balance(1), 200); + }); } #[test] fn slash_reserved_on_non_existent_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // Slash on non-existent account is okay. - assert_eq!(Balances::slash_reserved(&12345, 1_300), (NegativeImbalance::new(0), 1300)); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + // Slash on non-existent account is okay. + assert_eq!( + Balances::slash_reserved(&12345, 1_300), + (NegativeImbalance::new(0), 1300) + ); + }); } #[test] fn operations_on_dead_account_should_not_change_state() { - // These functions all use `mutate_account` which may introduce a storage change when - // the account never existed to begin with, and shouldn't exist in the end. - ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { - assert!(!frame_system::Account::::contains_key(1337)); - - // Unreserve - assert_storage_noop!(assert_eq!(Balances::unreserve(&1337, 42), 42)); - // Reserve - assert_noop!(Balances::reserve(&1337, 42), Error::::InsufficientBalance); - // Slash Reserve - assert_storage_noop!(assert_eq!(Balances::slash_reserved(&1337, 42).1, 42)); - // Repatriate Reserve - assert_noop!( - Balances::repatriate_reserved(&1337, &1338, 42, Free), - Error::::DeadAccount - ); - // Slash - assert_storage_noop!(assert_eq!(Balances::slash(&1337, 42).1, 42)); - }); + // These functions all use `mutate_account` which may introduce a storage change when + // the account never existed to begin with, and shouldn't exist in the end. + ExtBuilder::default() + .existential_deposit(1) + .build_and_execute_with(|| { + assert!(!frame_system::Account::::contains_key(1337)); + + // Unreserve + assert_storage_noop!(assert_eq!(Balances::unreserve(&1337, 42), 42)); + // Reserve + assert_noop!( + Balances::reserve(&1337, 42), + Error::::InsufficientBalance + ); + // Slash Reserve + assert_storage_noop!(assert_eq!(Balances::slash_reserved(&1337, 42).1, 42)); + // Repatriate Reserve + assert_noop!( + Balances::repatriate_reserved(&1337, &1338, 42, Free), + Error::::DeadAccount + ); + // Slash + assert_storage_noop!(assert_eq!(Balances::slash(&1337, 42).1, 42)); + }); } #[test] #[should_panic = "The existential deposit must be greater than zero!"] fn zero_ed_is_prohibited() { - // These functions all use `mutate_account` which may introduce a storage change when - // the account never existed to begin with, and shouldn't exist in the end. - ExtBuilder::default().existential_deposit(0).build_and_execute_with(|| { - Balances::integrity_test(); - }); + // These functions all use `mutate_account` which may introduce a storage change when + // the account never existed to begin with, and shouldn't exist in the end. + ExtBuilder::default() + .existential_deposit(0) + .build_and_execute_with(|| { + Balances::integrity_test(); + }); } #[test] fn named_reserve_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); - let id_1 = TestId::Foo; - let id_2 = TestId::Bar; - let id_3 = TestId::Baz; + let id_1 = TestId::Foo; + let id_2 = TestId::Bar; + let id_3 = TestId::Baz; - // reserve + // reserve - assert_noop!( - Balances::reserve_named(&id_1, &1, 112), - Error::::InsufficientBalance - ); + assert_noop!( + Balances::reserve_named(&id_1, &1, 112), + Error::::InsufficientBalance + ); - assert_ok!(Balances::reserve_named(&id_1, &1, 12)); + assert_ok!(Balances::reserve_named(&id_1, &1, 12)); - assert_eq!(Balances::reserved_balance(1), 12); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 12); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); + assert_eq!(Balances::reserved_balance(1), 12); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 12); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); - assert_ok!(Balances::reserve_named(&id_1, &1, 2)); + assert_ok!(Balances::reserve_named(&id_1, &1, 2)); - assert_eq!(Balances::reserved_balance(1), 14); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); + assert_eq!(Balances::reserved_balance(1), 14); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); - assert_ok!(Balances::reserve_named(&id_2, &1, 23)); + assert_ok!(Balances::reserve_named(&id_2, &1, 23)); - assert_eq!(Balances::reserved_balance(1), 37); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); + assert_eq!(Balances::reserved_balance(1), 37); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); - assert_ok!(Balances::reserve(&1, 34)); + assert_ok!(Balances::reserve(&1, 34)); - assert_eq!(Balances::reserved_balance(1), 71); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); + assert_eq!(Balances::reserved_balance(1), 71); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); - assert_eq!(Balances::total_balance(&1), 111); - assert_eq!(Balances::free_balance(1), 40); + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 40); - assert_noop!(Balances::reserve_named(&id_3, &1, 2), Error::::TooManyReserves); + assert_noop!( + Balances::reserve_named(&id_3, &1, 2), + Error::::TooManyReserves + ); - // unreserve + // unreserve - assert_eq!(Balances::unreserve_named(&id_1, &1, 10), 0); + assert_eq!(Balances::unreserve_named(&id_1, &1, 10), 0); - assert_eq!(Balances::reserved_balance(1), 61); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 4); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); + assert_eq!(Balances::reserved_balance(1), 61); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 4); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); - assert_eq!(Balances::unreserve_named(&id_1, &1, 5), 1); + assert_eq!(Balances::unreserve_named(&id_1, &1, 5), 1); - assert_eq!(Balances::reserved_balance(1), 57); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); + assert_eq!(Balances::reserved_balance(1), 57); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); - assert_eq!(Balances::unreserve_named(&id_2, &1, 3), 0); + assert_eq!(Balances::unreserve_named(&id_2, &1, 3), 0); - assert_eq!(Balances::reserved_balance(1), 54); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20); + assert_eq!(Balances::reserved_balance(1), 54); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20); - assert_eq!(Balances::total_balance(&1), 111); - assert_eq!(Balances::free_balance(1), 57); + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 57); - // slash_reserved_named + // slash_reserved_named - assert_ok!(Balances::reserve_named(&id_1, &1, 10)); + assert_ok!(Balances::reserve_named(&id_1, &1, 10)); - assert_eq!(Balances::slash_reserved_named(&id_1, &1, 25).1, 15); + assert_eq!(Balances::slash_reserved_named(&id_1, &1, 25).1, 15); - assert_eq!(Balances::reserved_balance(1), 54); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20); - assert_eq!(Balances::total_balance(&1), 101); + assert_eq!(Balances::reserved_balance(1), 54); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20); + assert_eq!(Balances::total_balance(&1), 101); - assert_eq!(Balances::slash_reserved_named(&id_2, &1, 5).1, 0); + assert_eq!(Balances::slash_reserved_named(&id_2, &1, 5).1, 0); - assert_eq!(Balances::reserved_balance(1), 49); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15); - assert_eq!(Balances::total_balance(&1), 96); + assert_eq!(Balances::reserved_balance(1), 49); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15); + assert_eq!(Balances::total_balance(&1), 96); - // repatriate_reserved_named + // repatriate_reserved_named - let _ = Balances::deposit_creating(&2, 100); + let _ = Balances::deposit_creating(&2, 100); - assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Reserved).unwrap(), 0); + assert_eq!( + Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Reserved).unwrap(), + 0 + ); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); - assert_eq!(Balances::reserved_balance_named(&id_2, &2), 10); - assert_eq!(Balances::reserved_balance(2), 10); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); + assert_eq!(Balances::reserved_balance_named(&id_2, &2), 10); + assert_eq!(Balances::reserved_balance(2), 10); - assert_eq!(Balances::repatriate_reserved_named(&id_2, &2, &1, 11, Reserved).unwrap(), 1); + assert_eq!( + Balances::repatriate_reserved_named(&id_2, &2, &1, 11, Reserved).unwrap(), + 1 + ); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15); - assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0); - assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15); + assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0); + assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Free).unwrap(), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); - assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0); - assert_eq!(Balances::free_balance(2), 110); + assert_eq!( + Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Free).unwrap(), + 0 + ); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); + assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0); + assert_eq!(Balances::free_balance(2), 110); - // repatriate_reserved_named to self + // repatriate_reserved_named to self - assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &1, 10, Reserved).unwrap(), 5); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); + assert_eq!( + Balances::repatriate_reserved_named(&id_2, &1, &1, 10, Reserved).unwrap(), + 5 + ); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); - assert_eq!(Balances::free_balance(1), 47); + assert_eq!(Balances::free_balance(1), 47); - assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &1, 15, Free).unwrap(), 10); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); + assert_eq!( + Balances::repatriate_reserved_named(&id_2, &1, &1, 15, Free).unwrap(), + 10 + ); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); - assert_eq!(Balances::free_balance(1), 52); - }); + assert_eq!(Balances::free_balance(1), 52); + }); } #[test] fn reserve_must_succeed_if_can_reserve_does() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 1); - let _ = Balances::deposit_creating(&2, 2); - assert!(Balances::can_reserve(&1, 1) == Balances::reserve(&1, 1).is_ok()); - assert!(Balances::can_reserve(&2, 1) == Balances::reserve(&2, 1).is_ok()); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 1); + let _ = Balances::deposit_creating(&2, 2); + assert!(Balances::can_reserve(&1, 1) == Balances::reserve(&1, 1).is_ok()); + assert!(Balances::can_reserve(&2, 1) == Balances::reserve(&2, 1).is_ok()); + }); } #[test] fn reserved_named_to_yourself_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 110); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); - let id = TestId::Foo; + let id = TestId::Foo; - assert_ok!(Balances::reserve_named(&id, &1, 50)); - assert_ok!(Balances::repatriate_reserved_named(&id, &1, &1, 50, Free), 0); - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + assert_ok!(Balances::reserve_named(&id, &1, 50)); + assert_ok!( + Balances::repatriate_reserved_named(&id, &1, &1, 50, Free), + 0 + ); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - assert_ok!(Balances::reserve_named(&id, &1, 50)); - assert_ok!(Balances::repatriate_reserved_named(&id, &1, &1, 60, Free), 10); - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - }); + assert_ok!(Balances::reserve_named(&id, &1, 50)); + assert_ok!( + Balances::repatriate_reserved_named(&id, &1, &1, 60, Free), + 10 + ); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + }); } #[test] fn ensure_reserved_named_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); - let id = TestId::Foo; + let id = TestId::Foo; - assert_ok!(Balances::ensure_reserved_named(&id, &1, 15)); - assert_eq!(Balances::reserved_balance_named(&id, &1), 15); + assert_ok!(Balances::ensure_reserved_named(&id, &1, 15)); + assert_eq!(Balances::reserved_balance_named(&id, &1), 15); - assert_ok!(Balances::ensure_reserved_named(&id, &1, 10)); - assert_eq!(Balances::reserved_balance_named(&id, &1), 10); + assert_ok!(Balances::ensure_reserved_named(&id, &1, 10)); + assert_eq!(Balances::reserved_balance_named(&id, &1), 10); - assert_ok!(Balances::ensure_reserved_named(&id, &1, 20)); - assert_eq!(Balances::reserved_balance_named(&id, &1), 20); - }); + assert_ok!(Balances::ensure_reserved_named(&id, &1, 20)); + assert_eq!(Balances::reserved_balance_named(&id, &1), 20); + }); } #[test] fn unreserve_all_named_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); - let id = TestId::Foo; + let id = TestId::Foo; - assert_ok!(Balances::reserve_named(&id, &1, 15)); + assert_ok!(Balances::reserve_named(&id, &1, 15)); - assert_eq!(Balances::unreserve_all_named(&id, &1), 15); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - assert_eq!(Balances::free_balance(1), 111); + assert_eq!(Balances::unreserve_all_named(&id, &1), 15); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + assert_eq!(Balances::free_balance(1), 111); - assert_eq!(Balances::unreserve_all_named(&id, &1), 0); - }); + assert_eq!(Balances::unreserve_all_named(&id, &1), 0); + }); } #[test] fn slash_all_reserved_named_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); - let id = TestId::Foo; + let id = TestId::Foo; - assert_ok!(Balances::reserve_named(&id, &1, 15)); + assert_ok!(Balances::reserve_named(&id, &1, 15)); - assert_eq!(Balances::slash_all_reserved_named(&id, &1).peek(), 15); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - assert_eq!(Balances::free_balance(1), 96); + assert_eq!(Balances::slash_all_reserved_named(&id, &1).peek(), 15); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + assert_eq!(Balances::free_balance(1), 96); - assert_eq!(Balances::slash_all_reserved_named(&id, &1).peek(), 0); - }); + assert_eq!(Balances::slash_all_reserved_named(&id, &1).peek(), 0); + }); } #[test] fn repatriate_all_reserved_named_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - let _ = Balances::deposit_creating(&2, 10); - let _ = Balances::deposit_creating(&3, 10); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + let _ = Balances::deposit_creating(&2, 10); + let _ = Balances::deposit_creating(&3, 10); - let id = TestId::Foo; + let id = TestId::Foo; - assert_ok!(Balances::reserve_named(&id, &1, 15)); + assert_ok!(Balances::reserve_named(&id, &1, 15)); - assert_ok!(Balances::repatriate_all_reserved_named(&id, &1, &2, Reserved)); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id, &2), 15); + assert_ok!(Balances::repatriate_all_reserved_named( + &id, &1, &2, Reserved + )); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id, &2), 15); - assert_ok!(Balances::repatriate_all_reserved_named(&id, &2, &3, Free)); - assert_eq!(Balances::reserved_balance_named(&id, &2), 0); - assert_eq!(Balances::free_balance(3), 25); - }); + assert_ok!(Balances::repatriate_all_reserved_named(&id, &2, &3, Free)); + assert_eq!(Balances::reserved_balance_named(&id, &2), 0); + assert_eq!(Balances::free_balance(3), 25); + }); } #[test] fn freezing_and_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Consumer is shared between freezing and locking. - assert_eq!(System::consumers(&1), 0); - assert_ok!(>::set_freeze(&TestId::Foo, &1, 4)); - assert_eq!(System::consumers(&1), 1); - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&1), 1); - - // Frozen and locked balances update correctly. - assert_eq!(Balances::account(&1).frozen, 5); - assert_ok!(>::set_freeze(&TestId::Foo, &1, 6)); - assert_eq!(Balances::account(&1).frozen, 6); - assert_ok!(>::set_freeze(&TestId::Foo, &1, 4)); - assert_eq!(Balances::account(&1).frozen, 5); - Balances::set_lock(ID_1, &1, 3, WithdrawReasons::all()); - assert_eq!(Balances::account(&1).frozen, 4); - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - assert_eq!(Balances::account(&1).frozen, 5); - - // Locks update correctly. - Balances::remove_lock(ID_1, &1); - assert_eq!(Balances::account(&1).frozen, 4); - assert_eq!(System::consumers(&1), 1); - assert_ok!(>::set_freeze(&TestId::Foo, &1, 0)); - assert_eq!(Balances::account(&1).frozen, 0); - assert_eq!(System::consumers(&1), 0); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + // Consumer is shared between freezing and locking. + assert_eq!(System::consumers(&1), 0); + assert_ok!(>::set_freeze( + &TestId::Foo, + &1, + 4 + )); + assert_eq!(System::consumers(&1), 1); + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 1); + + // Frozen and locked balances update correctly. + assert_eq!(Balances::account(&1).frozen, 5); + assert_ok!(>::set_freeze( + &TestId::Foo, + &1, + 6 + )); + assert_eq!(Balances::account(&1).frozen, 6); + assert_ok!(>::set_freeze( + &TestId::Foo, + &1, + 4 + )); + assert_eq!(Balances::account(&1).frozen, 5); + Balances::set_lock(ID_1, &1, 3, WithdrawReasons::all()); + assert_eq!(Balances::account(&1).frozen, 4); + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_eq!(Balances::account(&1).frozen, 5); + + // Locks update correctly. + Balances::remove_lock(ID_1, &1); + assert_eq!(Balances::account(&1).frozen, 4); + assert_eq!(System::consumers(&1), 1); + assert_ok!(>::set_freeze( + &TestId::Foo, + &1, + 0 + )); + assert_eq!(Balances::account(&1).frozen, 0); + assert_eq!(System::consumers(&1), 0); + }); } #[test] fn self_transfer_noop() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - assert_eq!(pallet_balances::TotalIssuance::::get(), 0); - let _ = Balances::deposit_creating(&1, 100); - - // The account is set up properly: - assert_eq!( - events(), - [ - Event::Deposit { who: 1, amount: 100 }.into(), - SysEvent::NewAccount { account: 1 }.into(), - Event::Endowed { account: 1, free_balance: 100 }.into(), - Event::Issued { amount: 100 }.into(), - ] - ); - assert_eq!(Balances::free_balance(1), 100); - assert_eq!(pallet_balances::TotalIssuance::::get(), 100); - - // Transfers to self are No-OPs: - let _g = StorageNoopGuard::new(); - for i in 0..200 { - let r = Balances::transfer_allow_death(Some(1).into(), 1, i); - - if i <= 100 { - assert_ok!(r); - } else { - assert!(r.is_err()); - } - - assert!(events().is_empty()); - assert_eq!(Balances::free_balance(1), 100, "Balance unchanged by self transfer"); - assert_eq!( - pallet_balances::TotalIssuance::::get(), - 100, - "TI unchanged by self transfers" - ); - } - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + assert_eq!(pallet_balances::TotalIssuance::::get(), 0); + let _ = Balances::deposit_creating(&1, 100); + + // The account is set up properly: + assert_eq!( + events(), + [ + Event::Deposit { + who: 1, + amount: 100 + } + .into(), + SysEvent::NewAccount { account: 1 }.into(), + Event::Endowed { + account: 1, + free_balance: 100 + } + .into(), + Event::Issued { amount: 100 }.into(), + ] + ); + assert_eq!(Balances::free_balance(1), 100); + assert_eq!(pallet_balances::TotalIssuance::::get(), 100); + + // Transfers to self are No-OPs: + let _g = StorageNoopGuard::new(); + for i in 0..200 { + let r = Balances::transfer_allow_death(Some(1).into(), 1, i); + + if i <= 100 { + assert_ok!(r); + } else { + assert!(r.is_err()); + } + + assert!(events().is_empty()); + assert_eq!( + Balances::free_balance(1), + 100, + "Balance unchanged by self transfer" + ); + assert_eq!( + pallet_balances::TotalIssuance::::get(), + 100, + "TI unchanged by self transfers" + ); + } + }); } diff --git a/pallets/balances/src/tests/dispatchable_tests.rs b/pallets/balances/src/tests/dispatchable_tests.rs index b75c3e69..bf3792bb 100644 --- a/pallets/balances/src/tests/dispatchable_tests.rs +++ b/pallets/balances/src/tests/dispatchable_tests.rs @@ -19,8 +19,8 @@ use super::*; use crate::{ - AdjustmentDirection::{Decrease as Dec, Increase as Inc}, - Event, + AdjustmentDirection::{Decrease as Dec, Increase as Inc}, + Event, }; use frame_support::traits::{fungible::Unbalanced, tokens::Preservation::Expendable}; use fungible::{hold::Mutate as HoldMutate, Inspect, Mutate}; @@ -30,355 +30,441 @@ const ALICE: u64 = 1; #[test] fn default_indexing_on_new_accounts_should_not_work2() { - ExtBuilder::default() - .existential_deposit(10) - .monied(true) - .build_and_execute_with(|| { - // account 5 should not exist - // ext_deposit is 10, value is 9, not satisfies for ext_deposit - assert_noop!( - Balances::transfer_allow_death(Some(1).into(), 5, 9), - TokenError::BelowMinimum, - ); - assert_eq!(Balances::free_balance(1), 100); - }); + ExtBuilder::default() + .existential_deposit(10) + .monied(true) + .build_and_execute_with(|| { + // account 5 should not exist + // ext_deposit is 10, value is 9, not satisfies for ext_deposit + assert_noop!( + Balances::transfer_allow_death(Some(1).into(), 5, 9), + TokenError::BelowMinimum, + ); + assert_eq!(Balances::free_balance(1), 100); + }); } #[test] fn dust_account_removal_should_work() { - ExtBuilder::default() - .existential_deposit(100) - .monied(true) - .build_and_execute_with(|| { - System::inc_account_nonce(2); - assert_eq!(System::account_nonce(2), 1); - assert_eq!(Balances::total_balance(&2), 2000); - // index 1 (account 2) becomes zombie - assert_ok!(Balances::transfer_allow_death(Some(2).into(), 5, 1901)); - assert_eq!(Balances::total_balance(&2), 0); - assert_eq!(Balances::total_balance(&5), 1901); - assert_eq!(System::account_nonce(2), 0); - }); + ExtBuilder::default() + .existential_deposit(100) + .monied(true) + .build_and_execute_with(|| { + System::inc_account_nonce(2); + assert_eq!(System::account_nonce(2), 1); + assert_eq!(Balances::total_balance(&2), 2000); + // index 1 (account 2) becomes zombie + assert_ok!(Balances::transfer_allow_death(Some(2).into(), 5, 1901)); + assert_eq!(Balances::total_balance(&2), 0); + assert_eq!(Balances::total_balance(&5), 1901); + assert_eq!(System::account_nonce(2), 0); + }); } #[test] fn balance_transfer_works() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::mint_into(&1, 111); - assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 69)); - assert_eq!(Balances::total_balance(&1), 42); - assert_eq!(Balances::total_balance(&2), 69); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 111); + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 69)); + assert_eq!(Balances::total_balance(&1), 42); + assert_eq!(Balances::total_balance(&2), 69); + }); } #[test] fn force_transfer_works() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::mint_into(&1, 111); - assert_noop!(Balances::force_transfer(Some(2).into(), 1, 2, 69), BadOrigin,); - assert_ok!(Balances::force_transfer(RawOrigin::Root.into(), 1, 2, 69)); - assert_eq!(Balances::total_balance(&1), 42); - assert_eq!(Balances::total_balance(&2), 69); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 111); + assert_noop!( + Balances::force_transfer(Some(2).into(), 1, 2, 69), + BadOrigin, + ); + assert_ok!(Balances::force_transfer(RawOrigin::Root.into(), 1, 2, 69)); + assert_eq!(Balances::total_balance(&1), 42); + assert_eq!(Balances::total_balance(&2), 69); + }); } #[test] fn balance_transfer_when_on_hold_should_not_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::mint_into(&1, 111); - assert_ok!(Balances::hold(&TestId::Foo, &1, 69)); - assert_noop!( - Balances::transfer_allow_death(Some(1).into(), 2, 69), - TokenError::FundsUnavailable, - ); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 111); + assert_ok!(Balances::hold(&TestId::Foo, &1, 69)); + assert_noop!( + Balances::transfer_allow_death(Some(1).into(), 2, 69), + TokenError::FundsUnavailable, + ); + }); } #[test] fn transfer_keep_alive_works() { - ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { - let _ = Balances::mint_into(&1, 100); - assert_noop!( - Balances::transfer_keep_alive(Some(1).into(), 2, 100), - TokenError::NotExpendable - ); - assert_eq!(Balances::total_balance(&1), 100); - assert_eq!(Balances::total_balance(&2), 0); - }); + ExtBuilder::default() + .existential_deposit(1) + .build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 100); + assert_noop!( + Balances::transfer_keep_alive(Some(1).into(), 2, 100), + TokenError::NotExpendable + ); + assert_eq!(Balances::total_balance(&1), 100); + assert_eq!(Balances::total_balance(&2), 0); + }); } #[test] fn transfer_keep_alive_all_free_succeed() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 300)); - assert_ok!(Balances::hold(&TestId::Foo, &1, 100)); - assert_ok!(Balances::transfer_keep_alive(Some(1).into(), 2, 100)); - assert_eq!(Balances::total_balance(&1), 200); - assert_eq!(Balances::total_balance(&2), 100); - }); + ExtBuilder::default() + .existential_deposit(100) + .build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 300)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 100)); + assert_ok!(Balances::transfer_keep_alive(Some(1).into(), 2, 100)); + assert_eq!(Balances::total_balance(&1), 200); + assert_eq!(Balances::total_balance(&2), 100); + }); } #[test] fn transfer_all_works_1() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // setup - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200)); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); - // transfer all and allow death - assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); - assert_eq!(Balances::total_balance(&1), 0); - assert_eq!(Balances::total_balance(&2), 200); - }); + ExtBuilder::default() + .existential_deposit(100) + .build() + .execute_with(|| { + // setup + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); + // transfer all and allow death + assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); + assert_eq!(Balances::total_balance(&1), 0); + assert_eq!(Balances::total_balance(&2), 200); + }); } #[test] fn transfer_all_works_2() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // setup - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200)); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); - // transfer all and keep alive - assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); - assert_eq!(Balances::total_balance(&1), 100); - assert_eq!(Balances::total_balance(&2), 100); - }); + ExtBuilder::default() + .existential_deposit(100) + .build() + .execute_with(|| { + // setup + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); + // transfer all and keep alive + assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); + assert_eq!(Balances::total_balance(&1), 100); + assert_eq!(Balances::total_balance(&2), 100); + }); } #[test] fn transfer_all_works_3() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // setup - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210)); - assert_ok!(Balances::hold(&TestId::Foo, &1, 10)); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); - // transfer all and allow death w/ reserved - assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); - assert_eq!(Balances::total_balance(&1), 110); - assert_eq!(Balances::total_balance(&2), 100); - }); + ExtBuilder::default() + .existential_deposit(100) + .build() + .execute_with(|| { + // setup + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 10)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); + // transfer all and allow death w/ reserved + assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); + assert_eq!(Balances::total_balance(&1), 110); + assert_eq!(Balances::total_balance(&2), 100); + }); } #[test] fn transfer_all_works_4() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // setup - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210)); - assert_ok!(Balances::hold(&TestId::Foo, &1, 10)); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); - // transfer all and keep alive w/ reserved - assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); - assert_eq!(Balances::total_balance(&1), 110); - assert_eq!(Balances::total_balance(&2), 100); - }); + ExtBuilder::default() + .existential_deposit(100) + .build() + .execute_with(|| { + // setup + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 10)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); + // transfer all and keep alive w/ reserved + assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); + assert_eq!(Balances::total_balance(&1), 110); + assert_eq!(Balances::total_balance(&2), 100); + }); } #[test] fn set_balance_handles_killing_account() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::mint_into(&1, 111); - assert_ok!(frame_system::Pallet::::inc_consumers(&1)); - assert_noop!( - Balances::force_set_balance(RuntimeOrigin::root(), 1, 0), - DispatchError::ConsumerRemaining, - ); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 111); + assert_ok!(frame_system::Pallet::::inc_consumers(&1)); + assert_noop!( + Balances::force_set_balance(RuntimeOrigin::root(), 1, 0), + DispatchError::ConsumerRemaining, + ); + }); } #[test] fn set_balance_handles_total_issuance() { - ExtBuilder::default().build_and_execute_with(|| { - let old_total_issuance = pallet_balances::TotalIssuance::::get(); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 69)); - assert_eq!(pallet_balances::TotalIssuance::::get(), old_total_issuance + 69); - assert_eq!(Balances::total_balance(&1337), 69); - assert_eq!(Balances::free_balance(1337), 69); - }); + ExtBuilder::default().build_and_execute_with(|| { + let old_total_issuance = pallet_balances::TotalIssuance::::get(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 69)); + assert_eq!( + pallet_balances::TotalIssuance::::get(), + old_total_issuance + 69 + ); + assert_eq!(Balances::total_balance(&1337), 69); + assert_eq!(Balances::free_balance(1337), 69); + }); } #[test] fn upgrade_accounts_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - System::inc_providers(&7); - assert_ok!(::AccountStore::try_mutate_exists( - &7, - |a| -> DispatchResult { - *a = Some(AccountData { - free: 5, - reserved: 5, - frozen: Zero::zero(), - flags: crate::types::ExtraFlags::old_logic(), - }); - Ok(()) - } - )); - assert!(!Balances::account(&7).flags.is_new_logic()); - assert_eq!(System::providers(&7), 1); - assert_eq!(System::consumers(&7), 0); - assert_ok!(Balances::upgrade_accounts(Some(1).into(), vec![7])); - assert!(Balances::account(&7).flags.is_new_logic()); - assert_eq!(System::providers(&7), 1); - assert_eq!(System::consumers(&7), 1); - - >::unreserve(&7, 5); - assert_ok!(>::transfer(&7, &1, 10, Expendable)); - assert_eq!(Balances::total_balance(&7), 0); - assert_eq!(System::providers(&7), 0); - assert_eq!(System::consumers(&7), 0); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + System::inc_providers(&7); + assert_ok!(::AccountStore::try_mutate_exists( + &7, + |a| -> DispatchResult { + *a = Some(AccountData { + free: 5, + reserved: 5, + frozen: Zero::zero(), + flags: crate::types::ExtraFlags::old_logic(), + }); + Ok(()) + } + )); + assert!(!Balances::account(&7).flags.is_new_logic()); + assert_eq!(System::providers(&7), 1); + assert_eq!(System::consumers(&7), 0); + assert_ok!(Balances::upgrade_accounts(Some(1).into(), vec![7])); + assert!(Balances::account(&7).flags.is_new_logic()); + assert_eq!(System::providers(&7), 1); + assert_eq!(System::consumers(&7), 1); + + >::unreserve(&7, 5); + assert_ok!(>::transfer( + &7, &1, 10, Expendable + )); + assert_eq!(Balances::total_balance(&7), 0); + assert_eq!(System::providers(&7), 0); + assert_eq!(System::consumers(&7), 0); + }); } #[test] #[docify::export] fn force_adjust_total_issuance_example() { - ExtBuilder::default().build_and_execute_with(|| { - // First we set the TotalIssuance to 64 by giving Alice a balance of 64. - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), ALICE, 64)); - let old_ti = pallet_balances::TotalIssuance::::get(); - assert_eq!(old_ti, 64, "TI should be 64"); - - // Now test the increase: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 32)); - let new_ti = pallet_balances::TotalIssuance::::get(); - assert_eq!(old_ti + 32, new_ti, "Should increase by 32"); - - // If Alice tries to call it, it errors: - assert_noop!( - Balances::force_adjust_total_issuance(RawOrigin::Signed(ALICE).into(), Inc, 32), - BadOrigin, - ); - }); + ExtBuilder::default().build_and_execute_with(|| { + // First we set the TotalIssuance to 64 by giving Alice a balance of 64. + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + ALICE, + 64 + )); + let old_ti = pallet_balances::TotalIssuance::::get(); + assert_eq!(old_ti, 64, "TI should be 64"); + + // Now test the increase: + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Inc, + 32 + )); + let new_ti = pallet_balances::TotalIssuance::::get(); + assert_eq!(old_ti + 32, new_ti, "Should increase by 32"); + + // If Alice tries to call it, it errors: + assert_noop!( + Balances::force_adjust_total_issuance(RawOrigin::Signed(ALICE).into(), Inc, 32), + BadOrigin, + ); + }); } #[test] fn force_adjust_total_issuance_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 64)); - let ti = pallet_balances::TotalIssuance::::get(); - - // Increase works: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 32)); - assert_eq!(pallet_balances::TotalIssuance::::get(), ti + 32); - System::assert_last_event(RuntimeEvent::Balances(Event::TotalIssuanceForced { - old: 64, - new: 96, - })); - - // Decrease works: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 64)); - assert_eq!(pallet_balances::TotalIssuance::::get(), ti - 32); - System::assert_last_event(RuntimeEvent::Balances(Event::TotalIssuanceForced { - old: 96, - new: 32, - })); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 64)); + let ti = pallet_balances::TotalIssuance::::get(); + + // Increase works: + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Inc, + 32 + )); + assert_eq!(pallet_balances::TotalIssuance::::get(), ti + 32); + System::assert_last_event(RuntimeEvent::Balances(Event::TotalIssuanceForced { + old: 64, + new: 96, + })); + + // Decrease works: + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Dec, + 64 + )); + assert_eq!(pallet_balances::TotalIssuance::::get(), ti - 32); + System::assert_last_event(RuntimeEvent::Balances(Event::TotalIssuanceForced { + old: 96, + new: 32, + })); + }); } #[test] fn force_adjust_total_issuance_saturates() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 64)); - let ti = pallet_balances::TotalIssuance::::get(); - let max = ::Balance::max_value(); - assert_eq!(ti, 64); - - // Increment saturates: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, max)); - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 123)); - assert_eq!(pallet_balances::TotalIssuance::::get(), max); - - // Decrement saturates: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, max)); - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 123)); - assert_eq!(pallet_balances::TotalIssuance::::get(), 0); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 64)); + let ti = pallet_balances::TotalIssuance::::get(); + let max = ::Balance::max_value(); + assert_eq!(ti, 64); + + // Increment saturates: + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Inc, + max + )); + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Inc, + 123 + )); + assert_eq!(pallet_balances::TotalIssuance::::get(), max); + + // Decrement saturates: + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Dec, + max + )); + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Dec, + 123 + )); + assert_eq!(pallet_balances::TotalIssuance::::get(), 0); + }); } #[test] fn force_adjust_total_issuance_rejects_zero_delta() { - ExtBuilder::default().build_and_execute_with(|| { - assert_noop!( - Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 0), - Error::::DeltaZero, - ); - assert_noop!( - Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 0), - Error::::DeltaZero, - ); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_noop!( + Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 0), + Error::::DeltaZero, + ); + assert_noop!( + Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 0), + Error::::DeltaZero, + ); + }); } #[test] fn force_adjust_total_issuance_rejects_more_than_inactive() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 64)); - Balances::deactivate(16u32.into()); - - assert_eq!(pallet_balances::TotalIssuance::::get(), 64); - assert_eq!(Balances::active_issuance(), 48); - - // Works with up to 48: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 40),); - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 8),); - assert_eq!(pallet_balances::TotalIssuance::::get(), 16); - assert_eq!(Balances::active_issuance(), 0); - // Errors with more than 48: - assert_noop!( - Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 1), - Error::::IssuanceDeactivated, - ); - // Increasing again increases the inactive issuance: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 10),); - assert_eq!(pallet_balances::TotalIssuance::::get(), 26); - assert_eq!(Balances::active_issuance(), 10); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 64)); + Balances::deactivate(16u32.into()); + + assert_eq!(pallet_balances::TotalIssuance::::get(), 64); + assert_eq!(Balances::active_issuance(), 48); + + // Works with up to 48: + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Dec, + 40 + ),); + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Dec, + 8 + ),); + assert_eq!(pallet_balances::TotalIssuance::::get(), 16); + assert_eq!(Balances::active_issuance(), 0); + // Errors with more than 48: + assert_noop!( + Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 1), + Error::::IssuanceDeactivated, + ); + // Increasing again increases the inactive issuance: + assert_ok!(Balances::force_adjust_total_issuance( + RawOrigin::Root.into(), + Inc, + 10 + ),); + assert_eq!(pallet_balances::TotalIssuance::::get(), 26); + assert_eq!(Balances::active_issuance(), 10); + }); } #[test] fn burn_works() { - ExtBuilder::default().build().execute_with(|| { - // Prepare account with initial balance - let (account, init_balance) = (1, 37); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account, init_balance)); - let init_issuance = pallet_balances::TotalIssuance::::get(); - let (keep_alive, allow_death) = (true, false); - - // 1. Cannot burn more than what's available - assert_noop!( - Balances::burn(Some(account).into(), init_balance + 1, allow_death), - TokenError::FundsUnavailable, - ); - - // 2. Burn some funds, without reaping the account - let burn_amount_1 = 1; - assert_ok!(Balances::burn(Some(account).into(), burn_amount_1, allow_death)); - System::assert_last_event(RuntimeEvent::Balances(Event::Burned { - who: account, - amount: burn_amount_1, - })); - assert_eq!(pallet_balances::TotalIssuance::::get(), init_issuance - burn_amount_1); - assert_eq!(Balances::total_balance(&account), init_balance - burn_amount_1); - - // 3. Cannot burn funds below existential deposit if `keep_alive` is `true` - let burn_amount_2 = - init_balance - burn_amount_1 - ::ExistentialDeposit::get() + 1; - assert_noop!( - Balances::burn(Some(account).into(), init_balance + 1, keep_alive), - TokenError::FundsUnavailable, - ); - - // 4. Burn some more funds, this time reaping the account - assert_ok!(Balances::burn(Some(account).into(), burn_amount_2, allow_death)); - System::assert_last_event(RuntimeEvent::Balances(Event::Burned { - who: account, - amount: burn_amount_2, - })); - assert_eq!( - pallet_balances::TotalIssuance::::get(), - init_issuance - burn_amount_1 - burn_amount_2 - ); - assert!(Balances::total_balance(&account).is_zero()); - }); + ExtBuilder::default().build().execute_with(|| { + // Prepare account with initial balance + let (account, init_balance) = (1, 37); + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + account, + init_balance + )); + let init_issuance = pallet_balances::TotalIssuance::::get(); + let (keep_alive, allow_death) = (true, false); + + // 1. Cannot burn more than what's available + assert_noop!( + Balances::burn(Some(account).into(), init_balance + 1, allow_death), + TokenError::FundsUnavailable, + ); + + // 2. Burn some funds, without reaping the account + let burn_amount_1 = 1; + assert_ok!(Balances::burn( + Some(account).into(), + burn_amount_1, + allow_death + )); + System::assert_last_event(RuntimeEvent::Balances(Event::Burned { + who: account, + amount: burn_amount_1, + })); + assert_eq!( + pallet_balances::TotalIssuance::::get(), + init_issuance - burn_amount_1 + ); + assert_eq!( + Balances::total_balance(&account), + init_balance - burn_amount_1 + ); + + // 3. Cannot burn funds below existential deposit if `keep_alive` is `true` + let burn_amount_2 = + init_balance - burn_amount_1 - ::ExistentialDeposit::get() + 1; + assert_noop!( + Balances::burn(Some(account).into(), init_balance + 1, keep_alive), + TokenError::FundsUnavailable, + ); + + // 4. Burn some more funds, this time reaping the account + assert_ok!(Balances::burn( + Some(account).into(), + burn_amount_2, + allow_death + )); + System::assert_last_event(RuntimeEvent::Balances(Event::Burned { + who: account, + amount: burn_amount_2, + })); + assert_eq!( + pallet_balances::TotalIssuance::::get(), + init_issuance - burn_amount_1 - burn_amount_2 + ); + assert!(Balances::total_balance(&account).is_zero()); + }); } diff --git a/pallets/balances/src/tests/fungible_tests.rs b/pallets/balances/src/tests/fungible_tests.rs index e42fcca8..c104942e 100644 --- a/pallets/balances/src/tests/fungible_tests.rs +++ b/pallets/balances/src/tests/fungible_tests.rs @@ -19,652 +19,798 @@ use super::*; use frame_support::traits::{ - tokens::{ - Fortitude::{Force, Polite}, - Precision::{BestEffort, Exact}, - Preservation::{Expendable, Preserve, Protect}, - Restriction::Free, - }, - Consideration, Footprint, LinearStoragePrice, MaybeConsideration, + tokens::{ + Fortitude::{Force, Polite}, + Precision::{BestEffort, Exact}, + Preservation::{Expendable, Preserve, Protect}, + Restriction::Free, + }, + Consideration, Footprint, LinearStoragePrice, MaybeConsideration, }; use fungible::{ - FreezeConsideration, HoldConsideration, Inspect, InspectFreeze, InspectHold, - LoneFreezeConsideration, LoneHoldConsideration, Mutate, MutateFreeze, MutateHold, Unbalanced, + FreezeConsideration, HoldConsideration, Inspect, InspectFreeze, InspectHold, + LoneFreezeConsideration, LoneHoldConsideration, Mutate, MutateFreeze, MutateHold, Unbalanced, }; use sp_core::ConstU128; #[test] fn inspect_trait_reducible_balance_basic_works() { - ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| { - Balances::set_balance(&1, 100); - assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100); - assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 90); - assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90); - assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100); - assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90); - assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90); - }); + ExtBuilder::default() + .existential_deposit(10) + .build_and_execute_with(|| { + Balances::set_balance(&1, 100); + assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100); + assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 90); + assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90); + assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100); + assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90); + assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90); + }); } #[test] fn inspect_trait_reducible_balance_other_provide_works() { - ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| { - Balances::set_balance(&1, 100); - System::inc_providers(&1); - assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100); - assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 100); - assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90); - assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100); - assert_eq!(Balances::reducible_balance(&1, Protect, Force), 100); - assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90); - }); + ExtBuilder::default() + .existential_deposit(10) + .build_and_execute_with(|| { + Balances::set_balance(&1, 100); + System::inc_providers(&1); + assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100); + assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 100); + assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90); + assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100); + assert_eq!(Balances::reducible_balance(&1, Protect, Force), 100); + assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90); + }); } #[test] fn inspect_trait_reducible_balance_frozen_works() { - ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| { - Balances::set_balance(&1, 100); - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 50)); - assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 50); - assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 50); - assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 50); - assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 90); - assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90); - assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90); - }); + ExtBuilder::default() + .existential_deposit(10) + .build_and_execute_with(|| { + Balances::set_balance(&1, 100); + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 50)); + assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 50); + assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 50); + assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 50); + assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 90); + assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90); + assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90); + }); } #[test] fn unbalanced_trait_set_balance_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_eq!(>::balance(&1337), 0); - assert_ok!(Balances::write_balance(&1337, 100)); - assert_eq!(>::balance(&1337), 100); - - assert_ok!(>::hold(&TestId::Foo, &1337, 60)); - assert_eq!(>::balance(&1337), 40); - assert_eq!(>::total_balance_on_hold(&1337), 60); - assert_eq!( - >::balance_on_hold(&TestId::Foo, &1337), - 60 - ); - - assert_noop!(Balances::write_balance(&1337, 0), Error::::InsufficientBalance); - - assert_ok!(Balances::write_balance(&1337, 1)); - assert_eq!(>::balance(&1337), 1); - assert_eq!( - >::balance_on_hold(&TestId::Foo, &1337), - 60 - ); - - assert_ok!(>::release(&TestId::Foo, &1337, 60, Exact)); - assert_eq!(>::balance_on_hold(&TestId::Foo, &1337), 0); - assert_eq!(>::total_balance_on_hold(&1337), 0); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_eq!(>::balance(&1337), 0); + assert_ok!(Balances::write_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + + assert_ok!(>::hold( + &TestId::Foo, + &1337, + 60 + )); + assert_eq!(>::balance(&1337), 40); + assert_eq!( + >::total_balance_on_hold(&1337), + 60 + ); + assert_eq!( + >::balance_on_hold(&TestId::Foo, &1337), + 60 + ); + + assert_noop!( + Balances::write_balance(&1337, 0), + Error::::InsufficientBalance + ); + + assert_ok!(Balances::write_balance(&1337, 1)); + assert_eq!(>::balance(&1337), 1); + assert_eq!( + >::balance_on_hold(&TestId::Foo, &1337), + 60 + ); + + assert_ok!(>::release( + &TestId::Foo, + &1337, + 60, + Exact + )); + assert_eq!( + >::balance_on_hold(&TestId::Foo, &1337), + 0 + ); + assert_eq!( + >::total_balance_on_hold(&1337), + 0 + ); + }); } #[test] fn unbalanced_trait_set_total_issuance_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_eq!(>::total_issuance(), 0); - Balances::set_total_issuance(100); - assert_eq!(>::total_issuance(), 100); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_eq!(>::total_issuance(), 0); + Balances::set_total_issuance(100); + assert_eq!(>::total_issuance(), 100); + }); } #[test] fn unbalanced_trait_decrease_balance_simple_works() { - ExtBuilder::default().build_and_execute_with(|| { - // An Account that starts at 100 - assert_ok!(Balances::write_balance(&1337, 100)); - assert_eq!(>::balance(&1337), 100); - // and reserves 50 - assert_ok!(>::hold(&TestId::Foo, &1337, 50)); - assert_eq!(>::balance(&1337), 50); - // and is decreased by 20 - assert_ok!(Balances::decrease_balance(&1337, 20, Exact, Expendable, Polite)); - assert_eq!(>::balance(&1337), 30); - }); + ExtBuilder::default().build_and_execute_with(|| { + // An Account that starts at 100 + assert_ok!(Balances::write_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + // and reserves 50 + assert_ok!(>::hold( + &TestId::Foo, + &1337, + 50 + )); + assert_eq!(>::balance(&1337), 50); + // and is decreased by 20 + assert_ok!(Balances::decrease_balance( + &1337, 20, Exact, Expendable, Polite + )); + assert_eq!(>::balance(&1337), 30); + }); } #[test] fn unbalanced_trait_decrease_balance_works_1() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::write_balance(&1337, 100)); - assert_eq!(>::balance(&1337), 100); - - assert_noop!( - Balances::decrease_balance(&1337, 101, Exact, Expendable, Polite), - TokenError::FundsUnavailable - ); - assert_eq!(Balances::decrease_balance(&1337, 100, Exact, Expendable, Polite), Ok(100)); - assert_eq!(>::balance(&1337), 0); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_ok!(Balances::write_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + + assert_noop!( + Balances::decrease_balance(&1337, 101, Exact, Expendable, Polite), + TokenError::FundsUnavailable + ); + assert_eq!( + Balances::decrease_balance(&1337, 100, Exact, Expendable, Polite), + Ok(100) + ); + assert_eq!(>::balance(&1337), 0); + }); } #[test] fn unbalanced_trait_decrease_balance_works_2() { - ExtBuilder::default().build_and_execute_with(|| { - // free: 40, reserved: 60 - assert_ok!(Balances::write_balance(&1337, 100)); - assert_ok!(Balances::hold(&TestId::Foo, &1337, 60)); - assert_eq!(>::balance(&1337), 40); - assert_eq!(Balances::total_balance_on_hold(&1337), 60); - assert_noop!( - Balances::decrease_balance(&1337, 40, Exact, Expendable, Polite), - TokenError::FundsUnavailable - ); - assert_eq!(Balances::decrease_balance(&1337, 39, Exact, Expendable, Polite), Ok(39)); - assert_eq!(>::balance(&1337), 1); - assert_eq!(Balances::total_balance_on_hold(&1337), 60); - }); + ExtBuilder::default().build_and_execute_with(|| { + // free: 40, reserved: 60 + assert_ok!(Balances::write_balance(&1337, 100)); + assert_ok!(Balances::hold(&TestId::Foo, &1337, 60)); + assert_eq!(>::balance(&1337), 40); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + assert_noop!( + Balances::decrease_balance(&1337, 40, Exact, Expendable, Polite), + TokenError::FundsUnavailable + ); + assert_eq!( + Balances::decrease_balance(&1337, 39, Exact, Expendable, Polite), + Ok(39) + ); + assert_eq!(>::balance(&1337), 1); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + }); } #[test] fn unbalanced_trait_decrease_balance_at_most_works_1() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::write_balance(&1337, 100)); - assert_eq!(>::balance(&1337), 100); - - assert_eq!(Balances::decrease_balance(&1337, 101, BestEffort, Expendable, Polite), Ok(100)); - assert_eq!(>::balance(&1337), 0); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_ok!(Balances::write_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + + assert_eq!( + Balances::decrease_balance(&1337, 101, BestEffort, Expendable, Polite), + Ok(100) + ); + assert_eq!(>::balance(&1337), 0); + }); } #[test] fn unbalanced_trait_decrease_balance_at_most_works_2() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::write_balance(&1337, 99)); - assert_eq!(Balances::decrease_balance(&1337, 99, BestEffort, Expendable, Polite), Ok(99)); - assert_eq!(>::balance(&1337), 0); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_ok!(Balances::write_balance(&1337, 99)); + assert_eq!( + Balances::decrease_balance(&1337, 99, BestEffort, Expendable, Polite), + Ok(99) + ); + assert_eq!(>::balance(&1337), 0); + }); } #[test] fn unbalanced_trait_decrease_balance_at_most_works_3() { - ExtBuilder::default().build_and_execute_with(|| { - // free: 40, reserved: 60 - assert_ok!(Balances::write_balance(&1337, 100)); - assert_ok!(Balances::hold(&TestId::Foo, &1337, 60)); - assert_eq!(Balances::free_balance(1337), 40); - assert_eq!(Balances::total_balance_on_hold(&1337), 60); - assert_eq!(Balances::decrease_balance(&1337, 0, BestEffort, Expendable, Polite), Ok(0)); - assert_eq!(Balances::free_balance(1337), 40); - assert_eq!(Balances::total_balance_on_hold(&1337), 60); - assert_eq!(Balances::decrease_balance(&1337, 10, BestEffort, Expendable, Polite), Ok(10)); - assert_eq!(Balances::free_balance(1337), 30); - assert_eq!(Balances::decrease_balance(&1337, 200, BestEffort, Expendable, Polite), Ok(29)); - assert_eq!(>::balance(&1337), 1); - assert_eq!(Balances::free_balance(1337), 1); - assert_eq!(Balances::total_balance_on_hold(&1337), 60); - }); + ExtBuilder::default().build_and_execute_with(|| { + // free: 40, reserved: 60 + assert_ok!(Balances::write_balance(&1337, 100)); + assert_ok!(Balances::hold(&TestId::Foo, &1337, 60)); + assert_eq!(Balances::free_balance(1337), 40); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + assert_eq!( + Balances::decrease_balance(&1337, 0, BestEffort, Expendable, Polite), + Ok(0) + ); + assert_eq!(Balances::free_balance(1337), 40); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + assert_eq!( + Balances::decrease_balance(&1337, 10, BestEffort, Expendable, Polite), + Ok(10) + ); + assert_eq!(Balances::free_balance(1337), 30); + assert_eq!( + Balances::decrease_balance(&1337, 200, BestEffort, Expendable, Polite), + Ok(29) + ); + assert_eq!(>::balance(&1337), 1); + assert_eq!(Balances::free_balance(1337), 1); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + }); } #[test] fn unbalanced_trait_increase_balance_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_noop!(Balances::increase_balance(&1337, 0, Exact), TokenError::BelowMinimum); - assert_eq!(Balances::increase_balance(&1337, 1, Exact), Ok(1)); - assert_noop!(Balances::increase_balance(&1337, Balance::MAX, Exact), ArithmeticError::Overflow); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_noop!( + Balances::increase_balance(&1337, 0, Exact), + TokenError::BelowMinimum + ); + assert_eq!(Balances::increase_balance(&1337, 1, Exact), Ok(1)); + assert_noop!( + Balances::increase_balance(&1337, Balance::MAX, Exact), + ArithmeticError::Overflow + ); + }); } #[test] fn unbalanced_trait_increase_balance_at_most_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_eq!(Balances::increase_balance(&1337, 0, BestEffort), Ok(0)); - assert_eq!(Balances::increase_balance(&1337, 1, BestEffort), Ok(1)); - assert_eq!(Balances::increase_balance(&1337, Balance::MAX, BestEffort), Ok(Balance::MAX - 1)); - }); + ExtBuilder::default().build_and_execute_with(|| { + assert_eq!(Balances::increase_balance(&1337, 0, BestEffort), Ok(0)); + assert_eq!(Balances::increase_balance(&1337, 1, BestEffort), Ok(1)); + assert_eq!( + Balances::increase_balance(&1337, Balance::MAX, BestEffort), + Ok(Balance::MAX - 1) + ); + }); } #[test] fn freezing_and_holds_should_overlap() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); - assert_ok!(Balances::hold(&TestId::Foo, &1, 9)); - assert_eq!(Balances::account(&1).free, 1); - assert_eq!(System::consumers(&1), 1); - assert_eq!(Balances::account(&1).free, 1); - assert_eq!(Balances::account(&1).frozen, 10); - assert_eq!(Balances::account(&1).reserved, 9); - assert_eq!(Balances::total_balance_on_hold(&1), 9); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 9)); + assert_eq!(Balances::account(&1).free, 1); + assert_eq!(System::consumers(&1), 1); + assert_eq!(Balances::account(&1).free, 1); + assert_eq!(Balances::account(&1).frozen, 10); + assert_eq!(Balances::account(&1).reserved, 9); + assert_eq!(Balances::total_balance_on_hold(&1), 9); + }); } #[test] fn frozen_hold_balance_cannot_be_moved_without_force() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); - assert_ok!(Balances::hold(&TestId::Foo, &1, 9)); - assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9); - assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 0); - let e = TokenError::Frozen; - assert_noop!( - Balances::transfer_on_hold(&TestId::Foo, &1, &2, 1, Exact, Free, Polite), - e - ); - assert_ok!(Balances::transfer_on_hold(&TestId::Foo, &1, &2, 1, Exact, Free, Force)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 9)); + assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9); + assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 0); + let e = TokenError::Frozen; + assert_noop!( + Balances::transfer_on_hold(&TestId::Foo, &1, &2, 1, Exact, Free, Polite), + e + ); + assert_ok!(Balances::transfer_on_hold( + &TestId::Foo, + &1, + &2, + 1, + Exact, + Free, + Force + )); + }); } #[test] fn frozen_hold_balance_best_effort_transfer_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); - assert_ok!(Balances::hold(&TestId::Foo, &1, 9)); - assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9); - assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 5); - assert_ok!(Balances::transfer_on_hold( - &TestId::Foo, - &1, - &2, - 10, - BestEffort, - Free, - Polite - )); - assert_eq!(Balances::total_balance(&1), 5); - assert_eq!(Balances::total_balance(&2), 25); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 9)); + assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9); + assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 5); + assert_ok!(Balances::transfer_on_hold( + &TestId::Foo, + &1, + &2, + 10, + BestEffort, + Free, + Polite + )); + assert_eq!(Balances::total_balance(&1), 5); + assert_eq!(Balances::total_balance(&2), 25); + }); } #[test] fn partial_freezing_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); - assert_eq!(System::consumers(&1), 1); - assert_ok!(>::transfer(&1, &2, 5, Expendable)); - assert_noop!( - >::transfer(&1, &2, 1, Expendable), - TokenError::Frozen - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_eq!(System::consumers(&1), 1); + assert_ok!(>::transfer( + &1, &2, 5, Expendable + )); + assert_noop!( + >::transfer(&1, &2, 1, Expendable), + TokenError::Frozen + ); + }); } #[test] fn thaw_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, Balance::MAX)); - assert_ok!(Balances::thaw(&TestId::Foo, &1)); - assert_eq!(System::consumers(&1), 0); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0); - assert_eq!(Balances::account(&1).frozen, 0); - assert_ok!(>::transfer(&1, &2, 10, Expendable)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, Balance::MAX)); + assert_ok!(Balances::thaw(&TestId::Foo, &1)); + assert_eq!(System::consumers(&1), 0); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0); + assert_eq!(Balances::account(&1).frozen, 0); + assert_ok!(>::transfer( + &1, &2, 10, Expendable + )); + }); } #[test] fn set_freeze_zero_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, Balance::MAX)); - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 0)); - assert_eq!(System::consumers(&1), 0); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0); - assert_eq!(Balances::account(&1).frozen, 0); - assert_ok!(>::transfer(&1, &2, 10, Expendable)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, Balance::MAX)); + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 0)); + assert_eq!(System::consumers(&1), 0); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0); + assert_eq!(Balances::account(&1).frozen, 0); + assert_ok!(>::transfer( + &1, &2, 10, Expendable + )); + }); } #[test] fn set_freeze_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, Balance::MAX)); - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); - assert_ok!(>::transfer(&1, &2, 5, Expendable)); - assert_noop!( - >::transfer(&1, &2, 1, Expendable), - TokenError::Frozen - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, Balance::MAX)); + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_ok!(>::transfer( + &1, &2, 5, Expendable + )); + assert_noop!( + >::transfer(&1, &2, 1, Expendable), + TokenError::Frozen + ); + }); } #[test] fn extend_freeze_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); - assert_ok!(Balances::extend_freeze(&TestId::Foo, &1, 10)); - assert_eq!(Balances::account(&1).frozen, 10); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 10); - assert_noop!( - >::transfer(&1, &2, 1, Expendable), - TokenError::Frozen - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_ok!(Balances::extend_freeze(&TestId::Foo, &1, 10)); + assert_eq!(Balances::account(&1).frozen, 10); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 10); + assert_noop!( + >::transfer(&1, &2, 1, Expendable), + TokenError::Frozen + ); + }); } #[test] fn double_freezing_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); - assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 5)); - assert_eq!(System::consumers(&1), 1); - assert_ok!(>::transfer(&1, &2, 5, Expendable)); - assert_noop!( - >::transfer(&1, &2, 1, Expendable), - TokenError::Frozen - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 5)); + assert_eq!(System::consumers(&1), 1); + assert_ok!(>::transfer( + &1, &2, 5, Expendable + )); + assert_noop!( + >::transfer(&1, &2, 1, Expendable), + TokenError::Frozen + ); + }); } #[test] fn can_hold_entire_balance_when_second_provider() { - ExtBuilder::default() - .existential_deposit(1) - .monied(false) - .build_and_execute_with(|| { - >::set_balance(&1, 100); - assert_noop!(Balances::hold(&TestId::Foo, &1, 100), TokenError::FundsUnavailable); - System::inc_providers(&1); - assert_eq!(System::providers(&1), 2); - assert_ok!(Balances::hold(&TestId::Foo, &1, 100)); - assert_eq!(System::providers(&1), 1); - assert_noop!(System::dec_providers(&1), DispatchError::ConsumerRemaining); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(false) + .build_and_execute_with(|| { + >::set_balance(&1, 100); + assert_noop!( + Balances::hold(&TestId::Foo, &1, 100), + TokenError::FundsUnavailable + ); + System::inc_providers(&1); + assert_eq!(System::providers(&1), 2); + assert_ok!(Balances::hold(&TestId::Foo, &1, 100)); + assert_eq!(System::providers(&1), 1); + assert_noop!(System::dec_providers(&1), DispatchError::ConsumerRemaining); + }); } #[test] fn unholding_frees_hold_slot() { - ExtBuilder::default() - .existential_deposit(1) - .monied(false) - .build_and_execute_with(|| { - >::set_balance(&1, 100); - assert_ok!(Balances::hold(&TestId::Foo, &1, 10)); - assert_ok!(Balances::hold(&TestId::Bar, &1, 10)); - assert_ok!(Balances::release(&TestId::Foo, &1, 10, Exact)); - assert_ok!(Balances::hold(&TestId::Baz, &1, 10)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(false) + .build_and_execute_with(|| { + >::set_balance(&1, 100); + assert_ok!(Balances::hold(&TestId::Foo, &1, 10)); + assert_ok!(Balances::hold(&TestId::Bar, &1, 10)); + assert_ok!(Balances::release(&TestId::Foo, &1, 10, Exact)); + assert_ok!(Balances::hold(&TestId::Baz, &1, 10)); + }); } #[test] fn sufficients_work_properly_with_reference_counting() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Only run PoC when the system pallet is enabled, since the underlying bug is in the - // system pallet it won't work with BalancesAccountStore - if UseSystem::get() { - // Start with a balance of 100 - >::set_balance(&1, 100); - // Emulate a sufficient, in reality this could be reached by transferring a - // sufficient asset to the account - System::inc_sufficients(&1); - // Spend the same balance multiple times - assert_ok!(>::transfer(&1, &1337, 100, Expendable)); - assert_eq!(Balances::free_balance(1), 0); - assert_noop!( - >::transfer(&1, &1337, 100, Expendable), - TokenError::FundsUnavailable - ); - } - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + // Only run PoC when the system pallet is enabled, since the underlying bug is in the + // system pallet it won't work with BalancesAccountStore + if UseSystem::get() { + // Start with a balance of 100 + >::set_balance(&1, 100); + // Emulate a sufficient, in reality this could be reached by transferring a + // sufficient asset to the account + System::inc_sufficients(&1); + // Spend the same balance multiple times + assert_ok!(>::transfer( + &1, &1337, 100, Expendable + )); + assert_eq!(Balances::free_balance(1), 0); + assert_noop!( + >::transfer(&1, &1337, 100, Expendable), + TokenError::FundsUnavailable + ); + } + }); } #[test] fn emit_events_with_changing_freezes() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::set_balance(&1, 100); - System::reset_events(); - - // Freeze = [] --> [10] - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); - assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 10 })]); - - // Freeze = [10] --> [15] - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 15)); - assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 5 })]); - - // Freeze = [15] --> [15, 20] - assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 20)); - assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 5 })]); - - // Freeze = [15, 20] --> [17, 20] - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 17)); - for event in events() { - match event { - RuntimeEvent::Balances(crate::Event::Frozen { .. }) => { - assert!(false, "unexpected freeze event") - }, - RuntimeEvent::Balances(crate::Event::Thawed { .. }) => { - assert!(false, "unexpected thaw event") - }, - _ => continue, - } - } - - // Freeze = [17, 20] --> [17, 15] - assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 15)); - assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 3 })]); - - // Freeze = [17, 15] --> [15] - assert_ok!(Balances::thaw(&TestId::Foo, &1)); - assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 2 })]); - - // Freeze = [15] --> [] - assert_ok!(Balances::thaw(&TestId::Bar, &1)); - assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 15 })]); - }); + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::set_balance(&1, 100); + System::reset_events(); + + // Freeze = [] --> [10] + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Frozen { + who: 1, + amount: 10 + })] + ); + + // Freeze = [10] --> [15] + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 15)); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Frozen { + who: 1, + amount: 5 + })] + ); + + // Freeze = [15] --> [15, 20] + assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 20)); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Frozen { + who: 1, + amount: 5 + })] + ); + + // Freeze = [15, 20] --> [17, 20] + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 17)); + for event in events() { + match event { + RuntimeEvent::Balances(crate::Event::Frozen { .. }) => { + assert!(false, "unexpected freeze event") + } + RuntimeEvent::Balances(crate::Event::Thawed { .. }) => { + assert!(false, "unexpected thaw event") + } + _ => continue, + } + } + + // Freeze = [17, 20] --> [17, 15] + assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 15)); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Thawed { + who: 1, + amount: 3 + })] + ); + + // Freeze = [17, 15] --> [15] + assert_ok!(Balances::thaw(&TestId::Foo, &1)); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Thawed { + who: 1, + amount: 2 + })] + ); + + // Freeze = [15] --> [] + assert_ok!(Balances::thaw(&TestId::Bar, &1)); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Thawed { + who: 1, + amount: 15 + })] + ); + }); } #[test] fn withdraw_precision_exact_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); - assert_eq!(Balances::account(&1).free, 10); - assert_eq!(Balances::account(&1).frozen, 10); - - // `BestEffort` will not reduce anything - assert_ok!(>::withdraw( - &1, 5, BestEffort, Preserve, Polite - )); - - assert_eq!(Balances::account(&1).free, 10); - assert_eq!(Balances::account(&1).frozen, 10); - - assert_noop!( - >::withdraw(&1, 5, Exact, Preserve, Polite), - TokenError::FundsUnavailable - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); + assert_eq!(Balances::account(&1).free, 10); + assert_eq!(Balances::account(&1).frozen, 10); + + // `BestEffort` will not reduce anything + assert_ok!(>::withdraw( + &1, 5, BestEffort, Preserve, Polite + )); + + assert_eq!(Balances::account(&1).free, 10); + assert_eq!(Balances::account(&1).frozen, 10); + + assert_noop!( + >::withdraw(&1, 5, Exact, Preserve, Polite), + TokenError::FundsUnavailable + ); + }); } #[test] fn freeze_consideration_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - type Consideration = FreezeConsideration< - u64, - Balances, - FooReason, - LinearStoragePrice, ConstU128<1>, Balance>, - Footprint, - >; - - let who = 4; - // freeze amount taken somewhere outside of our (Consideration) scope. - let extend_freeze = 15; - - let ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); - assert!(ticket.is_none()); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); - - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4); - - assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, extend_freeze)); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4 + extend_freeze); - - let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 8 + extend_freeze); - - let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); - assert!(ticket.is_none()); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), extend_freeze); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10 + extend_freeze); - - ticket.drop(&who).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), extend_freeze); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + type Consideration = FreezeConsideration< + u64, + Balances, + FooReason, + LinearStoragePrice, ConstU128<1>, Balance>, + Footprint, + >; + + let who = 4; + // freeze amount taken somewhere outside of our (Consideration) scope. + let extend_freeze = 15; + + let ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(ticket.is_none()); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); + + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); + + let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4); + + assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, extend_freeze)); + assert_eq!( + Balances::balance_frozen(&TestId::Foo, &who), + 4 + extend_freeze + ); + + let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); + assert_eq!( + Balances::balance_frozen(&TestId::Foo, &who), + 8 + extend_freeze + ); + + let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(ticket.is_none()); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), extend_freeze); + + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); + assert_eq!( + Balances::balance_frozen(&TestId::Foo, &who), + 10 + extend_freeze + ); + + ticket.drop(&who).unwrap(); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), extend_freeze); + }); } #[test] fn hold_consideration_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - type Consideration = HoldConsideration< - u64, - Balances, - FooReason, - LinearStoragePrice, ConstU128<1>, Balance>, - Footprint, - >; - - let who = 4; - // hold amount taken somewhere outside of our (Consideration) scope. - let extend_hold = 15; - - let ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); - assert!(ticket.is_none()); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - - let ticket = ticket.update(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); - - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4); - - assert_ok!(Balances::hold(&TestId::Foo, &who, extend_hold)); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4 + extend_hold); - - let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 8 + extend_hold); - - let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); - assert!(ticket.is_none()); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), extend_hold); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10 + extend_hold); - - ticket.drop(&who).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), extend_hold); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + type Consideration = HoldConsideration< + u64, + Balances, + FooReason, + LinearStoragePrice, ConstU128<1>, Balance>, + Footprint, + >; + + let who = 4; + // hold amount taken somewhere outside of our (Consideration) scope. + let extend_hold = 15; + + let ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(ticket.is_none()); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); + + let ticket = ticket.update(&who, Footprint::from_parts(10, 1)).unwrap(); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); + + let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4); + + assert_ok!(Balances::hold(&TestId::Foo, &who, extend_hold)); + assert_eq!( + Balances::balance_on_hold(&TestId::Foo, &who), + 4 + extend_hold + ); + + let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); + assert_eq!( + Balances::balance_on_hold(&TestId::Foo, &who), + 8 + extend_hold + ); + + let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); + assert!(ticket.is_none()); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), extend_hold); + + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); + assert_eq!( + Balances::balance_on_hold(&TestId::Foo, &who), + 10 + extend_hold + ); + + ticket.drop(&who).unwrap(); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), extend_hold); + }); } #[test] fn lone_freeze_consideration_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - type Consideration = LoneFreezeConsideration< - u64, - Balances, - FooReason, - LinearStoragePrice, ConstU128<1>, Balance>, - Footprint, - >; - - let who = 4; - let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); - - assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, 5)); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 15); - - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4); - - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); - - ticket.drop(&who).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + type Consideration = LoneFreezeConsideration< + u64, + Balances, + FooReason, + LinearStoragePrice, ConstU128<1>, Balance>, + Footprint, + >; + + let who = 4; + let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); + + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); + + assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, 5)); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 15); + + let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4); + + assert_eq!( + ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), + zero_ticket + ); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); + + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); + + ticket.drop(&who).unwrap(); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); + }); } #[test] fn lone_hold_consideration_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - type Consideration = LoneHoldConsideration< - u64, - Balances, - FooReason, - LinearStoragePrice, ConstU128<1>, Balance>, - Footprint, - >; - - let who = 4; - let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); - - assert_ok!(Balances::hold(&TestId::Foo, &who, 5)); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 15); - - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4); - - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); - - ticket.drop(&who).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + type Consideration = LoneHoldConsideration< + u64, + Balances, + FooReason, + LinearStoragePrice, ConstU128<1>, Balance>, + Footprint, + >; + + let who = 4; + let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); + + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); + + assert_ok!(Balances::hold(&TestId::Foo, &who, 5)); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 15); + + let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4); + + assert_eq!( + ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), + zero_ticket + ); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); + + let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); + + ticket.drop(&who).unwrap(); + assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); + }); } diff --git a/pallets/balances/src/tests/general_tests.rs b/pallets/balances/src/tests/general_tests.rs index d5a6d8d4..31a1ed6d 100644 --- a/pallets/balances/src/tests/general_tests.rs +++ b/pallets/balances/src/tests/general_tests.rs @@ -18,16 +18,16 @@ #![cfg(test)] use crate::{ - system::AccountInfo, - tests::{ensure_ti_valid, Balances, ExtBuilder, System, Test, TestId, UseSystem}, - AccountData, ExtraFlags, TotalIssuance, + system::AccountInfo, + tests::{ensure_ti_valid, Balances, ExtBuilder, System, Test, TestId, UseSystem}, + AccountData, ExtraFlags, TotalIssuance, }; use frame_support::{ - assert_noop, assert_ok, hypothetically, - traits::{ - fungible::{Mutate, MutateHold}, - tokens::Precision, - }, + assert_noop, assert_ok, hypothetically, + traits::{ + fungible::{Mutate, MutateHold}, + tokens::Precision, + }, }; use sp_runtime::DispatchError; @@ -37,107 +37,112 @@ use sp_runtime::DispatchError; /// one consumer ref for its reserved balance. #[test] fn regression_historic_acc_does_not_evaporate_reserve() { - ExtBuilder::default().build_and_execute_with(|| { - UseSystem::set(true); - let (alice, bob) = (0, 1); - // Alice is in a bad state with consumer == 0 && reserved > 0: - Balances::set_balance(&alice, 100); - TotalIssuance::::put(100); - ensure_ti_valid(); - - assert_ok!(Balances::hold(&TestId::Foo, &alice, 10)); - // This is the issue of the account: - System::dec_consumers(&alice); - - assert_eq!( - System::account(alice), - AccountInfo { - data: AccountData { - free: 90, - reserved: 10, - frozen: 0, - flags: ExtraFlags(1u128 << 127), - }, - nonce: 0, - consumers: 0, // should be 1 on a good acc - providers: 1, - sufficients: 0, - } - ); - - ensure_ti_valid(); - - // Reaping the account is prevented by the new logic: - assert_noop!( - Balances::transfer_allow_death(Some(alice).into(), bob, 90), - DispatchError::ConsumerRemaining - ); - assert_noop!( - Balances::transfer_all(Some(alice).into(), bob, false), - DispatchError::ConsumerRemaining - ); - - // normal transfers still work: - hypothetically!({ - assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40)); - // Alice got back her consumer ref: - assert_eq!(System::consumers(&alice), 1); - ensure_ti_valid(); - }); - hypothetically!({ - assert_ok!(Balances::transfer_all(Some(alice).into(), bob, true)); - // Alice got back her consumer ref: - assert_eq!(System::consumers(&alice), 1); - ensure_ti_valid(); - }); - - // un-reserving all does not add a consumer ref: - hypothetically!({ - assert_ok!(Balances::release(&TestId::Foo, &alice, 10, Precision::Exact)); - assert_eq!(System::consumers(&alice), 0); - assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40)); - assert_eq!(System::consumers(&alice), 0); - ensure_ti_valid(); - }); - // un-reserving some does add a consumer ref: - hypothetically!({ - assert_ok!(Balances::release(&TestId::Foo, &alice, 5, Precision::Exact)); - assert_eq!(System::consumers(&alice), 1); - assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40)); - assert_eq!(System::consumers(&alice), 1); - ensure_ti_valid(); - }); - }); + ExtBuilder::default().build_and_execute_with(|| { + UseSystem::set(true); + let (alice, bob) = (0, 1); + // Alice is in a bad state with consumer == 0 && reserved > 0: + Balances::set_balance(&alice, 100); + TotalIssuance::::put(100); + ensure_ti_valid(); + + assert_ok!(Balances::hold(&TestId::Foo, &alice, 10)); + // This is the issue of the account: + System::dec_consumers(&alice); + + assert_eq!( + System::account(alice), + AccountInfo { + data: AccountData { + free: 90, + reserved: 10, + frozen: 0, + flags: ExtraFlags(1u128 << 127), + }, + nonce: 0, + consumers: 0, // should be 1 on a good acc + providers: 1, + sufficients: 0, + } + ); + + ensure_ti_valid(); + + // Reaping the account is prevented by the new logic: + assert_noop!( + Balances::transfer_allow_death(Some(alice).into(), bob, 90), + DispatchError::ConsumerRemaining + ); + assert_noop!( + Balances::transfer_all(Some(alice).into(), bob, false), + DispatchError::ConsumerRemaining + ); + + // normal transfers still work: + hypothetically!({ + assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40)); + // Alice got back her consumer ref: + assert_eq!(System::consumers(&alice), 1); + ensure_ti_valid(); + }); + hypothetically!({ + assert_ok!(Balances::transfer_all(Some(alice).into(), bob, true)); + // Alice got back her consumer ref: + assert_eq!(System::consumers(&alice), 1); + ensure_ti_valid(); + }); + + // un-reserving all does not add a consumer ref: + hypothetically!({ + assert_ok!(Balances::release( + &TestId::Foo, + &alice, + 10, + Precision::Exact + )); + assert_eq!(System::consumers(&alice), 0); + assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40)); + assert_eq!(System::consumers(&alice), 0); + ensure_ti_valid(); + }); + // un-reserving some does add a consumer ref: + hypothetically!({ + assert_ok!(Balances::release(&TestId::Foo, &alice, 5, Precision::Exact)); + assert_eq!(System::consumers(&alice), 1); + assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40)); + assert_eq!(System::consumers(&alice), 1); + ensure_ti_valid(); + }); + }); } #[cfg(feature = "try-runtime")] #[test] fn try_state_works() { - use crate::{Config, Freezes, Holds}; - use frame_support::{ - storage, - traits::{Get, Hooks, VariantCount}, - }; - - ExtBuilder::default().build_and_execute_with(|| { - storage::unhashed::put( - &Holds::::hashed_key_for(1), - &vec![0u8; ::RuntimeHoldReason::VARIANT_COUNT as usize + 1], - ); - - assert!(format!("{:?}", Balances::try_state(0).unwrap_err()) - .contains("Found `Hold` with too many elements")); - }); - - ExtBuilder::default().build_and_execute_with(|| { - let max_freezes: u32 = ::MaxFreezes::get(); - - storage::unhashed::put( - &Freezes::::hashed_key_for(1), - &vec![0u8; max_freezes as usize + 1], - ); - - assert!(format!("{:?}", Balances::try_state(0).unwrap_err()) - .contains("Found `Freeze` with too many elements")); - }); + use crate::{Config, Freezes, Holds}; + use frame_support::{ + storage, + traits::{Get, Hooks, VariantCount}, + }; + + ExtBuilder::default().build_and_execute_with(|| { + storage::unhashed::put( + &Holds::::hashed_key_for(1), + &vec![0u8; ::RuntimeHoldReason::VARIANT_COUNT as usize + 1], + ); + + assert!(format!("{:?}", Balances::try_state(0).unwrap_err()) + .contains("Found `Hold` with too many elements")); + }); + + ExtBuilder::default().build_and_execute_with(|| { + let max_freezes: u32 = ::MaxFreezes::get(); + + storage::unhashed::put( + &Freezes::::hashed_key_for(1), + &vec![0u8; max_freezes as usize + 1], + ); + + assert!(format!("{:?}", Balances::try_state(0).unwrap_err()) + .contains("Found `Freeze` with too many elements")); + }); } diff --git a/pallets/balances/src/tests/mod.rs b/pallets/balances/src/tests/mod.rs index 22a917b7..9d0708a4 100644 --- a/pallets/balances/src/tests/mod.rs +++ b/pallets/balances/src/tests/mod.rs @@ -22,23 +22,23 @@ use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ - assert_err, assert_noop, assert_ok, assert_storage_noop, derive_impl, - dispatch::{DispatchInfo, GetDispatchInfo}, - parameter_types, - traits::{ - fungible, ConstU32, ConstU8, Imbalance as ImbalanceT, OnUnbalanced, StorageMapShim, - StoredMap, VariantCount, VariantCountOf, WhitelistedStorageKeys, - }, - weights::{IdentityFee, Weight}, + assert_err, assert_noop, assert_ok, assert_storage_noop, derive_impl, + dispatch::{DispatchInfo, GetDispatchInfo}, + parameter_types, + traits::{ + fungible, ConstU32, ConstU8, Imbalance as ImbalanceT, OnUnbalanced, StorageMapShim, + StoredMap, VariantCount, VariantCountOf, WhitelistedStorageKeys, + }, + weights::{IdentityFee, Weight}, }; use frame_system::{self as system, RawOrigin}; use pallet_transaction_payment::{ChargeTransactionPayment, FungibleAdapter, Multiplier}; use scale_info::TypeInfo; use sp_core::hexdisplay::HexDisplay; use sp_runtime::{ - traits::{BadOrigin, Zero}, - ArithmeticError, BuildStorage, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, - TokenError, + traits::{BadOrigin, Zero}, + ArithmeticError, BuildStorage, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, + TokenError, }; use std::collections::BTreeSet; @@ -52,164 +52,168 @@ mod reentrancy_tests; type Block = frame_system::mocking::MockBlock; #[derive( - Encode, - Decode, - Copy, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - MaxEncodedLen, - TypeInfo, - RuntimeDebug, + Encode, + Decode, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + MaxEncodedLen, + TypeInfo, + RuntimeDebug, )] pub enum TestId { - Foo, - Bar, - Baz, + Foo, + Bar, + Baz, } impl VariantCount for TestId { - const VARIANT_COUNT: u32 = 3; + const VARIANT_COUNT: u32 = 3; } frame_support::construct_runtime!( - pub enum Test { - System: frame_system, - Balances: pallet_balances, - TransactionPayment: pallet_transaction_payment, - } + pub enum Test { + System: frame_system, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, + } ); type Balance = u128; parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - frame_support::weights::Weight::from_parts(1024, u64::MAX), - ); - pub static ExistentialDeposit: Balance = 1; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max( + frame_support::weights::Weight::from_parts(1024, u64::MAX), + ); + pub static ExistentialDeposit: Balance = 1; } - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { - type Block = Block; - type AccountData = super::AccountData; + type Block = Block; + type AccountData = super::AccountData; } #[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] impl pallet_transaction_payment::Config for Test { - type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = FungibleAdapter, ()>; - type OperationalFeeMultiplier = ConstU8<5>; - type WeightToFee = IdentityFee; - type LengthToFee = IdentityFee; + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = FungibleAdapter, ()>; + type OperationalFeeMultiplier = ConstU8<5>; + type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; } parameter_types! { - pub FooReason: TestId = TestId::Foo; + pub FooReason: TestId = TestId::Foo; } - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl Config for Test { - type Balance = Balance; - type DustRemoval = DustTrap; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = TestAccountStore; - type MaxReserves = ConstU32<2>; - type ReserveIdentifier = TestId; - type RuntimeHoldReason = TestId; - type RuntimeFreezeReason = TestId; - type FreezeIdentifier = TestId; - type MaxFreezes = VariantCountOf; + type Balance = Balance; + type DustRemoval = DustTrap; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = TestAccountStore; + type MaxReserves = ConstU32<2>; + type ReserveIdentifier = TestId; + type RuntimeHoldReason = TestId; + type RuntimeFreezeReason = TestId; + type FreezeIdentifier = TestId; + type MaxFreezes = VariantCountOf; } #[derive(Clone)] pub struct ExtBuilder { - existential_deposit: Balance, - monied: bool, - dust_trap: Option, + existential_deposit: Balance, + monied: bool, + dust_trap: Option, } impl Default for ExtBuilder { - fn default() -> Self { - Self { existential_deposit: 1, monied: false, dust_trap: None } - } + fn default() -> Self { + Self { + existential_deposit: 1, + monied: false, + dust_trap: None, + } + } } impl ExtBuilder { - pub fn existential_deposit(mut self, existential_deposit: Balance) -> Self { - self.existential_deposit = existential_deposit; - self - } - pub fn monied(mut self, monied: bool) -> Self { - self.monied = monied; - if self.existential_deposit == 0 { - self.existential_deposit = 1; - } - self - } - pub fn dust_trap(mut self, account: u64) -> Self { - self.dust_trap = Some(account); - self - } - pub fn set_associated_consts(&self) { - DUST_TRAP_TARGET.with(|v| v.replace(self.dust_trap)); - EXISTENTIAL_DEPOSIT.with(|v| v.replace(self.existential_deposit)); - } - pub fn build(self) -> sp_io::TestExternalities { - self.set_associated_consts(); - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { - balances: if self.monied { - vec![ - (1, 10 * self.existential_deposit), - (2, 20 * self.existential_deposit), - (3, 30 * self.existential_deposit), - (4, 40 * self.existential_deposit), - (12, 10 * self.existential_deposit), - ] - } else { - vec![] - }, - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } - pub fn build_and_execute_with(self, f: impl Fn()) { - let other = self.clone(); - UseSystem::set(false); - other.build().execute_with(&f); - UseSystem::set(true); - self.build().execute_with(f); - } + pub fn existential_deposit(mut self, existential_deposit: Balance) -> Self { + self.existential_deposit = existential_deposit; + self + } + pub fn monied(mut self, monied: bool) -> Self { + self.monied = monied; + if self.existential_deposit == 0 { + self.existential_deposit = 1; + } + self + } + pub fn dust_trap(mut self, account: u64) -> Self { + self.dust_trap = Some(account); + self + } + pub fn set_associated_consts(&self) { + DUST_TRAP_TARGET.with(|v| v.replace(self.dust_trap)); + EXISTENTIAL_DEPOSIT.with(|v| v.replace(self.existential_deposit)); + } + pub fn build(self) -> sp_io::TestExternalities { + self.set_associated_consts(); + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + pallet_balances::GenesisConfig:: { + balances: if self.monied { + vec![ + (1, 10 * self.existential_deposit), + (2, 20 * self.existential_deposit), + (3, 30 * self.existential_deposit), + (4, 40 * self.existential_deposit), + (12, 10 * self.existential_deposit), + ] + } else { + vec![] + }, + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } + pub fn build_and_execute_with(self, f: impl Fn()) { + let other = self.clone(); + UseSystem::set(false); + other.build().execute_with(&f); + UseSystem::set(true); + self.build().execute_with(f); + } } parameter_types! { - static DustTrapTarget: Option = None; + static DustTrapTarget: Option = None; } pub struct DustTrap; impl OnUnbalanced> for DustTrap { - fn on_nonzero_unbalanced(amount: CreditOf) { - match DustTrapTarget::get() { - None => drop(amount), - Some(a) => { - let result = >::resolve(&a, amount); - debug_assert!(result.is_ok()); - }, - } - } + fn on_nonzero_unbalanced(amount: CreditOf) { + match DustTrapTarget::get() { + None => drop(amount), + Some(a) => { + let result = >::resolve(&a, amount); + debug_assert!(result.is_ok()); + } + } + } } parameter_types! { - pub static UseSystem: bool = false; + pub static UseSystem: bool = false; } type BalancesAccountStore = StorageMapShim, u64, super::AccountData>; @@ -217,104 +221,116 @@ type SystemAccountStore = frame_system::Pallet; pub struct TestAccountStore; impl StoredMap> for TestAccountStore { - fn get(k: &u64) -> super::AccountData { - if UseSystem::get() { - >::get(k) - } else { - >::get(k) - } - } - fn try_mutate_exists>( - k: &u64, - f: impl FnOnce(&mut Option>) -> Result, - ) -> Result { - if UseSystem::get() { - >::try_mutate_exists(k, f) - } else { - >::try_mutate_exists(k, f) - } - } - fn mutate( - k: &u64, - f: impl FnOnce(&mut super::AccountData) -> R, - ) -> Result { - if UseSystem::get() { - >::mutate(k, f) - } else { - >::mutate(k, f) - } - } - fn mutate_exists( - k: &u64, - f: impl FnOnce(&mut Option>) -> R, - ) -> Result { - if UseSystem::get() { - >::mutate_exists(k, f) - } else { - >::mutate_exists(k, f) - } - } - fn insert(k: &u64, t: super::AccountData) -> Result<(), DispatchError> { - if UseSystem::get() { - >::insert(k, t) - } else { - >::insert(k, t) - } - } - fn remove(k: &u64) -> Result<(), DispatchError> { - if UseSystem::get() { - >::remove(k) - } else { - >::remove(k) - } - } + fn get(k: &u64) -> super::AccountData { + if UseSystem::get() { + >::get(k) + } else { + >::get(k) + } + } + fn try_mutate_exists>( + k: &u64, + f: impl FnOnce(&mut Option>) -> Result, + ) -> Result { + if UseSystem::get() { + >::try_mutate_exists(k, f) + } else { + >::try_mutate_exists(k, f) + } + } + fn mutate( + k: &u64, + f: impl FnOnce(&mut super::AccountData) -> R, + ) -> Result { + if UseSystem::get() { + >::mutate(k, f) + } else { + >::mutate(k, f) + } + } + fn mutate_exists( + k: &u64, + f: impl FnOnce(&mut Option>) -> R, + ) -> Result { + if UseSystem::get() { + >::mutate_exists(k, f) + } else { + >::mutate_exists(k, f) + } + } + fn insert(k: &u64, t: super::AccountData) -> Result<(), DispatchError> { + if UseSystem::get() { + >::insert(k, t) + } else { + >::insert(k, t) + } + } + fn remove(k: &u64) -> Result<(), DispatchError> { + if UseSystem::get() { + >::remove(k) + } else { + >::remove(k) + } + } } pub fn events() -> Vec { - let evt = System::events().into_iter().map(|evt| evt.event).collect::>(); - System::reset_events(); - evt + let evt = System::events() + .into_iter() + .map(|evt| evt.event) + .collect::>(); + System::reset_events(); + evt } /// create a transaction info struct from weight. Handy to avoid building the whole struct. pub fn info_from_weight(w: Weight) -> DispatchInfo { - DispatchInfo { call_weight: w, ..Default::default() } + DispatchInfo { + call_weight: w, + ..Default::default() + } } /// Check that the total-issuance matches the sum of all accounts' total balances. pub fn ensure_ti_valid() { - let mut sum = 0; - - for acc in frame_system::Account::::iter_keys() { - if UseSystem::get() { - let data = frame_system::Pallet::::account(acc); - sum += data.data.total(); - } else { - let data = crate::Account::::get(acc); - sum += data.total(); - } - } - - assert_eq!(TotalIssuance::::get(), sum, "Total Issuance wrong"); + let mut sum = 0; + + for acc in frame_system::Account::::iter_keys() { + if UseSystem::get() { + let data = frame_system::Pallet::::account(acc); + sum += data.data.total(); + } else { + let data = crate::Account::::get(acc); + sum += data.total(); + } + } + + assert_eq!(TotalIssuance::::get(), sum, "Total Issuance wrong"); } #[test] fn weights_sane() { - let info = crate::Call::::transfer_allow_death { dest: 10, value: 4 }.get_dispatch_info(); - assert_eq!(<() as crate::WeightInfo>::transfer_allow_death(), info.call_weight); - - let info = crate::Call::::force_unreserve { who: 10, amount: 4 }.get_dispatch_info(); - assert_eq!(<() as crate::WeightInfo>::force_unreserve(), info.call_weight); + let info = crate::Call::::transfer_allow_death { dest: 10, value: 4 }.get_dispatch_info(); + assert_eq!( + <() as crate::WeightInfo>::transfer_allow_death(), + info.call_weight + ); + + let info = crate::Call::::force_unreserve { who: 10, amount: 4 }.get_dispatch_info(); + assert_eq!( + <() as crate::WeightInfo>::force_unreserve(), + info.call_weight + ); } #[test] fn check_whitelist() { - let whitelist: BTreeSet = AllPalletsWithSystem::whitelisted_storage_keys() - .iter() - .map(|s| HexDisplay::from(&s.key).to_string()) - .collect(); - // Inactive Issuance - assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f1ccde6872881f893a21de93dfe970cd5")); - // Total Issuance - assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80")); + let whitelist: BTreeSet = AllPalletsWithSystem::whitelisted_storage_keys() + .iter() + .map(|s| HexDisplay::from(&s.key).to_string()) + .collect(); + // Inactive Issuance + assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f1ccde6872881f893a21de93dfe970cd5")); + // Total Issuance + assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80")); } diff --git a/pallets/balances/src/tests/reentrancy_tests.rs b/pallets/balances/src/tests/reentrancy_tests.rs index ba98d4ef..cd4e4305 100644 --- a/pallets/balances/src/tests/reentrancy_tests.rs +++ b/pallets/balances/src/tests/reentrancy_tests.rs @@ -19,178 +19,201 @@ use super::*; use frame_support::traits::tokens::{ - Fortitude::Force, - Precision::BestEffort, - Preservation::{Expendable, Protect}, + Fortitude::Force, + Precision::BestEffort, + Preservation::{Expendable, Protect}, }; use fungible::Balanced; #[test] fn transfer_dust_removal_tst1_should_work() { - ExtBuilder::default() - .existential_deposit(100) - .dust_trap(1) - .build_and_execute_with(|| { - // Verification of reentrancy in dust removal - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000)); - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500)); - - // In this transaction, account 2 free balance - // drops below existential balance - // and dust balance is removed from account 2 - assert_ok!(Balances::transfer_allow_death(RawOrigin::Signed(2).into(), 3, 450)); - - // As expected dust balance is removed. - assert_eq!(Balances::free_balance(2), 0); - - // As expected beneficiary account 3 - // received the transferred fund. - assert_eq!(Balances::free_balance(3), 450); - - // Dust balance is deposited to account 1 - // during the process of dust removal. - assert_eq!(Balances::free_balance(1), 1050); - - // Verify the events - assert_eq!(System::events().len(), 14); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { - from: 2, - to: 3, - amount: 450, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { - account: 2, - amount: 50, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: 1, - amount: 50, - })); - }); + ExtBuilder::default() + .existential_deposit(100) + .dust_trap(1) + .build_and_execute_with(|| { + // Verification of reentrancy in dust removal + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000)); + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500)); + + // In this transaction, account 2 free balance + // drops below existential balance + // and dust balance is removed from account 2 + assert_ok!(Balances::transfer_allow_death( + RawOrigin::Signed(2).into(), + 3, + 450 + )); + + // As expected dust balance is removed. + assert_eq!(Balances::free_balance(2), 0); + + // As expected beneficiary account 3 + // received the transferred fund. + assert_eq!(Balances::free_balance(3), 450); + + // Dust balance is deposited to account 1 + // during the process of dust removal. + assert_eq!(Balances::free_balance(1), 1050); + + // Verify the events + assert_eq!(System::events().len(), 14); + + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { + from: 2, + to: 3, + amount: 450, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { + account: 2, + amount: 50, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 50, + })); + }); } #[test] fn transfer_dust_removal_tst2_should_work() { - ExtBuilder::default() - .existential_deposit(100) - .dust_trap(1) - .build_and_execute_with(|| { - // Verification of reentrancy in dust removal - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000)); - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500)); - - // In this transaction, account 2 free balance - // drops below existential balance - // and dust balance is removed from account 2 - assert_ok!(Balances::transfer_allow_death(RawOrigin::Signed(2).into(), 1, 450)); - - // As expected dust balance is removed. - assert_eq!(Balances::free_balance(2), 0); - - // Dust balance is deposited to account 1 - // during the process of dust removal. - assert_eq!(Balances::free_balance(1), 1500); - - // Verify the events - assert_eq!(System::events().len(), 12); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { - from: 2, - to: 1, - amount: 450, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { - account: 2, - amount: 50, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: 1, - amount: 50, - })); - }); + ExtBuilder::default() + .existential_deposit(100) + .dust_trap(1) + .build_and_execute_with(|| { + // Verification of reentrancy in dust removal + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000)); + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500)); + + // In this transaction, account 2 free balance + // drops below existential balance + // and dust balance is removed from account 2 + assert_ok!(Balances::transfer_allow_death( + RawOrigin::Signed(2).into(), + 1, + 450 + )); + + // As expected dust balance is removed. + assert_eq!(Balances::free_balance(2), 0); + + // Dust balance is deposited to account 1 + // during the process of dust removal. + assert_eq!(Balances::free_balance(1), 1500); + + // Verify the events + assert_eq!(System::events().len(), 12); + + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { + from: 2, + to: 1, + amount: 450, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { + account: 2, + amount: 50, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 50, + })); + }); } #[test] fn repatriating_reserved_balance_dust_removal_should_work() { - ExtBuilder::default() - .existential_deposit(100) - .dust_trap(1) - .build_and_execute_with(|| { - // Verification of reentrancy in dust removal - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000)); - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500)); - - // Reserve a value on account 2, - // Such that free balance is lower than - // Existential deposit. - assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), 1, 450)); - - // Since free balance of account 2 is lower than - // existential deposit, dust amount is - // removed from the account 2 - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::free_balance(2), 0); - - // account 1 is credited with reserved amount - // together with dust balance during dust - // removal. - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::free_balance(1), 1500); - - // Verify the events - assert_eq!(System::events().len(), 12); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { - from: 2, - to: 1, - amount: 450, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { - account: 2, - amount: 50, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: 1, - amount: 50, - })); - }); + ExtBuilder::default() + .existential_deposit(100) + .dust_trap(1) + .build_and_execute_with(|| { + // Verification of reentrancy in dust removal + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000)); + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500)); + + // Reserve a value on account 2, + // Such that free balance is lower than + // Existential deposit. + assert_ok!(Balances::transfer_allow_death( + RuntimeOrigin::signed(2), + 1, + 450 + )); + + // Since free balance of account 2 is lower than + // existential deposit, dust amount is + // removed from the account 2 + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::free_balance(2), 0); + + // account 1 is credited with reserved amount + // together with dust balance during dust + // removal. + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::free_balance(1), 1500); + + // Verify the events + assert_eq!(System::events().len(), 12); + + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { + from: 2, + to: 1, + amount: 450, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { + account: 2, + amount: 50, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 50, + })); + }); } #[test] fn emit_events_with_no_existential_deposit_suicide_with_dust() { - ExtBuilder::default().existential_deposit(2).build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 100)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::NewAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), - RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), - RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), - ] - ); - - let res = Balances::withdraw(&1, 98, BestEffort, Protect, Force); - assert_eq!(res.unwrap().peek(), 98); - - // no events - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 98 })] - ); - - let res = Balances::withdraw(&1, 1, BestEffort, Expendable, Force); - assert_eq!(res.unwrap().peek(), 1); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 1 }), - RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 1 }) - ] - ); - }); + ExtBuilder::default() + .existential_deposit(2) + .build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 100)); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::NewAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Endowed { + account: 1, + free_balance: 100 + }), + RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), + RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), + ] + ); + + let res = Balances::withdraw(&1, 98, BestEffort, Protect, Force); + assert_eq!(res.unwrap().peek(), 98); + + // no events + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Withdraw { + who: 1, + amount: 98 + })] + ); + + let res = Balances::withdraw(&1, 1, BestEffort, Expendable, Force); + assert_eq!(res.unwrap().peek(), 1); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::DustLost { + account: 1, + amount: 1 + }), + RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 1 }) + ] + ); + }); } diff --git a/pallets/balances/src/types.rs b/pallets/balances/src/types.rs index f066c1ea..e080c2d2 100644 --- a/pallets/balances/src/types.rs +++ b/pallets/balances/src/types.rs @@ -27,76 +27,76 @@ use sp_runtime::{RuntimeDebug, Saturating}; /// Simplified reasons for withdrawing balance. #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub enum Reasons { - /// Paying system transaction fees. - Fee = 0, - /// Any reason other than paying system transaction fees. - Misc = 1, - /// Any reason at all. - All = 2, + /// Paying system transaction fees. + Fee = 0, + /// Any reason other than paying system transaction fees. + Misc = 1, + /// Any reason at all. + All = 2, } impl From for Reasons { - fn from(r: WithdrawReasons) -> Reasons { - if r == WithdrawReasons::TRANSACTION_PAYMENT { - Reasons::Fee - } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) { - Reasons::All - } else { - Reasons::Misc - } - } + fn from(r: WithdrawReasons) -> Reasons { + if r == WithdrawReasons::TRANSACTION_PAYMENT { + Reasons::Fee + } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) { + Reasons::All + } else { + Reasons::Misc + } + } } impl BitOr for Reasons { - type Output = Reasons; - fn bitor(self, other: Reasons) -> Reasons { - if self == other { - return self - } - Reasons::All - } + type Output = Reasons; + fn bitor(self, other: Reasons) -> Reasons { + if self == other { + return self; + } + Reasons::All + } } /// A single lock on a balance. There can be many of these on an account and they "overlap", so the /// same balance is frozen by multiple locks. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct BalanceLock { - /// An identifier for this lock. Only one lock may be in existence for each identifier. - pub id: LockIdentifier, - /// The amount which the free balance may not drop below when this lock is in effect. - pub amount: Balance, - /// If true, then the lock remains in effect even for payment of transaction fees. - pub reasons: Reasons, + /// An identifier for this lock. Only one lock may be in existence for each identifier. + pub id: LockIdentifier, + /// The amount which the free balance may not drop below when this lock is in effect. + pub amount: Balance, + /// If true, then the lock remains in effect even for payment of transaction fees. + pub reasons: Reasons, } /// Store named reserved balance. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct ReserveData { - /// The identifier for the named reserve. - pub id: ReserveIdentifier, - /// The amount of the named reserve. - pub amount: Balance, + /// The identifier for the named reserve. + pub id: ReserveIdentifier, + /// The amount of the named reserve. + pub amount: Balance, } /// All balance information for an account. #[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct AccountData { - /// Non-reserved part of the balance which the account holder may be able to control. - /// - /// This is the only balance that matters in terms of most operations on tokens. - pub free: Balance, - /// Balance which is has active holds on it and may not be used at all. - /// - /// This is the sum of all individual holds together with any sums still under the (deprecated) - /// reserves API. - pub reserved: Balance, - /// The amount that `free + reserved` may not drop below when reducing the balance, except for - /// actions where the account owner cannot reasonably benefit from the balance reduction, such - /// as slashing. - pub frozen: Balance, - /// Extra information about this account. The MSB is a flag indicating whether the new ref- - /// counting logic is in place for this account. - pub flags: ExtraFlags, + /// Non-reserved part of the balance which the account holder may be able to control. + /// + /// This is the only balance that matters in terms of most operations on tokens. + pub free: Balance, + /// Balance which is has active holds on it and may not be used at all. + /// + /// This is the sum of all individual holds together with any sums still under the (deprecated) + /// reserves API. + pub reserved: Balance, + /// The amount that `free + reserved` may not drop below when reducing the balance, except for + /// actions where the account owner cannot reasonably benefit from the balance reduction, such + /// as slashing. + pub frozen: Balance, + /// Extra information about this account. The MSB is a flag indicating whether the new ref- + /// counting logic is in place for this account. + pub flags: ExtraFlags, } const IS_NEW_LOGIC: u128 = 0x80000000_00000000_00000000_00000000u128; @@ -104,51 +104,54 @@ const IS_NEW_LOGIC: u128 = 0x80000000_00000000_00000000_00000000u128; #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub struct ExtraFlags(pub(crate) u128); impl Default for ExtraFlags { - fn default() -> Self { - Self(IS_NEW_LOGIC) - } + fn default() -> Self { + Self(IS_NEW_LOGIC) + } } impl ExtraFlags { - pub fn old_logic() -> Self { - Self(0) - } - pub fn set_new_logic(&mut self) { - self.0 |= IS_NEW_LOGIC - } - pub fn is_new_logic(&self) -> bool { - (self.0 & IS_NEW_LOGIC) == IS_NEW_LOGIC - } + pub fn old_logic() -> Self { + Self(0) + } + pub fn set_new_logic(&mut self) { + self.0 |= IS_NEW_LOGIC + } + pub fn is_new_logic(&self) -> bool { + (self.0 & IS_NEW_LOGIC) == IS_NEW_LOGIC + } } impl AccountData { - pub fn usable(&self) -> Balance { - self.free.saturating_sub(self.frozen) - } - - /// The total balance in this account including any that is reserved and ignoring any frozen. - pub fn total(&self) -> Balance { - self.free.saturating_add(self.reserved) - } + pub fn usable(&self) -> Balance { + self.free.saturating_sub(self.frozen) + } + + /// The total balance in this account including any that is reserved and ignoring any frozen. + pub fn total(&self) -> Balance { + self.free.saturating_add(self.reserved) + } } pub struct DustCleaner, I: 'static = ()>( - pub(crate) Option<(T::AccountId, CreditOf)>, + pub(crate) Option<(T::AccountId, CreditOf)>, ); impl, I: 'static> Drop for DustCleaner { - fn drop(&mut self) { - if let Some((who, dust)) = self.0.take() { - Pallet::::deposit_event(Event::DustLost { account: who, amount: dust.peek() }); - T::DustRemoval::on_unbalanced(dust); - } - } + fn drop(&mut self) { + if let Some((who, dust)) = self.0.take() { + Pallet::::deposit_event(Event::DustLost { + account: who, + amount: dust.peek(), + }); + T::DustRemoval::on_unbalanced(dust); + } + } } /// Whether something should be interpreted as an increase or a decrease. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] pub enum AdjustmentDirection { - /// Increase the amount. - Increase, - /// Decrease the amount. - Decrease, + /// Increase the amount. + Increase, + /// Decrease the amount. + Decrease, } diff --git a/pallets/faucet/src/lib.rs b/pallets/faucet/src/lib.rs index 42dc6ec4..43c3e8a8 100644 --- a/pallets/faucet/src/lib.rs +++ b/pallets/faucet/src/lib.rs @@ -59,7 +59,7 @@ pub mod pallet { pub fn mint_new_tokens( _origin: OriginFor, dest: ::Source, - _seed: u64 + _seed: u64, ) -> DispatchResult { // Get the destination address let dest = T::Lookup::lookup(dest)?; @@ -92,11 +92,11 @@ pub mod pallet { fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { match call { - Call::mint_new_tokens { dest, seed} => ValidTransaction::with_tag_prefix("Faucet") + Call::mint_new_tokens { dest, seed } => ValidTransaction::with_tag_prefix("Faucet") .priority(100) .longevity(64) .propagate(true) - .and_provides((dest,seed,>::block_number())) + .and_provides((dest, seed, >::block_number())) .build(), _ => Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), } diff --git a/pallets/mining-rewards/src/lib.rs b/pallets/mining-rewards/src/lib.rs index 12454913..e5ebdc1d 100644 --- a/pallets/mining-rewards/src/lib.rs +++ b/pallets/mining-rewards/src/lib.rs @@ -15,166 +15,163 @@ pub use weights::*; #[frame_support::pallet] pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - use sp_runtime::generic::DigestItem; - use sp_consensus_pow::POW_ENGINE_ID; - use codec::Decode; - use frame_support::traits::{Currency, Get, Imbalance, OnUnbalanced}; - use sp_runtime::traits::Saturating; - use frame_support::traits::fungible::{DecreaseIssuance,IncreaseIssuance}; - - pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; - - pub type NegativeImbalanceOf = frame_support::traits::fungible::Imbalance< - u128, - DecreaseIssuance<::AccountId, pallet_balances::Pallet>, - IncreaseIssuance<::AccountId, pallet_balances::Pallet> - >; - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::storage] - #[pallet::getter(fn collected_fees)] - pub(super) type CollectedFees = StorageValue<_, BalanceOf, ValueQuery>; - - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - /// The currency in which fees are paid and rewards are issued - type Currency: Currency; - - /// The base block reward given to miners - #[pallet::constant] - type BlockReward: Get>; - - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A miner has been identified for a block - MinerRewarded { - /// Block number - block: BlockNumberFor, - /// Miner account - miner: T::AccountId, - /// Total reward (base + fees) - reward: BalanceOf, - }, - /// Transaction fees were collected for later distribution - FeesCollected { - /// The amount collected - amount: BalanceOf, - /// Total fees waiting for distribution - total: BalanceOf, - }, - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(_block_number: BlockNumberFor) -> Weight { - // Return weight consumed - Weight::from_parts(10_000, 0) - } - - fn on_finalize(block_number: BlockNumberFor){ - // Extract miner ID from the pre-runtime digest - if let Some(miner) = Self::extract_miner_from_digest() { - - // Get the block reward - let base_reward = T::BlockReward::get(); - - let tx_fees = >::take(); - - log::info!("💰 Base reward: {:?}", base_reward); - log::info!("💰 Tx_fees: {:?}",tx_fees); - - let total_reward = base_reward.saturating_add(tx_fees); - - // Create imbalance for block reward - let reward_imbalance = T::Currency::issue(total_reward); - - // We could do this in a more sophisticated way with OnUnbalanced - T::Currency::resolve_creating(&miner, reward_imbalance); - - // Emit an event - Self::deposit_event(Event::MinerRewarded { - block: block_number, - miner: miner.clone(), - reward: total_reward, - }); - - log::info!( + use super::*; + use codec::Decode; + use frame_support::pallet_prelude::*; + use frame_support::traits::fungible::{DecreaseIssuance, IncreaseIssuance}; + use frame_support::traits::{Currency, Get, Imbalance, OnUnbalanced}; + use frame_system::pallet_prelude::*; + use sp_consensus_pow::POW_ENGINE_ID; + use sp_runtime::generic::DigestItem; + use sp_runtime::traits::Saturating; + + pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + + pub type NegativeImbalanceOf = frame_support::traits::fungible::Imbalance< + u128, + DecreaseIssuance<::AccountId, pallet_balances::Pallet>, + IncreaseIssuance<::AccountId, pallet_balances::Pallet>, + >; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn collected_fees)] + pub(super) type CollectedFees = StorageValue<_, BalanceOf, ValueQuery>; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + /// The currency in which fees are paid and rewards are issued + type Currency: Currency; + + /// The base block reward given to miners + #[pallet::constant] + type BlockReward: Get>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A miner has been identified for a block + MinerRewarded { + /// Block number + block: BlockNumberFor, + /// Miner account + miner: T::AccountId, + /// Total reward (base + fees) + reward: BalanceOf, + }, + /// Transaction fees were collected for later distribution + FeesCollected { + /// The amount collected + amount: BalanceOf, + /// Total fees waiting for distribution + total: BalanceOf, + }, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_block_number: BlockNumberFor) -> Weight { + // Return weight consumed + Weight::from_parts(10_000, 0) + } + + fn on_finalize(block_number: BlockNumberFor) { + // Extract miner ID from the pre-runtime digest + if let Some(miner) = Self::extract_miner_from_digest() { + // Get the block reward + let base_reward = T::BlockReward::get(); + + let tx_fees = >::take(); + + log::info!("💰 Base reward: {:?}", base_reward); + log::info!("💰 Tx_fees: {:?}", tx_fees); + + let total_reward = base_reward.saturating_add(tx_fees); + + // Create imbalance for block reward + let reward_imbalance = T::Currency::issue(total_reward); + + // We could do this in a more sophisticated way with OnUnbalanced + T::Currency::resolve_creating(&miner, reward_imbalance); + + // Emit an event + Self::deposit_event(Event::MinerRewarded { + block: block_number, + miner: miner.clone(), + reward: total_reward, + }); + + log::info!( target: "mining-rewards", "💰 Miner rewarded: {:?}", total_reward); - let miner_balance = T::Currency::free_balance(&miner); - log::info!(target: "mining-rewards", + let miner_balance = T::Currency::free_balance(&miner); + log::info!(target: "mining-rewards", "🏦 Miner balance: {:?}", miner_balance); - - } else { - log::info!( + } else { + log::info!( target: "mining-rewards", "No rewards address provided for block {:?}", block_number ); - } - } - } - - impl Pallet { - /// Extract miner account ID from the pre-runtime digest - fn extract_miner_from_digest() -> Option { - // Get the digest from the current block - let digest = >::digest(); - - // Look for pre-runtime digest with POW_ENGINE_ID - for log in digest.logs.iter() { - if let DigestItem::PreRuntime(engine_id, data) = log { - if engine_id == &POW_ENGINE_ID { - // Try to decode the miner account ID - if let Ok(miner) = T::AccountId::decode(&mut &data[..]) { - return Some(miner); - } - } - } - } - None - } - - pub fn collect_transaction_fees(fees: BalanceOf) { - >::mutate(|total_fees| { - *total_fees = total_fees.saturating_add(fees); - }); - Self::deposit_event(Event::FeesCollected { - amount: fees, - total: >::get(), - }); - } - } - - pub struct TransactionFeesCollector(PhantomData); - - impl OnUnbalanced> for TransactionFeesCollector - where - T: Config + pallet_balances::Config, - BalanceOf: From - { - fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { - - let value_u128 = amount.peek(); - - Pallet::::collect_transaction_fees(BalanceOf::::from(value_u128)); - - drop(amount); - } - } -} \ No newline at end of file + } + } + } + + impl Pallet { + /// Extract miner account ID from the pre-runtime digest + fn extract_miner_from_digest() -> Option { + // Get the digest from the current block + let digest = >::digest(); + + // Look for pre-runtime digest with POW_ENGINE_ID + for log in digest.logs.iter() { + if let DigestItem::PreRuntime(engine_id, data) = log { + if engine_id == &POW_ENGINE_ID { + // Try to decode the miner account ID + if let Ok(miner) = T::AccountId::decode(&mut &data[..]) { + return Some(miner); + } + } + } + } + None + } + + pub fn collect_transaction_fees(fees: BalanceOf) { + >::mutate(|total_fees| { + *total_fees = total_fees.saturating_add(fees); + }); + Self::deposit_event(Event::FeesCollected { + amount: fees, + total: >::get(), + }); + } + } + + pub struct TransactionFeesCollector(PhantomData); + + impl OnUnbalanced> for TransactionFeesCollector + where + T: Config + pallet_balances::Config, + BalanceOf: From, + { + fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { + let value_u128 = amount.peek(); + + Pallet::::collect_transaction_fees(BalanceOf::::from(value_u128)); + + drop(amount); + } + } +} diff --git a/pallets/mining-rewards/src/mock.rs b/pallets/mining-rewards/src/mock.rs index 7b8cc647..69417fa7 100644 --- a/pallets/mining-rewards/src/mock.rs +++ b/pallets/mining-rewards/src/mock.rs @@ -1,18 +1,15 @@ use crate as pallet_mining_rewards; -use frame_support::{ - parameter_types, - traits::ConstU32, -}; -use sp_consensus_pow::POW_ENGINE_ID; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, Digest, DigestItem, -}; use codec::Encode; use frame_support::__private::sp_io; use frame_support::traits::{Everything, Hooks}; +use frame_support::{parameter_types, traits::ConstU32}; +use sp_consensus_pow::POW_ENGINE_ID; use sp_runtime::app_crypto::sp_core; use sp_runtime::testing::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, Digest, DigestItem, +}; // Configure a mock runtime to test the pallet frame_support::construct_runtime!( @@ -34,60 +31,60 @@ parameter_types! { } impl frame_system::Config for Test { - type RuntimeEvent = RuntimeEvent; - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type RuntimeTask = (); - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Block = Block; - type BlockHashCount = BlockHashCount; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type ExtensionsWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - type SingleBlockMigrations = (); - type MultiBlockMigrator = (); - type PreInherents = (); - type PostInherents = (); - type PostTransactions = (); + type RuntimeEvent = RuntimeEvent; + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = (); + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type ExtensionsWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); } impl pallet_balances::Config for Test { - type RuntimeEvent = RuntimeEvent; - type RuntimeHoldReason = (); - type RuntimeFreezeReason = (); - type WeightInfo = (); - type Balance = Balance; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type ReserveIdentifier = [u8; 8]; - type FreezeIdentifier = (); - type MaxLocks = ConstU32<50>; - type MaxReserves = (); - type MaxFreezes = ConstU32<0>; - type DoneSlashHandler = (); + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); + type WeightInfo = (); + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxLocks = ConstU32<50>; + type MaxReserves = (); + type MaxFreezes = ConstU32<0>; + type DoneSlashHandler = (); } impl pallet_mining_rewards::Config for Test { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - type Currency = Balances; - type BlockReward = BlockReward; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type BlockReward = BlockReward; } // Configure a default miner account for tests @@ -96,49 +93,51 @@ pub const MINER2: u64 = 2; // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: vec![ - (MINER, ExistentialDeposit::get()), - (MINER2, ExistentialDeposit::get()), - ], - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); // Start at block 1 - ext + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (MINER, ExistentialDeposit::get()), + (MINER2, ExistentialDeposit::get()), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); // Start at block 1 + ext } // Helper function to create a block digest with a miner pre-runtime digest pub fn set_miner_digest(miner: u64) { - let miner_bytes = miner.encode(); - let pre_digest = DigestItem::PreRuntime(POW_ENGINE_ID, miner_bytes); - let digest = Digest { logs: vec![pre_digest] }; - - // Set the digest in the system - System::reset_events(); - System::initialize(&1, &sp_core::H256::default(), &digest); + let miner_bytes = miner.encode(); + let pre_digest = DigestItem::PreRuntime(POW_ENGINE_ID, miner_bytes); + let digest = Digest { + logs: vec![pre_digest], + }; + + // Set the digest in the system + System::reset_events(); + System::initialize(&1, &sp_core::H256::default(), &digest); } // Helper function to run a block pub fn run_to_block(n: u64) { - while System::block_number() < n { - let block_number = System::block_number(); + while System::block_number() < n { + let block_number = System::block_number(); - // Run on_finalize for the current block - MiningRewards::on_finalize(block_number); - System::on_finalize(block_number); + // Run on_finalize for the current block + MiningRewards::on_finalize(block_number); + System::on_finalize(block_number); - // Increment block number - System::set_block_number(block_number + 1); + // Increment block number + System::set_block_number(block_number + 1); - // Run on_initialize for the next block - System::on_initialize(block_number + 1); - MiningRewards::on_initialize(block_number + 1); - } -} \ No newline at end of file + // Run on_initialize for the next block + System::on_initialize(block_number + 1); + MiningRewards::on_initialize(block_number + 1); + } +} diff --git a/pallets/mining-rewards/src/tests.rs b/pallets/mining-rewards/src/tests.rs index 6fc319c7..28ba15f4 100644 --- a/pallets/mining-rewards/src/tests.rs +++ b/pallets/mining-rewards/src/tests.rs @@ -25,8 +25,9 @@ fn miner_reward_works() { Event::MinerRewarded { block: 1, miner: MINER, - reward: 50 - }.into() + reward: 50, + } + .into(), ); }); } @@ -49,7 +50,8 @@ fn miner_reward_with_transaction_fees_works() { Event::FeesCollected { amount: 25, total: 25, - }.into() + } + .into(), ); // Run the on_finalize hook @@ -66,8 +68,9 @@ fn miner_reward_with_transaction_fees_works() { Event::MinerRewarded { block: 1, miner: MINER, - reward: 75 - }.into() + reward: 75, + } + .into(), ); }); } @@ -257,4 +260,4 @@ fn test_run_to_block_helper() { // Verify we're at the expected block number assert_eq!(System::block_number(), 3); }); -} \ No newline at end of file +} diff --git a/pallets/qpow/src/lib.rs b/pallets/qpow/src/lib.rs index 106e45ec..5e2282b8 100644 --- a/pallets/qpow/src/lib.rs +++ b/pallets/qpow/src/lib.rs @@ -13,353 +13,356 @@ mod benchmarking; #[frame_support::pallet] pub mod pallet { - use frame_support::{pallet_prelude::*, traits::BuildGenesisConfig, traits::Time}; - use frame_support::sp_runtime::SaturatedConversion; - use frame_system::pallet_prelude::BlockNumberFor; - use sp_arithmetic::FixedU128; - use frame_support::sp_runtime::Saturating; - use frame_support::sp_runtime::traits::{One, Zero}; - use sp_core::U512; - use core::ops::{Shl, Shr}; - use sp_std::prelude::*; - use qpow_math::{is_valid_nonce, get_nonce_distance, get_random_rsa, hash_to_group_bigint}; - - #[pallet::pallet] - pub struct Pallet(_); - - - #[pallet::storage] - pub type BlockDistanceThresholds = StorageMap<_,Twox64Concat,BlockNumberFor, U512, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn latest_nonce)] - pub type LatestNonce = StorageValue<_, [u8; 64]>; - - #[pallet::storage] - pub type LastBlockTime = StorageValue<_, u64, ValueQuery>; - - #[pallet::storage] - pub type LastBlockDuration = StorageValue<_, u64, ValueQuery>; - - #[pallet::storage] - pub type CurrentDistanceThreshold = StorageValue<_, U512, ValueQuery>; - - #[pallet::storage] - pub type TotalWork = StorageValue<_, U512, ValueQuery>; - - #[pallet::storage] - pub type BlocksInPeriod = StorageValue<_, u32, ValueQuery>; - - #[pallet::storage] - pub type BlockTimeHistory = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>; - - // Index for current position in ring buffer - #[pallet::storage] - pub type HistoryIndex = StorageValue<_, u32, ValueQuery>; - - // Current history size - #[pallet::storage] - pub type HistorySize = StorageValue<_, u32, ValueQuery>; - - #[pallet::config] - pub trait Config: frame_system::Config + pallet_timestamp::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type WeightInfo: WeightInfo; - - #[pallet::constant] - type InitialDistanceThresholdExponent: Get; - - #[pallet::constant] - type DifficultyAdjustPercentClamp: Get; - - #[pallet::constant] - type TargetBlockTime: Get; - - #[pallet::constant] - type AdjustmentPeriod: Get; - - #[pallet::constant] - type BlockTimeHistorySize: Get; - - #[pallet::constant] - type MaxReorgDepth: Get; - } - - #[pallet::genesis_config] - pub struct GenesisConfig { - pub initial_distance: U512, - #[serde(skip)] - pub _phantom: PhantomData, - } - - //#[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { - initial_distance: U512::one().shl(T::InitialDistanceThresholdExponent::get()), - _phantom: PhantomData, - } - } - } - - #[pallet::genesis_build] - impl BuildGenesisConfig for GenesisConfig { - fn build(&self) { - let initial_proof = [0u8; 64]; - >::put(initial_proof); - let initial_distance_threshold = get_initial_distance_threshold::(); - - // Set current distance_threshold for the genesis block - >::put(initial_distance_threshold); - - // Save initial distance_threshold for the genesis block - let genesis_block_number = BlockNumberFor::::zero(); - >::insert(genesis_block_number, initial_distance_threshold); - - // Initialize the total distance_threshold with the genesis block's distance_threshold - >::put(U512::one()); - } - } - - //TODO all this should be generated with benchmarks - - pub trait WeightInfo { - fn submit_proof() -> Weight; - } - - pub struct DefaultWeightInfo; - - impl WeightInfo for DefaultWeightInfo { - fn submit_proof() -> Weight { - Weight::from_parts(10_000, 0) - } - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - ProofSubmitted { - nonce: [u8; 64], - }, - DistanceThresholdAdjusted { - old_distance_threshold: U512, - new_distance_threshold: U512, - observed_block_time: u64, - }, - } - - #[pallet::error] - pub enum Error { - InvalidSolution, - ArithmeticOverflow - } - - pub fn get_initial_distance_threshold() -> U512 { - U512::one().shl(T::InitialDistanceThresholdExponent::get()) - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(_block_number: BlockNumberFor) -> Weight { - Weight::zero() - } - - /// Called when there is remaining weight at the end of the block. - fn on_idle(_block_number: BlockNumberFor, _remaining_weight: Weight) -> Weight { - if >::get() == 0 { - >::put(pallet_timestamp::Pallet::::now().saturated_into::()); - let initial_distance_threshold: U512 = get_initial_distance_threshold::(); - >::put(initial_distance_threshold); - } - Weight::zero() - } - - /// Called at the end of each block. - fn on_finalize(block_number: BlockNumberFor) { - let blocks = >::get(); - let current_distance_threshold = >::get(); - log::info!( + use core::ops::{Shl, Shr}; + use frame_support::sp_runtime::traits::{One, Zero}; + use frame_support::sp_runtime::SaturatedConversion; + use frame_support::sp_runtime::Saturating; + use frame_support::{pallet_prelude::*, traits::BuildGenesisConfig, traits::Time}; + use frame_system::pallet_prelude::BlockNumberFor; + use qpow_math::{get_nonce_distance, get_random_rsa, hash_to_group_bigint, is_valid_nonce}; + use sp_arithmetic::FixedU128; + use sp_core::U512; + use sp_std::prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type BlockDistanceThresholds = + StorageMap<_, Twox64Concat, BlockNumberFor, U512, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn latest_nonce)] + pub type LatestNonce = StorageValue<_, [u8; 64]>; + + #[pallet::storage] + pub type LastBlockTime = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + pub type LastBlockDuration = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + pub type CurrentDistanceThreshold = StorageValue<_, U512, ValueQuery>; + + #[pallet::storage] + pub type TotalWork = StorageValue<_, U512, ValueQuery>; + + #[pallet::storage] + pub type BlocksInPeriod = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + pub type BlockTimeHistory = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>; + + // Index for current position in ring buffer + #[pallet::storage] + pub type HistoryIndex = StorageValue<_, u32, ValueQuery>; + + // Current history size + #[pallet::storage] + pub type HistorySize = StorageValue<_, u32, ValueQuery>; + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_timestamp::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type WeightInfo: WeightInfo; + + #[pallet::constant] + type InitialDistanceThresholdExponent: Get; + + #[pallet::constant] + type DifficultyAdjustPercentClamp: Get; + + #[pallet::constant] + type TargetBlockTime: Get; + + #[pallet::constant] + type AdjustmentPeriod: Get; + + #[pallet::constant] + type BlockTimeHistorySize: Get; + + #[pallet::constant] + type MaxReorgDepth: Get; + } + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub initial_distance: U512, + #[serde(skip)] + pub _phantom: PhantomData, + } + + //#[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { + initial_distance: U512::one().shl(T::InitialDistanceThresholdExponent::get()), + _phantom: PhantomData, + } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + let initial_proof = [0u8; 64]; + >::put(initial_proof); + let initial_distance_threshold = get_initial_distance_threshold::(); + + // Set current distance_threshold for the genesis block + >::put(initial_distance_threshold); + + // Save initial distance_threshold for the genesis block + let genesis_block_number = BlockNumberFor::::zero(); + >::insert(genesis_block_number, initial_distance_threshold); + + // Initialize the total distance_threshold with the genesis block's distance_threshold + >::put(U512::one()); + } + } + + //TODO all this should be generated with benchmarks + + pub trait WeightInfo { + fn submit_proof() -> Weight; + } + + pub struct DefaultWeightInfo; + + impl WeightInfo for DefaultWeightInfo { + fn submit_proof() -> Weight { + Weight::from_parts(10_000, 0) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ProofSubmitted { + nonce: [u8; 64], + }, + DistanceThresholdAdjusted { + old_distance_threshold: U512, + new_distance_threshold: U512, + observed_block_time: u64, + }, + } + + #[pallet::error] + pub enum Error { + InvalidSolution, + ArithmeticOverflow, + } + + pub fn get_initial_distance_threshold() -> U512 { + U512::one().shl(T::InitialDistanceThresholdExponent::get()) + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_block_number: BlockNumberFor) -> Weight { + Weight::zero() + } + + /// Called when there is remaining weight at the end of the block. + fn on_idle(_block_number: BlockNumberFor, _remaining_weight: Weight) -> Weight { + if >::get() == 0 { + >::put( + pallet_timestamp::Pallet::::now().saturated_into::(), + ); + let initial_distance_threshold: U512 = get_initial_distance_threshold::(); + >::put(initial_distance_threshold); + } + Weight::zero() + } + + /// Called at the end of each block. + fn on_finalize(block_number: BlockNumberFor) { + let blocks = >::get(); + let current_distance_threshold = >::get(); + log::info!( "📢 QPoW: before submit at block {:?}, blocks_in_period={}, current_distance_threshold={}", block_number, blocks, current_distance_threshold ); - Self::adjust_distance_threshold(); - } - } - - impl Pallet { - - const FIXED_U128_SCALE: u128 = 1_000_000_000_000_000_000; // 10^18 - - // Block time recording for median calculation - fn record_block_time(block_time: u64) { - // History size limiter - let max_history = T::BlockTimeHistorySize::get(); - let mut index = >::get(); - let size = >::get(); - - // Save block time - >::insert(index, block_time); - - // Update index and time - index = (index.saturating_add(1)) % max_history; - let new_size = if size < max_history { size.saturating_add(1) } else { max_history }; - - >::put(index); - >::put(new_size); - - log::info!( - "📊 Recorded block time: {}ms, history size: {}/{}", - block_time, - new_size, - max_history - ); - } - - // Sum of block times - pub fn get_block_time_sum() -> u64 { - let size = >::get(); - - if size == 0 { - return T::TargetBlockTime::get(); - } - - // Take all data - let mut sum = 0; - for i in 0..size { - sum = sum.saturating_add(>::get(i)); - } - - log::info!( - "📊 Calculated total adjustment period time: {}ms from {} samples", - sum, - size - ); - - sum - } - - - // Median calculation - pub fn get_median_block_time() -> u64 { - let size = >::get(); - - if size == 0 { - return T::TargetBlockTime::get(); - } - - // Take all data - let mut times = Vec::with_capacity(size as usize); - for i in 0..size { - times.push(>::get(i)); - } - - log::info!("📊 Block times: {:?}", times); - - // Sort it - times.sort(); - - - let median_time = if times.len() % 2 == 0u32 as usize { - (times[times.len() / 2 - 1].saturating_add(times[times.len() / 2])) / 2 - } else { - times[times.len() / 2] - }; - - log::info!( - "📊 Calculated median block time: {}ms from {} samples", - median_time, - times.len() - ); - - median_time - } - - fn percentage_change(big_a: U512, big_b: U512) -> (U512, bool) { - let a = big_a.shr(10); - let b = big_b.shr(10); - let (larger, smaller) = if a > b { (a, b) } else { (b, a) }; - let abs_diff = larger - smaller; - let change = abs_diff.saturating_mul(U512::from(100u64)) / a; - - (change, b >= a) - } - - fn adjust_distance_threshold() { - // Get current time - let now = pallet_timestamp::Pallet::::now().saturated_into::(); - let last_time = >::get(); - let blocks = >::get(); - let current_distance_threshold = >::get(); - let current_block_number = >::block_number(); - - // Store distance_threshold for block - >::insert(current_block_number, current_distance_threshold); - - // Update TotalWork - let old_total_work = >::get(); - let current_work = Self::get_difficulty(); - let new_total_work = old_total_work.saturating_add(current_work); - >::put(new_total_work); - log::info!( - "Total work: now={}, last_time={}, diff={}", - new_total_work, - old_total_work, - new_total_work - old_total_work - ); - - - // Increment number of blocks in period - >::put(blocks.saturating_add(1)); - - // Only calculate block time if we're past the genesis block - if current_block_number > One::one() { - let block_time = now.saturating_sub(last_time); - - log::info!( - "Time calculation: now={}, last_time={}, diff={}ms", - now, - last_time, - block_time - ); - - // Store the actual block duration - >::put(block_time); - - // record new block time - Self::record_block_time(block_time); - } - - // Add last block time for the next calculations - >::put(now); - - // Should we correct distance_threshold ? - if blocks >= T::AdjustmentPeriod::get() { - let history_size = >::get(); - if history_size > 0 { - let observed_block_time = Self::get_block_time_sum(); - let target_time = T::TargetBlockTime::get().saturating_mul(history_size as u64); - - let new_distance_threshold = Self::calculate_distance_threshold( - current_distance_threshold, - observed_block_time, - target_time - ); - - // Save new distance_threshold - >::put(new_distance_threshold); - - // Propagate new Event - Self::deposit_event(Event::DistanceThresholdAdjusted { - old_distance_threshold: current_distance_threshold, - new_distance_threshold, - observed_block_time, - }); - - let (pct_change, is_positive) = Self::percentage_change(current_distance_threshold, new_distance_threshold); - - log::info!( + Self::adjust_distance_threshold(); + } + } + + impl Pallet { + const FIXED_U128_SCALE: u128 = 1_000_000_000_000_000_000; // 10^18 + + // Block time recording for median calculation + fn record_block_time(block_time: u64) { + // History size limiter + let max_history = T::BlockTimeHistorySize::get(); + let mut index = >::get(); + let size = >::get(); + + // Save block time + >::insert(index, block_time); + + // Update index and time + index = (index.saturating_add(1)) % max_history; + let new_size = if size < max_history { + size.saturating_add(1) + } else { + max_history + }; + + >::put(index); + >::put(new_size); + + log::info!( + "📊 Recorded block time: {}ms, history size: {}/{}", + block_time, + new_size, + max_history + ); + } + + // Sum of block times + pub fn get_block_time_sum() -> u64 { + let size = >::get(); + + if size == 0 { + return T::TargetBlockTime::get(); + } + + // Take all data + let mut sum = 0; + for i in 0..size { + sum = sum.saturating_add(>::get(i)); + } + + log::info!( + "📊 Calculated total adjustment period time: {}ms from {} samples", + sum, + size + ); + + sum + } + + // Median calculation + pub fn get_median_block_time() -> u64 { + let size = >::get(); + + if size == 0 { + return T::TargetBlockTime::get(); + } + + // Take all data + let mut times = Vec::with_capacity(size as usize); + for i in 0..size { + times.push(>::get(i)); + } + + log::info!("📊 Block times: {:?}", times); + + // Sort it + times.sort(); + + let median_time = if times.len() % 2 == 0u32 as usize { + (times[times.len() / 2 - 1].saturating_add(times[times.len() / 2])) / 2 + } else { + times[times.len() / 2] + }; + + log::info!( + "📊 Calculated median block time: {}ms from {} samples", + median_time, + times.len() + ); + + median_time + } + + fn percentage_change(big_a: U512, big_b: U512) -> (U512, bool) { + let a = big_a.shr(10); + let b = big_b.shr(10); + let (larger, smaller) = if a > b { (a, b) } else { (b, a) }; + let abs_diff = larger - smaller; + let change = abs_diff.saturating_mul(U512::from(100u64)) / a; + + (change, b >= a) + } + + fn adjust_distance_threshold() { + // Get current time + let now = pallet_timestamp::Pallet::::now().saturated_into::(); + let last_time = >::get(); + let blocks = >::get(); + let current_distance_threshold = >::get(); + let current_block_number = >::block_number(); + + // Store distance_threshold for block + >::insert(current_block_number, current_distance_threshold); + + // Update TotalWork + let old_total_work = >::get(); + let current_work = Self::get_difficulty(); + let new_total_work = old_total_work.saturating_add(current_work); + >::put(new_total_work); + log::info!( + "Total work: now={}, last_time={}, diff={}", + new_total_work, + old_total_work, + new_total_work - old_total_work + ); + + // Increment number of blocks in period + >::put(blocks.saturating_add(1)); + + // Only calculate block time if we're past the genesis block + if current_block_number > One::one() { + let block_time = now.saturating_sub(last_time); + + log::info!( + "Time calculation: now={}, last_time={}, diff={}ms", + now, + last_time, + block_time + ); + + // Store the actual block duration + >::put(block_time); + + // record new block time + Self::record_block_time(block_time); + } + + // Add last block time for the next calculations + >::put(now); + + // Should we correct distance_threshold ? + if blocks >= T::AdjustmentPeriod::get() { + let history_size = >::get(); + if history_size > 0 { + let observed_block_time = Self::get_block_time_sum(); + let target_time = T::TargetBlockTime::get().saturating_mul(history_size as u64); + + let new_distance_threshold = Self::calculate_distance_threshold( + current_distance_threshold, + observed_block_time, + target_time, + ); + + // Save new distance_threshold + >::put(new_distance_threshold); + + // Propagate new Event + Self::deposit_event(Event::DistanceThresholdAdjusted { + old_distance_threshold: current_distance_threshold, + new_distance_threshold, + observed_block_time, + }); + + let (pct_change, is_positive) = + Self::percentage_change(current_distance_threshold, new_distance_threshold); + + log::info!( "🟢 Adjusted mining distance threshold {}{}%: {}.. -> {}.. (observed block time: {}ms, target: {}ms) ", if is_positive {"+"} else {"-"}, pct_change, @@ -368,171 +371,181 @@ pub mod pallet { observed_block_time, target_time ); - } - - // Reset counters before new iteration - >::put(0); - >::put(now); - } - else if blocks == 0 { - >::put(now); - } - } - - pub fn calculate_distance_threshold( - current_distance_threshold: U512, - observed_block_time: u64, - target_block_time: u64, - ) -> U512 { - log::info!("📊 Calculating new distance_threshold ---------------------------------------------"); - // Calculate ratio using FixedU128 - let clamp = FixedU128::from_rational(T::DifficultyAdjustPercentClamp::get() as u128, 100u128); - let one = FixedU128::from_rational(1u128, 1u128); - let ratio = FixedU128::from_rational(observed_block_time as u128, target_block_time as u128) - .min(one.saturating_add(clamp)) - .max(one.saturating_sub(clamp)); - log::info!("💧 Clamped block_time ratio as FixedU128: {} ", ratio); - - // Calculate adjusted distance_threshold - let mut adjusted = if ratio == one { - current_distance_threshold - } else { - let ratio_512 = U512::from(ratio.into_inner()); - - // Apply to current distance_threshold, divide first because it's too big already - let adj = current_distance_threshold.checked_div(U512::from(Self::FIXED_U128_SCALE)); - match adj { - Some(value) => value.saturating_mul(ratio_512), - None => { - log::warn!("Division by zero or overflow in distance_threshold calculation"); - return current_distance_threshold; - } - } - }; - - let min_distance = Self::get_min_distance(); - if adjusted < min_distance { + } + + // Reset counters before new iteration + >::put(0); + >::put(now); + } else if blocks == 0 { + >::put(now); + } + } + + pub fn calculate_distance_threshold( + current_distance_threshold: U512, + observed_block_time: u64, + target_block_time: u64, + ) -> U512 { + log::info!("📊 Calculating new distance_threshold ---------------------------------------------"); + // Calculate ratio using FixedU128 + let clamp = + FixedU128::from_rational(T::DifficultyAdjustPercentClamp::get() as u128, 100u128); + let one = FixedU128::from_rational(1u128, 1u128); + let ratio = + FixedU128::from_rational(observed_block_time as u128, target_block_time as u128) + .min(one.saturating_add(clamp)) + .max(one.saturating_sub(clamp)); + log::info!("💧 Clamped block_time ratio as FixedU128: {} ", ratio); + + // Calculate adjusted distance_threshold + let mut adjusted = if ratio == one { + current_distance_threshold + } else { + let ratio_512 = U512::from(ratio.into_inner()); + + // Apply to current distance_threshold, divide first because it's too big already + let adj = + current_distance_threshold.checked_div(U512::from(Self::FIXED_U128_SCALE)); + match adj { + Some(value) => value.saturating_mul(ratio_512), + None => { + log::warn!( + "Division by zero or overflow in distance_threshold calculation" + ); + return current_distance_threshold; + } + } + }; + + let min_distance = Self::get_min_distance(); + if adjusted < min_distance { adjusted = min_distance; } else { - let max_distance = Self::get_max_distance(); - if adjusted > max_distance { - adjusted = max_distance; - } - } - - log::info!("🟢 Current Distance Threshold: {}..", current_distance_threshold.shr(100)); - log::info!("🟢 Next Distance Threshold: {}..", adjusted.shr(100)); - log::info!("🕒 Observed Block Time Sum: {}ms", observed_block_time); - log::info!("🎯 Target Block Time Sum: {}ms", target_block_time); - - adjusted - } - } - - impl Pallet { - - pub fn is_valid_nonce(header: [u8; 32], nonce: [u8; 64], threshold: U512) -> bool { - is_valid_nonce(header, nonce, threshold) - } - - pub fn get_nonce_distance( - header: [u8; 32], // 256-bit header - nonce: [u8; 64], // 512-bit nonce - ) -> U512 { - get_nonce_distance(header, nonce) - } - - pub fn get_random_rsa(header: &[u8; 32]) -> (U512, U512) { - get_random_rsa(header) - } - - pub fn hash_to_group_bigint(h: &U512, m: &U512, n: &U512, solution: &U512) -> U512 { - hash_to_group_bigint(h, m, n, solution) - } - - // Function used during block import from the network - pub fn verify_for_import(header: [u8; 32], nonce: [u8; 64]) -> bool { - // During import, we use the current network distance_threshold - // This value will be correct because we're importing at the appropriate point in the chain - let current_distance_threshold = Self::get_distance_threshold(); - - // Verify using current distance_threshold - let valid = Self::is_valid_nonce(header, nonce, current_distance_threshold); - - if valid { - // Store the proof but don't emit event - imported blocks shouldn't trigger events - >::put(nonce); - // No new events for imported blocks - } - - valid - } - - // Function used to verify a block that's already in the chain - pub fn verify_historical_block(header: [u8; 32], nonce: [u8; 64], block_number: BlockNumberFor) -> bool { - // Get the stored distance_threshold for this specific block - let block_distance_threshold = Self::get_distance_threshold_at_block(block_number); - - if block_distance_threshold == U512::zero() { - // No stored distance_threshold - cannot verify - return false; - } - - // Verify with historical distance_threshold - Self::is_valid_nonce(header, nonce, block_distance_threshold) - } - - - // Function for local mining - pub fn submit_nonce(header: [u8; 32], nonce: [u8; 64]) -> bool { - let distance_threshold = Self::get_distance_threshold(); - let valid = Self::is_valid_nonce(header, nonce, distance_threshold); - - if valid { - >::put(nonce); - Self::deposit_event(Event::ProofSubmitted { nonce }); - } - - valid - } - - pub fn get_distance_threshold() -> U512 { - let stored = >::get(); - if stored == U512::zero() { - return get_initial_distance_threshold::(); - } - stored - } - - pub fn get_min_distance() -> U512 { - U512::one() - } - - pub fn get_max_distance() -> U512 { - get_initial_distance_threshold::().shl(2) - } - - pub fn get_difficulty() -> U512 { - Self::get_max_distance() / Self::get_distance_threshold() - } - - pub fn get_distance_threshold_at_block(block_number: BlockNumberFor) -> U512 { - >::get(block_number) - } - - pub fn get_total_work() -> U512 { - >::get() - } - - pub fn get_last_block_time() -> u64 { - >::get() - } - - pub fn get_last_block_duration() -> u64 { - >::get() - } - - pub fn get_max_reorg_depth() -> u32 { T::MaxReorgDepth::get() } - - } -} \ No newline at end of file + let max_distance = Self::get_max_distance(); + if adjusted > max_distance { + adjusted = max_distance; + } + } + + log::info!( + "🟢 Current Distance Threshold: {}..", + current_distance_threshold.shr(100) + ); + log::info!("🟢 Next Distance Threshold: {}..", adjusted.shr(100)); + log::info!("🕒 Observed Block Time Sum: {}ms", observed_block_time); + log::info!("🎯 Target Block Time Sum: {}ms", target_block_time); + + adjusted + } + } + + impl Pallet { + pub fn is_valid_nonce(header: [u8; 32], nonce: [u8; 64], threshold: U512) -> bool { + is_valid_nonce(header, nonce, threshold) + } + + pub fn get_nonce_distance( + header: [u8; 32], // 256-bit header + nonce: [u8; 64], // 512-bit nonce + ) -> U512 { + get_nonce_distance(header, nonce) + } + + pub fn get_random_rsa(header: &[u8; 32]) -> (U512, U512) { + get_random_rsa(header) + } + + pub fn hash_to_group_bigint(h: &U512, m: &U512, n: &U512, solution: &U512) -> U512 { + hash_to_group_bigint(h, m, n, solution) + } + + // Function used during block import from the network + pub fn verify_for_import(header: [u8; 32], nonce: [u8; 64]) -> bool { + // During import, we use the current network distance_threshold + // This value will be correct because we're importing at the appropriate point in the chain + let current_distance_threshold = Self::get_distance_threshold(); + + // Verify using current distance_threshold + let valid = Self::is_valid_nonce(header, nonce, current_distance_threshold); + + if valid { + // Store the proof but don't emit event - imported blocks shouldn't trigger events + >::put(nonce); + // No new events for imported blocks + } + + valid + } + + // Function used to verify a block that's already in the chain + pub fn verify_historical_block( + header: [u8; 32], + nonce: [u8; 64], + block_number: BlockNumberFor, + ) -> bool { + // Get the stored distance_threshold for this specific block + let block_distance_threshold = Self::get_distance_threshold_at_block(block_number); + + if block_distance_threshold == U512::zero() { + // No stored distance_threshold - cannot verify + return false; + } + + // Verify with historical distance_threshold + Self::is_valid_nonce(header, nonce, block_distance_threshold) + } + + // Function for local mining + pub fn submit_nonce(header: [u8; 32], nonce: [u8; 64]) -> bool { + let distance_threshold = Self::get_distance_threshold(); + let valid = Self::is_valid_nonce(header, nonce, distance_threshold); + + if valid { + >::put(nonce); + Self::deposit_event(Event::ProofSubmitted { nonce }); + } + + valid + } + + pub fn get_distance_threshold() -> U512 { + let stored = >::get(); + if stored == U512::zero() { + return get_initial_distance_threshold::(); + } + stored + } + + pub fn get_min_distance() -> U512 { + U512::one() + } + + pub fn get_max_distance() -> U512 { + get_initial_distance_threshold::().shl(2) + } + + pub fn get_difficulty() -> U512 { + Self::get_max_distance() / Self::get_distance_threshold() + } + + pub fn get_distance_threshold_at_block(block_number: BlockNumberFor) -> U512 { + >::get(block_number) + } + + pub fn get_total_work() -> U512 { + >::get() + } + + pub fn get_last_block_time() -> u64 { + >::get() + } + + pub fn get_last_block_duration() -> u64 { + >::get() + } + + pub fn get_max_reorg_depth() -> u32 { + T::MaxReorgDepth::get() + } + } +} diff --git a/pallets/qpow/src/mock.rs b/pallets/qpow/src/mock.rs index d39445eb..e04389d1 100644 --- a/pallets/qpow/src/mock.rs +++ b/pallets/qpow/src/mock.rs @@ -1,100 +1,97 @@ -use std::ops::Shl; use crate as pallet_qpow; -use frame_support::{parameter_types, traits::Everything}; +use crate::DefaultWeightInfo; use frame_support::pallet_prelude::{ConstU32, TypedGet}; use frame_support::traits::{ConstU64, ConstU8}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, -}; -use sp_core::{H256}; +use frame_support::{parameter_types, traits::Everything}; use primitive_types::U512; -use crate::DefaultWeightInfo; +use sp_core::H256; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use sp_runtime::BuildStorage; +use std::ops::Shl; type Block = frame_system::mocking::MockBlock; parameter_types! { pub const BlockHashCount: u64 = 250; - pub const MinimumPeriod: u64 = 100; // 100ms + pub const MinimumPeriod: u64 = 100; // 100ms } impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type Block = Block; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - // Change Index to Nonce - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - // Change Header to RuntimeEvent - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - type RuntimeTask = (); - type SingleBlockMigrations = (); - type MultiBlockMigrator = (); - type PreInherents = (); - type PostInherents = (); - type PostTransactions = (); - type ExtensionsWeightInfo = (); + type BaseCallFilter = Everything; + type Block = Block; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + // Change Index to Nonce + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + // Change Header to RuntimeEvent + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type RuntimeTask = (); + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); + type ExtensionsWeightInfo = (); } - frame_support::construct_runtime!( pub enum Test { System: frame_system, - Timestamp: pallet_timestamp, + Timestamp: pallet_timestamp, QPow: pallet_qpow, } ); impl pallet_timestamp::Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); } impl pallet_qpow::Config for Test { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = DefaultWeightInfo; - type InitialDistanceThresholdExponent = ConstU32<508>; - type DifficultyAdjustPercentClamp = ConstU8<10>; - type TargetBlockTime = ConstU64<1000>; - type AdjustmentPeriod = ConstU32<1>; - type BlockTimeHistorySize = ConstU32<5>; - type MaxReorgDepth = ConstU32<10>; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = DefaultWeightInfo; + type InitialDistanceThresholdExponent = ConstU32<508>; + type DifficultyAdjustPercentClamp = ConstU8<10>; + type TargetBlockTime = ConstU64<1000>; + type AdjustmentPeriod = ConstU32<1>; + type BlockTimeHistorySize = ConstU32<5>; + type MaxReorgDepth = ConstU32<10>; } - // Build genesis storage according to the mock runtime pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .unwrap(); + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); - // Add QPow genesis configuration - pallet_qpow::GenesisConfig:: { - initial_distance: U512::one().shl(::InitialDistanceThresholdExponent::get()), - _phantom: Default::default(), - } - .assimilate_storage(&mut t) - .unwrap(); + // Add QPow genesis configuration + pallet_qpow::GenesisConfig:: { + initial_distance: U512::one() + .shl(::InitialDistanceThresholdExponent::get()), + _phantom: Default::default(), + } + .assimilate_storage(&mut t) + .unwrap(); - t.into() + t.into() } diff --git a/pallets/qpow/src/tests.rs b/pallets/qpow/src/tests.rs index a984a4ee..185ec582 100644 --- a/pallets/qpow/src/tests.rs +++ b/pallets/qpow/src/tests.rs @@ -1,11 +1,13 @@ -use std::ops::Shl; +use crate::mock::*; +use crate::Config; +use crate::{BlockTimeHistory, HistoryIndex, HistorySize}; use frame_support::pallet_prelude::TypedGet; use frame_support::traits::Hooks; -use crate::mock::*; use primitive_types::U512; -use crate::{BlockTimeHistory, HistoryIndex, HistorySize}; -use crate::Config; -use qpow_math::{mod_pow, is_coprime, is_prime, get_random_rsa, hash_to_group_bigint_sha, sha3_512}; +use qpow_math::{ + get_random_rsa, hash_to_group_bigint_sha, is_coprime, is_prime, mod_pow, sha3_512, +}; +use std::ops::Shl; #[test] fn test_submit_valid_proof() { @@ -33,31 +35,40 @@ fn test_submit_valid_proof() { // Check if we found a pair where one is valid and one is invalid if invalid_distance > distance_threshold && valid_distance <= distance_threshold { - println!("Found test pair: invalid={}, valid={}", i, i+1); - println!("Invalid distance: {}, Valid distance: {}, Threshold: {}", - invalid_distance, valid_distance, distance_threshold); + println!("Found test pair: invalid={}, valid={}", i, i + 1); + println!( + "Invalid distance: {}, Valid distance: {}, Threshold: {}", + invalid_distance, valid_distance, distance_threshold + ); found_pair = true; break; } } if !found_pair { - panic!("Could not find valid/invalid nonce pair for testing with distance_threshold {}", distance_threshold); + panic!( + "Could not find valid/invalid nonce pair for testing with distance_threshold {}", + distance_threshold + ); } // Now run the test with our dynamically found values // Submit an invalid proof - assert!(!QPow::submit_nonce(header, invalid_nonce), - "Nonce should be invalid with distance {} > threshold {}", - QPow::get_nonce_distance(header, invalid_nonce), - max_distance - distance_threshold); + assert!( + !QPow::submit_nonce(header, invalid_nonce), + "Nonce should be invalid with distance {} > threshold {}", + QPow::get_nonce_distance(header, invalid_nonce), + max_distance - distance_threshold + ); // Submit a valid proof - assert!(QPow::submit_nonce(header, valid_nonce), - "Nonce should be valid with distance {} <= threshold {}", - QPow::get_nonce_distance(header, valid_nonce), - max_distance - distance_threshold); + assert!( + QPow::submit_nonce(header, valid_nonce), + "Nonce should be valid with distance {} <= threshold {}", + QPow::get_nonce_distance(header, valid_nonce), + max_distance - distance_threshold + ); assert_eq!(QPow::latest_nonce(), Some(valid_nonce)); @@ -65,7 +76,7 @@ fn test_submit_valid_proof() { let mut second_valid = valid_nonce; let mut found_second = false; - for i in valid_nonce[63]+1..255 { + for i in valid_nonce[63] + 1..255 { second_valid[63] = i; let distance = QPow::get_nonce_distance(header, second_valid); if distance <= max_distance - distance_threshold { @@ -107,14 +118,19 @@ fn test_verify_for_import() { let distance = QPow::get_nonce_distance(header, valid_nonce); if distance <= distance_threshold { - println!("Found valid nonce with value {} - distance: {}, threshold: {}", - i, distance, distance_threshold); + println!( + "Found valid nonce with value {} - distance: {}, threshold: {}", + i, distance, distance_threshold + ); found_valid = true; break; } } - assert!(found_valid, "Could not find valid nonce for testing. Adjust test parameters."); + assert!( + found_valid, + "Could not find valid nonce for testing. Adjust test parameters." + ); // Now verify using the dynamically found valid nonce assert!(QPow::verify_for_import(header, valid_nonce)); @@ -205,24 +221,28 @@ fn test_verify_historical_block() { }); } - #[test] fn test_distance_threshold_storage_and_retrieval() { new_test_ext().execute_with(|| { // 1. Test genesis block distance_threshold let genesis_distance_threshold = QPow::get_distance_threshold_at_block(0); - let initial_distance_threshold = U512::one().shl(::InitialDistanceThresholdExponent::get()); + let initial_distance_threshold = + U512::one().shl(::InitialDistanceThresholdExponent::get()); - assert_eq!(genesis_distance_threshold, initial_distance_threshold, - "Genesis block should have initial distance_threshold"); + assert_eq!( + genesis_distance_threshold, initial_distance_threshold, + "Genesis block should have initial distance_threshold" + ); // 2. Simulate block production run_to_block(1); // 3. Check distance_threshold for block 1 let block_1_distance_threshold = QPow::get_distance_threshold_at_block(1); - assert_eq!(block_1_distance_threshold, initial_distance_threshold, - "Block 1 should have same distance_threshold as initial"); + assert_eq!( + block_1_distance_threshold, initial_distance_threshold, + "Block 1 should have same distance_threshold as initial" + ); // 4. Simulate adjustment period let adjustment_period = ::AdjustmentPeriod::get(); @@ -230,14 +250,19 @@ fn test_distance_threshold_storage_and_retrieval() { // 5. Verify historical blocks maintain their distance_threshold let block_1_distance_threshold_after = QPow::get_distance_threshold_at_block(1); - assert_eq!(block_1_distance_threshold_after, block_1_distance_threshold, - "Historical block distance_threshold should not change"); + assert_eq!( + block_1_distance_threshold_after, block_1_distance_threshold, + "Historical block distance_threshold should not change" + ); // 6. Verify nonexistent block returns 0 let latest_block = System::block_number(); let future_block = latest_block + 1000; - assert_eq!(QPow::get_distance_threshold_at_block(future_block), U512::zero(), - "Future block distance_threshold should be 0"); + assert_eq!( + QPow::get_distance_threshold_at_block(future_block), + U512::zero(), + "Future block distance_threshold should be 0" + ); }); } @@ -248,8 +273,11 @@ fn test_total_distance_threshold_initialization() { new_test_ext().execute_with(|| { // Initially, total distance_threshold should be as genesis distance_threshold let initial_work = U512::one(); - assert_eq!(QPow::get_total_work(), initial_work, - "Initial TotalWork should be 0"); + assert_eq!( + QPow::get_total_work(), + initial_work, + "Initial TotalWork should be 0" + ); // After the first btest_total_distance_threshold_increases_with_each_blocklock, TotalWork should equal block 1's distance_threshold run_to_block(1); @@ -257,8 +285,11 @@ fn test_total_distance_threshold_initialization() { let max_distance = QPow::get_max_distance(); let current_work = max_distance / block_1_distance_threshold; let total_work = QPow::get_total_work(); - assert_eq!(total_work, initial_work + current_work, - "TotalWork after block 1 should equal block 1's distance_threshold"); + assert_eq!( + total_work, + initial_work + current_work, + "TotalWork after block 1 should equal block 1's distance_threshold" + ); }); } @@ -274,8 +305,11 @@ fn test_total_distance_threshold_accumulation() { expected_total = expected_total.saturating_add(max_distance / block_distance_threshold); let stored_total = QPow::get_total_work(); - assert_eq!(stored_total, expected_total, - "TotalDifficulty after block {} should be the sum of all blocks' difficulties", i); + assert_eq!( + stored_total, expected_total, + "TotalDifficulty after block {} should be the sum of all blocks' difficulties", + i + ); } }); } @@ -288,11 +322,16 @@ fn test_total_distance_threshold_after_adjustment() { run_to_block(adjustment_period + 1); let max_distance = QPow::get_max_distance(); // Check if distance_threshold has changed - let initial_distance_threshold = U512::one().shl(::InitialDistanceThresholdExponent::get()); - let new_distance_threshold = QPow::get_distance_threshold_at_block((adjustment_period + 1) as u64); + let initial_distance_threshold = + U512::one().shl(::InitialDistanceThresholdExponent::get()); + let new_distance_threshold = + QPow::get_distance_threshold_at_block((adjustment_period + 1) as u64); // We assume distance_threshold may have changed - println!("Initial distance_threshold: {}, New distance_threshold: {}", initial_distance_threshold, new_distance_threshold); + println!( + "Initial distance_threshold: {}, New distance_threshold: {}", + initial_distance_threshold, new_distance_threshold + ); // Calculate expected cumulative distance_threshold let mut expected_total = U512::one(); @@ -303,8 +342,10 @@ fn test_total_distance_threshold_after_adjustment() { // Compare with stored value let stored_total = QPow::get_total_work(); - assert_eq!(stored_total, expected_total, - "TotalDifficulty should correctly account for distance_threshold changes"); + assert_eq!( + stored_total, expected_total, + "TotalDifficulty should correctly account for distance_threshold changes" + ); }); } @@ -317,19 +358,26 @@ fn test_total_distance_threshold_increases_with_each_block() { // Run to block 1 and check the increase run_to_block(1); let total_after_block_1 = QPow::get_total_work(); - assert!(total_after_block_1 > initial_total, - "TotalDifficulty should increase after a new block"); + assert!( + total_after_block_1 > initial_total, + "TotalDifficulty should increase after a new block" + ); // Run to block 2 and check the increase again run_to_block(2); let total_after_block_2 = QPow::get_total_work(); - assert!(total_after_block_2 > total_after_block_1, - "TotalDifficulty should increase after each new block"); + assert!( + total_after_block_2 > total_after_block_1, + "TotalDifficulty should increase after each new block" + ); let max_distance = QPow::get_max_distance(); // Verify that the increase matches the distance_threshold of block 2 let block_2_diff = total_after_block_2 - total_after_block_1; - assert_eq!(block_2_diff, max_distance / QPow::get_distance_threshold_at_block(2), - "TotalDifficulty increase should match the distance_threshold of the new block"); + assert_eq!( + block_2_diff, + max_distance / QPow::get_distance_threshold_at_block(2), + "TotalDifficulty increase should match the distance_threshold of the new block" + ); }); } @@ -345,16 +393,22 @@ fn test_integrated_verification_flow() { // Use a nonce that we know works for our tests let mut nonce = [0u8; 64]; - nonce[63] = 38; // This worked in your previous tests + nonce[63] = 38; // This worked in your previous tests // Make sure it's actually valid let distance = QPow::get_nonce_distance(header, nonce); - println!("Nonce distance: {}, Threshold: {}", distance, distance_threshold); + println!( + "Nonce distance: {}, Threshold: {}", + distance, distance_threshold + ); if distance > distance_threshold { println!("WARNING: Test nonce is not valid for current distance_threshold!"); // Either generate a valid nonce here or fail the test - assert!(distance <= distance_threshold, "Cannot proceed with invalid test nonce"); + assert!( + distance <= distance_threshold, + "Cannot proceed with invalid test nonce" + ); } // 1. First, simulate mining by submitting a nonce @@ -376,10 +430,10 @@ fn test_compute_pow_valid_nonce() { h[31] = 123; // For value 123 let mut m = [0u8; 32]; - m[31] = 5; // For value 5 + m[31] = 5; // For value 5 let mut n = [0u8; 64]; - n[63] = 17; // For value 17 + n[63] = 17; // For value 17 let mut nonce = [0u8; 64]; nonce[63] = 2; // For value 2 @@ -390,7 +444,7 @@ fn test_compute_pow_valid_nonce() { let manual_mod = mod_pow( &U512::from_big_endian(&m), &(U512::from_big_endian(&h) + U512::from_big_endian(&nonce)), - &U512::from_big_endian(&n) + &U512::from_big_endian(&n), ); let manual_hash = sha3_512(manual_mod); @@ -405,10 +459,10 @@ fn test_compute_pow_overflow_check() { let h = [0xfu8; 32]; let mut m = [0u8; 32]; - m[31] = 5; // For value 5 + m[31] = 5; // For value 5 let mut n = [0u8; 64]; - n[63] = 17; // For value 17 + n[63] = 17; // For value 17 let mut nonce = [0u8; 64]; nonce[63] = 2; // For value 2 @@ -419,7 +473,7 @@ fn test_compute_pow_overflow_check() { let manual_mod = mod_pow( &U512::from_big_endian(&m), &(U512::from_big_endian(&h) + U512::from_big_endian(&nonce)), - &U512::from_big_endian(&n) + &U512::from_big_endian(&n), ); let manual_hash = sha3_512(manual_mod); @@ -691,7 +745,6 @@ fn pack_u512_to_f64(value: U512) -> f64 { highest_64_bits as f64 } - #[test] fn test_calculate_distance_threshold_stability_over_time() { new_test_ext().execute_with(|| { @@ -730,7 +783,10 @@ fn test_median_block_time_empty_history() { // When history is empty, we should get TargetBlockTime let target_block_time = ::TargetBlockTime::get(); let median = QPow::get_median_block_time(); - assert_eq!(median, target_block_time, "Empty history should return target block time"); + assert_eq!( + median, target_block_time, + "Empty history should return target block time" + ); }); } @@ -745,7 +801,10 @@ fn test_median_block_time_single_value() { // Median of a single value is that value let median = QPow::get_median_block_time(); - assert_eq!(median, block_time, "Median of a single value should be that value"); + assert_eq!( + median, block_time, + "Median of a single value should be that value" + ); }); } @@ -766,7 +825,10 @@ fn test_median_block_time_odd_count() { // Median of sorted values [1000, 2000, 3000, 4000, 5000] is 3000 let expected_median = 3000; let median = QPow::get_median_block_time(); - assert_eq!(median, expected_median, "Median of odd count should be the middle value"); + assert_eq!( + median, expected_median, + "Median of odd count should be the middle value" + ); }); } @@ -787,7 +849,10 @@ fn test_median_block_time_even_count() { // Median of sorted values [1000, 2000, 3000, 4000] is (2000 + 3000) / 2 = 2500 let expected_median = 2500; let median = QPow::get_median_block_time(); - assert_eq!(median, expected_median, "Median of even count should be average of two middle values"); + assert_eq!( + median, expected_median, + "Median of even count should be average of two middle values" + ); }); } @@ -808,7 +873,10 @@ fn test_median_block_time_with_duplicates() { // Median of sorted values [1000, 2000, 2000, 2000, 3000] is 2000 let expected_median = 2000; let median = QPow::get_median_block_time(); - assert_eq!(median, expected_median, "Median with duplicates should be correctly calculated"); + assert_eq!( + median, expected_median, + "Median with duplicates should be correctly calculated" + ); }); } @@ -842,7 +910,10 @@ fn test_median_block_time_ring_buffer() { // New median from [3000, 4000, 5000, 6000, 7000] let new_median = QPow::get_median_block_time(); - assert_eq!(new_median, 5000, "New median should be calculated from updated ring buffer"); + assert_eq!( + new_median, 5000, + "New median should be calculated from updated ring buffer" + ); }); } @@ -851,15 +922,20 @@ fn test_block_distance_threshold_storage_and_retrieval() { new_test_ext().execute_with(|| { // 1. Test that genesis block distance_threshold is properly set let genesis_distance_threshold = QPow::get_distance_threshold_at_block(0); - let initial_distance_threshold = U512::one().shl(::InitialDistanceThresholdExponent::get()); - assert_eq!(genesis_distance_threshold, initial_distance_threshold, - "Genesis block should have initial distance_threshold"); + let initial_distance_threshold = + U512::one().shl(::InitialDistanceThresholdExponent::get()); + assert_eq!( + genesis_distance_threshold, initial_distance_threshold, + "Genesis block should have initial distance_threshold" + ); // 2. Simulate block production and distance_threshold adjustment run_to_block(1); let block_1_distance_threshold = QPow::get_distance_threshold_at_block(1); - assert_eq!(block_1_distance_threshold, initial_distance_threshold, - "Block 1 should have same distance_threshold as initial"); + assert_eq!( + block_1_distance_threshold, initial_distance_threshold, + "Block 1 should have same distance_threshold as initial" + ); // 3. Simulate multiple blocks to trigger distance_threshold adjustment let adjustment_period = ::AdjustmentPeriod::get(); @@ -867,25 +943,25 @@ fn test_block_distance_threshold_storage_and_retrieval() { // 4. Check that distance_threshold for early blocks hasn't changed let block_1_distance_threshold_after = QPow::get_distance_threshold_at_block(1); - assert_eq!(block_1_distance_threshold_after, block_1_distance_threshold, - "Historical block distance_threshold should not change"); + assert_eq!( + block_1_distance_threshold_after, block_1_distance_threshold, + "Historical block distance_threshold should not change" + ); // 5. Test non-existent block (future block) let latest_block = System::block_number(); let future_block = latest_block + 1000; let future_distance_threshold = QPow::get_distance_threshold_at_block(future_block); - assert_eq!(future_distance_threshold, U512::zero(), - "Future block distance_threshold should return 0"); + assert_eq!( + future_distance_threshold, + U512::zero(), + "Future block distance_threshold should return 0" + ); }); } //////////// Support methods -pub fn hash_to_group( - h: &[u8; 32], - m: &[u8; 32], - n: &[u8; 64], - nonce: &[u8; 64] -) -> U512 { +pub fn hash_to_group(h: &[u8; 32], m: &[u8; 32], n: &[u8; 64], nonce: &[u8; 64]) -> U512 { let h = U512::from_big_endian(h); let m = U512::from_big_endian(m); let n = U512::from_big_endian(n); diff --git a/pallets/wormhole/src/lib.rs b/pallets/wormhole/src/lib.rs index 3d2f61d1..276066f6 100644 --- a/pallets/wormhole/src/lib.rs +++ b/pallets/wormhole/src/lib.rs @@ -10,21 +10,21 @@ mod tests; #[frame_support::pallet] pub mod pallet { + use codec::{Decode, Encode}; use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::pallet_prelude::*; - use sp_std::vec::Vec; - use codec::{Encode, Decode}; + use lazy_static::lazy_static; + use pallet_balances::{Config as BalancesConfig, Pallet as BalancesPallet}; use plonky2::{ + field::{goldilocks_field::GoldilocksField, types::PrimeField64}, plonk::{ + circuit_data::{CommonCircuitData, VerifierCircuitData}, config::{GenericConfig, PoseidonGoldilocksConfig}, proof::ProofWithPublicInputs, - circuit_data::{VerifierCircuitData, CommonCircuitData}, }, - field::{goldilocks_field::GoldilocksField, types::PrimeField64}, - util::serialization::DefaultGateSerializer + util::serialization::DefaultGateSerializer, }; - use lazy_static::lazy_static; - use pallet_balances::{Pallet as BalancesPallet, Config as BalancesConfig}; + use sp_std::vec::Vec; #[pallet::pallet] pub struct Pallet(_); @@ -64,7 +64,8 @@ pub mod pallet { impl WormholePublicInputs { // Convert from a vector of GoldilocksField elements pub fn from_fields(fields: &[GoldilocksField]) -> Result> { - if fields.len() < 16 { // Ensure we have enough fields + if fields.len() < 16 { + // Ensure we have enough fields return Err(Error::::InvalidPublicInputs); } @@ -75,12 +76,14 @@ pub mod pallet { // First 8 fields (64 bytes) are the nullifier for i in 0..8 { - nullifier[i*8..(i+1)*8].copy_from_slice(&fields[i].to_canonical_u64().to_le_bytes()); + nullifier[i * 8..(i + 1) * 8] + .copy_from_slice(&fields[i].to_canonical_u64().to_le_bytes()); } // Next 4 fields (32 bytes) are the exit account for i in 0..4 { - account_bytes[i*8..(i+1)*8].copy_from_slice(&fields[i+8].to_canonical_u64().to_le_bytes()); + account_bytes[i * 8..(i + 1) * 8] + .copy_from_slice(&fields[i + 8].to_canonical_u64().to_le_bytes()); } // Next field is exit amount @@ -91,7 +94,8 @@ pub mod pallet { // Last 2 fields are storage root for i in 0..4 { - storage_root[i*8..(i+1)*8].copy_from_slice(&fields[i+14].to_canonical_u64().to_le_bytes()); + storage_root[i * 8..(i + 1) * 8] + .copy_from_slice(&fields[i + 14].to_canonical_u64().to_le_bytes()); } let exit_account = T::AccountId::decode(&mut &account_bytes[..]) @@ -123,7 +127,8 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn used_nullifiers)] - pub(super) type UsedNullifiers = StorageMap<_, Blake2_128Concat, [u8; 64], bool, ValueQuery>; + pub(super) type UsedNullifiers = + StorageMap<_, Blake2_128Concat, [u8; 64], bool, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -150,10 +155,7 @@ pub mod pallet { impl Pallet { #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::verify_wormhole_proof())] - pub fn verify_wormhole_proof( - origin: OriginFor, - proof_bytes: Vec, - ) -> DispatchResult { + pub fn verify_wormhole_proof(origin: OriginFor, proof_bytes: Vec) -> DispatchResult { ensure_none(origin)?; let proof = ProofWithPublicInputs::from_bytes(proof_bytes.clone(), &*CIRCUIT_DATA) @@ -169,30 +171,29 @@ pub mod pallet { // log::error!("{:?}", public_inputs.storage_root); // log::error!("{:?}", public_inputs.fee_amount); - // Verify nullifier hasn't been used - ensure!(!UsedNullifiers::::contains_key(public_inputs.nullifier), Error::::NullifierAlreadyUsed); + ensure!( + !UsedNullifiers::::contains_key(public_inputs.nullifier), + Error::::NullifierAlreadyUsed + ); - VERIFIER_DATA.verify(proof) - .map_err(|_e| { - // log::error!("Verification failed: {:?}", e.to_string()); - Error::::VerificationFailed - })?; + VERIFIER_DATA.verify(proof).map_err(|_e| { + // log::error!("Verification failed: {:?}", e.to_string()); + Error::::VerificationFailed + })?; // Mark nullifier as used UsedNullifiers::::insert(public_inputs.nullifier, true); // let exit_balance: <::Currency as Currency<::AccountId>>::Balance - let exit_balance: ::Balance - = public_inputs.exit_amount + let exit_balance: ::Balance = public_inputs + .exit_amount .try_into() .map_err(|_| "Conversion from u64 to Balance failed")?; // Mint new tokens to the exit account - let _ = BalancesPallet::::deposit_creating( - &public_inputs.exit_account, - exit_balance - ); + let _ = + BalancesPallet::::deposit_creating(&public_inputs.exit_account, exit_balance); // // Emit event Self::deposit_event(Event::ProofVerified { diff --git a/pallets/wormhole/src/mock.rs b/pallets/wormhole/src/mock.rs index 36b559f4..c374dbb4 100644 --- a/pallets/wormhole/src/mock.rs +++ b/pallets/wormhole/src/mock.rs @@ -13,7 +13,6 @@ // use pallet_balances; // use pallet_balances::AccountData; - // type Block = MockBlock; // type Balance = u64; @@ -84,7 +83,6 @@ // } // ); - // impl pallet_wormhole::Config for Test { // type RuntimeEvent = RuntimeEvent; // } diff --git a/pallets/wormhole/src/tests.rs b/pallets/wormhole/src/tests.rs index fa63de55..31b581d3 100644 --- a/pallets/wormhole/src/tests.rs +++ b/pallets/wormhole/src/tests.rs @@ -6,7 +6,6 @@ // use crate::{mock::*, Error, Event}; // use frame_support::{assert_noop, assert_ok}; - // // Helper function to generate proof and inputs for a given n // fn get_test_proof() -> Vec { // include_bytes! ("../proof.hex").to_vec() @@ -46,4 +45,4 @@ // }); // } -// } \ No newline at end of file +// } diff --git a/primitives/consensus/pow/src/lib.rs b/primitives/consensus/pow/src/lib.rs index f9747064..9baf5c9c 100644 --- a/primitives/consensus/pow/src/lib.rs +++ b/primitives/consensus/pow/src/lib.rs @@ -44,7 +44,6 @@ impl TotalDifficulty for sp_core::U512 { } } - impl TotalDifficulty for sp_core::U256 { fn increment(&mut self, other: Self) { let ret = self.saturating_add(other); @@ -60,17 +59,17 @@ impl TotalDifficulty for u128 { } sp_api::decl_runtime_apis! { - /// API necessary for timestamp-based difficulty adjustment algorithms. - pub trait TimestampApi { - /// Return the timestamp in the current block. - fn timestamp() -> Moment; - } + /// API necessary for timestamp-based difficulty adjustment algorithms. + pub trait TimestampApi { + /// Return the timestamp in the current block. + fn timestamp() -> Moment; + } - /// API for those chains that put their difficulty adjustment algorithm directly + /// API for those chains that put their difficulty adjustment algorithm directly /// onto runtime. Note that while putting difficulty adjustment algorithm to /// runtime is safe, putting the PoW algorithm on runtime is not. - pub trait DifficultyApi { - /// Return the target difficulty of the next block. - fn difficulty() -> Difficulty; - } + pub trait DifficultyApi { + /// Return the target difficulty of the next block. + fn difficulty() -> Difficulty; + } } diff --git a/primitives/consensus/qpow/src/lib.rs b/primitives/consensus/qpow/src/lib.rs index 3bf7ec3a..46c7e8ba 100644 --- a/primitives/consensus/qpow/src/lib.rs +++ b/primitives/consensus/qpow/src/lib.rs @@ -32,8 +32,8 @@ sp_api::decl_runtime_apis! { /// calculate distance header with nonce to with nonce fn get_nonce_distance( header: [u8; 32], // 256-bit header - nonce: [u8; 64], // 512-bit nonce - ) -> U512; + nonce: [u8; 64], // 512-bit nonce + ) -> U512; /// Get the max possible reorg depth fn get_max_reorg_depth() -> u32; @@ -83,4 +83,4 @@ pub enum Error { ArithmeticError, /// Other error occurred Other(Vec), -} \ No newline at end of file +} diff --git a/primitives/crypto/wormhole/src/lib.rs b/primitives/crypto/wormhole/src/lib.rs index b1803780..79ac0a41 100644 --- a/primitives/crypto/wormhole/src/lib.rs +++ b/primitives/crypto/wormhole/src/lib.rs @@ -16,8 +16,8 @@ //! //! The hashing strategy ensures determinism while hiding the original secret. -use sp_core::{Hasher, H256}; use poseidon_resonance::PoseidonHasher; +use sp_core::{Hasher, H256}; /// Salt used when deriving wormhole addresses. pub const ADDRESS_SALT: [u8; 8] = *b"wormhole"; @@ -49,7 +49,8 @@ impl WormholePair { use rand::RngCore; let mut random_bytes = [0u8; 32]; - OsRng.try_fill_bytes(&mut random_bytes) + OsRng + .try_fill_bytes(&mut random_bytes) .map_err(|_| WormholeError::InvalidSecretFormat)?; let secret = PoseidonHasher::hash(&random_bytes); @@ -65,12 +66,11 @@ impl WormholePair { /// /// # Returns /// `Ok(true)` if the address matches the derived one, `Ok(false)` otherwise. - pub fn verify(address: &H256, secret: &[u8;32]) -> Result { + pub fn verify(address: &H256, secret: &[u8; 32]) -> Result { let generated_address = Self::generate_pair_from_secret(secret).address; Ok(&generated_address == address) } - /// Verifies whether the given combined hash generates the specified wormhole address. /// /// # Arguments @@ -80,7 +80,10 @@ impl WormholePair { /// /// # Returns /// `Ok(true)` if the address matches the derived one, `Ok(false)` otherwise. - pub fn verify_with_combined_hash(address: &H256, combined_hash: &[u8; 32]) -> Result { + pub fn verify_with_combined_hash( + address: &H256, + combined_hash: &[u8; 32], + ) -> Result { let generated = PoseidonHasher::hash(combined_hash); Ok(&generated == address) } @@ -89,8 +92,7 @@ impl WormholePair { /// /// This function performs a secondary Poseidon hash over the salt + hashed secret /// to derive the wormhole address. - fn generate_pair_from_secret(secret: &[u8;32]) -> WormholePair { - + fn generate_pair_from_secret(secret: &[u8; 32]) -> WormholePair { let mut combined = Vec::with_capacity(ADDRESS_SALT.len() + secret.as_ref().len()); combined.extend_from_slice(&ADDRESS_SALT); combined.extend_from_slice(secret.as_ref()); @@ -275,4 +277,4 @@ mod tests { // Assert assert_ne!(pair_with_salt.address, address_with_different_salt); } -} \ No newline at end of file +} diff --git a/qpow-math/src/lib.rs b/qpow-math/src/lib.rs index c214f45c..4248c8e8 100644 --- a/qpow-math/src/lib.rs +++ b/qpow-math/src/lib.rs @@ -14,10 +14,7 @@ pub fn is_valid_nonce(header: [u8; 32], nonce: [u8; 64], threshold: U512) -> boo } let distance = get_nonce_distance(header, nonce); - log::debug!("difficulty = {}, threshold = {}", - distance, - threshold - ); + log::debug!("difficulty = {}, threshold = {}", distance, threshold); distance <= threshold } @@ -38,7 +35,7 @@ pub fn get_nonce_distance( // Compare PoW results let nonce_element = hash_to_group_bigint_sha(&header_int, &m, &n, &nonce_int); - + target.bitxor(nonce_element) } @@ -81,7 +78,6 @@ pub fn hash_to_group_bigint_sha(h: &U512, m: &U512, n: &U512, solution: &U512) - sha3_512(result) } - // no split chunks by Nik pub fn hash_to_group_bigint(h: &U512, m: &U512, n: &U512, solution: &U512) -> U512 { // Compute sum = h + solution @@ -89,7 +85,6 @@ pub fn hash_to_group_bigint(h: &U512, m: &U512, n: &U512, solution: &U512) -> U5 //log::info!("ComputePoW: h={:?}, m={:?}, n={:?}, solution={:?}, sum={:?}", h, m, n, solution, sum); // Compute m^sum mod n using modular exponentiation - mod_pow(m, &sum, n) } @@ -198,4 +193,4 @@ pub fn sha3_512(input: U512) -> U512 { sha3.update(bytes); let output = U512::from_big_endian(sha3.finalize().as_slice()); output -} \ No newline at end of file +} diff --git a/runtime/build.rs b/runtime/build.rs index caac8518..138ae658 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -1,13 +1,13 @@ #[cfg(all(feature = "std", feature = "metadata-hash"))] fn main() { - substrate_wasm_builder::WasmBuilder::init_with_defaults() - .enable_metadata_hash("UNIT", 12) - .build(); + substrate_wasm_builder::WasmBuilder::init_with_defaults() + .enable_metadata_hash("UNIT", 12) + .build(); } #[cfg(all(feature = "std", not(feature = "metadata-hash")))] fn main() { - substrate_wasm_builder::WasmBuilder::build_using_defaults(); + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } /// The wasm builder is deactivated when compiling diff --git a/runtime/src/apis.rs b/runtime/src/apis.rs index e879fbaf..aa317c6b 100644 --- a/runtime/src/apis.rs +++ b/runtime/src/apis.rs @@ -26,316 +26,319 @@ // External crates imports use alloc::vec::Vec; use frame_support::{ - genesis_builder_helper::{build_state, get_preset}, - weights::Weight, + genesis_builder_helper::{build_state, get_preset}, + weights::Weight, }; use primitive_types::U512; use sp_api::impl_runtime_apis; use sp_core::OpaqueMetadata; use sp_runtime::{ - traits::Block as BlockT, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, }; use sp_version::RuntimeVersion; // Local module imports -use super::{AccountId, Balance, Block, Executive, InherentDataExt, Nonce, Runtime, RuntimeCall, RuntimeGenesisConfig, System, TransactionPayment, VERSION}; +use super::{ + AccountId, Balance, Block, Executive, InherentDataExt, Nonce, Runtime, RuntimeCall, + RuntimeGenesisConfig, System, TransactionPayment, VERSION, +}; impl_runtime_apis! { - impl sp_api::Core for Runtime { - fn version() -> RuntimeVersion { - VERSION - } - - fn execute_block(block: Block) { - Executive::execute_block(block); - } - - fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { - Executive::initialize_block(header) - } - } - - impl sp_api::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - OpaqueMetadata::new(Runtime::metadata().into()) - } - - fn metadata_at_version(version: u32) -> Option { - Runtime::metadata_at_version(version) - } - - fn metadata_versions() -> Vec { - Runtime::metadata_versions() - } - } - - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { - - Executive::apply_extrinsic(extrinsic) - } - - fn finalize_block() -> ::Header { - Executive::finalize_block() - } - - fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { - data.create_extrinsics() - } - - fn check_inherents( - block: Block, - data: sp_inherents::InherentData, - ) -> sp_inherents::CheckInherentsResult { - data.check_extrinsics(&block) - } - } - - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - source: TransactionSource, - tx: ::Extrinsic, - block_hash: ::Hash, - ) -> TransactionValidity { - Executive::validate_transaction(source, tx, block_hash) - } - } - - impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &::Header) { - Executive::offchain_worker(header) - } - } - - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(_seed: Option>) -> Vec { - Vec::new() - } - - //TODO - we don't have session keys now, but it looks like we would have to redefine Session trait to have them. - fn decode_session_keys( - _encoded: Vec, - ) -> Option, sp_core::crypto::KeyTypeId)>> { - None - } - } - - impl sp_consensus_qpow::QPoWApi for Runtime { - - fn verify_for_import(header: [u8; 32], nonce: [u8; 64]) -> bool { - pallet_qpow::Pallet::::verify_for_import(header, nonce) - } - - fn verify_historical_block(header: [u8; 32], nonce: [u8; 64], block_number: u32) -> bool { - // Convert u32 to the appropriate BlockNumber type used by your runtime - let block_number_param = block_number; - pallet_qpow::Pallet::::verify_historical_block(header, nonce, block_number_param) - } - - fn submit_nonce(header: [u8; 32], nonce: [u8; 64]) -> bool { - pallet_qpow::Pallet::::submit_nonce(header, nonce) - } - - fn get_max_reorg_depth() -> u32 { - pallet_qpow::Pallet::::get_max_reorg_depth() - } - - fn get_difficulty() -> U512 { - pallet_qpow::Pallet::::get_difficulty() - } - - fn get_distance_threshold() -> U512 { - pallet_qpow::Pallet::::get_distance_threshold() - } - - fn get_distance_threshold_at_block(block_number: u32) -> U512 { - // Convert u32 to the appropriate BlockNumber type used by your runtime - let block_number_param = block_number; - pallet_qpow::Pallet::::get_distance_threshold_at_block(block_number_param) - } - - fn get_total_work() -> U512 { - pallet_qpow::Pallet::::get_total_work() - } - - fn get_block_time_sum() -> u64 { - pallet_qpow::Pallet::::get_block_time_sum() - } - - fn get_median_block_time() -> u64 { - pallet_qpow::Pallet::::get_median_block_time() - } - - fn get_last_block_time() -> u64 { - pallet_qpow::Pallet::::get_last_block_time() - } - - fn get_last_block_duration() -> u64 { - pallet_qpow::Pallet::::get_last_block_duration() - } - - fn get_latest_nonce() -> Option<[u8; 64]> { - >::get() - } - - fn get_chain_height() -> u32 { - frame_system::pallet::Pallet::::block_number() - } - - fn get_random_rsa(header: &[u8; 32]) -> (U512, U512) { - pallet_qpow::Pallet::::get_random_rsa(header) - } - - fn hash_to_group_bigint(h: &U512, m: &U512, n: &U512, solution: &U512) -> U512{ - pallet_qpow::Pallet::::hash_to_group_bigint(h,m,n,solution) - } - fn get_max_distance() -> U512 { - pallet_qpow::Pallet::::get_max_distance() - } - fn get_nonce_distance( - header: [u8; 32], - nonce: [u8; 64] - ) -> U512 { - pallet_qpow::Pallet::::get_nonce_distance(header, nonce) - } - } - - impl sp_faucet::FaucetApi for Runtime { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block); + } + + fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(_seed: Option>) -> Vec { + Vec::new() + } + + //TODO - we don't have session keys now, but it looks like we would have to redefine Session trait to have them. + fn decode_session_keys( + _encoded: Vec, + ) -> Option, sp_core::crypto::KeyTypeId)>> { + None + } + } + + impl sp_consensus_qpow::QPoWApi for Runtime { + + fn verify_for_import(header: [u8; 32], nonce: [u8; 64]) -> bool { + pallet_qpow::Pallet::::verify_for_import(header, nonce) + } + + fn verify_historical_block(header: [u8; 32], nonce: [u8; 64], block_number: u32) -> bool { + // Convert u32 to the appropriate BlockNumber type used by your runtime + let block_number_param = block_number; + pallet_qpow::Pallet::::verify_historical_block(header, nonce, block_number_param) + } + + fn submit_nonce(header: [u8; 32], nonce: [u8; 64]) -> bool { + pallet_qpow::Pallet::::submit_nonce(header, nonce) + } + + fn get_max_reorg_depth() -> u32 { + pallet_qpow::Pallet::::get_max_reorg_depth() + } + + fn get_difficulty() -> U512 { + pallet_qpow::Pallet::::get_difficulty() + } + + fn get_distance_threshold() -> U512 { + pallet_qpow::Pallet::::get_distance_threshold() + } + + fn get_distance_threshold_at_block(block_number: u32) -> U512 { + // Convert u32 to the appropriate BlockNumber type used by your runtime + let block_number_param = block_number; + pallet_qpow::Pallet::::get_distance_threshold_at_block(block_number_param) + } + + fn get_total_work() -> U512 { + pallet_qpow::Pallet::::get_total_work() + } + + fn get_block_time_sum() -> u64 { + pallet_qpow::Pallet::::get_block_time_sum() + } + + fn get_median_block_time() -> u64 { + pallet_qpow::Pallet::::get_median_block_time() + } + + fn get_last_block_time() -> u64 { + pallet_qpow::Pallet::::get_last_block_time() + } + + fn get_last_block_duration() -> u64 { + pallet_qpow::Pallet::::get_last_block_duration() + } + + fn get_latest_nonce() -> Option<[u8; 64]> { + >::get() + } + + fn get_chain_height() -> u32 { + frame_system::pallet::Pallet::::block_number() + } + + fn get_random_rsa(header: &[u8; 32]) -> (U512, U512) { + pallet_qpow::Pallet::::get_random_rsa(header) + } + + fn hash_to_group_bigint(h: &U512, m: &U512, n: &U512, solution: &U512) -> U512{ + pallet_qpow::Pallet::::hash_to_group_bigint(h,m,n,solution) + } + fn get_max_distance() -> U512 { + pallet_qpow::Pallet::::get_max_distance() + } + fn get_nonce_distance( + header: [u8; 32], + nonce: [u8; 64] + ) -> U512 { + pallet_qpow::Pallet::::get_nonce_distance(header, nonce) + } + } + + impl sp_faucet::FaucetApi for Runtime { fn account_balance(account: AccountId) -> (Balance, Balance) { let free = pallet_balances::Pallet::::free_balance(&account); let reserved = pallet_balances::Pallet::::reserved_balance(&account); (free, reserved) } - } - - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { - fn account_nonce(account: AccountId) -> Nonce { - System::account_nonce(account) - } - } - - impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { - fn query_info( - uxt: ::Extrinsic, - len: u32, - ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { - TransactionPayment::query_info(uxt, len) - } - fn query_fee_details( - uxt: ::Extrinsic, - len: u32, - ) -> pallet_transaction_payment::FeeDetails { - TransactionPayment::query_fee_details(uxt, len) - } - fn query_weight_to_fee(weight: Weight) -> Balance { - TransactionPayment::weight_to_fee(weight) - } - fn query_length_to_fee(length: u32) -> Balance { - TransactionPayment::length_to_fee(length) - } - } - - impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi - for Runtime - { - fn query_call_info( - call: RuntimeCall, - len: u32, - ) -> pallet_transaction_payment::RuntimeDispatchInfo { - TransactionPayment::query_call_info(call, len) - } - fn query_call_fee_details( - call: RuntimeCall, - len: u32, - ) -> pallet_transaction_payment::FeeDetails { - TransactionPayment::query_call_fee_details(call, len) - } - fn query_weight_to_fee(weight: Weight) -> Balance { - TransactionPayment::weight_to_fee(weight) - } - fn query_length_to_fee(length: u32) -> Balance { - TransactionPayment::length_to_fee(length) - } - } - - #[cfg(feature = "runtime-benchmarks")] - impl frame_benchmarking::Benchmark for Runtime { - fn benchmark_metadata(extra: bool) -> ( - Vec, - Vec, - ) { - use frame_benchmarking::{baseline, Benchmarking, BenchmarkList}; - use frame_support::traits::StorageInfoTrait; - use frame_system_benchmarking::Pallet as SystemBench; - use baseline::Pallet as BaselineBench; - use super::*; - - let mut list = Vec::::new(); - list_benchmarks!(list, extra); - - let storage_info = AllPalletsWithSystem::storage_info(); - - (list, storage_info) - } - - fn dispatch_benchmark( - config: frame_benchmarking::BenchmarkConfig - ) -> Result, alloc::string::String> { - use frame_benchmarking::{baseline, Benchmarking, BenchmarkBatch}; - use sp_storage::TrackedStorageKey; - use frame_system_benchmarking::Pallet as SystemBench; - use baseline::Pallet as BaselineBench; - use super::*; - - impl frame_system_benchmarking::Config for Runtime {} - impl baseline::Config for Runtime {} - - use frame_support::traits::WhitelistedStorageKeys; - let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); - - let mut batches = Vec::::new(); - let params = (&config, &whitelist); - add_benchmarks!(params, batches); - - Ok(batches) - } - } - - #[cfg(feature = "try-runtime")] - impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { - // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to - // have a backtrace here. If any of the pre/post migration checks fail, we shall stop - // right here and right now. - let weight = Executive::try_runtime_upgrade(checks).unwrap(); - (weight, super::configs::RuntimeBlockWeights::get().max_block) - } - - fn execute_block( - block: Block, - state_root_check: bool, - signature_check: bool, - select: frame_try_runtime::TryStateSelect - ) -> Weight { - // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to - // have a backtrace here. - Executive::try_execute_block(block, state_root_check, signature_check, select).expect("execute-block failed") - } - } - - impl sp_genesis_builder::GenesisBuilder for Runtime { - fn build_state(config: Vec) -> sp_genesis_builder::Result { - build_state::(config) - } - - fn get_preset(id: &Option) -> Option> { - get_preset::(id, crate::genesis_config_presets::get_preset) - } - - fn preset_names() -> Vec { - crate::genesis_config_presets::preset_names() - } - } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{baseline, Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + use super::*; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, alloc::string::String> { + use frame_benchmarking::{baseline, Benchmarking, BenchmarkBatch}; + use sp_storage::TrackedStorageKey; + use frame_system_benchmarking::Pallet as SystemBench; + use baseline::Pallet as BaselineBench; + use super::*; + + impl frame_system_benchmarking::Config for Runtime {} + impl baseline::Config for Runtime {} + + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. If any of the pre/post migration checks fail, we shall stop + // right here and right now. + let weight = Executive::try_runtime_upgrade(checks).unwrap(); + (weight, super::configs::RuntimeBlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + select: frame_try_runtime::TryStateSelect + ) -> Weight { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, signature_check, select).expect("execute-block failed") + } + } + + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn build_state(config: Vec) -> sp_genesis_builder::Result { + build_state::(config) + } + + fn get_preset(id: &Option) -> Option> { + get_preset::(id, crate::genesis_config_presets::get_preset) + } + + fn preset_names() -> Vec { + crate::genesis_config_presets::preset_names() + } + } } diff --git a/runtime/src/genesis_config_presets.rs b/runtime/src/genesis_config_presets.rs index dca58633..b8880cbe 100644 --- a/runtime/src/genesis_config_presets.rs +++ b/runtime/src/genesis_config_presets.rs @@ -17,7 +17,7 @@ use crate::{AccountId, BalancesConfig, RuntimeGenesisConfig, SudoConfig}; use alloc::{vec, vec::Vec}; -use dilithium_crypto::pair::{crystal_alice, dilithium_bob, crystal_charlie}; +use dilithium_crypto::pair::{crystal_alice, crystal_charlie, dilithium_bob}; use serde_json::Value; use sp_core::crypto::Ss58Codec; use sp_genesis_builder::{self, PresetId}; @@ -28,44 +28,40 @@ use sp_runtime::traits::IdentifyAccount; pub const LIVE_TESTNET_RUNTIME_PRESET: &str = "live_testnet"; fn test_root_account() -> AccountId { - account_from_ss58("5FktBKPnRkY5QvF2NmFNUNh55mJvBtgMth5QoBjFJ4E4BbFf") + account_from_ss58("5FktBKPnRkY5QvF2NmFNUNh55mJvBtgMth5QoBjFJ4E4BbFf") } fn dilithium_default_accounts() -> Vec { - vec![ - crystal_alice().into_account(), - dilithium_bob().into_account(), - crystal_charlie().into_account(), - ] + vec![ + crystal_alice().into_account(), + dilithium_bob().into_account(), + crystal_charlie().into_account(), + ] } // Returns the genesis config presets populated with given parameters. -fn genesis_template( - endowed_accounts: Vec, - root: AccountId, -) -> Value { - let config = RuntimeGenesisConfig { - balances: BalancesConfig { - balances: endowed_accounts - .iter() - .cloned() - .map(|k| (k, 1u128 << 60)) - .collect::>(), - }, - sudo: SudoConfig { key: Some(root.clone()) }, - ..Default::default() - }; +fn genesis_template(endowed_accounts: Vec, root: AccountId) -> Value { + let config = RuntimeGenesisConfig { + balances: BalancesConfig { + balances: endowed_accounts + .iter() + .cloned() + .map(|k| (k, 1u128 << 60)) + .collect::>(), + }, + sudo: SudoConfig { + key: Some(root.clone()), + }, + ..Default::default() + }; - serde_json::to_value(config).expect("Could not build genesis config.") + serde_json::to_value(config).expect("Could not build genesis config.") } /// Return the development genesis config. pub fn development_config_genesis() -> Value { - let mut endowed_accounts = vec![]; + let mut endowed_accounts = vec![]; endowed_accounts.extend(dilithium_default_accounts()); - genesis_template( - endowed_accounts, - crystal_alice().into_account() - ) + genesis_template(endowed_accounts, crystal_alice().into_account()) } /// Return the live testnet genesis config. @@ -73,48 +69,45 @@ pub fn development_config_genesis() -> Value { /// Endows only the specified test account and sets it as Sudo. pub fn live_testnet_config_genesis() -> Value { let endowed_accounts = vec![test_root_account()]; - log::info!("endowed account: {:?}", test_root_account().to_ss58check()); + log::info!("endowed account: {:?}", test_root_account().to_ss58check()); - genesis_template( - endowed_accounts, - test_root_account() - ) + genesis_template(endowed_accounts, test_root_account()) } /// Return the local genesis config preset. pub fn local_config_genesis() -> Value { - genesis_template( - AccountKeyring::iter() - .filter(|v| v != &AccountKeyring::One && v != &AccountKeyring::Two) - .map(|v| v.to_account_id()) - .collect::>(), - test_root_account() - ) + genesis_template( + AccountKeyring::iter() + .filter(|v| v != &AccountKeyring::One && v != &AccountKeyring::Two) + .map(|v| v.to_account_id()) + .collect::>(), + test_root_account(), + ) } /// Provides the JSON representation of predefined genesis config for given `id`. pub fn get_preset(id: &PresetId) -> Option> { - let patch = match id.as_ref() { - sp_genesis_builder::DEV_RUNTIME_PRESET => development_config_genesis(), - sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET => local_config_genesis(), + let patch = match id.as_ref() { + sp_genesis_builder::DEV_RUNTIME_PRESET => development_config_genesis(), + sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET => local_config_genesis(), LIVE_TESTNET_RUNTIME_PRESET => live_testnet_config_genesis(), - _ => return None, - }; - Some( - serde_json::to_string(&patch) - .expect("serialization to json is expected to work. qed.") - .into_bytes(), - ) + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) } fn account_from_ss58(ss58: &str) -> AccountId { - AccountId::from_ss58check(ss58).expect("Failed to decode SS58 address") + AccountId::from_ss58check(ss58).expect("Failed to decode SS58 address") } /// List of supported presets. pub fn preset_names() -> Vec { - vec![ - PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), - PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), + vec![ + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), + PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), PresetId::from(LIVE_TESTNET_RUNTIME_PRESET), - ] + ] } diff --git a/runtime/src/governance.rs b/runtime/src/governance.rs index 7732d549..f68935df 100644 --- a/runtime/src/governance.rs +++ b/runtime/src/governance.rs @@ -1,10 +1,16 @@ -use crate::{AccountId, Balance, Balances, BlockNumber, Runtime, RuntimeOrigin, DAYS, HOURS, MICRO_UNIT, UNIT}; +use crate::{ + AccountId, Balance, Balances, BlockNumber, Runtime, RuntimeOrigin, DAYS, HOURS, MICRO_UNIT, + UNIT, +}; use alloc::vec::Vec; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use frame_support::pallet_prelude::TypeInfo; #[cfg(feature = "runtime-benchmarks")] use frame_support::traits::Currency; -use frame_support::traits::{CallerTrait, Consideration, Footprint, ReservableCurrency, Get, EnsureOrigin, OriginTrait, EnsureOriginWithArg}; +use frame_support::traits::{ + CallerTrait, Consideration, EnsureOrigin, EnsureOriginWithArg, Footprint, Get, OriginTrait, + ReservableCurrency, +}; use pallet_ranked_collective::Rank; use sp_core::crypto::AccountId32; use sp_runtime::traits::{Convert, MaybeConvert}; @@ -51,7 +57,6 @@ impl Consideration for PreimageDeposit { Ok(()) } - ///We will have to finally focus on fees, so weight and benchamrks will be important. /// For now, it's AI implementation @@ -84,15 +89,15 @@ impl pallet_referenda::TracksInfo for CommunityTracksInfo 0, pallet_referenda::TrackInfo { name: "signed", - max_deciding: 5, // Allow several concurrent proposals - decision_deposit: 500 * UNIT, // Moderate deposit - prepare_period: 12 * HOURS, // Shorter preparation time - decision_period: 7 * DAYS, // 1 week voting period - confirm_period: 12 * HOURS, // 12 hours confirmation + max_deciding: 5, // Allow several concurrent proposals + decision_deposit: 500 * UNIT, // Moderate deposit + prepare_period: 12 * HOURS, // Shorter preparation time + decision_period: 7 * DAYS, // 1 week voting period + confirm_period: 12 * HOURS, // 12 hours confirmation min_enactment_period: 1 * DAYS, // 1 day until execution min_approval: pallet_referenda::Curve::LinearDecreasing { length: Perbill::from_percent(100), - floor: Perbill::from_percent(55), // Majority approval required + floor: Perbill::from_percent(55), // Majority approval required ceil: Perbill::from_percent(70), }, min_support: pallet_referenda::Curve::LinearDecreasing { @@ -102,27 +107,26 @@ impl pallet_referenda::TracksInfo for CommunityTracksInfo }, }, ), - // Track 1: Signaling Track (non-binding community opinions) // - For community sentiment and direction gathering ( 1, pallet_referenda::TrackInfo { name: "signaling", - max_deciding: 20, // High throughput for community proposals - decision_deposit: 100 * UNIT, // Low deposit requirement - prepare_period: 6 * HOURS, // Short preparation time - decision_period: 5 * DAYS, // Standard voting period - confirm_period: 3 * HOURS, // Minimal confirmation period - min_enactment_period: 1, // 1 Block - immediate "execution" (just for record-keeping) + max_deciding: 20, // High throughput for community proposals + decision_deposit: 100 * UNIT, // Low deposit requirement + prepare_period: 6 * HOURS, // Short preparation time + decision_period: 5 * DAYS, // Standard voting period + confirm_period: 3 * HOURS, // Minimal confirmation period + min_enactment_period: 1, // 1 Block - immediate "execution" (just for record-keeping) min_approval: pallet_referenda::Curve::LinearDecreasing { length: Perbill::from_percent(100), - floor: Perbill::from_percent(50), // Simple majority approval + floor: Perbill::from_percent(50), // Simple majority approval ceil: Perbill::from_percent(60), }, min_support: pallet_referenda::Curve::LinearDecreasing { length: Perbill::from_percent(100), - floor: Perbill::from_percent(1), // Very low support threshold + floor: Perbill::from_percent(1), // Very low support threshold ceil: Perbill::from_percent(10), }, }, @@ -131,7 +135,6 @@ impl pallet_referenda::TracksInfo for CommunityTracksInfo &TRACKS } - fn track_for(id: &Self::RuntimeOrigin) -> Result { // Check for system origins first if let Some(system_origin) = id.as_system_ref() { @@ -148,7 +151,6 @@ impl pallet_referenda::TracksInfo for CommunityTracksInfo Err(()) } - fn info(id: Self::Id) -> Option<&'static pallet_referenda::TrackInfo> { Self::tracks() .iter() @@ -168,7 +170,6 @@ impl pallet_referenda::TracksInfo for CommunityTracksInfo } } - pub struct TechCollectiveTracksInfo; impl pallet_referenda::TracksInfo for TechCollectiveTracksInfo { type Id = u16; @@ -182,16 +183,16 @@ impl pallet_referenda::TracksInfo for TechCollectiveTracks 0, pallet_referenda::TrackInfo { name: "root", - max_deciding: 1, // Only 1 referendum can be in deciding phase at a time - decision_deposit: 1000 * UNIT, // Highest deposit requirement to prevent spam - prepare_period: 1 * DAYS, // 1 day preparation before voting begins - decision_period: 5 * DAYS, // 5 days for community to vote - confirm_period: 2 * DAYS, // 2 days confirmation period once passing + max_deciding: 1, // Only 1 referendum can be in deciding phase at a time + decision_deposit: 1000 * UNIT, // Highest deposit requirement to prevent spam + prepare_period: 1 * DAYS, // 1 day preparation before voting begins + decision_period: 5 * DAYS, // 5 days for community to vote + confirm_period: 2 * DAYS, // 2 days confirmation period once passing min_enactment_period: 2 * DAYS, // 2 day between approval and execution min_approval: pallet_referenda::Curve::LinearDecreasing { length: Perbill::from_percent(100), - floor: Perbill::from_percent(75), // Minimum 75% approval at end - ceil: Perbill::from_percent(100), // Requires 100% approval at start + floor: Perbill::from_percent(75), // Minimum 75% approval at end + ceil: Perbill::from_percent(100), // Requires 100% approval at start }, min_support: pallet_referenda::Curve::LinearDecreasing { length: Perbill::from_percent(0), @@ -205,7 +206,6 @@ impl pallet_referenda::TracksInfo for TechCollectiveTracks &TRACKS } - fn track_for(id: &Self::RuntimeOrigin) -> Result { // Check for system origins first if let Some(system_origin) = id.as_system_ref() { @@ -223,7 +223,6 @@ impl pallet_referenda::TracksInfo for TechCollectiveTracks Err(()) } - fn info(id: Self::Id) -> Option<&'static pallet_referenda::TrackInfo> { Self::tracks() .iter() @@ -243,7 +242,6 @@ impl pallet_referenda::TracksInfo for TechCollectiveTracks } } - /// Converts a track ID to a minimum required rank for voting. /// Currently, all tracks require rank 0 as the minimum rank. /// In the future, this could be extended to support multiple ranks @@ -257,7 +255,7 @@ impl pallet_referenda::TracksInfo for TechCollectiveTracks pub struct MinRankOfClassConverter(PhantomData); impl> Convert for MinRankOfClassConverter { fn convert(_a: u16) -> u16 { - 0 // Currently, all tracks require rank 0 as the minimum rank + 0 // Currently, all tracks require rank 0 as the minimum rank } } @@ -271,7 +269,8 @@ impl> MaybeConvert for GlobalMaxMembers { pub struct RootOrMemberForCollectiveOriginImpl(PhantomData<(Runtime, I)>); -impl EnsureOrigin for RootOrMemberForCollectiveOriginImpl +impl EnsureOrigin + for RootOrMemberForCollectiveOriginImpl where Runtime: pallet_ranked_collective::Config + frame_system::Config, ::RuntimeOrigin: @@ -313,9 +312,10 @@ where pub type RootOrMemberForCollectiveOrigin = RootOrMemberForCollectiveOriginImpl; -pub struct RootOrMemberForTechReferendaOriginImpl(PhantomData<(Runtime, I)>); +pub struct RootOrMemberForTechReferendaOriginImpl(PhantomData<(Runtime, I)>); -impl EnsureOriginWithArg for RootOrMemberForTechReferendaOriginImpl +impl EnsureOriginWithArg + for RootOrMemberForTechReferendaOriginImpl where Runtime: frame_system::Config + pallet_ranked_collective::Config, ::RuntimeOrigin: @@ -324,13 +324,17 @@ where { type Success = Runtime::AccountId; - fn try_origin(o: Runtime::RuntimeOrigin, _: &crate::OriginCaller) -> Result { + fn try_origin( + o: Runtime::RuntimeOrigin, + _: &crate::OriginCaller, + ) -> Result { let pallets_origin = o.clone().into_caller(); if let crate::OriginCaller::system(frame_system::RawOrigin::Root) = pallets_origin { if let Ok(signer) = as EnsureOrigin< Runtime::RuntimeOrigin, - >>::try_origin(o.clone()) { + >>::try_origin(o.clone()) + { return Ok(signer); } } @@ -352,9 +356,10 @@ where #[cfg(feature = "runtime-benchmarks")] fn try_successful_origin(_arg: &crate::OriginCaller) -> Result { - Ok(frame_system::RawOrigin::::Signed( - AccountId32::new([0u8; 32]) - ).into()) + Ok( + frame_system::RawOrigin::::Signed(AccountId32::new([0u8; 32])) + .into(), + ) } } diff --git a/runtime/src/transaction_extensions.rs b/runtime/src/transaction_extensions.rs index 57cf085a..4c28c8a3 100644 --- a/runtime/src/transaction_extensions.rs +++ b/runtime/src/transaction_extensions.rs @@ -1,7 +1,7 @@ //! Custom signed extensions for the runtime. use crate::*; use codec::{Decode, Encode}; -use core::{marker::PhantomData}; +use core::marker::PhantomData; use frame_support::pallet_prelude::{InvalidTransaction, ValidTransaction}; use frame_support::traits::fungible::Inspect; use frame_support::traits::tokens::Preservation; @@ -154,10 +154,10 @@ impl #[cfg(test)] mod tests { + use super::*; use frame_support::pallet_prelude::{TransactionValidityError, UnknownTransaction}; use pallet_reversible_transfers::PendingTransfers; use sp_runtime::{traits::TxBaseImplication, AccountId32}; - use super::*; fn alice() -> AccountId { AccountId32::from([1; 32]) } @@ -227,8 +227,7 @@ mod tests { let origin = RuntimeOrigin::signed(alice()); // Test the prepare method - ext - .clone() + ext.clone() .prepare((), &origin, &call, &Default::default(), 0) .unwrap(); assert_eq!((), ()); @@ -270,8 +269,7 @@ mod tests { let origin = RuntimeOrigin::signed(charlie()); // Test the prepare method - ext - .clone() + ext.clone() .prepare((), &origin, &call, &Default::default(), 0) .unwrap(); diff --git a/runtime/tests/governance_engine.rs b/runtime/tests/governance_engine.rs index 026e2c61..6f808335 100644 --- a/runtime/tests/governance_engine.rs +++ b/runtime/tests/governance_engine.rs @@ -6,17 +6,15 @@ mod tests { use crate::common::{account_id, new_test_ext, run_to_block}; use codec::Encode; use frame_support::traits::{ConstU32, Currency, QueryPreimage}; - use frame_support::{ - assert_noop, assert_ok, traits::PreimageProvider, BoundedVec, - }; + use frame_support::{assert_noop, assert_ok, traits::PreimageProvider, BoundedVec}; use pallet_conviction_voting::AccountVote::Standard; use pallet_conviction_voting::Vote; use pallet_referenda::TracksInfo; + use poseidon_resonance::PoseidonHasher; use resonance_runtime::{ Balances, BlockNumber, ConvictionVoting, OriginCaller, Preimage, Referenda, Runtime, RuntimeCall, RuntimeOrigin, Scheduler, UNIT, }; - use poseidon_resonance::PoseidonHasher; use sp_runtime::traits::Hash; // Helper function to create simple test data diff --git a/runtime/tests/integration.rs b/runtime/tests/integration.rs index 415f3865..d2acf348 100644 --- a/runtime/tests/integration.rs +++ b/runtime/tests/integration.rs @@ -1,12 +1,8 @@ use codec::{Decode, Encode}; -use dilithium_crypto::{ - ResonanceSignatureWithPublic, ResonanceSignatureScheme, PUB_KEY_BYTES, -}; +use dilithium_crypto::{ResonanceSignatureScheme, ResonanceSignatureWithPublic, PUB_KEY_BYTES}; use sp_core::ByteArray; use sp_runtime::{ - generic::UncheckedExtrinsic,generic::Preamble, - traits::Verify, - AccountId32, MultiAddress, + generic::Preamble, generic::UncheckedExtrinsic, traits::Verify, AccountId32, MultiAddress, }; // Add this to bring as_slice and from_slice into scope // Placeholder types (replace with your actual runtime types) @@ -41,7 +37,7 @@ mod tests { // // Integration test for dilithium signatures - // Tests valid case and fail cases + // Tests valid case and fail cases // #[test] fn test_dilithium_extrinsic() { @@ -52,14 +48,20 @@ mod tests { let keypair = hdwallet::generate(Some(&entropy)).expect("Failed to generate keypair"); let pk_bytes: [u8; PUB_KEY_BYTES] = keypair.public.to_bytes(); - println!("Gen Public Key (hex): {:?}", format_hex_truncated(&pk_bytes)); + println!( + "Gen Public Key (hex): {:?}", + format_hex_truncated(&pk_bytes) + ); // Create and sign a payload let payload: RuntimeCall = 42; // Example call let msg = payload.encode(); let sig_bytes = keypair.sign(&msg, None, false).expect("Signing failed"); - println!("Gen Signature (hex): {:?}", format_hex_truncated(&sig_bytes)); + println!( + "Gen Signature (hex): {:?}", + format_hex_truncated(&sig_bytes) + ); let signature = ResonanceSignature::from_slice(&sig_bytes).expect("Signature length mismatch"); @@ -74,7 +76,10 @@ mod tests { println!("Payload AccountId: {:?}", &id); let signed_extra: SignedExtra = (); - let sig_with_public = ResonanceSignatureWithPublic::new(signature, ResonancePublic::from_slice(&pk_bytes).unwrap()); + let sig_with_public = ResonanceSignatureWithPublic::new( + signature, + ResonancePublic::from_slice(&pk_bytes).unwrap(), + ); let extrinsic = UncheckedExtrinsic::new_signed( payload, @@ -120,7 +125,10 @@ mod tests { let sig = sig_public.signature(); let sig_bytes = sig.as_slice(); println!("Decoded Signature: {:?}", format_hex_truncated(sig_bytes)); - println!("Decoded Public Key: {:?}", format_hex_truncated(sig_public.public().as_ref())); + println!( + "Decoded Public Key: {:?}", + format_hex_truncated(sig_public.public().as_ref()) + ); } _ => println!("Decoded Signature: --"), } @@ -171,7 +179,10 @@ mod tests { let signature_wrong_key = ResonanceSignature::try_from(&sig_bytes_wrong_key[..]) .expect("Signature length mismatch"); - let sig_with_public = ResonanceSignatureWithPublic::new(signature_wrong_key, ResonancePublic::from_slice(&pk_bytes).unwrap()); + let sig_with_public = ResonanceSignatureWithPublic::new( + signature_wrong_key, + ResonancePublic::from_slice(&pk_bytes).unwrap(), + ); // Create transaction with invalid signature let extrinsic = UncheckedExtrinsic::new_signed( @@ -226,7 +237,10 @@ mod tests { let id_2 = Address::Id(account_id_2); let signed_extra: SignedExtra = (); - let sig_with_public = ResonanceSignatureWithPublic::new(signature, ResonancePublic::from_slice(&pk_bytes).unwrap()); + let sig_with_public = ResonanceSignatureWithPublic::new( + signature, + ResonancePublic::from_slice(&pk_bytes).unwrap(), + ); // Create transaction with wrong account ID. let extrinsic = UncheckedExtrinsic::new_signed( @@ -277,7 +291,7 @@ mod tests { let sig_bytes = keypair.sign(&msg, None, false).expect("Signing failed"); let signature = ResonanceSignature::from_slice(&sig_bytes).expect("Signature length mismatch"); - + let account_id = PoseidonHasher::hash(&pk_bytes).0.into(); let id = Address::Id(account_id); let signed_extra: SignedExtra = (); @@ -285,7 +299,10 @@ mod tests { // Create transaction with wrong payload. Should fail. let wrong_payload: RuntimeCall = 40; - let sig_with_public = ResonanceSignatureWithPublic::new(signature, ResonancePublic::from_slice(&pk_bytes).unwrap()); + let sig_with_public = ResonanceSignatureWithPublic::new( + signature, + ResonancePublic::from_slice(&pk_bytes).unwrap(), + ); let extrinsic = UncheckedExtrinsic::new_signed( wrong_payload, @@ -319,7 +336,7 @@ mod tests { // // Integration test for sr25519 extrinsic - // Keep this as long as we still have these around. + // Keep this as long as we still have these around. // #[test] fn test_sr25519_extrinsic() { @@ -407,5 +424,4 @@ mod tests { _ => panic!("Decoded extrinsic has no signature"), } } - -} \ No newline at end of file +}