diff --git a/Cargo.lock b/Cargo.lock index 74fa334b..48f65395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5561,6 +5561,7 @@ name = "nssa" version = "0.1.0" dependencies = [ "amm_core", + "anyhow", "borsh", "env_logger", "hex", diff --git a/Cargo.toml b/Cargo.toml index 0e7d0cbc..8be7cb96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,6 +147,8 @@ warnings = "deny" [workspace.lints] clippy.all = { level = "deny", priority = -1 } + +# Pedantic clippy.pedantic = { level = "deny", priority = -1 } # Reason: documenting every function returning Result is too verbose and doesn't add much value when you have good error types. @@ -157,5 +159,92 @@ clippy.missing-panics-doc = "allow" clippy.similar-names = "allow" # Reason: this lint is too strict and hard to fix. clippy.too-many-lines = "allow" -# Reason: std hasher is fine for us in public functions +# Reason: std hasher is fine for us in public functions. clippy.implicit-hasher = "allow" + +# Restriction +clippy.restriction = { level = "deny", priority = -1 } + +# Reason: we deny the whole `restriction` group but we allow things that don't make sense for us. +# That way we can still benefit from new lints added to the `restriction` group without having to +# explicitly allow them. +# As a downside our contributors don't know if some lint was enabled intentionally or just no one +# else faced it before to allow it but we can handle this during code reviews. +clippy.blanket-clippy-restriction-lints = "allow" +# Reason: we can't avoid using unwrap for now. +clippy.unwrap-used = "allow" +# Reason: we can't avoid using expect for now. +clippy.expect-used = "allow" +# Reason: unreachable is good in many cases. +clippy.unreachable = "allow" +# Reason: this is ridiculous strict in our codebase and doesn't add any value. +clippy.single-call-fn = "allow" +# Reason: we use panic in some places and it's okay. +clippy.panic = "allow" +# Reason: shadowing is good most of the times. +clippy.shadow-reuse = "allow" +# Reason: implicit return is good. +clippy.implicit-return = "allow" +# Reason: std is fine for us, we don't need to use core. +clippy.std-instead-of-core = "allow" +# Reason: std is fine for us, we don't need to use alloc. +clippy.std-instead-of-alloc = "allow" +# Reason: default methods are good most of the time. +clippy.missing-trait-methods = "allow" +# Reason: this is too verbose and doesn't help much if you have rust analyzer. +clippy.pattern-type-mismatch = "allow" +# Reason: decreases readability. +clippy.assertions-on-result-states = "allow" +# Reason: documenting every assert is too verbose. +clippy.missing-assert-message = "allow" +# Reason: documenting private items is too verbose and doesn't add much value. +clippy.missing-docs-in-private-items = "allow" +# Reason: we use separated suffix style. +clippy.separated_literal_suffix = "allow" +# Reason: sometimes absolute paths are more readable. +clippy.absolute-paths = "allow" +# Reason: sometimes it's as readable as full variable naming. +clippy.min-ident-chars = "allow" +# Reason: it's very common and handy. +clippy.indexing-slicing = "allow" +# Reason: we use little endian style. +clippy.little-endian-bytes = "allow" +# Reason: we use this style of pub visibility. +clippy.pub-with-shorthand = "allow" +# Reason: question mark operator is very cool. +clippy.question-mark-used = "allow" +# Reason: it's fine to panic in tests and some functions where it makes sense. +clippy.panic-in-result-fn = "allow" +# Reason: we don't care that much about inlining and LTO should take care of it. +clippy.missing_inline_in_public_items = "allow" +# Reason: it's okay for us. +clippy.default-numeric-fallback = "allow" +# Reason: this is fine for us. +clippy.exhaustive-enums = "allow" +# Reason: this is fine for us. +clippy.exhaustive-structs = "allow" +# Reason: this helps readability when item is imported in other modules. +clippy.module-name-repetitions = "allow" +# Reason: mostly historical reasons, maybe we'll address this in future. +clippy.mod-module-files = "allow" +# Reason: named module files is our preferred way. +clippy.self-named-module-files = "allow" +# Reason: this is actually quite handy. +clippy.impl-trait-in-params = "allow" +# Reason: this is often useful. +clippy.use-debug = "allow" +# Reason: this is sometimes useful. +clippy.field-scoped-visibility-modifiers = "allow" +# Reason: `pub use` is good for re-exports and hiding unnecessary details. +clippy.pub-use = "allow" +# Reason: we prefer semicolons inside blocks. +clippy.semicolon-outside-block = "allow" +# Reason: we don't do it blindly, this is mostly internal constraints checks. +clippy.unwrap-in-result = "allow" +# Reason: we don't see any problems with that. +clippy.shadow-same = "allow" +# Reason: this lint is too verbose. +clippy.let-underscore-untyped = "allow" +# Reason: this lint is actually bad as it forces to use wildcard `..` instead of +# field-by-field `_` which may lead to subtle bugs when new fields are added to the struct. +clippy.unneeded-field-pattern = "allow" diff --git a/bedrock_client/src/lib.rs b/bedrock_client/src/lib.rs index 227cc47a..bf65bfbb 100644 --- a/bedrock_client/src/lib.rs +++ b/bedrock_client/src/lib.rs @@ -2,7 +2,7 @@ use std::time::Duration; use anyhow::{Context as _, Result}; use common::config::BasicAuth; -use futures::{Stream, TryFutureExt}; +use futures::{Stream, TryFutureExt as _}; #[expect(clippy::single_component_path_imports, reason = "Satisfy machete")] use humantime_serde; use log::{info, warn}; diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..aa90e0d6 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,54 @@ +module-item-order-groupings = [ + [ + "use", + [ + "use", + ], + ], + [ + "modules", + [ + "extern_crate", + "mod", + "foreign_mod", + ], + ], + [ + "macros", + [ + "macro", + ], + ], + [ + "global_asm", + [ + "global_asm", + ], + ], + [ + "UPPER_SNAKE_CASE", + [ + "static", + "const", + ], + ], + [ + "PascalCase", + [ + "ty_alias", + "enum", + "struct", + "union", + "trait", + "trait_alias", + "impl", + ], + ], + [ + "lower_snake_case", + [ + "fn", + ], + ], +] +source-item-ordering = ["module"] diff --git a/common/src/block.rs b/common/src/block.rs index e5a58aed..3731a6c2 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -1,7 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use nssa::AccountId; use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256, digest::FixedOutput}; +use sha2::{Digest as _, Sha256, digest::FixedOutput as _}; use crate::{HashType, transaction::NSSATransaction}; @@ -20,7 +20,7 @@ pub struct BlockMeta { #[derive(Debug, Clone)] /// Our own hasher. /// Currently it is SHA256 hasher wrapper. May change in a future. -pub struct OwnHasher {} +pub struct OwnHasher; impl OwnHasher { fn hash(data: &[u8]) -> HashType { @@ -130,7 +130,7 @@ mod tests { use crate::{HashType, block::HashableBlockData, test_utils}; #[test] - fn test_encoding_roundtrip() { + fn encoding_roundtrip() { let transactions = vec![test_utils::produce_dummy_empty_transaction()]; let block = test_utils::produce_dummy_block(1, Some(HashType([1; 32])), transactions); let hashable = HashableBlockData::from(block); diff --git a/common/src/config.rs b/common/src/config.rs index 537c35bd..94d00ff5 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -42,7 +42,7 @@ impl FromStr for BasicAuth { })?; Ok(Self { - username: username.to_string(), + username: username.to_owned(), password: password.map(std::string::ToString::to_string), }) } diff --git a/common/src/error.rs b/common/src/error.rs index 3301bc87..bc037a60 100644 --- a/common/src/error.rs +++ b/common/src/error.rs @@ -28,8 +28,8 @@ impl From for SequencerClientError { #[derive(Debug, thiserror::Error)] pub enum ExecutionFailureKind { - #[error("Failed to get account data from sequencer")] - SequencerError, + #[error("Failed to get data from sequencer")] + SequencerError(#[source] anyhow::Error), #[error("Inputs amounts does not match outputs")] AmountMismatchError, #[error("Accounts key not found")] diff --git a/common/src/lib.rs b/common/src/lib.rs index 972aeadc..11c450f6 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -46,7 +46,7 @@ impl FromStr for HashType { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { - let mut bytes = [0u8; 32]; + let mut bytes = [0_u8; 32]; hex::decode_to_slice(s, &mut bytes)?; Ok(HashType(bytes)) } @@ -90,7 +90,7 @@ mod tests { #[test] fn serialization_roundtrip() { - let original = HashType([1u8; 32]); + let original = HashType([1_u8; 32]); let serialized = original.to_string(); let deserialized = HashType::from_str(&serialized).unwrap(); assert_eq!(original, deserialized); diff --git a/common/src/rpc_primitives/errors.rs b/common/src/rpc_primitives/errors.rs index 1c376a53..03f7fc16 100644 --- a/common/src/rpc_primitives/errors.rs +++ b/common/src/rpc_primitives/errors.rs @@ -5,10 +5,10 @@ use serde_json::{Value, to_value}; #[derive(serde::Serialize)] pub struct RpcParseError(pub String); -#[allow(clippy::too_long_first_doc_paragraph)] -/// This struct may be returned from JSON RPC server in case of error +/// This struct may be returned from JSON RPC server in case of error. +/// /// It is expected that that this struct has impls From<_> all other RPC errors -/// like [`RpcBlockError`](crate::types::blocks::RpcBlockError) +/// like [`RpcBlockError`](crate::types::blocks::RpcBlockError). #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)] #[serde(deny_unknown_fields)] pub struct RpcError { @@ -167,6 +167,7 @@ impl From for RpcError { impl From for RpcError { fn from(_: std::convert::Infallible) -> Self { + // SAFETY: Infallible error can never be constructed, so this code can never be reached. unsafe { core::hint::unreachable_unchecked() } } } diff --git a/common/src/rpc_primitives/message.rs b/common/src/rpc_primitives/message.rs index 5092dbc9..80948149 100644 --- a/common/src/rpc_primitives/message.rs +++ b/common/src/rpc_primitives/message.rs @@ -13,12 +13,14 @@ use std::fmt::{Formatter, Result as FmtResult}; use serde::{ de::{Deserializer, Error, Unexpected, Visitor}, - ser::{SerializeStruct, Serializer}, + ser::{SerializeStruct as _, Serializer}, }; use serde_json::{Result as JsonResult, Value}; use super::errors::RpcError; +pub type Parsed = Result; + #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct Version; @@ -29,9 +31,12 @@ impl serde::Serialize for Version { } impl<'de> serde::Deserialize<'de> for Version { + #[expect( + clippy::renamed_function_params, + reason = "More readable than original serde parameter names" + )] fn deserialize>(deserializer: D) -> Result { struct VersionVisitor; - #[allow(clippy::needless_lifetimes)] impl Visitor<'_> for VersionVisitor { type Value = Version; @@ -53,6 +58,10 @@ impl<'de> serde::Deserialize<'de> for Version { /// An RPC request. #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)] #[serde(deny_unknown_fields)] +#[expect( + clippy::partial_pub_fields, + reason = "We don't want to allow access to the version, but the others are public for ease of use" +)] pub struct Request { jsonrpc: Version, pub method: String, @@ -99,6 +108,10 @@ impl Request { /// A response to an RPC. /// /// It is created by the methods on [Request](struct.Request.html). +#[expect( + clippy::partial_pub_fields, + reason = "We don't want to allow access to the version, but the others are public for ease of use" +)] #[derive(Debug, Clone, PartialEq)] pub struct Response { jsonrpc: Version, @@ -110,30 +123,22 @@ impl serde::Serialize for Response { fn serialize(&self, serializer: S) -> Result { let mut sub = serializer.serialize_struct("Response", 3)?; sub.serialize_field("jsonrpc", &self.jsonrpc)?; - match self.result { - Ok(ref value) => sub.serialize_field("result", value), - Err(ref err) => sub.serialize_field("error", err), + match &self.result { + Ok(value) => sub.serialize_field("result", value), + Err(err) => sub.serialize_field("error", err), }?; sub.serialize_field("id", &self.id)?; sub.end() } } -/// Deserializer for `Option` that produces `Some(Value::Null)`. -/// -/// The usual one produces None in that case. But we need to know the difference between -/// `{x: null}` and `{}`. -fn some_value<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { - serde::Deserialize::deserialize(deserializer).map(Some) -} - /// A helper trick for deserialization. #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct WireResponse { // It is actually used to eat and sanity check the deserialized text - #[allow(dead_code)] - jsonrpc: Version, + #[serde(rename = "jsonrpc")] + _jsonrpc: Version, // Make sure we accept null as Some(Value::Null), instead of going to None #[serde(default, deserialize_with = "some_value")] result: Option, @@ -164,6 +169,10 @@ impl<'de> serde::Deserialize<'de> for Response { } /// A notification (doesn't expect an answer). +#[expect( + clippy::partial_pub_fields, + reason = "We don't want to allow access to the version, but the others are public for ease of use" +)] #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)] #[serde(deny_unknown_fields)] pub struct Notification { @@ -261,11 +270,24 @@ impl Message { pub fn id(&self) -> Value { match self { Message::Request(req) => req.id.clone(), - _ => Value::Null, + Message::Response(response) => response.id.clone(), + Message::Notification(_) | Message::Batch(_) | Message::UnmatchedSub(_) => Value::Null, } } } +impl From for String { + fn from(val: Message) -> Self { + ::serde_json::ser::to_string(&val).unwrap() + } +} + +impl From for Vec { + fn from(val: Message) -> Self { + ::serde_json::ser::to_vec(&val).unwrap() + } +} + /// A broken message. /// /// Protocol-level errors. @@ -286,11 +308,11 @@ impl Broken { /// with the right values. #[must_use] pub fn reply(&self) -> Message { - match *self { + match self { Broken::Unmatched(_) => Message::error(RpcError::parse_error( "JSON RPC Request format was expected".to_owned(), )), - Broken::SyntaxError(ref e) => Message::error(RpcError::parse_error(e.clone())), + Broken::SyntaxError(e) => Message::error(RpcError::parse_error(e.clone())), } } } @@ -312,8 +334,6 @@ pub fn decoded_to_parsed(res: JsonResult) -> Parsed { } } -pub type Parsed = Result; - /// Read a [Message](enum.Message.html) from a slice. /// /// Invalid JSON or JSONRPC messages are reported as [Broken](enum.Broken.html). @@ -328,16 +348,12 @@ pub fn from_str(s: &str) -> Parsed { from_slice(s.as_bytes()) } -impl From for String { - fn from(val: Message) -> Self { - ::serde_json::ser::to_string(&val).unwrap() - } -} - -impl From for Vec { - fn from(val: Message) -> Self { - ::serde_json::ser::to_vec(&val).unwrap() - } +/// Deserializer for `Option` that produces `Some(Value::Null)`. +/// +/// The usual one produces None in that case. But we need to know the difference between +/// `{x: null}` and `{}`. +fn some_value<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + serde::Deserialize::deserialize(deserializer).map(Some) } #[cfg(test)] @@ -475,7 +491,6 @@ mod tests { /// /// The reject is done by returning it as Unmatched. #[test] - #[allow(clippy::panic)] fn broken() { // A helper with one test fn one(input: &str) { @@ -499,7 +514,7 @@ mod tests { // Something completely different one(r#"{"x": [1, 2, 3]}"#); - match from_str(r"{]") { + match from_str("{]") { Err(Broken::SyntaxError(_)) => (), other => panic!("Something unexpected: {other:?}"), } @@ -510,7 +525,6 @@ mod tests { /// This doesn't have a full coverage, because there's not much to actually test there. /// Most of it is related to the ids. #[test] - #[allow(clippy::panic)] #[ignore = "Not a full coverage test"] fn constructors() { let msg1 = Message::request("call".to_owned(), json!([1, 2, 3])); @@ -528,9 +542,9 @@ mod tests { }; let id1 = req1.id.clone(); // When we answer a message, we get the same ID - if let Message::Response(ref resp) = req1.reply(json!([1, 2, 3])) { + if let Message::Response(resp) = req1.reply(json!([1, 2, 3])) { assert_eq!( - *resp, + resp, Response { jsonrpc: Version, result: Ok(json!([1, 2, 3])), @@ -542,11 +556,9 @@ mod tests { } let id2 = req2.id.clone(); // The same with an error - if let Message::Response(ref resp) = - req2.error(RpcError::new(42, "Wrong!".to_owned(), None)) - { + if let Message::Response(resp) = req2.error(RpcError::new(42, "Wrong!".to_owned(), None)) { assert_eq!( - *resp, + resp, Response { jsonrpc: Version, result: Err(RpcError::new(42, "Wrong!".to_owned(), None)), @@ -557,11 +569,11 @@ mod tests { panic!("Not a response"); } // When we have unmatched, we generate a top-level error with Null id. - if let Message::Response(ref resp) = + if let Message::Response(resp) = Message::error(RpcError::new(43, "Also wrong!".to_owned(), None)) { assert_eq!( - *resp, + resp, Response { jsonrpc: Version, result: Err(RpcError::new(43, "Also wrong!".to_owned(), None)), diff --git a/common/src/rpc_primitives/parser.rs b/common/src/rpc_primitives/parser.rs index 983e2f6c..933f8daf 100644 --- a/common/src/rpc_primitives/parser.rs +++ b/common/src/rpc_primitives/parser.rs @@ -3,6 +3,17 @@ use serde_json::Value; use super::errors::RpcParseError; +#[macro_export] +macro_rules! parse_request { + ($request_name:ty) => { + impl RpcRequest for $request_name { + fn parse(value: Option) -> Result { + parse_params::(value) + } + } + }; +} + pub trait RpcRequest: Sized { fn parse(value: Option) -> Result; } @@ -15,13 +26,3 @@ pub fn parse_params(value: Option) -> Result { - impl RpcRequest for $request_name { - fn parse(value: Option) -> Result { - parse_params::(value) - } - } - }; -} diff --git a/common/src/rpc_primitives/requests.rs b/common/src/rpc_primitives/requests.rs index 8c61ee32..2782bffa 100644 --- a/common/src/rpc_primitives/requests.rs +++ b/common/src/rpc_primitives/requests.rs @@ -11,8 +11,62 @@ use super::{ }; use crate::{HashType, parse_request}; +mod base64_deser { + use base64::{Engine as _, engine::general_purpose}; + use serde::{self, Deserialize, Deserializer, Serializer, ser::SerializeSeq as _}; + + pub mod vec { + use super::*; + + pub fn serialize(bytes_vec: &[Vec], serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(bytes_vec.len()))?; + for bytes in bytes_vec { + let s = general_purpose::STANDARD.encode(bytes); + seq.serialize_element(&s)?; + } + seq.end() + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let base64_strings: Vec = Deserialize::deserialize(deserializer)?; + base64_strings + .into_iter() + .map(|s| { + general_purpose::STANDARD + .decode(&s) + .map_err(serde::de::Error::custom) + }) + .collect() + } + } + + pub fn serialize(bytes: &[u8], serializer: S) -> Result + where + S: Serializer, + { + let base64_string = general_purpose::STANDARD.encode(bytes); + serializer.serialize_str(&base64_string) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let base64_string: String = Deserialize::deserialize(deserializer)?; + general_purpose::STANDARD + .decode(&base64_string) + .map_err(serde::de::Error::custom) + } +} + #[derive(Serialize, Deserialize, Debug)] -pub struct HelloRequest {} +pub struct HelloRequest; #[derive(Serialize, Deserialize, Debug)] pub struct RegisterAccountRequest { @@ -38,13 +92,13 @@ pub struct GetBlockRangeDataRequest { } #[derive(Serialize, Deserialize, Debug)] -pub struct GetGenesisIdRequest {} +pub struct GetGenesisIdRequest; #[derive(Serialize, Deserialize, Debug)] -pub struct GetLastBlockRequest {} +pub struct GetLastBlockRequest; #[derive(Serialize, Deserialize, Debug)] -pub struct GetInitialTestnetAccountsRequest {} +pub struct GetInitialTestnetAccountsRequest; #[derive(Serialize, Deserialize, Debug)] pub struct GetAccountBalanceRequest { @@ -72,7 +126,7 @@ pub struct GetProofForCommitmentRequest { } #[derive(Serialize, Deserialize, Debug)] -pub struct GetProgramIdsRequest {} +pub struct GetProgramIdsRequest; parse_request!(HelloRequest); parse_request!(RegisterAccountRequest); @@ -117,60 +171,6 @@ pub struct GetBlockRangeDataResponse { pub blocks: Vec>, } -mod base64_deser { - use base64::{Engine as _, engine::general_purpose}; - use serde::{self, Deserialize, Deserializer, Serializer, ser::SerializeSeq as _}; - - pub fn serialize(bytes: &[u8], serializer: S) -> Result - where - S: Serializer, - { - let base64_string = general_purpose::STANDARD.encode(bytes); - serializer.serialize_str(&base64_string) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let base64_string: String = Deserialize::deserialize(deserializer)?; - general_purpose::STANDARD - .decode(&base64_string) - .map_err(serde::de::Error::custom) - } - - pub mod vec { - use super::*; - - pub fn serialize(bytes_vec: &[Vec], serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(bytes_vec.len()))?; - for bytes in bytes_vec { - let s = general_purpose::STANDARD.encode(bytes); - seq.serialize_element(&s)?; - } - seq.end() - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> - where - D: Deserializer<'de>, - { - let base64_strings: Vec = Deserialize::deserialize(deserializer)?; - base64_strings - .into_iter() - .map(|s| { - general_purpose::STANDARD - .decode(&s) - .map_err(serde::de::Error::custom) - }) - .collect() - } - } -} - #[derive(Serialize, Deserialize, Debug)] pub struct GetGenesisIdResponse { pub genesis_id: u64, diff --git a/common/src/sequencer_client.rs b/common/src/sequencer_client.rs index dc6c8ca7..1efa0992 100644 --- a/common/src/sequencer_client.rs +++ b/common/src/sequencer_client.rs @@ -31,11 +31,12 @@ use crate::{ }; #[derive(Debug, Clone, Deserialize)] -#[allow(dead_code)] struct SequencerRpcResponse { - jsonrpc: String, + #[serde(rename = "jsonrpc")] + _jsonrpc: String, result: serde_json::Value, - id: u64, + #[serde(rename = "id")] + _id: u64, } #[derive(Clone)] @@ -69,7 +70,7 @@ impl SequencerClient { payload: Value, ) -> Result { let request = - rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload); + rpc_primitives::message::Request::from_payload_version_2_0(method.to_owned(), payload); log::debug!( "Calling method {method} with payload {request:?} to sequencer at {}", diff --git a/common/src/test_utils.rs b/common/src/test_utils.rs index 1d166ade..e78f823f 100644 --- a/common/src/test_utils.rs +++ b/common/src/test_utils.rs @@ -31,7 +31,7 @@ pub fn produce_dummy_block( let block_data = HashableBlockData { block_id: id, prev_block_hash: prev_hash.unwrap_or_default(), - timestamp: id * 100, + timestamp: id.saturating_mul(100), transactions, }; diff --git a/examples/program_deployment/methods/guest/src/bin/hello_world_with_move_function.rs b/examples/program_deployment/methods/guest/src/bin/hello_world_with_move_function.rs index e278a732..65f0f9cd 100644 --- a/examples/program_deployment/methods/guest/src/bin/hello_world_with_move_function.rs +++ b/examples/program_deployment/methods/guest/src/bin/hello_world_with_move_function.rs @@ -21,10 +21,11 @@ use nssa_core::{ // In case an input account is uninitialized, the program will claim it when // producing the post-state. -type Instruction = (u8, Vec); const WRITE_FUNCTION_ID: u8 = 0; const MOVE_DATA_FUNCTION_ID: u8 = 1; +type Instruction = (u8, Vec); + fn build_post_state(post_account: Account) -> AccountPostState { if post_account.program_owner == DEFAULT_PROGRAM_ID { // This produces a claim request diff --git a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs index 43839ba9..4371b000 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs @@ -1,3 +1,8 @@ +#![expect( + clippy::print_stdout, + reason = "This is an example program, it's fine to print to stdout" +)] + use nssa::{ AccountId, PublicTransaction, program::Program, diff --git a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs index 85bd19c6..84cb6068 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs @@ -19,10 +19,11 @@ use wallet::{PrivacyPreservingAccount, WalletCore}; // methods/guest/target/riscv32im-risc0-zkvm-elf/docker/hello_world_with_move_function.bin \ // write-public Ds8q5PjLcKwwV97Zi7duhRVF9uwA2PuYMoLL7FwCzsXE Hola -type Instruction = (u8, Vec); const WRITE_FUNCTION_ID: u8 = 0; const MOVE_DATA_FUNCTION_ID: u8 = 1; +type Instruction = (u8, Vec); + #[derive(Parser, Debug)] struct Cli { /// Path to program binary diff --git a/explorer_service/src/components/mod.rs b/explorer_service/src/components/mod.rs index a0032b10..306c79a8 100644 --- a/explorer_service/src/components/mod.rs +++ b/explorer_service/src/components/mod.rs @@ -1,7 +1,7 @@ -pub mod account_preview; -pub mod block_preview; -pub mod transaction_preview; - pub use account_preview::AccountPreview; pub use block_preview::BlockPreview; pub use transaction_preview::TransactionPreview; + +pub mod account_preview; +pub mod block_preview; +pub mod transaction_preview; diff --git a/explorer_service/src/format_utils.rs b/explorer_service/src/format_utils.rs index 07d7e8ec..393e6f8e 100644 --- a/explorer_service/src/format_utils.rs +++ b/explorer_service/src/format_utils.rs @@ -1,6 +1,11 @@ //! Formatting utilities for the explorer /// Format timestamp to human-readable string +#[expect( + clippy::integer_division, + clippy::integer_division_remainder_used, + reason = "We need to convert milliseconds to seconds, and this is the most straightforward way to do it" +)] pub fn format_timestamp(timestamp: u64) -> String { let seconds = timestamp / 1000; let datetime = chrono::DateTime::from_timestamp( diff --git a/explorer_service/src/lib.rs b/explorer_service/src/lib.rs index fadc164b..edfe7df5 100644 --- a/explorer_service/src/lib.rs +++ b/explorer_service/src/lib.rs @@ -1,5 +1,6 @@ #![expect( clippy::must_use_candidate, + clippy::same_name_method, reason = "Warns on code generated by leptos macros" )] diff --git a/explorer_service/src/main.rs b/explorer_service/src/main.rs index 357219ef..06ddb4d6 100644 --- a/explorer_service/src/main.rs +++ b/explorer_service/src/main.rs @@ -1,3 +1,7 @@ +#[expect( + clippy::print_stdout, + reason = "This is just simple and handy for such a small server" +)] #[cfg(feature = "ssr")] #[tokio::main] async fn main() { @@ -5,7 +9,7 @@ async fn main() { use clap::Parser; use explorer_service::App; use leptos::prelude::*; - use leptos_axum::{LeptosRoutes, generate_route_list}; + use leptos_axum::{LeptosRoutes as _, generate_route_list}; use leptos_meta::MetaTags; /// LEZ Block Explorer Server CLI arguments. diff --git a/explorer_service/src/pages/account_page.rs b/explorer_service/src/pages/account_page.rs index 5f95d8e0..a02a8b7c 100644 --- a/explorer_service/src/pages/account_page.rs +++ b/explorer_service/src/pages/account_page.rs @@ -10,11 +10,11 @@ use crate::{api, components::TransactionPreview}; #[component] pub fn AccountPage() -> impl IntoView { let params = use_params_map(); - let (tx_offset, set_tx_offset) = signal(0u64); + let (tx_offset, set_tx_offset) = signal(0_u64); let (all_transactions, set_all_transactions) = signal(Vec::new()); let (is_loading, set_is_loading) = signal(false); let (has_more, set_has_more) = signal(true); - let tx_limit = 10u64; + let tx_limit = 10_u64; // Parse account ID from URL params let account_id = move || { @@ -27,7 +27,7 @@ pub fn AccountPage() -> impl IntoView { match acc_id_opt { Some(acc_id) => api::get_account(acc_id).await, None => Err(leptos::prelude::ServerFnError::ServerError( - "Invalid account ID".to_string(), + "Invalid account ID".to_owned(), )), } }); @@ -37,7 +37,7 @@ pub fn AccountPage() -> impl IntoView { match acc_id_opt { Some(acc_id) => api::get_transactions_by_account(acc_id, 0, tx_limit).await, None => Err(leptos::prelude::ServerFnError::ServerError( - "Invalid account ID".to_string(), + "Invalid account ID".to_owned(), )), } }); @@ -59,7 +59,7 @@ pub fn AccountPage() -> impl IntoView { }; set_is_loading.set(true); - let current_offset = tx_offset.get() + tx_limit; + let current_offset = tx_offset.get().saturating_add(tx_limit); set_tx_offset.set(current_offset); leptos::task::spawn_local(async move { @@ -111,101 +111,99 @@ pub fn AccountPage() -> impl IntoView {
"Account ID:" {account_id_str} -
-
- "Balance:" - {balance_str} -
-
- "Program Owner:" - {program_id} -
-
- "Nonce:" - {nonce_str} -
-
- "Data:" - {format!("{data_len} bytes")} -
- - + +
+ "Balance:" + {balance_str} +
+
+ "Program Owner:" + {program_id} +
+
+ "Nonce:" + {nonce_str} +
+
+ "Data:" + {format!("{data_len} bytes")} +
+ + - } - }> - - {move || { - transactions_resource - .get() - .map(|result| match result { - Ok(_) => { - let txs = all_transactions.get(); - if txs.is_empty() { - view! { -
- "No transactions found" -
- } - .into_any() - } else { - view! { -
-
- {txs - .into_iter() - .map(|tx| { - view! { } - }) - .collect::>()} -
- {move || { - if has_more.get() { - view! { - - } - .into_any() - } else { - ().into_any() + } + }> + {move || { + transactions_resource + .get() + .map(|load_tx_result| match load_tx_result { + Ok(_) => { + let txs = all_transactions.get(); + if txs.is_empty() { + view! { +
+ "No transactions found" +
} - }} + .into_any() + } else { + view! { +
+
+ {txs + .into_iter() + .map(|tx| { + view! { } + }) + .collect::>()} +
+ {move || { + if has_more.get() { + view! { +
- } - .into_any() - } - } - Err(e) => { - view! { -
- {format!("Failed to load transactions: {e}")} -
- } - .into_any() - } - }) - }} + + } + .into_any() + } else { + ().into_any() + } + }} - -
- - } - .into_any() + + } + .into_any() + } + } + Err(e) => { + view! { +
+ {format!("Failed to load transactions: {e}")} +
+ } + .into_any() + } + }) + }} + + + + } + .into_any() } Err(e) => { view! { @@ -218,7 +216,6 @@ pub fn AccountPage() -> impl IntoView { } }) }} - } diff --git a/explorer_service/src/pages/block_page.rs b/explorer_service/src/pages/block_page.rs index d63d9f8e..8f54fe18 100644 --- a/explorer_service/src/pages/block_page.rs +++ b/explorer_service/src/pages/block_page.rs @@ -38,7 +38,7 @@ pub fn BlockPage() -> impl IntoView { Some(BlockIdOrHash::BlockId(id)) => api::get_block_by_id(id).await, Some(BlockIdOrHash::Hash(hash)) => api::get_block_by_hash(hash).await, None => Err(leptos::prelude::ServerFnError::ServerError( - "Invalid block ID or hash".to_string(), + "Invalid block ID or hash".to_owned(), )), } }, diff --git a/explorer_service/src/pages/mod.rs b/explorer_service/src/pages/mod.rs index f4220145..92885150 100644 --- a/explorer_service/src/pages/mod.rs +++ b/explorer_service/src/pages/mod.rs @@ -1,9 +1,9 @@ -pub mod account_page; -pub mod block_page; -pub mod main_page; -pub mod transaction_page; - pub use account_page::AccountPage; pub use block_page::BlockPage; pub use main_page::MainPage; pub use transaction_page::TransactionPage; + +pub mod account_page; +pub mod block_page; +pub mod main_page; +pub mod transaction_page; diff --git a/explorer_service/src/pages/transaction_page.rs b/explorer_service/src/pages/transaction_page.rs index 266f3f80..211dc505 100644 --- a/explorer_service/src/pages/transaction_page.rs +++ b/explorer_service/src/pages/transaction_page.rs @@ -4,7 +4,7 @@ use indexer_service_protocol::{ HashType, PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage, ProgramDeploymentTransaction, PublicMessage, PublicTransaction, Transaction, WitnessSet, }; -use itertools::{EitherOrBoth, Itertools}; +use itertools::{EitherOrBoth, Itertools as _}; use leptos::prelude::*; use leptos_router::{components::A, hooks::use_params_map}; @@ -17,16 +17,14 @@ pub fn TransactionPage() -> impl IntoView { let transaction_resource = Resource::new( move || { - params - .read() - .get("hash") - .and_then(|s| HashType::from_str(&s).ok()) + let s = params.read().get("hash")?; + HashType::from_str(&s).ok() }, |hash_opt| async move { match hash_opt { Some(hash) => api::get_transaction(hash).await, None => Err(leptos::prelude::ServerFnError::ServerError( - "Invalid transaction hash".to_string(), + "Invalid transaction hash".to_owned(), )), } }, @@ -141,7 +139,7 @@ pub fn TransactionPage() -> impl IntoView { {account_id_str} - " (nonce: "{"Not affected by this transaction".to_string()}" )" + " (nonce: "{"Not affected by this transaction".to_owned()}" )" } @@ -153,7 +151,7 @@ pub fn TransactionPage() -> impl IntoView { {"Account not found"} - " (nonce: "{"Account not found".to_string()}" )" + " (nonce: "{"Account not found".to_owned()}" )" } @@ -244,7 +242,7 @@ pub fn TransactionPage() -> impl IntoView { {account_id_str} - " (nonce: "{"Not affected by this transaction".to_string()}" )" + " (nonce: "{"Not affected by this transaction".to_owned()}" )" } @@ -256,7 +254,7 @@ pub fn TransactionPage() -> impl IntoView { {"Account not found"} - " (nonce: "{"Account not found".to_string()}" )" + " (nonce: "{"Account not found".to_owned()}" )" } diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index f2fb2ce9..8742184f 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -21,20 +21,16 @@ impl IndexerStore { /// ATTENTION: Will overwrite genesis block. pub fn open_db_with_genesis( location: &Path, - start_data: Option<(Block, V02State)>, + genesis_block: &Block, + initial_state: &V02State, ) -> Result { - let dbio = RocksDBIO::open_or_create(location, start_data)?; + let dbio = RocksDBIO::open_or_create(location, genesis_block, initial_state)?; Ok(Self { dbio: Arc::new(dbio), }) } - /// Reopening existing database - pub fn open_db_restart(location: &Path) -> Result { - Self::open_db_with_genesis(location, None) - } - pub fn last_observed_l1_lib_header(&self) -> Result> { Ok(self .dbio @@ -120,6 +116,6 @@ impl IndexerStore { // to represent correct block finality block.bedrock_status = BedrockStatus::Finalized; - Ok(self.dbio.put_block(block, l1_header.into())?) + Ok(self.dbio.put_block(&block, l1_header.into())?) } } diff --git a/indexer/core/src/lib.rs b/indexer/core/src/lib.rs index 38fb5989..a6e4409b 100644 --- a/indexer/core/src/lib.rs +++ b/indexer/core/src/lib.rs @@ -52,7 +52,7 @@ impl IndexerCore { // ToDo: remove key from indexer config, use some default. let signing_key = nssa::PrivateKey::try_new(config.signing_key).unwrap(); let channel_genesis_msg_id = [0; 32]; - let start_block = hashable_data.into_pending_block(&signing_key, channel_genesis_msg_id); + let genesis_block = hashable_data.into_pending_block(&signing_key, channel_genesis_msg_id); // This is a troubling moment, because changes in key protocol can // affect this. And indexer can not reliably ask this data from sequencer @@ -94,7 +94,7 @@ impl IndexerCore { config.bedrock_client_config.auth.clone(), )?, config, - store: IndexerStore::open_db_with_genesis(&home, Some((start_block, state)))?, + store: IndexerStore::open_db_with_genesis(&home, &genesis_block, &state)?, }) } @@ -102,9 +102,9 @@ impl IndexerCore { async_stream::stream! { info!("Searching for initial header"); - let last_l1_lib_header = self.store.last_observed_l1_lib_header()?; + let last_stored_l1_lib_header = self.store.last_observed_l1_lib_header()?; - let mut prev_last_l1_lib_header = if let Some(last_l1_lib_header) = last_l1_lib_header { + let mut prev_last_l1_lib_header = if let Some(last_l1_lib_header) = last_stored_l1_lib_header { info!("Last l1 lib header found: {last_l1_lib_header}"); last_l1_lib_header } else { @@ -318,6 +318,10 @@ fn parse_block_owned( decoded_channel_id: &ChannelId, ) -> (Vec, HeaderId) { ( + #[expect( + clippy::wildcard_enum_match_arm, + reason = "We are only interested in channel inscription ops, so it's fine to ignore the rest" + )] l1_block .transactions() .flat_map(|tx| { diff --git a/indexer/service/protocol/src/lib.rs b/indexer/service/protocol/src/lib.rs index 3a47e095..42c513a2 100644 --- a/indexer/service/protocol/src/lib.rs +++ b/indexer/service/protocol/src/lib.rs @@ -14,6 +14,40 @@ use serde_with::{DeserializeFromStr, SerializeDisplay}; #[cfg(feature = "convert")] mod convert; +mod base64 { + use base64::prelude::{BASE64_STANDARD, Engine as _}; + use serde::{Deserialize as _, Deserializer, Serialize as _, Serializer}; + + pub mod arr { + use super::{Deserializer, Serializer}; + + pub fn serialize(v: &[u8], s: S) -> Result { + super::serialize(v, s) + } + + pub fn deserialize<'de, const N: usize, D: Deserializer<'de>>( + d: D, + ) -> Result<[u8; N], D::Error> { + let vec = super::deserialize(d)?; + vec.try_into().map_err(|_bytes| { + serde::de::Error::custom(format!("Invalid length, expected {N} bytes")) + }) + } + } + + pub fn serialize(v: &[u8], s: S) -> Result { + let base64 = BASE64_STANDARD.encode(v); + String::serialize(&base64, s) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { + let base64 = String::deserialize(d)?; + BASE64_STANDARD + .decode(base64.as_bytes()) + .map_err(serde::de::Error::custom) + } +} + pub type Nonce = u128; #[derive( @@ -23,24 +57,41 @@ pub struct ProgramId(pub [u32; 8]); impl Display for ProgramId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let bytes: Vec = self.0.iter().flat_map(|n| n.to_be_bytes()).collect(); + let bytes: Vec = self.0.iter().flat_map(|n| n.to_le_bytes()).collect(); write!(f, "{}", bytes.to_base58()) } } +#[derive(Debug)] +pub enum ProgramIdParseError { + InvalidBase58(base58::FromBase58Error), + InvalidLength(usize), +} + +impl Display for ProgramIdParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProgramIdParseError::InvalidBase58(err) => write!(f, "invalid base58: {err:?}"), + ProgramIdParseError::InvalidLength(len) => { + write!(f, "invalid length: expected 32 bytes, got {len}") + } + } + } +} + impl FromStr for ProgramId { - type Err = hex::FromHexError; + type Err = ProgramIdParseError; fn from_str(s: &str) -> Result { let bytes = s .from_base58() - .map_err(|_| hex::FromHexError::InvalidStringLength)?; + .map_err(ProgramIdParseError::InvalidBase58)?; if bytes.len() != 32 { - return Err(hex::FromHexError::InvalidStringLength); + return Err(ProgramIdParseError::InvalidLength(bytes.len())); } - let mut arr = [0u32; 8]; + let mut arr = [0_u32; 8]; for (i, chunk) in bytes.chunks_exact(4).enumerate() { - arr[i] = u32::from_be_bytes(chunk.try_into().unwrap()); + arr[i] = u32::from_le_bytes(chunk.try_into().unwrap()); } Ok(ProgramId(arr)) } @@ -72,7 +123,7 @@ impl FromStr for AccountId { bytes.len() )); } - let mut value = [0u8; 32]; + let mut value = [0_u8; 32]; value.copy_from_slice(&bytes); Ok(AccountId { value }) } @@ -121,7 +172,7 @@ impl FromStr for Signature { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { - let mut bytes = [0u8; 64]; + let mut bytes = [0_u8; 64]; hex::decode_to_slice(s, &mut bytes)?; Ok(Signature(bytes)) } @@ -141,6 +192,7 @@ pub enum Transaction { impl Transaction { /// Get the hash of the transaction + #[expect(clippy::same_name_method, reason = "This is handy")] #[must_use] pub fn hash(&self) -> &self::HashType { match self { @@ -284,7 +336,7 @@ impl FromStr for HashType { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { - let mut bytes = [0u8; 32]; + let mut bytes = [0_u8; 32]; hex::decode_to_slice(s, &mut bytes)?; Ok(HashType(bytes)) } @@ -303,37 +355,3 @@ pub enum BedrockStatus { Safe, Finalized, } - -mod base64 { - use base64::prelude::{BASE64_STANDARD, Engine as _}; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - pub mod arr { - use super::{Deserializer, Serializer}; - - pub fn serialize(v: &[u8], s: S) -> Result { - super::serialize(v, s) - } - - pub fn deserialize<'de, const N: usize, D: Deserializer<'de>>( - d: D, - ) -> Result<[u8; N], D::Error> { - let vec = super::deserialize(d)?; - vec.try_into().map_err(|_| { - serde::de::Error::custom(format!("Invalid length, expected {N} bytes")) - }) - } - } - - pub fn serialize(v: &[u8], s: S) -> Result { - let base64 = BASE64_STANDARD.encode(v); - String::serialize(&base64, s) - } - - pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { - let base64 = String::deserialize(d)?; - BASE64_STANDARD - .decode(base64.as_bytes()) - .map_err(serde::de::Error::custom) - } -} diff --git a/indexer/service/src/main.rs b/indexer/service/src/main.rs index e66fe401..b34777b4 100644 --- a/indexer/service/src/main.rs +++ b/indexer/service/src/main.rs @@ -15,6 +15,10 @@ struct Args { } #[tokio::main] +#[expect( + clippy::integer_division_remainder_used, + reason = "Generated by select! macro, can't be easily rewritten to avoid this lint" +)] async fn main() -> Result<()> { env_logger::init(); diff --git a/indexer/service/src/mock_service.rs b/indexer/service/src/mock_service.rs index ac052a18..bc131740 100644 --- a/indexer/service/src/mock_service.rs +++ b/indexer/service/src/mock_service.rs @@ -1,6 +1,9 @@ #![expect( + clippy::as_conversions, + clippy::arithmetic_side_effects, clippy::cast_possible_truncation, clippy::cast_lossless, + clippy::integer_division_remainder_used, reason = "Mock service uses intentional casts and format patterns for test data generation" )] use std::collections::HashMap; @@ -31,7 +34,7 @@ impl MockIndexerService { // Create some mock accounts let account_ids: Vec = (0..5) .map(|i| { - let mut value = [0u8; 32]; + let mut value = [0_u8; 32]; value[0] = i; AccountId { value } }) @@ -50,11 +53,11 @@ impl MockIndexerService { } // Create 100 blocks with transactions - let mut prev_hash = HashType([0u8; 32]); + let mut prev_hash = HashType([0_u8; 32]); for block_id in 1..=100 { let block_hash = { - let mut hash = [0u8; 32]; + let mut hash = [0_u8; 32]; hash[0] = block_id as u8; hash[1] = 0xff; HashType(hash) @@ -67,7 +70,7 @@ impl MockIndexerService { for tx_idx in 0..num_txs { let tx_hash = { - let mut hash = [0u8; 32]; + let mut hash = [0_u8; 32]; hash[0] = block_id as u8; hash[1] = tx_idx as u8; HashType(hash) @@ -79,7 +82,7 @@ impl MockIndexerService { 0 | 1 => Transaction::Public(PublicTransaction { hash: tx_hash, message: PublicMessage { - program_id: ProgramId([1u32; 8]), + program_id: ProgramId([1_u32; 8]), account_ids: vec![ account_ids[tx_idx as usize % account_ids.len()], account_ids[(tx_idx as usize + 1) % account_ids.len()], @@ -101,7 +104,7 @@ impl MockIndexerService { ], nonces: vec![block_id as u128], public_post_states: vec![Account { - program_owner: ProgramId([1u32; 8]), + program_owner: ProgramId([1_u32; 8]), balance: 500, data: Data(vec![0xdd, 0xee]), nonce: block_id as u128, @@ -143,7 +146,7 @@ impl MockIndexerService { prev_block_hash: prev_hash, hash: block_hash, timestamp: 1_704_067_200_000 + (block_id * 12_000), // ~12 seconds per block - signature: Signature([0u8; 64]), + signature: Signature([0_u8; 64]), }, body: BlockBody { transactions: block_transactions, @@ -191,7 +194,7 @@ impl indexer_service_rpc::RpcServer for MockIndexerService { .last() .map(|bl| bl.header.block_id) .ok_or_else(|| { - ErrorObjectOwned::owned(-32001, "Last block not found".to_string(), None::<()>) + ErrorObjectOwned::owned(-32001, "Last block not found".to_owned(), None::<()>) }) } diff --git a/indexer/service/src/service.rs b/indexer/service/src/service.rs index 838e1403..85d4f32f 100644 --- a/indexer/service/src/service.rs +++ b/indexer/service/src/service.rs @@ -154,8 +154,10 @@ impl SubscriptionService { pub async fn add_subscription(&self, subscription: Subscription) -> Result<()> { let guard = self.parts.load(); - if let Err(err) = guard.new_subscription_sender.send(subscription) { - error!("Failed to send new subscription to subscription service with error: {err:#?}"); + if let Err(send_err) = guard.new_subscription_sender.send(subscription) { + error!( + "Failed to send new subscription to subscription service with error: {send_err:#?}" + ); // Respawn the subscription service loop if it has finished (either with error or panic) if guard.handle.is_finished() { @@ -177,7 +179,7 @@ impl SubscriptionService { } } - bail!(err) + bail!(send_err) } Ok(()) @@ -192,6 +194,10 @@ impl SubscriptionService { let mut block_stream = pin!(indexer.subscribe_parse_block_stream()); + #[expect( + clippy::integer_division_remainder_used, + reason = "Generated by select! macro, can't be easily rewritten to avoid this lint" + )] loop { tokio::select! { sub = sub_receiver.recv() => { @@ -289,7 +295,7 @@ pub fn not_yet_implemented_error() -> ErrorObjectOwned { fn db_error(err: anyhow::Error) -> ErrorObjectOwned { ErrorObjectOwned::owned( ErrorCode::InternalError.code(), - "DBError".to_string(), + "DBError".to_owned(), Some(format!("{err:#?}")), ) } diff --git a/integration_tests/src/config.rs b/integration_tests/src/config.rs index 062fa442..e20179eb 100644 --- a/integration_tests/src/config.rs +++ b/integration_tests/src/config.rs @@ -1,6 +1,6 @@ use std::{net::SocketAddr, path::PathBuf, time::Duration}; -use anyhow::{Context, Result}; +use anyhow::{Context as _, Result}; use bytesize::ByteSize; use common::block::{AccountInitialData, CommitmentsInitialData}; use indexer_service::{BackoffConfig, ChannelId, ClientConfig, IndexerConfig}; @@ -13,30 +13,6 @@ use wallet::config::{ InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, WalletConfig, }; -pub fn indexer_config( - bedrock_addr: SocketAddr, - home: PathBuf, - initial_data: &InitialData, -) -> Result { - Ok(IndexerConfig { - home, - consensus_info_polling_interval: Duration::from_secs(1), - bedrock_client_config: ClientConfig { - addr: addr_to_url(UrlProtocol::Http, bedrock_addr) - .context("Failed to convert bedrock addr to URL")?, - auth: None, - backoff: BackoffConfig { - start_delay: Duration::from_millis(100), - max_retries: 10, - }, - }, - initial_accounts: initial_data.sequencer_initial_accounts(), - initial_commitments: initial_data.sequencer_initial_commitments(), - signing_key: [37; 32], - channel_id: bedrock_channel_id(), - }) -} - /// Sequencer config options available for custom changes in integration tests. #[derive(Debug, Clone, Copy)] pub struct SequencerPartialConfig { @@ -57,66 +33,6 @@ impl Default for SequencerPartialConfig { } } -pub fn sequencer_config( - partial: SequencerPartialConfig, - home: PathBuf, - bedrock_addr: SocketAddr, - indexer_addr: SocketAddr, - initial_data: &InitialData, -) -> Result { - let SequencerPartialConfig { - max_num_tx_in_block, - max_block_size, - mempool_max_size, - block_create_timeout, - } = partial; - - Ok(SequencerConfig { - home, - override_rust_log: None, - genesis_id: 1, - is_genesis_random: true, - max_num_tx_in_block, - max_block_size, - mempool_max_size, - block_create_timeout, - retry_pending_blocks_timeout: Duration::from_secs(120), - port: 0, - initial_accounts: initial_data.sequencer_initial_accounts(), - initial_commitments: initial_data.sequencer_initial_commitments(), - signing_key: [37; 32], - bedrock_config: BedrockConfig { - backoff: BackoffConfig { - start_delay: Duration::from_millis(100), - max_retries: 5, - }, - channel_id: bedrock_channel_id(), - node_url: addr_to_url(UrlProtocol::Http, bedrock_addr) - .context("Failed to convert bedrock addr to URL")?, - auth: None, - }, - indexer_rpc_url: addr_to_url(UrlProtocol::Ws, indexer_addr) - .context("Failed to convert indexer addr to URL")?, - }) -} - -pub fn wallet_config( - sequencer_addr: SocketAddr, - initial_data: &InitialData, -) -> Result { - Ok(WalletConfig { - override_rust_log: None, - sequencer_addr: addr_to_url(UrlProtocol::Http, sequencer_addr) - .context("Failed to convert sequencer addr to URL")?, - seq_poll_timeout: Duration::from_secs(30), - seq_tx_poll_max_blocks: 15, - seq_poll_max_retries: 10, - seq_block_poll_max_amount: 100, - initial_accounts: initial_data.wallet_initial_accounts(), - basic_auth: None, - }) -} - pub struct InitialData { pub public_accounts: Vec<(PrivateKey, u128)>, pub private_accounts: Vec<(KeyChain, Account)>, @@ -223,11 +139,11 @@ impl InitialData { }) .chain(self.private_accounts.iter().map(|(key_chain, account)| { let account_id = AccountId::from(&key_chain.nullifer_public_key); - InitialAccountData::Private(InitialAccountDataPrivate { + InitialAccountData::Private(Box::new(InitialAccountDataPrivate { account_id, account: account.clone(), key_chain: key_chain.clone(), - }) + })) })) .collect() } @@ -248,6 +164,90 @@ impl std::fmt::Display for UrlProtocol { } } +pub fn indexer_config( + bedrock_addr: SocketAddr, + home: PathBuf, + initial_data: &InitialData, +) -> Result { + Ok(IndexerConfig { + home, + consensus_info_polling_interval: Duration::from_secs(1), + bedrock_client_config: ClientConfig { + addr: addr_to_url(UrlProtocol::Http, bedrock_addr) + .context("Failed to convert bedrock addr to URL")?, + auth: None, + backoff: BackoffConfig { + start_delay: Duration::from_millis(100), + max_retries: 10, + }, + }, + initial_accounts: initial_data.sequencer_initial_accounts(), + initial_commitments: initial_data.sequencer_initial_commitments(), + signing_key: [37; 32], + channel_id: bedrock_channel_id(), + }) +} + +pub fn sequencer_config( + partial: SequencerPartialConfig, + home: PathBuf, + bedrock_addr: SocketAddr, + indexer_addr: SocketAddr, + initial_data: &InitialData, +) -> Result { + let SequencerPartialConfig { + max_num_tx_in_block, + max_block_size, + mempool_max_size, + block_create_timeout, + } = partial; + + Ok(SequencerConfig { + home, + override_rust_log: None, + genesis_id: 1, + is_genesis_random: true, + max_num_tx_in_block, + max_block_size, + mempool_max_size, + block_create_timeout, + retry_pending_blocks_timeout: Duration::from_secs(120), + port: 0, + initial_accounts: initial_data.sequencer_initial_accounts(), + initial_commitments: initial_data.sequencer_initial_commitments(), + signing_key: [37; 32], + bedrock_config: BedrockConfig { + backoff: BackoffConfig { + start_delay: Duration::from_millis(100), + max_retries: 5, + }, + channel_id: bedrock_channel_id(), + node_url: addr_to_url(UrlProtocol::Http, bedrock_addr) + .context("Failed to convert bedrock addr to URL")?, + auth: None, + }, + indexer_rpc_url: addr_to_url(UrlProtocol::Ws, indexer_addr) + .context("Failed to convert indexer addr to URL")?, + }) +} + +pub fn wallet_config( + sequencer_addr: SocketAddr, + initial_data: &InitialData, +) -> Result { + Ok(WalletConfig { + override_rust_log: None, + sequencer_addr: addr_to_url(UrlProtocol::Http, sequencer_addr) + .context("Failed to convert sequencer addr to URL")?, + seq_poll_timeout: Duration::from_secs(30), + seq_tx_poll_max_blocks: 15, + seq_poll_max_retries: 10, + seq_block_poll_max_amount: 100, + initial_accounts: initial_data.wallet_initial_accounts(), + basic_auth: None, + }) +} + pub fn addr_to_url(protocol: UrlProtocol, addr: SocketAddr) -> Result { // Convert 0.0.0.0 to 127.0.0.1 for client connections // When binding to port 0, the server binds to 0.0.0.0: @@ -262,7 +262,7 @@ pub fn addr_to_url(protocol: UrlProtocol, addr: SocketAddr) -> Result { } fn bedrock_channel_id() -> ChannelId { - let channel_id: [u8; 32] = [0u8, 1] + let channel_id: [u8; 32] = [0_u8, 1] .repeat(16) .try_into() .unwrap_or_else(|_| unreachable!()); diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index b2263b5e..2492d939 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -2,15 +2,15 @@ use std::{net::SocketAddr, path::PathBuf, sync::LazyLock}; -use anyhow::{Context, Result, bail}; -use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; +use anyhow::{Context as _, Result, bail}; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; use common::{HashType, sequencer_client::SequencerClient, transaction::NSSATransaction}; use futures::FutureExt as _; use indexer_service::IndexerHandle; use log::{debug, error, warn}; use nssa::{AccountId, PrivacyPreservingTransaction}; use nssa_core::Commitment; -use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait}; +use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait as _}; use sequencer_runner::SequencerHandle; use tempfile::TempDir; use testcontainers::compose::DockerCompose; @@ -156,10 +156,12 @@ impl TestContext { } let mut port = None; - let mut attempt = 0; - let max_attempts = 5; + let mut attempt = 0_u32; + let max_attempts = 5_u32; while port.is_none() && attempt < max_attempts { - attempt += 1; + attempt = attempt + .checked_add(1) + .expect("We check that attempt < max_attempts, so this won't overflow"); match up_and_retrieve_port(&mut compose).await { Ok(p) => { port = Some(p); @@ -449,6 +451,10 @@ pub fn format_private_account_id(account_id: AccountId) -> String { format!("Private/{account_id}") } +#[expect( + clippy::wildcard_enum_match_arm, + reason = "We want the code to panic if the transaction type is not PrivacyPreserving" +)] pub async fn fetch_privacy_preserving_tx( seq_client: &SequencerClient, tx_hash: HashType, diff --git a/integration_tests/tests/account.rs b/integration_tests/tests/account.rs index 5a700707..02813b4c 100644 --- a/integration_tests/tests/account.rs +++ b/integration_tests/tests/account.rs @@ -1,3 +1,8 @@ +#![expect( + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + use anyhow::Result; use integration_tests::TestContext; use log::info; @@ -36,7 +41,7 @@ async fn get_existing_account() -> Result<()> { async fn new_public_account_with_label() -> Result<()> { let mut ctx = TestContext::new().await?; - let label = "my-test-public-account".to_string(); + let label = "my-test-public-account".to_owned(); let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None, label: Some(label.clone()), @@ -68,7 +73,7 @@ async fn new_public_account_with_label() -> Result<()> { async fn new_private_account_with_label() -> Result<()> { let mut ctx = TestContext::new().await?; - let label = "my-test-private-account".to_string(); + let label = "my-test-private-account".to_owned(); let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None, label: Some(label.clone()), diff --git a/integration_tests/tests/amm.rs b/integration_tests/tests/amm.rs index ecea91b3..bdb2da72 100644 --- a/integration_tests/tests/amm.rs +++ b/integration_tests/tests/amm.rs @@ -1,3 +1,9 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + use std::time::Duration; use anyhow::Result; @@ -108,7 +114,7 @@ async fn amm_public() -> Result<()> { let subcommand = TokenProgramAgnosticSubcommand::New { definition_account_id: format_public_account_id(definition_account_id_1), supply_account_id: format_public_account_id(supply_account_id_1), - name: "A NAM1".to_string(), + name: "A NAM1".to_owned(), total_supply: 37, }; wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; @@ -132,7 +138,7 @@ async fn amm_public() -> Result<()> { let subcommand = TokenProgramAgnosticSubcommand::New { definition_account_id: format_public_account_id(definition_account_id_2), supply_account_id: format_public_account_id(supply_account_id_2), - name: "A NAM2".to_string(), + name: "A NAM2".to_owned(), total_supply: 37, }; wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; diff --git a/integration_tests/tests/auth_transfer/main.rs b/integration_tests/tests/auth_transfer/main.rs index c97008bd..62b8b836 100644 --- a/integration_tests/tests/auth_transfer/main.rs +++ b/integration_tests/tests/auth_transfer/main.rs @@ -1,2 +1,8 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + mod private; mod public; diff --git a/integration_tests/tests/block_size_limit.rs b/integration_tests/tests/block_size_limit.rs index 47fabdc0..41c9fc76 100644 --- a/integration_tests/tests/block_size_limit.rs +++ b/integration_tests/tests/block_size_limit.rs @@ -1,3 +1,9 @@ +#![expect( + clippy::as_conversions, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + use std::time::Duration; use anyhow::Result; @@ -24,7 +30,7 @@ async fn reject_oversized_transaction() -> Result<()> { // Create a transaction that's definitely too large // Block size is 1 MiB (1,048,576 bytes), minus ~200 bytes for header = ~1,048,376 bytes max tx // Create a 1.1 MiB binary to ensure it exceeds the limit - let oversized_binary = vec![0u8; 1100 * 1024]; // 1.1 MiB binary + let oversized_binary = vec![0_u8; 1100 * 1024]; // 1.1 MiB binary let message = nssa::program_deployment_transaction::Message::new(oversized_binary); let tx = nssa::ProgramDeploymentTransaction::new(message); @@ -62,7 +68,7 @@ async fn accept_transaction_within_limit() -> Result<()> { .await?; // Create a small program deployment that should fit - let small_binary = vec![0u8; 1024]; // 1 KiB binary + let small_binary = vec![0_u8; 1024]; // 1 KiB binary let message = nssa::program_deployment_transaction::Message::new(small_binary); let tx = nssa::ProgramDeploymentTransaction::new(message); diff --git a/integration_tests/tests/config.rs b/integration_tests/tests/config.rs index 5443edc9..09105833 100644 --- a/integration_tests/tests/config.rs +++ b/integration_tests/tests/config.rs @@ -1,3 +1,9 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + use anyhow::Result; use integration_tests::TestContext; use log::info; @@ -12,8 +18,8 @@ async fn modify_config_field() -> Result<()> { // Change config field let command = Command::Config(ConfigSubcommand::Set { - key: "seq_poll_timeout".to_string(), - value: "1s".to_string(), + key: "seq_poll_timeout".to_owned(), + value: "1s".to_owned(), }); wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; @@ -22,7 +28,7 @@ async fn modify_config_field() -> Result<()> { // Return how it was at the beginning let command = Command::Config(ConfigSubcommand::Set { - key: "seq_poll_timeout".to_string(), + key: "seq_poll_timeout".to_owned(), value: format!("{old_seq_poll_timeout:?}"), }); wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; diff --git a/integration_tests/tests/indexer.rs b/integration_tests/tests/indexer.rs index f12ae600..55c6757d 100644 --- a/integration_tests/tests/indexer.rs +++ b/integration_tests/tests/indexer.rs @@ -1,7 +1,13 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + use std::time::Duration; -use anyhow::{Context, Result}; -use indexer_service_rpc::RpcClient; +use anyhow::{Context as _, Result}; +use indexer_service_rpc::RpcClient as _; use integration_tests::{ TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id, format_public_account_id, verify_commitment_is_in_state, diff --git a/integration_tests/tests/keys_restoration.rs b/integration_tests/tests/keys_restoration.rs index 8806fdab..f438ef70 100644 --- a/integration_tests/tests/keys_restoration.rs +++ b/integration_tests/tests/keys_restoration.rs @@ -1,6 +1,12 @@ -use std::{str::FromStr, time::Duration}; +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] -use anyhow::{Context, Result}; +use std::{str::FromStr as _, time::Duration}; + +use anyhow::{Context as _, Result}; use integration_tests::{ TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx, format_private_account_id, format_public_account_id, verify_commitment_is_in_state, diff --git a/integration_tests/tests/pinata.rs b/integration_tests/tests/pinata.rs index da5f13c3..38cfeac3 100644 --- a/integration_tests/tests/pinata.rs +++ b/integration_tests/tests/pinata.rs @@ -1,3 +1,9 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + use std::time::Duration; use anyhow::{Context as _, Result}; diff --git a/integration_tests/tests/program_deployment.rs b/integration_tests/tests/program_deployment.rs index 098083d2..76837576 100644 --- a/integration_tests/tests/program_deployment.rs +++ b/integration_tests/tests/program_deployment.rs @@ -1,3 +1,8 @@ +#![expect( + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + use std::{path::PathBuf, time::Duration}; use anyhow::Result; diff --git a/integration_tests/tests/token.rs b/integration_tests/tests/token.rs index 0ff6eee5..d3fbfdc1 100644 --- a/integration_tests/tests/token.rs +++ b/integration_tests/tests/token.rs @@ -1,3 +1,9 @@ +#![expect( + clippy::shadow_unrelated, + clippy::tests_outside_test_module, + reason = "We don't care about these in tests" +)] + use std::time::Duration; use anyhow::{Context as _, Result}; @@ -69,7 +75,7 @@ async fn create_and_transfer_public_token() -> Result<()> { }; // Create new token - let name = "A NAME".to_string(); + let name = "A NAME".to_owned(); let total_supply = 37; let subcommand = TokenProgramAgnosticSubcommand::New { definition_account_id: format_public_account_id(definition_account_id), @@ -317,7 +323,7 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> { }; // Create new token - let name = "A NAME".to_string(); + let name = "A NAME".to_owned(); let total_supply = 37; let subcommand = TokenProgramAgnosticSubcommand::New { definition_account_id: format_public_account_id(definition_account_id), @@ -475,7 +481,7 @@ async fn create_token_with_private_definition() -> Result<()> { }; // Create token with private definition - let name = "A NAME".to_string(); + let name = "A NAME".to_owned(); let total_supply = 37; let subcommand = TokenProgramAgnosticSubcommand::New { definition_account_id: format_private_account_id(definition_account_id), @@ -671,7 +677,7 @@ async fn create_token_with_private_definition_and_supply() -> Result<()> { }; // Create token with both private definition and supply - let name = "A NAME".to_string(); + let name = "A NAME".to_owned(); let total_supply = 37; let subcommand = TokenProgramAgnosticSubcommand::New { definition_account_id: format_private_account_id(definition_account_id), @@ -843,7 +849,7 @@ async fn shielded_token_transfer() -> Result<()> { }; // Create token - let name = "A NAME".to_string(); + let name = "A NAME".to_owned(); let total_supply = 37; let subcommand = TokenProgramAgnosticSubcommand::New { definition_account_id: format_public_account_id(definition_account_id), @@ -966,7 +972,7 @@ async fn deshielded_token_transfer() -> Result<()> { }; // Create token with private supply - let name = "A NAME".to_string(); + let name = "A NAME".to_owned(); let total_supply = 37; let subcommand = TokenProgramAgnosticSubcommand::New { definition_account_id: format_public_account_id(definition_account_id), @@ -1073,7 +1079,7 @@ async fn token_claiming_path_with_private_accounts() -> Result<()> { }; // Create token - let name = "A NAME".to_string(); + let name = "A NAME".to_owned(); let total_supply = 37; let subcommand = TokenProgramAgnosticSubcommand::New { definition_account_id: format_private_account_id(definition_account_id), diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index 9f7dd16b..a1e67fe5 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -1,3 +1,14 @@ +#![expect( + clippy::arithmetic_side_effects, + clippy::float_arithmetic, + clippy::missing_asserts_for_indexing, + clippy::as_conversions, + clippy::tests_outside_test_module, + clippy::integer_division, + clippy::integer_division_remainder_used, + reason = "We don't care about these in tests" +)] + use std::time::{Duration, Instant}; use anyhow::Result; @@ -21,6 +32,102 @@ use nssa_core::{ }; use tokio::test; +pub(crate) struct TpsTestManager { + public_keypairs: Vec<(PrivateKey, AccountId)>, + target_tps: u64, +} + +impl TpsTestManager { + /// Generates public account keypairs. These are used to populate the config and to generate + /// valid public transactions for the tps test. + pub(crate) fn new(target_tps: u64, number_transactions: usize) -> Self { + let public_keypairs = (1..(number_transactions + 2)) + .map(|i| { + let mut private_key_bytes = [0_u8; 32]; + private_key_bytes[..8].copy_from_slice(&i.to_le_bytes()); + let private_key = PrivateKey::try_new(private_key_bytes).unwrap(); + let public_key = PublicKey::new_from_private_key(&private_key); + let account_id = AccountId::from(&public_key); + (private_key, account_id) + }) + .collect(); + Self { + public_keypairs, + target_tps, + } + } + + #[expect( + clippy::cast_precision_loss, + reason = "This is just for testing purposes, we don't care about precision loss here" + )] + pub(crate) fn target_time(&self) -> Duration { + let number_transactions = (self.public_keypairs.len() - 1) as u64; + Duration::from_secs_f64(number_transactions as f64 / self.target_tps as f64) + } + + /// Build a batch of public transactions to submit to the node. + pub fn build_public_txs(&self) -> Vec { + // Create valid public transactions + let program = Program::authenticated_transfer_program(); + let public_txs: Vec = self + .public_keypairs + .windows(2) + .map(|pair| { + let amount: u128 = 1; + let message = putx::Message::try_new( + program.id(), + [pair[0].1, pair[1].1].to_vec(), + [0_u128].to_vec(), + amount, + ) + .unwrap(); + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[&pair[0].0]); + PublicTransaction::new(message, witness_set) + }) + .collect(); + + public_txs + } + + /// Generates a sequencer configuration with initial balance in a number of public accounts. + /// The transactions generated with the function `build_public_txs` will be valid in a node + /// started with the config from this method. + fn generate_initial_data(&self) -> InitialData { + // Create public public keypairs + let public_accounts = self + .public_keypairs + .iter() + .map(|(key, _)| (key.clone(), 10)) + .collect(); + + // Generate an initial commitment to be used with the privacy preserving transaction + // created with the `build_privacy_transaction` function. + let key_chain = KeyChain::new_os_random(); + let account = Account { + balance: 100, + nonce: 0xdead_beef, + program_owner: Program::authenticated_transfer_program().id(), + data: Data::default(), + }; + + InitialData { + public_accounts, + private_accounts: vec![(key_chain, account)], + } + } + + fn generate_sequencer_partial_config() -> SequencerPartialConfig { + SequencerPartialConfig { + max_num_tx_in_block: 300, + max_block_size: ByteSize::mb(500), + mempool_max_size: 10_000, + block_create_timeout: Duration::from_secs(12), + } + } +} + // TODO: Make a proper benchmark instead of an ad-hoc test #[test] pub async fn tps_test() -> Result<()> { @@ -95,102 +202,6 @@ pub async fn tps_test() -> Result<()> { Ok(()) } -pub(crate) struct TpsTestManager { - public_keypairs: Vec<(PrivateKey, AccountId)>, - target_tps: u64, -} - -impl TpsTestManager { - /// Generates public account keypairs. These are used to populate the config and to generate - /// valid public transactions for the tps test. - pub(crate) fn new(target_tps: u64, number_transactions: usize) -> Self { - let public_keypairs = (1..(number_transactions + 2)) - .map(|i| { - let mut private_key_bytes = [0u8; 32]; - private_key_bytes[..8].copy_from_slice(&i.to_le_bytes()); - let private_key = PrivateKey::try_new(private_key_bytes).unwrap(); - let public_key = PublicKey::new_from_private_key(&private_key); - let account_id = AccountId::from(&public_key); - (private_key, account_id) - }) - .collect(); - Self { - public_keypairs, - target_tps, - } - } - - #[expect( - clippy::cast_precision_loss, - reason = "This is just for testing purposes, we don't care about precision loss here" - )] - pub(crate) fn target_time(&self) -> Duration { - let number_transactions = (self.public_keypairs.len() - 1) as u64; - Duration::from_secs_f64(number_transactions as f64 / self.target_tps as f64) - } - - /// Build a batch of public transactions to submit to the node. - pub fn build_public_txs(&self) -> Vec { - // Create valid public transactions - let program = Program::authenticated_transfer_program(); - let public_txs: Vec = self - .public_keypairs - .windows(2) - .map(|pair| { - let amount: u128 = 1; - let message = putx::Message::try_new( - program.id(), - [pair[0].1, pair[1].1].to_vec(), - [0u128].to_vec(), - amount, - ) - .unwrap(); - let witness_set = - nssa::public_transaction::WitnessSet::for_message(&message, &[&pair[0].0]); - PublicTransaction::new(message, witness_set) - }) - .collect(); - - public_txs - } - - /// Generates a sequencer configuration with initial balance in a number of public accounts. - /// The transactions generated with the function `build_public_txs` will be valid in a node - /// started with the config from this method. - fn generate_initial_data(&self) -> InitialData { - // Create public public keypairs - let public_accounts = self - .public_keypairs - .iter() - .map(|(key, _)| (key.clone(), 10)) - .collect(); - - // Generate an initial commitment to be used with the privacy preserving transaction - // created with the `build_privacy_transaction` function. - let key_chain = KeyChain::new_os_random(); - let account = Account { - balance: 100, - nonce: 0xdead_beef, - program_owner: Program::authenticated_transfer_program().id(), - data: Data::default(), - }; - - InitialData { - public_accounts, - private_accounts: vec![(key_chain, account)], - } - } - - fn generate_sequencer_partial_config() -> SequencerPartialConfig { - SequencerPartialConfig { - max_num_tx_in_block: 300, - max_block_size: ByteSize::mb(500), - mempool_max_size: 10_000, - block_create_timeout: Duration::from_secs(12), - } - } -} - /// Builds a single privacy transaction to use in stress tests. This involves generating a proof so /// it may take a while to run. In normal execution of the node this transaction will be accepted /// only once. Disabling the node's nullifier uniqueness check allows to submit this transaction diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 4355e5f1..380f8b7b 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -1,7 +1,19 @@ +#![expect( + clippy::redundant_test_prefix, + reason = "Otherwise names interfere with ffi bindings" +)] +#![expect( + clippy::tests_outside_test_module, + clippy::undocumented_unsafe_blocks, + clippy::multiple_unsafe_ops_per_block, + clippy::shadow_unrelated, + reason = "We don't care about these in tests" +)] + use std::{ collections::HashSet, ffi::{CStr, CString, c_char}, - io::Write, + io::Write as _, path::Path, time::Duration, }; @@ -208,7 +220,7 @@ fn new_wallet_rust_with_default_config(password: &str) -> Result { config_path.clone(), storage_path.clone(), None, - password.to_string(), + password.to_owned(), ) } @@ -222,7 +234,7 @@ fn load_existing_ffi_wallet(home: &Path) -> Result<*mut WalletHandle> { } #[test] -fn test_wallet_ffi_create_public_accounts() -> Result<()> { +fn wallet_ffi_create_public_accounts() -> Result<()> { let password = "password_for_tests"; let n_accounts = 10; // First `n_accounts` public accounts created with Rust wallet @@ -257,7 +269,7 @@ fn test_wallet_ffi_create_public_accounts() -> Result<()> { } #[test] -fn test_wallet_ffi_create_private_accounts() -> Result<()> { +fn wallet_ffi_create_private_accounts() -> Result<()> { let password = "password_for_tests"; let n_accounts = 10; // First `n_accounts` private accounts created with Rust wallet @@ -291,7 +303,7 @@ fn test_wallet_ffi_create_private_accounts() -> Result<()> { Ok(()) } #[test] -fn test_wallet_ffi_save_and_load_persistent_storage() -> Result<()> { +fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> { let ctx = BlockingTestContext::new()?; let mut out_private_account_id = FfiBytes32::from_bytes([0; 32]); let home = tempfile::tempdir()?; @@ -635,7 +647,7 @@ fn test_wallet_ffi_account_id_to_base58() -> Result<()> { } #[test] -fn test_wallet_ffi_base58_to_account_id() -> Result<()> { +fn wallet_ffi_base58_to_account_id() -> Result<()> { let private_key = PrivateKey::new_os_random(); let public_key = PublicKey::new_from_private_key(&private_key); let account_id = AccountId::from(&public_key); @@ -655,7 +667,7 @@ fn test_wallet_ffi_base58_to_account_id() -> Result<()> { } #[test] -fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> { +fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -717,7 +729,7 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> { } #[test] -fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> { +fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; @@ -791,7 +803,7 @@ fn test_wallet_ffi_transfer_public() -> Result<()> { let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let from: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into(); let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[1]).into(); - let amount: [u8; 16] = 100u128.to_le_bytes(); + let amount: [u8; 16] = 100_u128.to_le_bytes(); let mut transfer_result = FfiTransferResult::default(); unsafe { @@ -854,7 +866,7 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> { ); (out_account_id, out_keys) }; - let amount: [u8; 16] = 100u128.to_le_bytes(); + let amount: [u8; 16] = 100_u128.to_le_bytes(); let mut transfer_result = FfiTransferResult::default(); unsafe { @@ -918,7 +930,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> { let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let from: FfiBytes32 = (&ctx.ctx().existing_private_accounts()[0]).into(); let to = FfiBytes32::from_bytes([37; 32]); - let amount: [u8; 16] = 100u128.to_le_bytes(); + let amount: [u8; 16] = 100_u128.to_le_bytes(); let mut transfer_result = FfiTransferResult::default(); unsafe { @@ -989,7 +1001,7 @@ fn test_wallet_ffi_transfer_private() -> Result<()> { (out_account_id, out_keys) }; - let amount: [u8; 16] = 100u128.to_le_bytes(); + let amount: [u8; 16] = 100_u128.to_le_bytes(); let mut transfer_result = FfiTransferResult::default(); unsafe { diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index a10a5191..6ef9e305 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -2,8 +2,8 @@ use nssa_core::{ NullifierPublicKey, SharedSecretKey, encryption::{EphemeralPublicKey, EphemeralSecretKey, ViewingPublicKey}, }; -use rand::{RngCore, rngs::OsRng}; -use sha2::Digest; +use rand::{RngCore as _, rngs::OsRng}; +use sha2::Digest as _; #[derive(Debug)] /// Ephemeral secret key holder. Non-clonable as intended for one-time use. Produces ephemeral @@ -12,18 +12,6 @@ pub struct EphemeralKeyHolder { ephemeral_secret_key: EphemeralSecretKey, } -#[must_use] -pub fn produce_one_sided_shared_secret_receiver( - vpk: &ViewingPublicKey, -) -> (SharedSecretKey, EphemeralPublicKey) { - let mut esk = [0; 32]; - OsRng.fill_bytes(&mut esk); - ( - SharedSecretKey::new(&esk, vpk), - EphemeralPublicKey::from_scalar(esk), - ) -} - impl EphemeralKeyHolder { #[must_use] pub fn new(receiver_nullifier_public_key: &NullifierPublicKey) -> Self { @@ -51,3 +39,15 @@ impl EphemeralKeyHolder { SharedSecretKey::new(&self.ephemeral_secret_key, receiver_viewing_public_key) } } + +#[must_use] +pub fn produce_one_sided_shared_secret_receiver( + vpk: &ViewingPublicKey, +) -> (SharedSecretKey, EphemeralPublicKey) { + let mut esk = [0; 32]; + OsRng.fill_bytes(&mut esk); + ( + SharedSecretKey::new(&esk, vpk), + EphemeralPublicKey::from_scalar(esk), + ) +} diff --git a/key_protocol/src/key_management/key_tree/chain_index.rs b/key_protocol/src/key_management/key_tree/chain_index.rs index 80dced3b..e3700393 100644 --- a/key_protocol/src/key_management/key_tree/chain_index.rs +++ b/key_protocol/src/key_management/key_tree/chain_index.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -use itertools::Itertools; +use itertools::Itertools as _; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize, Hash)] @@ -76,14 +76,14 @@ impl ChainIndex { } #[must_use] - pub fn next_in_line(&self) -> ChainIndex { + pub fn next_in_line(&self) -> Option { let mut chain = self.0.clone(); // ToDo: Add overflow check if let Some(last_p) = chain.last_mut() { - *last_p += 1; + *last_p = last_p.checked_add(1)?; } - ChainIndex(chain) + Some(ChainIndex(chain)) } #[must_use] @@ -101,7 +101,8 @@ impl ChainIndex { if self.0.is_empty() { None } else { - Some(ChainIndex(self.0[..(self.0.len() - 1)].to_vec())) + let last = self.0.len().checked_sub(1)?; + Some(ChainIndex(self.0[..last].to_vec())) } } @@ -115,14 +116,17 @@ impl ChainIndex { #[must_use] pub fn depth(&self) -> u32 { - self.0.iter().map(|cci| cci + 1).sum() + self.0 + .iter() + .map(|cci| cci.checked_add(1).expect("Max cci reached")) + .sum() } fn collapse_back(&self) -> Option { let mut res = self.parent()?; let last_mut = res.0.last_mut()?; - *last_mut += *(self.0.last()?) + 1; + *last_mut = last_mut.checked_add(self.0.last()?.checked_add(1)?)?; Some(res) } @@ -139,8 +143,8 @@ impl ChainIndex { let mut stack = vec![ChainIndex(vec![0; depth])]; let mut cumulative_stack = vec![ChainIndex(vec![0; depth])]; - while let Some(id) = stack.pop() { - if let Some(collapsed_id) = id.collapse_back() { + while let Some(top_id) = stack.pop() { + if let Some(collapsed_id) = top_id.collapse_back() { for id in collapsed_id.shuffle_iter() { stack.push(id.clone()); cumulative_stack.push(id); @@ -155,8 +159,8 @@ impl ChainIndex { let mut stack = vec![ChainIndex(vec![0; depth])]; let mut cumulative_stack = vec![ChainIndex(vec![0; depth])]; - while let Some(id) = stack.pop() { - if let Some(collapsed_id) = id.collapse_back() { + while let Some(top_id) = stack.pop() { + if let Some(collapsed_id) = top_id.collapse_back() { for id in collapsed_id.shuffle_iter() { stack.push(id.clone()); cumulative_stack.push(id); @@ -173,7 +177,7 @@ mod tests { use super::*; #[test] - fn test_chain_id_root_correct() { + fn chain_id_root_correct() { let chain_id = ChainIndex::root(); let chain_id_2 = ChainIndex::from_str("/").unwrap(); @@ -181,21 +185,21 @@ mod tests { } #[test] - fn test_chain_id_deser_correct() { + fn chain_id_deser_correct() { let chain_id = ChainIndex::from_str("/257").unwrap(); assert_eq!(chain_id.chain(), &[257]); } #[test] - fn test_chain_id_deser_failure_no_root() { + fn chain_id_deser_failure_no_root() { let chain_index_error = ChainIndex::from_str("257").err().unwrap(); assert!(matches!(chain_index_error, ChainIndexError::NoRootFound)); } #[test] - fn test_chain_id_deser_failure_int_parsing_failure() { + fn chain_id_deser_failure_int_parsing_failure() { let chain_index_error = ChainIndex::from_str("/hello").err().unwrap(); assert!(matches!( @@ -205,15 +209,15 @@ mod tests { } #[test] - fn test_chain_id_next_in_line_correct() { + fn chain_id_next_in_line_correct() { let chain_id = ChainIndex::from_str("/257").unwrap(); - let next_in_line = chain_id.next_in_line(); + let next_in_line = chain_id.next_in_line().unwrap(); assert_eq!(next_in_line, ChainIndex::from_str("/258").unwrap()); } #[test] - fn test_chain_id_child_correct() { + fn chain_id_child_correct() { let chain_id = ChainIndex::from_str("/257").unwrap(); let child = chain_id.nth_child(3); @@ -221,16 +225,16 @@ mod tests { } #[test] - fn test_correct_display() { + fn correct_display() { let chainid = ChainIndex(vec![5, 7, 8]); let string_index = format!("{chainid}"); - assert_eq!(string_index, "/5/7/8".to_string()); + assert_eq!(string_index, "/5/7/8".to_owned()); } #[test] - fn test_prev_in_line() { + fn prev_in_line() { let chain_id = ChainIndex(vec![1, 7, 3]); let prev_chain_id = chain_id.previous_in_line().unwrap(); @@ -239,7 +243,7 @@ mod tests { } #[test] - fn test_prev_in_line_no_prev() { + fn prev_in_line_no_prev() { let chain_id = ChainIndex(vec![1, 7, 0]); let prev_chain_id = chain_id.previous_in_line(); @@ -248,7 +252,7 @@ mod tests { } #[test] - fn test_parent() { + fn parent() { let chain_id = ChainIndex(vec![1, 7, 3]); let parent_chain_id = chain_id.parent().unwrap(); @@ -257,7 +261,7 @@ mod tests { } #[test] - fn test_parent_no_parent() { + fn parent_no_parent() { let chain_id = ChainIndex(vec![]); let parent_chain_id = chain_id.parent(); @@ -266,7 +270,7 @@ mod tests { } #[test] - fn test_parent_root() { + fn parent_root() { let chain_id = ChainIndex(vec![1]); let parent_chain_id = chain_id.parent().unwrap(); @@ -275,7 +279,7 @@ mod tests { } #[test] - fn test_collapse_back() { + fn collapse_back() { let chain_id = ChainIndex(vec![1, 1]); let collapsed = chain_id.collapse_back().unwrap(); @@ -284,7 +288,7 @@ mod tests { } #[test] - fn test_collapse_back_one() { + fn collapse_back_one() { let chain_id = ChainIndex(vec![1]); let collapsed = chain_id.collapse_back(); @@ -293,7 +297,7 @@ mod tests { } #[test] - fn test_collapse_back_root() { + fn collapse_back_root() { let chain_id = ChainIndex(vec![]); let collapsed = chain_id.collapse_back(); @@ -302,7 +306,7 @@ mod tests { } #[test] - fn test_shuffle() { + fn shuffle() { for id in ChainIndex::chain_ids_at_depth(5) { println!("{id}"); } diff --git a/key_protocol/src/key_management/key_tree/keys_private.rs b/key_protocol/src/key_management/key_tree/keys_private.rs index 1ff7095e..0f8b423b 100644 --- a/key_protocol/src/key_management/key_tree/keys_private.rs +++ b/key_protocol/src/key_management/key_tree/keys_private.rs @@ -1,4 +1,4 @@ -use k256::{Scalar, elliptic_curve::PrimeField}; +use k256::{Scalar, elliptic_curve::PrimeField as _}; use nssa_core::{NullifierPublicKey, encryption::ViewingPublicKey}; use serde::{Deserialize, Serialize}; @@ -54,6 +54,10 @@ impl KeyNode for ChildKeysPrivate { } fn nth_child(&self, cci: u32) -> Self { + #[expect( + clippy::arithmetic_side_effects, + reason = "Multiplying finite field scalars gives no unexpected side effects" + )] let parent_pt = Scalar::from_repr(self.value.0.private_key_holder.nullifier_secret_key.into()) .expect("Key generated as scalar, must be valid representation") @@ -113,27 +117,27 @@ impl KeyNode for ChildKeysPrivate { } } -impl<'a> From<&'a ChildKeysPrivate> for &'a (KeyChain, nssa::Account) { - fn from(value: &'a ChildKeysPrivate) -> Self { +impl<'keys> From<&'keys ChildKeysPrivate> for &'keys (KeyChain, nssa::Account) { + fn from(value: &'keys ChildKeysPrivate) -> Self { &value.value } } -impl<'a> From<&'a mut ChildKeysPrivate> for &'a mut (KeyChain, nssa::Account) { - fn from(value: &'a mut ChildKeysPrivate) -> Self { +impl<'keys> From<&'keys mut ChildKeysPrivate> for &'keys mut (KeyChain, nssa::Account) { + fn from(value: &'keys mut ChildKeysPrivate) -> Self { &mut value.value } } #[cfg(test)] mod tests { - use nssa_core::{NullifierPublicKey, NullifierSecretKey}; + use nssa_core::NullifierSecretKey; use super::*; use crate::key_management::{self, secret_holders::ViewingSecretKey}; #[test] - fn test_master_key_generation() { + fn master_key_generation() { let seed: [u8; 64] = [ 252, 56, 204, 83, 232, 123, 209, 188, 187, 167, 39, 213, 71, 39, 58, 65, 125, 134, 255, 49, 43, 108, 92, 53, 173, 164, 94, 142, 150, 74, 21, 163, 43, 144, 226, 87, 199, 18, @@ -143,7 +147,7 @@ mod tests { let keys = ChildKeysPrivate::root(seed); - let expected_ssk: SecretSpendingKey = key_management::secret_holders::SecretSpendingKey([ + let expected_ssk = key_management::secret_holders::SecretSpendingKey([ 246, 79, 26, 124, 135, 95, 52, 51, 201, 27, 48, 194, 2, 144, 51, 219, 245, 128, 139, 222, 42, 195, 105, 33, 115, 97, 186, 0, 97, 14, 218, 191, ]); @@ -158,7 +162,7 @@ mod tests { 34, 234, 19, 222, 2, 22, 12, 163, 252, 88, 11, 0, 163, ]; - let expected_npk: NullifierPublicKey = nssa_core::NullifierPublicKey([ + let expected_npk = nssa_core::NullifierPublicKey([ 7, 123, 125, 191, 233, 183, 201, 4, 20, 214, 155, 210, 45, 234, 27, 240, 194, 111, 97, 247, 155, 113, 122, 246, 192, 0, 70, 61, 76, 71, 70, 2, ]); @@ -181,7 +185,7 @@ mod tests { } #[test] - fn test_child_keys_generation() { + fn child_keys_generation() { let seed: [u8; 64] = [ 252, 56, 204, 83, 232, 123, 209, 188, 187, 167, 39, 213, 71, 39, 58, 65, 125, 134, 255, 49, 43, 108, 92, 53, 173, 164, 94, 142, 150, 74, 21, 163, 43, 144, 226, 87, 199, 18, @@ -190,7 +194,7 @@ mod tests { ]; let root_node = ChildKeysPrivate::root(seed); - let child_node = ChildKeysPrivate::nth_child(&root_node, 42u32); + let child_node = ChildKeysPrivate::nth_child(&root_node, 42_u32); let expected_ccc: [u8; 32] = [ 145, 59, 225, 32, 54, 168, 14, 45, 60, 253, 57, 202, 31, 86, 142, 234, 51, 57, 154, 88, @@ -201,7 +205,7 @@ mod tests { 19, 100, 119, 73, 191, 225, 234, 219, 129, 88, 40, 229, 63, 225, 189, 136, 69, 172, 221, 186, 147, 83, 150, 207, 70, 17, 228, 70, 113, 87, 227, 31, ]; - let expected_npk: NullifierPublicKey = nssa_core::NullifierPublicKey([ + let expected_npk = nssa_core::NullifierPublicKey([ 133, 235, 223, 151, 12, 69, 26, 222, 60, 125, 235, 125, 167, 212, 201, 168, 101, 242, 111, 239, 1, 228, 12, 252, 146, 53, 75, 17, 187, 255, 122, 181, ]); diff --git a/key_protocol/src/key_management/key_tree/keys_public.rs b/key_protocol/src/key_management/key_tree/keys_public.rs index 7e041c3f..06c3de05 100644 --- a/key_protocol/src/key_management/key_tree/keys_public.rs +++ b/key_protocol/src/key_management/key_tree/keys_public.rs @@ -16,7 +16,7 @@ impl ChildKeysPublic { fn compute_hash_value(&self, cci: u32) -> [u8; 64] { let mut hash_input = vec![]; - if 2u32.pow(31) > cci { + if 2_u32.pow(31) > cci { // Non-harden hash_input.extend_from_slice(self.cpk.value()); hash_input.extend_from_slice(&cci.to_le_bytes()); @@ -97,8 +97,8 @@ impl KeyNode for ChildKeysPublic { } } -impl<'a> From<&'a ChildKeysPublic> for &'a nssa::PrivateKey { - fn from(value: &'a ChildKeysPublic) -> Self { +impl<'keys> From<&'keys ChildKeysPublic> for &'keys nssa::PrivateKey { + fn from(value: &'keys ChildKeysPublic) -> Self { &value.csk } } @@ -110,7 +110,7 @@ mod tests { use super::*; #[test] - fn test_master_keys_generation() { + fn master_keys_generation() { let seed = [ 88, 189, 37, 237, 199, 125, 151, 226, 69, 153, 165, 113, 191, 69, 188, 221, 9, 34, 173, 134, 61, 109, 34, 103, 121, 39, 237, 14, 107, 194, 24, 194, 191, 14, 237, 185, 12, 87, @@ -141,7 +141,7 @@ mod tests { } #[test] - fn test_harden_child_keys_generation() { + fn harden_child_keys_generation() { let seed = [ 88, 189, 37, 237, 199, 125, 151, 226, 69, 153, 165, 113, 191, 69, 188, 221, 9, 34, 173, 134, 61, 109, 34, 103, 121, 39, 237, 14, 107, 194, 24, 194, 191, 14, 237, 185, 12, 87, @@ -149,7 +149,7 @@ mod tests { 187, 148, 92, 44, 253, 210, 37, ]; let root_keys = ChildKeysPublic::root(seed); - let cci = (2u32).pow(31) + 13; + let cci = (2_u32).pow(31) + 13; let child_keys = ChildKeysPublic::nth_child(&root_keys, cci); print!( @@ -181,7 +181,7 @@ mod tests { } #[test] - fn test_nonharden_child_keys_generation() { + fn nonharden_child_keys_generation() { let seed = [ 88, 189, 37, 237, 199, 125, 151, 226, 69, 153, 165, 113, 191, 69, 188, 221, 9, 34, 173, 134, 61, 109, 34, 103, 121, 39, 237, 14, 107, 194, 24, 194, 191, 14, 237, 185, 12, 87, @@ -221,7 +221,7 @@ mod tests { } #[test] - fn test_edge_case_child_keys_generation_2_power_31() { + fn edge_case_child_keys_generation_2_power_31() { let seed = [ 88, 189, 37, 237, 199, 125, 151, 226, 69, 153, 165, 113, 191, 69, 188, 221, 9, 34, 173, 134, 61, 109, 34, 103, 121, 39, 237, 14, 107, 194, 24, 194, 191, 14, 237, 185, 12, 87, @@ -229,7 +229,7 @@ mod tests { 187, 148, 92, 44, 253, 210, 37, ]; let root_keys = ChildKeysPublic::root(seed); - let cci = (2u32).pow(31); //equivant to 0, thus non-harden. + let cci = (2_u32).pow(31); //equivant to 0, thus non-harden. let child_keys = ChildKeysPublic::nth_child(&root_keys, cci); let expected_ccc = [ diff --git a/key_protocol/src/key_management/key_tree/mod.rs b/key_protocol/src/key_management/key_tree/mod.rs index bae44fbd..bdd15cb0 100644 --- a/key_protocol/src/key_management/key_tree/mod.rs +++ b/key_protocol/src/key_management/key_tree/mod.rs @@ -1,7 +1,4 @@ -use std::{ - collections::{BTreeMap, HashMap}, - sync::Arc, -}; +use std::{collections::BTreeMap, sync::Arc}; use anyhow::Result; use common::sequencer_client::SequencerClient; @@ -25,7 +22,7 @@ pub const DEPTH_SOFT_CAP: u32 = 20; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct KeyTree { pub key_map: BTreeMap, - pub account_id_map: HashMap, + pub account_id_map: BTreeMap, } pub type KeyTreePublic = KeyTree; @@ -44,7 +41,7 @@ impl KeyTree { let account_id = root_keys.account_id(); let key_map = BTreeMap::from_iter([(ChainIndex::root(), root_keys)]); - let account_id_map = HashMap::from_iter([(account_id, ChainIndex::root())]); + let account_id_map = BTreeMap::from_iter([(account_id, ChainIndex::root())]); Self { key_map, @@ -53,7 +50,7 @@ impl KeyTree { } pub fn new_from_root(root: N) -> Self { - let account_id_map = HashMap::from_iter([(root.account_id(), ChainIndex::root())]); + let account_id_map = BTreeMap::from_iter([(root.account_id(), ChainIndex::root())]); let key_map = BTreeMap::from_iter([(ChainIndex::root(), root)]); Self { @@ -84,7 +81,7 @@ impl KeyTree { let rightmost_child = parent_id.nth_child(right); let rightmost_ref = self.key_map.get(&rightmost_child); - let rightmost_ref_next = self.key_map.get(&rightmost_child.next_in_line()); + let rightmost_ref_next = self.key_map.get(&rightmost_child.next_in_line()?); match (&rightmost_ref, &rightmost_ref_next) { (Some(_), Some(_)) => { @@ -92,7 +89,7 @@ impl KeyTree { right = u32::midpoint(right, right_border); } (Some(_), None) => { - break Some(right + 1); + break Some(right.checked_add(1)?); } (None, None) => { right_border = right; @@ -133,7 +130,7 @@ impl KeyTree { break 'outer chain_id; } } - depth += 1; + depth = depth.checked_add(1).expect("Max depth reached"); } } @@ -156,15 +153,13 @@ impl KeyTree { #[must_use] pub fn get_node(&self, account_id: nssa::AccountId) -> Option<&N> { - self.account_id_map - .get(&account_id) - .and_then(|chain_id| self.key_map.get(chain_id)) + let chain_id = self.account_id_map.get(&account_id)?; + self.key_map.get(chain_id) } pub fn get_node_mut(&mut self, account_id: nssa::AccountId) -> Option<&mut N> { - self.account_id_map - .get(&account_id) - .and_then(|chain_id| self.key_map.get_mut(chain_id)) + let chain_id = self.account_id_map.get(&account_id)?; + self.key_map.get_mut(chain_id) } pub fn insert(&mut self, account_id: nssa::AccountId, chain_index: ChainIndex, node: N) { @@ -173,7 +168,7 @@ impl KeyTree { } pub fn remove(&mut self, addr: nssa::AccountId) -> Option { - let chain_index = self.account_id_map.remove(&addr).unwrap(); + let chain_index = self.account_id_map.remove(&addr)?; self.key_map.remove(&chain_index) } @@ -192,7 +187,10 @@ impl KeyTree { while (next_id.depth()) < depth { self.generate_new_node(&curr_id); id_stack.push(next_id.clone()); - next_id = next_id.next_in_line(); + next_id = match next_id.next_in_line() { + Some(id) => id, + None => break, + }; } } } @@ -225,7 +223,10 @@ impl KeyTree { while (next_id.depth()) < depth { id_stack.push(next_id.clone()); - next_id = next_id.next_in_line(); + next_id = match next_id.next_in_line() { + Some(id) => id, + None => break, + }; } } } @@ -240,7 +241,8 @@ impl KeyTree { /// /// Slow, maintains tree consistency. pub fn cleanup_tree_remove_uninit_layered(&mut self, depth: u32) { - 'outer: for i in (1..(depth as usize)).rev() { + let depth = usize::try_from(depth).expect("Depth is expected to fit in usize"); + 'outer: for i in (1..depth).rev() { println!("Cleanup of tree at depth {i}"); for id in ChainIndex::chain_ids_at_depth(i) { if let Some(node) = self.key_map.get(&id) { @@ -286,7 +288,10 @@ impl KeyTree { while (next_id.depth()) < depth { id_stack.push(next_id.clone()); - next_id = next_id.next_in_line(); + next_id = match next_id.next_in_line() { + Some(id) => id, + None => break, + }; } } @@ -305,7 +310,8 @@ impl KeyTree { depth: u32, client: Arc, ) -> Result<()> { - 'outer: for i in (1..(depth as usize)).rev() { + let depth = usize::try_from(depth).expect("Depth is expected to fit in usize"); + 'outer: for i in (1..depth).rev() { println!("Cleanup of tree at depth {i}"); for id in ChainIndex::chain_ids_at_depth(i) { if let Some(node) = self.key_map.get(&id) { @@ -328,7 +334,9 @@ impl KeyTree { #[cfg(test)] mod tests { - use std::{collections::HashSet, str::FromStr}; + #![expect(clippy::shadow_unrelated, reason = "We don't care about this in tests")] + + use std::{collections::HashSet, str::FromStr as _}; use nssa::AccountId; @@ -341,7 +349,7 @@ mod tests { } #[test] - fn test_simple_key_tree() { + fn simple_key_tree() { let seed_holder = seed_holder_for_tests(); let tree = KeyTreePublic::new(&seed_holder); @@ -354,7 +362,7 @@ mod tests { } #[test] - fn test_small_key_tree() { + fn small_key_tree() { let seed_holder = seed_holder_for_tests(); let mut tree = KeyTreePrivate::new(&seed_holder); @@ -393,7 +401,7 @@ mod tests { } #[test] - fn test_key_tree_can_not_make_child_keys() { + fn key_tree_can_not_make_child_keys() { let seed_holder = seed_holder_for_tests(); let mut tree = KeyTreePrivate::new(&seed_holder); @@ -423,7 +431,7 @@ mod tests { } #[test] - fn test_key_tree_complex_structure() { + fn key_tree_complex_structure() { let seed_holder = seed_holder_for_tests(); let mut tree = KeyTreePublic::new(&seed_holder); @@ -518,7 +526,7 @@ mod tests { } #[test] - fn test_tree_balancing_automatic() { + fn tree_balancing_automatic() { let seed_holder = seed_holder_for_tests(); let mut tree = KeyTreePublic::new(&seed_holder); @@ -533,7 +541,7 @@ mod tests { } #[test] - fn test_cleanup() { + fn cleanup() { let seed_holder = seed_holder_for_tests(); let mut tree = KeyTreePrivate::new(&seed_holder); @@ -566,13 +574,13 @@ mod tests { tree.cleanup_tree_remove_uninit_layered(10); let mut key_set_res = HashSet::new(); - key_set_res.insert("/0".to_string()); - key_set_res.insert("/1".to_string()); - key_set_res.insert("/2".to_string()); - key_set_res.insert("/".to_string()); - key_set_res.insert("/0/0".to_string()); - key_set_res.insert("/0/1".to_string()); - key_set_res.insert("/1/0".to_string()); + key_set_res.insert("/0".to_owned()); + key_set_res.insert("/1".to_owned()); + key_set_res.insert("/2".to_owned()); + key_set_res.insert("/".to_owned()); + key_set_res.insert("/0/0".to_owned()); + key_set_res.insert("/0/1".to_owned()); + key_set_res.insert("/1/0".to_owned()); let mut key_set = HashSet::new(); @@ -582,28 +590,16 @@ mod tests { assert_eq!(key_set, key_set_res); - let acc = tree - .key_map - .get(&ChainIndex::from_str("/1").unwrap()) - .unwrap(); + let acc = &tree.key_map[&ChainIndex::from_str("/1").unwrap()]; assert_eq!(acc.value.1.balance, 2); - let acc = tree - .key_map - .get(&ChainIndex::from_str("/2").unwrap()) - .unwrap(); + let acc = &tree.key_map[&ChainIndex::from_str("/2").unwrap()]; assert_eq!(acc.value.1.balance, 3); - let acc = tree - .key_map - .get(&ChainIndex::from_str("/0/1").unwrap()) - .unwrap(); + let acc = &tree.key_map[&ChainIndex::from_str("/0/1").unwrap()]; assert_eq!(acc.value.1.balance, 5); - let acc = tree - .key_map - .get(&ChainIndex::from_str("/1/0").unwrap()) - .unwrap(); + let acc = &tree.key_map[&ChainIndex::from_str("/1/0").unwrap()]; assert_eq!(acc.value.1.balance, 6); } } diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index b8e3c261..3b0a6139 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -5,12 +5,12 @@ use nssa_core::{ use secret_holders::{PrivateKeyHolder, SecretSpendingKey, SeedHolder}; use serde::{Deserialize, Serialize}; -pub type PublicAccountSigningKey = [u8; 32]; - pub mod ephemeral_key_holder; pub mod key_tree; pub mod secret_holders; +pub type PublicAccountSigningKey = [u8; 32]; + #[derive(Serialize, Deserialize, Clone, Debug)] /// Entrypoint to key management pub struct KeyChain { @@ -77,9 +77,9 @@ impl KeyChain { #[cfg(test)] mod tests { use aes_gcm::aead::OsRng; - use base58::ToBase58; - use k256::{AffinePoint, elliptic_curve::group::GroupEncoding}; - use rand::RngCore; + use base58::ToBase58 as _; + use k256::{AffinePoint, elliptic_curve::group::GroupEncoding as _}; + use rand::RngCore as _; use super::*; use crate::key_management::{ @@ -87,19 +87,19 @@ mod tests { }; #[test] - fn test_new_os_random() { + fn new_os_random() { // Ensure that a new KeyChain instance can be created without errors. let account_id_key_holder = KeyChain::new_os_random(); // Check that key holder fields are initialized with expected types assert_ne!( account_id_key_holder.nullifer_public_key.as_ref(), - &[0u8; 32] + &[0_u8; 32] ); } #[test] - fn test_calculate_shared_secret_receiver() { + fn calculate_shared_secret_receiver() { let account_id_key_holder = KeyChain::new_os_random(); // Generate a random ephemeral public key sender @@ -180,7 +180,7 @@ mod tests { } #[test] - fn test_non_trivial_chain_index() { + fn non_trivial_chain_index() { let keys = account_with_chain_index_2_for_tests(); let eph_key_holder = EphemeralKeyHolder::new(&keys.nullifer_public_key); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 0c096be4..db39757e 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -4,9 +4,9 @@ use nssa_core::{ NullifierPublicKey, NullifierSecretKey, encryption::{Scalar, ViewingPublicKey}, }; -use rand::{RngCore, rngs::OsRng}; +use rand::{RngCore as _, rngs::OsRng}; use serde::{Deserialize, Serialize}; -use sha2::{Digest, digest::FixedOutput}; +use sha2::{Digest as _, digest::FixedOutput as _}; const NSSA_ENTROPY_BYTES: [u8; 32] = [0; 32]; @@ -27,6 +27,7 @@ pub type ViewingSecretKey = Scalar; #[derive(Serialize, Deserialize, Debug, Clone)] /// Private key holder. Produces public keys. Can produce `account_id`. Can produce shared secret /// for recepient. +#[expect(clippy::partial_pub_fields, reason = "TODO: fix later")] pub struct PrivateKeyHolder { pub nullifier_secret_key: NullifierSecretKey, pub(crate) viewing_secret_key: ViewingSecretKey, @@ -84,7 +85,7 @@ impl SecretSpendingKey { const SUFFIX_2: &[u8; 19] = &[0; 19]; let index = match index { - None => 0u32, + None => 0_u32, _ => index.expect("Expect a valid u32"), }; @@ -105,7 +106,7 @@ impl SecretSpendingKey { const SUFFIX_2: &[u8; 19] = &[0; 19]; let index = match index { - None => 0u32, + None => 0_u32, _ => index.expect("Expect a valid u32"), }; @@ -158,7 +159,7 @@ mod tests { assert_eq!(seed_holder.seed.len(), 64); - let _ = seed_holder.generate_secret_spending_key_hash(); + let _hash = seed_holder.generate_secret_spending_key_hash(); } #[test] @@ -169,15 +170,15 @@ mod tests { let top_secret_key_holder = seed_holder.produce_top_secret_key_holder(); - let _ = top_secret_key_holder.generate_viewing_secret_key(None); + let _vsk = top_secret_key_holder.generate_viewing_secret_key(None); } #[test] fn two_seeds_generated_same_from_same_mnemonic() { let mnemonic = "test_pass"; - let seed_holder1 = SeedHolder::new_mnemonic(mnemonic.to_string()); - let seed_holder2 = SeedHolder::new_mnemonic(mnemonic.to_string()); + let seed_holder1 = SeedHolder::new_mnemonic(mnemonic.to_owned()); + let seed_holder2 = SeedHolder::new_mnemonic(mnemonic.to_owned()); assert_eq!(seed_holder1.seed, seed_holder2.seed); } diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index f0ce49fa..d870b902 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -190,8 +190,8 @@ impl Default for NSSAUserData { Self::new_with_accounts( BTreeMap::new(), BTreeMap::new(), - KeyTreePublic::new(&SeedHolder::new_mnemonic("default".to_string())), - KeyTreePrivate::new(&SeedHolder::new_mnemonic("default".to_string())), + KeyTreePublic::new(&SeedHolder::new_mnemonic("default".to_owned())), + KeyTreePrivate::new(&SeedHolder::new_mnemonic("default".to_owned())), ) .unwrap() } @@ -202,7 +202,7 @@ mod tests { use super::*; #[test] - fn test_new_account() { + fn new_account() { let mut user_data = NSSAUserData::default(); let (account_id_private, _) = user_data diff --git a/key_protocol/src/lib.rs b/key_protocol/src/lib.rs index 1a52c202..e3fe31cf 100644 --- a/key_protocol/src/lib.rs +++ b/key_protocol/src/lib.rs @@ -1,2 +1,4 @@ +#![expect(clippy::print_stdout, reason = "TODO: fix later")] + pub mod key_management; pub mod key_protocol_core; diff --git a/mempool/src/lib.rs b/mempool/src/lib.rs index 874a2fa3..fa844065 100644 --- a/mempool/src/lib.rs +++ b/mempool/src/lib.rs @@ -66,13 +66,13 @@ mod tests { use super::*; #[test] - async fn test_mempool_new() { + async fn mempool_new() { let (mut pool, _handle): (MemPool, _) = MemPool::new(10); assert_eq!(pool.pop(), None); } #[test] - async fn test_push_and_pop() { + async fn push_and_pop() { let (mut pool, handle) = MemPool::new(10); handle.push(1).await.unwrap(); @@ -83,7 +83,7 @@ mod tests { } #[test] - async fn test_multiple_push_pop() { + async fn multiple_push_pop() { let (mut pool, handle) = MemPool::new(10); handle.push(1).await.unwrap(); @@ -97,13 +97,13 @@ mod tests { } #[test] - async fn test_pop_empty() { + async fn pop_empty() { let (mut pool, _handle): (MemPool, _) = MemPool::new(10); assert_eq!(pool.pop(), None); } #[test] - async fn test_max_size() { + async fn max_size() { let (mut pool, handle) = MemPool::new(2); handle.push(1).await.unwrap(); @@ -116,7 +116,7 @@ mod tests { } #[test] - async fn test_push_front() { + async fn push_front() { let (mut pool, handle) = MemPool::new(10); handle.push(1).await.unwrap(); diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 671ea853..d10ba78c 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] nssa_core = { workspace = true, features = ["host"] } +anyhow.workspace = true thiserror.workspace = true risc0-zkvm.workspace = true serde.workspace = true diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 59c8a203..98799c9f 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -3,7 +3,7 @@ use std::{ str::FromStr, }; -use base58::{FromBase58, ToBase58}; +use base58::{FromBase58 as _, ToBase58 as _}; use borsh::{BorshDeserialize, BorshSerialize}; pub use data::Data; use serde::{Deserialize, Serialize}; @@ -125,7 +125,7 @@ impl FromStr for AccountId { if bytes.len() != 32 { return Err(AccountIdError::InvalidLength(bytes.len())); } - let mut value = [0u8; 32]; + let mut value = [0_u8; 32]; value.copy_from_slice(&bytes); Ok(AccountId { value }) } @@ -143,28 +143,28 @@ mod tests { use crate::program::DEFAULT_PROGRAM_ID; #[test] - fn test_zero_balance_account_data_creation() { + fn zero_balance_account_data_creation() { let new_acc = Account::default(); assert_eq!(new_acc.balance, 0); } #[test] - fn test_zero_nonce_account_data_creation() { + fn zero_nonce_account_data_creation() { let new_acc = Account::default(); assert_eq!(new_acc.nonce, 0); } #[test] - fn test_empty_data_account_data_creation() { + fn empty_data_account_data_creation() { let new_acc = Account::default(); assert!(new_acc.data.is_empty()); } #[test] - fn test_default_program_owner_account_data_creation() { + fn default_program_owner_account_data_creation() { let new_acc = Account::default(); assert_eq!(new_acc.program_owner, DEFAULT_PROGRAM_ID); @@ -172,7 +172,7 @@ mod tests { #[cfg(feature = "host")] #[test] - fn test_account_with_metadata_constructor() { + fn account_with_metadata_constructor() { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, @@ -194,7 +194,7 @@ mod tests { fn parse_valid_account_id() { let base58_str = "11111111111111111111111111111111"; let account_id: AccountId = base58_str.parse().unwrap(); - assert_eq!(account_id.value, [0u8; 32]); + assert_eq!(account_id.value, [0_u8; 32]); } #[cfg(feature = "host")] diff --git a/nssa/core/src/account/data.rs b/nssa/core/src/account/data.rs index d244ee47..36f82653 100644 --- a/nssa/core/src/account/data.rs +++ b/nssa/core/src/account/data.rs @@ -22,18 +22,17 @@ impl Data { ) -> Result { use std::io::Read as _; - let mut u32_bytes = [0u8; 4]; + let mut u32_bytes = [0_u8; 4]; cursor.read_exact(&mut u32_bytes)?; let data_length = u32::from_le_bytes(u32_bytes); - if data_length as usize - > usize::try_from(DATA_MAX_LENGTH.as_u64()).expect("DATA_MAX_LENGTH fits in usize") - { + if u64::from(data_length) > DATA_MAX_LENGTH.as_u64() { return Err( std::io::Error::new(std::io::ErrorKind::InvalidData, DataTooBigError).into(), ); } - let mut data = vec![0; data_length as usize]; + let mut data = + vec![0; usize::try_from(data_length).expect("data length is expected to fit in usize")]; cursor.read_exact(&mut data)?; Ok(Self(data)) } @@ -135,15 +134,10 @@ impl BorshDeserialize for Data { let len = u32::deserialize_reader(reader)?; match len { 0 => Ok(Self::default()), - len if len as usize - > usize::try_from(DATA_MAX_LENGTH.as_u64()) - .expect("DATA_MAX_LENGTH fits in usize") => - { - Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - DataTooBigError, - )) - } + len if u64::from(len) > DATA_MAX_LENGTH.as_u64() => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + DataTooBigError, + )), len => { let vec_bytes = u8::vec_from_reader(len, reader)? .expect("can't be None in current borsh crate implementation"); @@ -158,9 +152,9 @@ mod tests { use super::*; #[test] - fn test_data_max_length_allowed() { + fn data_max_length_allowed() { let max_vec = vec![ - 0u8; + 0_u8; usize::try_from(DATA_MAX_LENGTH.as_u64()) .expect("DATA_MAX_LENGTH fits in usize") ]; @@ -169,9 +163,9 @@ mod tests { } #[test] - fn test_data_too_big_error() { + fn data_too_big_error() { let big_vec = vec![ - 0u8; + 0_u8; usize::try_from(DATA_MAX_LENGTH.as_u64()) .expect("DATA_MAX_LENGTH fits in usize") + 1 @@ -181,9 +175,9 @@ mod tests { } #[test] - fn test_borsh_deserialize_exceeding_limit_error() { + fn borsh_deserialize_exceeding_limit_error() { let too_big_data = vec![ - 0u8; + 0_u8; usize::try_from(DATA_MAX_LENGTH.as_u64()) .expect("DATA_MAX_LENGTH fits in usize") + 1 @@ -196,9 +190,9 @@ mod tests { } #[test] - fn test_json_deserialize_exceeding_limit_error() { + fn json_deserialize_exceeding_limit_error() { let data = vec![ - 0u8; + 0_u8; usize::try_from(DATA_MAX_LENGTH.as_u64()) .expect("DATA_MAX_LENGTH fits in usize") + 1 diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index fd65491a..e4ff6e2d 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -61,7 +61,7 @@ mod tests { }; #[test] - fn test_privacy_preserving_circuit_output_to_bytes_is_compatible_with_from_slice() { + fn privacy_preserving_circuit_output_to_bytes_is_compatible_with_from_slice() { let output = PrivacyPreservingCircuitOutput { public_pre_states: vec![ AccountWithMetadata::new( @@ -69,7 +69,7 @@ mod tests { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 12_345_678_901_234_567_890, data: b"test data".to_vec().try_into().unwrap(), - nonce: 18_446_744_073_709_551_614, + nonce: 0xFFFF_FFFF_FFFF_FFFE, }, true, AccountId::new([0; 32]), @@ -89,7 +89,7 @@ mod tests { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 100, data: b"post state data".to_vec().try_into().unwrap(), - nonce: 18_446_744_073_709_551_615, + nonce: 0xFFFF_FFFF_FFFF_FFFF, }], ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])], new_commitments: vec![Commitment::new( diff --git a/nssa/core/src/commitment.rs b/nssa/core/src/commitment.rs index 58bd838c..87e1ac79 100644 --- a/nssa/core/src/commitment.rs +++ b/nssa/core/src/commitment.rs @@ -1,29 +1,9 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use risc0_zkvm::sha::{Impl, Sha256}; +use risc0_zkvm::sha::{Impl, Sha256 as _}; use serde::{Deserialize, Serialize}; use crate::{NullifierPublicKey, account::Account}; -#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)] -#[cfg_attr( - any(feature = "host", test), - derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord) -)] -pub struct Commitment(pub(super) [u8; 32]); - -#[cfg(any(feature = "host", test))] -impl std::fmt::Debug for Commitment { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use std::fmt::Write as _; - - let hex: String = self.0.iter().fold(String::new(), |mut acc, b| { - write!(acc, "{b:02x}").expect("writing to string should not fail"); - acc - }); - write!(f, "Commitment({hex})") - } -} - /// A commitment to all zero data. /// ```python /// from hashlib import sha256 @@ -48,6 +28,26 @@ pub const DUMMY_COMMITMENT_HASH: [u8; 32] = [ 194, 216, 67, 56, 251, 208, 226, 0, 117, 149, 39, ]; +#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr( + any(feature = "host", test), + derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord) +)] +pub struct Commitment(pub(super) [u8; 32]); + +#[cfg(any(feature = "host", test))] +impl std::fmt::Debug for Commitment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::fmt::Write as _; + + let hex: String = self.0.iter().fold(String::new(), |mut acc, b| { + write!(acc, "{b:02x}").expect("writing to string should not fail"); + acc + }); + write!(f, "Commitment({hex})") + } +} + impl Commitment { /// Generates the commitment to a private account owned by user for npk: /// SHA256(npk || `program_owner` || balance || nonce || SHA256(data)) @@ -93,12 +93,12 @@ pub fn compute_digest_for_path( for node in &proof.1 { let is_left_child = level_index & 1 == 0; if is_left_child { - let mut bytes = [0u8; 64]; + let mut bytes = [0_u8; 64]; bytes[..32].copy_from_slice(&result); bytes[32..].copy_from_slice(node); result = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); } else { - let mut bytes = [0u8; 64]; + let mut bytes = [0_u8; 64]; bytes[..32].copy_from_slice(node); bytes[32..].copy_from_slice(&result); result = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); @@ -110,14 +110,14 @@ pub fn compute_digest_for_path( #[cfg(test)] mod tests { - use risc0_zkvm::sha::{Impl, Sha256}; + use risc0_zkvm::sha::{Impl, Sha256 as _}; use crate::{ Commitment, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, NullifierPublicKey, account::Account, }; #[test] - fn test_nothing_up_my_sleeve_dummy_commitment() { + fn nothing_up_my_sleeve_dummy_commitment() { let default_account = Account::default(); let npk_null = NullifierPublicKey([0; 32]); let expected_dummy_commitment = Commitment::new(&npk_null, &default_account); @@ -125,7 +125,7 @@ mod tests { } #[test] - fn test_nothing_up_my_sleeve_dummy_commitment_hash() { + fn nothing_up_my_sleeve_dummy_commitment_hash() { let expected_dummy_commitment_hash: [u8; 32] = Impl::hash_bytes(&DUMMY_COMMITMENT.to_byte_array()) .as_bytes() diff --git a/nssa/core/src/encoding.rs b/nssa/core/src/encoding.rs index 6050a234..04e3ac88 100644 --- a/nssa/core/src/encoding.rs +++ b/nssa/core/src/encoding.rs @@ -2,7 +2,7 @@ #[cfg(feature = "host")] use std::io::Cursor; #[cfg(feature = "host")] -use std::io::Read; +use std::io::Read as _; #[cfg(feature = "host")] use crate::Nullifier; @@ -37,11 +37,11 @@ impl Account { pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { use crate::account::data::Data; - let mut u32_bytes = [0u8; 4]; - let mut u128_bytes = [0u8; 16]; + let mut u32_bytes = [0_u8; 4]; + let mut u128_bytes = [0_u8; 16]; // program owner - let mut program_owner = [0u32; 8]; + let mut program_owner = [0_u32; 8]; for word in &mut program_owner { cursor.read_exact(&mut u32_bytes)?; *word = u32::from_le_bytes(u32_bytes); @@ -82,7 +82,7 @@ impl Commitment { /// Deserializes a commitment from a cursor. #[cfg(feature = "host")] pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { - let mut bytes = [0u8; 32]; + let mut bytes = [0_u8; 32]; cursor.read_exact(&mut bytes)?; Ok(Self(bytes)) } @@ -110,7 +110,7 @@ impl Nullifier { /// Deserializes a nullifier from a cursor. pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { - let mut bytes = [0u8; 32]; + let mut bytes = [0_u8; 32]; cursor.read_exact(&mut bytes)?; Ok(Self(bytes)) } @@ -148,7 +148,9 @@ impl Ciphertext { cursor.read_exact(&mut u32_bytes)?; let ciphertext_lenght = u32::from_le_bytes(u32_bytes); - let mut ciphertext = vec![0; ciphertext_lenght as usize]; + let ciphertext_length = + usize::try_from(ciphertext_lenght).expect("ciphertext length fits in usize"); + let mut ciphertext = vec![0; ciphertext_length]; cursor.read_exact(&mut ciphertext)?; Ok(Self(ciphertext)) @@ -183,7 +185,7 @@ mod tests { use super::*; #[test] - fn test_enconding() { + fn enconding() { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 123_456_789_012_345_678_901_234_567_890_123_456, @@ -204,7 +206,7 @@ mod tests { } #[test] - fn test_commitment_to_bytes() { + fn commitment_to_bytes() { let commitment = Commitment((0..32).collect::>().try_into().unwrap()); let expected_bytes: [u8; 32] = (0..32).collect::>().try_into().unwrap(); @@ -214,7 +216,7 @@ mod tests { #[cfg(feature = "host")] #[test] - fn test_nullifier_to_bytes() { + fn nullifier_to_bytes() { let nullifier = Nullifier((0..32).collect::>().try_into().unwrap()); let expected_bytes: [u8; 32] = (0..32).collect::>().try_into().unwrap(); @@ -224,7 +226,7 @@ mod tests { #[cfg(feature = "host")] #[test] - fn test_commitment_to_bytes_roundtrip() { + fn commitment_to_bytes_roundtrip() { let commitment = Commitment((0..32).collect::>().try_into().unwrap()); let bytes = commitment.to_byte_array(); let mut cursor = Cursor::new(bytes.as_ref()); @@ -234,7 +236,7 @@ mod tests { #[cfg(feature = "host")] #[test] - fn test_nullifier_to_bytes_roundtrip() { + fn nullifier_to_bytes_roundtrip() { let nullifier = Nullifier((0..32).collect::>().try_into().unwrap()); let bytes = nullifier.to_byte_array(); let mut cursor = Cursor::new(bytes.as_ref()); @@ -244,7 +246,7 @@ mod tests { #[cfg(feature = "host")] #[test] - fn test_account_to_bytes_roundtrip() { + fn account_to_bytes_roundtrip() { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 123_456_789_012_345_678_901_234_567_890_123_456, diff --git a/nssa/core/src/encryption/mod.rs b/nssa/core/src/encryption/mod.rs index 4b07428b..1d722a5e 100644 --- a/nssa/core/src/encryption/mod.rs +++ b/nssa/core/src/encryption/mod.rs @@ -1,18 +1,16 @@ use borsh::{BorshDeserialize, BorshSerialize}; use chacha20::{ ChaCha20, - cipher::{KeyIvInit, StreamCipher}, + cipher::{KeyIvInit as _, StreamCipher as _}, }; -use risc0_zkvm::sha::{Impl, Sha256}; +use risc0_zkvm::sha::{Impl, Sha256 as _}; use serde::{Deserialize, Serialize}; - -#[cfg(feature = "host")] -pub mod shared_key_derivation; - #[cfg(feature = "host")] pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, ViewingPublicKey}; use crate::{Commitment, account::Account}; +#[cfg(feature = "host")] +pub mod shared_key_derivation; pub type Scalar = [u8; 32]; @@ -78,6 +76,10 @@ impl EncryptionScheme { } #[cfg(feature = "host")] + #[expect( + clippy::print_stdout, + reason = "This is the current way to debug things. TODO: fix later" + )] #[must_use] pub fn decrypt( ciphertext: &Ciphertext, diff --git a/nssa/core/src/encryption/shared_key_derivation.rs b/nssa/core/src/encryption/shared_key_derivation.rs index 9e37668f..c5890f49 100644 --- a/nssa/core/src/encryption/shared_key_derivation.rs +++ b/nssa/core/src/encryption/shared_key_derivation.rs @@ -1,11 +1,16 @@ +#![expect( + clippy::arithmetic_side_effects, + reason = "Multiplication of finite field elements can't overflow" +)] + use std::fmt::Write as _; use borsh::{BorshDeserialize, BorshSerialize}; use k256::{ AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint, elliptic_curve::{ - PrimeField, - sec1::{FromEncodedPoint, ToEncodedPoint}, + PrimeField as _, + sec1::{FromEncodedPoint as _, ToEncodedPoint as _}, }, }; use serde::{Deserialize, Serialize}; @@ -61,9 +66,9 @@ impl SharedSecretKey { let shared = ProjectivePoint::from(pubkey_affine) * scalar; let shared_affine = shared.to_affine(); - let encoded = shared_affine.to_encoded_point(false); - let x_bytes_slice = encoded.x().unwrap(); - let mut x_bytes = [0u8; 32]; + let shared_affine_encoded = shared_affine.to_encoded_point(false); + let x_bytes_slice = shared_affine_encoded.x().unwrap(); + let mut x_bytes = [0_u8; 32]; x_bytes.copy_from_slice(x_bytes_slice); Self(x_bytes) diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index 8d4fce5f..8014c7ca 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -1,10 +1,7 @@ -pub mod account; -mod circuit_io; -mod commitment; -mod encoding; -pub mod encryption; -mod nullifier; -pub mod program; +#![expect( + clippy::multiple_inherent_impl, + reason = "We prefer to group methods by functionality rather than by type for encoding" +)] pub use circuit_io::{PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput}; pub use commitment::{ @@ -14,5 +11,13 @@ pub use commitment::{ pub use encryption::{EncryptionScheme, SharedSecretKey}; pub use nullifier::{Nullifier, NullifierPublicKey, NullifierSecretKey}; +pub mod account; +mod circuit_io; +mod commitment; +mod encoding; +pub mod encryption; +mod nullifier; +pub mod program; + #[cfg(feature = "host")] pub mod error; diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index 5038138f..2ba9afc4 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -1,5 +1,5 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use risc0_zkvm::sha::{Impl, Sha256}; +use risc0_zkvm::sha::{Impl, Sha256 as _}; use serde::{Deserialize, Serialize}; use crate::{Commitment, account::AccountId}; @@ -88,8 +88,8 @@ mod tests { use super::*; #[test] - fn test_constructor_for_account_update() { - let commitment = Commitment((0..32u8).collect::>().try_into().unwrap()); + fn constructor_for_account_update() { + let commitment = Commitment((0..32_u8).collect::>().try_into().unwrap()); let nsk = [0x42; 32]; let expected_nullifier = Nullifier([ 148, 243, 116, 209, 140, 231, 211, 61, 35, 62, 114, 110, 143, 224, 82, 201, 221, 34, @@ -100,7 +100,7 @@ mod tests { } #[test] - fn test_constructor_for_account_initialization() { + fn constructor_for_account_initialization() { let npk = NullifierPublicKey([ 112, 188, 193, 129, 150, 55, 228, 67, 88, 168, 29, 151, 5, 92, 23, 190, 17, 162, 164, 255, 29, 105, 42, 186, 43, 11, 157, 168, 132, 225, 17, 163, @@ -114,7 +114,7 @@ mod tests { } #[test] - fn test_from_secret_key() { + fn from_secret_key() { let nsk = [ 57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30, 196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28, @@ -128,7 +128,7 @@ mod tests { } #[test] - fn test_account_id_from_nullifier_public_key() { + fn account_id_from_nullifier_public_key() { let nsk = [ 57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30, 196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28, diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 1b97b117..b541e5f0 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize}; use crate::account::{Account, AccountId, AccountWithMetadata}; -pub type ProgramId = [u32; 8]; -pub type InstructionData = Vec; pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8]; pub const MAX_NUMBER_CHAINED_CALLS: usize = 10; +pub type ProgramId = [u32; 8]; +pub type InstructionData = Vec; pub struct ProgramInput { pub pre_states: Vec, pub instruction: T, @@ -30,24 +30,9 @@ impl PdaSeed { } } -#[must_use] -pub fn compute_authorized_pdas( - caller_program_id: Option, - pda_seeds: &[PdaSeed], -) -> HashSet { - caller_program_id - .map(|caller_program_id| { - pda_seeds - .iter() - .map(|pda_seed| AccountId::from((&caller_program_id, pda_seed))) - .collect() - }) - .unwrap_or_default() -} - impl From<(&ProgramId, &PdaSeed)> for AccountId { fn from(value: (&ProgramId, &PdaSeed)) -> Self { - use risc0_zkvm::sha::{Impl, Sha256}; + use risc0_zkvm::sha::{Impl, Sha256 as _}; const PROGRAM_DERIVED_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/NSSA/v0.2/AccountId/PDA/\x00\x00\x00\x00\x00\x00\x00"; @@ -176,6 +161,48 @@ pub struct ProgramOutput { pub chained_calls: Vec, } +/// Representation of a number as `lo + hi * 2^128`. +#[derive(PartialEq, Eq)] +struct WrappedBalanceSum { + lo: u128, + hi: u128, +} + +impl WrappedBalanceSum { + /// Constructs a [`WrappedBalanceSum`] from an iterator of balances. + /// + /// Returns [`None`] if balance sum overflows `lo + hi * 2^128` representation, which is not + /// expected in practical scenarios. + fn from_balances(balances: impl Iterator) -> Option { + let mut wrapped = WrappedBalanceSum { lo: 0, hi: 0 }; + + for balance in balances { + let (new_sum, did_overflow) = wrapped.lo.overflowing_add(balance); + if did_overflow { + wrapped.hi = wrapped.hi.checked_add(1)?; + } + wrapped.lo = new_sum; + } + + Some(wrapped) + } +} + +#[must_use] +pub fn compute_authorized_pdas( + caller_program_id: Option, + pda_seeds: &[PdaSeed], +) -> HashSet { + caller_program_id + .map(|caller_program_id| { + pda_seeds + .iter() + .map(|pda_seed| AccountId::from((&caller_program_id, pda_seed))) + .collect() + }) + .unwrap_or_default() +} + /// Reads the NSSA inputs from the guest environment. #[must_use] pub fn read_nssa_inputs() -> (ProgramInput, InstructionData) { @@ -310,39 +337,12 @@ fn validate_uniqueness_of_account_ids(pre_states: &[AccountWithMetadata]) -> boo number_of_accounts == number_of_account_ids } -/// Representation of a number as `lo + hi * 2^128`. -#[derive(PartialEq, Eq)] -struct WrappedBalanceSum { - lo: u128, - hi: u128, -} - -impl WrappedBalanceSum { - /// Constructs a [`WrappedBalanceSum`] from an iterator of balances. - /// - /// Returns [`None`] if balance sum overflows `lo + hi * 2^128` representation, which is not - /// expected in practical scenarios. - fn from_balances(balances: impl Iterator) -> Option { - let mut wrapped = WrappedBalanceSum { lo: 0, hi: 0 }; - - for balance in balances { - let (new_sum, did_overflow) = wrapped.lo.overflowing_add(balance); - if did_overflow { - wrapped.hi = wrapped.hi.checked_add(1)?; - } - wrapped.lo = new_sum; - } - - Some(wrapped) - } -} - #[cfg(test)] mod tests { use super::*; #[test] - fn test_post_state_new_with_claim_constructor() { + fn post_state_new_with_claim_constructor() { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, @@ -357,7 +357,7 @@ mod tests { } #[test] - fn test_post_state_new_without_claim_constructor() { + fn post_state_new_without_claim_constructor() { let account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, @@ -372,7 +372,7 @@ mod tests { } #[test] - fn test_post_state_account_getter() { + fn post_state_account_getter() { let mut account = Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], balance: 1337, diff --git a/nssa/src/encoding/program_deployment_transaction.rs b/nssa/src/encoding/program_deployment_transaction.rs index 9b1b9406..fc1bf459 100644 --- a/nssa/src/encoding/program_deployment_transaction.rs +++ b/nssa/src/encoding/program_deployment_transaction.rs @@ -16,7 +16,7 @@ mod tests { use crate::{ProgramDeploymentTransaction, program_deployment_transaction::Message}; #[test] - fn test_roundtrip() { + fn roundtrip() { let message = Message::new(vec![0xca, 0xfe, 0xca, 0xfe, 0x01, 0x02, 0x03]); let tx = ProgramDeploymentTransaction::new(message); let bytes = tx.to_bytes(); diff --git a/nssa/src/error.rs b/nssa/src/error.rs index c42b70d2..3576b366 100644 --- a/nssa/src/error.rs +++ b/nssa/src/error.rs @@ -29,7 +29,7 @@ pub enum NssaError { Io(#[from] io::Error), #[error("Invalid Public Key")] - InvalidPublicKey, + InvalidPublicKey(#[source] secp256k1::Error), #[error("Risc0 error: {0}")] ProgramWriteInputFailed(String), @@ -59,13 +59,16 @@ pub enum NssaError { CircuitProvingError(String), #[error("Invalid program bytecode")] - InvalidProgramBytecode, + InvalidProgramBytecode(#[source] anyhow::Error), #[error("Program already exists")] ProgramAlreadyExists, #[error("Chain of calls is too long")] MaxChainedCallsDepthExceeded, + + #[error("Max account nonce reached")] + MaxAccountNonceReached, } #[cfg(test)] @@ -83,7 +86,7 @@ mod tests { } #[test] - fn test_ensure() { + fn ensure_works() { assert!(test_function_ensure(true).is_ok()); assert!(test_function_ensure(false).is_err()); } diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index 47a0eadb..bc7cf121 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -1,16 +1,7 @@ -pub mod program_methods { - include!(concat!(env!("OUT_DIR"), "/program_methods/mod.rs")); -} - -pub mod encoding; -pub mod error; -mod merkle_tree; -pub mod privacy_preserving_transaction; -pub mod program; -pub mod program_deployment_transaction; -pub mod public_transaction; -mod signature; -mod state; +#![expect( + clippy::multiple_inherent_impl, + reason = "We prefer to group methods by functionality rather than by type for encoding" +)] pub use nssa_core::{ SharedSecretKey, @@ -26,3 +17,17 @@ pub use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID; pub use public_transaction::PublicTransaction; pub use signature::{PrivateKey, PublicKey, Signature}; pub use state::V02State; + +pub mod encoding; +pub mod error; +mod merkle_tree; +pub mod privacy_preserving_transaction; +pub mod program; +pub mod program_deployment_transaction; +pub mod public_transaction; +mod signature; +mod state; + +pub mod program_methods { + include!(concat!(env!("OUT_DIR"), "/program_methods/mod.rs")); +} diff --git a/nssa/src/merkle_tree/mod.rs b/nssa/src/merkle_tree/mod.rs index b15891b7..a2d97784 100644 --- a/nssa/src/merkle_tree/mod.rs +++ b/nssa/src/merkle_tree/mod.rs @@ -1,25 +1,13 @@ +#![expect(clippy::arithmetic_side_effects, reason = "TODO: fix later")] + use borsh::{BorshDeserialize, BorshSerialize}; -use sha2::{Digest, Sha256}; +use sha2::{Digest as _, Sha256}; mod default_values; type Value = [u8; 32]; type Node = [u8; 32]; -/// Compute parent as the hash of two child nodes -fn hash_two(left: &Node, right: &Node) -> Node { - let mut hasher = Sha256::new(); - hasher.update(left); - hasher.update(right); - hasher.finalize().into() -} - -fn hash_value(value: &Value) -> Node { - let mut hasher = Sha256::new(); - hasher.update(value); - hasher.finalize().into() -} - #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Clone, BorshSerialize, BorshDeserialize)] pub struct MerkleTree { @@ -36,7 +24,8 @@ impl MerkleTree { fn root_index(&self) -> usize { let tree_depth = self.depth(); - let capacity_depth = self.capacity.trailing_zeros() as usize; + let capacity_depth = + usize::try_from(self.capacity.trailing_zeros()).expect("u32 fits in usize"); if tree_depth == capacity_depth { 0 @@ -48,7 +37,8 @@ impl MerkleTree { /// Number of levels required to hold all nodes fn depth(&self) -> usize { - self.length.next_power_of_two().trailing_zeros() as usize + usize::try_from(self.length.next_power_of_two().trailing_zeros()) + .expect("u32 fits in usize") } fn get_node(&self, index: usize) -> &Node { @@ -62,7 +52,7 @@ impl MerkleTree { pub fn with_capacity(capacity: usize) -> Self { // Adjust capacity to ensure power of two let capacity = capacity.next_power_of_two(); - let total_depth = capacity.trailing_zeros() as usize; + let total_depth = usize::try_from(capacity.trailing_zeros()).expect("u32 fits in usize"); let nodes = default_values::DEFAULT_VALUES[..=total_depth] .iter() @@ -152,15 +142,33 @@ impl MerkleTree { } } +/// Compute parent as the hash of two child nodes +fn hash_two(left: &Node, right: &Node) -> Node { + let mut hasher = Sha256::new(); + hasher.update(left); + hasher.update(right); + hasher.finalize().into() +} + +fn hash_value(value: &Value) -> Node { + let mut hasher = Sha256::new(); + hasher.update(value); + hasher.finalize().into() +} + fn prev_power_of_two(x: usize) -> usize { if x == 0 { return 0; } - 1 << (usize::BITS as usize - x.leading_zeros() as usize - 1) + 1 << (usize::BITS - x.leading_zeros() - 1) } #[cfg(test)] mod tests { + use hex_literal::hex; + + use super::*; + impl MerkleTree { pub fn new(values: &[Value]) -> Self { let mut this = Self::with_capacity(values.len()); @@ -171,11 +179,8 @@ mod tests { } } - use hex_literal::hex; - - use super::*; #[test] - fn test_empty_merkle_tree() { + fn empty_merkle_tree() { let tree = MerkleTree::with_capacity(4); let expected_root = hex!("0000000000000000000000000000000000000000000000000000000000000000"); @@ -185,7 +190,7 @@ mod tests { } #[test] - fn test_merkle_tree_0() { + fn merkle_tree_0() { let values = [[0; 32]]; let tree = MerkleTree::new(&values); assert_eq!(tree.root(), hash_value(&[0; 32])); @@ -194,7 +199,7 @@ mod tests { } #[test] - fn test_merkle_tree_1() { + fn merkle_tree_1() { let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; let tree = MerkleTree::new(&values); let expected_root = @@ -205,7 +210,7 @@ mod tests { } #[test] - fn test_merkle_tree_2() { + fn merkle_tree_2() { let values = [[1; 32], [2; 32], [3; 32], [0; 32]]; let tree = MerkleTree::new(&values); let expected_root = @@ -216,7 +221,7 @@ mod tests { } #[test] - fn test_merkle_tree_3() { + fn merkle_tree_3() { let values = [[1; 32], [2; 32], [3; 32]]; let tree = MerkleTree::new(&values); let expected_root = @@ -227,7 +232,7 @@ mod tests { } #[test] - fn test_merkle_tree_4() { + fn merkle_tree_4() { let values = [[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; let tree = MerkleTree::new(&values); let expected_root = @@ -239,7 +244,7 @@ mod tests { } #[test] - fn test_merkle_tree_5() { + fn merkle_tree_5() { let values = [ [11; 32], [12; 32], [12; 32], [13; 32], [14; 32], [15; 32], [15; 32], [13; 32], [13; 32], [15; 32], [11; 32], @@ -253,7 +258,7 @@ mod tests { } #[test] - fn test_merkle_tree_6() { + fn merkle_tree_6() { let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; let tree = MerkleTree::new(&values); let expected_root = @@ -262,7 +267,7 @@ mod tests { } #[test] - fn test_with_capacity_4() { + fn with_capacity_4() { let tree = MerkleTree::with_capacity(4); assert_eq!(tree.length, 0); @@ -277,7 +282,7 @@ mod tests { } #[test] - fn test_with_capacity_5() { + fn with_capacity_5() { let tree = MerkleTree::with_capacity(5); assert_eq!(tree.length, 0); @@ -295,7 +300,7 @@ mod tests { } #[test] - fn test_with_capacity_6() { + fn with_capacity_6() { let mut tree = MerkleTree::with_capacity(100); let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; @@ -312,7 +317,7 @@ mod tests { } #[test] - fn test_with_capacity_7() { + fn with_capacity_7() { let mut tree = MerkleTree::with_capacity(599); let values = [[1; 32], [2; 32], [3; 32]]; @@ -328,7 +333,7 @@ mod tests { } #[test] - fn test_with_capacity_8() { + fn with_capacity_8() { let mut tree = MerkleTree::with_capacity(1); let values = [[1; 32], [2; 32], [3; 32]]; @@ -344,7 +349,7 @@ mod tests { } #[test] - fn test_insert_value_1() { + fn insert_value_1() { let mut tree = MerkleTree::with_capacity(1); let values = [[1; 32], [2; 32], [3; 32]]; @@ -358,7 +363,7 @@ mod tests { } #[test] - fn test_insert_value_2() { + fn insert_value_2() { let mut tree = MerkleTree::with_capacity(1); let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; @@ -373,7 +378,7 @@ mod tests { } #[test] - fn test_insert_value_3() { + fn insert_value_3() { let mut tree = MerkleTree::with_capacity(1); let values = [[11; 32], [12; 32], [13; 32], [14; 32], [15; 32]]; @@ -405,7 +410,7 @@ mod tests { } #[test] - fn test_authentication_path_1() { + fn authentication_path_1() { let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; let tree = MerkleTree::new(&values); let expected_authentication_path = vec![ @@ -418,7 +423,7 @@ mod tests { } #[test] - fn test_authentication_path_2() { + fn authentication_path_2() { let values = [[1; 32], [2; 32], [3; 32]]; let tree = MerkleTree::new(&values); let expected_authentication_path = vec![ @@ -431,7 +436,7 @@ mod tests { } #[test] - fn test_authentication_path_3() { + fn authentication_path_3() { let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; let tree = MerkleTree::new(&values); let expected_authentication_path = vec![ @@ -445,14 +450,14 @@ mod tests { } #[test] - fn test_authentication_path_4() { + fn authentication_path_4() { let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; let tree = MerkleTree::new(&values); assert!(tree.get_authentication_path_for(5).is_none()); } #[test] - fn test_authentication_path_5() { + fn authentication_path_5() { let values = [[1; 32], [2; 32], [3; 32], [4; 32], [5; 32]]; let tree = MerkleTree::new(&values); let index = 4; @@ -467,7 +472,7 @@ mod tests { } #[test] - fn test_tree_with_63_insertions() { + fn tree_with_63_insertions() { let values = [ hex!("cd00acab0f45736e6c6311f1953becc0b69a062e7c2a7310875d28bdf9ef9c5b"), hex!("0df5a6afbcc7bf126caf7084acfc593593ab512e6ca433c61c1a922be40a04ea"), diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 25542aee..d1afe96d 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -30,6 +30,12 @@ impl Proof { pub fn from_inner(inner: Vec) -> Self { Self(inner) } + + pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { + let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); + let receipt = Receipt::new(inner, circuit_output.to_bytes()); + receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() + } } #[derive(Clone)] @@ -69,20 +75,20 @@ pub fn execute_and_prove( program_with_dependencies: &ProgramWithDependencies, ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { let ProgramWithDependencies { - program, + program: initial_program, dependencies, } = program_with_dependencies; let mut env_builder = ExecutorEnv::builder(); let mut program_outputs = Vec::new(); let initial_call = ChainedCall { - program_id: program.id(), + program_id: initial_program.id(), instruction_data, pre_states, pda_seeds: vec![], }; - let mut chained_calls = VecDeque::from_iter([(initial_call, program)]); + let mut chained_calls = VecDeque::from_iter([(initial_call, initial_program)]); let mut chain_calls_counter = 0; while let Some((chained_call, program)) = chained_calls.pop_front() { if chain_calls_counter >= MAX_NUMBER_CHAINED_CALLS { @@ -113,7 +119,9 @@ pub fn execute_and_prove( chained_calls.push_front((new_call, next_program)); } - chain_calls_counter += 1; + chain_calls_counter = chain_calls_counter + .checked_add(1) + .expect("we check the max depth at the beginning of the loop"); } let circuit_input = PrivacyPreservingCircuitInput { @@ -163,16 +171,10 @@ fn execute_and_prove_program( .receipt) } -impl Proof { - pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool { - let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap(); - let receipt = Receipt::new(inner, circuit_output.to_bytes()); - receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok() - } -} - #[cfg(test)] mod tests { + #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] + use nssa_core::{ Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, account::{Account, AccountId, AccountWithMetadata, data::Data}, diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index a0b9379c..fedd4438 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -4,7 +4,7 @@ use nssa_core::{ account::{Account, Nonce}, encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey}, }; -use sha2::{Digest, Sha256}; +use sha2::{Digest as _, Sha256}; use crate::{AccountId, error::NssaError}; @@ -56,7 +56,7 @@ pub struct Message { impl std::fmt::Debug for Message { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - struct HexDigest<'a>(&'a [u8; 32]); + struct HexDigest<'arr>(&'arr [u8; 32]); impl std::fmt::Debug for HexDigest<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", hex::encode(self.0)) @@ -120,7 +120,7 @@ pub mod tests { account::Account, encryption::{EphemeralPublicKey, ViewingPublicKey}, }; - use sha2::{Digest, Sha256}; + use sha2::{Digest as _, Sha256}; use crate::{ AccountId, @@ -165,7 +165,7 @@ pub mod tests { } #[test] - fn test_encrypted_account_data_constructor() { + fn encrypted_account_data_constructor() { let npk = NullifierPublicKey::from(&[1; 32]); let vpk = ViewingPublicKey::from_scalar([2; 32]); let account = Account::default(); diff --git a/nssa/src/privacy_preserving_transaction/mod.rs b/nssa/src/privacy_preserving_transaction/mod.rs index 48d88181..aab09147 100644 --- a/nssa/src/privacy_preserving_transaction/mod.rs +++ b/nssa/src/privacy_preserving_transaction/mod.rs @@ -1,9 +1,9 @@ +pub use message::Message; +pub use transaction::PrivacyPreservingTransaction; +pub use witness_set::WitnessSet; + pub mod message; pub mod transaction; pub mod witness_set; pub mod circuit; - -pub use message::Message; -pub use transaction::PrivacyPreservingTransaction; -pub use witness_set::WitnessSet; diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 1b89ea19..72119959 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,4 +1,7 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + hash::Hash, +}; use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ @@ -188,7 +191,6 @@ fn check_privacy_preserving_circuit_proof_is_valid( .ok_or(NssaError::InvalidPrivacyPreservingProof) } -use std::hash::Hash; fn n_unique(data: &[T]) -> usize { let set: HashSet<&T> = data.iter().collect(); set.len() @@ -225,7 +227,7 @@ mod tests { } #[test] - fn test_privacy_preserving_transaction_encoding_bytes_roundtrip() { + fn privacy_preserving_transaction_encoding_bytes_roundtrip() { let tx = transaction_for_tests(); let bytes = tx.to_bytes(); let tx_from_bytes = PrivacyPreservingTransaction::from_bytes(&bytes).unwrap(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 29033a50..87b85749 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -24,10 +24,10 @@ pub struct Program { impl Program { pub fn new(bytecode: Vec) -> Result { let binary = risc0_binfmt::ProgramBinary::decode(&bytecode) - .map_err(|_| NssaError::InvalidProgramBytecode)?; + .map_err(NssaError::InvalidProgramBytecode)?; let id = binary .compute_image_id() - .map_err(|_| NssaError::InvalidProgramBytecode)? + .map_err(NssaError::InvalidProgramBytecode)? .into(); Ok(Self { elf: bytecode, id }) } @@ -117,6 +117,7 @@ impl Program { } #[must_use] + #[expect(clippy::non_ascii_literal, reason = "More readable")] pub fn pinata_token() -> Self { use crate::program_methods::PINATA_TOKEN_ELF; Self::new(PINATA_TOKEN_ELF.to_vec()).expect("Piñata program must be a valid R0BF file") @@ -286,7 +287,7 @@ mod tests { } #[test] - fn test_program_execution() { + fn program_execution() { let program = Program::simple_balance_transfer(); let balance_to_move: u128 = 11_223_344_556_677; let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); @@ -320,7 +321,7 @@ mod tests { } #[test] - fn test_builtin_programs() { + fn builtin_programs() { let auth_transfer_program = Program::authenticated_transfer_program(); let token_program = Program::token(); let pinata_program = Program::pinata(); diff --git a/nssa/src/program_deployment_transaction/mod.rs b/nssa/src/program_deployment_transaction/mod.rs index b498826e..d1fc7d0d 100644 --- a/nssa/src/program_deployment_transaction/mod.rs +++ b/nssa/src/program_deployment_transaction/mod.rs @@ -1,5 +1,5 @@ -mod message; -mod transaction; - pub use message::Message; pub use transaction::ProgramDeploymentTransaction; + +mod message; +mod transaction; diff --git a/nssa/src/public_transaction/mod.rs b/nssa/src/public_transaction/mod.rs index 278e9df7..1af61e10 100644 --- a/nssa/src/public_transaction/mod.rs +++ b/nssa/src/public_transaction/mod.rs @@ -1,7 +1,7 @@ -mod message; -mod transaction; -mod witness_set; - pub use message::Message; pub use transaction::PublicTransaction; pub use witness_set::WitnessSet; + +mod message; +mod transaction; +mod witness_set; diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index fdd55510..d19c12d4 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -6,7 +6,7 @@ use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution}, }; -use sha2::{Digest, digest::FixedOutput}; +use sha2::{Digest as _, digest::FixedOutput as _}; use crate::{ V02State, ensure, @@ -216,7 +216,9 @@ impl PublicTransaction { chained_calls.push_front((new_call, Some(chained_call.program_id))); } - chain_calls_counter += 1; + chain_calls_counter = chain_calls_counter + .checked_add(1) + .expect("we check the max depth at the beginning of the loop"); } // Check that all modified uninitialized accounts where claimed @@ -242,7 +244,7 @@ impl PublicTransaction { #[cfg(test)] pub mod tests { - use sha2::{Digest, digest::FixedOutput}; + use sha2::{Digest as _, digest::FixedOutput as _}; use crate::{ AccountId, PrivateKey, PublicKey, PublicTransaction, Signature, V02State, @@ -282,7 +284,7 @@ pub mod tests { } #[test] - fn test_new_constructor() { + fn new_constructor() { let tx = transaction_for_tests(); let message = tx.message().clone(); let witness_set = tx.witness_set().clone(); @@ -292,19 +294,19 @@ pub mod tests { } #[test] - fn test_message_getter() { + fn message_getter() { let tx = transaction_for_tests(); assert_eq!(&tx.message, tx.message()); } #[test] - fn test_witness_set_getter() { + fn witness_set_getter() { let tx = transaction_for_tests(); assert_eq!(&tx.witness_set, tx.witness_set()); } #[test] - fn test_signer_account_ids() { + fn signer_account_ids() { let tx = transaction_for_tests(); let expected_signer_account_ids = vec![ AccountId::new([ @@ -321,7 +323,7 @@ pub mod tests { } #[test] - fn test_public_transaction_encoding_bytes_roundtrip() { + fn public_transaction_encoding_bytes_roundtrip() { let tx = transaction_for_tests(); let bytes = tx.to_bytes(); let tx_from_bytes = PublicTransaction::from_bytes(&bytes).unwrap(); @@ -329,7 +331,7 @@ pub mod tests { } #[test] - fn test_hash_is_sha256_of_transaction_bytes() { + fn hash_is_sha256_of_transaction_bytes() { let tx = transaction_for_tests(); let hash = tx.hash(); let expected_hash: [u8; 32] = { @@ -342,7 +344,7 @@ pub mod tests { } #[test] - fn test_account_id_list_cant_have_duplicates() { + fn account_id_list_cant_have_duplicates() { let (key1, _, addr1, _) = keys_for_tests(); let state = state_for_tests(); let nonces = vec![0, 0]; @@ -362,7 +364,7 @@ pub mod tests { } #[test] - fn test_number_of_nonces_must_match_number_of_signatures() { + fn number_of_nonces_must_match_number_of_signatures() { let (key1, key2, addr1, addr2) = keys_for_tests(); let state = state_for_tests(); let nonces = vec![0]; @@ -382,7 +384,7 @@ pub mod tests { } #[test] - fn test_all_signatures_must_be_valid() { + fn all_signatures_must_be_valid() { let (key1, key2, addr1, addr2) = keys_for_tests(); let state = state_for_tests(); let nonces = vec![0, 0]; @@ -403,7 +405,7 @@ pub mod tests { } #[test] - fn test_nonces_must_match_the_state_current_nonces() { + fn nonces_must_match_the_state_current_nonces() { let (key1, key2, addr1, addr2) = keys_for_tests(); let state = state_for_tests(); let nonces = vec![0, 1]; @@ -423,7 +425,7 @@ pub mod tests { } #[test] - fn test_program_id_must_belong_to_bulitin_program_ids() { + fn program_id_must_belong_to_bulitin_program_ids() { let (key1, key2, addr1, addr2) = keys_for_tests(); let state = state_for_tests(); let nonces = vec![0, 0]; diff --git a/nssa/src/public_transaction/witness_set.rs b/nssa/src/public_transaction/witness_set.rs index db139d86..59ef5c50 100644 --- a/nssa/src/public_transaction/witness_set.rs +++ b/nssa/src/public_transaction/witness_set.rs @@ -60,7 +60,7 @@ mod tests { use crate::AccountId; #[test] - fn test_for_message_constructor() { + fn for_message_constructor() { let key1 = PrivateKey::try_new([1; 32]).unwrap(); let key2 = PrivateKey::try_new([2; 32]).unwrap(); let pubkey1 = PublicKey::new_from_private_key(&key1); diff --git a/nssa/src/signature/bip340_test_vectors.rs b/nssa/src/signature/bip340_test_vectors.rs index ae473b9c..b7a992b6 100644 --- a/nssa/src/signature/bip340_test_vectors.rs +++ b/nssa/src/signature/bip340_test_vectors.rs @@ -1,9 +1,5 @@ use crate::{PrivateKey, PublicKey, Signature}; -fn hex_to_bytes(hex: &str) -> [u8; N] { - hex::decode(hex).unwrap().try_into().unwrap() -} - pub struct TestVector { pub seckey: Option, pub pubkey: PublicKey, @@ -365,3 +361,7 @@ pub fn test_vectors() -> Vec { }, ] } + +fn hex_to_bytes(hex: &str) -> [u8; N] { + hex::decode(hex).unwrap().try_into().unwrap() +} diff --git a/nssa/src/signature/mod.rs b/nssa/src/signature/mod.rs index 7e2928ea..63377f15 100644 --- a/nssa/src/signature/mod.rs +++ b/nssa/src/signature/mod.rs @@ -1,10 +1,10 @@ -mod private_key; -mod public_key; - use borsh::{BorshDeserialize, BorshSerialize}; pub use private_key::PrivateKey; pub use public_key::PublicKey; -use rand::{RngCore, rngs::OsRng}; +use rand::{RngCore as _, rngs::OsRng}; + +mod private_key; +mod public_key; #[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Signature { @@ -20,7 +20,7 @@ impl std::fmt::Debug for Signature { impl Signature { #[must_use] pub fn new(key: &PrivateKey, message: &[u8]) -> Self { - let mut aux_random = [0u8; 32]; + let mut aux_random = [0_u8; 32]; OsRng.fill_bytes(&mut aux_random); Self::new_with_aux_random(key, message, aux_random) } @@ -64,7 +64,7 @@ mod tests { } #[test] - fn test_signature_generation_from_bip340_test_vectors() { + fn signature_generation_from_bip340_test_vectors() { for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { let Some(private_key) = test_vector.seckey else { continue; @@ -87,7 +87,7 @@ mod tests { } #[test] - fn test_signature_verification_from_bip340_test_vectors() { + fn signature_verification_from_bip340_test_vectors() { for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { let message = test_vector.message.unwrap_or(vec![]); let expected_result = test_vector.verification_result; diff --git a/nssa/src/signature/private_key.rs b/nssa/src/signature/private_key.rs index 03121a90..f643406a 100644 --- a/nssa/src/signature/private_key.rs +++ b/nssa/src/signature/private_key.rs @@ -1,4 +1,4 @@ -use rand::{Rng, rngs::OsRng}; +use rand::{Rng as _, rngs::OsRng}; use serde::{Deserialize, Serialize}; use crate::error::NssaError; @@ -42,13 +42,13 @@ impl PrivateKey { mod tests { use super::*; #[test] - fn test_value_getter() { + fn value_getter() { let key = PrivateKey::try_new([1; 32]).unwrap(); assert_eq!(key.value(), &key.0); } #[test] - fn test_produce_key() { + fn produce_key() { let _key = PrivateKey::new_os_random(); } } diff --git a/nssa/src/signature/public_key.rs b/nssa/src/signature/public_key.rs index d916db23..0522e114 100644 --- a/nssa/src/signature/public_key.rs +++ b/nssa/src/signature/public_key.rs @@ -1,7 +1,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::account::AccountId; use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; +use sha2::{Digest as _, Sha256}; use crate::{PrivateKey, error::NssaError}; @@ -16,15 +16,10 @@ impl std::fmt::Debug for PublicKey { impl BorshDeserialize for PublicKey { fn deserialize_reader(reader: &mut R) -> std::io::Result { - let mut buf = [0u8; 32]; + let mut buf = [0_u8; 32]; reader.read_exact(&mut buf)?; - Self::try_new(buf).map_err(|_| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Invalid public key: not a valid point", - ) - }) + Self::try_new(buf).map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err)) } } @@ -44,7 +39,7 @@ impl PublicKey { pub fn try_new(value: [u8; 32]) -> Result { // Check point is valid let _ = secp256k1::XOnlyPublicKey::from_byte_array(value) - .map_err(|_| NssaError::InvalidPublicKey)?; + .map_err(NssaError::InvalidPublicKey)?; Ok(Self(value)) } @@ -71,7 +66,7 @@ mod test { use crate::{PublicKey, error::NssaError, signature::bip340_test_vectors}; #[test] - fn test_try_new_invalid_public_key_from_bip340_test_vectors_5() { + fn try_new_invalid_public_key_from_bip340_test_vectors_5() { let value_invalid_key = [ 238, 253, 234, 76, 219, 103, 119, 80, 164, 32, 254, 232, 7, 234, 207, 33, 235, 152, 152, 174, 121, 185, 118, 135, 102, 228, 250, 160, 74, 45, 74, 52, @@ -79,11 +74,11 @@ mod test { let result = PublicKey::try_new(value_invalid_key); - assert!(matches!(result, Err(NssaError::InvalidPublicKey))); + assert!(matches!(result, Err(NssaError::InvalidPublicKey(_)))); } #[test] - fn test_try_new_invalid_public_key_from_bip340_test_vector_14() { + fn try_new_invalid_public_key_from_bip340_test_vector_14() { let value_invalid_key = [ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 255, 255, 252, 48, @@ -91,11 +86,11 @@ mod test { let result = PublicKey::try_new(value_invalid_key); - assert!(matches!(result, Err(NssaError::InvalidPublicKey))); + assert!(matches!(result, Err(NssaError::InvalidPublicKey(_)))); } #[test] - fn test_try_new_valid_public_keys() { + fn try_new_valid_public_keys() { for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { let expected_public_key = test_vector.pubkey; let public_key = PublicKey::try_new(*expected_public_key.value()).unwrap(); @@ -104,7 +99,7 @@ mod test { } #[test] - fn test_public_key_generation_from_bip340_test_vectors() { + fn public_key_generation_from_bip340_test_vectors() { for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() { let Some(private_key) = &test_vector.seckey else { continue; @@ -119,7 +114,7 @@ mod test { } #[test] - fn test_correct_ser_deser_roundtrip() { + fn correct_ser_deser_roundtrip() { let pub_key = PublicKey::try_new([42; 32]).unwrap(); let pub_key_borsh_ser = borsh::to_vec(&pub_key).unwrap(); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 2c99556f..2210ae5c 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -160,6 +160,10 @@ impl V02State { ) -> Result<(), NssaError> { let state_diff = tx.validate_and_produce_public_state_diff(self)?; + #[expect( + clippy::iter_over_hash_type, + reason = "Iteration order doesn't matter here" + )] for (account_id, post) in state_diff { let current_account = self.get_account_by_id_mut(account_id); @@ -168,7 +172,10 @@ impl V02State { for account_id in tx.signer_account_ids() { let current_account = self.get_account_by_id_mut(account_id); - current_account.nonce += 1; + current_account.nonce = current_account + .nonce + .checked_add(1) + .ok_or(NssaError::MaxAccountNonceReached)?; } Ok(()) @@ -196,6 +203,10 @@ impl V02State { self.private_state.1.extend(new_nullifiers); // 4. Update public accounts + #[expect( + clippy::iter_over_hash_type, + reason = "Iteration order doesn't matter here" + )] for (account_id, post) in public_state_diff { let current_account = self.get_account_by_id_mut(account_id); *current_account = post; @@ -204,7 +215,10 @@ impl V02State { // 5. Increment nonces for public signers for account_id in tx.signer_account_ids() { let current_account = self.get_account_by_id_mut(account_id); - current_account.nonce += 1; + current_account.nonce = current_account + .nonce + .checked_add(1) + .ok_or(NssaError::MaxAccountNonceReached)?; } Ok(()) @@ -252,7 +266,7 @@ impl V02State { for commitment in new_commitments { if self.private_state.0.contains(commitment) { return Err(NssaError::InvalidInput( - "Commitment already seen".to_string(), + "Commitment already seen".to_owned(), )); } } @@ -265,13 +279,11 @@ impl V02State { ) -> Result<(), NssaError> { for (nullifier, digest) in new_nullifiers { if self.private_state.1.contains(nullifier) { - return Err(NssaError::InvalidInput( - "Nullifier already seen".to_string(), - )); + return Err(NssaError::InvalidInput("Nullifier already seen".to_owned())); } if !self.private_state.0.root_history.contains(digest) { return Err(NssaError::InvalidInput( - "Unrecognized commitment set digest".to_string(), + "Unrecognized commitment set digest".to_owned(), )); } } @@ -313,6 +325,11 @@ impl V02State { #[cfg(test)] pub mod tests { + #![expect( + clippy::arithmetic_side_effects, + clippy::shadow_unrelated, + reason = "We don't care about it in tests" + )] use std::collections::HashMap; @@ -341,202 +358,6 @@ pub mod tests { state::MAX_NUMBER_CHAINED_CALLS, }; - fn transfer_transaction( - from: AccountId, - from_key: &PrivateKey, - nonce: u128, - to: AccountId, - balance: u128, - ) -> PublicTransaction { - let account_ids = vec![from, to]; - let nonces = vec![nonce]; - let program_id = Program::authenticated_transfer_program().id(); - let message = - public_transaction::Message::try_new(program_id, account_ids, nonces, balance).unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[from_key]); - PublicTransaction::new(message, witness_set) - } - - #[test] - fn test_new_with_genesis() { - let key1 = PrivateKey::try_new([1; 32]).unwrap(); - let key2 = PrivateKey::try_new([2; 32]).unwrap(); - let addr1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); - let addr2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); - let initial_data = [(addr1, 100u128), (addr2, 151u128)]; - let authenticated_transfers_program = Program::authenticated_transfer_program(); - let expected_public_state = { - let mut this = HashMap::new(); - this.insert( - addr1, - Account { - balance: 100, - program_owner: authenticated_transfers_program.id(), - ..Account::default() - }, - ); - this.insert( - addr2, - Account { - balance: 151, - program_owner: authenticated_transfers_program.id(), - ..Account::default() - }, - ); - this - }; - let expected_builtin_programs = { - let mut this = HashMap::new(); - this.insert( - authenticated_transfers_program.id(), - authenticated_transfers_program, - ); - this.insert(Program::token().id(), Program::token()); - this.insert(Program::amm().id(), Program::amm()); - this - }; - - let state = V02State::new_with_genesis_accounts(&initial_data, &[]); - - assert_eq!(state.public_state, expected_public_state); - assert_eq!(state.programs, expected_builtin_programs); - } - - #[test] - fn test_insert_program() { - let mut state = V02State::new_with_genesis_accounts(&[], &[]); - let program_to_insert = Program::simple_balance_transfer(); - let program_id = program_to_insert.id(); - assert!(!state.programs.contains_key(&program_id)); - - state.insert_program(program_to_insert); - - assert!(state.programs.contains_key(&program_id)); - } - - #[test] - fn test_get_account_by_account_id_non_default_account() { - let key = PrivateKey::try_new([1; 32]).unwrap(); - let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); - let initial_data = [(account_id, 100u128)]; - let state = V02State::new_with_genesis_accounts(&initial_data, &[]); - let expected_account = state.public_state.get(&account_id).unwrap(); - - let account = state.get_account_by_id(account_id); - - assert_eq!(&account, expected_account); - } - - #[test] - fn test_get_account_by_account_id_default_account() { - let addr2 = AccountId::new([0; 32]); - let state = V02State::new_with_genesis_accounts(&[], &[]); - let expected_account = Account::default(); - - let account = state.get_account_by_id(addr2); - - assert_eq!(account, expected_account); - } - - #[test] - fn test_builtin_programs_getter() { - let state = V02State::new_with_genesis_accounts(&[], &[]); - - let builtin_programs = state.programs(); - - assert_eq!(builtin_programs, &state.programs); - } - - #[test] - fn transition_from_authenticated_transfer_program_invocation_default_account_destination() { - let key = PrivateKey::try_new([1; 32]).unwrap(); - let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); - let initial_data = [(account_id, 100)]; - let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); - let from = account_id; - let to = AccountId::new([2; 32]); - assert_eq!(state.get_account_by_id(to), Account::default()); - let balance_to_move = 5; - - let tx = transfer_transaction(from, &key, 0, to, balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); - - assert_eq!(state.get_account_by_id(from).balance, 95); - assert_eq!(state.get_account_by_id(to).balance, 5); - assert_eq!(state.get_account_by_id(from).nonce, 1); - assert_eq!(state.get_account_by_id(to).nonce, 0); - } - - #[test] - fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() { - let key = PrivateKey::try_new([1; 32]).unwrap(); - let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); - let initial_data = [(account_id, 100)]; - let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); - let from = account_id; - let from_key = key; - let to = AccountId::new([2; 32]); - let balance_to_move = 101; - assert!(state.get_account_by_id(from).balance < balance_to_move); - - let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move); - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); - assert_eq!(state.get_account_by_id(from).balance, 100); - assert_eq!(state.get_account_by_id(to).balance, 0); - assert_eq!(state.get_account_by_id(from).nonce, 0); - assert_eq!(state.get_account_by_id(to).nonce, 0); - } - - #[test] - fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() { - let key1 = PrivateKey::try_new([1; 32]).unwrap(); - let key2 = PrivateKey::try_new([2; 32]).unwrap(); - let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); - let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); - let initial_data = [(account_id1, 100), (account_id2, 200)]; - let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); - let from = account_id2; - let from_key = key2; - let to = account_id1; - assert_ne!(state.get_account_by_id(to), Account::default()); - let balance_to_move = 8; - - let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); - - assert_eq!(state.get_account_by_id(from).balance, 192); - assert_eq!(state.get_account_by_id(to).balance, 108); - assert_eq!(state.get_account_by_id(from).nonce, 1); - assert_eq!(state.get_account_by_id(to).nonce, 0); - } - - #[test] - fn transition_from_sequence_of_authenticated_transfer_program_invocations() { - let key1 = PrivateKey::try_new([8; 32]).unwrap(); - let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); - let key2 = PrivateKey::try_new([2; 32]).unwrap(); - let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); - let initial_data = [(account_id1, 100)]; - let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); - let account_id3 = AccountId::new([3; 32]); - let balance_to_move = 5; - - let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); - let balance_to_move = 3; - let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move); - state.transition_from_public_transaction(&tx).unwrap(); - - assert_eq!(state.get_account_by_id(account_id1).balance, 95); - assert_eq!(state.get_account_by_id(account_id2).balance, 2); - assert_eq!(state.get_account_by_id(account_id3).balance, 3); - assert_eq!(state.get_account_by_id(account_id1).nonce, 1); - assert_eq!(state.get_account_by_id(account_id2).nonce, 1); - assert_eq!(state.get_account_by_id(account_id3).nonce, 0); - } - impl V02State { pub fn force_insert_account(&mut self, account_id: AccountId, account: Account) { self.public_state.insert(account_id, account); @@ -608,253 +429,6 @@ pub mod tests { } } - #[test] - fn test_program_should_fail_if_modifies_nonces() { - let initial_data = [(AccountId::new([1; 32]), 100)]; - let mut state = - V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let account_ids = vec![AccountId::new([1; 32])]; - let program_id = Program::nonce_changer_program().id(); - let message = - public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_if_output_accounts_exceed_inputs() { - let initial_data = [(AccountId::new([1; 32]), 100)]; - let mut state = - V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let account_ids = vec![AccountId::new([1; 32])]; - let program_id = Program::extra_output_program().id(); - let message = - public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_with_missing_output_accounts() { - let initial_data = [(AccountId::new([1; 32]), 100)]; - let mut state = - V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let account_ids = vec![AccountId::new([1; 32]), AccountId::new([2; 32])]; - let program_id = Program::missing_output_program().id(); - let message = - public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() { - let initial_data = [(AccountId::new([1; 32]), 0)]; - let mut state = - V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let account_id = AccountId::new([1; 32]); - let account = state.get_account_by_id(account_id); - // Assert the target account only differs from the default account in the program owner - // field - assert_ne!(account.program_owner, Account::default().program_owner); - assert_eq!(account.balance, Account::default().balance); - assert_eq!(account.nonce, Account::default().nonce); - assert_eq!(account.data, Account::default().data); - let program_id = Program::program_owner_changer().id(); - let message = - public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_balance() { - let initial_data = []; - let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) - .with_test_programs() - .with_non_default_accounts_but_default_program_owners(); - let account_id = AccountId::new([255; 32]); - let account = state.get_account_by_id(account_id); - // Assert the target account only differs from the default account in balance field - assert_eq!(account.program_owner, Account::default().program_owner); - assert_ne!(account.balance, Account::default().balance); - assert_eq!(account.nonce, Account::default().nonce); - assert_eq!(account.data, Account::default().data); - let program_id = Program::program_owner_changer().id(); - let message = - public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() { - let initial_data = []; - let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) - .with_test_programs() - .with_non_default_accounts_but_default_program_owners(); - let account_id = AccountId::new([254; 32]); - let account = state.get_account_by_id(account_id); - // Assert the target account only differs from the default account in nonce field - assert_eq!(account.program_owner, Account::default().program_owner); - assert_eq!(account.balance, Account::default().balance); - assert_ne!(account.nonce, Account::default().nonce); - assert_eq!(account.data, Account::default().data); - let program_id = Program::program_owner_changer().id(); - let message = - public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_data() { - let initial_data = []; - let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) - .with_test_programs() - .with_non_default_accounts_but_default_program_owners(); - let account_id = AccountId::new([253; 32]); - let account = state.get_account_by_id(account_id); - // Assert the target account only differs from the default account in data field - assert_eq!(account.program_owner, Account::default().program_owner); - assert_eq!(account.balance, Account::default().balance); - assert_eq!(account.nonce, Account::default().nonce); - assert_ne!(account.data, Account::default().data); - let program_id = Program::program_owner_changer().id(); - let message = - public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_if_transfers_balance_from_non_owned_account() { - let initial_data = [(AccountId::new([1; 32]), 100)]; - let mut state = - V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let sender_account_id = AccountId::new([1; 32]); - let receiver_account_id = AccountId::new([2; 32]); - let balance_to_move: u128 = 1; - let program_id = Program::simple_balance_transfer().id(); - assert_ne!( - state.get_account_by_id(sender_account_id).program_owner, - program_id - ); - let message = public_transaction::Message::try_new( - program_id, - vec![sender_account_id, receiver_account_id], - vec![], - balance_to_move, - ) - .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_if_modifies_data_of_non_owned_account() { - let initial_data = []; - let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) - .with_test_programs() - .with_non_default_accounts_but_default_program_owners(); - let account_id = AccountId::new([255; 32]); - let program_id = Program::data_changer().id(); - - assert_ne!(state.get_account_by_id(account_id), Account::default()); - assert_ne!( - state.get_account_by_id(account_id).program_owner, - program_id - ); - let message = - public_transaction::Message::try_new(program_id, vec![account_id], vec![], vec![0]) - .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_if_does_not_preserve_total_balance_by_minting() { - let initial_data = []; - let mut state = - V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let account_id = AccountId::new([1; 32]); - let program_id = Program::minter().id(); - - let message = - public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - - #[test] - fn test_program_should_fail_if_does_not_preserve_total_balance_by_burning() { - let initial_data = []; - let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) - .with_test_programs() - .with_account_owned_by_burner_program(); - let program_id = Program::burner().id(); - let account_id = AccountId::new([252; 32]); - assert_eq!( - state.get_account_by_id(account_id).program_owner, - program_id - ); - let balance_to_burn: u128 = 1; - assert!(state.get_account_by_id(account_id).balance > balance_to_burn); - - let message = public_transaction::Message::try_new( - program_id, - vec![account_id], - vec![], - balance_to_burn, - ) - .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - let result = state.transition_from_public_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); - } - pub struct TestPublicKeys { pub signing_key: PrivateKey, } @@ -865,12 +439,6 @@ pub mod tests { } } - fn test_public_account_keys_1() -> TestPublicKeys { - TestPublicKeys { - signing_key: PrivateKey::try_new([37; 32]).unwrap(), - } - } - pub struct TestPrivateKeys { pub nsk: NullifierSecretKey, pub vsk: Scalar, @@ -886,1464 +454,6 @@ pub mod tests { } } - pub fn test_private_account_keys_1() -> TestPrivateKeys { - TestPrivateKeys { - nsk: [13; 32], - vsk: [31; 32], - } - } - - pub fn test_private_account_keys_2() -> TestPrivateKeys { - TestPrivateKeys { - nsk: [38; 32], - vsk: [83; 32], - } - } - - fn shielded_balance_transfer_for_tests( - sender_keys: &TestPublicKeys, - recipient_keys: &TestPrivateKeys, - balance_to_move: u128, - state: &V02State, - ) -> PrivacyPreservingTransaction { - let sender = AccountWithMetadata::new( - state.get_account_by_id(sender_keys.account_id()), - true, - sender_keys.account_id(), - ); - - let sender_nonce = sender.account.nonce; - - let recipient = AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); - - let esk = [3; 32]; - let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.vpk()); - let epk = EphemeralPublicKey::from_scalar(esk); - - let (output, proof) = circuit::execute_and_prove( - vec![sender, recipient], - Program::serialize_instruction(balance_to_move).unwrap(), - vec![0, 2], - vec![0xdead_beef], - vec![(recipient_keys.npk(), shared_secret)], - vec![], - vec![None], - &Program::authenticated_transfer_program().into(), - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![sender_keys.account_id()], - vec![sender_nonce], - vec![(recipient_keys.npk(), recipient_keys.vpk(), epk)], - output, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, proof, &[&sender_keys.signing_key]); - PrivacyPreservingTransaction::new(message, witness_set) - } - - fn private_balance_transfer_for_tests( - sender_keys: &TestPrivateKeys, - sender_private_account: &Account, - recipient_keys: &TestPrivateKeys, - balance_to_move: u128, - new_nonces: [Nonce; 2], - state: &V02State, - ) -> PrivacyPreservingTransaction { - let program = Program::authenticated_transfer_program(); - let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); - let sender_pre = - AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk()); - let recipient_pre = - AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); - - let esk_1 = [3; 32]; - let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.vpk()); - let epk_1 = EphemeralPublicKey::from_scalar(esk_1); - - let esk_2 = [3; 32]; - let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.vpk()); - let epk_2 = EphemeralPublicKey::from_scalar(esk_2); - - let (output, proof) = circuit::execute_and_prove( - vec![sender_pre, recipient_pre], - Program::serialize_instruction(balance_to_move).unwrap(), - vec![1, 2], - new_nonces.to_vec(), - vec![ - (sender_keys.npk(), shared_secret_1), - (recipient_keys.npk(), shared_secret_2), - ], - vec![sender_keys.nsk], - vec![state.get_proof_for_commitment(&sender_commitment), None], - &program.into(), - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![], - vec![], - vec![ - (sender_keys.npk(), sender_keys.vpk(), epk_1), - (recipient_keys.npk(), recipient_keys.vpk(), epk_2), - ], - output, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, proof, &[]); - - PrivacyPreservingTransaction::new(message, witness_set) - } - - fn deshielded_balance_transfer_for_tests( - sender_keys: &TestPrivateKeys, - sender_private_account: &Account, - recipient_account_id: &AccountId, - balance_to_move: u128, - new_nonce: Nonce, - state: &V02State, - ) -> PrivacyPreservingTransaction { - let program = Program::authenticated_transfer_program(); - let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); - let sender_pre = - AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk()); - let recipient_pre = AccountWithMetadata::new( - state.get_account_by_id(*recipient_account_id), - false, - *recipient_account_id, - ); - - let esk = [3; 32]; - let shared_secret = SharedSecretKey::new(&esk, &sender_keys.vpk()); - let epk = EphemeralPublicKey::from_scalar(esk); - - let (output, proof) = circuit::execute_and_prove( - vec![sender_pre, recipient_pre], - Program::serialize_instruction(balance_to_move).unwrap(), - vec![1, 0], - vec![new_nonce], - vec![(sender_keys.npk(), shared_secret)], - vec![sender_keys.nsk], - vec![state.get_proof_for_commitment(&sender_commitment)], - &program.into(), - ) - .unwrap(); - - let message = Message::try_from_circuit_output( - vec![*recipient_account_id], - vec![], - vec![(sender_keys.npk(), sender_keys.vpk(), epk)], - output, - ) - .unwrap(); - - let witness_set = WitnessSet::for_message(&message, proof, &[]); - - PrivacyPreservingTransaction::new(message, witness_set) - } - - #[test] - fn test_transition_from_privacy_preserving_transaction_shielded() { - let sender_keys = test_public_account_keys_1(); - let recipient_keys = test_private_account_keys_1(); - - let mut state = - V02State::new_with_genesis_accounts(&[(sender_keys.account_id(), 200)], &[]); - - let balance_to_move = 37; - - let tx = shielded_balance_transfer_for_tests( - &sender_keys, - &recipient_keys, - balance_to_move, - &state, - ); - - let expected_sender_post = { - let mut this = state.get_account_by_id(sender_keys.account_id()); - this.balance -= balance_to_move; - this.nonce += 1; - this - }; - - let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap(); - assert!(!state.private_state.0.contains(&expected_new_commitment)); - - state - .transition_from_privacy_preserving_transaction(&tx) - .unwrap(); - - let sender_post = state.get_account_by_id(sender_keys.account_id()); - assert_eq!(sender_post, expected_sender_post); - assert!(state.private_state.0.contains(&expected_new_commitment)); - - assert_eq!( - state.get_account_by_id(sender_keys.account_id()).balance, - 200 - balance_to_move - ); - } - - #[test] - fn test_transition_from_privacy_preserving_transaction_private() { - let sender_keys = test_private_account_keys_1(); - let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), - balance: 100, - nonce: 0xdead_beef, - data: Data::default(), - }; - let recipient_keys = test_private_account_keys_2(); - - let mut state = V02State::new_with_genesis_accounts(&[], &[]) - .with_private_account(&sender_keys, &sender_private_account); - - let balance_to_move = 37; - - let tx = private_balance_transfer_for_tests( - &sender_keys, - &sender_private_account, - &recipient_keys, - balance_to_move, - [0xcafe_cafe, 0xfeca_feca], - &state, - ); - - let expected_new_commitment_1 = Commitment::new( - &sender_keys.npk(), - &Account { - program_owner: Program::authenticated_transfer_program().id(), - nonce: 0xcafe_cafe, - balance: sender_private_account.balance - balance_to_move, - data: Data::default(), - }, - ); - - let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account); - let expected_new_nullifier = - Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk); - - let expected_new_commitment_2 = Commitment::new( - &recipient_keys.npk(), - &Account { - program_owner: Program::authenticated_transfer_program().id(), - nonce: 0xfeca_feca, - balance: balance_to_move, - ..Account::default() - }, - ); - - let previous_public_state = state.public_state.clone(); - assert!(state.private_state.0.contains(&sender_pre_commitment)); - assert!(!state.private_state.0.contains(&expected_new_commitment_1)); - assert!(!state.private_state.0.contains(&expected_new_commitment_2)); - assert!(!state.private_state.1.contains(&expected_new_nullifier)); - - state - .transition_from_privacy_preserving_transaction(&tx) - .unwrap(); - - assert_eq!(state.public_state, previous_public_state); - assert!(state.private_state.0.contains(&sender_pre_commitment)); - assert!(state.private_state.0.contains(&expected_new_commitment_1)); - assert!(state.private_state.0.contains(&expected_new_commitment_2)); - assert!(state.private_state.1.contains(&expected_new_nullifier)); - } - - #[test] - fn test_transition_from_privacy_preserving_transaction_deshielded() { - let sender_keys = test_private_account_keys_1(); - let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), - balance: 100, - nonce: 0xdead_beef, - data: Data::default(), - }; - let recipient_keys = test_public_account_keys_1(); - let recipient_initial_balance = 400; - let mut state = V02State::new_with_genesis_accounts( - &[(recipient_keys.account_id(), recipient_initial_balance)], - &[], - ) - .with_private_account(&sender_keys, &sender_private_account); - - let balance_to_move = 37; - - let expected_recipient_post = { - let mut this = state.get_account_by_id(recipient_keys.account_id()); - this.balance += balance_to_move; - this - }; - - let tx = deshielded_balance_transfer_for_tests( - &sender_keys, - &sender_private_account, - &recipient_keys.account_id(), - balance_to_move, - 0xcafe_cafe, - &state, - ); - - let expected_new_commitment = Commitment::new( - &sender_keys.npk(), - &Account { - program_owner: Program::authenticated_transfer_program().id(), - nonce: 0xcafe_cafe, - balance: sender_private_account.balance - balance_to_move, - data: Data::default(), - }, - ); - - let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account); - let expected_new_nullifier = - Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk); - - assert!(state.private_state.0.contains(&sender_pre_commitment)); - assert!(!state.private_state.0.contains(&expected_new_commitment)); - assert!(!state.private_state.1.contains(&expected_new_nullifier)); - - state - .transition_from_privacy_preserving_transaction(&tx) - .unwrap(); - - let recipient_post = state.get_account_by_id(recipient_keys.account_id()); - assert_eq!(recipient_post, expected_recipient_post); - assert!(state.private_state.0.contains(&sender_pre_commitment)); - assert!(state.private_state.0.contains(&expected_new_commitment)); - assert!(state.private_state.1.contains(&expected_new_nullifier)); - assert_eq!( - state.get_account_by_id(recipient_keys.account_id()).balance, - recipient_initial_balance + balance_to_move - ); - } - - #[test] - fn test_burner_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::burner(); - let public_account = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - - let result = execute_and_prove( - vec![public_account], - Program::serialize_instruction(10u128).unwrap(), - vec![0], - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_minter_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::minter(); - let public_account = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 0, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - - let result = execute_and_prove( - vec![public_account], - Program::serialize_instruction(10u128).unwrap(), - vec![0], - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_nonce_changer_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::nonce_changer_program(); - let public_account = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 0, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - - let result = execute_and_prove( - vec![public_account], - Program::serialize_instruction(()).unwrap(), - vec![0], - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() { - let program = Program::data_changer(); - let public_account = AccountWithMetadata::new( - Account { - program_owner: [0, 1, 2, 3, 4, 5, 6, 7], - balance: 0, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - - let result = execute_and_prove( - vec![public_account], - Program::serialize_instruction(vec![0]).unwrap(), - vec![0], - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_data_changer_program_should_fail_for_too_large_data_in_privacy_preserving_circuit() { - let program = Program::data_changer(); - let public_account = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 0, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - - let large_data: Vec = - vec![ - 0; - usize::try_from(nssa_core::account::data::DATA_MAX_LENGTH.as_u64()) - .expect("DATA_MAX_LENGTH fits in usize") - + 1 - ]; - - let result = execute_and_prove( - vec![public_account], - Program::serialize_instruction(large_data).unwrap(), - vec![0], - vec![], - vec![], - vec![], - vec![], - &program.clone().into(), - ); - - assert!(matches!(result, Err(NssaError::ProgramProveFailed(_)))); - } - - #[test] - fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::extra_output_program(); - let public_account = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 0, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - - let result = execute_and_prove( - vec![public_account], - Program::serialize_instruction(()).unwrap(), - vec![0], - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_missing_output_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::missing_output_program(); - let public_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 0, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - let public_account_2 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 0, - ..Account::default() - }, - true, - AccountId::new([1; 32]), - ); - - let result = execute_and_prove( - vec![public_account_1, public_account_2], - Program::serialize_instruction(()).unwrap(), - vec![0, 0], - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_program_owner_changer_should_fail_in_privacy_preserving_circuit() { - let program = Program::program_owner_changer(); - let public_account = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 0, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - - let result = execute_and_prove( - vec![public_account], - Program::serialize_instruction(()).unwrap(), - vec![0], - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() { - let program = Program::simple_balance_transfer(); - let public_account_1 = AccountWithMetadata::new( - Account { - program_owner: [0, 1, 2, 3, 4, 5, 6, 7], - balance: 100, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - let public_account_2 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 0, - ..Account::default() - }, - true, - AccountId::new([1; 32]), - ); - - let result = execute_and_prove( - vec![public_account_1, public_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![0, 0], - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_fails_if_visibility_masks_have_incorrect_lenght() { - let program = Program::simple_balance_transfer(); - let public_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - let public_account_2 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 0, - ..Account::default() - }, - true, - AccountId::new([1; 32]), - ); - - // Setting only one visibility mask for a circuit execution with two pre_state accounts. - let visibility_mask = [0]; - let result = execute_and_prove( - vec![public_account_1, public_account_2], - Program::serialize_instruction(10u128).unwrap(), - visibility_mask.to_vec(), - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_fails_if_insufficient_nonces_are_provided() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); - - // Setting only one nonce for an execution with two private accounts. - let private_account_nonces = [0xdead_beef1]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - private_account_nonces.to_vec(), - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_fails_if_insufficient_keys_are_provided() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); - - // Setting only one key for an execution with two private accounts. - let private_account_keys = [( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - )]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - private_account_keys.to_vec(), - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_fails_if_insufficient_commitment_proofs_are_provided() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); - - // Setting no second commitment proof. - let private_account_membership_proofs = [Some((0, vec![]))]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - private_account_membership_proofs.to_vec(), - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_fails_if_insufficient_auth_keys_are_provided() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); - - // Setting no auth key for an execution with one non default private accounts. - let private_account_nsks = []; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - private_account_nsks.to_vec(), - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_fails_if_invalid_auth_keys_are_provided() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); - - let private_account_keys = [ - // First private account is the sender - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - // Second private account is the recipient - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ]; - - // Setting the recipient key to authorize the sender. - // This should be set to the sender private account in - // a normal circumstance. The recipient can't authorize this. - let private_account_nsks = [recipient_keys.nsk]; - let private_account_membership_proofs = [Some((0, vec![]))]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - private_account_keys.to_vec(), - private_account_nsks.to_vec(), - private_account_membership_proofs.to_vec(), - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_should_fail_if_new_private_account_with_non_default_balance_is_provided() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = AccountWithMetadata::new( - Account { - // Non default balance - balance: 1, - ..Account::default() - }, - false, - &recipient_keys.npk(), - ); - - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_provided() - { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = AccountWithMetadata::new( - Account { - // Non default program_owner - program_owner: [0, 1, 2, 3, 4, 5, 6, 7], - ..Account::default() - }, - false, - &recipient_keys.npk(), - ); - - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_should_fail_if_new_private_account_with_non_default_data_is_provided() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = AccountWithMetadata::new( - Account { - // Non default data - data: b"hola mundo".to_vec().try_into().unwrap(), - ..Account::default() - }, - false, - &recipient_keys.npk(), - ); - - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = AccountWithMetadata::new( - Account { - // Non default nonce - nonce: 0xdead_beef, - ..Account::default() - }, - false, - &recipient_keys.npk(), - ); - - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_should_fail_if_new_private_account_is_provided_with_default_values_but_marked_as_authorized() - { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = AccountWithMetadata::new( - Account::default(), - // This should be set to false in normal circumstances - true, - &recipient_keys.npk(), - ); - - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_should_fail_with_invalid_visibility_mask_value() { - let program = Program::simple_balance_transfer(); - let public_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - let public_account_2 = - AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); - - let visibility_mask = [0, 3]; - let result = execute_and_prove( - vec![public_account_1, public_account_2], - Program::serialize_instruction(10u128).unwrap(), - visibility_mask.to_vec(), - vec![], - vec![], - vec![], - vec![], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_should_fail_with_too_many_nonces() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); - - // Setting three new private account nonces for a circuit execution with only two private - // accounts. - let private_account_nonces = [0xdead_beef1, 0xdead_beef2, 0xdead_beef3]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - private_account_nonces.to_vec(), - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_should_fail_with_too_many_private_account_keys() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); - - // Setting three private account keys for a circuit execution with only two private - // accounts. - let private_account_keys = [ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ( - sender_keys.npk(), - SharedSecretKey::new(&[57; 32], &sender_keys.vpk()), - ), - ]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - vec![1, 2], - vec![0xdead_beef1, 0xdead_beef2], - private_account_keys.to_vec(), - vec![sender_keys.nsk], - vec![Some((0, vec![]))], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_circuit_should_fail_with_too_many_private_account_auth_keys() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let recipient_keys = test_private_account_keys_2(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - let private_account_2 = - AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); - - // Setting two private account keys for a circuit execution with only one non default - // private account (visibility mask equal to 1 means that auth keys are expected). - let visibility_mask = [1, 2]; - let private_account_nsks = [sender_keys.nsk, recipient_keys.nsk]; - let private_account_membership_proofs = [Some((0, vec![])), Some((1, vec![]))]; - let result = execute_and_prove( - vec![private_account_1, private_account_2], - Program::serialize_instruction(10u128).unwrap(), - visibility_mask.to_vec(), - vec![0xdead_beef1, 0xdead_beef2], - vec![ - ( - sender_keys.npk(), - SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - ), - ( - recipient_keys.npk(), - SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - ), - ], - private_account_nsks.to_vec(), - private_account_membership_proofs.to_vec(), - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_private_accounts_can_only_be_initialized_once() { - let sender_keys = test_private_account_keys_1(); - let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), - balance: 100, - nonce: 0xdead_beef, - data: Data::default(), - }; - let recipient_keys = test_private_account_keys_2(); - - let mut state = V02State::new_with_genesis_accounts(&[], &[]) - .with_private_account(&sender_keys, &sender_private_account); - - let balance_to_move = 37; - - let tx = private_balance_transfer_for_tests( - &sender_keys, - &sender_private_account, - &recipient_keys, - balance_to_move, - [0xcafe_cafe, 0xfeca_feca], - &state, - ); - - state - .transition_from_privacy_preserving_transaction(&tx) - .unwrap(); - - let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), - balance: 100 - balance_to_move, - nonce: 0xcafe_cafe, - data: Data::default(), - }; - - let tx = private_balance_transfer_for_tests( - &sender_keys, - &sender_private_account, - &recipient_keys, - balance_to_move, - [0x1234, 0x5678], - &state, - ); - - let result = state.transition_from_privacy_preserving_transaction(&tx); - - assert!(matches!(result, Err(NssaError::InvalidInput(_)))); - let NssaError::InvalidInput(error_message) = result.err().unwrap() else { - panic!("Incorrect message error"); - }; - let expected_error_message = "Nullifier already seen".to_string(); - assert_eq!(error_message, expected_error_message); - } - - #[test] - fn test_circuit_should_fail_if_there_are_repeated_ids() { - let program = Program::simple_balance_transfer(); - let sender_keys = test_private_account_keys_1(); - let private_account_1 = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 100, - ..Account::default() - }, - true, - &sender_keys.npk(), - ); - - let visibility_mask = [1, 1]; - let private_account_nsks = [sender_keys.nsk, sender_keys.nsk]; - let private_account_membership_proofs = [Some((1, vec![])), Some((1, vec![]))]; - let shared_secret = SharedSecretKey::new(&[55; 32], &sender_keys.vpk()); - let result = execute_and_prove( - vec![private_account_1.clone(), private_account_1], - Program::serialize_instruction(100u128).unwrap(), - visibility_mask.to_vec(), - vec![0xdead_beef1, 0xdead_beef2], - vec![ - (sender_keys.npk(), shared_secret), - (sender_keys.npk(), shared_secret), - ], - private_account_nsks.to_vec(), - private_account_membership_proofs.to_vec(), - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn test_claiming_mechanism() { - let program = Program::authenticated_transfer_program(); - let key = PrivateKey::try_new([1; 32]).unwrap(); - let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); - let initial_balance = 100; - let initial_data = [(account_id, initial_balance)]; - let mut state = - V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let from = account_id; - let from_key = key; - let to = AccountId::new([2; 32]); - let amount: u128 = 37; - - // Check the recipient is an uninitialized account - assert_eq!(state.get_account_by_id(to), Account::default()); - - let expected_recipient_post = Account { - program_owner: program.id(), - balance: amount, - ..Account::default() - }; - - let message = - public_transaction::Message::try_new(program.id(), vec![from, to], vec![0], amount) - .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); - let tx = PublicTransaction::new(message, witness_set); - - state.transition_from_public_transaction(&tx).unwrap(); - - let recipient_post = state.get_account_by_id(to); - - assert_eq!(recipient_post, expected_recipient_post); - } - - #[test] - fn test_public_chained_call() { - let program = Program::chain_caller(); - let key = PrivateKey::try_new([1; 32]).unwrap(); - let from = AccountId::from(&PublicKey::new_from_private_key(&key)); - let to = AccountId::new([2; 32]); - let initial_balance = 1000; - let initial_data = [(from, initial_balance), (to, 0)]; - let mut state = - V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let from_key = key; - let amount: u128 = 37; - let instruction: (u128, ProgramId, u32, Option) = ( - amount, - Program::authenticated_transfer_program().id(), - 2, - None, - ); - - let expected_to_post = Account { - program_owner: Program::authenticated_transfer_program().id(), - balance: amount * 2, // The `chain_caller` chains the program twice - ..Account::default() - }; - - let message = public_transaction::Message::try_new( - program.id(), - vec![to, from], // The chain_caller program permutes the account order in the chain - // call - vec![0], - instruction, - ) - .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); - let tx = PublicTransaction::new(message, witness_set); - - state.transition_from_public_transaction(&tx).unwrap(); - - let from_post = state.get_account_by_id(from); - let to_post = state.get_account_by_id(to); - // The `chain_caller` program calls the program twice - assert_eq!(from_post.balance, initial_balance - 2 * amount); - assert_eq!(to_post, expected_to_post); - } - - #[test] - fn test_execution_fails_if_chained_calls_exceeds_depth() { - let program = Program::chain_caller(); - let key = PrivateKey::try_new([1; 32]).unwrap(); - let from = AccountId::from(&PublicKey::new_from_private_key(&key)); - let to = AccountId::new([2; 32]); - let initial_balance = 100; - let initial_data = [(from, initial_balance), (to, 0)]; - let mut state = - V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); - let from_key = key; - let amount: u128 = 0; - let instruction: (u128, ProgramId, u32, Option) = ( - amount, - Program::authenticated_transfer_program().id(), - u32::try_from(MAX_NUMBER_CHAINED_CALLS).expect("MAX_NUMBER_CHAINED_CALLS fits in u32") - + 1, - None, - ); - - let message = public_transaction::Message::try_new( - program.id(), - vec![to, from], // The chain_caller program permutes the account order in the chain - // call - vec![0], - instruction, - ) - .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); - let tx = PublicTransaction::new(message, witness_set); - - let result = state.transition_from_public_transaction(&tx); - assert!(matches!( - result, - Err(NssaError::MaxChainedCallsDepthExceeded) - )); - } - struct PrivateKeysForTests; impl PrivateKeysForTests { @@ -2593,7 +703,7 @@ pub mod tests { fn user_token_a_holding() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::user_token_a_holding_init(), @@ -2605,7 +715,7 @@ pub mod tests { fn user_token_b_holding() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::user_token_b_holding_init(), @@ -2617,7 +727,7 @@ pub mod tests { fn pool_definition_init() -> Account { Account { program_owner: Program::amm().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -2627,7 +737,7 @@ pub mod tests { liquidity_pool_supply: BalanceForTests::pool_lp_supply_init(), reserve_a: BalanceForTests::vault_a_balance_init(), reserve_b: BalanceForTests::vault_b_balance_init(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -2637,7 +747,7 @@ pub mod tests { fn token_a_definition_account() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: BalanceForTests::token_a_supply(), @@ -2650,7 +760,7 @@ pub mod tests { fn token_b_definition_acc() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: BalanceForTests::token_b_supply(), @@ -2663,7 +773,7 @@ pub mod tests { fn token_lp_definition_acc() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: BalanceForTests::token_lp_supply(), @@ -2676,7 +786,7 @@ pub mod tests { fn vault_a_init() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_balance_init(), @@ -2688,7 +798,7 @@ pub mod tests { fn vault_b_init() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_balance_init(), @@ -2700,7 +810,7 @@ pub mod tests { fn user_token_lp_holding() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_lp_definition_id(), balance: BalanceForTests::user_token_lp_holding_init(), @@ -2712,7 +822,7 @@ pub mod tests { fn vault_a_swap_1() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_balance_swap_1(), @@ -2724,7 +834,7 @@ pub mod tests { fn vault_b_swap_1() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_balance_swap_1(), @@ -2736,7 +846,7 @@ pub mod tests { fn pool_definition_swap_1() -> Account { Account { program_owner: Program::amm().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -2746,7 +856,7 @@ pub mod tests { liquidity_pool_supply: BalanceForTests::pool_lp_supply_init(), reserve_a: BalanceForTests::vault_a_balance_swap_1(), reserve_b: BalanceForTests::vault_b_balance_swap_1(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -2756,7 +866,7 @@ pub mod tests { fn user_token_a_holding_swap_1() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::user_token_a_holding_swap_1(), @@ -2768,7 +878,7 @@ pub mod tests { fn user_token_b_holding_swap_1() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::user_token_b_holding_swap_1(), @@ -2780,7 +890,7 @@ pub mod tests { fn vault_a_swap_2() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_balance_swap_2(), @@ -2792,7 +902,7 @@ pub mod tests { fn vault_b_swap_2() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_balance_swap_2(), @@ -2804,7 +914,7 @@ pub mod tests { fn pool_definition_swap_2() -> Account { Account { program_owner: Program::amm().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -2814,7 +924,7 @@ pub mod tests { liquidity_pool_supply: BalanceForTests::pool_lp_supply_init(), reserve_a: BalanceForTests::vault_a_balance_swap_2(), reserve_b: BalanceForTests::vault_b_balance_swap_2(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -2824,7 +934,7 @@ pub mod tests { fn user_token_a_holding_swap_2() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::user_token_a_holding_swap_2(), @@ -2836,7 +946,7 @@ pub mod tests { fn user_token_b_holding_swap_2() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::user_token_b_holding_swap_2(), @@ -2848,7 +958,7 @@ pub mod tests { fn vault_a_add() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_balance_add(), @@ -2860,7 +970,7 @@ pub mod tests { fn vault_b_add() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_balance_add(), @@ -2872,7 +982,7 @@ pub mod tests { fn pool_definition_add() -> Account { Account { program_owner: Program::amm().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -2882,7 +992,7 @@ pub mod tests { liquidity_pool_supply: BalanceForTests::token_lp_supply_add(), reserve_a: BalanceForTests::vault_a_balance_add(), reserve_b: BalanceForTests::vault_b_balance_add(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -2892,7 +1002,7 @@ pub mod tests { fn user_token_a_holding_add() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::user_token_a_holding_add(), @@ -2904,7 +1014,7 @@ pub mod tests { fn user_token_b_holding_add() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::user_token_b_holding_add(), @@ -2916,7 +1026,7 @@ pub mod tests { fn user_token_lp_holding_add() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_lp_definition_id(), balance: BalanceForTests::user_token_lp_holding_add(), @@ -2928,7 +1038,7 @@ pub mod tests { fn token_lp_definition_add() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: BalanceForTests::token_lp_supply_add(), @@ -2941,7 +1051,7 @@ pub mod tests { fn vault_a_remove() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_balance_remove(), @@ -2953,7 +1063,7 @@ pub mod tests { fn vault_b_remove() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_balance_remove(), @@ -2965,7 +1075,7 @@ pub mod tests { fn pool_definition_remove() -> Account { Account { program_owner: Program::amm().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -2975,7 +1085,7 @@ pub mod tests { liquidity_pool_supply: BalanceForTests::token_lp_supply_remove(), reserve_a: BalanceForTests::vault_a_balance_remove(), reserve_b: BalanceForTests::vault_b_balance_remove(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -2985,7 +1095,7 @@ pub mod tests { fn user_token_a_holding_remove() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::user_token_a_holding_remove(), @@ -2997,7 +1107,7 @@ pub mod tests { fn user_token_b_holding_remove() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::user_token_b_holding_remove(), @@ -3009,7 +1119,7 @@ pub mod tests { fn user_token_lp_holding_remove() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_lp_definition_id(), balance: BalanceForTests::user_token_lp_holding_remove(), @@ -3021,7 +1131,7 @@ pub mod tests { fn token_lp_definition_remove() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: BalanceForTests::token_lp_supply_remove(), @@ -3034,7 +1144,7 @@ pub mod tests { fn token_lp_definition_init_inactive() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: 0, @@ -3047,7 +1157,7 @@ pub mod tests { fn vault_a_init_inactive() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: 0, @@ -3059,7 +1169,7 @@ pub mod tests { fn vault_b_init_inactive() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: 0, @@ -3071,7 +1181,7 @@ pub mod tests { fn pool_definition_inactive() -> Account { Account { program_owner: Program::amm().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -3081,7 +1191,7 @@ pub mod tests { liquidity_pool_supply: 0, reserve_a: 0, reserve_b: 0, - fees: 0u128, + fees: 0_u128, active: false, }), nonce: 0, @@ -3091,7 +1201,7 @@ pub mod tests { fn user_token_a_holding_new_init() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::user_token_a_holding_new_definition(), @@ -3103,7 +1213,7 @@ pub mod tests { fn user_token_b_holding_new_init() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::user_token_b_holding_new_definition(), @@ -3115,7 +1225,7 @@ pub mod tests { fn user_token_lp_holding_new_init() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_lp_definition_id(), balance: BalanceForTests::lp_supply_init(), @@ -3127,7 +1237,7 @@ pub mod tests { fn token_lp_definition_new_init() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), total_supply: BalanceForTests::lp_supply_init(), @@ -3140,7 +1250,7 @@ pub mod tests { fn pool_definition_new_init() -> Account { Account { program_owner: Program::amm().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -3150,7 +1260,7 @@ pub mod tests { liquidity_pool_supply: BalanceForTests::lp_supply_init(), reserve_a: BalanceForTests::vault_a_balance_init(), reserve_b: BalanceForTests::vault_b_balance_init(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -3160,7 +1270,7 @@ pub mod tests { fn user_token_lp_holding_init_zero() -> Account { Account { program_owner: Program::token().id(), - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_lp_definition_id(), balance: 0, @@ -3170,6 +1280,1912 @@ pub mod tests { } } + fn transfer_transaction( + from: AccountId, + from_key: &PrivateKey, + nonce: u128, + to: AccountId, + balance: u128, + ) -> PublicTransaction { + let account_ids = vec![from, to]; + let nonces = vec![nonce]; + let program_id = Program::authenticated_transfer_program().id(); + let message = + public_transaction::Message::try_new(program_id, account_ids, nonces, balance).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[from_key]); + PublicTransaction::new(message, witness_set) + } + + #[test] + fn new_with_genesis() { + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let addr1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); + let addr2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); + let initial_data = [(addr1, 100_u128), (addr2, 151_u128)]; + let authenticated_transfers_program = Program::authenticated_transfer_program(); + let expected_public_state = { + let mut this = HashMap::new(); + this.insert( + addr1, + Account { + balance: 100, + program_owner: authenticated_transfers_program.id(), + ..Account::default() + }, + ); + this.insert( + addr2, + Account { + balance: 151, + program_owner: authenticated_transfers_program.id(), + ..Account::default() + }, + ); + this + }; + let expected_builtin_programs = { + let mut this = HashMap::new(); + this.insert( + authenticated_transfers_program.id(), + authenticated_transfers_program, + ); + this.insert(Program::token().id(), Program::token()); + this.insert(Program::amm().id(), Program::amm()); + this + }; + + let state = V02State::new_with_genesis_accounts(&initial_data, &[]); + + assert_eq!(state.public_state, expected_public_state); + assert_eq!(state.programs, expected_builtin_programs); + } + + #[test] + fn insert_program() { + let mut state = V02State::new_with_genesis_accounts(&[], &[]); + let program_to_insert = Program::simple_balance_transfer(); + let program_id = program_to_insert.id(); + assert!(!state.programs.contains_key(&program_id)); + + state.insert_program(program_to_insert); + + assert!(state.programs.contains_key(&program_id)); + } + + #[test] + fn get_account_by_account_id_non_default_account() { + let key = PrivateKey::try_new([1; 32]).unwrap(); + let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); + let initial_data = [(account_id, 100_u128)]; + let state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let expected_account = &state.public_state[&account_id]; + + let account = state.get_account_by_id(account_id); + + assert_eq!(&account, expected_account); + } + + #[test] + fn get_account_by_account_id_default_account() { + let addr2 = AccountId::new([0; 32]); + let state = V02State::new_with_genesis_accounts(&[], &[]); + let expected_account = Account::default(); + + let account = state.get_account_by_id(addr2); + + assert_eq!(account, expected_account); + } + + #[test] + fn builtin_programs_getter() { + let state = V02State::new_with_genesis_accounts(&[], &[]); + + let builtin_programs = state.programs(); + + assert_eq!(builtin_programs, &state.programs); + } + + #[test] + fn transition_from_authenticated_transfer_program_invocation_default_account_destination() { + let key = PrivateKey::try_new([1; 32]).unwrap(); + let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); + let initial_data = [(account_id, 100)]; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let from = account_id; + let to = AccountId::new([2; 32]); + assert_eq!(state.get_account_by_id(to), Account::default()); + let balance_to_move = 5; + + let tx = transfer_transaction(from, &key, 0, to, balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_id(from).balance, 95); + assert_eq!(state.get_account_by_id(to).balance, 5); + assert_eq!(state.get_account_by_id(from).nonce, 1); + assert_eq!(state.get_account_by_id(to).nonce, 0); + } + + #[test] + fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() { + let key = PrivateKey::try_new([1; 32]).unwrap(); + let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); + let initial_data = [(account_id, 100)]; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let from = account_id; + let from_key = key; + let to = AccountId::new([2; 32]); + let balance_to_move = 101; + assert!(state.get_account_by_id(from).balance < balance_to_move); + + let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move); + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_)))); + assert_eq!(state.get_account_by_id(from).balance, 100); + assert_eq!(state.get_account_by_id(to).balance, 0); + assert_eq!(state.get_account_by_id(from).nonce, 0); + assert_eq!(state.get_account_by_id(to).nonce, 0); + } + + #[test] + fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() { + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); + let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); + let initial_data = [(account_id1, 100), (account_id2, 200)]; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let from = account_id2; + let from_key = key2; + let to = account_id1; + assert_ne!(state.get_account_by_id(to), Account::default()); + let balance_to_move = 8; + + let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_id(from).balance, 192); + assert_eq!(state.get_account_by_id(to).balance, 108); + assert_eq!(state.get_account_by_id(from).nonce, 1); + assert_eq!(state.get_account_by_id(to).nonce, 0); + } + + #[test] + fn transition_from_sequence_of_authenticated_transfer_program_invocations() { + let key1 = PrivateKey::try_new([8; 32]).unwrap(); + let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); + let key2 = PrivateKey::try_new([2; 32]).unwrap(); + let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); + let initial_data = [(account_id1, 100)]; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]); + let account_id3 = AccountId::new([3; 32]); + let balance_to_move = 5; + + let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + let balance_to_move = 3; + let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move); + state.transition_from_public_transaction(&tx).unwrap(); + + assert_eq!(state.get_account_by_id(account_id1).balance, 95); + assert_eq!(state.get_account_by_id(account_id2).balance, 2); + assert_eq!(state.get_account_by_id(account_id3).balance, 3); + assert_eq!(state.get_account_by_id(account_id1).nonce, 1); + assert_eq!(state.get_account_by_id(account_id2).nonce, 1); + assert_eq!(state.get_account_by_id(account_id3).nonce, 0); + } + + #[test] + fn program_should_fail_if_modifies_nonces() { + let initial_data = [(AccountId::new([1; 32]), 100)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_ids = vec![AccountId::new([1; 32])]; + let program_id = Program::nonce_changer_program().id(); + let message = + public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_output_accounts_exceed_inputs() { + let initial_data = [(AccountId::new([1; 32]), 100)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_ids = vec![AccountId::new([1; 32])]; + let program_id = Program::extra_output_program().id(); + let message = + public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_with_missing_output_accounts() { + let initial_data = [(AccountId::new([1; 32]), 100)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_ids = vec![AccountId::new([1; 32]), AccountId::new([2; 32])]; + let program_id = Program::missing_output_program().id(); + let message = + public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() { + let initial_data = [(AccountId::new([1; 32]), 0)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let account = state.get_account_by_id(account_id); + // Assert the target account only differs from the default account in the program owner + // field + assert_ne!(account.program_owner, Account::default().program_owner); + assert_eq!(account.balance, Account::default().balance); + assert_eq!(account.nonce, Account::default().nonce); + assert_eq!(account.data, Account::default().data); + let program_id = Program::program_owner_changer().id(); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_program_owner_with_only_non_default_balance() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let account_id = AccountId::new([255; 32]); + let account = state.get_account_by_id(account_id); + // Assert the target account only differs from the default account in balance field + assert_eq!(account.program_owner, Account::default().program_owner); + assert_ne!(account.balance, Account::default().balance); + assert_eq!(account.nonce, Account::default().nonce); + assert_eq!(account.data, Account::default().data); + let program_id = Program::program_owner_changer().id(); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let account_id = AccountId::new([254; 32]); + let account = state.get_account_by_id(account_id); + // Assert the target account only differs from the default account in nonce field + assert_eq!(account.program_owner, Account::default().program_owner); + assert_eq!(account.balance, Account::default().balance); + assert_ne!(account.nonce, Account::default().nonce); + assert_eq!(account.data, Account::default().data); + let program_id = Program::program_owner_changer().id(); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_program_owner_with_only_non_default_data() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let account_id = AccountId::new([253; 32]); + let account = state.get_account_by_id(account_id); + // Assert the target account only differs from the default account in data field + assert_eq!(account.program_owner, Account::default().program_owner); + assert_eq!(account.balance, Account::default().balance); + assert_eq!(account.nonce, Account::default().nonce); + assert_ne!(account.data, Account::default().data); + let program_id = Program::program_owner_changer().id(); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_transfers_balance_from_non_owned_account() { + let initial_data = [(AccountId::new([1; 32]), 100)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let sender_account_id = AccountId::new([1; 32]); + let receiver_account_id = AccountId::new([2; 32]); + let balance_to_move: u128 = 1; + let program_id = Program::simple_balance_transfer().id(); + assert_ne!( + state.get_account_by_id(sender_account_id).program_owner, + program_id + ); + let message = public_transaction::Message::try_new( + program_id, + vec![sender_account_id, receiver_account_id], + vec![], + balance_to_move, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_modifies_data_of_non_owned_account() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_non_default_accounts_but_default_program_owners(); + let account_id = AccountId::new([255; 32]); + let program_id = Program::data_changer().id(); + + assert_ne!(state.get_account_by_id(account_id), Account::default()); + assert_ne!( + state.get_account_by_id(account_id).program_owner, + program_id + ); + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], vec![0]) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_does_not_preserve_total_balance_by_minting() { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let program_id = Program::minter().id(); + + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn program_should_fail_if_does_not_preserve_total_balance_by_burning() { + let initial_data = []; + let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]) + .with_test_programs() + .with_account_owned_by_burner_program(); + let program_id = Program::burner().id(); + let account_id = AccountId::new([252; 32]); + assert_eq!( + state.get_account_by_id(account_id).program_owner, + program_id + ); + let balance_to_burn: u128 = 1; + assert!(state.get_account_by_id(account_id).balance > balance_to_burn); + + let message = public_transaction::Message::try_new( + program_id, + vec![account_id], + vec![], + balance_to_burn, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + let result = state.transition_from_public_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + fn test_public_account_keys_1() -> TestPublicKeys { + TestPublicKeys { + signing_key: PrivateKey::try_new([37; 32]).unwrap(), + } + } + + pub fn test_private_account_keys_1() -> TestPrivateKeys { + TestPrivateKeys { + nsk: [13; 32], + vsk: [31; 32], + } + } + + pub fn test_private_account_keys_2() -> TestPrivateKeys { + TestPrivateKeys { + nsk: [38; 32], + vsk: [83; 32], + } + } + + fn shielded_balance_transfer_for_tests( + sender_keys: &TestPublicKeys, + recipient_keys: &TestPrivateKeys, + balance_to_move: u128, + state: &V02State, + ) -> PrivacyPreservingTransaction { + let sender = AccountWithMetadata::new( + state.get_account_by_id(sender_keys.account_id()), + true, + sender_keys.account_id(), + ); + + let sender_nonce = sender.account.nonce; + + let recipient = AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + let esk = [3; 32]; + let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.vpk()); + let epk = EphemeralPublicKey::from_scalar(esk); + + let (output, proof) = circuit::execute_and_prove( + vec![sender, recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![0, 2], + vec![0xdead_beef], + vec![(recipient_keys.npk(), shared_secret)], + vec![], + vec![None], + &Program::authenticated_transfer_program().into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![sender_keys.account_id()], + vec![sender_nonce], + vec![(recipient_keys.npk(), recipient_keys.vpk(), epk)], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[&sender_keys.signing_key]); + PrivacyPreservingTransaction::new(message, witness_set) + } + + fn private_balance_transfer_for_tests( + sender_keys: &TestPrivateKeys, + sender_private_account: &Account, + recipient_keys: &TestPrivateKeys, + balance_to_move: u128, + new_nonces: [Nonce; 2], + state: &V02State, + ) -> PrivacyPreservingTransaction { + let program = Program::authenticated_transfer_program(); + let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); + let sender_pre = + AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk()); + let recipient_pre = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + let esk_1 = [3; 32]; + let shared_secret_1 = SharedSecretKey::new(&esk_1, &sender_keys.vpk()); + let epk_1 = EphemeralPublicKey::from_scalar(esk_1); + + let esk_2 = [3; 32]; + let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.vpk()); + let epk_2 = EphemeralPublicKey::from_scalar(esk_2); + + let (output, proof) = circuit::execute_and_prove( + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + new_nonces.to_vec(), + vec![ + (sender_keys.npk(), shared_secret_1), + (recipient_keys.npk(), shared_secret_2), + ], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment), None], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + (sender_keys.npk(), sender_keys.vpk(), epk_1), + (recipient_keys.npk(), recipient_keys.vpk(), epk_2), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + PrivacyPreservingTransaction::new(message, witness_set) + } + + fn deshielded_balance_transfer_for_tests( + sender_keys: &TestPrivateKeys, + sender_private_account: &Account, + recipient_account_id: &AccountId, + balance_to_move: u128, + new_nonce: Nonce, + state: &V02State, + ) -> PrivacyPreservingTransaction { + let program = Program::authenticated_transfer_program(); + let sender_commitment = Commitment::new(&sender_keys.npk(), sender_private_account); + let sender_pre = + AccountWithMetadata::new(sender_private_account.clone(), true, &sender_keys.npk()); + let recipient_pre = AccountWithMetadata::new( + state.get_account_by_id(*recipient_account_id), + false, + *recipient_account_id, + ); + + let esk = [3; 32]; + let shared_secret = SharedSecretKey::new(&esk, &sender_keys.vpk()); + let epk = EphemeralPublicKey::from_scalar(esk); + + let (output, proof) = circuit::execute_and_prove( + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 0], + vec![new_nonce], + vec![(sender_keys.npk(), shared_secret)], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment)], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![*recipient_account_id], + vec![], + vec![(sender_keys.npk(), sender_keys.vpk(), epk)], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + PrivacyPreservingTransaction::new(message, witness_set) + } + + #[test] + fn transition_from_privacy_preserving_transaction_shielded() { + let sender_keys = test_public_account_keys_1(); + let recipient_keys = test_private_account_keys_1(); + + let mut state = + V02State::new_with_genesis_accounts(&[(sender_keys.account_id(), 200)], &[]); + + let balance_to_move = 37; + + let tx = shielded_balance_transfer_for_tests( + &sender_keys, + &recipient_keys, + balance_to_move, + &state, + ); + + let expected_sender_post = { + let mut this = state.get_account_by_id(sender_keys.account_id()); + this.balance -= balance_to_move; + this.nonce += 1; + this + }; + + let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap(); + assert!(!state.private_state.0.contains(&expected_new_commitment)); + + state + .transition_from_privacy_preserving_transaction(&tx) + .unwrap(); + + let sender_post = state.get_account_by_id(sender_keys.account_id()); + assert_eq!(sender_post, expected_sender_post); + assert!(state.private_state.0.contains(&expected_new_commitment)); + + assert_eq!( + state.get_account_by_id(sender_keys.account_id()).balance, + 200 - balance_to_move + ); + } + + #[test] + fn transition_from_privacy_preserving_transaction_private() { + let sender_keys = test_private_account_keys_1(); + let sender_private_account = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: 100, + nonce: 0xdead_beef, + data: Data::default(), + }; + let recipient_keys = test_private_account_keys_2(); + + let mut state = V02State::new_with_genesis_accounts(&[], &[]) + .with_private_account(&sender_keys, &sender_private_account); + + let balance_to_move = 37; + + let tx = private_balance_transfer_for_tests( + &sender_keys, + &sender_private_account, + &recipient_keys, + balance_to_move, + [0xcafe_cafe, 0xfeca_feca], + &state, + ); + + let expected_new_commitment_1 = Commitment::new( + &sender_keys.npk(), + &Account { + program_owner: Program::authenticated_transfer_program().id(), + nonce: 0xcafe_cafe, + balance: sender_private_account.balance - balance_to_move, + data: Data::default(), + }, + ); + + let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account); + let expected_new_nullifier = + Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk); + + let expected_new_commitment_2 = Commitment::new( + &recipient_keys.npk(), + &Account { + program_owner: Program::authenticated_transfer_program().id(), + nonce: 0xfeca_feca, + balance: balance_to_move, + ..Account::default() + }, + ); + + let previous_public_state = state.public_state.clone(); + assert!(state.private_state.0.contains(&sender_pre_commitment)); + assert!(!state.private_state.0.contains(&expected_new_commitment_1)); + assert!(!state.private_state.0.contains(&expected_new_commitment_2)); + assert!(!state.private_state.1.contains(&expected_new_nullifier)); + + state + .transition_from_privacy_preserving_transaction(&tx) + .unwrap(); + + assert_eq!(state.public_state, previous_public_state); + assert!(state.private_state.0.contains(&sender_pre_commitment)); + assert!(state.private_state.0.contains(&expected_new_commitment_1)); + assert!(state.private_state.0.contains(&expected_new_commitment_2)); + assert!(state.private_state.1.contains(&expected_new_nullifier)); + } + + #[test] + fn transition_from_privacy_preserving_transaction_deshielded() { + let sender_keys = test_private_account_keys_1(); + let sender_private_account = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: 100, + nonce: 0xdead_beef, + data: Data::default(), + }; + let recipient_keys = test_public_account_keys_1(); + let recipient_initial_balance = 400; + let mut state = V02State::new_with_genesis_accounts( + &[(recipient_keys.account_id(), recipient_initial_balance)], + &[], + ) + .with_private_account(&sender_keys, &sender_private_account); + + let balance_to_move = 37; + + let expected_recipient_post = { + let mut this = state.get_account_by_id(recipient_keys.account_id()); + this.balance += balance_to_move; + this + }; + + let tx = deshielded_balance_transfer_for_tests( + &sender_keys, + &sender_private_account, + &recipient_keys.account_id(), + balance_to_move, + 0xcafe_cafe, + &state, + ); + + let expected_new_commitment = Commitment::new( + &sender_keys.npk(), + &Account { + program_owner: Program::authenticated_transfer_program().id(), + nonce: 0xcafe_cafe, + balance: sender_private_account.balance - balance_to_move, + data: Data::default(), + }, + ); + + let sender_pre_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account); + let expected_new_nullifier = + Nullifier::for_account_update(&sender_pre_commitment, &sender_keys.nsk); + + assert!(state.private_state.0.contains(&sender_pre_commitment)); + assert!(!state.private_state.0.contains(&expected_new_commitment)); + assert!(!state.private_state.1.contains(&expected_new_nullifier)); + + state + .transition_from_privacy_preserving_transaction(&tx) + .unwrap(); + + let recipient_post = state.get_account_by_id(recipient_keys.account_id()); + assert_eq!(recipient_post, expected_recipient_post); + assert!(state.private_state.0.contains(&sender_pre_commitment)); + assert!(state.private_state.0.contains(&expected_new_commitment)); + assert!(state.private_state.1.contains(&expected_new_nullifier)); + assert_eq!( + state.get_account_by_id(recipient_keys.account_id()).balance, + recipient_initial_balance + balance_to_move + ); + } + + #[test] + fn burner_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::burner(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + vec![public_account], + Program::serialize_instruction(10_u128).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn minter_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::minter(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + vec![public_account], + Program::serialize_instruction(10_u128).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn nonce_changer_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::nonce_changer_program(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() { + let program = Program::data_changer(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + vec![public_account], + Program::serialize_instruction(vec![0]).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn data_changer_program_should_fail_for_too_large_data_in_privacy_preserving_circuit() { + let program = Program::data_changer(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let large_data: Vec = + vec![ + 0; + usize::try_from(nssa_core::account::data::DATA_MAX_LENGTH.as_u64()) + .expect("DATA_MAX_LENGTH fits in usize") + + 1 + ]; + + let result = execute_and_prove( + vec![public_account], + Program::serialize_instruction(large_data).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], + &program.clone().into(), + ); + + assert!(matches!(result, Err(NssaError::ProgramProveFailed(_)))); + } + + #[test] + fn extra_output_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::extra_output_program(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn missing_output_program_should_fail_in_privacy_preserving_circuit() { + let program = Program::missing_output_program(); + let public_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let public_account_2 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([1; 32]), + ); + + let result = execute_and_prove( + vec![public_account_1, public_account_2], + Program::serialize_instruction(()).unwrap(), + vec![0, 0], + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn program_owner_changer_should_fail_in_privacy_preserving_circuit() { + let program = Program::program_owner_changer(); + let public_account = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + + let result = execute_and_prove( + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata::new( + Account { + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let public_account_2 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([1; 32]), + ); + + let result = execute_and_prove( + vec![public_account_1, public_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![0, 0], + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_visibility_masks_have_incorrect_lenght() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let public_account_2 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 0, + ..Account::default() + }, + true, + AccountId::new([1; 32]), + ); + + // Setting only one visibility mask for a circuit execution with two pre_state accounts. + let visibility_mask = [0]; + let result = execute_and_prove( + vec![public_account_1, public_account_2], + Program::serialize_instruction(10_u128).unwrap(), + visibility_mask.to_vec(), + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_insufficient_nonces_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + // Setting only one nonce for an execution with two private accounts. + let private_account_nonces = [0xdead_beef1]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + private_account_nonces.to_vec(), + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_insufficient_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + // Setting only one key for an execution with two private accounts. + let private_account_keys = [( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + )]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_insufficient_commitment_proofs_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + // Setting no second commitment proof. + let private_account_membership_proofs = [Some((0, vec![]))]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + private_account_membership_proofs.to_vec(), + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_insufficient_auth_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + // Setting no auth key for an execution with one non default private accounts. + let private_account_nsks = []; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + private_account_nsks.to_vec(), + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_invalid_auth_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + let private_account_keys = [ + // First private account is the sender + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + // Second private account is the recipient + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ]; + + // Setting the recipient key to authorize the sender. + // This should be set to the sender private account in + // a normal circumstance. The recipient can't authorize this. + let private_account_nsks = [recipient_keys.nsk]; + let private_account_membership_proofs = [Some((0, vec![]))]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + private_account_keys.to_vec(), + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = AccountWithMetadata::new( + Account { + // Non default balance + balance: 1, + ..Account::default() + }, + false, + &recipient_keys.npk(), + ); + + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = AccountWithMetadata::new( + Account { + // Non default program_owner + program_owner: [0, 1, 2, 3, 4, 5, 6, 7], + ..Account::default() + }, + false, + &recipient_keys.npk(), + ); + + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = AccountWithMetadata::new( + Account { + // Non default data + data: b"hola mundo".to_vec().try_into().unwrap(), + ..Account::default() + }, + false, + &recipient_keys.npk(), + ); + + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = AccountWithMetadata::new( + Account { + // Non default nonce + nonce: 0xdead_beef, + ..Account::default() + }, + false, + &recipient_keys.npk(), + ); + + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_if_new_private_account_is_provided_with_default_values_but_marked_as_authorized() + { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = AccountWithMetadata::new( + Account::default(), + // This should be set to false in normal circumstances + true, + &recipient_keys.npk(), + ); + + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_with_invalid_visibility_mask_value() { + let program = Program::simple_balance_transfer(); + let public_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + AccountId::new([0; 32]), + ); + let public_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + let visibility_mask = [0, 3]; + let result = execute_and_prove( + vec![public_account_1, public_account_2], + Program::serialize_instruction(10_u128).unwrap(), + visibility_mask.to_vec(), + vec![], + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_with_too_many_nonces() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + // Setting three new private account nonces for a circuit execution with only two private + // accounts. + let private_account_nonces = [0xdead_beef1, 0xdead_beef2, 0xdead_beef3]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + private_account_nonces.to_vec(), + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_with_too_many_private_account_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + // Setting three private account keys for a circuit execution with only two private + // accounts. + let private_account_keys = [ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ( + sender_keys.npk(), + SharedSecretKey::new(&[57; 32], &sender_keys.vpk()), + ), + ]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![0xdead_beef1, 0xdead_beef2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_with_too_many_private_account_auth_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + // Setting two private account keys for a circuit execution with only one non default + // private account (visibility mask equal to 1 means that auth keys are expected). + let visibility_mask = [1, 2]; + let private_account_nsks = [sender_keys.nsk, recipient_keys.nsk]; + let private_account_membership_proofs = [Some((0, vec![])), Some((1, vec![]))]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + visibility_mask.to_vec(), + vec![0xdead_beef1, 0xdead_beef2], + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn private_accounts_can_only_be_initialized_once() { + let sender_keys = test_private_account_keys_1(); + let sender_private_account = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: 100, + nonce: 0xdead_beef, + data: Data::default(), + }; + let recipient_keys = test_private_account_keys_2(); + + let mut state = V02State::new_with_genesis_accounts(&[], &[]) + .with_private_account(&sender_keys, &sender_private_account); + + let balance_to_move = 37; + + let tx = private_balance_transfer_for_tests( + &sender_keys, + &sender_private_account, + &recipient_keys, + balance_to_move, + [0xcafe_cafe, 0xfeca_feca], + &state, + ); + + state + .transition_from_privacy_preserving_transaction(&tx) + .unwrap(); + + let sender_private_account = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: 100 - balance_to_move, + nonce: 0xcafe_cafe, + data: Data::default(), + }; + + let tx = private_balance_transfer_for_tests( + &sender_keys, + &sender_private_account, + &recipient_keys, + balance_to_move, + [0x1234, 0x5678], + &state, + ); + + let result = state.transition_from_privacy_preserving_transaction(&tx); + + assert!(matches!(result, Err(NssaError::InvalidInput(_)))); + let NssaError::InvalidInput(error_message) = result.err().unwrap() else { + panic!("Incorrect message error"); + }; + let expected_error_message = "Nullifier already seen".to_owned(); + assert_eq!(error_message, expected_error_message); + } + + #[test] + fn circuit_should_fail_if_there_are_repeated_ids() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + + let visibility_mask = [1, 1]; + let private_account_nsks = [sender_keys.nsk, sender_keys.nsk]; + let private_account_membership_proofs = [Some((1, vec![])), Some((1, vec![]))]; + let shared_secret = SharedSecretKey::new(&[55; 32], &sender_keys.vpk()); + let result = execute_and_prove( + vec![private_account_1.clone(), private_account_1], + Program::serialize_instruction(100_u128).unwrap(), + visibility_mask.to_vec(), + vec![0xdead_beef1, 0xdead_beef2], + vec![ + (sender_keys.npk(), shared_secret), + (sender_keys.npk(), shared_secret), + ], + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn claiming_mechanism() { + let program = Program::authenticated_transfer_program(); + let key = PrivateKey::try_new([1; 32]).unwrap(); + let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); + let initial_balance = 100; + let initial_data = [(account_id, initial_balance)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let from = account_id; + let from_key = key; + let to = AccountId::new([2; 32]); + let amount: u128 = 37; + + // Check the recipient is an uninitialized account + assert_eq!(state.get_account_by_id(to), Account::default()); + + let expected_recipient_post = Account { + program_owner: program.id(), + balance: amount, + ..Account::default() + }; + + let message = + public_transaction::Message::try_new(program.id(), vec![from, to], vec![0], amount) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); + let tx = PublicTransaction::new(message, witness_set); + + state.transition_from_public_transaction(&tx).unwrap(); + + let recipient_post = state.get_account_by_id(to); + + assert_eq!(recipient_post, expected_recipient_post); + } + + #[test] + fn public_chained_call() { + let program = Program::chain_caller(); + let key = PrivateKey::try_new([1; 32]).unwrap(); + let from = AccountId::from(&PublicKey::new_from_private_key(&key)); + let to = AccountId::new([2; 32]); + let initial_balance = 1000; + let initial_data = [(from, initial_balance), (to, 0)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let from_key = key; + let amount: u128 = 37; + let instruction: (u128, ProgramId, u32, Option) = ( + amount, + Program::authenticated_transfer_program().id(), + 2, + None, + ); + + let expected_to_post = Account { + program_owner: Program::authenticated_transfer_program().id(), + balance: amount * 2, // The `chain_caller` chains the program twice + ..Account::default() + }; + + let message = public_transaction::Message::try_new( + program.id(), + vec![to, from], // The chain_caller program permutes the account order in the chain + // call + vec![0], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); + let tx = PublicTransaction::new(message, witness_set); + + state.transition_from_public_transaction(&tx).unwrap(); + + let from_post = state.get_account_by_id(from); + let to_post = state.get_account_by_id(to); + // The `chain_caller` program calls the program twice + assert_eq!(from_post.balance, initial_balance - 2 * amount); + assert_eq!(to_post, expected_to_post); + } + + #[test] + fn execution_fails_if_chained_calls_exceeds_depth() { + let program = Program::chain_caller(); + let key = PrivateKey::try_new([1; 32]).unwrap(); + let from = AccountId::from(&PublicKey::new_from_private_key(&key)); + let to = AccountId::new([2; 32]); + let initial_balance = 100; + let initial_data = [(from, initial_balance), (to, 0)]; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let from_key = key; + let amount: u128 = 0; + let instruction: (u128, ProgramId, u32, Option) = ( + amount, + Program::authenticated_transfer_program().id(), + u32::try_from(MAX_NUMBER_CHAINED_CALLS).expect("MAX_NUMBER_CHAINED_CALLS fits in u32") + + 1, + None, + ); + + let message = public_transaction::Message::try_new( + program.id(), + vec![to, from], // The chain_caller program permutes the account order in the chain + // call + vec![0], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + assert!(matches!( + result, + Err(NssaError::MaxChainedCallsDepthExceeded) + )); + } + fn state_for_amm_tests() -> V02State { let initial_data = []; let mut state = @@ -3232,7 +3248,7 @@ pub mod tests { } #[test] - fn test_simple_amm_remove() { + fn simple_amm_remove() { let mut state = state_for_amm_tests(); let instruction = amm_core::Instruction::RemoveLiquidity { @@ -3291,7 +3307,7 @@ pub mod tests { } #[test] - fn test_simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() { + fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() { let mut state = state_for_amm_tests_with_new_def(); // Uninitialized in constructor @@ -3371,7 +3387,7 @@ pub mod tests { } #[test] - fn test_simple_amm_new_definition_inactive_initialized_pool_init_user_lp() { + fn simple_amm_new_definition_inactive_initialized_pool_init_user_lp() { let mut state = state_for_amm_tests_with_new_def(); // Uninitialized in constructor @@ -3455,7 +3471,7 @@ pub mod tests { } #[test] - fn test_simple_amm_new_definition_uninitialized_pool() { + fn simple_amm_new_definition_uninitialized_pool() { let mut state = state_for_amm_tests_with_new_def(); // Uninitialized in constructor @@ -3527,7 +3543,7 @@ pub mod tests { } #[test] - fn test_simple_amm_add() { + fn simple_amm_add() { env_logger::init(); let mut state = state_for_amm_tests(); @@ -3590,7 +3606,7 @@ pub mod tests { } #[test] - fn test_simple_amm_swap_1() { + fn simple_amm_swap_1() { let mut state = state_for_amm_tests(); let instruction = amm_core::Instruction::Swap { @@ -3641,7 +3657,7 @@ pub mod tests { } #[test] - fn test_simple_amm_swap_2() { + fn simple_amm_swap_2() { let mut state = state_for_amm_tests(); let instruction = amm_core::Instruction::Swap { @@ -3691,7 +3707,7 @@ pub mod tests { } #[test] - fn test_execution_that_requires_authentication_of_a_program_derived_account_id_succeeds() { + fn execution_that_requires_authentication_of_a_program_derived_account_id_succeeds() { let chain_caller = Program::chain_caller(); let pda_seed = PdaSeed::new([37; 32]); let from = AccountId::from((&chain_caller.id(), &pda_seed)); @@ -3733,7 +3749,7 @@ pub mod tests { } #[test] - fn test_claiming_mechanism_within_chain_call() { + fn claiming_mechanism_within_chain_call() { // This test calls the authenticated transfer program through the chain_caller program. // The transfer is made from an initialized sender to an uninitialized recipient. And // it is expected that the recipient account is claimed by the authenticated transfer @@ -3790,7 +3806,7 @@ pub mod tests { #[test_case::test_case(1; "single call")] #[test_case::test_case(2; "two calls")] - fn test_private_chained_call(number_of_calls: u32) { + fn private_chained_call(number_of_calls: u32) { // Arrange let chain_caller = Program::chain_caller(); let auth_transfers = Program::authenticated_transfer_program(); @@ -3907,7 +3923,7 @@ pub mod tests { } #[test] - fn test_pda_mechanism_with_pinata_token_program() { + fn pda_mechanism_with_pinata_token_program() { let pinata_token = Program::pinata_token(); let token = Program::token(); @@ -3986,7 +4002,7 @@ pub mod tests { } #[test] - fn test_claiming_mechanism_cannot_claim_initialied_accounts() { + fn claiming_mechanism_cannot_claim_initialied_accounts() { let claimer = Program::claimer(); let mut state = V02State::new_with_genesis_accounts(&[], &[]).with_test_programs(); let account_id = AccountId::new([2; 32]); @@ -4014,7 +4030,7 @@ pub mod tests { /// This test ensures that even if a malicious program tries to perform overflow of balances /// it will not be able to break the balance validation. #[test] - fn test_malicious_program_cannot_break_balance_validation() { + fn malicious_program_cannot_break_balance_validation() { let sender_key = PrivateKey::try_new([37; 32]).unwrap(); let sender_id = AccountId::from(&PublicKey::new_from_private_key(&sender_key)); let sender_init_balance: u128 = 10; @@ -4077,7 +4093,7 @@ pub mod tests { } #[test] - fn test_private_authorized_uninitialized_account() { + fn private_authorized_uninitialized_account() { let mut state = V02State::new_with_genesis_accounts(&[], &[]); // Set up keys for the authorized private account @@ -4132,7 +4148,7 @@ pub mod tests { } #[test] - fn test_private_account_claimed_then_used_without_init_flag_should_fail() { + fn private_account_claimed_then_used_without_init_flag_should_fail() { let mut state = V02State::new_with_genesis_accounts(&[], &[]).with_test_programs(); // Set up keys for the private account @@ -4216,7 +4232,7 @@ pub mod tests { } #[test] - fn test_public_changer_claimer_no_data_change_no_claim_succeeds() { + fn public_changer_claimer_no_data_change_no_claim_succeeds() { let initial_data = []; let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); @@ -4240,7 +4256,7 @@ pub mod tests { } #[test] - fn test_public_changer_claimer_data_change_no_claim_fails() { + fn public_changer_claimer_data_change_no_claim_fails() { let initial_data = []; let mut state = V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); @@ -4263,7 +4279,7 @@ pub mod tests { } #[test] - fn test_private_changer_claimer_no_data_change_no_claim_succeeds() { + fn private_changer_claimer_no_data_change_no_claim_succeeds() { let program = Program::changer_claimer(); let sender_keys = test_private_account_keys_1(); let private_account = @@ -4290,7 +4306,7 @@ pub mod tests { } #[test] - fn test_private_changer_claimer_data_change_no_claim_fails() { + fn private_changer_claimer_data_change_no_claim_fails() { let program = Program::changer_claimer(); let sender_keys = test_private_account_keys_1(); let private_account = @@ -4318,7 +4334,7 @@ pub mod tests { } #[test] - fn test_malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() { + fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() { // Arrange let malicious_program = Program::malicious_authorization_changer(); let auth_transfers = Program::authenticated_transfer_program(); @@ -4345,7 +4361,7 @@ pub mod tests { ) .with_test_programs(); - let balance_to_transfer = 10u128; + let balance_to_transfer = 10_u128; let instruction = (balance_to_transfer, auth_transfers.id()); let recipient_esk = [3; 32]; @@ -4374,10 +4390,10 @@ pub mod tests { } #[test] - fn test_state_serialization_roundtrip() { + fn state_serialization_roundtrip() { let account_id_1 = AccountId::new([1; 32]); let account_id_2 = AccountId::new([2; 32]); - let initial_data = [(account_id_1, 100u128), (account_id_2, 151u128)]; + let initial_data = [(account_id_1, 100_u128), (account_id_2, 151_u128)]; let state = V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); let bytes = borsh::to_vec(&state).unwrap(); let state_from_bytes: V02State = borsh::from_slice(&bytes).unwrap(); diff --git a/program_methods/guest/src/bin/authenticated_transfer.rs b/program_methods/guest/src/bin/authenticated_transfer.rs index 43f06c56..7835f733 100644 --- a/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/program_methods/guest/src/bin/authenticated_transfer.rs @@ -31,24 +31,24 @@ fn transfer( // Continue only if the sender has authorized this operation assert!(sender.is_authorized, "Sender must be authorized"); - // Continue only if the sender has enough balance - assert!( - sender.account.balance >= balance_to_move, - "Sender has insufficient balance" - ); - // Create accounts post states, with updated balances let sender_post = { // Modify sender's balance let mut sender_post_account = sender.account; - sender_post_account.balance -= balance_to_move; + sender_post_account.balance = sender_post_account + .balance + .checked_sub(balance_to_move) + .expect("Sender has insufficient balance"); AccountPostState::new(sender_post_account) }; let recipient_post = { // Modify recipient's balance let mut recipient_post_account = recipient.account; - recipient_post_account.balance += balance_to_move; + recipient_post_account.balance = recipient_post_account + .balance + .checked_add(balance_to_move) + .expect("Recipient balance overflow"); // Claim recipient account if it has default program owner if recipient_post_account.program_owner == DEFAULT_PROGRAM_ID { diff --git a/program_methods/guest/src/bin/pinata.rs b/program_methods/guest/src/bin/pinata.rs index 70d76db2..fd19fb4c 100644 --- a/program_methods/guest/src/bin/pinata.rs +++ b/program_methods/guest/src/bin/pinata.rs @@ -1,5 +1,5 @@ use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; -use risc0_zkvm::sha::{Impl, Sha256}; +use risc0_zkvm::sha::{Impl, Sha256 as _}; const PRIZE: u128 = 150; @@ -28,7 +28,7 @@ impl Challenge { bytes[..32].copy_from_slice(&self.seed); bytes[32..].copy_from_slice(&solution.to_le_bytes()); let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); - let difficulty = self.difficulty as usize; + let difficulty = usize::from(self.difficulty); digest[..difficulty].iter().all(|&b| b == 0) } @@ -64,13 +64,19 @@ fn main() { let mut pinata_post = pinata.account.clone(); let mut winner_post = winner.account.clone(); - pinata_post.balance -= PRIZE; + pinata_post.balance = pinata_post + .balance + .checked_sub(PRIZE) + .expect("Not enough balance in the pinata"); pinata_post.data = data .next_data() .to_vec() .try_into() .expect("33 bytes should fit into Data"); - winner_post.balance += PRIZE; + winner_post.balance = winner_post + .balance + .checked_add(PRIZE) + .expect("Overflow when adding prize to winner"); write_nssa_outputs( instruction_words, diff --git a/program_methods/guest/src/bin/pinata_token.rs b/program_methods/guest/src/bin/pinata_token.rs index e0b269a4..4089e6e0 100644 --- a/program_methods/guest/src/bin/pinata_token.rs +++ b/program_methods/guest/src/bin/pinata_token.rs @@ -5,7 +5,7 @@ use nssa_core::{ write_nssa_outputs_with_chained_call, }, }; -use risc0_zkvm::sha::{Impl, Sha256}; +use risc0_zkvm::sha::{Impl, Sha256 as _}; const PRIZE: u128 = 150; @@ -34,7 +34,7 @@ impl Challenge { bytes[..32].copy_from_slice(&self.seed); bytes[32..].copy_from_slice(&solution.to_le_bytes()); let digest: [u8; 32] = Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap(); - let difficulty = self.difficulty as usize; + let difficulty = usize::from(self.difficulty); digest[..difficulty].iter().all(|&b| b == 0) } diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 51dd4c4c..f4b3e6c9 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -16,31 +16,6 @@ use nssa_core::{ }; use risc0_zkvm::{guest::env, serde::to_vec}; -fn main() { - let PrivacyPreservingCircuitInput { - program_outputs, - visibility_mask, - private_account_nonces, - private_account_keys, - private_account_nsks, - private_account_membership_proofs, - program_id, - } = env::read(); - - let execution_state = ExecutionState::derive_from_outputs(program_id, program_outputs); - - let output = compute_circuit_output( - execution_state, - &visibility_mask, - &private_account_nonces, - &private_account_keys, - &private_account_nsks, - &private_account_membership_proofs, - ); - - env::commit(&output); -} - /// State of the involved accounts before and after program execution. struct ExecutionState { pre_states: Vec, @@ -117,7 +92,9 @@ impl ExecutionState { program_output.pre_states, program_output.post_states, ); - chain_calls_counter += 1; + chain_calls_counter = chain_calls_counter.checked_add(1).expect( + "Chain calls counter should not overflow as it checked before incrementing", + ); } assert!( @@ -249,10 +226,10 @@ fn compute_circuit_output( let mut private_membership_proofs_iter = private_account_membership_proofs.iter(); let mut output_index = 0; - for (visibility_mask, (pre_state, post_state)) in + for (account_visibility_mask, (pre_state, post_state)) in visibility_mask.iter().copied().zip(states_iter) { - match visibility_mask { + match account_visibility_mask { 0 => { // Public account output.public_pre_states.push(pre_state); @@ -269,7 +246,7 @@ fn compute_circuit_output( "AccountId mismatch" ); - let new_nullifier = if visibility_mask == 1 { + let new_nullifier = if account_visibility_mask == 1 { // Private account with authentication let Some(nsk) = private_nsks_iter.next() else { @@ -347,7 +324,10 @@ fn compute_circuit_output( output.new_commitments.push(commitment_post); output.ciphertexts.push(encrypted_account); - output_index += 1; + output_index = match output_index.checked_add(1) { + Some(val) => val, + None => panic!("Too many private accounts, output index overflow"), + } } _ => panic!("Invalid visibility mask value"), } @@ -402,3 +382,28 @@ fn compute_nullifier_and_set_digest( }, ) } + +fn main() { + let PrivacyPreservingCircuitInput { + program_outputs, + visibility_mask, + private_account_nonces, + private_account_keys, + private_account_nsks, + private_account_membership_proofs, + program_id, + } = env::read(); + + let execution_state = ExecutionState::derive_from_outputs(program_id, program_outputs); + + let output = compute_circuit_output( + execution_state, + &visibility_mask, + &private_account_nonces, + &private_account_keys, + &private_account_nsks, + &private_account_membership_proofs, + ); + + env::commit(&output); +} diff --git a/programs/amm/core/src/lib.rs b/programs/amm/core/src/lib.rs index 128460a2..d499ae9f 100644 --- a/programs/amm/core/src/lib.rs +++ b/programs/amm/core/src/lib.rs @@ -130,7 +130,7 @@ pub fn compute_pool_pda_seed( definition_token_a_id: AccountId, definition_token_b_id: AccountId, ) -> PdaSeed { - use risc0_zkvm::sha::{Impl, Sha256}; + use risc0_zkvm::sha::{Impl, Sha256 as _}; let (token_1, token_2) = match definition_token_a_id .value() @@ -167,7 +167,7 @@ pub fn compute_vault_pda( #[must_use] pub fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId) -> PdaSeed { - use risc0_zkvm::sha::{Impl, Sha256}; + use risc0_zkvm::sha::{Impl, Sha256 as _}; let mut bytes = [0; 64]; bytes[0..32].copy_from_slice(&pool_id.to_bytes()); @@ -188,7 +188,7 @@ pub fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId #[must_use] pub fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed { - use risc0_zkvm::sha::{Impl, Sha256}; + use risc0_zkvm::sha::{Impl, Sha256 as _}; let mut bytes = [0; 64]; bytes[0..32].copy_from_slice(&pool_id.to_bytes()); diff --git a/programs/amm/src/lib.rs b/programs/amm/src/lib.rs index e50c738e..6142eda6 100644 --- a/programs/amm/src/lib.rs +++ b/programs/amm/src/lib.rs @@ -1,5 +1,12 @@ //! The AMM Program implementation. +#![expect( + clippy::arithmetic_side_effects, + clippy::integer_division, + clippy::integer_division_remainder_used, + reason = "TODO: Fix later" +)] + pub use amm_core as core; pub mod add; diff --git a/programs/amm/src/new_definition.rs b/programs/amm/src/new_definition.rs index b3642349..83b4a3ad 100644 --- a/programs/amm/src/new_definition.rs +++ b/programs/amm/src/new_definition.rs @@ -103,7 +103,7 @@ pub fn new_definition( liquidity_pool_supply: initial_lp, reserve_a: token_a_amount.into(), reserve_b: token_b_amount.into(), - fees: 0u128, // TODO: we assume all fees are 0 for now. + fees: 0_u128, // TODO: we assume all fees are 0 for now. active: true, }; diff --git a/programs/amm/src/tests.rs b/programs/amm/src/tests.rs index 203e3284..6bb7cc1f 100644 --- a/programs/amm/src/tests.rs +++ b/programs/amm/src/tests.rs @@ -410,7 +410,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::user_token_a_balance(), @@ -426,7 +426,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::user_token_b_balance(), @@ -442,7 +442,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_reserve_init(), @@ -458,7 +458,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_reserve_init(), @@ -474,7 +474,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_reserve_high(), @@ -490,7 +490,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_reserve_high(), @@ -506,7 +506,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_reserve_low(), @@ -522,7 +522,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_reserve_low(), @@ -538,7 +538,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: 0, @@ -554,7 +554,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: 0, @@ -570,7 +570,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: BalanceForTests::lp_supply_init(), @@ -587,7 +587,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: BalanceForTests::lp_supply_init(), @@ -604,7 +604,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_lp_definition_id(), balance: 0, @@ -620,7 +620,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_lp_definition_id(), balance: BalanceForTests::user_token_lp_balance(), @@ -636,7 +636,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -646,7 +646,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::lp_supply_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -660,7 +660,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -670,7 +670,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::lp_supply_init(), reserve_a: 0, reserve_b: BalanceForTests::vault_b_reserve_init(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -684,7 +684,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -694,7 +694,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::lp_supply_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: 0, - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -708,7 +708,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -718,7 +718,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::vault_a_reserve_low(), reserve_a: BalanceForTests::vault_a_reserve_low(), reserve_b: BalanceForTests::vault_b_reserve_high(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -732,7 +732,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -742,7 +742,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::vault_a_reserve_high(), reserve_a: BalanceForTests::vault_a_reserve_high(), reserve_b: BalanceForTests::vault_b_reserve_low(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -756,7 +756,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -766,7 +766,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::lp_supply_init(), reserve_a: BalanceForTests::vault_a_swap_test_1(), reserve_b: BalanceForTests::vault_b_swap_test_1(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -780,7 +780,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -790,7 +790,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::lp_supply_init(), reserve_a: BalanceForTests::vault_a_swap_test_2(), reserve_b: BalanceForTests::vault_b_swap_test_2(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -804,7 +804,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -814,7 +814,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::vault_a_reserve_low(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -828,7 +828,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -838,7 +838,7 @@ impl AccountForTests { liquidity_pool_supply: 989, reserve_a: BalanceForTests::vault_a_add_successful(), reserve_b: BalanceForTests::vault_b_add_successful(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -852,7 +852,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -862,7 +862,7 @@ impl AccountForTests { liquidity_pool_supply: 607, reserve_a: BalanceForTests::vault_a_remove_successful(), reserve_b: BalanceForTests::vault_b_remove_successful(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -876,7 +876,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -886,7 +886,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::lp_supply_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), - fees: 0u128, + fees: 0_u128, active: false, }), nonce: 0, @@ -900,7 +900,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -910,7 +910,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::lp_supply_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), - fees: 0u128, + fees: 0_u128, active: false, }), nonce: 0, @@ -924,7 +924,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_a_definition_id(), balance: BalanceForTests::vault_a_reserve_init(), @@ -940,7 +940,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: TOKEN_PROGRAM_ID, - balance: 0u128, + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::token_b_definition_id(), balance: BalanceForTests::vault_b_reserve_init(), @@ -956,7 +956,7 @@ impl AccountForTests { AccountWithMetadata { account: Account { program_owner: ProgramId::default(), - balance: 0u128, + balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForTests::token_a_definition_id(), definition_token_b_id: IdForTests::token_b_definition_id(), @@ -966,7 +966,7 @@ impl AccountForTests { liquidity_pool_supply: BalanceForTests::lp_supply_init(), reserve_a: BalanceForTests::vault_a_reserve_init(), reserve_b: BalanceForTests::vault_b_reserve_init(), - fees: 0u128, + fees: 0_u128, active: true, }), nonce: 0, @@ -978,7 +978,7 @@ impl AccountForTests { } #[test] -fn test_pool_pda_produces_unique_id_for_token_pair() { +fn pool_pda_produces_unique_id_for_token_pair() { assert!( amm_core::compute_pool_pda( AMM_PROGRAM_ID, @@ -994,7 +994,7 @@ fn test_pool_pda_produces_unique_id_for_token_pair() { #[should_panic(expected = "Vault A was not provided")] #[test] -fn test_call_add_liquidity_vault_a_omitted() { +fn call_add_liquidity_vault_a_omitted() { let _post_states = add_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_with_wrong_id(), @@ -1011,7 +1011,7 @@ fn test_call_add_liquidity_vault_a_omitted() { #[should_panic(expected = "Vault B was not provided")] #[test] -fn test_call_add_liquidity_vault_b_omitted() { +fn call_add_liquidity_vault_b_omitted() { let _post_states = add_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1028,7 +1028,7 @@ fn test_call_add_liquidity_vault_b_omitted() { #[should_panic(expected = "LP definition mismatch")] #[test] -fn test_call_add_liquidity_lp_definition_mismatch() { +fn call_add_liquidity_lp_definition_mismatch() { let _post_states = add_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1045,7 +1045,7 @@ fn test_call_add_liquidity_lp_definition_mismatch() { #[should_panic(expected = "Both max-balances must be nonzero")] #[test] -fn test_call_add_liquidity_zero_balance_1() { +fn call_add_liquidity_zero_balance_1() { let _post_states = add_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1062,7 +1062,7 @@ fn test_call_add_liquidity_zero_balance_1() { #[should_panic(expected = "Both max-balances must be nonzero")] #[test] -fn test_call_add_liquidity_zero_balance_2() { +fn call_add_liquidity_zero_balance_2() { let _post_states = add_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1079,7 +1079,7 @@ fn test_call_add_liquidity_zero_balance_2() { #[should_panic(expected = "Vaults' balances must be at least the reserve amounts")] #[test] -fn test_call_add_liquidity_vault_insufficient_balance_1() { +fn call_add_liquidity_vault_insufficient_balance_1() { let _post_states = add_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init_zero(), @@ -1096,7 +1096,7 @@ fn test_call_add_liquidity_vault_insufficient_balance_1() { #[should_panic(expected = "Vaults' balances must be at least the reserve amounts")] #[test] -fn test_call_add_liquidity_vault_insufficient_balance_2() { +fn call_add_liquidity_vault_insufficient_balance_2() { let _post_states = add_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1113,7 +1113,7 @@ fn test_call_add_liquidity_vault_insufficient_balance_2() { #[should_panic(expected = "A trade amount is 0")] #[test] -fn test_call_add_liquidity_actual_amount_zero_1() { +fn call_add_liquidity_actual_amount_zero_1() { let _post_states = add_liquidity( AccountForTests::pool_definition_init_reserve_a_low(), AccountForTests::vault_a_init_low(), @@ -1130,7 +1130,7 @@ fn test_call_add_liquidity_actual_amount_zero_1() { #[should_panic(expected = "A trade amount is 0")] #[test] -fn test_call_add_liquidity_actual_amount_zero_2() { +fn call_add_liquidity_actual_amount_zero_2() { let _post_states = add_liquidity( AccountForTests::pool_definition_init_reserve_b_low(), AccountForTests::vault_a_init_high(), @@ -1147,7 +1147,7 @@ fn test_call_add_liquidity_actual_amount_zero_2() { #[should_panic(expected = "Reserves must be nonzero")] #[test] -fn test_call_add_liquidity_reserves_zero_1() { +fn call_add_liquidity_reserves_zero_1() { let _post_states = add_liquidity( AccountForTests::pool_definition_init_reserve_a_zero(), AccountForTests::vault_a_init(), @@ -1164,7 +1164,7 @@ fn test_call_add_liquidity_reserves_zero_1() { #[should_panic(expected = "Reserves must be nonzero")] #[test] -fn test_call_add_liquidity_reserves_zero_2() { +fn call_add_liquidity_reserves_zero_2() { let _post_states = add_liquidity( AccountForTests::pool_definition_init_reserve_b_zero(), AccountForTests::vault_a_init(), @@ -1181,7 +1181,7 @@ fn test_call_add_liquidity_reserves_zero_2() { #[should_panic(expected = "Payable LP must be nonzero")] #[test] -fn test_call_add_liquidity_payable_lp_zero() { +fn call_add_liquidity_payable_lp_zero() { let _post_states = add_liquidity( AccountForTests::pool_definition_add_zero_lp(), AccountForTests::vault_a_init(), @@ -1197,7 +1197,7 @@ fn test_call_add_liquidity_payable_lp_zero() { } #[test] -fn test_call_add_liquidity_chained_call_successsful() { +fn call_add_liquidity_chained_call_successsful() { let (post_states, chained_calls) = add_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1226,7 +1226,7 @@ fn test_call_add_liquidity_chained_call_successsful() { #[should_panic(expected = "Vault A was not provided")] #[test] -fn test_call_remove_liquidity_vault_a_omitted() { +fn call_remove_liquidity_vault_a_omitted() { let _post_states = remove_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_with_wrong_id(), @@ -1243,7 +1243,7 @@ fn test_call_remove_liquidity_vault_a_omitted() { #[should_panic(expected = "Vault B was not provided")] #[test] -fn test_call_remove_liquidity_vault_b_omitted() { +fn call_remove_liquidity_vault_b_omitted() { let _post_states = remove_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1260,7 +1260,7 @@ fn test_call_remove_liquidity_vault_b_omitted() { #[should_panic(expected = "LP definition mismatch")] #[test] -fn test_call_remove_liquidity_lp_def_mismatch() { +fn call_remove_liquidity_lp_def_mismatch() { let _post_states = remove_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1277,7 +1277,7 @@ fn test_call_remove_liquidity_lp_def_mismatch() { #[should_panic(expected = "Invalid liquidity account provided")] #[test] -fn test_call_remove_liquidity_insufficient_liquidity_amount() { +fn call_remove_liquidity_insufficient_liquidity_amount() { let _post_states = remove_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1297,7 +1297,7 @@ fn test_call_remove_liquidity_insufficient_liquidity_amount() { expected = "Insufficient minimal withdraw amount (Token A) provided for liquidity amount" )] #[test] -fn test_call_remove_liquidity_insufficient_balance_1() { +fn call_remove_liquidity_insufficient_balance_1() { let _post_states = remove_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1316,7 +1316,7 @@ fn test_call_remove_liquidity_insufficient_balance_1() { expected = "Insufficient minimal withdraw amount (Token B) provided for liquidity amount" )] #[test] -fn test_call_remove_liquidity_insufficient_balance_2() { +fn call_remove_liquidity_insufficient_balance_2() { let _post_states = remove_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1333,7 +1333,7 @@ fn test_call_remove_liquidity_insufficient_balance_2() { #[should_panic(expected = "Minimum withdraw amount must be nonzero")] #[test] -fn test_call_remove_liquidity_min_bal_zero_1() { +fn call_remove_liquidity_min_bal_zero_1() { let _post_states = remove_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1350,7 +1350,7 @@ fn test_call_remove_liquidity_min_bal_zero_1() { #[should_panic(expected = "Minimum withdraw amount must be nonzero")] #[test] -fn test_call_remove_liquidity_min_bal_zero_2() { +fn call_remove_liquidity_min_bal_zero_2() { let _post_states = remove_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1366,7 +1366,7 @@ fn test_call_remove_liquidity_min_bal_zero_2() { } #[test] -fn test_call_remove_liquidity_chained_call_successful() { +fn call_remove_liquidity_chained_call_successful() { let (post_states, chained_calls) = remove_liquidity( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1395,7 +1395,7 @@ fn test_call_remove_liquidity_chained_call_successful() { #[should_panic(expected = "Balances must be nonzero")] #[test] -fn test_call_new_definition_with_zero_balance_1() { +fn call_new_definition_with_zero_balance_1() { let _post_states = new_definition( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1412,7 +1412,7 @@ fn test_call_new_definition_with_zero_balance_1() { #[should_panic(expected = "Balances must be nonzero")] #[test] -fn test_call_new_definition_with_zero_balance_2() { +fn call_new_definition_with_zero_balance_2() { let _post_states = new_definition( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1429,7 +1429,7 @@ fn test_call_new_definition_with_zero_balance_2() { #[should_panic(expected = "Cannot set up a swap for a token with itself")] #[test] -fn test_call_new_definition_same_token_definition() { +fn call_new_definition_same_token_definition() { let _post_states = new_definition( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1446,7 +1446,7 @@ fn test_call_new_definition_same_token_definition() { #[should_panic(expected = "Liquidity pool Token Definition Account ID does not match PDA")] #[test] -fn test_call_new_definition_wrong_liquidity_id() { +fn call_new_definition_wrong_liquidity_id() { let _post_states = new_definition( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1463,7 +1463,7 @@ fn test_call_new_definition_wrong_liquidity_id() { #[should_panic(expected = "Pool Definition Account ID does not match PDA")] #[test] -fn test_call_new_definition_wrong_pool_id() { +fn call_new_definition_wrong_pool_id() { let _post_states = new_definition( AccountForTests::pool_definition_with_wrong_id(), AccountForTests::vault_a_init(), @@ -1480,7 +1480,7 @@ fn test_call_new_definition_wrong_pool_id() { #[should_panic(expected = "Vault ID does not match PDA")] #[test] -fn test_call_new_definition_wrong_vault_id_1() { +fn call_new_definition_wrong_vault_id_1() { let _post_states = new_definition( AccountForTests::pool_definition_init(), AccountForTests::vault_a_with_wrong_id(), @@ -1497,7 +1497,7 @@ fn test_call_new_definition_wrong_vault_id_1() { #[should_panic(expected = "Vault ID does not match PDA")] #[test] -fn test_call_new_definition_wrong_vault_id_2() { +fn call_new_definition_wrong_vault_id_2() { let _post_states = new_definition( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1514,7 +1514,7 @@ fn test_call_new_definition_wrong_vault_id_2() { #[should_panic(expected = "Cannot initialize an active Pool Definition")] #[test] -fn test_call_new_definition_cannot_initialize_active_pool() { +fn call_new_definition_cannot_initialize_active_pool() { let _post_states = new_definition( AccountForTests::pool_definition_active(), AccountForTests::vault_a_init(), @@ -1531,7 +1531,7 @@ fn test_call_new_definition_cannot_initialize_active_pool() { #[should_panic(expected = "Cannot initialize an active Pool Definition")] #[test] -fn test_call_new_definition_chained_call_successful() { +fn call_new_definition_chained_call_successful() { let (post_states, chained_calls) = new_definition( AccountForTests::pool_definition_active(), AccountForTests::vault_a_init(), @@ -1560,7 +1560,7 @@ fn test_call_new_definition_chained_call_successful() { #[should_panic(expected = "AccountId is not a token type for the pool")] #[test] -fn test_call_swap_incorrect_token_type() { +fn call_swap_incorrect_token_type() { let _post_states = swap( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1575,7 +1575,7 @@ fn test_call_swap_incorrect_token_type() { #[should_panic(expected = "Vault A was not provided")] #[test] -fn test_call_swap_vault_a_omitted() { +fn call_swap_vault_a_omitted() { let _post_states = swap( AccountForTests::pool_definition_init(), AccountForTests::vault_a_with_wrong_id(), @@ -1590,7 +1590,7 @@ fn test_call_swap_vault_a_omitted() { #[should_panic(expected = "Vault B was not provided")] #[test] -fn test_call_swap_vault_b_omitted() { +fn call_swap_vault_b_omitted() { let _post_states = swap( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1605,7 +1605,7 @@ fn test_call_swap_vault_b_omitted() { #[should_panic(expected = "Reserve for Token A exceeds vault balance")] #[test] -fn test_call_swap_reserves_vault_mismatch_1() { +fn call_swap_reserves_vault_mismatch_1() { let _post_states = swap( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init_low(), @@ -1620,7 +1620,7 @@ fn test_call_swap_reserves_vault_mismatch_1() { #[should_panic(expected = "Reserve for Token B exceeds vault balance")] #[test] -fn test_call_swap_reserves_vault_mismatch_2() { +fn call_swap_reserves_vault_mismatch_2() { let _post_states = swap( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1635,7 +1635,7 @@ fn test_call_swap_reserves_vault_mismatch_2() { #[should_panic(expected = "Pool is inactive")] #[test] -fn test_call_swap_ianctive() { +fn call_swap_ianctive() { let _post_states = swap( AccountForTests::pool_definition_inactive(), AccountForTests::vault_a_init(), @@ -1650,7 +1650,7 @@ fn test_call_swap_ianctive() { #[should_panic(expected = "Withdraw amount is less than minimal amount out")] #[test] -fn test_call_swap_below_min_out() { +fn call_swap_below_min_out() { let _post_states = swap( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1664,7 +1664,7 @@ fn test_call_swap_below_min_out() { } #[test] -fn test_call_swap_chained_call_successful_1() { +fn call_swap_chained_call_successful_1() { let (post_states, chained_calls) = swap( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1694,7 +1694,7 @@ fn test_call_swap_chained_call_successful_1() { } #[test] -fn test_call_swap_chained_call_successful_2() { +fn call_swap_chained_call_successful_2() { let (post_states, chained_calls) = swap( AccountForTests::pool_definition_init(), AccountForTests::vault_a_init(), @@ -1724,7 +1724,7 @@ fn test_call_swap_chained_call_successful_2() { } #[test] -fn test_new_definition_lp_asymmetric_amounts() { +fn new_definition_lp_asymmetric_amounts() { let (post_states, chained_calls) = new_definition( AccountForTests::pool_definition_inactive(), AccountForTests::vault_a_init(), @@ -1751,10 +1751,10 @@ fn test_new_definition_lp_asymmetric_amounts() { } #[test] -fn test_new_definition_lp_symmetric_amounts() { +fn new_definition_lp_symmetric_amounts() { // token_a=100, token_b=100 → LP=sqrt(10_000)=100 - let token_a_amount = 100u128; - let token_b_amount = 100u128; + let token_a_amount = 100_u128; + let token_b_amount = 100_u128; let expected_lp = (token_a_amount * token_b_amount).isqrt(); assert_eq!(expected_lp, 100); diff --git a/programs/token/src/new_definition.rs b/programs/token/src/new_definition.rs index 9ba61047..8da55dc1 100644 --- a/programs/token/src/new_definition.rs +++ b/programs/token/src/new_definition.rs @@ -106,7 +106,7 @@ pub fn new_definition_with_metadata( standard: metadata.standard, uri: metadata.uri, creators: metadata.creators, - primary_sale_date: 0u64, // TODO #261: future works to implement this + primary_sale_date: 0_u64, // TODO #261: future works to implement this }; let mut definition_target_account_post = definition_target_account.account; diff --git a/programs/token/src/print_nft.rs b/programs/token/src/print_nft.rs index 33793ee2..c7177a43 100644 --- a/programs/token/src/print_nft.rs +++ b/programs/token/src/print_nft.rs @@ -37,7 +37,7 @@ pub fn print_nft( *print_balance > 1, "Insufficient balance to print another NFT copy" ); - *print_balance -= 1; + *print_balance = print_balance.checked_sub(1).expect("Checked above"); let mut master_account_post = master_account.account; master_account_post.data = Data::from(&master_account_data); diff --git a/programs/token/src/tests.rs b/programs/token/src/tests.rs index cf95c4d4..db0aa1bb 100644 --- a/programs/token/src/tests.rs +++ b/programs/token/src/tests.rs @@ -1,4 +1,9 @@ #![cfg(test)] +#![expect( + clippy::shadow_unrelated, + clippy::arithmetic_side_effects, + reason = "We don't care about it in tests" +)] use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data}; use token_core::{ @@ -25,8 +30,8 @@ impl AccountForTests { fn definition_account_auth() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: BalanceForTests::init_supply(), @@ -42,8 +47,8 @@ impl AccountForTests { fn definition_account_without_auth() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: BalanceForTests::init_supply(), @@ -59,8 +64,8 @@ impl AccountForTests { fn holding_different_definition() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id_diff(), balance: BalanceForTests::holding_balance(), @@ -75,8 +80,8 @@ impl AccountForTests { fn holding_same_definition_with_authorization() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::holding_balance(), @@ -91,8 +96,8 @@ impl AccountForTests { fn holding_same_definition_without_authorization() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::holding_balance(), @@ -107,8 +112,8 @@ impl AccountForTests { fn holding_same_definition_without_authorization_overflow() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::init_supply(), @@ -123,8 +128,8 @@ impl AccountForTests { fn definition_account_post_burn() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: BalanceForTests::init_supply_burned(), @@ -140,8 +145,8 @@ impl AccountForTests { fn holding_account_post_burn() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::holding_balance_burned(), @@ -164,8 +169,8 @@ impl AccountForTests { fn init_mint() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [0u32; 8], - balance: 0u128, + program_owner: [0_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::mint_success(), @@ -180,8 +185,8 @@ impl AccountForTests { fn holding_account_same_definition_mint() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::holding_balance_mint(), @@ -196,8 +201,8 @@ impl AccountForTests { fn definition_account_mint() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: BalanceForTests::init_supply_mint(), @@ -213,8 +218,8 @@ impl AccountForTests { fn holding_same_definition_with_authorization_and_large_balance() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::mint_overflow(), @@ -229,8 +234,8 @@ impl AccountForTests { fn definition_account_with_authorization_nonfungible() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenDefinition::NonFungible { name: String::from("test"), printable_supply: BalanceForTests::printable_copies(), @@ -254,8 +259,8 @@ impl AccountForTests { fn holding_account_init() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::init_supply(), @@ -270,8 +275,8 @@ impl AccountForTests { fn definition_account_unclaimed() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [0u32; 8], - balance: 0u128, + program_owner: [0_u32; 8], + balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), total_supply: BalanceForTests::init_supply(), @@ -287,8 +292,8 @@ impl AccountForTests { fn holding_account_unclaimed() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [0u32; 8], - balance: 0u128, + program_owner: [0_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::init_supply(), @@ -303,8 +308,8 @@ impl AccountForTests { fn holding_account2_init() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::init_supply(), @@ -319,8 +324,8 @@ impl AccountForTests { fn holding_account2_init_post_transfer() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::recipient_post_transfer(), @@ -335,8 +340,8 @@ impl AccountForTests { fn holding_account_init_post_transfer() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForTests::pool_definition_id(), balance: BalanceForTests::sender_post_transfer(), @@ -351,8 +356,8 @@ impl AccountForTests { fn holding_account_master_nft() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::NftMaster { definition_id: IdForTests::pool_definition_id(), print_balance: BalanceForTests::printable_copies(), @@ -367,8 +372,8 @@ impl AccountForTests { fn holding_account_master_nft_insufficient_balance() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::NftMaster { definition_id: IdForTests::pool_definition_id(), print_balance: 1, @@ -383,8 +388,8 @@ impl AccountForTests { fn holding_account_master_nft_after_print() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::NftMaster { definition_id: IdForTests::pool_definition_id(), print_balance: BalanceForTests::printable_copies() - 1, @@ -399,8 +404,8 @@ impl AccountForTests { fn holding_account_printed_nft() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [0u32; 8], - balance: 0u128, + program_owner: [0_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::NftPrintedCopy { definition_id: IdForTests::pool_definition_id(), owned: true, @@ -415,8 +420,8 @@ impl AccountForTests { fn holding_account_with_master_nft_transferred_to() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [0u32; 8], - balance: 0u128, + program_owner: [0_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::NftMaster { definition_id: IdForTests::pool_definition_id(), print_balance: BalanceForTests::printable_copies(), @@ -431,8 +436,8 @@ impl AccountForTests { fn holding_account_master_nft_post_transfer() -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: [5u32; 8], - balance: 0u128, + program_owner: [5_u32; 8], + balance: 0_u128, data: Data::from(&TokenHolding::NftMaster { definition_id: IdForTests::pool_definition_id(), print_balance: 0, @@ -523,7 +528,7 @@ impl IdForTests { #[should_panic(expected = "Definition target account must have default values")] #[test] -fn test_new_definition_non_default_first_account_should_fail() { +fn new_definition_non_default_first_account_should_fail() { let definition_account = AccountWithMetadata { account: Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], @@ -547,7 +552,7 @@ fn test_new_definition_non_default_first_account_should_fail() { #[should_panic(expected = "Holding target account must have default values")] #[test] -fn test_new_definition_non_default_second_account_should_fail() { +fn new_definition_non_default_second_account_should_fail() { let definition_account = AccountWithMetadata { account: Account::default(), is_authorized: true, @@ -570,7 +575,7 @@ fn test_new_definition_non_default_second_account_should_fail() { } #[test] -fn test_new_definition_with_valid_inputs_succeeds() { +fn new_definition_with_valid_inputs_succeeds() { let definition_account = AccountForTests::definition_account_uninit(); let holding_account = AccountForTests::holding_account_uninit(); @@ -595,7 +600,7 @@ fn test_new_definition_with_valid_inputs_succeeds() { #[should_panic(expected = "Sender and recipient definition id mismatch")] #[test] -fn test_transfer_with_different_definition_ids_should_fail() { +fn transfer_with_different_definition_ids_should_fail() { let sender = AccountForTests::holding_same_definition_with_authorization(); let recipient = AccountForTests::holding_different_definition(); let _post_states = transfer(sender, recipient, 10); @@ -603,7 +608,7 @@ fn test_transfer_with_different_definition_ids_should_fail() { #[should_panic(expected = "Insufficient balance")] #[test] -fn test_transfer_with_insufficient_balance_should_fail() { +fn transfer_with_insufficient_balance_should_fail() { let sender = AccountForTests::holding_same_definition_with_authorization(); let recipient = AccountForTests::holding_account_same_definition_mint(); // Attempt to transfer more than balance @@ -612,14 +617,14 @@ fn test_transfer_with_insufficient_balance_should_fail() { #[should_panic(expected = "Sender authorization is missing")] #[test] -fn test_transfer_without_sender_authorization_should_fail() { +fn transfer_without_sender_authorization_should_fail() { let sender = AccountForTests::holding_same_definition_without_authorization(); let recipient = AccountForTests::holding_account_uninit(); let _post_states = transfer(sender, recipient, 37); } #[test] -fn test_transfer_with_valid_inputs_succeeds() { +fn transfer_with_valid_inputs_succeeds() { let sender = AccountForTests::holding_account_init(); let recipient = AccountForTests::holding_account2_init(); let post_states = transfer(sender, recipient, BalanceForTests::transfer_amount()); @@ -637,7 +642,7 @@ fn test_transfer_with_valid_inputs_succeeds() { #[should_panic(expected = "Invalid balance for NFT Master transfer")] #[test] -fn test_transfer_with_master_nft_invalid_balance() { +fn transfer_with_master_nft_invalid_balance() { let sender = AccountForTests::holding_account_master_nft(); let recipient = AccountForTests::holding_account_uninit(); let _post_states = transfer(sender, recipient, BalanceForTests::transfer_amount()); @@ -645,14 +650,14 @@ fn test_transfer_with_master_nft_invalid_balance() { #[should_panic(expected = "Invalid balance in recipient account for NFT transfer")] #[test] -fn test_transfer_with_master_nft_invalid_recipient_balance() { +fn transfer_with_master_nft_invalid_recipient_balance() { let sender = AccountForTests::holding_account_master_nft(); let recipient = AccountForTests::holding_account_with_master_nft_transferred_to(); let _post_states = transfer(sender, recipient, BalanceForTests::printable_copies()); } #[test] -fn test_transfer_with_master_nft_success() { +fn transfer_with_master_nft_success() { let sender = AccountForTests::holding_account_master_nft(); let recipient = AccountForTests::holding_account_uninit(); let post_states = transfer(sender, recipient, BalanceForTests::printable_copies()); @@ -669,7 +674,7 @@ fn test_transfer_with_master_nft_success() { } #[test] -fn test_token_initialize_account_succeeds() { +fn token_initialize_account_succeeds() { let sender = AccountForTests::holding_account_init(); let recipient = AccountForTests::holding_account2_init(); let post_states = transfer(sender, recipient, BalanceForTests::transfer_amount()); @@ -687,7 +692,7 @@ fn test_token_initialize_account_succeeds() { #[test] #[should_panic(expected = "Mismatch Token Definition and Token Holding")] -fn test_burn_mismatch_def() { +fn burn_mismatch_def() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_different_definition(); let _post_states = burn( @@ -699,7 +704,7 @@ fn test_burn_mismatch_def() { #[test] #[should_panic(expected = "Authorization is missing")] -fn test_burn_missing_authorization() { +fn burn_missing_authorization() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_same_definition_without_authorization(); let _post_states = burn( @@ -711,7 +716,7 @@ fn test_burn_missing_authorization() { #[test] #[should_panic(expected = "Insufficient balance to burn")] -fn test_burn_insufficient_balance() { +fn burn_insufficient_balance() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_same_definition_with_authorization(); let _post_states = burn( @@ -723,7 +728,7 @@ fn test_burn_insufficient_balance() { #[test] #[should_panic(expected = "Total supply underflow")] -fn test_burn_total_supply_underflow() { +fn burn_total_supply_underflow() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_same_definition_with_authorization_and_large_balance(); @@ -735,7 +740,7 @@ fn test_burn_total_supply_underflow() { } #[test] -fn test_burn_success() { +fn burn_success() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_same_definition_with_authorization(); let post_states = burn( @@ -758,7 +763,7 @@ fn test_burn_success() { #[test] #[should_panic(expected = "Holding account must be valid")] -fn test_mint_not_valid_holding_account() { +fn mint_not_valid_holding_account() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::definition_account_without_auth(); let _post_states = mint( @@ -770,7 +775,7 @@ fn test_mint_not_valid_holding_account() { #[test] #[should_panic(expected = "Definition account must be valid")] -fn test_mint_not_valid_definition_account() { +fn mint_not_valid_definition_account() { let definition_account = AccountForTests::holding_same_definition_with_authorization(); let holding_account = AccountForTests::holding_same_definition_without_authorization(); let _post_states = mint( @@ -782,7 +787,7 @@ fn test_mint_not_valid_definition_account() { #[test] #[should_panic(expected = "Definition authorization is missing")] -fn test_mint_missing_authorization() { +fn mint_missing_authorization() { let definition_account = AccountForTests::definition_account_without_auth(); let holding_account = AccountForTests::holding_same_definition_without_authorization(); let _post_states = mint( @@ -794,7 +799,7 @@ fn test_mint_missing_authorization() { #[test] #[should_panic(expected = "Mismatch Token Definition and Token Holding")] -fn test_mint_mismatched_token_definition() { +fn mint_mismatched_token_definition() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_different_definition(); let _post_states = mint( @@ -805,7 +810,7 @@ fn test_mint_mismatched_token_definition() { } #[test] -fn test_mint_success() { +fn mint_success() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_same_definition_without_authorization(); let post_states = mint( @@ -827,7 +832,7 @@ fn test_mint_success() { } #[test] -fn test_mint_uninit_holding_success() { +fn mint_uninit_holding_success() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_account_uninit(); let post_states = mint( @@ -851,7 +856,7 @@ fn test_mint_uninit_holding_success() { #[test] #[should_panic(expected = "Total supply overflow")] -fn test_mint_total_supply_overflow() { +fn mint_total_supply_overflow() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_same_definition_without_authorization(); let _post_states = mint( @@ -863,7 +868,7 @@ fn test_mint_total_supply_overflow() { #[test] #[should_panic(expected = "Balance overflow on minting")] -fn test_mint_holding_account_overflow() { +fn mint_holding_account_overflow() { let definition_account = AccountForTests::definition_account_auth(); let holding_account = AccountForTests::holding_same_definition_without_authorization_overflow(); let _post_states = mint( @@ -875,7 +880,7 @@ fn test_mint_holding_account_overflow() { #[test] #[should_panic(expected = "Cannot mint additional supply for Non-Fungible Tokens")] -fn test_mint_cannot_mint_unmintable_tokens() { +fn mint_cannot_mint_unmintable_tokens() { let definition_account = AccountForTests::definition_account_with_authorization_nonfungible(); let holding_account = AccountForTests::holding_account_master_nft(); let _post_states = mint( @@ -887,7 +892,7 @@ fn test_mint_cannot_mint_unmintable_tokens() { #[should_panic(expected = "Definition target account must have default values")] #[test] -fn test_call_new_definition_metadata_with_init_definition() { +fn call_new_definition_metadata_with_init_definition() { let definition_account = AccountForTests::definition_account_auth(); let metadata_account = AccountWithMetadata { account: Account::default(), @@ -901,12 +906,12 @@ fn test_call_new_definition_metadata_with_init_definition() { }; let new_definition = NewTokenDefinition::Fungible { name: String::from("test"), - total_supply: 15u128, + total_supply: 15_u128, }; let metadata = NewTokenMetadata { standard: MetadataStandard::Simple, - uri: "test_uri".to_string(), - creators: "test_creators".to_string(), + uri: "test_uri".to_owned(), + creators: "test_creators".to_owned(), }; let _post_states = new_definition_with_metadata( definition_account, @@ -919,7 +924,7 @@ fn test_call_new_definition_metadata_with_init_definition() { #[should_panic(expected = "Metadata target account must have default values")] #[test] -fn test_call_new_definition_metadata_with_init_metadata() { +fn call_new_definition_metadata_with_init_metadata() { let definition_account = AccountWithMetadata { account: Account::default(), is_authorized: true, @@ -933,12 +938,12 @@ fn test_call_new_definition_metadata_with_init_metadata() { let metadata_account = AccountForTests::holding_account_same_definition_mint(); let new_definition = NewTokenDefinition::Fungible { name: String::from("test"), - total_supply: 15u128, + total_supply: 15_u128, }; let metadata = NewTokenMetadata { standard: MetadataStandard::Simple, - uri: "test_uri".to_string(), - creators: "test_creators".to_string(), + uri: "test_uri".to_owned(), + creators: "test_creators".to_owned(), }; let _post_states = new_definition_with_metadata( definition_account, @@ -951,7 +956,7 @@ fn test_call_new_definition_metadata_with_init_metadata() { #[should_panic(expected = "Holding target account must have default values")] #[test] -fn test_call_new_definition_metadata_with_init_holding() { +fn call_new_definition_metadata_with_init_holding() { let definition_account = AccountWithMetadata { account: Account::default(), is_authorized: true, @@ -965,12 +970,12 @@ fn test_call_new_definition_metadata_with_init_holding() { let holding_account = AccountForTests::holding_account_same_definition_mint(); let new_definition = NewTokenDefinition::Fungible { name: String::from("test"), - total_supply: 15u128, + total_supply: 15_u128, }; let metadata = NewTokenMetadata { standard: MetadataStandard::Simple, - uri: "test_uri".to_string(), - creators: "test_creators".to_string(), + uri: "test_uri".to_owned(), + creators: "test_creators".to_owned(), }; let _post_states = new_definition_with_metadata( definition_account, @@ -983,7 +988,7 @@ fn test_call_new_definition_metadata_with_init_holding() { #[should_panic(expected = "Master NFT Account must be authorized")] #[test] -fn test_print_nft_master_account_must_be_authorized() { +fn print_nft_master_account_must_be_authorized() { let master_account = AccountForTests::holding_account_uninit(); let printed_account = AccountForTests::holding_account_uninit(); let _post_states = print_nft(master_account, printed_account); @@ -991,7 +996,7 @@ fn test_print_nft_master_account_must_be_authorized() { #[should_panic(expected = "Printed Account must be uninitialized")] #[test] -fn test_print_nft_print_account_initialized() { +fn print_nft_print_account_initialized() { let master_account = AccountForTests::holding_account_master_nft(); let printed_account = AccountForTests::holding_account_init(); let _post_states = print_nft(master_account, printed_account); @@ -999,7 +1004,7 @@ fn test_print_nft_print_account_initialized() { #[should_panic(expected = "Invalid Token Holding data")] #[test] -fn test_print_nft_master_nft_invalid_token_holding() { +fn print_nft_master_nft_invalid_token_holding() { let master_account = AccountForTests::definition_account_auth(); let printed_account = AccountForTests::holding_account_uninit(); let _post_states = print_nft(master_account, printed_account); @@ -1007,7 +1012,7 @@ fn test_print_nft_master_nft_invalid_token_holding() { #[should_panic(expected = "Invalid Token Holding provided as NFT Master Account")] #[test] -fn test_print_nft_master_nft_not_nft_master_account() { +fn print_nft_master_nft_not_nft_master_account() { let master_account = AccountForTests::holding_account_init(); let printed_account = AccountForTests::holding_account_uninit(); let _post_states = print_nft(master_account, printed_account); @@ -1015,14 +1020,14 @@ fn test_print_nft_master_nft_not_nft_master_account() { #[should_panic(expected = "Insufficient balance to print another NFT copy")] #[test] -fn test_print_nft_master_nft_insufficient_balance() { +fn print_nft_master_nft_insufficient_balance() { let master_account = AccountForTests::holding_account_master_nft_insufficient_balance(); let printed_account = AccountForTests::holding_account_uninit(); let _post_states = print_nft(master_account, printed_account); } #[test] -fn test_print_nft_success() { +fn print_nft_success() { let master_account = AccountForTests::holding_account_master_nft(); let printed_account = AccountForTests::holding_account_uninit(); let post_states = print_nft(master_account, printed_account); diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index 78d434a9..ad8247ed 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -1,9 +1,9 @@ -use anyhow::{Context, Result}; +use anyhow::{Context as _, Result}; use bedrock_client::BedrockClient; pub use common::block::Block; pub use logos_blockchain_core::mantle::{MantleTx, SignedMantleTx, ops::channel::MsgId}; use logos_blockchain_core::mantle::{ - Op, OpProof, Transaction, TxHash, ledger, + Op, OpProof, Transaction as _, TxHash, ledger, ops::channel::{ChannelId, inscribe::InscriptionOp}, }; pub use logos_blockchain_key_management_system_service::keys::Ed25519Key; @@ -14,7 +14,7 @@ use crate::config::BedrockConfig; #[expect(async_fn_in_trait, reason = "We don't care about Send/Sync here")] pub trait BlockSettlementClientTrait: Clone { //// Create a new client. - fn new(config: &BedrockConfig, bedrock_signing_key: Ed25519Key) -> Result; + fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result; /// Get the bedrock channel ID used by this client. fn bedrock_channel_id(&self) -> ChannelId; diff --git a/sequencer_core/src/block_store.rs b/sequencer_core/src/block_store.rs index 19f6cb0e..3cec3127 100644 --- a/sequencer_core/src/block_store.rs +++ b/sequencer_core/src/block_store.rs @@ -24,16 +24,13 @@ impl SequencerStore { /// ATTENTION: Will overwrite genesis block. pub fn open_db_with_genesis( location: &Path, - genesis_block: Option<(&Block, MantleMsgId)>, + genesis_block: &Block, + genesis_msg_id: MantleMsgId, signing_key: nssa::PrivateKey, ) -> Result { - let tx_hash_to_block_map = if let Some((block, _msg_id)) = &genesis_block { - block_to_transactions_map(block) - } else { - HashMap::new() - }; + let tx_hash_to_block_map = block_to_transactions_map(genesis_block); - let dbio = RocksDBIO::open_or_create(location, genesis_block)?; + let dbio = RocksDBIO::open_or_create(location, genesis_block, genesis_msg_id)?; let genesis_id = dbio.get_meta_first_block_in_db()?; @@ -45,11 +42,6 @@ impl SequencerStore { }) } - /// Reopening existing database - pub fn open_db_restart(location: &Path, signing_key: nssa::PrivateKey) -> Result { - SequencerStore::open_db_with_genesis(location, None, signing_key) - } - pub fn get_block_at_id(&self, id: u64) -> Result { Ok(self.dbio.get_block(id)?) } @@ -120,13 +112,15 @@ pub(crate) fn block_to_transactions_map(block: &Block) -> HashMap #[cfg(test)] mod tests { + #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] + use common::{block::HashableBlockData, test_utils::sequencer_sign_key_for_testing}; use tempfile::tempdir; use super::*; #[test] - fn test_get_transaction_by_hash() { + fn get_transaction_by_hash() { let temp_dir = tempdir().unwrap(); let path = temp_dir.path(); @@ -141,12 +135,9 @@ mod tests { let genesis_block = genesis_block_hashable_data.into_pending_block(&signing_key, [0; 32]); // Start an empty node store - let mut node_store = SequencerStore::open_db_with_genesis( - path, - Some((&genesis_block, [0; 32])), - signing_key, - ) - .unwrap(); + let mut node_store = + SequencerStore::open_db_with_genesis(path, &genesis_block, [0; 32], signing_key) + .unwrap(); let tx = common::test_utils::produce_dummy_empty_transaction(); let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]); @@ -163,7 +154,7 @@ mod tests { } #[test] - fn test_latest_block_meta_returns_genesis_meta_initially() { + fn latest_block_meta_returns_genesis_meta_initially() { let temp_dir = tempdir().unwrap(); let path = temp_dir.path(); @@ -179,12 +170,9 @@ mod tests { let genesis_block = genesis_block_hashable_data.into_pending_block(&signing_key, [0; 32]); let genesis_hash = genesis_block.header.hash; - let node_store = SequencerStore::open_db_with_genesis( - path, - Some((&genesis_block, [0; 32])), - signing_key, - ) - .unwrap(); + let node_store = + SequencerStore::open_db_with_genesis(path, &genesis_block, [0; 32], signing_key) + .unwrap(); // Verify that initially the latest block hash equals genesis hash let latest_meta = node_store.latest_block_meta().unwrap(); @@ -193,7 +181,7 @@ mod tests { } #[test] - fn test_latest_block_meta_updates_after_new_block() { + fn latest_block_meta_updates_after_new_block() { let temp_dir = tempdir().unwrap(); let path = temp_dir.path(); @@ -207,12 +195,9 @@ mod tests { }; let genesis_block = genesis_block_hashable_data.into_pending_block(&signing_key, [0; 32]); - let mut node_store = SequencerStore::open_db_with_genesis( - path, - Some((&genesis_block, [0; 32])), - signing_key, - ) - .unwrap(); + let mut node_store = + SequencerStore::open_db_with_genesis(path, &genesis_block, [0; 32], signing_key) + .unwrap(); // Add a new block let tx = common::test_utils::produce_dummy_empty_transaction(); @@ -232,7 +217,7 @@ mod tests { } #[test] - fn test_mark_block_finalized() { + fn mark_block_finalized() { let temp_dir = tempdir().unwrap(); let path = temp_dir.path(); @@ -246,12 +231,9 @@ mod tests { }; let genesis_block = genesis_block_hashable_data.into_pending_block(&signing_key, [0; 32]); - let mut node_store = SequencerStore::open_db_with_genesis( - path, - Some((&genesis_block, [0; 32])), - signing_key, - ) - .unwrap(); + let mut node_store = + SequencerStore::open_db_with_genesis(path, &genesis_block, [0; 32], signing_key) + .unwrap(); // Add a new block with Pending status let tx = common::test_utils::produce_dummy_empty_transaction(); diff --git a/sequencer_core/src/lib.rs b/sequencer_core/src/lib.rs index 36bd03d8..cd39dfc0 100644 --- a/sequencer_core/src/lib.rs +++ b/sequencer_core/src/lib.rs @@ -13,6 +13,8 @@ use config::SequencerConfig; use log::{error, info, warn}; use logos_blockchain_key_management_system_service::keys::{ED25519_SECRET_KEY_SIZE, Ed25519Key}; use mempool::{MemPool, MemPoolHandle}; +#[cfg(feature = "mock")] +pub use mock::SequencerCoreWithMockClients; use crate::{ block_settlement_client::{BlockSettlementClient, BlockSettlementClientTrait, MsgId}, @@ -24,11 +26,9 @@ pub mod block_settlement_client; pub mod block_store; pub mod config; pub mod indexer_client; -#[cfg(feature = "mock")] -pub mod mock; #[cfg(feature = "mock")] -pub use mock::SequencerCoreWithMockClients; +pub mod mock; pub struct SequencerCore< BC: BlockSettlementClientTrait = BlockSettlementClient, @@ -82,7 +82,8 @@ impl SequencerCore SequencerCore Result<(SignedMantleTx, MsgId)> { let now = Instant::now(); - let new_block_height = self.chain_height + 1; + let new_block_height = self + .chain_height + .checked_add(1) + .with_context(|| format!("Max block height reached: {}", self.chain_height))?; let mut valid_transactions = vec![]; @@ -338,12 +342,14 @@ impl SequencerCore Result { if path.exists() { let key_bytes = std::fs::read(path)?; + let key_array: [u8; ED25519_SECRET_KEY_SIZE] = key_bytes .try_into() - .map_err(|_| anyhow!("Found key with incorrect length"))?; + .map_err(|_bytes| anyhow!("Found key with incorrect length"))?; + Ok(Ed25519Key::from_bytes(&key_array)) } else { - let mut key_bytes = [0u8; ED25519_SECRET_KEY_SIZE]; + let mut key_bytes = [0_u8; ED25519_SECRET_KEY_SIZE]; rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut key_bytes); // Create parent directory if it doesn't exist if let Some(parent) = path.parent() { @@ -354,11 +360,14 @@ fn load_or_create_signing_key(path: &Path) -> Result { } } -#[cfg(all(test, feature = "mock"))] +#[cfg(test)] +#[cfg(feature = "mock")] mod tests { + #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] + use std::{pin::pin, str::FromStr as _, time::Duration}; - use base58::ToBase58; + use base58::ToBase58 as _; use bedrock_client::BackoffConfig; use common::{ block::AccountInitialData, test_utils::sequencer_sign_key_for_testing, @@ -381,7 +390,7 @@ mod tests { SequencerConfig { home, - override_rust_log: Some("info".to_string()), + override_rust_log: Some("info".to_owned()), genesis_id: 1, is_genesis_random: false, max_num_tx_in_block: 10, @@ -462,7 +471,7 @@ mod tests { } #[tokio::test] - async fn test_start_from_config() { + async fn start_from_config() { let config = setup_sequencer_config(); let (sequencer, _mempool_handle) = SequencerCoreWithMockClients::start_from_config(config.clone()).await; @@ -482,7 +491,7 @@ mod tests { } #[tokio::test] - async fn test_start_different_intial_accounts_balances() { + async fn start_different_intial_accounts_balances() { let acc1_account_id: Vec = vec![ 27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24, 52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143, @@ -523,7 +532,7 @@ mod tests { } #[test] - fn test_transaction_pre_check_pass() { + fn transaction_pre_check_pass() { let tx = common::test_utils::produce_dummy_empty_transaction(); let result = tx.transaction_stateless_check(); @@ -531,7 +540,7 @@ mod tests { } #[tokio::test] - async fn test_transaction_pre_check_native_transfer_valid() { + async fn transaction_pre_check_native_transfer_valid() { let (sequencer, _mempool_handle) = common_setup().await; let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id; @@ -548,7 +557,7 @@ mod tests { } #[tokio::test] - async fn test_transaction_pre_check_native_transfer_other_signature() { + async fn transaction_pre_check_native_transfer_other_signature() { let (mut sequencer, _mempool_handle) = common_setup().await; let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id; @@ -573,7 +582,7 @@ mod tests { } #[tokio::test] - async fn test_transaction_pre_check_native_transfer_sent_too_much() { + async fn transaction_pre_check_native_transfer_sent_too_much() { let (mut sequencer, _mempool_handle) = common_setup().await; let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id; @@ -600,7 +609,7 @@ mod tests { } #[tokio::test] - async fn test_transaction_execute_native_transfer() { + async fn transaction_execute_native_transfer() { let (mut sequencer, _mempool_handle) = common_setup().await; let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id; @@ -622,7 +631,7 @@ mod tests { } #[tokio::test] - async fn test_push_tx_into_mempool_blocks_until_mempool_is_full() { + async fn push_tx_into_mempool_blocks_until_mempool_is_full() { let config = SequencerConfig { mempool_max_size: 1, ..setup_sequencer_config() @@ -649,7 +658,7 @@ mod tests { } #[tokio::test] - async fn test_produce_new_block_with_mempool_transactions() { + async fn produce_new_block_with_mempool_transactions() { let (mut sequencer, mempool_handle) = common_setup().await; let genesis_height = sequencer.chain_height; @@ -662,7 +671,7 @@ mod tests { } #[tokio::test] - async fn test_replay_transactions_are_rejected_in_the_same_block() { + async fn replay_transactions_are_rejected_in_the_same_block() { let (mut sequencer, mempool_handle) = common_setup().await; let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id; @@ -694,7 +703,7 @@ mod tests { } #[tokio::test] - async fn test_replay_transactions_are_rejected_in_different_blocks() { + async fn replay_transactions_are_rejected_in_different_blocks() { let (mut sequencer, mempool_handle) = common_setup().await; let acc1 = sequencer.sequencer_config.initial_accounts[0].account_id; @@ -730,7 +739,7 @@ mod tests { } #[tokio::test] - async fn test_restart_from_storage() { + async fn restart_from_storage() { let config = setup_sequencer_config(); let acc1_account_id = config.initial_accounts[0].account_id; let acc2_account_id = config.initial_accounts[1].account_id; @@ -782,7 +791,7 @@ mod tests { } #[tokio::test] - async fn test_get_pending_blocks() { + async fn get_pending_blocks() { let config = setup_sequencer_config(); let (mut sequencer, _mempool_handle) = SequencerCoreWithMockClients::start_from_config(config).await; @@ -799,7 +808,7 @@ mod tests { } #[tokio::test] - async fn test_delete_blocks() { + async fn delete_blocks() { let config = setup_sequencer_config(); let (mut sequencer, _mempool_handle) = SequencerCoreWithMockClients::start_from_config(config).await; @@ -822,7 +831,7 @@ mod tests { } #[tokio::test] - async fn test_produce_block_with_correct_prev_meta_after_restart() { + async fn produce_block_with_correct_prev_meta_after_restart() { let config = setup_sequencer_config(); let acc1_account_id = config.initial_accounts[0].account_id; let acc2_account_id = config.initial_accounts[1].account_id; @@ -895,7 +904,7 @@ mod tests { } #[tokio::test] - async fn test_start_from_config_uses_db_height_not_config_genesis() { + async fn start_from_config_uses_db_height_not_config_genesis() { let mut config = setup_sequencer_config(); let original_genesis_id = config.genesis_id; diff --git a/sequencer_core/src/mock.rs b/sequencer_core/src/mock.rs index 930ff946..45a682e2 100644 --- a/sequencer_core/src/mock.rs +++ b/sequencer_core/src/mock.rs @@ -19,10 +19,10 @@ pub struct MockBlockSettlementClient { } impl BlockSettlementClientTrait for MockBlockSettlementClient { - fn new(config: &BedrockConfig, bedrock_signing_key: Ed25519Key) -> Result { + fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result { Ok(Self { bedrock_channel_id: config.channel_id, - bedrock_signing_key, + bedrock_signing_key: signing_key, }) } @@ -46,10 +46,10 @@ pub struct MockBlockSettlementClientWithError { } impl BlockSettlementClientTrait for MockBlockSettlementClientWithError { - fn new(config: &BedrockConfig, bedrock_signing_key: Ed25519Key) -> Result { + fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result { Ok(Self { bedrock_channel_id: config.channel_id, - bedrock_signing_key, + bedrock_signing_key: signing_key, }) } diff --git a/sequencer_rpc/src/lib.rs b/sequencer_rpc/src/lib.rs index 68b13d7f..b6049715 100644 --- a/sequencer_rpc/src/lib.rs +++ b/sequencer_rpc/src/lib.rs @@ -1,7 +1,3 @@ -pub mod net_utils; -pub mod process; -pub mod types; - use std::sync::Arc; use common::{ @@ -10,6 +6,8 @@ use common::{ }; use mempool::MemPoolHandle; pub use net_utils::*; +#[cfg(feature = "standalone")] +use sequencer_core::mock::{MockBlockSettlementClient, MockIndexerClient}; use sequencer_core::{ SequencerCore, block_settlement_client::{BlockSettlementClient, BlockSettlementClientTrait}, @@ -21,6 +19,13 @@ use tokio::sync::Mutex; use self::types::err_rpc::RpcErr; +pub mod net_utils; +pub mod process; +pub mod types; + +#[cfg(feature = "standalone")] +pub type JsonHandlerWithMockClients = JsonHandler; + // ToDo: Add necessary fields pub struct JsonHandler< BC: BlockSettlementClientTrait = BlockSettlementClient, @@ -51,9 +56,3 @@ pub fn rpc_error_responce_inverter(err: RpcError) -> RpcError { data: content, } } - -#[cfg(feature = "standalone")] -use sequencer_core::mock::{MockBlockSettlementClient, MockIndexerClient}; - -#[cfg(feature = "standalone")] -pub type JsonHandlerWithMockClients = JsonHandler; diff --git a/sequencer_rpc/src/net_utils.rs b/sequencer_rpc/src/net_utils.rs index d170638d..e306ec0e 100644 --- a/sequencer_rpc/src/net_utils.rs +++ b/sequencer_rpc/src/net_utils.rs @@ -6,28 +6,26 @@ use common::{ rpc_primitives::{RpcConfig, message::Message}, transaction::NSSATransaction, }; -use futures::{Future, FutureExt}; +use futures::{Future, FutureExt as _}; use log::info; use mempool::MemPoolHandle; #[cfg(not(feature = "standalone"))] use sequencer_core::SequencerCore; #[cfg(feature = "standalone")] use sequencer_core::SequencerCoreWithMockClients as SequencerCore; +use tokio::sync::Mutex; #[cfg(not(feature = "standalone"))] use super::JsonHandler; - -#[cfg(feature = "standalone")] -type JsonHandler = super::JsonHandlerWithMockClients; - -use tokio::sync::Mutex; - use crate::process::Process; pub const SHUTDOWN_TIMEOUT_SECS: u64 = 10; pub const NETWORK: &str = "network"; +#[cfg(feature = "standalone")] +type JsonHandler = super::JsonHandlerWithMockClients; + pub(crate) fn rpc_handler( message: web::Json, handler: web::Data

, @@ -41,7 +39,7 @@ pub(crate) fn rpc_handler( fn get_cors(cors_allowed_origins: &[String]) -> Cors { let mut cors = Cors::permissive(); - if cors_allowed_origins != ["*".to_string()] { + if cors_allowed_origins != ["*".to_owned()] { for origin in cors_allowed_origins { cors = cors.allowed_origin(origin); } @@ -72,7 +70,7 @@ pub async fn new_http_server( .try_into() .expect("`max_block_size` is expected to fit into usize"); let handler = web::Data::new(JsonHandler { - sequencer_state: seuquencer_core.clone(), + sequencer_state: Arc::clone(&seuquencer_core), mempool_handle, max_block_size, }); @@ -95,12 +93,12 @@ pub async fn new_http_server( .shutdown_timeout(SHUTDOWN_TIMEOUT_SECS) .disable_signals(); - let [addr] = http_server + let [final_addr] = http_server .addrs() .try_into() .expect("Exactly one address bound is expected for sequencer HTTP server"); - info!(target:NETWORK, "HTTP server started at {addr}"); + info!(target:NETWORK, "HTTP server started at {final_addr}"); - Ok((http_server.run(), addr)) + Ok((http_server.run(), final_addr)) } diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index b96510a8..66978bea 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; use actix_web::Error as HttpError; -use base64::{Engine, engine::general_purpose}; +use base64::{Engine as _, engine::general_purpose}; use common::{ block::{AccountInitialData, HashableBlockData}, rpc_primitives::{ errors::RpcError, message::{Message, Request}, - parser::RpcRequest, + parser::RpcRequest as _, requests::{ GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, @@ -78,12 +78,11 @@ impl< impl JsonHandler { /// Example of request processing - #[allow(clippy::unused_async)] - async fn process_temp_hello(&self, request: Request) -> Result { + fn process_temp_hello(request: Request) -> Result { let _hello_request = HelloRequest::parse(Some(request.params))?; let response = HelloResponse { - greeting: HELLO_FROM_SEQUENCER.to_string(), + greeting: HELLO_FROM_SEQUENCER.to_owned(), }; respond(response) @@ -99,9 +98,7 @@ impl JsonHandler let tx_hash = tx.hash(); - let tx_size = borsh::to_vec(&tx) - .map_err(|_| TransactionMalformationError::FailedToDecode { tx: tx_hash })? - .len(); + let tx_size = send_tx_req.transaction.len(); let max_tx_size = self.max_block_size.saturating_sub(BLOCK_HEADER_OVERHEAD); @@ -125,7 +122,7 @@ impl JsonHandler .expect("Mempool is closed, this is a bug"); let response = SendTxResponse { - status: TRANSACTION_SUBMITTED.to_string(), + status: TRANSACTION_SUBMITTED.to_owned(), tx_hash, }; @@ -306,14 +303,14 @@ impl JsonHandler let mut program_ids = HashMap::new(); program_ids.insert( - "authenticated_transfer".to_string(), + "authenticated_transfer".to_owned(), Program::authenticated_transfer_program().id(), ); - program_ids.insert("token".to_string(), Program::token().id()); - program_ids.insert("pinata".to_string(), Program::pinata().id()); - program_ids.insert("amm".to_string(), Program::amm().id()); + program_ids.insert("token".to_owned(), Program::token().id()); + program_ids.insert("pinata".to_owned(), Program::pinata().id()); + program_ids.insert("amm".to_owned(), Program::amm().id()); program_ids.insert( - "privacy_preserving_circuit".to_string(), + "privacy_preserving_circuit".to_owned(), nssa::PRIVACY_PRESERVING_CIRCUIT_ID, ); let response = GetProgramIdsResponse { program_ids }; @@ -322,7 +319,7 @@ impl JsonHandler pub async fn process_request_internal(&self, request: Request) -> Result { match request.method.as_ref() { - HELLO => self.process_temp_hello(request).await, + HELLO => Self::process_temp_hello(request), SEND_TX => self.process_send_tx(request).await, GET_BLOCK => self.process_get_block_data(request).await, GET_BLOCK_RANGE => self.process_get_block_range_data(request).await, @@ -340,16 +337,12 @@ impl JsonHandler } } -#[expect( - clippy::cast_possible_truncation, - reason = "Test code assumes usize is large enough for max_block_size" -)] #[cfg(test)] mod tests { use std::{str::FromStr as _, sync::Arc, time::Duration}; - use base58::ToBase58; - use base64::{Engine, engine::general_purpose}; + use base58::ToBase58 as _; + use base64::{Engine as _, engine::general_purpose}; use bedrock_client::BackoffConfig; use common::{ block::AccountInitialData, config::BasicAuth, test_utils::sequencer_sign_key_for_testing, @@ -396,7 +389,7 @@ mod tests { SequencerConfig { home, - override_rust_log: Some("info".to_string()), + override_rust_log: Some("info".to_owned()), genesis_id: 1, is_genesis_random: false, max_num_tx_in_block: 10, @@ -416,7 +409,7 @@ mod tests { channel_id: [42; 32].into(), node_url: "http://localhost:8080".parse().unwrap(), auth: Some(BasicAuth { - username: "user".to_string(), + username: "user".to_owned(), password: None, }), }, @@ -461,7 +454,9 @@ mod tests { .produce_new_block_with_mempool_transactions() .unwrap(); - let max_block_size = sequencer_core.sequencer_config().max_block_size.as_u64() as usize; + let max_block_size = + usize::try_from(sequencer_core.sequencer_config().max_block_size.as_u64()) + .expect("`max_block_size` is expected to fit in usize"); let sequencer_core = Arc::new(Mutex::new(sequencer_core)); ( @@ -499,7 +494,7 @@ mod tests { } #[actix_web::test] - async fn test_get_account_balance_for_non_existent_account() { + async fn get_account_balance_for_non_existent_account() { let (json_handler, _, _) = components_for_tests().await; let request = serde_json::json!({ "jsonrpc": "2.0", @@ -521,7 +516,7 @@ mod tests { } #[actix_web::test] - async fn test_get_account_balance_for_invalid_base58() { + async fn get_account_balance_for_invalid_base58() { let (json_handler, _, _) = components_for_tests().await; let request = serde_json::json!({ "jsonrpc": "2.0", @@ -551,7 +546,7 @@ mod tests { } #[actix_web::test] - async fn test_get_account_balance_for_invalid_length() { + async fn get_account_balance_for_invalid_length() { let (json_handler, _, _) = components_for_tests().await; let request = serde_json::json!({ "jsonrpc": "2.0", @@ -581,7 +576,7 @@ mod tests { } #[actix_web::test] - async fn test_get_account_balance_for_existing_account() { + async fn get_account_balance_for_existing_account() { let (json_handler, initial_accounts, _) = components_for_tests().await; let acc1_id = initial_accounts[0].account_id; @@ -606,7 +601,7 @@ mod tests { } #[actix_web::test] - async fn test_get_accounts_nonces_for_non_existent_account() { + async fn get_accounts_nonces_for_non_existent_account() { let (json_handler, _, _) = components_for_tests().await; let request = serde_json::json!({ "jsonrpc": "2.0", @@ -628,7 +623,7 @@ mod tests { } #[actix_web::test] - async fn test_get_accounts_nonces_for_existent_account() { + async fn get_accounts_nonces_for_existent_account() { let (json_handler, initial_accounts, _) = components_for_tests().await; let acc1_id = initial_accounts[0].account_id; @@ -654,7 +649,7 @@ mod tests { } #[actix_web::test] - async fn test_get_account_data_for_non_existent_account() { + async fn get_account_data_for_non_existent_account() { let (json_handler, _, _) = components_for_tests().await; let request = serde_json::json!({ "jsonrpc": "2.0", @@ -681,7 +676,7 @@ mod tests { } #[actix_web::test] - async fn test_get_transaction_by_hash_for_non_existent_hash() { + async fn get_transaction_by_hash_for_non_existent_hash() { let (json_handler, _, _) = components_for_tests().await; let request = serde_json::json!({ "jsonrpc": "2.0", @@ -703,7 +698,7 @@ mod tests { } #[actix_web::test] - async fn test_get_transaction_by_hash_for_invalid_hex() { + async fn get_transaction_by_hash_for_invalid_hex() { let (json_handler, _, _) = components_for_tests().await; let request = serde_json::json!({ "jsonrpc": "2.0", @@ -734,7 +729,7 @@ mod tests { } #[actix_web::test] - async fn test_get_transaction_by_hash_for_invalid_length() { + async fn get_transaction_by_hash_for_invalid_length() { let (json_handler, _, _) = components_for_tests().await; let request = serde_json::json!({ "jsonrpc": "2.0", @@ -765,7 +760,7 @@ mod tests { } #[actix_web::test] - async fn test_get_transaction_by_hash_for_existing_transaction() { + async fn get_transaction_by_hash_for_existing_transaction() { let (json_handler, _, tx) = components_for_tests().await; let tx_hash_hex = hex::encode(tx.hash()); let expected_base64_encoded = general_purpose::STANDARD.encode(borsh::to_vec(&tx).unwrap()); diff --git a/sequencer_rpc/src/types/err_rpc.rs b/sequencer_rpc/src/types/err_rpc.rs index e92a578e..4cb75606 100644 --- a/sequencer_rpc/src/types/err_rpc.rs +++ b/sequencer_rpc/src/types/err_rpc.rs @@ -2,7 +2,16 @@ use common::{ rpc_primitives::errors::{RpcError, RpcParseError}, transaction::TransactionMalformationError, }; -use log::debug; + +macro_rules! standard_rpc_err_kind { + ($type_name:path) => { + impl RpcErrKind for $type_name { + fn into_rpc_err(self) -> RpcError { + self.into() + } + } + }; +} pub struct RpcErr(pub RpcError); @@ -18,15 +27,6 @@ impl From for RpcErr { } } -macro_rules! standard_rpc_err_kind { - ($type_name:path) => { - impl RpcErrKind for $type_name { - fn into_rpc_err(self) -> RpcError { - self.into() - } - } - }; -} standard_rpc_err_kind!(RpcError); standard_rpc_err_kind!(RpcParseError); @@ -47,10 +47,3 @@ impl RpcErrKind for TransactionMalformationError { RpcError::invalid_params(Some(serde_json::to_value(self).unwrap())) } } - -#[allow(clippy::needless_pass_by_value)] -#[must_use] -pub fn from_rpc_err_into_anyhow_err(rpc_err: RpcError) -> anyhow::Error { - debug!("Rpc error cast to anyhow error : err {rpc_err:?}"); - anyhow::anyhow!(format!("{rpc_err:#?}")) -} diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index 3148157c..4e0c32ca 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -40,6 +40,10 @@ impl SequencerHandle { /// Runs the sequencer indefinitely, monitoring its tasks. /// /// If no error occurs, this function will never return. + #[expect( + clippy::integer_division_remainder_used, + reason = "Generated by select! macro, can't be easily rewritten to avoid this lint" + )] pub async fn run_forever(&mut self) -> Result { let Self { addr: _, @@ -292,9 +296,10 @@ pub async fn main_runner() -> Result<()> { let app_config = SequencerConfig::from_path(&home_dir.join("sequencer_config.json"))?; - if let Some(ref rust_log) = app_config.override_rust_log { + if let Some(rust_log) = &app_config.override_rust_log { info!("RUST_LOG env var set to {rust_log:?}"); + // SAFETY: there is no other threads running at this point unsafe { std::env::set_var(RUST_LOG, rust_log); } diff --git a/storage/src/indexer.rs b/storage/src/indexer.rs index d9ff8fd9..ad17147d 100644 --- a/storage/src/indexer.rs +++ b/storage/src/indexer.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, ops::Div, path::Path, sync::Arc}; +use std::{collections::HashMap, path::Path, sync::Arc}; use common::{ block::{Block, BlockId}, @@ -55,18 +55,16 @@ pub const CF_ACC_TO_TX: &str = "cf_acc_to_tx"; pub type DbResult = Result; -fn closest_breakpoint_id(block_id: u64) -> u64 { - block_id - .saturating_sub(1) - .div(u64::from(BREAKPOINT_INTERVAL)) -} - pub struct RocksDBIO { pub db: DBWithThreadMode, } impl RocksDBIO { - pub fn open_or_create(path: &Path, start_data: Option<(Block, V02State)>) -> DbResult { + pub fn open_or_create( + path: &Path, + genesis_block: &Block, + initial_state: &V02State, + ) -> DbResult { let mut cf_opts = Options::default(); cf_opts.set_max_write_buffer_number(16); // ToDo: Add more column families for different data @@ -85,31 +83,29 @@ impl RocksDBIO { &db_opts, path, vec![cfb, cfmeta, cfbreakpoint, cfhti, cftti, cfameta, cfatt], - ); + ) + .map_err(|err| DbError::RocksDbError { + error: err, + additional_info: Some("Failed to open or create DB".to_owned()), + })?; - let dbio = Self { - // There is no point in handling this from runner code - db: db.unwrap(), - }; + let dbio = Self { db }; let is_start_set = dbio.get_meta_is_first_block_set()?; if is_start_set { Ok(dbio) - } else if let Some((block, initial_state)) = start_data { - let block_id = block.header.block_id; + } else { + let block_id = genesis_block.header.block_id; dbio.put_meta_last_block_in_db(block_id)?; - dbio.put_meta_first_block_in_db(block)?; + dbio.put_meta_first_block_in_db(genesis_block)?; dbio.put_meta_is_first_block_set()?; // First breakpoint setup - dbio.put_breakpoint(0, &initial_state)?; + dbio.put_breakpoint(0, initial_state)?; dbio.put_meta_last_breakpoint_id(0)?; Ok(dbio) - } else { - // Here we are trying to start a DB without a block, one should not do it. - unreachable!() } } @@ -173,7 +169,7 @@ impl RocksDBIO { borsh::to_vec(&DB_META_FIRST_BLOCK_IN_DB_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_string()), + Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_owned()), ) })?, ) @@ -183,12 +179,12 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to deserialize first block".to_string()), + Some("Failed to deserialize first block".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "First block not found".to_string(), + "First block not found".to_owned(), )) } } @@ -202,7 +198,7 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LAST_BLOCK_IN_DB_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_string()), + Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_owned()), ) })?, ) @@ -212,12 +208,12 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to deserialize last block".to_string()), + Some("Failed to deserialize last block".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "Last block not found".to_string(), + "Last block not found".to_owned(), )) } } @@ -233,8 +229,7 @@ impl RocksDBIO { DbError::borsh_cast_message( err, Some( - "Failed to serialize DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY" - .to_string(), + "Failed to serialize DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY".to_owned(), ), ) }, @@ -246,7 +241,7 @@ impl RocksDBIO { borsh::from_slice::<[u8; 32]>(&data).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to deserialize last l1 lib header".to_string()), + Some("Failed to deserialize last l1 lib header".to_owned()), ) }) }) @@ -262,7 +257,7 @@ impl RocksDBIO { borsh::to_vec(&DB_META_FIRST_BLOCK_SET_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_string()), + Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_owned()), ) })?, ) @@ -280,7 +275,7 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LAST_BREAKPOINT_ID).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LAST_BREAKPOINT_ID".to_string()), + Some("Failed to serialize DB_META_LAST_BREAKPOINT_ID".to_owned()), ) })?, ) @@ -290,17 +285,17 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to deserialize last breakpoint id".to_string()), + Some("Failed to deserialize last breakpoint id".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "Last breakpoint id not found".to_string(), + "Last breakpoint id not found".to_owned(), )) } } - pub fn put_meta_first_block_in_db(&self, block: Block) -> DbResult<()> { + pub fn put_meta_first_block_in_db(&self, block: &Block) -> DbResult<()> { let cf_meta = self.meta_column(); self.db .put_cf( @@ -308,13 +303,13 @@ impl RocksDBIO { borsh::to_vec(&DB_META_FIRST_BLOCK_IN_DB_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_string()), + Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_owned()), ) })?, borsh::to_vec(&block.header.block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize first block id".to_string()), + Some("Failed to serialize first block id".to_owned()), ) })?, ) @@ -332,13 +327,13 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LAST_BLOCK_IN_DB_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_string()), + Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_owned()), ) })?, borsh::to_vec(&block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize last block id".to_string()), + Some("Failed to serialize last block id".to_owned()), ) })?, ) @@ -359,8 +354,7 @@ impl RocksDBIO { DbError::borsh_cast_message( err, Some( - "Failed to serialize DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY" - .to_string(), + "Failed to serialize DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY".to_owned(), ), ) }, @@ -368,7 +362,7 @@ impl RocksDBIO { borsh::to_vec(&l1_lib_header).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize last l1 block header".to_string()), + Some("Failed to serialize last l1 block header".to_owned()), ) })?, ) @@ -384,13 +378,13 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LAST_BREAKPOINT_ID).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LAST_BREAKPOINT_ID".to_string()), + Some("Failed to serialize DB_META_LAST_BREAKPOINT_ID".to_owned()), ) })?, borsh::to_vec(&br_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize last block id".to_string()), + Some("Failed to serialize last block id".to_owned()), ) })?, ) @@ -406,10 +400,10 @@ impl RocksDBIO { borsh::to_vec(&DB_META_FIRST_BLOCK_SET_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_string()), + Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_owned()), ) })?, - [1u8; 1], + [1_u8; 1], ) .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; Ok(()) @@ -417,7 +411,7 @@ impl RocksDBIO { // Block - pub fn put_block(&self, block: Block, l1_lib_header: [u8; 32]) -> DbResult<()> { + pub fn put_block(&self, block: &Block, l1_lib_header: [u8; 32]) -> DbResult<()> { let cf_block = self.block_column(); let cf_hti = self.hash_to_id_column(); let cf_tti: Arc> = self.tx_hash_to_id_column(); @@ -430,13 +424,13 @@ impl RocksDBIO { borsh::to_vec(&block.header.block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block id".to_string()), + Some("Failed to serialize block id".to_owned()), ) })?, borsh::to_vec(&block).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block data".to_string()), + Some("Failed to serialize block data".to_owned()), ) })?, ) @@ -455,13 +449,13 @@ impl RocksDBIO { borsh::to_vec(&block.header.hash).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block hash".to_string()), + Some("Failed to serialize block hash".to_owned()), ) })?, borsh::to_vec(&block.header.block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block id".to_string()), + Some("Failed to serialize block id".to_owned()), ) })?, ) @@ -469,7 +463,7 @@ impl RocksDBIO { let mut acc_to_tx_map: HashMap<[u8; 32], Vec<[u8; 32]>> = HashMap::new(); - for tx in block.body.transactions { + for tx in &block.body.transactions { let tx_hash = tx.hash(); self.db @@ -478,13 +472,13 @@ impl RocksDBIO { borsh::to_vec(&tx_hash).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize tx hash".to_string()), + Some("Failed to serialize tx hash".to_owned()), ) })?, borsh::to_vec(&block.header.block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block id".to_string()), + Some("Failed to serialize block id".to_owned()), ) })?, ) @@ -504,6 +498,10 @@ impl RocksDBIO { } } + #[expect( + clippy::iter_over_hash_type, + reason = "RocksDB will keep ordering persistent" + )] for (acc_id, tx_hashes) in acc_to_tx_map { self.put_account_transactions(acc_id, &tx_hashes)?; } @@ -528,7 +526,7 @@ impl RocksDBIO { borsh::to_vec(&block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block id".to_string()), + Some("Failed to serialize block id".to_owned()), ) })?, ) @@ -538,12 +536,12 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|serr| { DbError::borsh_cast_message( serr, - Some("Failed to deserialize block data".to_string()), + Some("Failed to deserialize block data".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "Block on this id not found".to_string(), + "Block on this id not found".to_owned(), )) } } @@ -575,7 +573,7 @@ impl RocksDBIO { borsh::to_vec(&block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block id".to_string()), + Some("Failed to serialize block id".to_owned()), ) })?, ) @@ -585,7 +583,7 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|serr| { DbError::borsh_cast_message( serr, - Some("Failed to deserialize block data".to_string()), + Some("Failed to deserialize block data".to_owned()), ) })?) } else { @@ -610,13 +608,13 @@ impl RocksDBIO { borsh::to_vec(&br_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize breakpoint id".to_string()), + Some("Failed to serialize breakpoint id".to_owned()), ) })?, borsh::to_vec(&breakpoint).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize breakpoint data".to_string()), + Some("Failed to serialize breakpoint data".to_owned()), ) })?, ) @@ -632,7 +630,7 @@ impl RocksDBIO { borsh::to_vec(&br_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize breakpoint id".to_string()), + Some("Failed to serialize breakpoint id".to_owned()), ) })?, ) @@ -642,12 +640,12 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|serr| { DbError::borsh_cast_message( serr, - Some("Failed to deserialize breakpoint data".to_string()), + Some("Failed to deserialize breakpoint data".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "Breakpoint on this id not found".to_string(), + "Breakpoint on this id not found".to_owned(), )) } } @@ -662,7 +660,9 @@ impl RocksDBIO { // ToDo: update it to handle any genesis id // right now works correctly only if genesis_id < BREAKPOINT_INTERVAL let start = if br_id != 0 { - u64::from(BREAKPOINT_INTERVAL) * br_id + u64::from(BREAKPOINT_INTERVAL) + .checked_mul(br_id) + .expect("Reached maximum breakpoint id") } else { self.get_meta_first_block_in_db()? }; @@ -690,7 +690,7 @@ impl RocksDBIO { Ok(breakpoint) } else { Err(DbError::db_interaction_error( - "Block on this id not found".to_string(), + "Block on this id not found".to_owned(), )) } } @@ -701,8 +701,13 @@ impl RocksDBIO { pub fn put_next_breakpoint(&self) -> DbResult<()> { let last_block = self.get_meta_last_block_in_db()?; - let next_breakpoint_id = self.get_meta_last_breakpoint_id()? + 1; - let block_to_break_id = next_breakpoint_id * u64::from(BREAKPOINT_INTERVAL); + let next_breakpoint_id = self + .get_meta_last_breakpoint_id()? + .checked_add(1) + .expect("Reached maximum breakpoint id"); + let block_to_break_id = next_breakpoint_id + .checked_mul(u64::from(BREAKPOINT_INTERVAL)) + .expect("Reached maximum breakpoint id"); if block_to_break_id <= last_block { let next_breakpoint = self.calculate_state_for_id(block_to_break_id)?; @@ -711,7 +716,7 @@ impl RocksDBIO { self.put_meta_last_breakpoint_id(next_breakpoint_id) } else { Err(DbError::db_interaction_error( - "Breakpoint not yet achieved".to_string(), + "Breakpoint not yet achieved".to_owned(), )) } } @@ -727,7 +732,7 @@ impl RocksDBIO { borsh::to_vec(&hash).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block hash".to_string()), + Some("Failed to serialize block hash".to_owned()), ) })?, ) @@ -735,14 +740,11 @@ impl RocksDBIO { if let Some(data) = res { Ok(borsh::from_slice::(&data).map_err(|serr| { - DbError::borsh_cast_message( - serr, - Some("Failed to deserialize block id".to_string()), - ) + DbError::borsh_cast_message(serr, Some("Failed to deserialize block id".to_owned())) })?) } else { Err(DbError::db_interaction_error( - "Block on this hash not found".to_string(), + "Block on this hash not found".to_owned(), )) } } @@ -756,7 +758,7 @@ impl RocksDBIO { borsh::to_vec(&tx_hash).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize transaction hash".to_string()), + Some("Failed to serialize transaction hash".to_owned()), ) })?, ) @@ -764,14 +766,11 @@ impl RocksDBIO { if let Some(data) = res { Ok(borsh::from_slice::(&data).map_err(|serr| { - DbError::borsh_cast_message( - serr, - Some("Failed to deserialize block id".to_string()), - ) + DbError::borsh_cast_message(serr, Some("Failed to deserialize block id".to_owned())) })?) } else { Err(DbError::db_interaction_error( - "Block for this tx hash not found".to_string(), + "Block for this tx hash not found".to_owned(), )) } } @@ -789,12 +788,12 @@ impl RocksDBIO { write_batch.put_cf( &cf_ameta, borsh::to_vec(&acc_id).map_err(|err| { - DbError::borsh_cast_message(err, Some("Failed to serialize account id".to_string())) + DbError::borsh_cast_message(err, Some("Failed to serialize account id".to_owned())) })?, borsh::to_vec(&num_tx).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize acc metadata".to_string()), + Some("Failed to serialize acc metadata".to_owned()), ) })?, ); @@ -805,12 +804,12 @@ impl RocksDBIO { fn get_acc_meta_num_tx(&self, acc_id: [u8; 32]) -> DbResult> { let cf_ameta = self.account_meta_column(); let res = self.db.get_cf(&cf_ameta, acc_id).map_err(|rerr| { - DbError::rocksdb_cast_message(rerr, Some("Failed to read from acc meta cf".to_string())) + DbError::rocksdb_cast_message(rerr, Some("Failed to read from acc meta cf".to_owned())) })?; res.map(|data| { borsh::from_slice::(&data).map_err(|serr| { - DbError::borsh_cast_message(serr, Some("Failed to deserialize num tx".to_string())) + DbError::borsh_cast_message(serr, Some("Failed to deserialize num tx".to_owned())) }) }) .transpose() @@ -828,16 +827,18 @@ impl RocksDBIO { let mut write_batch = WriteBatch::new(); for (tx_id, tx_hash) in tx_hashes.iter().enumerate() { - let put_id = acc_num_tx + tx_id as u64; + let put_id = acc_num_tx + .checked_add( + u64::try_from(tx_id) + .expect("Transaction number for account expected to fit in u64"), + ) + .expect("Reached maximum number of transactions for account"); let mut prefix = borsh::to_vec(&acc_id).map_err(|berr| { - DbError::borsh_cast_message( - berr, - Some("Failed to serialize account id".to_string()), - ) + DbError::borsh_cast_message(berr, Some("Failed to serialize account id".to_owned())) })?; let suffix = borsh::to_vec(&put_id).map_err(|berr| { - DbError::borsh_cast_message(berr, Some("Failed to serialize tx id".to_string())) + DbError::borsh_cast_message(berr, Some("Failed to serialize tx id".to_owned())) })?; prefix.extend_from_slice(&suffix); @@ -848,7 +849,7 @@ impl RocksDBIO { borsh::to_vec(tx_hash).map_err(|berr| { DbError::borsh_cast_message( berr, - Some("Failed to serialize tx hash".to_string()), + Some("Failed to serialize tx hash".to_owned()), ) })?, ); @@ -856,12 +857,17 @@ impl RocksDBIO { self.update_acc_meta_batch( acc_id, - acc_num_tx + (tx_hashes.len() as u64), + acc_num_tx + .checked_add( + u64::try_from(tx_hashes.len()) + .expect("Number of transactions expected to fit in u64"), + ) + .expect("Reached maximum number of transactions for account"), &mut write_batch, )?; self.db.write(write_batch).map_err(|rerr| { - DbError::rocksdb_cast_message(rerr, Some("Failed to write batch".to_string())) + DbError::rocksdb_cast_message(rerr, Some("Failed to write batch".to_owned())) }) } @@ -876,15 +882,12 @@ impl RocksDBIO { // ToDo: Multi get this - for tx_id in offset..(offset + limit) { + for tx_id in offset..(offset.saturating_add(limit)) { let mut prefix = borsh::to_vec(&acc_id).map_err(|berr| { - DbError::borsh_cast_message( - berr, - Some("Failed to serialize account id".to_string()), - ) + DbError::borsh_cast_message(berr, Some("Failed to serialize account id".to_owned())) })?; let suffix = borsh::to_vec(&tx_id).map_err(|berr| { - DbError::borsh_cast_message(berr, Some("Failed to serialize tx id".to_string())) + DbError::borsh_cast_message(berr, Some("Failed to serialize tx id".to_owned())) })?; prefix.extend_from_slice(&suffix); @@ -898,7 +901,7 @@ impl RocksDBIO { Ok(borsh::from_slice::<[u8; 32]>(&data).map_err(|serr| { DbError::borsh_cast_message( serr, - Some("Failed to deserialize tx_hash".to_string()), + Some("Failed to deserialize tx_hash".to_owned()), ) })?) } else { @@ -941,8 +944,17 @@ impl RocksDBIO { } } +fn closest_breakpoint_id(block_id: u64) -> u64 { + block_id + .saturating_sub(1) + .checked_div(u64::from(BREAKPOINT_INTERVAL)) + .expect("Breakpoint interval is not zero") +} + #[cfg(test)] mod tests { + #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] + use nssa::AccountId; use tempfile::tempdir; @@ -999,12 +1011,12 @@ mod tests { } #[test] - fn test_start_db() { + fn start_db() { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); - let dbio = RocksDBIO::open_or_create(temdir_path, Some((genesis_block(), initial_state()))) - .unwrap(); + let dbio = + RocksDBIO::open_or_create(temdir_path, &genesis_block(), &initial_state()).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let first_id = dbio.get_meta_first_block_in_db().unwrap(); @@ -1030,18 +1042,18 @@ mod tests { } #[test] - fn test_one_block_insertion() { + fn one_block_insertion() { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); - let dbio = RocksDBIO::open_or_create(temdir_path, Some((genesis_block(), initial_state()))) - .unwrap(); + let dbio = + RocksDBIO::open_or_create(temdir_path, &genesis_block(), &initial_state()).unwrap(); let prev_hash = genesis_block().header.hash; let transfer_tx = transfer(1, 0, true); let block = common::test_utils::produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]); - dbio.put_block(block, [1; 32]).unwrap(); + dbio.put_block(&block, [1; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let first_id = dbio.get_meta_first_block_in_db().unwrap(); @@ -1069,12 +1081,12 @@ mod tests { } #[test] - fn test_new_breakpoint() { + fn new_breakpoint() { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); - let dbio = RocksDBIO::open_or_create(temdir_path, Some((genesis_block(), initial_state()))) - .unwrap(); + let dbio = + RocksDBIO::open_or_create(temdir_path, &genesis_block(), &initial_state()).unwrap(); for i in 1..BREAKPOINT_INTERVAL { let last_id = dbio.get_meta_last_block_in_db().unwrap(); @@ -1087,7 +1099,7 @@ mod tests { Some(prev_hash), vec![transfer_tx], ); - dbio.put_block(block, [i; 32]).unwrap(); + dbio.put_block(&block, [i; 32]).unwrap(); } let last_id = dbio.get_meta_last_block_in_db().unwrap(); @@ -1125,12 +1137,12 @@ mod tests { } #[test] - fn test_simple_maps() { + fn simple_maps() { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); - let dbio = RocksDBIO::open_or_create(temdir_path, Some((genesis_block(), initial_state()))) - .unwrap(); + let dbio = + RocksDBIO::open_or_create(temdir_path, &genesis_block(), &initial_state()).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1141,7 +1153,7 @@ mod tests { let control_hash1 = block.header.hash; - dbio.put_block(block, [1; 32]).unwrap(); + dbio.put_block(&block, [1; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1152,7 +1164,7 @@ mod tests { let control_hash2 = block.header.hash; - dbio.put_block(block, [2; 32]).unwrap(); + dbio.put_block(&block, [2; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1163,7 +1175,7 @@ mod tests { let control_tx_hash1 = transfer_tx.hash(); let block = common::test_utils::produce_dummy_block(4, Some(prev_hash), vec![transfer_tx]); - dbio.put_block(block, [3; 32]).unwrap(); + dbio.put_block(&block, [3; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1174,7 +1186,7 @@ mod tests { let control_tx_hash2 = transfer_tx.hash(); let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]); - dbio.put_block(block, [4; 32]).unwrap(); + dbio.put_block(&block, [4; 32]).unwrap(); let control_block_id1 = dbio.get_block_id_by_hash(control_hash1.0).unwrap(); let control_block_id2 = dbio.get_block_id_by_hash(control_hash2.0).unwrap(); @@ -1188,14 +1200,14 @@ mod tests { } #[test] - fn test_block_batch() { + fn block_batch() { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); let mut block_res = vec![]; - let dbio = RocksDBIO::open_or_create(temdir_path, Some((genesis_block(), initial_state()))) - .unwrap(); + let dbio = + RocksDBIO::open_or_create(temdir_path, &genesis_block(), &initial_state()).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1205,7 +1217,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]); block_res.push(block.clone()); - dbio.put_block(block, [1; 32]).unwrap(); + dbio.put_block(&block, [1; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1215,7 +1227,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(3, Some(prev_hash), vec![transfer_tx]); block_res.push(block.clone()); - dbio.put_block(block, [2; 32]).unwrap(); + dbio.put_block(&block, [2; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1225,7 +1237,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(4, Some(prev_hash), vec![transfer_tx]); block_res.push(block.clone()); - dbio.put_block(block, [3; 32]).unwrap(); + dbio.put_block(&block, [3; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1235,7 +1247,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]); block_res.push(block.clone()); - dbio.put_block(block, [4; 32]).unwrap(); + dbio.put_block(&block, [4; 32]).unwrap(); let block_hashes_mem: Vec<[u8; 32]> = block_res.into_iter().map(|bl| bl.header.hash.0).collect(); @@ -1266,14 +1278,14 @@ mod tests { } #[test] - fn test_account_map() { + fn account_map() { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); let mut tx_hash_res = vec![]; - let dbio = RocksDBIO::open_or_create(temdir_path, Some((genesis_block(), initial_state()))) - .unwrap(); + let dbio = + RocksDBIO::open_or_create(temdir_path, &genesis_block(), &initial_state()).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1285,7 +1297,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(2, Some(prev_hash), vec![transfer_tx]); - dbio.put_block(block, [1; 32]).unwrap(); + dbio.put_block(&block, [1; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1297,7 +1309,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(3, Some(prev_hash), vec![transfer_tx]); - dbio.put_block(block, [2; 32]).unwrap(); + dbio.put_block(&block, [2; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1309,7 +1321,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(4, Some(prev_hash), vec![transfer_tx]); - dbio.put_block(block, [3; 32]).unwrap(); + dbio.put_block(&block, [3; 32]).unwrap(); let last_id = dbio.get_meta_last_block_in_db().unwrap(); let last_block = dbio.get_block(last_id).unwrap(); @@ -1321,7 +1333,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(5, Some(prev_hash), vec![transfer_tx]); - dbio.put_block(block, [4; 32]).unwrap(); + dbio.put_block(&block, [4; 32]).unwrap(); let acc1_tx = dbio.get_acc_transactions(*acc1().value(), 0, 4).unwrap(); let acc1_tx_hashes: Vec<[u8; 32]> = acc1_tx.into_iter().map(|tx| tx.hash().0).collect(); diff --git a/storage/src/sequencer.rs b/storage/src/sequencer.rs index 2de123dc..04ddc8e5 100644 --- a/storage/src/sequencer.rs +++ b/storage/src/sequencer.rs @@ -50,7 +50,8 @@ pub struct RocksDBIO { impl RocksDBIO { pub fn open_or_create( path: &Path, - start_block: Option<(&Block, MantleMsgId)>, + genesis_block: &Block, + genesis_msg_id: MantleMsgId, ) -> DbResult { let mut cf_opts = Options::default(); cf_opts.set_max_write_buffer_number(16); @@ -66,33 +67,31 @@ impl RocksDBIO { &db_opts, path, vec![cfb, cfmeta, cfstate], - ); + ) + .map_err(|err| DbError::RocksDbError { + error: err, + additional_info: Some("Failed to open or create DB".to_owned()), + })?; - let dbio = Self { - // There is no point in handling this from runner code - db: db.unwrap(), - }; + let dbio = Self { db }; let is_start_set = dbio.get_meta_is_first_block_set()?; if is_start_set { Ok(dbio) - } else if let Some((block, msg_id)) = start_block { - let block_id = block.header.block_id; - dbio.put_meta_first_block_in_db(block, msg_id)?; + } else { + let block_id = genesis_block.header.block_id; + dbio.put_meta_first_block_in_db(genesis_block, genesis_msg_id)?; dbio.put_meta_is_first_block_set()?; dbio.put_meta_last_block_in_db(block_id)?; dbio.put_meta_last_finalized_block_id(None)?; dbio.put_meta_latest_block_meta(&BlockMeta { - id: block.header.block_id, - hash: block.header.hash, - msg_id, + id: genesis_block.header.block_id, + hash: genesis_block.header.hash, + msg_id: genesis_msg_id, })?; Ok(dbio) - } else { - // Here we are trying to start a DB without a block, one should not do it. - unreachable!() } } @@ -132,7 +131,7 @@ impl RocksDBIO { borsh::to_vec(&DB_META_FIRST_BLOCK_IN_DB_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_string()), + Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_owned()), ) })?, ) @@ -142,12 +141,12 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to deserialize first block".to_string()), + Some("Failed to deserialize first block".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "First block not found".to_string(), + "First block not found".to_owned(), )) } } @@ -161,7 +160,7 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LAST_BLOCK_IN_DB_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_string()), + Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_owned()), ) })?, ) @@ -171,12 +170,12 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to deserialize last block".to_string()), + Some("Failed to deserialize last block".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "Last block not found".to_string(), + "Last block not found".to_owned(), )) } } @@ -190,7 +189,7 @@ impl RocksDBIO { borsh::to_vec(&DB_META_FIRST_BLOCK_SET_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_string()), + Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_owned()), ) })?, ) @@ -206,11 +205,11 @@ impl RocksDBIO { borsh::to_vec(&DB_NSSA_STATE_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_NSSA_STATE_KEY".to_string()), + Some("Failed to serialize DB_NSSA_STATE_KEY".to_owned()), ) })?, borsh::to_vec(state).map_err(|err| { - DbError::borsh_cast_message(err, Some("Failed to serialize NSSA state".to_string())) + DbError::borsh_cast_message(err, Some("Failed to serialize NSSA state".to_owned())) })?, ); @@ -225,13 +224,13 @@ impl RocksDBIO { borsh::to_vec(&DB_META_FIRST_BLOCK_IN_DB_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_string()), + Some("Failed to serialize DB_META_FIRST_BLOCK_IN_DB_KEY".to_owned()), ) })?, borsh::to_vec(&block.header.block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize first block id".to_string()), + Some("Failed to serialize first block id".to_owned()), ) })?, ) @@ -242,7 +241,7 @@ impl RocksDBIO { self.db.write(batch).map_err(|rerr| { DbError::rocksdb_cast_message( rerr, - Some("Failed to write first block in db".to_string()), + Some("Failed to write first block in db".to_owned()), ) })?; @@ -257,13 +256,13 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LAST_BLOCK_IN_DB_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_string()), + Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_owned()), ) })?, borsh::to_vec(&block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize last block id".to_string()), + Some("Failed to serialize last block id".to_owned()), ) })?, ) @@ -282,13 +281,13 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LAST_BLOCK_IN_DB_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_string()), + Some("Failed to serialize DB_META_LAST_BLOCK_IN_DB_KEY".to_owned()), ) })?, borsh::to_vec(&block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize last block id".to_string()), + Some("Failed to serialize last block id".to_owned()), ) })?, ); @@ -303,13 +302,13 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LAST_FINALIZED_BLOCK_ID).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LAST_FINALIZED_BLOCK_ID".to_string()), + Some("Failed to serialize DB_META_LAST_FINALIZED_BLOCK_ID".to_owned()), ) })?, borsh::to_vec(&block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize last block id".to_string()), + Some("Failed to serialize last block id".to_owned()), ) })?, ) @@ -325,10 +324,10 @@ impl RocksDBIO { borsh::to_vec(&DB_META_FIRST_BLOCK_SET_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_string()), + Some("Failed to serialize DB_META_FIRST_BLOCK_SET_KEY".to_owned()), ) })?, - [1u8; 1], + [1_u8; 1], ) .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; Ok(()) @@ -342,13 +341,13 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LATEST_BLOCK_META_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LATEST_BLOCK_META_KEY".to_string()), + Some("Failed to serialize DB_META_LATEST_BLOCK_META_KEY".to_owned()), ) })?, borsh::to_vec(&block_meta).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize latest block meta".to_string()), + Some("Failed to serialize latest block meta".to_owned()), ) })?, ) @@ -367,13 +366,13 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LATEST_BLOCK_META_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LATEST_BLOCK_META_KEY".to_string()), + Some("Failed to serialize DB_META_LATEST_BLOCK_META_KEY".to_owned()), ) })?, borsh::to_vec(&block_meta).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize latest block meta".to_string()), + Some("Failed to serialize latest block meta".to_owned()), ) })?, ); @@ -389,7 +388,7 @@ impl RocksDBIO { borsh::to_vec(&DB_META_LATEST_BLOCK_META_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize DB_META_LATEST_BLOCK_META_KEY".to_string()), + Some("Failed to serialize DB_META_LATEST_BLOCK_META_KEY".to_owned()), ) })?, ) @@ -399,12 +398,12 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to deserialize latest block meta".to_string()), + Some("Failed to deserialize latest block meta".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "Latest block meta not found".to_string(), + "Latest block meta not found".to_owned(), )) } } @@ -437,10 +436,10 @@ impl RocksDBIO { batch.put_cf( &cf_block, borsh::to_vec(&block.header.block_id).map_err(|err| { - DbError::borsh_cast_message(err, Some("Failed to serialize block id".to_string())) + DbError::borsh_cast_message(err, Some("Failed to serialize block id".to_owned())) })?, borsh::to_vec(block).map_err(|err| { - DbError::borsh_cast_message(err, Some("Failed to serialize block data".to_string())) + DbError::borsh_cast_message(err, Some("Failed to serialize block data".to_owned())) })?, ); Ok(()) @@ -455,7 +454,7 @@ impl RocksDBIO { borsh::to_vec(&block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block id".to_string()), + Some("Failed to serialize block id".to_owned()), ) })?, ) @@ -465,12 +464,12 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|serr| { DbError::borsh_cast_message( serr, - Some("Failed to deserialize block data".to_string()), + Some("Failed to deserialize block data".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "Block on this id not found".to_string(), + "Block on this id not found".to_owned(), )) } } @@ -484,7 +483,7 @@ impl RocksDBIO { borsh::to_vec(&DB_NSSA_STATE_KEY).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block id".to_string()), + Some("Failed to serialize block id".to_owned()), ) })?, ) @@ -494,12 +493,12 @@ impl RocksDBIO { Ok(borsh::from_slice::(&data).map_err(|serr| { DbError::borsh_cast_message( serr, - Some("Failed to deserialize block data".to_string()), + Some("Failed to deserialize block data".to_owned()), ) })?) } else { Err(DbError::db_interaction_error( - "Block on this id not found".to_string(), + "Block on this id not found".to_owned(), )) } } @@ -507,7 +506,7 @@ impl RocksDBIO { pub fn delete_block(&self, block_id: u64) -> DbResult<()> { let cf_block = self.block_column(); let key = borsh::to_vec(&block_id).map_err(|err| { - DbError::borsh_cast_message(err, Some("Failed to serialize block id".to_string())) + DbError::borsh_cast_message(err, Some("Failed to serialize block id".to_owned())) })?; if self @@ -517,7 +516,7 @@ impl RocksDBIO { .is_none() { return Err(DbError::db_interaction_error( - "Block on this id not found".to_string(), + "Block on this id not found".to_owned(), )); } @@ -539,13 +538,13 @@ impl RocksDBIO { borsh::to_vec(&block_id).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block id".to_string()), + Some("Failed to serialize block id".to_owned()), ) })?, borsh::to_vec(&block).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to serialize block data".to_string()), + Some("Failed to serialize block data".to_owned()), ) })?, ) @@ -567,14 +566,14 @@ impl RocksDBIO { let (_key, value) = res.map_err(|rerr| { DbError::rocksdb_cast_message( rerr, - Some("Failed to get key value pair".to_string()), + Some("Failed to get key value pair".to_owned()), ) })?; borsh::from_slice::(&value).map_err(|err| { DbError::borsh_cast_message( err, - Some("Failed to deserialize block data".to_string()), + Some("Failed to deserialize block data".to_owned()), ) }) }) diff --git a/test_program_methods/guest/src/bin/burner.rs b/test_program_methods/guest/src/bin/burner.rs index e9abdc16..a2256aa3 100644 --- a/test_program_methods/guest/src/bin/burner.rs +++ b/test_program_methods/guest/src/bin/burner.rs @@ -17,7 +17,7 @@ fn main() { let account_pre = &pre.account; let mut account_post = account_pre.clone(); - account_post.balance -= balance_to_burn; + account_post.balance = account_post.balance.saturating_sub(balance_to_burn); write_nssa_outputs( instruction_words, diff --git a/test_program_methods/guest/src/bin/chain_caller.rs b/test_program_methods/guest/src/bin/chain_caller.rs index e430a0fa..bc17391c 100644 --- a/test_program_methods/guest/src/bin/chain_caller.rs +++ b/test_program_methods/guest/src/bin/chain_caller.rs @@ -42,8 +42,16 @@ fn main() { }; chained_calls.push(new_chained_call); - running_sender_pre.account.balance -= balance; - running_recipient_pre.account.balance += balance; + running_sender_pre.account.balance = + match running_sender_pre.account.balance.checked_sub(balance) { + Some(new_balance) => new_balance, + None => return, + }; + running_recipient_pre.account.balance = + match running_recipient_pre.account.balance.checked_add(balance) { + Some(new_balance) => new_balance, + None => return, + }; } write_nssa_outputs_with_chained_call( diff --git a/test_program_methods/guest/src/bin/minter.rs b/test_program_methods/guest/src/bin/minter.rs index 7a97a3cb..a602df56 100644 --- a/test_program_methods/guest/src/bin/minter.rs +++ b/test_program_methods/guest/src/bin/minter.rs @@ -11,7 +11,10 @@ fn main() { let account_pre = &pre.account; let mut account_post = account_pre.clone(); - account_post.balance += 1; + account_post.balance = account_post + .balance + .checked_add(1) + .expect("Balance overflow"); write_nssa_outputs( instruction_words, diff --git a/test_program_methods/guest/src/bin/modified_transfer.rs b/test_program_methods/guest/src/bin/modified_transfer.rs index 31e8db5e..3aee3816 100644 --- a/test_program_methods/guest/src/bin/modified_transfer.rs +++ b/test_program_methods/guest/src/bin/modified_transfer.rs @@ -1,3 +1,8 @@ +#![expect( + clippy::arithmetic_side_effects, + reason = "This program is intentionally malicious and is expected to have side effects." +)] + use nssa_core::{ account::{Account, AccountWithMetadata}, program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}, diff --git a/test_program_methods/guest/src/bin/nonce_changer.rs b/test_program_methods/guest/src/bin/nonce_changer.rs index c5653136..d7bea154 100644 --- a/test_program_methods/guest/src/bin/nonce_changer.rs +++ b/test_program_methods/guest/src/bin/nonce_changer.rs @@ -11,7 +11,7 @@ fn main() { let account_pre = &pre.account; let mut account_post = account_pre.clone(); - account_post.nonce += 1; + account_post.nonce = account_post.nonce.overflowing_add(1).0; write_nssa_outputs( instruction_words, diff --git a/test_program_methods/guest/src/bin/simple_balance_transfer.rs b/test_program_methods/guest/src/bin/simple_balance_transfer.rs index b6522a8a..55bbfcef 100644 --- a/test_program_methods/guest/src/bin/simple_balance_transfer.rs +++ b/test_program_methods/guest/src/bin/simple_balance_transfer.rs @@ -17,8 +17,14 @@ fn main() { let mut sender_post = sender_pre.account.clone(); let mut receiver_post = receiver_pre.account.clone(); - sender_post.balance -= balance; - receiver_post.balance += balance; + sender_post.balance = sender_post + .balance + .checked_sub(balance) + .expect("Not enough balance to transfer"); + receiver_post.balance = receiver_post + .balance + .checked_add(balance) + .expect("Overflow when adding balance"); write_nssa_outputs( instruction_words, diff --git a/wallet-ffi/src/error.rs b/wallet-ffi/src/error.rs index 81c52daa..b48eb31f 100644 --- a/wallet-ffi/src/error.rs +++ b/wallet-ffi/src/error.rs @@ -54,6 +54,10 @@ impl WalletFfiError { } /// Log an error message to stderr. +#[expect( + clippy::print_stderr, + reason = "In FFI context it's better to print errors than to return strings" +)] pub fn print_error(msg: impl Into) { eprintln!("[wallet-ffi] {}", msg.into()); } diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index ab6982c0..8030bf5a 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -130,6 +130,10 @@ pub unsafe extern "C" fn wallet_ffi_get_private_account_keys( let vpk_len = vpk_bytes.len(); let vpk_vec = vpk_bytes.to_vec(); let vpk_boxed = vpk_vec.into_boxed_slice(); + #[expect( + clippy::as_conversions, + reason = "We need to convert the boxed slice into a raw pointer for FFI" + )] let vpk_ptr = Box::into_raw(vpk_boxed) as *const u8; unsafe { diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index 9069fece..190c52ab 100644 --- a/wallet-ffi/src/lib.rs +++ b/wallet-ffi/src/lib.rs @@ -20,6 +20,22 @@ //! - Use the corresponding `wallet_ffi_free_*` function to free memory //! - Never free memory returned by FFI using standard C `free()` +#![expect( + clippy::undocumented_unsafe_blocks, + clippy::multiple_unsafe_ops_per_block, + reason = "TODO: fix later" +)] + +use std::sync::OnceLock; + +use common::error::ExecutionFailureKind; +// Re-export public types for cbindgen +pub use error::WalletFfiError as FfiError; +use tokio::runtime::Handle; +pub use types::*; + +use crate::error::print_error; + pub mod account; pub mod error; pub mod keys; @@ -29,15 +45,6 @@ pub mod transfer; pub mod types; pub mod wallet; -use std::sync::OnceLock; - -// Re-export public types for cbindgen -pub use error::WalletFfiError as FfiError; -use tokio::runtime::Handle; -pub use types::*; - -use crate::error::print_error; - static TOKIO_RUNTIME: OnceLock = OnceLock::new(); /// Get a reference to the global runtime. @@ -62,3 +69,22 @@ pub(crate) fn block_on(future: F) -> F::Output { let runtime = get_runtime(); runtime.block_on(future) } + +#[expect( + clippy::needless_pass_by_value, + reason = "Error is consumed to create FFI error response" +)] +#[expect( + clippy::wildcard_enum_match_arm, + reason = "We want to catch all errors for future proofing" +)] +pub(crate) fn map_execution_error(e: ExecutionFailureKind) -> FfiError { + match e { + ExecutionFailureKind::InsufficientFundsError => FfiError::InsufficientFunds, + ExecutionFailureKind::KeyNotFoundError => FfiError::KeyNotFound, + ExecutionFailureKind::SequencerError(_) | ExecutionFailureKind::SequencerClientError(_) => { + FfiError::NetworkError + } + _ => FfiError::InternalError, + } +} diff --git a/wallet-ffi/src/pinata.rs b/wallet-ffi/src/pinata.rs index ad15a4d6..7c8e21d0 100644 --- a/wallet-ffi/src/pinata.rs +++ b/wallet-ffi/src/pinata.rs @@ -2,7 +2,6 @@ use std::{ffi::CString, ptr, slice}; -use common::error::ExecutionFailureKind; use nssa::AccountId; use nssa_core::MembershipProof; use wallet::program_facades::pinata::Pinata; @@ -10,6 +9,7 @@ use wallet::program_facades::pinata::Pinata; use crate::{ block_on, error::{print_error, WalletFfiError}, + map_execution_error, types::{FfiBytes32, FfiTransferResult, WalletHandle}, wallet::get_wallet, }; @@ -92,7 +92,7 @@ pub unsafe extern "C" fn wallet_ffi_claim_pinata( (*out_result).tx_hash = ptr::null_mut(); (*out_result).success = false; } - map_execution_error(&e) + map_execution_error(e) } } } @@ -201,7 +201,7 @@ pub unsafe extern "C" fn wallet_ffi_claim_pinata_private_owned_already_initializ (*out_result).tx_hash = ptr::null_mut(); (*out_result).success = false; } - map_execution_error(&e) + map_execution_error(e) } } } @@ -287,18 +287,7 @@ pub unsafe extern "C" fn wallet_ffi_claim_pinata_private_owned_not_initialized( (*out_result).tx_hash = ptr::null_mut(); (*out_result).success = false; } - map_execution_error(&e) + map_execution_error(e) } } } - -fn map_execution_error(e: &ExecutionFailureKind) -> WalletFfiError { - match e { - ExecutionFailureKind::InsufficientFundsError => WalletFfiError::InsufficientFunds, - ExecutionFailureKind::KeyNotFoundError => WalletFfiError::KeyNotFound, - ExecutionFailureKind::SequencerError | ExecutionFailureKind::SequencerClientError(_) => { - WalletFfiError::NetworkError - } - _ => WalletFfiError::InternalError, - } -} diff --git a/wallet-ffi/src/transfer.rs b/wallet-ffi/src/transfer.rs index 5bee3420..da1892dd 100644 --- a/wallet-ffi/src/transfer.rs +++ b/wallet-ffi/src/transfer.rs @@ -2,13 +2,13 @@ use std::{ffi::CString, ptr}; -use common::error::ExecutionFailureKind; use nssa::AccountId; use wallet::program_facades::native_token_transfer::NativeTokenTransfer; use crate::{ block_on, error::{print_error, WalletFfiError}, + map_execution_error, types::{FfiBytes32, FfiTransferResult, WalletHandle}, wallet::get_wallet, FfiPrivateAccountKeys, @@ -685,18 +685,3 @@ pub unsafe extern "C" fn wallet_ffi_free_transfer_result(result: *mut FfiTransfe } } } - -#[expect( - clippy::needless_pass_by_value, - reason = "Error is consumed to create FFI error response" -)] -fn map_execution_error(e: ExecutionFailureKind) -> WalletFfiError { - match e { - ExecutionFailureKind::InsufficientFundsError => WalletFfiError::InsufficientFunds, - ExecutionFailureKind::KeyNotFoundError => WalletFfiError::KeyNotFound, - ExecutionFailureKind::SequencerError | ExecutionFailureKind::SequencerClientError(_) => { - WalletFfiError::NetworkError - } - _ => WalletFfiError::InternalError, - } -} diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index 2922e5d1..c689fe8e 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -199,6 +199,10 @@ impl From for nssa::AccountId { } impl From for FfiAccount { + #[expect( + clippy::as_conversions, + reason = "We need to convert to byte arrays for FFI" + )] fn from(value: nssa::Account) -> Self { // Convert account data to FFI type let data_vec: Vec = value.data.into(); @@ -230,7 +234,8 @@ impl TryFrom<&FfiAccount> for nssa::Account { let data = if value.data_len > 0 { unsafe { let slice = slice::from_raw_parts(value.data, value.data_len); - Data::try_from(slice.to_vec()).map_err(|_| WalletFfiError::InvalidTypeConversion)? + Data::try_from(slice.to_vec()) + .map_err(|_err| WalletFfiError::InvalidTypeConversion)? } } else { Data::default() @@ -257,7 +262,7 @@ impl TryFrom<&FfiPublicAccountKey> for nssa::PublicKey { fn try_from(value: &FfiPublicAccountKey) -> Result { let public_key = nssa::PublicKey::try_new(value.public_key.data) - .map_err(|_| WalletFfiError::InvalidTypeConversion)?; + .map_err(|_err| WalletFfiError::InvalidTypeConversion)?; Ok(public_key) } } diff --git a/wallet-ffi/src/wallet.rs b/wallet-ffi/src/wallet.rs index 26a31c20..9117d0ee 100644 --- a/wallet-ffi/src/wallet.rs +++ b/wallet-ffi/src/wallet.rs @@ -32,7 +32,7 @@ pub(crate) fn get_wallet( } /// Helper to get a mutable reference to the wallet wrapper. -#[allow(dead_code)] +#[expect(dead_code, reason = "Maybe used later")] pub(crate) fn get_wallet_mut( handle: *mut WalletHandle, ) -> Result<&'static mut WalletWrapper, WalletFfiError> { @@ -69,7 +69,7 @@ fn c_str_to_string(ptr: *const c_char, name: &str) -> Result Ok(s.to_string()), + Ok(s) => Ok(s.to_owned()), Err(e) => { print_error(format!("Invalid UTF-8 in {name}: {e}")); Err(WalletFfiError::InvalidUtf8) diff --git a/wallet/src/chain_storage.rs b/wallet/src/chain_storage.rs index a4b22a31..df703dc0 100644 --- a/wallet/src/chain_storage.rs +++ b/wallet/src/chain_storage.rs @@ -20,6 +20,10 @@ pub struct WalletChainStore { } impl WalletChainStore { + #[expect( + clippy::wildcard_enum_match_arm, + reason = "We perform search for specific variants only" + )] pub fn new( config: WalletConfig, persistent_accounts: Vec, @@ -158,7 +162,7 @@ impl WalletChainStore { #[cfg(test)] mod tests { use key_protocol::key_management::key_tree::{ - keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, traits::KeyNode, + keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, traits::KeyNode as _, }; use super::*; @@ -281,16 +285,16 @@ mod tests { chain_index: ChainIndex::root(), data: public_data, }), - PersistentAccountData::Private(PersistentAccountDataPrivate { + PersistentAccountData::Private(Box::new(PersistentAccountDataPrivate { account_id: private_data.account_id(), chain_index: ChainIndex::root(), data: private_data, - }), + })), ] } #[test] - fn test_new_initializes_correctly() { + fn new_initializes_correctly() { let config = create_sample_wallet_config(); let accs = create_sample_persistent_accounts(); diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index bd028794..bb9f9700 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -31,7 +31,7 @@ pub enum AccountSubcommand { #[command(subcommand)] New(NewSubcommand), /// Sync private accounts - SyncPrivate {}, + SyncPrivate, /// List all accounts owned by the wallet #[command(visible_alias = "ls")] List { @@ -80,7 +80,7 @@ impl WalletSubcommand for NewSubcommand { ) -> Result { match self { NewSubcommand::Public { cci, label } => { - if let Some(ref label) = label + if let Some(label) = &label && wallet_core .storage .labels @@ -117,7 +117,7 @@ impl WalletSubcommand for NewSubcommand { Ok(SubcommandReturnValue::RegisterAccount { account_id }) } NewSubcommand::Private { cci, label } => { - if let Some(ref label) = label + if let Some(label) = &label && wallet_core .storage .labels @@ -159,49 +159,8 @@ impl WalletSubcommand for NewSubcommand { } } -/// Formats account details for display, returning (description, `json_view`) -fn format_account_details(account: &Account) -> (String, String) { - let auth_tr_prog_id = Program::authenticated_transfer_program().id(); - let token_prog_id = Program::token().id(); - - match &account.program_owner { - o if *o == auth_tr_prog_id => { - let account_hr: HumanReadableAccount = account.clone().into(); - ( - "Account owned by authenticated transfer program".to_string(), - serde_json::to_string(&account_hr).unwrap(), - ) - } - o if *o == token_prog_id => { - if let Ok(token_def) = TokenDefinition::try_from(&account.data) { - ( - "Definition account owned by token program".to_string(), - serde_json::to_string(&token_def).unwrap(), - ) - } else if let Ok(token_hold) = TokenHolding::try_from(&account.data) { - ( - "Holding account owned by token program".to_string(), - serde_json::to_string(&token_hold).unwrap(), - ) - } else { - let account_hr: HumanReadableAccount = account.clone().into(); - ( - "Unknown token program account".to_string(), - serde_json::to_string(&account_hr).unwrap(), - ) - } - } - _ => { - let account_hr: HumanReadableAccount = account.clone().into(); - ( - "Account".to_string(), - serde_json::to_string(&account_hr).unwrap(), - ) - } - } -} - impl WalletSubcommand for AccountSubcommand { + #[expect(clippy::cognitive_complexity, reason = "TODO: fix later")] async fn handle_subcommand( self, wallet_core: &mut WalletCore, @@ -286,7 +245,7 @@ impl WalletSubcommand for AccountSubcommand { AccountSubcommand::New(new_subcommand) => { new_subcommand.handle_subcommand(wallet_core).await } - AccountSubcommand::SyncPrivate {} => { + AccountSubcommand::SyncPrivate => { let curr_last_block = wallet_core .sequencer_client .get_last_block() @@ -318,7 +277,7 @@ impl WalletSubcommand for AccountSubcommand { if let Some(label) = labels.get(&id_str) { format!("{prefix} [{label}]") } else { - prefix.to_string() + prefix.to_owned() } }; @@ -453,3 +412,45 @@ impl WalletSubcommand for AccountSubcommand { } } } + +/// Formats account details for display, returning (description, `json_view`) +fn format_account_details(account: &Account) -> (String, String) { + let auth_tr_prog_id = Program::authenticated_transfer_program().id(); + let token_prog_id = Program::token().id(); + + match &account.program_owner { + o if *o == auth_tr_prog_id => { + let account_hr: HumanReadableAccount = account.clone().into(); + ( + "Account owned by authenticated transfer program".to_string(), + serde_json::to_string(&account_hr).unwrap(), + ) + } + o if *o == token_prog_id => { + if let Ok(token_def) = TokenDefinition::try_from(&account.data) { + ( + "Definition account owned by token program".to_owned(), + serde_json::to_string(&token_def).unwrap(), + ) + } else if let Ok(token_hold) = TokenHolding::try_from(&account.data) { + ( + "Holding account owned by token program".to_string(), + serde_json::to_string(&token_hold).unwrap(), + ) + } else { + let account_hr: HumanReadableAccount = account.clone().into(); + ( + "Unknown token program account".to_owned(), + serde_json::to_string(&account_hr).unwrap(), + ) + } + } + _ => { + let account_hr: HumanReadableAccount = account.clone().into(); + ( + "Account".to_owned(), + serde_json::to_string(&account_hr).unwrap(), + ) + } + } +} diff --git a/wallet/src/cli/chain.rs b/wallet/src/cli/chain.rs index 1e3ec029..3979a84f 100644 --- a/wallet/src/cli/chain.rs +++ b/wallet/src/cli/chain.rs @@ -11,7 +11,7 @@ use crate::{ #[derive(Subcommand, Debug, Clone)] pub enum ChainSubcommand { /// Get current block id from sequencer - CurrentBlockId {}, + CurrentBlockId, /// Get block at id from sequencer Block { #[arg(short, long)] @@ -31,7 +31,7 @@ impl WalletSubcommand for ChainSubcommand { wallet_core: &mut WalletCore, ) -> Result { match self { - ChainSubcommand::CurrentBlockId {} => { + ChainSubcommand::CurrentBlockId => { let latest_block_res = wallet_core.sequencer_client.get_last_block().await?; println!("Last block id is {}", latest_block_res.last_block); diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index 42e2b4b2..a53f8047 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -1,6 +1,6 @@ -use std::{io::Write, path::PathBuf}; +use std::{io::Write as _, path::PathBuf, sync::Arc}; -use anyhow::{Context, Result}; +use anyhow::{Context as _, Result}; use clap::{Parser, Subcommand}; use common::HashType; use nssa::{ProgramDeploymentTransaction, program::Program}; @@ -52,7 +52,7 @@ pub enum Command { AMM(AmmProgramAgnosticSubcommand), /// Check the wallet can connect to the node and builtin local programs /// match the remote versions - CheckHealth {}, + CheckHealth, /// Command to setup config, get and set config fields #[command(subcommand)] Config(ConfigSubcommand), @@ -114,7 +114,7 @@ pub async fn execute_subcommand( Command::Pinata(pinata_subcommand) => { pinata_subcommand.handle_subcommand(wallet_core).await? } - Command::CheckHealth {} => { + Command::CheckHealth => { let remote_program_ids = wallet_core .sequencer_client .get_program_ids() @@ -150,7 +150,7 @@ pub async fn execute_subcommand( "Local ID for AMM program is different from remote" ); - println!("✅All looks good!"); + println!("\u{2705}All looks good!"); SubcommandReturnValue::Empty } @@ -206,7 +206,7 @@ pub fn read_password_from_stdin() -> Result { std::io::stdout().flush()?; std::io::stdin().read_line(&mut password)?; - Ok(password.trim().to_string()) + Ok(password.trim().to_owned()) } pub async fn execute_keys_restoration(wallet_core: &mut WalletCore, depth: u32) -> Result<()> { @@ -230,7 +230,7 @@ pub async fn execute_keys_restoration(wallet_core: &mut WalletCore, depth: u32) .storage .user_data .public_key_tree - .cleanup_tree_remove_uninit_layered(depth, wallet_core.sequencer_client.clone()) + .cleanup_tree_remove_uninit_layered(depth, Arc::clone(&wallet_core.sequencer_client)) .await?; println!("Public tree cleaned up"); diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index f03194ca..b891976b 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -346,7 +346,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { let to_npk = nssa_core::NullifierPublicKey(to_npk); let to_vpk_res = hex::decode(to_vpk)?; - let mut to_vpk = [0u8; 33]; + let mut to_vpk = [0_u8; 33]; to_vpk.copy_from_slice(&to_vpk_res); let to_vpk = nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_vpk.to_vec()); @@ -423,7 +423,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let to_npk = nssa_core::NullifierPublicKey(to_npk); let to_vpk_res = hex::decode(to_vpk)?; - let mut to_vpk = [0u8; 33]; + let mut to_vpk = [0_u8; 33]; to_vpk.copy_from_slice(&to_vpk_res); let to_vpk = nssa_core::encryption::shared_key_derivation::Secp256k1Point(to_vpk.to_vec()); diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index 1fec8058..0af61314 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result}; +use anyhow::{Context as _, Result}; use clap::Subcommand; use common::{PINATA_BASE58, transaction::NSSATransaction}; use nssa::{Account, AccountId}; @@ -34,13 +34,13 @@ impl WalletSubcommand for PinataProgramAgnosticSubcommand { match to_addr_privacy { AccountPrivacyKind::Public => { PinataProgramSubcommand::Public(PinataProgramSubcommandPublic::Claim { - pinata_account_id: PINATA_BASE58.to_string(), + pinata_account_id: PINATA_BASE58.to_owned(), winner_account_id: to, }) } AccountPrivacyKind::Private => PinataProgramSubcommand::Private( PinataProgramSubcommandPrivate::ClaimPrivateOwned { - pinata_account_id: PINATA_BASE58.to_string(), + pinata_account_id: PINATA_BASE58.to_owned(), winner_account_id: to, }, ), @@ -242,7 +242,7 @@ async fn find_solution(wallet: &WalletCore, pinata_account_id: AccountId) -> Res .data .as_ref() .try_into() - .map_err(|_| anyhow::Error::msg("invalid pinata account data"))?; + .map_err(|_err| anyhow::Error::msg("invalid pinata account data"))?; println!("Computing solution for pinata..."); let now = std::time::Instant::now(); @@ -257,7 +257,7 @@ fn compute_solution(data: [u8; 33]) -> u128 { let difficulty = data[0]; let seed = &data[1..]; - let mut solution = 0u128; + let mut solution = 0_u128; while !validate_solution(difficulty, seed, solution) { solution = solution.checked_add(1).expect("solution overflowed u128"); } @@ -276,6 +276,6 @@ fn validate_solution(difficulty: u8, seed: &[u8], solution: u128) -> bool { hasher.update(bytes); let digest: [u8; 32] = hasher.finalize_fixed().into(); - let difficulty = difficulty as usize; + let difficulty = usize::from(difficulty); digest[..difficulty].iter().all(|&b| b == 0) } diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index 805cbbd4..343960ac 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -755,7 +755,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { let recipient_npk = nssa_core::NullifierPublicKey(recipient_npk); let recipient_vpk_res = hex::decode(recipient_vpk)?; - let mut recipient_vpk = [0u8; 33]; + let mut recipient_vpk = [0_u8; 33]; recipient_vpk.copy_from_slice(&recipient_vpk_res); let recipient_vpk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( recipient_vpk.to_vec(), @@ -876,7 +876,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { let holder_npk = nssa_core::NullifierPublicKey(holder_npk); let holder_vpk_res = hex::decode(holder_vpk)?; - let mut holder_vpk = [0u8; 33]; + let mut holder_vpk = [0_u8; 33]; holder_vpk.copy_from_slice(&holder_vpk_res); let holder_vpk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( holder_vpk.to_vec(), @@ -1044,7 +1044,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { let recipient_npk = nssa_core::NullifierPublicKey(recipient_npk); let recipient_vpk_res = hex::decode(recipient_vpk)?; - let mut recipient_vpk = [0u8; 33]; + let mut recipient_vpk = [0_u8; 33]; recipient_vpk.copy_from_slice(&recipient_vpk_res); let recipient_vpk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( recipient_vpk.to_vec(), @@ -1188,7 +1188,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { let holder_npk = nssa_core::NullifierPublicKey(holder_npk); let holder_vpk_res = hex::decode(holder_vpk)?; - let mut holder_vpk = [0u8; 33]; + let mut holder_vpk = [0_u8; 33]; holder_vpk.copy_from_slice(&holder_vpk_res); let holder_vpk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( holder_vpk.to_vec(), diff --git a/wallet/src/config.rs b/wallet/src/config.rs index c555e4ed..ce1cf2ba 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -48,21 +48,19 @@ pub struct PersistentAccountDataPrivate { // Big difference in enum variants sizes // however it is improbable, that we will have that much accounts, that it will substantialy affect // memory -#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum InitialAccountData { Public(InitialAccountDataPublic), - Private(InitialAccountDataPrivate), + Private(Box), } // Big difference in enum variants sizes // however it is improbable, that we will have that much accounts, that it will substantialy affect // memory -#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PersistentAccountData { Public(PersistentAccountDataPublic), - Private(PersistentAccountDataPrivate), + Private(Box), Preconfigured(InitialAccountData), } @@ -95,6 +93,10 @@ pub struct PersistentStorage { impl PersistentStorage { pub fn from_path(path: &Path) -> Result { + #[expect( + clippy::wildcard_enum_match_arm, + reason = "We want to provide a specific error message for not found case" + )] match std::fs::File::open(path) { Ok(file) => { let storage_content = BufReader::new(file); @@ -141,7 +143,7 @@ impl From for InitialAccountData { impl From for InitialAccountData { fn from(value: InitialAccountDataPrivate) -> Self { - Self::Private(value) + Self::Private(Box::new(value)) } } @@ -153,7 +155,7 @@ impl From for PersistentAccountData { impl From for PersistentAccountData { fn from(value: PersistentAccountDataPrivate) -> Self { - Self::Private(value) + Self::Private(Box::new(value)) } } diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index fb3f8f8f..fe4fdec3 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -1,11 +1,11 @@ -use std::{collections::HashMap, path::PathBuf, str::FromStr}; +use std::{collections::HashMap, path::PathBuf, str::FromStr as _}; -use anyhow::Result; -use base58::ToBase58; +use anyhow::{Context as _, Result}; +use base58::ToBase58 as _; use key_protocol::key_protocol_core::NSSAUserData; use nssa::Account; use nssa_core::account::Nonce; -use rand::{RngCore, rngs::OsRng}; +use rand::{RngCore as _, rngs::OsRng}; use serde::Serialize; use crate::{ @@ -16,6 +16,39 @@ use crate::{ }, }; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AccountPrivacyKind { + Public, + Private, +} + +/// Human-readable representation of an account. +#[derive(Serialize)] +pub(crate) struct HumanReadableAccount { + balance: u128, + program_owner: String, + data: String, + nonce: u128, +} + +impl From for HumanReadableAccount { + fn from(account: Account) -> Self { + let program_owner = account + .program_owner + .iter() + .flat_map(|n| n.to_le_bytes()) + .collect::>() + .to_base58(); + let data = hex::encode(account.data); + Self { + balance: account.balance, + program_owner, + data, + nonce: account.nonce, + } + } +} + /// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. fn get_home_nssa_var() -> Result { Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?) @@ -100,11 +133,11 @@ pub fn produce_data_for_storage( for (account_id, (key_chain, account)) in &user_data.default_user_private_accounts { vec_for_storage.push( - InitialAccountData::Private(InitialAccountDataPrivate { + InitialAccountData::Private(Box::new(InitialAccountDataPrivate { account_id: *account_id, account: account.clone(), key_chain: key_chain.clone(), - }) + })) .into(), ); } @@ -124,23 +157,17 @@ pub(crate) fn produce_random_nonces(size: usize) -> Vec { result.into_iter().map(Nonce::from_le_bytes).collect() } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AccountPrivacyKind { - Public, - Private, -} - pub(crate) fn parse_addr_with_privacy_prefix( account_base58: &str, ) -> Result<(String, AccountPrivacyKind)> { if account_base58.starts_with("Public/") { Ok(( - account_base58.strip_prefix("Public/").unwrap().to_string(), + account_base58.strip_prefix("Public/").unwrap().to_owned(), AccountPrivacyKind::Public, )) } else if account_base58.starts_with("Private/") { Ok(( - account_base58.strip_prefix("Private/").unwrap().to_string(), + account_base58.strip_prefix("Private/").unwrap().to_owned(), AccountPrivacyKind::Private, )) } else { @@ -148,54 +175,12 @@ pub(crate) fn parse_addr_with_privacy_prefix( } } -/// Human-readable representation of an account. -#[derive(Serialize)] -pub(crate) struct HumanReadableAccount { - balance: u128, - program_owner: String, - data: String, - nonce: u128, -} - -impl From for HumanReadableAccount { - fn from(account: Account) -> Self { - let program_owner = account - .program_owner - .iter() - .flat_map(|n| n.to_le_bytes()) - .collect::>() - .to_base58(); - let data = hex::encode(account.data); - Self { - balance: account.balance, - program_owner, - data, - nonce: account.nonce, - } - } -} - #[cfg(test)] mod tests { use super::*; #[test] - fn test_get_home_get_env_var() { - unsafe { - std::env::set_var(HOME_DIR_ENV_VAR, "/path/to/configs"); - } - - let home = get_home().unwrap(); - - assert_eq!(PathBuf::from_str("/path/to/configs").unwrap(), home); - - unsafe { - std::env::remove_var(HOME_DIR_ENV_VAR); - } - } - - #[test] - fn test_addr_parse_with_privacy() { + fn addr_parse_with_privacy() { let addr_base58 = "Public/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy"; let (_, addr_kind) = parse_addr_with_privacy_prefix(addr_base58).unwrap(); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index f7f0d62c..b81ff784 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1,7 +1,17 @@ +#![expect( + clippy::print_stdout, + clippy::print_stderr, + reason = "This is a CLI application, printing to stdout and stderr is expected and convenient" +)] +#![expect( + clippy::shadow_unrelated, + reason = "Most of the shadows come from args parsing which is ok" +)] + use std::{path::PathBuf, sync::Arc}; -use anyhow::{Context, Result}; -use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; +use anyhow::{Context as _, Result}; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64}; use chain_storage::WalletChainStore; use common::{ HashType, error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse, @@ -18,7 +28,7 @@ use nssa::{ }; use nssa_core::{Commitment, MembershipProof, SharedSecretKey, program::InstructionData}; pub use privacy_preserving_tx::PrivacyPreservingAccount; -use tokio::io::AsyncWriteExt; +use tokio::io::AsyncWriteExt as _; use crate::{ config::{PersistentStorage, WalletConfigOverrides}, @@ -26,8 +36,6 @@ use crate::{ poller::TxPoller, }; -pub const HOME_DIR_ENV_VAR: &str = "NSSA_WALLET_HOME_DIR"; - pub mod chain_storage; pub mod cli; pub mod config; @@ -36,18 +44,20 @@ pub mod poller; mod privacy_preserving_tx; pub mod program_facades; +pub const HOME_DIR_ENV_VAR: &str = "NSSA_WALLET_HOME_DIR"; + pub enum AccDecodeData { Skip, Decode(nssa_core::SharedSecretKey, AccountId), } +#[expect(clippy::partial_pub_fields, reason = "TODO: make all fields private")] pub struct WalletCore { config_path: PathBuf, config_overrides: Option, storage: WalletChainStore, storage_path: PathBuf, poller: TxPoller, - // TODO: Make all fields private pub sequencer_client: Arc, pub last_synced_block: u64, } @@ -322,7 +332,6 @@ impl WalletCore { program: &ProgramWithDependencies, ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { // TODO: handle large Err-variant properly - #[allow(clippy::result_large_err)] self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| { Ok(()) }) @@ -401,12 +410,17 @@ impl WalletCore { } let before_polling = std::time::Instant::now(); - let num_of_blocks = block_id - self.last_synced_block; + let num_of_blocks = block_id.saturating_sub(self.last_synced_block); + if num_of_blocks == 0 { + return Ok(()); + } + println!("Syncing to block {block_id}. Blocks to sync: {num_of_blocks}"); let poller = self.poller.clone(); - let mut blocks = - std::pin::pin!(poller.poll_block_range(self.last_synced_block + 1..=block_id)); + let mut blocks = std::pin::pin!( + poller.poll_block_range(self.last_synced_block.saturating_add(1)..=block_id) + ); let bar = indicatif::ProgressBar::new(num_of_blocks); while let Some(block) = blocks.try_next().await? { diff --git a/wallet/src/main.rs b/wallet/src/main.rs index b2057296..4704675b 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,3 +1,8 @@ +#![expect( + clippy::print_stdout, + reason = "This is a CLI application, printing to stdout is expected and convenient" +)] + use anyhow::{Context as _, Result}; use clap::{CommandFactory as _, Parser as _}; use wallet::{ diff --git a/wallet/src/poller.rs b/wallet/src/poller.rs index 7f0d4e7f..4ef77e04 100644 --- a/wallet/src/poller.rs +++ b/wallet/src/poller.rs @@ -36,7 +36,7 @@ impl TxPoller { for poll_id in 1..max_blocks_to_query { info!("Poll {poll_id}"); - let mut try_error_counter = 0; + let mut try_error_counter = 0_u64; let tx_obj = loop { let tx_obj = self @@ -50,7 +50,9 @@ impl TxPoller { if let Ok(tx_obj) = tx_obj { break tx_obj; } - try_error_counter += 1; + try_error_counter = try_error_counter + .checked_add(1) + .expect("We check error counter in this loop"); if try_error_counter > self.polling_max_error_attempts { anyhow::bail!("Number of retries exceeded"); @@ -75,7 +77,7 @@ impl TxPoller { let mut chunk_start = *range.start(); loop { - let chunk_end = std::cmp::min(chunk_start + self.block_poll_max_amount - 1, *range.end()); + let chunk_end = std::cmp::min(chunk_start.saturating_add(self.block_poll_max_amount).saturating_sub(1), *range.end()); let blocks = self.client.get_block_range(chunk_start..=chunk_end).await?.blocks; for block in blocks { @@ -83,7 +85,7 @@ impl TxPoller { yield Ok(block); } - chunk_start = chunk_end + 1; + chunk_start = chunk_end.saturating_add(1); if chunk_start > *range.end() { break; } diff --git a/wallet/src/privacy_preserving_tx.rs b/wallet/src/privacy_preserving_tx.rs index 8726fc51..9cd23888 100644 --- a/wallet/src/privacy_preserving_tx.rs +++ b/wallet/src/privacy_preserving_tx.rs @@ -69,7 +69,7 @@ impl AccountManager { let acc = wallet .get_account_public(account_id) .await - .map_err(|_| ExecutionFailureKind::KeyNotFoundError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let sk = wallet.get_account_public_signing_key(account_id).cloned(); let account = AccountWithMetadata::new(acc.clone(), sk.is_some(), account_id); diff --git a/wallet/src/program_facades/amm.rs b/wallet/src/program_facades/amm.rs index 0722a769..251970bc 100644 --- a/wallet/src/program_facades/amm.rs +++ b/wallet/src/program_facades/amm.rs @@ -4,7 +4,7 @@ use nssa::{AccountId, program::Program}; use token_core::TokenHolding; use crate::WalletCore; -pub struct Amm<'w>(pub &'w WalletCore); +pub struct Amm<'wallet>(pub &'wallet WalletCore); impl Amm<'_> { pub async fn send_new_definition( @@ -27,18 +27,18 @@ impl Amm<'_> { .0 .get_account_public(user_holding_a) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let user_b_acc = self .0 .get_account_public(user_holding_b) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_a))? .definition_id(); let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_b))? .definition_id(); let amm_pool = @@ -61,7 +61,7 @@ impl Amm<'_> { .0 .get_accounts_nonces(vec![user_holding_a, user_holding_b]) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let signing_key_a = self .0 @@ -115,18 +115,18 @@ impl Amm<'_> { .0 .get_account_public(user_holding_a) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let user_b_acc = self .0 .get_account_public(user_holding_b) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_a))? .definition_id(); let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_b))? .definition_id(); let amm_pool = @@ -149,17 +149,17 @@ impl Amm<'_> { .0 .get_account_public(user_holding_a) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let token_holder_acc_b = self .0 .get_account_public(user_holding_b) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let token_holder_a = TokenHolding::try_from(&token_holder_acc_a.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))?; + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_a))?; let token_holder_b = TokenHolding::try_from(&token_holder_acc_b.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))?; + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_b))?; if token_holder_a.definition_id() == token_definition_id_in { account_id_auth = user_holding_a; @@ -175,7 +175,7 @@ impl Amm<'_> { .0 .get_accounts_nonces(vec![account_id_auth]) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let signing_key = self .0 @@ -221,18 +221,18 @@ impl Amm<'_> { .0 .get_account_public(user_holding_a) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let user_b_acc = self .0 .get_account_public(user_holding_b) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_a))? .definition_id(); let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_b))? .definition_id(); let amm_pool = @@ -255,7 +255,7 @@ impl Amm<'_> { .0 .get_accounts_nonces(vec![user_holding_a, user_holding_b]) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let signing_key_a = self .0 @@ -310,18 +310,18 @@ impl Amm<'_> { .0 .get_account_public(user_holding_a) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let user_b_acc = self .0 .get_account_public(user_holding_b) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let definition_token_a_id = TokenHolding::try_from(&user_a_acc.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_a))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_a))? .definition_id(); let definition_token_b_id = TokenHolding::try_from(&user_b_acc.data) - .map_err(|_| ExecutionFailureKind::AccountDataError(user_holding_b))? + .map_err(|_err| ExecutionFailureKind::AccountDataError(user_holding_b))? .definition_id(); let amm_pool = @@ -344,7 +344,7 @@ impl Amm<'_> { .0 .get_accounts_nonces(vec![user_holding_lp]) .await - .map_err(|_| ExecutionFailureKind::SequencerError)?; + .map_err(ExecutionFailureKind::SequencerError)?; let signing_key_lp = self .0 diff --git a/wallet/src/program_facades/native_token_transfer/mod.rs b/wallet/src/program_facades/native_token_transfer/mod.rs index 6d55ac0f..17fe3fd3 100644 --- a/wallet/src/program_facades/native_token_transfer/mod.rs +++ b/wallet/src/program_facades/native_token_transfer/mod.rs @@ -9,7 +9,7 @@ pub mod private; pub mod public; pub mod shielded; -pub struct NativeTokenTransfer<'w>(pub &'w WalletCore); +pub struct NativeTokenTransfer<'wallet>(pub &'wallet WalletCore); fn auth_transfer_preparation( balance_to_move: u128, @@ -22,7 +22,6 @@ fn auth_transfer_preparation( let program = Program::authenticated_transfer_program(); // TODO: handle large Err-variant properly - #[allow(clippy::result_large_err)] let tx_pre_check = move |accounts: &[&Account]| { let from = accounts[0]; if from.balance >= balance_to_move { diff --git a/wallet/src/program_facades/native_token_transfer/public.rs b/wallet/src/program_facades/native_token_transfer/public.rs index 3e4815a1..eb2adc9a 100644 --- a/wallet/src/program_facades/native_token_transfer/public.rs +++ b/wallet/src/program_facades/native_token_transfer/public.rs @@ -14,14 +14,18 @@ impl NativeTokenTransfer<'_> { to: AccountId, balance_to_move: u128, ) -> Result { - let Ok(balance) = self.0.get_account_balance(from).await else { - return Err(ExecutionFailureKind::SequencerError); - }; + let balance = self + .0 + .get_account_balance(from) + .await + .map_err(ExecutionFailureKind::SequencerError)?; if balance >= balance_to_move { - let Ok(nonces) = self.0.get_accounts_nonces(vec![from]).await else { - return Err(ExecutionFailureKind::SequencerError); - }; + let nonces = self + .0 + .get_accounts_nonces(vec![from]) + .await + .map_err(ExecutionFailureKind::SequencerError)?; let account_ids = vec![from, to]; let program_id = Program::authenticated_transfer_program().id(); @@ -48,9 +52,11 @@ impl NativeTokenTransfer<'_> { &self, from: AccountId, ) -> Result { - let Ok(nonces) = self.0.get_accounts_nonces(vec![from]).await else { - return Err(ExecutionFailureKind::SequencerError); - }; + let nonces = self + .0 + .get_accounts_nonces(vec![from]) + .await + .map_err(ExecutionFailureKind::SequencerError)?; let instruction: u128 = 0; let account_ids = vec![from]; diff --git a/wallet/src/program_facades/pinata.rs b/wallet/src/program_facades/pinata.rs index 68891ff5..c68fa658 100644 --- a/wallet/src/program_facades/pinata.rs +++ b/wallet/src/program_facades/pinata.rs @@ -4,7 +4,7 @@ use nssa_core::{MembershipProof, SharedSecretKey}; use crate::{PrivacyPreservingAccount, WalletCore}; -pub struct Pinata<'w>(pub &'w WalletCore); +pub struct Pinata<'wallet>(pub &'wallet WalletCore); impl Pinata<'_> { pub async fn claim( diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index 9543f593..fe5165ff 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -5,7 +5,7 @@ use token_core::Instruction; use crate::{PrivacyPreservingAccount, WalletCore}; -pub struct Token<'w>(pub &'w WalletCore); +pub struct Token<'wallet>(pub &'wallet WalletCore); impl Token<'_> { pub async fn send_new_definition( @@ -133,9 +133,11 @@ impl Token<'_> { let instruction = Instruction::Transfer { amount_to_transfer: amount, }; - let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else { - return Err(ExecutionFailureKind::SequencerError); - }; + let nonces = self + .0 + .get_accounts_nonces(vec![sender_account_id]) + .await + .map_err(ExecutionFailureKind::SequencerError)?; let message = nssa::public_transaction::Message::try_new( program_id, account_ids, @@ -332,9 +334,11 @@ impl Token<'_> { amount_to_burn: amount, }; - let Ok(nonces) = self.0.get_accounts_nonces(vec![holder_account_id]).await else { - return Err(ExecutionFailureKind::SequencerError); - }; + let nonces = self + .0 + .get_accounts_nonces(vec![holder_account_id]) + .await + .map_err(ExecutionFailureKind::SequencerError)?; let message = nssa::public_transaction::Message::try_new( Program::token().id(), account_ids, @@ -460,13 +464,11 @@ impl Token<'_> { amount_to_mint: amount, }; - let Ok(nonces) = self + let nonces = self .0 .get_accounts_nonces(vec![definition_account_id]) .await - else { - return Err(ExecutionFailureKind::SequencerError); - }; + .map_err(ExecutionFailureKind::SequencerError)?; let message = nssa::public_transaction::Message::try_new( Program::token().id(), account_ids,