feat: add restriction clippy lints

This commit is contained in:
Daniil Polyakov 2026-03-04 18:42:33 +03:00
parent efe8393ba0
commit e3b93b6e9a
137 changed files with 4171 additions and 3765 deletions

1
Cargo.lock generated
View File

@ -5561,6 +5561,7 @@ name = "nssa"
version = "0.1.0"
dependencies = [
"amm_core",
"anyhow",
"borsh",
"env_logger",
"hex",

View File

@ -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"

View File

@ -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};

54
clippy.toml Normal file
View File

@ -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"]

View File

@ -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);

View File

@ -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),
})
}

View File

@ -28,8 +28,8 @@ impl From<SequencerRpcError> 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")]

View File

@ -46,7 +46,7 @@ impl FromStr for HashType {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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);

View File

@ -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<RpcParseError> for RpcError {
impl From<std::convert::Infallible> 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() }
}
}

View File

@ -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<Message, Broken>;
#[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<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
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<Value>` 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<Option<Value>, 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<Value>,
@ -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<Message> for String {
fn from(val: Message) -> Self {
::serde_json::ser::to_string(&val).unwrap()
}
}
impl From<Message> for Vec<u8> {
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<WireMessage>) -> Parsed {
}
}
pub type Parsed = Result<Message, Broken>;
/// 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<Message> for String {
fn from(val: Message) -> Self {
::serde_json::ser::to_string(&val).unwrap()
}
}
impl From<Message> for Vec<u8> {
fn from(val: Message) -> Self {
::serde_json::ser::to_vec(&val).unwrap()
}
/// Deserializer for `Option<Value>` 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<Option<Value>, 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)),

View File

@ -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<Value>) -> Result<Self, RpcParseError> {
parse_params::<Self>(value)
}
}
};
}
pub trait RpcRequest: Sized {
fn parse(value: Option<Value>) -> Result<Self, RpcParseError>;
}
@ -15,13 +26,3 @@ pub fn parse_params<T: DeserializeOwned>(value: Option<Value>) -> Result<T, RpcP
Err(RpcParseError("Require at least one parameter".to_owned()))
}
}
#[macro_export]
macro_rules! parse_request {
($request_name:ty) => {
impl RpcRequest for $request_name {
fn parse(value: Option<Value>) -> Result<Self, RpcParseError> {
parse_params::<Self>(value)
}
}
};
}

View File

@ -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<S>(bytes_vec: &[Vec<u8>], serializer: S) -> Result<S::Ok, S::Error>
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<Vec<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let base64_strings: Vec<String> = Deserialize::deserialize(deserializer)?;
base64_strings
.into_iter()
.map(|s| {
general_purpose::STANDARD
.decode(&s)
.map_err(serde::de::Error::custom)
})
.collect()
}
}
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let base64_string = general_purpose::STANDARD.encode(bytes);
serializer.serialize_str(&base64_string)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, 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<Vec<u8>>,
}
mod base64_deser {
use base64::{Engine as _, engine::general_purpose};
use serde::{self, Deserialize, Deserializer, Serializer, ser::SerializeSeq as _};
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let base64_string = general_purpose::STANDARD.encode(bytes);
serializer.serialize_str(&base64_string)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, 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<S>(bytes_vec: &[Vec<u8>], serializer: S) -> Result<S::Ok, S::Error>
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<Vec<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
let base64_strings: Vec<String> = 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,

View File

@ -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<Value, SequencerClientError> {
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 {}",

View File

@ -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,
};

View File

@ -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<u8>);
const WRITE_FUNCTION_ID: u8 = 0;
const MOVE_DATA_FUNCTION_ID: u8 = 1;
type Instruction = (u8, Vec<u8>);
fn build_post_state(post_account: Account) -> AccountPostState {
if post_account.program_owner == DEFAULT_PROGRAM_ID {
// This produces a claim request

View File

@ -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,

View File

@ -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<u8>);
const WRITE_FUNCTION_ID: u8 = 0;
const MOVE_DATA_FUNCTION_ID: u8 = 1;
type Instruction = (u8, Vec<u8>);
#[derive(Parser, Debug)]
struct Cli {
/// Path to program binary

View File

@ -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;

View File

@ -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(

View File

@ -1,5 +1,6 @@
#![expect(
clippy::must_use_candidate,
clippy::same_name_method,
reason = "Warns on code generated by leptos macros"
)]

View File

@ -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.

View File

@ -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 {
<div class="info-row">
<span class="info-label">"Account ID:"</span>
<span class="info-value hash">{account_id_str}</span>
</div>
<div class="info-row">
<span class="info-label">"Balance:"</span>
<span class="info-value">{balance_str}</span>
</div>
<div class="info-row">
<span class="info-label">"Program Owner:"</span>
<span class="info-value hash">{program_id}</span>
</div>
<div class="info-row">
<span class="info-label">"Nonce:"</span>
<span class="info-value">{nonce_str}</span>
</div>
<div class="info-row">
<span class="info-label">"Data:"</span>
<span class="info-value">{format!("{data_len} bytes")}</span>
</div>
</div>
</div>
</div>
<div class="info-row">
<span class="info-label">"Balance:"</span>
<span class="info-value">{balance_str}</span>
</div>
<div class="info-row">
<span class="info-label">"Program Owner:"</span>
<span class="info-value hash">{program_id}</span>
</div>
<div class="info-row">
<span class="info-label">"Nonce:"</span>
<span class="info-value">{nonce_str}</span>
</div>
<div class="info-row">
<span class="info-label">"Data:"</span>
<span class="info-value">{format!("{data_len} bytes")}</span>
</div>
</div>
</div>
<div class="account-transactions">
<h2>"Transactions"</h2>
<Suspense fallback=move || {
view! { <div class="loading">"Loading transactions..."</div> }
}>
{move || {
transactions_resource
.get()
.map(|result| match result {
Ok(_) => {
let txs = all_transactions.get();
if txs.is_empty() {
view! {
<div class="no-transactions">
"No transactions found"
</div>
}
.into_any()
} else {
view! {
<div>
<div class="transactions-list">
{txs
.into_iter()
.map(|tx| {
view! { <TransactionPreview transaction=tx /> }
})
.collect::<Vec<_>>()}
</div>
{move || {
if has_more.get() {
view! {
<button
class="load-more-button"
on:click=load_more
disabled=move || is_loading.get()
>
{move || {
if is_loading.get() {
"Loading..."
} else {
"Load More"
}
}}
</button>
}
.into_any()
} else {
().into_any()
<div class="account-transactions">
<h2>"Transactions"</h2>
<Suspense fallback=move || {
view! { <div class="loading">"Loading transactions..."</div> }
}>
{move || {
transactions_resource
.get()
.map(|load_tx_result| match load_tx_result {
Ok(_) => {
let txs = all_transactions.get();
if txs.is_empty() {
view! {
<div class="no-transactions">
"No transactions found"
</div>
}
}}
.into_any()
} else {
view! {
<div>
<div class="transactions-list">
{txs
.into_iter()
.map(|tx| {
view! { <TransactionPreview transaction=tx /> }
})
.collect::<Vec<_>>()}
</div>
{move || {
if has_more.get() {
view! {
<button
class="load-more-button"
on:click=load_more
disabled=move || is_loading.get()
>
{move || {
if is_loading.get() {
"Loading..."
} else {
"Load More"
}
}}
</div>
}
.into_any()
}
}
Err(e) => {
view! {
<div class="error">
{format!("Failed to load transactions: {e}")}
</div>
}
.into_any()
}
})
}}
</button>
}
.into_any()
} else {
().into_any()
}
}}
</Suspense>
</div>
</div>
}
.into_any()
</div>
}
.into_any()
}
}
Err(e) => {
view! {
<div class="error">
{format!("Failed to load transactions: {e}")}
</div>
}
.into_any()
}
})
}}
</Suspense>
</div>
</div>
}
.into_any()
}
Err(e) => {
view! {
@ -218,7 +216,6 @@ pub fn AccountPage() -> impl IntoView {
}
})
}}
</Suspense>
</div>
}

View File

@ -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(),
)),
}
},

View File

@ -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;

View File

@ -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 {
<span class="hash">{account_id_str}</span>
</A>
<span class="nonce">
" (nonce: "{"Not affected by this transaction".to_string()}" )"
" (nonce: "{"Not affected by this transaction".to_owned()}" )"
</span>
</div>
}
@ -153,7 +151,7 @@ pub fn TransactionPage() -> impl IntoView {
<span class="hash">{"Account not found"}</span>
</A>
<span class="nonce">
" (nonce: "{"Account not found".to_string()}" )"
" (nonce: "{"Account not found".to_owned()}" )"
</span>
</div>
}
@ -244,7 +242,7 @@ pub fn TransactionPage() -> impl IntoView {
<span class="hash">{account_id_str}</span>
</A>
<span class="nonce">
" (nonce: "{"Not affected by this transaction".to_string()}" )"
" (nonce: "{"Not affected by this transaction".to_owned()}" )"
</span>
</div>
}
@ -256,7 +254,7 @@ pub fn TransactionPage() -> impl IntoView {
<span class="hash">{"Account not found"}</span>
</A>
<span class="nonce">
" (nonce: "{"Account not found".to_string()}" )"
" (nonce: "{"Account not found".to_owned()}" )"
</span>
</div>
}

View File

@ -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<Self> {
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> {
Self::open_db_with_genesis(location, None)
}
pub fn last_observed_l1_lib_header(&self) -> Result<Option<HeaderId>> {
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())?)
}
}

View File

@ -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<Block>, 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| {

View File

@ -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<S: Serializer>(v: &[u8], s: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(v: &[u8], s: S) -> Result<S::Ok, S::Error> {
let base64 = BASE64_STANDARD.encode(v);
String::serialize(&base64, s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, 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<u8> = self.0.iter().flat_map(|n| n.to_be_bytes()).collect();
let bytes: Vec<u8> = 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<Self, Self::Err> {
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<Self, Self::Err> {
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<Self, Self::Err> {
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<S: Serializer>(v: &[u8], s: S) -> Result<S::Ok, S::Error> {
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<S: Serializer>(v: &[u8], s: S) -> Result<S::Ok, S::Error> {
let base64 = BASE64_STANDARD.encode(v);
String::serialize(&base64, s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
let base64 = String::deserialize(d)?;
BASE64_STANDARD
.decode(base64.as_bytes())
.map_err(serde::de::Error::custom)
}
}

View File

@ -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();

View File

@ -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<AccountId> = (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::<()>)
})
}

View File

@ -154,8 +154,10 @@ impl SubscriptionService {
pub async fn add_subscription(&self, subscription: Subscription<BlockId>) -> 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:#?}")),
)
}

View File

@ -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<IndexerConfig> {
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<SequencerConfig> {
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<WalletConfig> {
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<IndexerConfig> {
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<SequencerConfig> {
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<WalletConfig> {
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<Url> {
// 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:<random_port>
@ -262,7 +262,7 @@ pub fn addr_to_url(protocol: UrlProtocol, addr: SocketAddr) -> Result<Url> {
}
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!());

View File

@ -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,

View File

@ -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()),

View File

@ -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?;

View File

@ -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;

View File

@ -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);

View File

@ -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?;

View File

@ -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,

View File

@ -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,

View File

@ -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};

View File

@ -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;

View File

@ -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),

View File

@ -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<PublicTransaction> {
// Create valid public transactions
let program = Program::authenticated_transfer_program();
let public_txs: Vec<PublicTransaction> = 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<PublicTransaction> {
// Create valid public transactions
let program = Program::authenticated_transfer_program();
let public_txs: Vec<PublicTransaction> = 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

View File

@ -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<WalletCore> {
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 {

View File

@ -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),
)
}

View File

@ -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<ChainIndex> {
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<Self> {
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}");
}

View File

@ -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,
]);

View File

@ -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 = [

View File

@ -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<N: KeyNode> {
pub key_map: BTreeMap<ChainIndex, N>,
pub account_id_map: HashMap<nssa::AccountId, ChainIndex>,
pub account_id_map: BTreeMap<nssa::AccountId, ChainIndex>,
}
pub type KeyTreePublic = KeyTree<ChildKeysPublic>;
@ -44,7 +41,7 @@ impl<N: KeyNode> KeyTree<N> {
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<N: KeyNode> KeyTree<N> {
}
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<N: KeyNode> KeyTree<N> {
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<N: KeyNode> KeyTree<N> {
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<N: KeyNode> KeyTree<N> {
break 'outer chain_id;
}
}
depth += 1;
depth = depth.checked_add(1).expect("Max depth reached");
}
}
@ -156,15 +153,13 @@ impl<N: KeyNode> KeyTree<N> {
#[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<N: KeyNode> KeyTree<N> {
}
pub fn remove(&mut self, addr: nssa::AccountId) -> Option<N> {
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<N: KeyNode> KeyTree<N> {
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<ChildKeysPrivate> {
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<ChildKeysPrivate> {
///
/// 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<ChildKeysPublic> {
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<ChildKeysPublic> {
depth: u32,
client: Arc<SequencerClient>,
) -> 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<ChildKeysPublic> {
#[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);
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -1,2 +1,4 @@
#![expect(clippy::print_stdout, reason = "TODO: fix later")]
pub mod key_management;
pub mod key_protocol_core;

View File

@ -66,13 +66,13 @@ mod tests {
use super::*;
#[test]
async fn test_mempool_new() {
async fn mempool_new() {
let (mut pool, _handle): (MemPool<u64>, _) = 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<u64>, _) = 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();

View File

@ -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

View File

@ -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")]

View File

@ -22,18 +22,17 @@ impl Data {
) -> Result<Self, crate::error::NssaCoreError> {
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

View File

@ -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(

View File

@ -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()

View File

@ -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<Self, NssaCoreError> {
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<Self, NssaCoreError> {
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<Self, NssaCoreError> {
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::<Vec<u8>>().try_into().unwrap());
let expected_bytes: [u8; 32] = (0..32).collect::<Vec<u8>>().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::<Vec<u8>>().try_into().unwrap());
let expected_bytes: [u8; 32] = (0..32).collect::<Vec<u8>>().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::<Vec<u8>>().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::<Vec<u8>>().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,

View File

@ -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,

View File

@ -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)

View File

@ -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;

View File

@ -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::<Vec<_>>().try_into().unwrap());
fn constructor_for_account_update() {
let commitment = Commitment((0..32_u8).collect::<Vec<_>>().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,

View File

@ -5,11 +5,11 @@ use serde::{Deserialize, Serialize};
use crate::account::{Account, AccountId, AccountWithMetadata};
pub type ProgramId = [u32; 8];
pub type InstructionData = Vec<u32>;
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<u32>;
pub struct ProgramInput<T> {
pub pre_states: Vec<AccountWithMetadata>,
pub instruction: T,
@ -30,24 +30,9 @@ impl PdaSeed {
}
}
#[must_use]
pub fn compute_authorized_pdas(
caller_program_id: Option<ProgramId>,
pda_seeds: &[PdaSeed],
) -> HashSet<AccountId> {
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<ChainedCall>,
}
/// 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<Item = u128>) -> Option<Self> {
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<ProgramId>,
pda_seeds: &[PdaSeed],
) -> HashSet<AccountId> {
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<T: DeserializeOwned>() -> (ProgramInput<T>, 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<Item = u128>) -> Option<Self> {
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,

View File

@ -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();

View File

@ -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());
}

View File

@ -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"));
}

View File

@ -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"),

View File

@ -30,6 +30,12 @@ impl Proof {
pub fn from_inner(inner: Vec<u8>) -> 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},

View File

@ -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();

View File

@ -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;

View File

@ -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<T: Eq + Hash>(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();

View File

@ -24,10 +24,10 @@ pub struct Program {
impl Program {
pub fn new(bytecode: Vec<u8>) -> Result<Self, NssaError> {
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();

View File

@ -1,5 +1,5 @@
mod message;
mod transaction;
pub use message::Message;
pub use transaction::ProgramDeploymentTransaction;
mod message;
mod transaction;

View File

@ -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;

View File

@ -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];

View File

@ -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);

View File

@ -1,9 +1,5 @@
use crate::{PrivateKey, PublicKey, Signature};
fn hex_to_bytes<const N: usize>(hex: &str) -> [u8; N] {
hex::decode(hex).unwrap().try_into().unwrap()
}
pub struct TestVector {
pub seckey: Option<PrivateKey>,
pub pubkey: PublicKey,
@ -365,3 +361,7 @@ pub fn test_vectors() -> Vec<TestVector> {
},
]
}
fn hex_to_bytes<const N: usize>(hex: &str) -> [u8; N] {
hex::decode(hex).unwrap().try_into().unwrap()
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
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<Self, NssaError> {
// 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();

File diff suppressed because it is too large Load Diff

View File

@ -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 {

View File

@ -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,

View File

@ -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)
}

View File

@ -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<AccountWithMetadata>,
@ -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);
}

View File

@ -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());

View File

@ -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;

View File

@ -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,
};

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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<Self>;
fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result<Self>;
/// Get the bedrock channel ID used by this client.
fn bedrock_channel_id(&self) -> ChannelId;

View File

@ -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<Self> {
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<Self> {
SequencerStore::open_db_with_genesis(location, None, signing_key)
}
pub fn get_block_at_id(&self, id: u64) -> Result<Block> {
Ok(self.dbio.get_block(id)?)
}
@ -120,13 +112,15 @@ pub(crate) fn block_to_transactions_map(block: &Block) -> HashMap<HashType, u64>
#[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();

View File

@ -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<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
// as fixing this issue may require actions non-native to program scope
let store = SequencerStore::open_db_with_genesis(
&config.home.join("rocksdb"),
Some((&genesis_block, genesis_msg_id.into())),
&genesis_block,
genesis_msg_id.into(),
signing_key,
)
.unwrap();
@ -182,7 +183,10 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
) -> 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<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
fn load_or_create_signing_key(path: &Path) -> Result<Ed25519Key> {
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<Ed25519Key> {
}
}
#[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<u8> = 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;

View File

@ -19,10 +19,10 @@ pub struct MockBlockSettlementClient {
}
impl BlockSettlementClientTrait for MockBlockSettlementClient {
fn new(config: &BedrockConfig, bedrock_signing_key: Ed25519Key) -> Result<Self> {
fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result<Self> {
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<Self> {
fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result<Self> {
Ok(Self {
bedrock_channel_id: config.channel_id,
bedrock_signing_key,
bedrock_signing_key: signing_key,
})
}

View File

@ -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<MockBlockSettlementClient, MockIndexerClient>;
// 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<MockBlockSettlementClient, MockIndexerClient>;

Some files were not shown because too many files have changed in this diff Show More