feat: add pedantic clippy lints

This commit is contained in:
Daniil Polyakov 2026-03-03 23:21:08 +03:00
parent 756f2f4135
commit efe8393ba0
145 changed files with 1549 additions and 1187 deletions

View File

@ -146,4 +146,16 @@ codegen-units = 1
warnings = "deny"
[workspace.lints]
clippy.all = "deny"
clippy.all = { level = "deny", priority = -1 }
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.
clippy.missing-errors-doc = "allow"
# Reason: most of the panics are internal and not part of the public API, so documenting them is not necessary.
clippy.missing-panics-doc = "allow"
# Reason: this isn't always bad and actually works well for our financial and cryptography code.
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
clippy.implicit-hasher = "allow"

View File

@ -96,9 +96,14 @@ impl BedrockClient {
}
fn backoff_strategy(&self) -> impl Iterator<Item = Duration> {
tokio_retry::strategy::FibonacciBackoff::from_millis(
self.backoff.start_delay.as_millis() as u64
)
.take(self.backoff.max_retries)
let start_delay_millis = self
.backoff
.start_delay
.as_millis()
.try_into()
.expect("Start delay must be less than u64::MAX milliseconds");
tokio_retry::strategy::FibonacciBackoff::from_millis(start_delay_millis)
.take(self.backoff.max_retries)
}
}

View File

@ -69,6 +69,7 @@ pub struct HashableBlockData {
}
impl HashableBlockData {
#[must_use]
pub fn into_pending_block(
self,
signing_key: &nssa::PrivateKey,
@ -93,6 +94,7 @@ impl HashableBlockData {
}
}
#[must_use]
pub fn block_hash(&self) -> BlockHash {
OwnHasher::hash(&borsh::to_vec(&self).unwrap())
}

View File

@ -43,7 +43,7 @@ impl FromStr for BasicAuth {
Ok(Self {
username: username.to_string(),
password: password.map(|p| p.to_string()),
password: password.map(std::string::ToString::to_string),
})
}
}

View File

@ -49,6 +49,7 @@ impl RpcError {
/// A generic constructor.
///
/// Mostly for completeness, doesn't do anything but filling in the corresponding fields.
#[must_use]
pub fn new(code: i64, message: String, data: Option<Value>) -> Self {
RpcError {
code,
@ -82,6 +83,7 @@ impl RpcError {
}
/// Create a parse error.
#[must_use]
pub fn parse_error(e: String) -> Self {
RpcError {
code: -32_700,
@ -93,12 +95,14 @@ impl RpcError {
}
}
#[must_use]
pub fn serialization_error(e: &str) -> Self {
RpcError::new_internal_error(Some(Value::String(e.to_owned())), e)
}
/// Helper method to define extract `INTERNAL_ERROR` in separate `RpcErrorKind`
/// Returns `HANDLER_ERROR` if the error is not internal one
#[must_use]
pub fn new_internal_or_handler_error(error_data: Option<Value>, error_struct: Value) -> Self {
if error_struct["name"] == "INTERNAL_ERROR" {
let error_message = match error_struct["info"].get("error_message") {
@ -111,6 +115,7 @@ impl RpcError {
}
}
#[must_use]
pub fn new_internal_error(error_data: Option<Value>, info: &str) -> Self {
RpcError {
code: -32_000,
@ -133,6 +138,7 @@ impl RpcError {
}
/// Create a method not found error.
#[must_use]
pub fn method_not_found(method: String) -> Self {
RpcError {
code: -32_601,

View File

@ -32,7 +32,7 @@ impl<'de> serde::Deserialize<'de> for Version {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct VersionVisitor;
#[allow(clippy::needless_lifetimes)]
impl<'de> Visitor<'de> for VersionVisitor {
impl Visitor<'_> for VersionVisitor {
type Value = Version;
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
@ -62,6 +62,7 @@ pub struct Request {
}
impl Request {
#[must_use]
pub fn from_payload_version_2_0(method: String, payload: serde_json::Value) -> Self {
Self {
jsonrpc: Version,
@ -75,6 +76,7 @@ impl Request {
/// Answer the request with a (positive) reply.
///
/// The ID is taken from the request.
#[must_use]
pub fn reply(&self, reply: Value) -> Message {
Message::Response(Response {
jsonrpc: Version,
@ -84,6 +86,7 @@ impl Request {
}
/// Answer the request with an error.
#[must_use]
pub fn error(&self, error: RpcError) -> Message {
Message::Response(Response {
jsonrpc: Version,
@ -212,6 +215,7 @@ impl Message {
/// A constructor for a request.
///
/// The ID is auto-set to dontcare.
#[must_use]
pub fn request(method: String, params: Value) -> Self {
let id = Value::from("dontcare");
Message::Request(Request {
@ -223,6 +227,7 @@ impl Message {
}
/// Create a top-level error (without an ID).
#[must_use]
pub fn error(error: RpcError) -> Self {
Message::Response(Response {
jsonrpc: Version,
@ -232,6 +237,7 @@ impl Message {
}
/// A constructor for a notification.
#[must_use]
pub fn notification(method: String, params: Value) -> Self {
Message::Notification(Notification {
jsonrpc: Version,
@ -241,6 +247,7 @@ impl Message {
}
/// A constructor for a response.
#[must_use]
pub fn response(id: Value, result: Result<Value, RpcError>) -> Self {
Message::Response(Response {
jsonrpc: Version,
@ -250,6 +257,7 @@ impl Message {
}
/// Returns id or Null if there is no id.
#[must_use]
pub fn id(&self) -> Value {
match self {
Message::Request(req) => req.id.clone(),
@ -276,6 +284,7 @@ impl Broken {
///
/// The error message for these things are specified in the RFC, so this just creates an error
/// with the right values.
#[must_use]
pub fn reply(&self) -> Message {
match *self {
Broken::Unmatched(_) => Message::error(RpcError::parse_error(
@ -343,7 +352,6 @@ mod tests {
/// But since serialization doesn't have to produce the exact same result (order, spaces, …),
/// we then serialize and deserialize the thing again and check it matches.
#[test]
#[allow(clippy::too_many_lines)]
fn message_serde() {
// A helper for running one message test
fn one(input: &str, expected: &Message) {
@ -491,10 +499,10 @@ mod tests {
// Something completely different
one(r#"{"x": [1, 2, 3]}"#);
match from_str(r#"{]"#) {
match from_str(r"{]") {
Err(Broken::SyntaxError(_)) => (),
other => panic!("Something unexpected: {other:?}"),
};
}
}
/// Test some non-trivial aspects of the constructors
@ -503,7 +511,7 @@ mod tests {
/// Most of it is related to the ids.
#[test]
#[allow(clippy::panic)]
#[ignore]
#[ignore = "Not a full coverage test"]
fn constructors() {
let msg1 = Message::request("call".to_owned(), json!([1, 2, 3]));
let msg2 = Message::request("call".to_owned(), json!([1, 2, 3]));

View File

@ -39,6 +39,7 @@ impl Default for RpcConfig {
}
impl RpcConfig {
#[must_use]
pub fn new(addr: &str) -> Self {
RpcConfig {
addr: addr.to_owned(),
@ -46,6 +47,7 @@ impl RpcConfig {
}
}
#[must_use]
pub fn with_port(port: u16) -> Self {
RpcConfig {
addr: format!("0.0.0.0:{port}"),

View File

@ -30,6 +30,14 @@ use crate::{
transaction::NSSATransaction,
};
#[derive(Debug, Clone, Deserialize)]
#[allow(dead_code)]
struct SequencerRpcResponse {
jsonrpc: String,
result: serde_json::Value,
id: u64,
}
#[derive(Clone)]
pub struct SequencerClient {
pub client: reqwest::Client,
@ -86,14 +94,6 @@ impl SequencerClient {
})
.await?;
#[derive(Debug, Clone, Deserialize)]
#[allow(dead_code)]
pub struct SequencerRpcResponse {
pub jsonrpc: String,
pub result: serde_json::Value,
pub id: u64,
}
if let Ok(response) = serde_json::from_value::<SequencerRpcResponse>(response_vall.clone())
{
Ok(response.result)

View File

@ -8,6 +8,7 @@ use crate::{
// Helpers
#[must_use]
pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([37; 32]).unwrap()
}
@ -21,6 +22,7 @@ pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
/// `prev_hash` - hash of previous block, provide None for genesis
///
/// `transactions` - vector of `EncodedTransaction` objects
#[must_use]
pub fn produce_dummy_block(
id: u64,
prev_hash: Option<HashType>,
@ -36,6 +38,7 @@ pub fn produce_dummy_block(
block_data.into_pending_block(&sequencer_sign_key_for_testing(), [0; 32])
}
#[must_use]
pub fn produce_dummy_empty_transaction() -> NSSATransaction {
let program_id = nssa::program::Program::authenticated_transfer_program().id();
let account_ids = vec![];
@ -56,12 +59,13 @@ pub fn produce_dummy_empty_transaction() -> NSSATransaction {
NSSATransaction::Public(nssa_tx)
}
#[must_use]
pub fn create_transaction_native_token_transfer(
from: AccountId,
nonce: u128,
to: AccountId,
balance_to_move: u128,
signing_key: nssa::PrivateKey,
signing_key: &nssa::PrivateKey,
) -> NSSATransaction {
let account_ids = vec![from, to];
let nonces = vec![nonce];
@ -73,7 +77,7 @@ pub fn create_transaction_native_token_transfer(
balance_to_move,
)
.unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]);
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);

View File

@ -13,6 +13,7 @@ pub enum NSSATransaction {
}
impl NSSATransaction {
#[must_use]
pub fn hash(&self) -> HashType {
HashType(match self {
NSSATransaction::Public(tx) => tx.hash(),
@ -21,6 +22,7 @@ impl NSSATransaction {
})
}
#[must_use]
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
match self {
NSSATransaction::ProgramDeployment(tx) => tx.affected_public_account_ids(),

View File

@ -36,9 +36,7 @@ fn main() {
// Fail if the input account is not authorized
// The `is_authorized` field will be correctly populated or verified by the system if
// authorization is provided.
if !pre_state.is_authorized {
panic!("Missing required authorization");
}
assert!(pre_state.is_authorized, "Missing required authorization");
// ####
// Construct the post state account values

View File

@ -1,5 +1,5 @@
use nssa_core::{
account::{Account, AccountWithMetadata},
account::{Account, AccountWithMetadata, Data},
program::{
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
},
@ -35,12 +35,12 @@ fn build_post_state(post_account: Account) -> AccountPostState {
}
}
fn write(pre_state: AccountWithMetadata, greeting: Vec<u8>) -> AccountPostState {
fn write(pre_state: AccountWithMetadata, greeting: &[u8]) -> AccountPostState {
// Construct the post state account values
let post_account = {
let mut this = pre_state.account.clone();
let mut this = pre_state.account;
let mut bytes = this.data.into_inner();
bytes.extend_from_slice(&greeting);
bytes.extend_from_slice(greeting);
this.data = bytes
.try_into()
.expect("Data should fit within the allowed limits");
@ -50,21 +50,18 @@ fn write(pre_state: AccountWithMetadata, greeting: Vec<u8>) -> AccountPostState
build_post_state(post_account)
}
fn move_data(
from_pre: &AccountWithMetadata,
to_pre: &AccountWithMetadata,
) -> Vec<AccountPostState> {
fn move_data(from_pre: AccountWithMetadata, to_pre: AccountWithMetadata) -> Vec<AccountPostState> {
// Construct the post state account values
let from_data: Vec<u8> = from_pre.account.data.clone().into();
let from_post = {
let mut this = from_pre.account.clone();
this.data = Default::default();
let mut this = from_pre.account;
this.data = Data::default();
build_post_state(this)
};
let to_post = {
let mut this = to_pre.account.clone();
let mut this = to_pre.account;
let mut bytes = this.data.into_inner();
bytes.extend_from_slice(&from_data);
this.data = bytes
@ -88,11 +85,11 @@ fn main() {
let post_states = match (pre_states.as_slice(), function_id, data.len()) {
([account_pre], WRITE_FUNCTION_ID, _) => {
let post = write(account_pre.clone(), data);
let post = write(account_pre.clone(), &data);
vec![post]
}
([account_from_pre, account_to_pre], MOVE_DATA_FUNCTION_ID, 0) => {
move_data(account_from_pre, account_to_pre)
move_data(account_from_pre.clone(), account_to_pre.clone())
}
_ => panic!("invalid params"),
};

View File

@ -29,7 +29,7 @@ fn main() {
let (
ProgramInput {
pre_states,
instruction: _,
instruction: (),
},
instruction_data,
) = read_nssa_inputs::<()>();

View File

@ -34,7 +34,7 @@ fn main() {
let (
ProgramInput {
pre_states,
instruction: _,
instruction: (),
},
instruction_data,
) = read_nssa_inputs::<()>();

View File

@ -148,5 +148,5 @@ async fn main() {
.await
.unwrap();
}
};
}
}

View File

@ -22,7 +22,7 @@ pub async fn get_account(account_id: AccountId) -> Result<Account, ServerFnError
client
.get_account(account_id)
.await
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
}
/// Search for a block, transaction, or account by query string
@ -80,7 +80,7 @@ pub async fn get_block_by_id(block_id: BlockId) -> Result<Block, ServerFnError>
client
.get_block_by_id(block_id)
.await
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
}
/// Get latest block ID
@ -91,7 +91,7 @@ pub async fn get_latest_block_id() -> Result<BlockId, ServerFnError> {
client
.get_last_finalized_block_id()
.await
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
}
/// Get block by hash
@ -102,7 +102,7 @@ pub async fn get_block_by_hash(block_hash: HashType) -> Result<Block, ServerFnEr
client
.get_block_by_hash(block_hash)
.await
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
}
/// Get transaction by hash
@ -113,33 +113,33 @@ pub async fn get_transaction(tx_hash: HashType) -> Result<Transaction, ServerFnE
client
.get_transaction(tx_hash)
.await
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
}
/// Get blocks with pagination
#[server]
pub async fn get_blocks(before: Option<u64>, limit: u32) -> Result<Vec<Block>, ServerFnError> {
pub async fn get_blocks(before: Option<BlockId>, limit: u64) -> Result<Vec<Block>, ServerFnError> {
use indexer_service_rpc::RpcClient as _;
let client = expect_context::<IndexerRpcClient>();
client
.get_blocks(before, limit)
.await
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
}
/// Get transactions by account
#[server]
pub async fn get_transactions_by_account(
account_id: AccountId,
limit: u32,
offset: u32,
offset: u64,
limit: u64,
) -> Result<Vec<Transaction>, ServerFnError> {
use indexer_service_rpc::RpcClient as _;
let client = expect_context::<IndexerRpcClient>();
client
.get_transactions_by_account(account_id, limit, offset)
.get_transactions_by_account(account_id, offset, limit)
.await
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {e}")))
}
/// Create the RPC client for the indexer service (server-side only)

View File

@ -13,6 +13,10 @@ fn transaction_type_info(tx: &Transaction) -> (&'static str, &'static str) {
/// Transaction preview component
#[component]
#[expect(
clippy::needless_pass_by_value,
reason = "Leptos component props are passed by value by framework convention"
)]
pub fn TransactionPreview(transaction: Transaction) -> impl IntoView {
let hash = transaction.hash();
let hash_str = hash.to_string();

View File

@ -3,7 +3,10 @@
/// Format timestamp to human-readable string
pub fn format_timestamp(timestamp: u64) -> String {
let seconds = timestamp / 1000;
let datetime = chrono::DateTime::from_timestamp(seconds as i64, 0)
.unwrap_or_else(|| chrono::DateTime::from_timestamp(0, 0).unwrap());
let datetime = chrono::DateTime::from_timestamp(
i64::try_from(seconds).expect("Timestamp out of range"),
0,
)
.unwrap_or_else(|| chrono::DateTime::from_timestamp(0, 0).unwrap());
datetime.format("%Y-%m-%d %H:%M:%S UTC").to_string()
}

View File

@ -1,3 +1,8 @@
#![expect(
clippy::must_use_candidate,
reason = "Warns on code generated by leptos macros"
)]
use leptos::prelude::*;
use leptos_meta::{Meta, Stylesheet, Title, provide_meta_context};
use leptos_router::{

View File

@ -8,8 +8,6 @@ async fn main() {
use leptos_axum::{LeptosRoutes, generate_route_list};
use leptos_meta::MetaTags;
env_logger::init();
/// LEZ Block Explorer Server CLI arguments.
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
@ -19,6 +17,8 @@ async fn main() {
indexer_rpc_url: url::Url,
}
env_logger::init();
let args = Args::parse();
let conf = get_configuration(None).unwrap();

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(0u32);
let (tx_offset, set_tx_offset) = signal(0u64);
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 = 10u32;
let tx_limit = 10u64;
// Parse account ID from URL params
let account_id = move || {
@ -35,7 +35,7 @@ pub fn AccountPage() -> impl IntoView {
// Load initial transactions
let transactions_resource = Resource::new(account_id, move |acc_id_opt| async move {
match acc_id_opt {
Some(acc_id) => api::get_transactions_by_account(acc_id, tx_limit, 0).await,
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(),
)),
@ -46,7 +46,9 @@ pub fn AccountPage() -> impl IntoView {
Effect::new(move || {
if let Some(Ok(txs)) = transactions_resource.get() {
set_all_transactions.set(txs.clone());
set_has_more.set(txs.len() as u32 == tx_limit);
set_has_more.set(
u64::try_from(txs.len()).expect("Transaction count should fit in u64") == tx_limit,
);
}
});
@ -61,14 +63,15 @@ pub fn AccountPage() -> impl IntoView {
set_tx_offset.set(current_offset);
leptos::task::spawn_local(async move {
match api::get_transactions_by_account(acc_id, tx_limit, current_offset).await {
match api::get_transactions_by_account(acc_id, current_offset, tx_limit).await {
Ok(new_txs) => {
let txs_count = new_txs.len() as u32;
let txs_count =
u64::try_from(new_txs.len()).expect("Transaction count should fit in u64");
set_all_transactions.update(|txs| txs.extend(new_txs));
set_has_more.set(txs_count == tx_limit);
}
Err(e) => {
log::error!("Failed to load more transactions: {}", e);
log::error!("Failed to load more transactions: {e}");
}
}
set_is_loading.set(false);
@ -123,7 +126,7 @@ pub fn AccountPage() -> impl IntoView {
</div>
<div class="info-row">
<span class="info-label">"Data:"</span>
<span class="info-value">{format!("{} bytes", data_len)}</span>
<span class="info-value">{format!("{data_len} bytes")}</span>
</div>
</div>
</div>
@ -190,7 +193,7 @@ pub fn AccountPage() -> impl IntoView {
Err(e) => {
view! {
<div class="error">
{format!("Failed to load transactions: {}", e)}
{format!("Failed to load transactions: {e}")}
</div>
}
.into_any()
@ -208,7 +211,7 @@ pub fn AccountPage() -> impl IntoView {
view! {
<div class="error-page">
<h1>"Error"</h1>
<p>{format!("Failed to load account: {}", e)}</p>
<p>{format!("Failed to load account: {e}")}</p>
</div>
}
.into_any()

View File

@ -144,7 +144,7 @@ pub fn BlockPage() -> impl IntoView {
view! {
<div class="error-page">
<h1>"Error"</h1>
<p>{format!("Failed to load block: {}", e)}</p>
<p>{format!("Failed to load block: {e}")}</p>
</div>
}
.into_any()

View File

@ -1,5 +1,8 @@
use leptos::prelude::*;
use leptos_router::hooks::{use_navigate, use_query_map};
use leptos_router::{
NavigateOptions,
hooks::{use_navigate, use_query_map},
};
use web_sys::SubmitEvent;
use crate::{
@ -7,7 +10,7 @@ use crate::{
components::{AccountPreview, BlockPreview, TransactionPreview},
};
const RECENT_BLOCKS_LIMIT: u32 = 10;
const RECENT_BLOCKS_LIMIT: u64 = 10;
/// Main page component
#[component]
@ -33,7 +36,7 @@ pub fn MainPage() -> impl IntoView {
match api::search(query).await {
Ok(result) => Some(result),
Err(e) => {
log::error!("Search error: {}", e);
log::error!("Search error: {e}");
None
}
}
@ -48,7 +51,7 @@ pub fn MainPage() -> impl IntoView {
// Load recent blocks on mount
let recent_blocks_resource = Resource::new(
|| (),
|_| async { api::get_blocks(None, RECENT_BLOCKS_LIMIT).await },
|()| async { api::get_blocks(None, RECENT_BLOCKS_LIMIT).await },
);
// Update all_blocks when initial load completes
@ -57,8 +60,11 @@ pub fn MainPage() -> impl IntoView {
let oldest_id = blocks.last().map(|b| b.header.block_id);
set_all_blocks.set(blocks.clone());
set_oldest_loaded_block_id.set(oldest_id);
set_has_more_blocks
.set(blocks.len() as u32 == RECENT_BLOCKS_LIMIT && oldest_id.unwrap_or(0) > 1);
set_has_more_blocks.set(
u64::try_from(blocks.len()).expect("usize should fit in u64")
== RECENT_BLOCKS_LIMIT
&& oldest_id.unwrap_or(0) > 1,
);
}
});
@ -75,7 +81,8 @@ pub fn MainPage() -> impl IntoView {
leptos::task::spawn_local(async move {
match api::get_blocks(before_id, RECENT_BLOCKS_LIMIT).await {
Ok(new_blocks) => {
let blocks_count = new_blocks.len() as u32;
let blocks_count =
u64::try_from(new_blocks.len()).expect("usize should fit in u64");
let new_oldest_id = new_blocks.last().map(|b| b.header.block_id);
set_all_blocks.update(|blocks| blocks.extend(new_blocks));
set_oldest_loaded_block_id.set(new_oldest_id);
@ -83,7 +90,7 @@ pub fn MainPage() -> impl IntoView {
.set(blocks_count == RECENT_BLOCKS_LIMIT && new_oldest_id.unwrap_or(0) > 1);
}
Err(e) => {
log::error!("Failed to load more blocks: {}", e);
log::error!("Failed to load more blocks: {e}");
}
}
set_is_loading_blocks.set(false);
@ -95,13 +102,13 @@ pub fn MainPage() -> impl IntoView {
ev.prevent_default();
let query = search_query.get();
if query.is_empty() {
navigate("?", Default::default());
navigate("?", NavigateOptions::default());
return;
}
navigate(
&format!("?q={}", urlencoding::encode(&query)),
Default::default(),
NavigateOptions::default(),
);
};
@ -142,78 +149,78 @@ pub fn MainPage() -> impl IntoView {
view! {
<div class="search-results">
<h2>"Search Results"</h2>
{if !has_results {
view! { <div class="not-found">"No results found"</div> }
.into_any()
} else {
view! {
<div class="results-container">
{if !blocks.is_empty() {
view! {
<div class="results-section">
<h3>"Blocks"</h3>
<div class="results-list">
{blocks
.into_iter()
.map(|block| {
view! { <BlockPreview block=block /> }
})
.collect::<Vec<_>>()}
{if has_results {
view! {
<div class="results-container">
{if blocks.is_empty() {
().into_any()
} else {
view! {
<div class="results-section">
<h3>"Blocks"</h3>
<div class="results-list">
{blocks
.into_iter()
.map(|block| {
view! { <BlockPreview block=block /> }
})
.collect::<Vec<_>>()}
</div>
</div>
</div>
}
.into_any()
} else {
().into_any()
}}
}
.into_any()
}}
{if !transactions.is_empty() {
view! {
<div class="results-section">
<h3>"Transactions"</h3>
<div class="results-list">
{transactions
.into_iter()
.map(|tx| {
view! { <TransactionPreview transaction=tx /> }
})
.collect::<Vec<_>>()}
{if transactions.is_empty() {
().into_any()
} else {
view! {
<div class="results-section">
<h3>"Transactions"</h3>
<div class="results-list">
{transactions
.into_iter()
.map(|tx| {
view! { <TransactionPreview transaction=tx /> }
})
.collect::<Vec<_>>()}
</div>
</div>
</div>
}
.into_any()
} else {
().into_any()
}}
}
.into_any()
}}
{if !accounts.is_empty() {
view! {
<div class="results-section">
<h3>"Accounts"</h3>
<div class="results-list">
{accounts
.into_iter()
.map(|(id, account)| {
view! {
<AccountPreview
account_id=id
account=account
/>
}
})
.collect::<Vec<_>>()}
{if accounts.is_empty() {
().into_any()
} else {
view! {
<div class="results-section">
<h3>"Accounts"</h3>
<div class="results-list">
{accounts
.into_iter()
.map(|(id, account)| {
view! {
<AccountPreview
account_id=id
account=account
/>
}
})
.collect::<Vec<_>>()}
</div>
</div>
</div>
}
.into_any()
} else {
().into_any()
}}
}
.into_any()
}}
</div>
}
.into_any()
}}
</div>
}
.into_any()
} else {
view! { <div class="not-found">"No results found"</div> }
.into_any()
}}
</div>
}
.into_any()
@ -274,7 +281,7 @@ pub fn MainPage() -> impl IntoView {
}
}
Err(e) => {
view! { <div class="error">{format!("Error: {}", e)}</div> }
view! { <div class="error">{format!("Error: {e}")}</div> }
.into_any()
}
})

View File

@ -105,7 +105,7 @@ pub fn TransactionPage() -> impl IntoView {
</div>
<div class="info-row">
<span class="info-label">"Proof Size:"</span>
<span class="info-value">{format!("{} bytes", proof_len)}</span>
<span class="info-value">{format!("{proof_len} bytes")}</span>
</div>
<div class="info-row">
<span class="info-label">"Signatures:"</span>
@ -212,7 +212,7 @@ pub fn TransactionPage() -> impl IntoView {
</div>
<div class="info-row">
<span class="info-label">"Proof Size:"</span>
<span class="info-value">{format!("{} bytes", proof_len)}</span>
<span class="info-value">{format!("{proof_len} bytes")}</span>
</div>
</div>
@ -284,7 +284,7 @@ pub fn TransactionPage() -> impl IntoView {
<div class="info-row">
<span class="info-label">"Bytecode Size:"</span>
<span class="info-value">
{format!("{} bytes", bytecode_len)}
{format!("{bytecode_len} bytes")}
</span>
</div>
</div>
@ -302,7 +302,7 @@ pub fn TransactionPage() -> impl IntoView {
view! {
<div class="error-page">
<h1>"Error"</h1>
<p>{format!("Failed to load transaction: {}", e)}</p>
<p>{format!("Failed to load transaction: {e}")}</p>
</div>
}
.into_any()

View File

@ -3,7 +3,7 @@ use std::{path::Path, sync::Arc};
use anyhow::Result;
use bedrock_client::HeaderId;
use common::{
block::{BedrockStatus, Block},
block::{BedrockStatus, Block, BlockId},
transaction::NSSATransaction,
};
use nssa::{Account, AccountId, V02State};
@ -50,7 +50,7 @@ impl IndexerStore {
Ok(self.dbio.get_block(id)?)
}
pub fn get_block_batch(&self, before: Option<u64>, limit: u64) -> Result<Vec<Block>> {
pub fn get_block_batch(&self, before: Option<BlockId>, limit: u64) -> Result<Vec<Block>> {
Ok(self.dbio.get_block_batch(before, limit)?)
}
@ -79,12 +79,14 @@ impl IndexerStore {
Ok(self.dbio.get_acc_transactions(acc_id, offset, limit)?)
}
#[must_use]
pub fn genesis_id(&self) -> u64 {
self.dbio
.get_meta_first_block_in_db()
.expect("Must be set at the DB startup")
}
#[must_use]
pub fn last_block(&self) -> u64 {
self.dbio
.get_meta_last_block_in_db()

View File

@ -43,11 +43,16 @@ pub struct IndexerConfig {
impl IndexerConfig {
pub fn from_path(config_path: &Path) -> Result<IndexerConfig> {
let file = File::open(config_path)
.with_context(|| format!("Failed to open indexer config at {config_path:?}"))?;
let file = File::open(config_path).with_context(|| {
format!("Failed to open indexer config at {}", config_path.display())
})?;
let reader = BufReader::new(file);
serde_json::from_reader(reader)
.with_context(|| format!("Failed to parse indexer config at {config_path:?}"))
serde_json::from_reader(reader).with_context(|| {
format!(
"Failed to parse indexer config at {}",
config_path.display()
)
})
}
}

View File

@ -98,43 +98,40 @@ impl IndexerCore {
})
}
pub async fn subscribe_parse_block_stream(&self) -> impl futures::Stream<Item = Result<Block>> {
pub fn subscribe_parse_block_stream(&self) -> impl futures::Stream<Item = Result<Block>> {
async_stream::stream! {
info!("Searching for initial header");
let last_l1_lib_header = self.store.last_observed_l1_lib_header()?;
let mut prev_last_l1_lib_header = match last_l1_lib_header {
Some(last_l1_lib_header) => {
info!("Last l1 lib header found: {last_l1_lib_header}");
last_l1_lib_header
},
None => {
info!("Last l1 lib header not found in DB");
info!("Searching for the start of a channel");
let mut prev_last_l1_lib_header = if let Some(last_l1_lib_header) = last_l1_lib_header {
info!("Last l1 lib header found: {last_l1_lib_header}");
last_l1_lib_header
} else {
info!("Last l1 lib header not found in DB");
info!("Searching for the start of a channel");
let BackfillData {
block_data: start_buff,
curr_fin_l1_lib_header: last_l1_lib_header,
} = self.search_for_channel_start().await?;
let BackfillData {
block_data: start_buff,
curr_fin_l1_lib_header: last_l1_lib_header,
} = self.search_for_channel_start().await?;
for BackfillBlockData {
l2_blocks: l2_block_vec,
l1_header,
} in start_buff {
let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect();
l2_blocks_parsed_ids.sort();
info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids);
for BackfillBlockData {
l2_blocks: l2_block_vec,
l1_header,
} in start_buff {
let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect();
l2_blocks_parsed_ids.sort_unstable();
info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids);
for l2_block in l2_block_vec {
self.store.put_block(l2_block.clone(), l1_header)?;
for l2_block in l2_block_vec {
self.store.put_block(l2_block.clone(), l1_header)?;
yield Ok(l2_block);
}
yield Ok(l2_block);
}
}
last_l1_lib_header
},
last_l1_lib_header
};
info!("Searching for initial header finished");
@ -157,7 +154,7 @@ impl IndexerCore {
l1_header: header,
} in buff {
let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect();
l2_blocks_parsed_ids.sort();
l2_blocks_parsed_ids.sort_unstable();
info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids);
for l2_block in l2_block_vec {
@ -177,14 +174,14 @@ impl IndexerCore {
async fn get_next_lib(&self, prev_lib: HeaderId) -> Result<HeaderId> {
loop {
let next_lib = self.get_lib().await?;
if next_lib != prev_lib {
break Ok(next_lib);
} else {
if next_lib == prev_lib {
info!(
"Wait {:?} to not spam the node",
self.config.consensus_info_polling_interval
);
tokio::time::sleep(self.config.consensus_info_polling_interval).await;
} else {
break Ok(next_lib);
}
}
}
@ -204,15 +201,13 @@ impl IndexerCore {
let mut cycle_header = curr_last_l1_lib_header;
loop {
let cycle_block =
if let Some(block) = self.bedrock_client.get_block_by_id(cycle_header).await? {
block
} else {
// First run can reach root easily
// so here we are optimistic about L1
// failing to get parent.
break;
};
let Some(cycle_block) = self.bedrock_client.get_block_by_id(cycle_header).await?
else {
// First run can reach root easily
// so here we are optimistic about L1
// failing to get parent.
break;
};
// It would be better to have id, but block does not have it, so slot will do.
info!(
@ -289,10 +284,9 @@ impl IndexerCore {
if cycle_block.header().id() == last_fin_l1_lib_header {
break;
} else {
// Step back to parent
cycle_header = cycle_block.header().parent();
}
// Step back to parent
cycle_header = cycle_block.header().parent();
// It would be better to have id, but block does not have it, so slot will do.
info!(
@ -335,7 +329,7 @@ fn parse_block_owned(
}) if channel_id == decoded_channel_id => {
borsh::from_slice::<Block>(inscription)
.inspect_err(|err| {
error!("Failed to deserialize our inscription with err: {err:#?}")
error!("Failed to deserialize our inscription with err: {err:#?}");
})
.ok()
}

View File

@ -1,6 +1,12 @@
//! Conversions between indexer_service_protocol types and nssa/nssa_core types
//! Conversions between `indexer_service_protocol` types and `nssa/nssa_core` types
use crate::*;
use crate::{
Account, AccountId, BedrockStatus, Block, BlockBody, BlockHeader, Ciphertext, Commitment,
CommitmentSetDigest, Data, EncryptedAccountData, EphemeralPublicKey, HashType, MantleMsgId,
Nullifier, PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage,
ProgramDeploymentTransaction, ProgramId, Proof, PublicKey, PublicMessage, PublicTransaction,
Signature, Transaction, WitnessSet,
};
// ============================================================================
// Account-related conversions

View File

@ -141,6 +141,7 @@ pub enum Transaction {
impl Transaction {
/// Get the hash of the transaction
#[must_use]
pub fn hash(&self) -> &self::HashType {
match self {
Transaction::Public(tx) => &tx.hash,
@ -308,7 +309,7 @@ mod base64 {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub mod arr {
use super::*;
use super::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(v: &[u8], s: S) -> Result<S::Ok, S::Error> {
super::serialize(v, s)

View File

@ -44,16 +44,16 @@ pub trait Rpc {
#[method(name = "getBlocks")]
async fn get_blocks(
&self,
before: Option<u64>,
limit: u32,
before: Option<BlockId>,
limit: u64,
) -> Result<Vec<Block>, ErrorObjectOwned>;
#[method(name = "getTransactionsByAccount")]
async fn get_transactions_by_account(
&self,
account_id: AccountId,
limit: u32,
offset: u32,
offset: u64,
limit: u64,
) -> Result<Vec<Transaction>, ErrorObjectOwned>;
// ToDo: expand healthcheck response into some kind of report

View File

@ -23,6 +23,7 @@ impl IndexerHandle {
}
}
#[must_use]
pub fn addr(&self) -> SocketAddr {
self.addr
}
@ -33,9 +34,14 @@ impl IndexerHandle {
.take()
.expect("Indexer server handle is set");
handle.stopped().await
handle.stopped().await;
}
#[expect(
clippy::redundant_closure_for_method_calls,
reason = "Clippy suggested path jsonrpsee::jsonrpsee_server::ServerHandle is not accessible"
)]
#[must_use]
pub fn is_stopped(&self) -> bool {
self.server_handle
.as_ref()

View File

@ -26,10 +26,10 @@ async fn main() -> Result<()> {
let indexer_handle = indexer_service::run_server(config, port).await?;
tokio::select! {
_ = cancellation_token.cancelled() => {
() = cancellation_token.cancelled() => {
info!("Shutting down server...");
}
_ = indexer_handle.stopped() => {
() = indexer_handle.stopped() => {
error!("Server stopped unexpectedly");
}
}

View File

@ -1,3 +1,8 @@
#![expect(
clippy::cast_possible_truncation,
clippy::cast_lossless,
reason = "Mock service uses intentional casts and format patterns for test data generation"
)]
use std::collections::HashMap;
use indexer_service_protocol::{
@ -9,7 +14,7 @@ use indexer_service_protocol::{
};
use jsonrpsee::{core::SubscriptionResult, types::ErrorObjectOwned};
/// A mock implementation of the IndexerService RPC for testing purposes.
/// A mock implementation of the `IndexerService` RPC for testing purposes.
pub struct MockIndexerService {
blocks: Vec<Block>,
accounts: HashMap<AccountId, Account>,
@ -17,6 +22,7 @@ pub struct MockIndexerService {
}
impl MockIndexerService {
#[must_use]
pub fn new_with_mock_blocks() -> Self {
let mut blocks = Vec::new();
let mut accounts = HashMap::new();
@ -136,7 +142,7 @@ impl MockIndexerService {
block_id,
prev_block_hash: prev_hash,
hash: block_hash,
timestamp: 1704067200000 + (block_id * 12000), // ~12 seconds per block
timestamp: 1_704_067_200_000 + (block_id * 12_000), // ~12 seconds per block
signature: Signature([0u8; 64]),
},
body: BlockBody {
@ -197,7 +203,7 @@ impl indexer_service_rpc::RpcServer for MockIndexerService {
.ok_or_else(|| {
ErrorObjectOwned::owned(
-32001,
format!("Block with ID {} not found", block_id),
format!("Block with ID {block_id} not found"),
None::<()>,
)
})
@ -227,15 +233,18 @@ impl indexer_service_rpc::RpcServer for MockIndexerService {
async fn get_blocks(
&self,
before: Option<u64>,
limit: u32,
before: Option<BlockId>,
limit: u64,
) -> Result<Vec<Block>, ErrorObjectOwned> {
let start_id = before.map_or_else(|| self.blocks.len() as u64, |id| id.saturating_sub(1));
let start_id = before.map_or_else(
|| self.blocks.len(),
|id| usize::try_from(id.saturating_sub(1)).expect("u64 should fit in usize"),
);
let result = (1..=start_id)
.rev()
.take(limit as usize)
.map_while(|block_id| self.blocks.get(block_id as usize - 1).cloned())
.map_while(|block_id| self.blocks.get(block_id - 1).cloned())
.collect();
Ok(result)
@ -244,8 +253,8 @@ impl indexer_service_rpc::RpcServer for MockIndexerService {
async fn get_transactions_by_account(
&self,
account_id: AccountId,
limit: u32,
offset: u32,
offset: u64,
limit: u64,
) -> Result<Vec<Transaction>, ErrorObjectOwned> {
let mut account_txs: Vec<_> = self
.transactions

View File

@ -90,19 +90,19 @@ impl indexer_service_rpc::RpcServer for IndexerService {
async fn get_blocks(
&self,
before: Option<u64>,
limit: u32,
before: Option<BlockId>,
limit: u64,
) -> Result<Vec<Block>, ErrorObjectOwned> {
let blocks = self
.indexer
.store
.get_block_batch(before, limit as u64)
.get_block_batch(before, limit)
.map_err(db_error)?;
let mut block_res = vec![];
for block in blocks {
block_res.push(block.into())
block_res.push(block.into());
}
Ok(block_res)
@ -111,19 +111,19 @@ impl indexer_service_rpc::RpcServer for IndexerService {
async fn get_transactions_by_account(
&self,
account_id: AccountId,
limit: u32,
offset: u32,
offset: u64,
limit: u64,
) -> Result<Vec<Transaction>, ErrorObjectOwned> {
let transactions = self
.indexer
.store
.get_transactions_by_account(account_id.value, offset as u64, limit as u64)
.get_transactions_by_account(account_id.value, offset, limit)
.map_err(db_error)?;
let mut tx_res = vec![];
for tx in transactions {
tx_res.push(tx.into())
tx_res.push(tx.into());
}
Ok(tx_res)
@ -177,8 +177,8 @@ impl SubscriptionService {
}
}
bail!(err);
};
bail!(err)
}
Ok(())
}
@ -190,7 +190,7 @@ impl SubscriptionService {
let handle = tokio::spawn(async move {
let mut subscribers = Vec::new();
let mut block_stream = pin!(indexer.subscribe_parse_block_stream().await);
let mut block_stream = pin!(indexer.subscribe_parse_block_stream());
loop {
tokio::select! {
@ -273,6 +273,7 @@ impl<T> Drop for Subscription<T> {
}
}
#[must_use]
pub fn not_yet_implemented_error() -> ErrorObjectOwned {
ErrorObject::owned(
ErrorCode::InternalError.code(),
@ -281,6 +282,10 @@ pub fn not_yet_implemented_error() -> ErrorObjectOwned {
)
}
#[expect(
clippy::needless_pass_by_value,
reason = "Error is consumed to extract details for error response"
)]
fn db_error(err: anyhow::Error) -> ErrorObjectOwned {
ErrorObjectOwned::owned(
ErrorCode::InternalError.code(),

View File

@ -38,6 +38,7 @@ pub fn indexer_config(
}
/// Sequencer config options available for custom changes in integration tests.
#[derive(Debug, Clone, Copy)]
pub struct SequencerPartialConfig {
pub max_num_tx_in_block: usize,
pub max_block_size: ByteSize,
@ -122,6 +123,7 @@ pub struct InitialData {
}
impl InitialData {
#[must_use]
pub fn with_two_public_and_two_private_initialized_accounts() -> Self {
let mut public_alice_private_key = PrivateKey::new_os_random();
let mut public_alice_public_key =
@ -231,6 +233,7 @@ impl InitialData {
}
}
#[derive(Debug, Clone, Copy)]
pub enum UrlProtocol {
Http,
Ws,

View File

@ -52,6 +52,7 @@ impl TestContext {
Self::builder().build().await
}
#[must_use]
pub fn builder() -> TestContextBuilder {
TestContextBuilder::new()
}
@ -120,6 +121,10 @@ impl TestContext {
// Setting port to 0 to avoid conflicts between parallel tests, actual port will be retrieved after container is up
.with_env("PORT", "0");
#[expect(
clippy::items_after_statements,
reason = "This is more readable is this function used just after its definition"
)]
async fn up_and_retrieve_port(compose: &mut DockerCompose) -> Result<u16> {
compose
.up()
@ -181,7 +186,10 @@ impl TestContext {
let temp_indexer_dir =
tempfile::tempdir().context("Failed to create temp dir for indexer home")?;
debug!("Using temp indexer home at {:?}", temp_indexer_dir.path());
debug!(
"Using temp indexer home at {}",
temp_indexer_dir.path().display()
);
let indexer_config = config::indexer_config(
bedrock_addr,
@ -206,8 +214,8 @@ impl TestContext {
tempfile::tempdir().context("Failed to create temp dir for sequencer home")?;
debug!(
"Using temp sequencer home at {:?}",
temp_sequencer_dir.path()
"Using temp sequencer home at {}",
temp_sequencer_dir.path().display()
);
let config = config::sequencer_config(
@ -260,10 +268,12 @@ impl TestContext {
}
/// Get reference to the wallet.
#[must_use]
pub fn wallet(&self) -> &WalletCore {
&self.wallet
}
#[must_use]
pub fn wallet_password(&self) -> &str {
&self.wallet_password
}
@ -274,16 +284,19 @@ impl TestContext {
}
/// Get reference to the sequencer client.
#[must_use]
pub fn sequencer_client(&self) -> &SequencerClient {
&self.sequencer_client
}
/// Get reference to the indexer client.
#[must_use]
pub fn indexer_client(&self) -> &IndexerClient {
&self.indexer_client
}
/// Get existing public account IDs in the wallet.
#[must_use]
pub fn existing_public_accounts(&self) -> Vec<AccountId> {
self.wallet
.storage()
@ -293,6 +306,7 @@ impl TestContext {
}
/// Get existing private account IDs in the wallet.
#[must_use]
pub fn existing_private_accounts(&self) -> Vec<AccountId> {
self.wallet
.storage()
@ -386,11 +400,13 @@ impl TestContextBuilder {
}
}
#[must_use]
pub fn with_initial_data(mut self, initial_data: config::InitialData) -> Self {
self.initial_data = Some(initial_data);
self
}
#[must_use]
pub fn with_sequencer_partial_config(
mut self,
sequencer_partial_config: config::SequencerPartialConfig,
@ -419,14 +435,16 @@ impl Drop for BlockingTestContext {
if let Some(ctx) = ctx.take() {
drop(ctx);
}
})
});
}
}
#[must_use]
pub fn format_public_account_id(account_id: AccountId) -> String {
format!("Public/{account_id}")
}
#[must_use]
pub fn format_private_account_id(account_id: AccountId) -> String {
format!("Private/{account_id}")
}

View File

@ -45,9 +45,8 @@ async fn new_public_account_with_label() -> Result<()> {
let result = execute_subcommand(ctx.wallet_mut(), command).await?;
// Extract the account_id from the result
let account_id = match result {
wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } => account_id,
_ => panic!("Expected RegisterAccount return value"),
let wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } = result else {
panic!("Expected RegisterAccount return value")
};
// Verify the label was stored
@ -78,9 +77,9 @@ async fn new_private_account_with_label() -> Result<()> {
let result = execute_subcommand(ctx.wallet_mut(), command).await?;
// Extract the account_id from the result
let account_id = match result {
wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } => account_id,
_ => panic!("Expected RegisterAccount return value"),
let wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } = result else {
panic!("Expected RegisterAccount return value")
};
// Verify the label was stored
@ -110,9 +109,9 @@ async fn new_public_account_without_label() -> Result<()> {
let result = execute_subcommand(ctx.wallet_mut(), command).await?;
// Extract the account_id from the result
let account_id = match result {
wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } => account_id,
_ => panic!("Expected RegisterAccount return value"),
let wallet::cli::SubcommandReturnValue::RegisterAccount { account_id } = result else {
panic!("Expected RegisterAccount return value")
};
// Verify no label was stored

View File

@ -86,7 +86,7 @@ async fn private_transfer_to_foreign_account() -> Result<()> {
assert_eq!(tx.message.new_commitments[0], new_commitment1);
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
for commitment in tx.message.new_commitments {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}
@ -198,7 +198,7 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> {
assert_eq!(tx.message.new_commitments[0], new_commitment1);
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
for commitment in tx.message.new_commitments {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}
@ -353,7 +353,7 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> {
// Verify commitments are in state
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
for commitment in tx.message.new_commitments {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}

View File

@ -112,7 +112,7 @@ async fn failed_transfer_with_insufficient_balance() -> Result<()> {
to: Some(format_public_account_id(ctx.existing_public_accounts()[1])),
to_npk: None,
to_vpk: None,
amount: 1000000,
amount: 1_000_000,
});
let failed_send = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await;

View File

@ -38,13 +38,12 @@ async fn reject_oversized_transaction() -> Result<()> {
);
let err = result.unwrap_err();
let err_str = format!("{:?}", err);
let err_str = format!("{err:?}");
// Check if the error contains information about transaction being too large
assert!(
err_str.contains("TransactionTooLarge") || err_str.contains("too large"),
"Expected TransactionTooLarge error, got: {}",
err_str
"Expected TransactionTooLarge error, got: {err_str}"
);
Ok(())

View File

@ -23,7 +23,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(),
value: format!("{:?}", old_seq_poll_timeout),
value: format!("{old_seq_poll_timeout:?}"),
});
wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;

View File

@ -12,7 +12,7 @@ use tokio::test;
use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand};
/// Timeout in milliseconds to reliably await for block finalization
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 600000;
const L2_TO_L1_TIMEOUT_MILLIS: u64 = 600_000;
#[test]
async fn indexer_test_run() -> Result<()> {

View File

@ -87,7 +87,7 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> {
assert_eq!(tx.message.new_commitments[0], new_commitment1);
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments.into_iter() {
for commitment in tx.message.new_commitments {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}

View File

@ -56,16 +56,17 @@ pub async fn tps_test() -> Result<()> {
for (i, tx_hash) in tx_hashes.iter().enumerate() {
loop {
if now.elapsed().as_millis() > target_time.as_millis() {
panic!("TPS test failed by timeout");
}
assert!(
now.elapsed().as_millis() <= target_time.as_millis(),
"TPS test failed by timeout"
);
let tx_obj = ctx
.sequencer_client()
.get_transaction_by_hash(*tx_hash)
.await
.inspect_err(|err| {
log::warn!("Failed to get transaction by hash {tx_hash} with error: {err:#?}")
log::warn!("Failed to get transaction by hash {tx_hash} with error: {err:#?}");
});
if let Ok(tx_obj) = tx_obj
@ -119,6 +120,10 @@ impl TpsTestManager {
}
}
#[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)
@ -165,7 +170,7 @@ impl TpsTestManager {
let key_chain = KeyChain::new_os_random();
let account = Account {
balance: 100,
nonce: 0xdeadbeef,
nonce: 0xdead_beef,
program_owner: Program::authenticated_transfer_program().id(),
data: Data::default(),
};
@ -200,7 +205,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
let sender_pre = AccountWithMetadata::new(
Account {
balance: 100,
nonce: 0xdeadbeef,
nonce: 0xdead_beef,
program_owner: program.id(),
data: Data::default(),
},
@ -234,7 +239,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(sender_npk.clone(), sender_ss),
(recipient_npk.clone(), recipient_ss),

View File

@ -152,7 +152,7 @@ unsafe extern "C" {
fn new_wallet_ffi_with_test_context_config(
ctx: &BlockingTestContext,
home: &Path,
) -> *mut WalletHandle {
) -> Result<*mut WalletHandle> {
let config_path = home.join("wallet_config.json");
let storage_path = home.join("storage.json");
let mut config = ctx.ctx().wallet().config().to_owned();
@ -163,75 +163,73 @@ fn new_wallet_ffi_with_test_context_config(
.write(true)
.create(true)
.truncate(true)
.open(&config_path)
.unwrap();
.open(&config_path)?;
let config_with_overrides_serialized = serde_json::to_vec_pretty(&config).unwrap();
let config_with_overrides_serialized = serde_json::to_vec_pretty(&config)?;
file.write_all(&config_with_overrides_serialized).unwrap();
file.write_all(&config_with_overrides_serialized)?;
let config_path = CString::new(config_path.to_str().unwrap()).unwrap();
let storage_path = CString::new(storage_path.to_str().unwrap()).unwrap();
let password = CString::new(ctx.ctx().wallet_password()).unwrap();
let config_path = CString::new(config_path.to_str().unwrap())?;
let storage_path = CString::new(storage_path.to_str().unwrap())?;
let password = CString::new(ctx.ctx().wallet_password())?;
unsafe {
Ok(unsafe {
wallet_ffi_create_new(
config_path.as_ptr(),
storage_path.as_ptr(),
password.as_ptr(),
)
}
})
}
fn new_wallet_ffi_with_default_config(password: &str) -> *mut WalletHandle {
let tempdir = tempdir().unwrap();
fn new_wallet_ffi_with_default_config(password: &str) -> Result<*mut WalletHandle> {
let tempdir = tempdir()?;
let config_path = tempdir.path().join("wallet_config.json");
let storage_path = tempdir.path().join("storage.json");
let config_path_c = CString::new(config_path.to_str().unwrap()).unwrap();
let storage_path_c = CString::new(storage_path.to_str().unwrap()).unwrap();
let password = CString::new(password).unwrap();
let config_path_c = CString::new(config_path.to_str().unwrap())?;
let storage_path_c = CString::new(storage_path.to_str().unwrap())?;
let password = CString::new(password)?;
unsafe {
Ok(unsafe {
wallet_ffi_create_new(
config_path_c.as_ptr(),
storage_path_c.as_ptr(),
password.as_ptr(),
)
}
})
}
fn new_wallet_rust_with_default_config(password: &str) -> WalletCore {
let tempdir = tempdir().unwrap();
fn new_wallet_rust_with_default_config(password: &str) -> Result<WalletCore> {
let tempdir = tempdir()?;
let config_path = tempdir.path().join("wallet_config.json");
let storage_path = tempdir.path().join("storage.json");
WalletCore::new_init_storage(
config_path.to_path_buf(),
storage_path.to_path_buf(),
config_path.clone(),
storage_path.clone(),
None,
password.to_string(),
)
.unwrap()
}
fn load_existing_ffi_wallet(home: &Path) -> *mut WalletHandle {
fn load_existing_ffi_wallet(home: &Path) -> Result<*mut WalletHandle> {
let config_path = home.join("wallet_config.json");
let storage_path = home.join("storage.json");
let config_path = CString::new(config_path.to_str().unwrap()).unwrap();
let storage_path = CString::new(storage_path.to_str().unwrap()).unwrap();
let config_path = CString::new(config_path.to_str().unwrap())?;
let storage_path = CString::new(storage_path.to_str().unwrap())?;
unsafe { wallet_ffi_open(config_path.as_ptr(), storage_path.as_ptr()) }
Ok(unsafe { wallet_ffi_open(config_path.as_ptr(), storage_path.as_ptr()) })
}
#[test]
fn test_wallet_ffi_create_public_accounts() {
fn test_wallet_ffi_create_public_accounts() -> Result<()> {
let password = "password_for_tests";
let n_accounts = 10;
// First `n_accounts` public accounts created with Rust wallet
let new_public_account_ids_rust = {
let mut account_ids = Vec::new();
let mut wallet_rust = new_wallet_rust_with_default_config(password);
let mut wallet_rust = new_wallet_rust_with_default_config(password)?;
for _ in 0..n_accounts {
let account_id = wallet_rust.create_new_account_public(None).0;
account_ids.push(*account_id.value());
@ -243,13 +241,10 @@ fn test_wallet_ffi_create_public_accounts() {
let new_public_account_ids_ffi = unsafe {
let mut account_ids = Vec::new();
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password);
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password)?;
for _ in 0..n_accounts {
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
wallet_ffi_create_account_public(
wallet_ffi_handle,
(&mut out_account_id) as *mut FfiBytes32,
);
wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut out_account_id);
account_ids.push(out_account_id.data);
}
wallet_ffi_destroy(wallet_ffi_handle);
@ -257,17 +252,19 @@ fn test_wallet_ffi_create_public_accounts() {
};
assert_eq!(new_public_account_ids_ffi, new_public_account_ids_rust);
Ok(())
}
#[test]
fn test_wallet_ffi_create_private_accounts() {
fn test_wallet_ffi_create_private_accounts() -> Result<()> {
let password = "password_for_tests";
let n_accounts = 10;
// First `n_accounts` private accounts created with Rust wallet
let new_private_account_ids_rust = {
let mut account_ids = Vec::new();
let mut wallet_rust = new_wallet_rust_with_default_config(password);
let mut wallet_rust = new_wallet_rust_with_default_config(password)?;
for _ in 0..n_accounts {
let account_id = wallet_rust.create_new_account_private(None).0;
account_ids.push(*account_id.value());
@ -279,56 +276,52 @@ fn test_wallet_ffi_create_private_accounts() {
let new_private_account_ids_ffi = unsafe {
let mut account_ids = Vec::new();
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password);
let wallet_ffi_handle = new_wallet_ffi_with_default_config(password)?;
for _ in 0..n_accounts {
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
wallet_ffi_create_account_private(
wallet_ffi_handle,
(&mut out_account_id) as *mut FfiBytes32,
);
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
account_ids.push(out_account_id.data);
}
wallet_ffi_destroy(wallet_ffi_handle);
account_ids
};
assert_eq!(new_private_account_ids_ffi, new_private_account_ids_rust)
assert_eq!(new_private_account_ids_ffi, new_private_account_ids_rust);
Ok(())
}
#[test]
fn test_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().unwrap();
let home = tempfile::tempdir()?;
// Create a private account with the wallet FFI and save it
unsafe {
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
wallet_ffi_create_account_private(
wallet_ffi_handle,
(&mut out_private_account_id) as *mut FfiBytes32,
);
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_private_account_id);
wallet_ffi_save(wallet_ffi_handle);
wallet_ffi_destroy(wallet_ffi_handle);
}
let private_account_keys = unsafe {
let wallet_ffi_handle = load_existing_ffi_wallet(home.path());
let wallet_ffi_handle = load_existing_ffi_wallet(home.path())?;
let mut private_account = FfiAccount::default();
let result = wallet_ffi_get_account_private(
wallet_ffi_handle,
(&out_private_account_id) as *const FfiBytes32,
(&mut private_account) as *mut FfiAccount,
&raw const out_private_account_id,
&raw mut private_account,
);
assert_eq!(result, error::WalletFfiError::Success);
let mut out_keys = FfiPrivateAccountKeys::default();
let result = wallet_ffi_get_private_account_keys(
wallet_ffi_handle,
(&out_private_account_id) as *const FfiBytes32,
(&mut out_keys) as *mut FfiPrivateAccountKeys,
&raw const out_private_account_id,
&raw mut out_keys,
);
assert_eq!(result, error::WalletFfiError::Success);
@ -346,17 +339,17 @@ fn test_wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
}
#[test]
fn test_wallet_ffi_list_accounts() {
fn test_wallet_ffi_list_accounts() -> Result<()> {
let password = "password_for_tests";
// Create the wallet FFI
let wallet_ffi_handle = unsafe {
let handle = new_wallet_ffi_with_default_config(password);
let handle = new_wallet_ffi_with_default_config(password)?;
// Create 5 public accounts and 5 private accounts
for _ in 0..5 {
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
wallet_ffi_create_account_public(handle, (&mut out_account_id) as *mut FfiBytes32);
wallet_ffi_create_account_private(handle, (&mut out_account_id) as *mut FfiBytes32);
wallet_ffi_create_account_public(handle, &raw mut out_account_id);
wallet_ffi_create_account_private(handle, &raw mut out_account_id);
}
handle
@ -364,7 +357,7 @@ fn test_wallet_ffi_list_accounts() {
// Create the wallet Rust
let wallet_rust = {
let mut wallet = new_wallet_rust_with_default_config(password);
let mut wallet = new_wallet_rust_with_default_config(password)?;
// Create 5 public accounts and 5 private accounts
for _ in 0..5 {
wallet.create_new_account_public(None);
@ -376,7 +369,7 @@ fn test_wallet_ffi_list_accounts() {
// Get the account list with FFI method
let mut wallet_ffi_account_list = unsafe {
let mut out_list = FfiAccountList::default();
wallet_ffi_list_accounts(wallet_ffi_handle, (&mut out_list) as *mut FfiAccountList);
wallet_ffi_list_accounts(wallet_ffi_handle, &raw mut out_list);
out_list
};
@ -400,7 +393,7 @@ fn test_wallet_ffi_list_accounts() {
assert_eq!(
wallet_rust_account_ids
.iter()
.map(|id| id.value())
.map(nssa::AccountId::value)
.collect::<HashSet<_>>(),
wallet_ffi_account_list_slice
.iter()
@ -409,7 +402,7 @@ fn test_wallet_ffi_list_accounts() {
);
// Assert `is_pub` flag is correct in the FFI result
for entry in wallet_ffi_account_list_slice.iter() {
for entry in wallet_ffi_account_list_slice {
let account_id = AccountId::new(entry.account_id.data);
let is_pub_default_in_rust_wallet = wallet_rust
.storage()
@ -429,27 +422,30 @@ fn test_wallet_ffi_list_accounts() {
}
unsafe {
wallet_ffi_free_account_list((&mut wallet_ffi_account_list) as *mut FfiAccountList);
wallet_ffi_free_account_list(&raw mut wallet_ffi_account_list);
wallet_ffi_destroy(wallet_ffi_handle);
}
Ok(())
}
#[test]
fn test_wallet_ffi_get_balance_public() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let balance = unsafe {
let mut out_balance: [u8; 16] = [0; 16];
let ffi_account_id = FfiBytes32::from(&account_id);
let _result = wallet_ffi_get_balance(
wallet_ffi_get_balance(
wallet_ffi_handle,
(&ffi_account_id) as *const FfiBytes32,
&raw const ffi_account_id,
true,
(&mut out_balance) as *mut [u8; 16],
);
&raw mut out_balance,
)
.unwrap();
u128::from_le_bytes(out_balance)
};
assert_eq!(balance, 10000);
@ -467,17 +463,18 @@ fn test_wallet_ffi_get_balance_public() -> Result<()> {
fn test_wallet_ffi_get_account_public() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let mut out_account = FfiAccount::default();
let account: Account = unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let _result = wallet_ffi_get_account_public(
wallet_ffi_get_account_public(
wallet_ffi_handle,
(&ffi_account_id) as *const FfiBytes32,
(&mut out_account) as *mut FfiAccount,
);
&raw const ffi_account_id,
&raw mut out_account,
)
.unwrap();
(&out_account).try_into().unwrap()
};
@ -490,7 +487,7 @@ fn test_wallet_ffi_get_account_public() -> Result<()> {
assert_eq!(account.nonce, 0);
unsafe {
wallet_ffi_free_account_data((&mut out_account) as *mut FfiAccount);
wallet_ffi_free_account_data(&raw mut out_account);
wallet_ffi_destroy(wallet_ffi_handle);
}
@ -503,17 +500,18 @@ fn test_wallet_ffi_get_account_public() -> Result<()> {
fn test_wallet_ffi_get_account_private() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let account_id: AccountId = ctx.ctx().existing_private_accounts()[0];
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let mut out_account = FfiAccount::default();
let account: Account = unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let _result = wallet_ffi_get_account_private(
wallet_ffi_get_account_private(
wallet_ffi_handle,
(&ffi_account_id) as *const FfiBytes32,
(&mut out_account) as *mut FfiAccount,
);
&raw const ffi_account_id,
&raw mut out_account,
)
.unwrap();
(&out_account).try_into().unwrap()
};
@ -526,7 +524,7 @@ fn test_wallet_ffi_get_account_private() -> Result<()> {
assert_eq!(account.nonce, 0);
unsafe {
wallet_ffi_free_account_data((&mut out_account) as *mut FfiAccount);
wallet_ffi_free_account_data(&raw mut out_account);
wallet_ffi_destroy(wallet_ffi_handle);
}
@ -539,17 +537,18 @@ fn test_wallet_ffi_get_account_private() -> Result<()> {
fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let account_id: AccountId = ctx.ctx().existing_public_accounts()[0];
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let mut out_key = FfiPublicAccountKey::default();
let key: PublicKey = unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let _result = wallet_ffi_get_public_account_key(
wallet_ffi_get_public_account_key(
wallet_ffi_handle,
(&ffi_account_id) as *const FfiBytes32,
(&mut out_key) as *mut FfiPublicAccountKey,
);
&raw const ffi_account_id,
&raw mut out_key,
)
.unwrap();
(&out_key).try_into().unwrap()
};
@ -577,17 +576,18 @@ fn test_wallet_ffi_get_public_account_keys() -> Result<()> {
fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
let ctx = BlockingTestContext::new()?;
let account_id: AccountId = ctx.ctx().existing_private_accounts()[0];
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
let mut keys = FfiPrivateAccountKeys::default();
unsafe {
let ffi_account_id = FfiBytes32::from(&account_id);
let _result = wallet_ffi_get_private_account_keys(
wallet_ffi_get_private_account_keys(
wallet_ffi_handle,
(&ffi_account_id) as *const FfiBytes32,
(&mut keys) as *mut FfiPrivateAccountKeys,
);
&raw const ffi_account_id,
&raw mut keys,
)
.unwrap();
};
let key_chain = &ctx
@ -606,7 +606,7 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
assert_eq!(&keys.vpk().unwrap(), expected_vpk);
unsafe {
wallet_ffi_free_private_account_keys((&mut keys) as *mut FfiPrivateAccountKeys);
wallet_ffi_free_private_account_keys(&raw mut keys);
wallet_ffi_destroy(wallet_ffi_handle);
}
@ -616,66 +616,65 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
}
#[test]
fn test_wallet_ffi_account_id_to_base58() {
fn test_wallet_ffi_account_id_to_base58() -> 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);
let ffi_bytes: FfiBytes32 = (&account_id).into();
let ptr = unsafe { wallet_ffi_account_id_to_base58((&ffi_bytes) as *const FfiBytes32) };
let ptr = unsafe { wallet_ffi_account_id_to_base58(&raw const ffi_bytes) };
let ffi_result = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
let ffi_result = unsafe { CStr::from_ptr(ptr).to_str()? };
assert_eq!(account_id.to_string(), ffi_result);
unsafe {
wallet_ffi_free_string(ptr);
}
Ok(())
}
#[test]
fn test_wallet_ffi_base58_to_account_id() {
fn test_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);
let account_id_str = account_id.to_string();
let account_id_c_str = CString::new(account_id_str.clone()).unwrap();
let account_id_c_str = CString::new(account_id_str.clone())?;
let account_id: AccountId = unsafe {
let mut out_account_id_bytes = FfiBytes32::default();
wallet_ffi_account_id_from_base58(
account_id_c_str.as_ptr(),
(&mut out_account_id_bytes) as *mut FfiBytes32,
);
wallet_ffi_account_id_from_base58(account_id_c_str.as_ptr(), &raw mut out_account_id_bytes);
out_account_id_bytes.into()
};
let expected_account_id = account_id_str.parse().unwrap();
let expected_account_id = account_id_str.parse()?;
assert_eq!(account_id, expected_account_id);
Ok(())
}
#[test]
fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
let ctx = BlockingTestContext::new().unwrap();
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
// Create a new uninitialized public account
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
unsafe {
wallet_ffi_create_account_public(
wallet_ffi_handle,
(&mut out_account_id) as *mut FfiBytes32,
);
wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut out_account_id);
}
// Check its program owner is the default program id
let account: Account = unsafe {
let mut out_account = FfiAccount::default();
let _result = wallet_ffi_get_account_public(
wallet_ffi_get_account_public(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut out_account) as *mut FfiAccount,
);
&raw const out_account_id,
&raw mut out_account,
)
.unwrap();
(&out_account).try_into().unwrap()
};
assert_eq!(account.program_owner, DEFAULT_PROGRAM_ID);
@ -685,8 +684,8 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
unsafe {
wallet_ffi_register_public_account(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut transfer_result) as *mut FfiTransferResult,
&raw const out_account_id,
&raw mut transfer_result,
);
}
@ -696,11 +695,12 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
// Check that the program owner is now the authenticated transfer program
let account: Account = unsafe {
let mut out_account = FfiAccount::default();
let _result = wallet_ffi_get_account_public(
wallet_ffi_get_account_public(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut out_account) as *mut FfiAccount,
);
&raw const out_account_id,
&raw mut out_account,
)
.unwrap();
(&out_account).try_into().unwrap()
};
assert_eq!(
@ -709,7 +709,7 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
);
unsafe {
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
wallet_ffi_free_transfer_result(&raw mut transfer_result);
wallet_ffi_destroy(wallet_ffi_handle);
}
@ -718,17 +718,14 @@ fn test_wallet_ffi_init_public_account_auth_transfer() -> Result<()> {
#[test]
fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
let ctx = BlockingTestContext::new().unwrap();
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
// Create a new uninitialized public account
let mut out_account_id = FfiBytes32::from_bytes([0; 32]);
unsafe {
wallet_ffi_create_account_private(
wallet_ffi_handle,
(&mut out_account_id) as *mut FfiBytes32,
);
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
}
// Check its program owner is the default program id
@ -736,8 +733,8 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
let mut out_account = FfiAccount::default();
wallet_ffi_get_account_private(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut out_account) as *mut FfiAccount,
&raw const out_account_id,
&raw mut out_account,
);
(&out_account).try_into().unwrap()
};
@ -748,8 +745,8 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
unsafe {
wallet_ffi_register_private_account(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut transfer_result) as *mut FfiTransferResult,
&raw const out_account_id,
&raw mut transfer_result,
);
}
@ -759,18 +756,19 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
// Sync private account local storage with onchain encrypted state
unsafe {
let mut current_height = 0;
wallet_ffi_get_current_block_height(wallet_ffi_handle, (&mut current_height) as *mut u64);
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height);
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
};
// Check that the program owner is now the authenticated transfer program
let account: Account = unsafe {
let mut out_account = FfiAccount::default();
let _result = wallet_ffi_get_account_private(
wallet_ffi_get_account_private(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut out_account) as *mut FfiAccount,
);
&raw const out_account_id,
&raw mut out_account,
)
.unwrap();
(&out_account).try_into().unwrap()
};
assert_eq!(
@ -779,7 +777,7 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
);
unsafe {
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
wallet_ffi_free_transfer_result(&raw mut transfer_result);
wallet_ffi_destroy(wallet_ffi_handle);
}
@ -788,9 +786,9 @@ fn test_wallet_ffi_init_private_account_auth_transfer() -> Result<()> {
#[test]
fn test_wallet_ffi_transfer_public() -> Result<()> {
let ctx = BlockingTestContext::new().unwrap();
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
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();
@ -799,10 +797,10 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
unsafe {
wallet_ffi_transfer_public(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
(&to) as *const FfiBytes32,
(&amount) as *const [u8; 16],
(&mut transfer_result) as *mut FfiTransferResult,
&raw const from,
&raw const to,
&raw const amount,
&raw mut transfer_result,
);
}
@ -811,23 +809,20 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
let from_balance = unsafe {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_get_balance(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
&raw const from,
true,
(&mut out_balance) as *mut [u8; 16],
);
&raw mut out_balance,
)
.unwrap();
u128::from_le_bytes(out_balance)
};
let to_balance = unsafe {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_handle,
(&to) as *const FfiBytes32,
true,
(&mut out_balance) as *mut [u8; 16],
);
wallet_ffi_get_balance(wallet_ffi_handle, &raw const to, true, &raw mut out_balance)
.unwrap();
u128::from_le_bytes(out_balance)
};
@ -835,7 +830,7 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
assert_eq!(to_balance, 20100);
unsafe {
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
wallet_ffi_free_transfer_result(&raw mut transfer_result);
wallet_ffi_destroy(wallet_ffi_handle);
}
@ -844,21 +839,18 @@ fn test_wallet_ffi_transfer_public() -> Result<()> {
#[test]
fn test_wallet_ffi_transfer_shielded() -> Result<()> {
let ctx = BlockingTestContext::new().unwrap();
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
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, to_keys) = unsafe {
let mut out_account_id = FfiBytes32::default();
let mut out_keys = FfiPrivateAccountKeys::default();
wallet_ffi_create_account_private(
wallet_ffi_handle,
(&mut out_account_id) as *mut FfiBytes32,
);
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
wallet_ffi_get_private_account_keys(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut out_keys) as *mut FfiPrivateAccountKeys,
&raw const out_account_id,
&raw mut out_keys,
);
(out_account_id, out_keys)
};
@ -868,10 +860,10 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
unsafe {
wallet_ffi_transfer_shielded(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
(&to_keys) as *const FfiPrivateAccountKeys,
(&amount) as *const [u8; 16],
(&mut transfer_result) as *mut FfiTransferResult,
&raw const from,
&raw const to_keys,
&raw const amount,
&raw mut transfer_result,
);
}
@ -881,18 +873,19 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
// Sync private account local storage with onchain encrypted state
unsafe {
let mut current_height = 0;
wallet_ffi_get_current_block_height(wallet_ffi_handle, (&mut current_height) as *mut u64);
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height);
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
};
let from_balance = unsafe {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_get_balance(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
&raw const from,
true,
(&mut out_balance) as *mut [u8; 16],
);
&raw mut out_balance,
)
.unwrap();
u128::from_le_bytes(out_balance)
};
@ -900,9 +893,9 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_handle,
(&to) as *const FfiBytes32,
&raw const to,
false,
(&mut out_balance) as *mut [u8; 16],
&raw mut out_balance,
);
u128::from_le_bytes(out_balance)
};
@ -911,7 +904,7 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
assert_eq!(to_balance, 100);
unsafe {
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
wallet_ffi_free_transfer_result(&raw mut transfer_result);
wallet_ffi_destroy(wallet_ffi_handle);
}
@ -920,9 +913,9 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> {
#[test]
fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
let ctx = BlockingTestContext::new().unwrap();
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
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();
@ -931,10 +924,10 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
unsafe {
wallet_ffi_transfer_deshielded(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
(&to) as *const FfiBytes32,
(&amount) as *const [u8; 16],
(&mut transfer_result) as *mut FfiTransferResult,
&raw const from,
&raw const to,
&raw const amount,
&raw mut transfer_result,
);
}
@ -944,7 +937,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
// Sync private account local storage with onchain encrypted state
unsafe {
let mut current_height = 0;
wallet_ffi_get_current_block_height(wallet_ffi_handle, (&mut current_height) as *mut u64);
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height);
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
};
@ -952,21 +945,17 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
&raw const from,
false,
(&mut out_balance) as *mut [u8; 16],
&raw mut out_balance,
);
u128::from_le_bytes(out_balance)
};
let to_balance = unsafe {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_handle,
(&to) as *const FfiBytes32,
true,
(&mut out_balance) as *mut [u8; 16],
);
let _result =
wallet_ffi_get_balance(wallet_ffi_handle, &raw const to, true, &raw mut out_balance);
u128::from_le_bytes(out_balance)
};
@ -974,7 +963,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
assert_eq!(to_balance, 100);
unsafe {
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
wallet_ffi_free_transfer_result(&raw mut transfer_result);
wallet_ffi_destroy(wallet_ffi_handle);
}
@ -983,22 +972,19 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
#[test]
fn test_wallet_ffi_transfer_private() -> Result<()> {
let ctx = BlockingTestContext::new().unwrap();
let home = tempfile::tempdir().unwrap();
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path());
let ctx = BlockingTestContext::new()?;
let home = tempfile::tempdir()?;
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, to_keys) = unsafe {
let mut out_account_id = FfiBytes32::default();
let mut out_keys = FfiPrivateAccountKeys::default();
wallet_ffi_create_account_private(
wallet_ffi_handle,
(&mut out_account_id) as *mut FfiBytes32,
);
wallet_ffi_create_account_private(wallet_ffi_handle, &raw mut out_account_id);
wallet_ffi_get_private_account_keys(
wallet_ffi_handle,
(&out_account_id) as *const FfiBytes32,
(&mut out_keys) as *mut FfiPrivateAccountKeys,
&raw const out_account_id,
&raw mut out_keys,
);
(out_account_id, out_keys)
};
@ -1009,10 +995,10 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
unsafe {
wallet_ffi_transfer_private(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
(&to_keys) as *const FfiPrivateAccountKeys,
(&amount) as *const [u8; 16],
(&mut transfer_result) as *mut FfiTransferResult,
&raw const from,
&raw const to_keys,
&raw const amount,
&raw mut transfer_result,
);
}
@ -1022,7 +1008,7 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
// Sync private account local storage with onchain encrypted state
unsafe {
let mut current_height = 0;
wallet_ffi_get_current_block_height(wallet_ffi_handle, (&mut current_height) as *mut u64);
wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height);
wallet_ffi_sync_to_block(wallet_ffi_handle, current_height);
};
@ -1030,9 +1016,9 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_handle,
(&from) as *const FfiBytes32,
&raw const from,
false,
(&mut out_balance) as *mut [u8; 16],
&raw mut out_balance,
);
u128::from_le_bytes(out_balance)
};
@ -1041,9 +1027,9 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
let mut out_balance: [u8; 16] = [0; 16];
let _result = wallet_ffi_get_balance(
wallet_ffi_handle,
(&to) as *const FfiBytes32,
&raw const to,
false,
(&mut out_balance) as *mut [u8; 16],
&raw mut out_balance,
);
u128::from_le_bytes(out_balance)
};
@ -1052,7 +1038,7 @@ fn test_wallet_ffi_transfer_private() -> Result<()> {
assert_eq!(to_balance, 100);
unsafe {
wallet_ffi_free_transfer_result((&mut transfer_result) as *mut FfiTransferResult);
wallet_ffi_free_transfer_result(&raw mut transfer_result);
wallet_ffi_destroy(wallet_ffi_handle);
}

View File

@ -12,6 +12,7 @@ pub struct EphemeralKeyHolder {
ephemeral_secret_key: EphemeralSecretKey,
}
#[must_use]
pub fn produce_one_sided_shared_secret_receiver(
vpk: &ViewingPublicKey,
) -> (SharedSecretKey, EphemeralPublicKey) {
@ -24,6 +25,7 @@ pub fn produce_one_sided_shared_secret_receiver(
}
impl EphemeralKeyHolder {
#[must_use]
pub fn new(receiver_nullifier_public_key: &NullifierPublicKey) -> Self {
let mut nonce_bytes = [0; 16];
OsRng.fill_bytes(&mut nonce_bytes);
@ -36,10 +38,12 @@ impl EphemeralKeyHolder {
}
}
#[must_use]
pub fn generate_ephemeral_public_key(&self) -> EphemeralPublicKey {
EphemeralPublicKey::from_scalar(self.ephemeral_secret_key)
}
#[must_use]
pub fn calculate_shared_secret_sender(
&self,
receiver_viewing_public_key: &ViewingPublicKey,

View File

@ -28,7 +28,7 @@ impl FromStr for ChainIndex {
let uprooted_substring = s.strip_prefix("/").unwrap();
let splitted_chain: Vec<&str> = uprooted_substring.split("/").collect();
let splitted_chain: Vec<&str> = uprooted_substring.split('/').collect();
let mut res = vec![];
for split_ch in splitted_chain {
@ -47,7 +47,7 @@ impl Display for ChainIndex {
write!(f, "{cci}/")?;
}
if let Some(last) = self.0.last() {
write!(f, "{}", last)?;
write!(f, "{last}")?;
}
Ok(())
}
@ -60,28 +60,33 @@ impl Default for ChainIndex {
}
impl ChainIndex {
#[must_use]
pub fn root() -> Self {
ChainIndex::default()
}
#[must_use]
pub fn chain(&self) -> &[u32] {
&self.0
}
#[must_use]
pub fn index(&self) -> Option<u32> {
self.chain().last().copied()
}
#[must_use]
pub fn next_in_line(&self) -> ChainIndex {
let mut chain = self.0.clone();
// ToDo: Add overflow check
if let Some(last_p) = chain.last_mut() {
*last_p += 1
*last_p += 1;
}
ChainIndex(chain)
}
#[must_use]
pub fn previous_in_line(&self) -> Option<ChainIndex> {
let mut chain = self.0.clone();
if let Some(last_p) = chain.last_mut() {
@ -91,6 +96,7 @@ impl ChainIndex {
Some(ChainIndex(chain))
}
#[must_use]
pub fn parent(&self) -> Option<ChainIndex> {
if self.0.is_empty() {
None
@ -99,6 +105,7 @@ impl ChainIndex {
}
}
#[must_use]
pub fn nth_child(&self, child_id: u32) -> ChainIndex {
let mut chain = self.0.clone();
chain.push(child_id);
@ -106,6 +113,7 @@ impl ChainIndex {
ChainIndex(chain)
}
#[must_use]
pub fn depth(&self) -> u32 {
self.0.iter().map(|cci| cci + 1).sum()
}
@ -124,7 +132,7 @@ impl ChainIndex {
.iter()
.permutations(self.0.len())
.unique()
.map(|item| ChainIndex(item.into_iter().cloned().collect()))
.map(|item| ChainIndex(item.into_iter().copied().collect()))
}
pub fn chain_ids_at_depth(depth: usize) -> impl Iterator<Item = ChainIndex> {
@ -227,7 +235,7 @@ mod tests {
let prev_chain_id = chain_id.previous_in_line().unwrap();
assert_eq!(prev_chain_id, ChainIndex(vec![1, 7, 2]))
assert_eq!(prev_chain_id, ChainIndex(vec![1, 7, 2]));
}
#[test]
@ -236,7 +244,7 @@ mod tests {
let prev_chain_id = chain_id.previous_in_line();
assert_eq!(prev_chain_id, None)
assert_eq!(prev_chain_id, None);
}
#[test]
@ -245,7 +253,7 @@ mod tests {
let parent_chain_id = chain_id.parent().unwrap();
assert_eq!(parent_chain_id, ChainIndex(vec![1, 7]))
assert_eq!(parent_chain_id, ChainIndex(vec![1, 7]));
}
#[test]
@ -254,7 +262,7 @@ mod tests {
let parent_chain_id = chain_id.parent();
assert_eq!(parent_chain_id, None)
assert_eq!(parent_chain_id, None);
}
#[test]
@ -263,7 +271,7 @@ mod tests {
let parent_chain_id = chain_id.parent().unwrap();
assert_eq!(parent_chain_id, ChainIndex::root())
assert_eq!(parent_chain_id, ChainIndex::root());
}
#[test]
@ -272,7 +280,7 @@ mod tests {
let collapsed = chain_id.collapse_back().unwrap();
assert_eq!(collapsed, ChainIndex(vec![3]))
assert_eq!(collapsed, ChainIndex(vec![3]));
}
#[test]
@ -281,7 +289,7 @@ mod tests {
let collapsed = chain_id.collapse_back();
assert_eq!(collapsed, None)
assert_eq!(collapsed, None);
}
#[test]
@ -290,7 +298,7 @@ mod tests {
let collapsed = chain_id.collapse_back();
assert_eq!(collapsed, None)
assert_eq!(collapsed, None);
}
#[test]

View File

@ -16,21 +16,18 @@ impl ChildKeysPublic {
fn compute_hash_value(&self, cci: u32) -> [u8; 64] {
let mut hash_input = vec![];
match ((2u32).pow(31)).cmp(&cci) {
if 2u32.pow(31) > cci {
// Non-harden
std::cmp::Ordering::Greater => {
hash_input.extend_from_slice(self.cpk.value());
hash_input.extend_from_slice(&cci.to_le_bytes());
hash_input.extend_from_slice(self.cpk.value());
hash_input.extend_from_slice(&cci.to_le_bytes());
hmac_sha512::HMAC::mac(hash_input, self.ccc)
}
hmac_sha512::HMAC::mac(hash_input, self.ccc)
} else {
// Harden
_ => {
hash_input.extend_from_slice(self.csk.value());
hash_input.extend_from_slice(&(cci).to_le_bytes());
hash_input.extend_from_slice(self.csk.value());
hash_input.extend_from_slice(&(cci).to_le_bytes());
hmac_sha512::HMAC::mac(hash_input, self.ccc)
}
hmac_sha512::HMAC::mac(hash_input, self.ccc)
}
}
}
@ -68,9 +65,10 @@ impl KeyNode for ChildKeysPublic {
)
.unwrap();
if secp256k1::constants::CURVE_ORDER < *csk.value() {
panic!("Secret key cannot exceed curve order");
}
assert!(
secp256k1::constants::CURVE_ORDER >= *csk.value(),
"Secret key cannot exceed curve order"
);
let ccc = *hash_value
.last_chunk::<32>()

View File

@ -32,6 +32,7 @@ pub type KeyTreePublic = KeyTree<ChildKeysPublic>;
pub type KeyTreePrivate = KeyTree<ChildKeysPrivate>;
impl<N: KeyNode> KeyTree<N> {
#[must_use]
pub fn new(seed: &SeedHolder) -> Self {
let seed_fit: [u8; 64] = seed
.seed
@ -63,6 +64,7 @@ impl<N: KeyNode> KeyTree<N> {
// ToDo: Add function to create a tree from list of nodes with consistency check.
#[must_use]
pub fn find_next_last_child_of_id(&self, parent_id: &ChainIndex) -> Option<u32> {
if !self.key_map.contains_key(parent_id) {
return None;
@ -87,14 +89,14 @@ impl<N: KeyNode> KeyTree<N> {
match (&rightmost_ref, &rightmost_ref_next) {
(Some(_), Some(_)) => {
left_border = right;
right = (right + right_border) / 2;
right = u32::midpoint(right, right_border);
}
(Some(_), None) => {
break Some(right + 1);
}
(None, None) => {
right_border = right;
right = (left_border + right) / 2;
right = u32::midpoint(left_border, right);
}
(None, Some(_)) => {
unreachable!();
@ -152,6 +154,7 @@ impl<N: KeyNode> KeyTree<N> {
self.fill_node(&self.find_next_slot_layered())
}
#[must_use]
pub fn get_node(&self, account_id: nssa::AccountId) -> Option<&N> {
self.account_id_map
.get(&account_id)

View File

@ -4,6 +4,7 @@ pub trait KeyNode {
fn root(seed: [u8; 64]) -> Self;
/// `cci`'s child of node
#[must_use]
fn nth_child(&self, cci: u32) -> Self;
fn chain_code(&self) -> &[u8; 32];

View File

@ -21,6 +21,7 @@ pub struct KeyChain {
}
impl KeyChain {
#[must_use]
pub fn new_os_random() -> Self {
// Currently dropping SeedHolder at the end of initialization.
// Now entirely sure if we need it in the future.
@ -40,6 +41,7 @@ impl KeyChain {
}
}
#[must_use]
pub fn new_mnemonic(passphrase: String) -> Self {
// Currently dropping SeedHolder at the end of initialization.
// Not entirely sure if we need it in the future.
@ -59,14 +61,15 @@ impl KeyChain {
}
}
#[must_use]
pub fn calculate_shared_secret_receiver(
&self,
ephemeral_public_key_sender: EphemeralPublicKey,
ephemeral_public_key_sender: &EphemeralPublicKey,
index: Option<u32>,
) -> SharedSecretKey {
SharedSecretKey::new(
&self.secret_spending_key.generate_viewing_secret_key(index),
&ephemeral_public_key_sender,
ephemeral_public_key_sender,
)
}
}
@ -106,7 +109,7 @@ mod tests {
// Calculate shared secret
let _shared_secret = account_id_key_holder
.calculate_shared_secret_receiver(ephemeral_public_key_sender, None);
.calculate_shared_secret_receiver(&ephemeral_public_key_sender, None);
}
#[test]
@ -184,7 +187,7 @@ mod tests {
let key_sender = eph_key_holder.calculate_shared_secret_sender(&keys.viewing_public_key);
let key_receiver = keys.calculate_shared_secret_receiver(
eph_key_holder.generate_ephemeral_public_key(),
&eph_key_holder.generate_ephemeral_public_key(),
Some(2),
);

View File

@ -25,14 +25,15 @@ pub struct SecretSpendingKey(pub(crate) [u8; 32]);
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.
/// Private key holder. Produces public keys. Can produce `account_id`. Can produce shared secret
/// for recepient.
pub struct PrivateKeyHolder {
pub nullifier_secret_key: NullifierSecretKey,
pub(crate) viewing_secret_key: ViewingSecretKey,
}
impl SeedHolder {
#[must_use]
pub fn new_os_random() -> Self {
let mut enthopy_bytes: [u8; 32] = [0; 32];
OsRng.fill_bytes(&mut enthopy_bytes);
@ -46,6 +47,7 @@ impl SeedHolder {
}
}
#[must_use]
pub fn new_mnemonic(passphrase: String) -> Self {
let mnemonic = Mnemonic::from_entropy(&NSSA_ENTROPY_BYTES)
.expect("Enthropy must be a multiple of 32 bytes");
@ -56,6 +58,7 @@ impl SeedHolder {
}
}
#[must_use]
pub fn generate_secret_spending_key_hash(&self) -> HashType {
let mut hash = hmac_sha512::HMAC::mac(&self.seed, "NSSA_seed");
@ -67,22 +70,24 @@ impl SeedHolder {
HashType(*hash.first_chunk::<32>().unwrap())
}
#[must_use]
pub fn produce_top_secret_key_holder(&self) -> SecretSpendingKey {
SecretSpendingKey(self.generate_secret_spending_key_hash().into())
}
}
impl SecretSpendingKey {
#[must_use]
pub fn generate_nullifier_secret_key(&self, index: Option<u32>) -> NullifierSecretKey {
const PREFIX: &[u8; 8] = b"LEE/keys";
const SUFFIX_1: &[u8; 1] = &[1];
const SUFFIX_2: &[u8; 19] = &[0; 19];
let index = match index {
None => 0u32,
_ => index.expect("Expect a valid u32"),
};
const PREFIX: &[u8; 8] = b"LEE/keys";
const SUFFIX_1: &[u8; 1] = &[1];
const SUFFIX_2: &[u8; 19] = &[0; 19];
let mut hasher = sha2::Sha256::new();
hasher.update(PREFIX);
hasher.update(self.0);
@ -93,14 +98,16 @@ impl SecretSpendingKey {
<NullifierSecretKey>::from(hasher.finalize_fixed())
}
#[must_use]
pub fn generate_viewing_secret_key(&self, index: Option<u32>) -> ViewingSecretKey {
const PREFIX: &[u8; 8] = b"LEE/keys";
const SUFFIX_1: &[u8; 1] = &[2];
const SUFFIX_2: &[u8; 19] = &[0; 19];
let index = match index {
None => 0u32,
_ => index.expect("Expect a valid u32"),
};
const PREFIX: &[u8; 8] = b"LEE/keys";
const SUFFIX_1: &[u8; 1] = &[2];
const SUFFIX_2: &[u8; 19] = &[0; 19];
let mut hasher = sha2::Sha256::new();
hasher.update(PREFIX);
@ -112,6 +119,7 @@ impl SecretSpendingKey {
hasher.finalize_fixed().into()
}
#[must_use]
pub fn produce_private_key_holder(&self, index: Option<u32>) -> PrivateKeyHolder {
PrivateKeyHolder {
nullifier_secret_key: self.generate_nullifier_secret_key(index),
@ -121,10 +129,12 @@ impl SecretSpendingKey {
}
impl PrivateKeyHolder {
#[must_use]
pub fn generate_nullifier_public_key(&self) -> NullifierPublicKey {
(&self.nullifier_secret_key).into()
}
#[must_use]
pub fn generate_viewing_public_key(&self) -> ViewingPublicKey {
ViewingPublicKey::from_scalar(self.viewing_secret_key)
}

View File

@ -34,7 +34,7 @@ impl NSSAUserData {
let expected_account_id =
nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(key));
if &expected_account_id != account_id {
println!("{}, {}", expected_account_id, account_id);
println!("{expected_account_id}, {account_id}");
check_res = false;
}
}
@ -48,7 +48,7 @@ impl NSSAUserData {
for (account_id, (key, _)) in accounts_keys_map {
let expected_account_id = nssa::AccountId::from(&key.nullifer_public_key);
if expected_account_id != *account_id {
println!("{}, {}", expected_account_id, account_id);
println!("{expected_account_id}, {account_id}");
check_res = false;
}
}
@ -86,7 +86,7 @@ impl NSSAUserData {
/// Generated new private key for public transaction signatures
///
/// Returns the account_id of new account
/// Returns the `account_id` of new account
pub fn generate_new_public_transaction_private_key(
&mut self,
parent_cci: Option<ChainIndex>,
@ -119,7 +119,7 @@ impl NSSAUserData {
/// Generated new private key for privacy preserving transactions
///
/// Returns the account_id of new account
/// Returns the `account_id` of new account
pub fn generate_new_privacy_preserving_transaction_key_chain(
&mut self,
parent_cci: Option<ChainIndex>,

View File

@ -6,6 +6,7 @@ pub struct MemPool<T> {
}
impl<T> MemPool<T> {
#[must_use]
pub fn new(max_size: usize) -> (Self, MemPoolHandle<T>) {
let (sender, receiver) = tokio::sync::mpsc::channel(max_size);
@ -17,6 +18,7 @@ impl<T> MemPool<T> {
(mem_pool, sender)
}
/// Pop an item from the mempool first checking the front buffer (LIFO) then the channel (FIFO).
pub fn pop(&mut self) -> Option<T> {
use tokio::sync::mpsc::error::TryRecvError;

View File

@ -1,4 +1,4 @@
use std::{env, fs, path::PathBuf};
use std::{env, fmt::Write as _, fs, path::PathBuf};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
@ -15,7 +15,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.collect::<Vec<_>>();
if bins.is_empty() {
return Err(format!("No .bin files found in {:?}", program_methods_dir).into());
return Err(format!("No .bin files found in {}", program_methods_dir.display()).into());
}
fs::create_dir_all(&mod_dir)?;
@ -25,14 +25,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let name = path.file_stem().unwrap().to_string_lossy();
let bytecode = fs::read(&path)?;
let image_id: [u32; 8] = risc0_binfmt::compute_image_id(&bytecode)?.into();
src.push_str(&format!(
write!(
src,
"pub const {}_ELF: &[u8] = include_bytes!(r#\"{}\"#);\n\
#[expect(clippy::unreadable_literal, reason = \"Generated image IDs from risc0 are cryptographic hashes represented as u32 arrays\")]\n\
pub const {}_ID: [u32; 8] = {:?};\n",
name.to_uppercase(),
path.display(),
name.to_uppercase(),
image_id
));
)?;
}
fs::write(&mod_file, src)?;
println!("cargo:warning=Generated module at {}", mod_file.display());

View File

@ -1,4 +1,7 @@
use std::{fmt::Display, str::FromStr};
use std::{
fmt::{Display, Write as _},
str::FromStr,
};
use base58::{FromBase58, ToBase58};
use borsh::{BorshDeserialize, BorshSerialize};
@ -25,12 +28,14 @@ pub struct Account {
impl std::fmt::Debug for Account {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let program_owner_hex: String = self
let program_owner_hex = self
.program_owner
.iter()
.flat_map(|n| n.to_le_bytes())
.map(|b| format!("{b:02x}"))
.collect();
.fold(String::new(), |mut acc, bytes| {
write!(acc, "{bytes:02x}").expect("writing to string should not fail");
acc
});
f.debug_struct("Account")
.field("program_owner", &program_owner_hex)
.field("balance", &self.balance)
@ -82,14 +87,17 @@ impl std::fmt::Debug for AccountId {
}
impl AccountId {
#[must_use]
pub fn new(value: [u8; 32]) -> Self {
Self { value }
}
#[must_use]
pub fn value(&self) -> &[u8; 32] {
&self.value
}
#[must_use]
pub fn into_value(self) -> [u8; 32] {
self.value
}
@ -172,7 +180,7 @@ mod tests {
.to_vec()
.try_into()
.unwrap(),
nonce: 0xdeadbeef,
nonce: 0xdead_beef,
};
let fingerprint = AccountId::new([8; 32]);
let new_acc_with_metadata = AccountWithMetadata::new(account.clone(), true, fingerprint);

View File

@ -10,10 +10,12 @@ pub const DATA_MAX_LENGTH: ByteSize = ByteSize::kib(100);
pub struct Data(Vec<u8>);
impl Data {
#[must_use]
pub fn into_inner(self) -> Vec<u8> {
self.0
}
/// Reads data from a cursor.
#[cfg(feature = "host")]
pub fn from_cursor(
cursor: &mut std::io::Cursor<&[u8]>,
@ -23,7 +25,9 @@ impl Data {
let mut u32_bytes = [0u8; 4];
cursor.read_exact(&mut u32_bytes)?;
let data_length = u32::from_le_bytes(u32_bytes);
if data_length as usize > DATA_MAX_LENGTH.as_u64() as usize {
if data_length as usize
> usize::try_from(DATA_MAX_LENGTH.as_u64()).expect("DATA_MAX_LENGTH fits in usize")
{
return Err(
std::io::Error::new(std::io::ErrorKind::InvalidData, DataTooBigError).into(),
);
@ -49,7 +53,9 @@ impl TryFrom<Vec<u8>> for Data {
type Error = DataTooBigError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() > DATA_MAX_LENGTH.as_u64() as usize {
if value.len()
> usize::try_from(DATA_MAX_LENGTH.as_u64()).expect("DATA_MAX_LENGTH fits in usize")
{
Err(DataTooBigError)
} else {
Ok(Self(value))
@ -98,13 +104,17 @@ impl<'de> Deserialize<'de> for Data {
A: serde::de::SeqAccess<'de>,
{
let mut vec = Vec::with_capacity(
seq.size_hint()
.unwrap_or(0)
.min(DATA_MAX_LENGTH.as_u64() as usize),
seq.size_hint().unwrap_or(0).min(
usize::try_from(DATA_MAX_LENGTH.as_u64())
.expect("DATA_MAX_LENGTH fits in usize"),
),
);
while let Some(value) = seq.next_element()? {
if vec.len() >= DATA_MAX_LENGTH.as_u64() as usize {
if vec.len()
>= usize::try_from(DATA_MAX_LENGTH.as_u64())
.expect("DATA_MAX_LENGTH fits in usize")
{
return Err(serde::de::Error::custom(DataTooBigError));
}
vec.push(value);
@ -125,10 +135,15 @@ impl BorshDeserialize for Data {
let len = u32::deserialize_reader(reader)?;
match len {
0 => Ok(Self::default()),
len if len as usize > DATA_MAX_LENGTH.as_u64() as usize => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
DataTooBigError,
)),
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 => {
let vec_bytes = u8::vec_from_reader(len, reader)?
.expect("can't be None in current borsh crate implementation");
@ -144,21 +159,35 @@ mod tests {
#[test]
fn test_data_max_length_allowed() {
let max_vec = vec![0u8; DATA_MAX_LENGTH.as_u64() as usize];
let max_vec = vec![
0u8;
usize::try_from(DATA_MAX_LENGTH.as_u64())
.expect("DATA_MAX_LENGTH fits in usize")
];
let result = Data::try_from(max_vec);
assert!(result.is_ok());
}
#[test]
fn test_data_too_big_error() {
let big_vec = vec![0u8; DATA_MAX_LENGTH.as_u64() as usize + 1];
let big_vec = vec![
0u8;
usize::try_from(DATA_MAX_LENGTH.as_u64())
.expect("DATA_MAX_LENGTH fits in usize")
+ 1
];
let result = Data::try_from(big_vec);
assert!(matches!(result, Err(DataTooBigError)));
}
#[test]
fn test_borsh_deserialize_exceeding_limit_error() {
let too_big_data = vec![0u8; DATA_MAX_LENGTH.as_u64() as usize + 1];
let too_big_data = vec![
0u8;
usize::try_from(DATA_MAX_LENGTH.as_u64())
.expect("DATA_MAX_LENGTH fits in usize")
+ 1
];
let mut serialized = Vec::new();
<_ as BorshSerialize>::serialize(&too_big_data, &mut serialized).unwrap();
@ -168,7 +197,12 @@ mod tests {
#[test]
fn test_json_deserialize_exceeding_limit_error() {
let data = vec![0u8; DATA_MAX_LENGTH.as_u64() as usize + 1];
let data = vec![
0u8;
usize::try_from(DATA_MAX_LENGTH.as_u64())
.expect("DATA_MAX_LENGTH fits in usize")
+ 1
];
let json = serde_json::to_string(&data).unwrap();
let result: Result<Data, _> = serde_json::from_str(&json);

View File

@ -42,6 +42,8 @@ pub struct PrivacyPreservingCircuitOutput {
#[cfg(feature = "host")]
impl PrivacyPreservingCircuitOutput {
/// Serializes the circuit output to a byte vector.
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
bytemuck::cast_slice(&risc0_zkvm::serde::to_vec(&self).unwrap()).to_vec()
}
@ -65,9 +67,9 @@ mod tests {
AccountWithMetadata::new(
Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 12345678901234567890,
balance: 12_345_678_901_234_567_890,
data: b"test data".to_vec().try_into().unwrap(),
nonce: 18446744073709551614,
nonce: 18_446_744_073_709_551_614,
},
true,
AccountId::new([0; 32]),
@ -75,9 +77,9 @@ mod tests {
AccountWithMetadata::new(
Account {
program_owner: [9, 9, 9, 8, 8, 8, 7, 7],
balance: 123123123456456567112,
balance: 123_123_123_456_456_567_112,
data: b"test data".to_vec().try_into().unwrap(),
nonce: 9999999999999999999999,
nonce: 9_999_999_999_999_999_999_999,
},
false,
AccountId::new([1; 32]),
@ -87,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: 18446744073709551615,
nonce: 18_446_744_073_709_551_615,
}],
ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])],
new_commitments: vec![Commitment::new(

View File

@ -14,7 +14,12 @@ 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 {
let hex: String = self.0.iter().map(|b| format!("{b:02x}")).collect();
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})")
}
}
@ -45,7 +50,8 @@ pub const DUMMY_COMMITMENT_HASH: [u8; 32] = [
impl Commitment {
/// Generates the commitment to a private account owned by user for npk:
/// SHA256(npk || program_owner || balance || nonce || SHA256(data))
/// SHA256(npk || `program_owner` || balance || nonce || SHA256(data))
#[must_use]
pub fn new(npk: &NullifierPublicKey, account: &Account) -> Self {
let mut bytes = Vec::new();
bytes.extend_from_slice(&npk.to_byte_array());
@ -73,6 +79,7 @@ pub type CommitmentSetDigest = [u8; 32];
pub type MembershipProof = (usize, Vec<[u8; 32]>);
/// Computes the resulting digest for the given membership proof and corresponding commitment
#[must_use]
pub fn compute_digest_for_path(
commitment: &Commitment,
proof: &MembershipProof,

View File

@ -17,6 +17,8 @@ use crate::{
};
impl Account {
/// Serializes the account to bytes.
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
for word in &self.program_owner {
@ -24,12 +26,13 @@ impl Account {
}
bytes.extend_from_slice(&self.balance.to_le_bytes());
bytes.extend_from_slice(&self.nonce.to_le_bytes());
let data_length: u32 = self.data.len() as u32;
let data_length: u32 = u32::try_from(self.data.len()).expect("data length fits in u32");
bytes.extend_from_slice(&data_length.to_le_bytes());
bytes.extend_from_slice(self.data.as_ref());
bytes
}
/// Deserializes an account from a cursor.
#[cfg(feature = "host")]
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
use crate::account::data::Data;
@ -65,15 +68,18 @@ impl Account {
}
impl Commitment {
#[must_use]
pub fn to_byte_array(&self) -> [u8; 32] {
self.0
}
#[cfg(feature = "host")]
#[must_use]
pub fn from_byte_array(bytes: [u8; 32]) -> Self {
Self(bytes)
}
/// 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];
@ -83,6 +89,7 @@ impl Commitment {
}
impl NullifierPublicKey {
#[must_use]
pub fn to_byte_array(&self) -> [u8; 32] {
self.0
}
@ -90,15 +97,18 @@ impl NullifierPublicKey {
#[cfg(feature = "host")]
impl Nullifier {
#[must_use]
pub fn to_byte_array(&self) -> [u8; 32] {
self.0
}
#[cfg(feature = "host")]
#[must_use]
pub fn from_byte_array(bytes: [u8; 32]) -> Self {
Self(bytes)
}
/// Deserializes a nullifier from a cursor.
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
let mut bytes = [0u8; 32];
cursor.read_exact(&mut bytes)?;
@ -107,9 +117,12 @@ impl Nullifier {
}
impl Ciphertext {
/// Serializes the ciphertext to bytes.
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let ciphertext_length: u32 = self.0.len() as u32;
let ciphertext_length: u32 =
u32::try_from(self.0.len()).expect("ciphertext length fits in u32");
bytes.extend_from_slice(&ciphertext_length.to_le_bytes());
bytes.extend_from_slice(&self.0);
@ -117,16 +130,19 @@ impl Ciphertext {
}
#[cfg(feature = "host")]
#[must_use]
pub fn into_inner(self) -> Vec<u8> {
self.0
}
#[cfg(feature = "host")]
#[must_use]
pub fn from_inner(inner: Vec<u8>) -> Self {
Self(inner)
}
#[cfg(feature = "host")]
/// Deserializes ciphertext from a cursor.
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
let mut u32_bytes = [0; 4];
@ -141,10 +157,13 @@ impl Ciphertext {
#[cfg(feature = "host")]
impl Secp256k1Point {
/// Converts the point to bytes.
#[must_use]
pub fn to_bytes(&self) -> [u8; 33] {
self.0.clone().try_into().unwrap()
}
/// Deserializes a secp256k1 point from a cursor.
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
let mut value = vec![0; 33];
cursor.read_exact(&mut value)?;
@ -153,6 +172,7 @@ impl Secp256k1Point {
}
impl AccountId {
#[must_use]
pub fn to_bytes(&self) -> [u8; 32] {
*self.value()
}
@ -166,7 +186,7 @@ mod tests {
fn test_enconding() {
let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 123456789012345678901234567890123456,
balance: 123_456_789_012_345_678_901_234_567_890_123_456,
nonce: 42,
data: b"hola mundo".to_vec().try_into().unwrap(),
};
@ -227,7 +247,7 @@ mod tests {
fn test_account_to_bytes_roundtrip() {
let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 123456789012345678901234567890123456,
balance: 123_456_789_012_345_678_901_234_567_890_123_456,
nonce: 42,
data: b"hola mundo".to_vec().try_into().unwrap(),
};

View File

@ -28,19 +28,25 @@ pub struct Ciphertext(pub(crate) Vec<u8>);
#[cfg(any(feature = "host", test))]
impl std::fmt::Debug for Ciphertext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let hex: String = self.0.iter().map(|b| format!("{b:02x}")).collect();
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, "Ciphertext({hex})")
}
}
impl EncryptionScheme {
#[must_use]
pub fn encrypt(
account: &Account,
shared_secret: &SharedSecretKey,
commitment: &Commitment,
output_index: u32,
) -> Ciphertext {
let mut buffer = account.to_bytes().to_vec();
let mut buffer = account.to_bytes().clone();
Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index);
Ciphertext(buffer)
}
@ -72,6 +78,7 @@ impl EncryptionScheme {
}
#[cfg(feature = "host")]
#[must_use]
pub fn decrypt(
ciphertext: &Ciphertext,
shared_secret: &SharedSecretKey,
@ -79,7 +86,7 @@ impl EncryptionScheme {
output_index: u32,
) -> Option<Account> {
use std::io::Cursor;
let mut buffer = ciphertext.0.to_owned();
let mut buffer = ciphertext.0.clone();
Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index);
let mut cursor = Cursor::new(buffer.as_slice());
@ -87,12 +94,12 @@ impl EncryptionScheme {
.inspect_err(|err| {
println!(
"Failed to decode {ciphertext:?} \n
with secret {:?} ,\n
with secret {:?} ,\n
commitment {commitment:?} ,\n
and output_index {output_index} ,\n
with error {err:?}",
shared_secret.0
)
);
})
.ok()
}

View File

@ -1,3 +1,5 @@
use std::fmt::Write as _;
use borsh::{BorshDeserialize, BorshSerialize};
use k256::{
AffinePoint, EncodedPoint, FieldBytes, ProjectivePoint,
@ -15,12 +17,16 @@ pub struct Secp256k1Point(pub Vec<u8>);
impl std::fmt::Debug for Secp256k1Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let hex: String = self.0.iter().map(|b| format!("{b:02x}")).collect();
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, "Secp256k1Point({hex})")
}
}
impl Secp256k1Point {
#[must_use]
pub fn from_scalar(value: Scalar) -> Secp256k1Point {
let x_bytes: FieldBytes = value.into();
let x = k256::Scalar::from_repr(x_bytes).unwrap();
@ -43,6 +49,8 @@ impl From<&EphemeralSecretKey> for EphemeralPublicKey {
}
impl SharedSecretKey {
/// Creates a new shared secret key from a scalar and a point.
#[must_use]
pub fn new(scalar: &Scalar, point: &Secp256k1Point) -> Self {
let scalar = k256::Scalar::from_repr((*scalar).into()).unwrap();
let point: [u8; 33] = point.0.clone().try_into().unwrap();

View File

@ -28,10 +28,10 @@ impl AsRef<[u8]> for NullifierPublicKey {
impl From<&NullifierSecretKey> for NullifierPublicKey {
fn from(value: &NullifierSecretKey) -> Self {
let mut bytes = Vec::new();
const PREFIX: &[u8; 8] = b"LEE/keys";
const SUFFIX_1: &[u8; 1] = &[7];
const SUFFIX_2: &[u8; 23] = &[0; 23];
let mut bytes = Vec::new();
bytes.extend_from_slice(PREFIX);
bytes.extend_from_slice(value);
bytes.extend_from_slice(SUFFIX_1);
@ -52,12 +52,19 @@ pub struct Nullifier(pub(super) [u8; 32]);
#[cfg(any(feature = "host", test))]
impl std::fmt::Debug for Nullifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let hex: String = self.0.iter().map(|b| format!("{b:02x}")).collect();
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, "Nullifier({hex})")
}
}
impl Nullifier {
/// Computes a nullifier for an account update.
#[must_use]
pub fn for_account_update(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self {
const UPDATE_PREFIX: &[u8; 32] = b"/NSSA/v0.2/Nullifier/Update/\x00\x00\x00\x00";
let mut bytes = UPDATE_PREFIX.to_vec();
@ -66,6 +73,8 @@ impl Nullifier {
Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap())
}
/// Computes a nullifier for an account initialization.
#[must_use]
pub fn for_account_initialization(npk: &NullifierPublicKey) -> Self {
const INIT_PREFIX: &[u8; 32] = b"/NSSA/v0.2/Nullifier/Initialize/";
let mut bytes = INIT_PREFIX.to_vec();

View File

@ -15,7 +15,7 @@ pub struct ProgramInput<T> {
pub instruction: T,
}
/// A 32-byte seed used to compute a *Program-Derived AccountId* (PDA).
/// A 32-byte seed used to compute a *Program-Derived `AccountId`* (PDA).
///
/// Each program can derive up to `2^256` unique account IDs by choosing different
/// seeds. PDAs allow programs to control namespaced account identifiers without
@ -24,11 +24,13 @@ pub struct ProgramInput<T> {
pub struct PdaSeed([u8; 32]);
impl PdaSeed {
#[must_use]
pub const fn new(value: [u8; 32]) -> Self {
Self(value)
}
}
#[must_use]
pub fn compute_authorized_pdas(
caller_program_id: Option<ProgramId>,
pda_seeds: &[PdaSeed],
@ -90,6 +92,7 @@ impl ChainedCall {
}
}
#[must_use]
pub fn with_pda_seeds(mut self, pda_seeds: Vec<PdaSeed>) -> Self {
self.pda_seeds = pda_seeds;
self
@ -110,6 +113,7 @@ pub struct AccountPostState {
impl AccountPostState {
/// Creates a post state without a claim request.
/// The executing program is not requesting ownership of the account.
#[must_use]
pub fn new(account: Account) -> Self {
Self {
account,
@ -120,6 +124,7 @@ impl AccountPostState {
/// Creates a post state that requests ownership of the account.
/// This indicates that the executing program intends to claim the
/// account as its own and is allowed to mutate it.
#[must_use]
pub fn new_claimed(account: Account) -> Self {
Self {
account,
@ -129,6 +134,7 @@ impl AccountPostState {
/// Creates a post state that requests ownership of the account
/// if the account's program owner is the default program ID.
#[must_use]
pub fn new_claimed_if_default(account: Account) -> Self {
let claim = account.program_owner == DEFAULT_PROGRAM_ID;
Self { account, claim }
@ -136,11 +142,13 @@ impl AccountPostState {
/// Returns `true` if this post state requests that the account
/// be claimed (owned) by the executing program.
#[must_use]
pub fn requires_claim(&self) -> bool {
self.claim
}
/// Returns the underlying account
#[must_use]
pub fn account(&self) -> &Account {
&self.account
}
@ -151,6 +159,7 @@ impl AccountPostState {
}
/// Consumes the post state and returns the underlying account
#[must_use]
pub fn into_account(self) -> Account {
self.account
}
@ -167,6 +176,8 @@ pub struct ProgramOutput {
pub chained_calls: Vec<ChainedCall>,
}
/// Reads the NSSA inputs from the guest environment.
#[must_use]
pub fn read_nssa_inputs<T: DeserializeOwned>() -> (ProgramInput<T>, InstructionData) {
let pre_states: Vec<AccountWithMetadata> = env::read();
let instruction_words: InstructionData = env::read();
@ -215,6 +226,7 @@ pub fn write_nssa_outputs_with_chained_call(
/// - `pre_states`: The list of input accounts, each annotated with authorization metadata.
/// - `post_states`: The list of resulting accounts after executing the program logic.
/// - `executing_program_id`: The identifier of the program that was executed.
#[must_use]
pub fn validate_execution(
pre_states: &[AccountWithMetadata],
post_states: &[AccountPostState],

View File

@ -4,6 +4,7 @@ use crate::{
};
impl Message {
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
borsh::to_vec(&self).expect("Autoderived borsh serialization failure")
}
@ -14,6 +15,7 @@ impl Message {
}
impl PrivacyPreservingTransaction {
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
borsh::to_vec(&self).expect("Autoderived borsh serialization failure")
}

View File

@ -1,6 +1,7 @@
use crate::{ProgramDeploymentTransaction, error::NssaError};
impl ProgramDeploymentTransaction {
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
borsh::to_vec(&self).expect("Autoderived borsh serialization failure")
}

View File

@ -7,6 +7,7 @@ impl Message {
}
impl PublicTransaction {
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
borsh::to_vec(&self).expect("Autoderived borsh serialization failure")
}

View File

@ -64,12 +64,12 @@ impl MerkleTree {
let capacity = capacity.next_power_of_two();
let total_depth = capacity.trailing_zeros() as usize;
let nodes = default_values::DEFAULT_VALUES[..(total_depth + 1)]
let nodes = default_values::DEFAULT_VALUES[..=total_depth]
.iter()
.rev()
.enumerate()
.flat_map(|(level, default_value)| std::iter::repeat_n(default_value, 1 << level))
.cloned()
.copied()
.collect();
Self {
@ -164,7 +164,7 @@ mod tests {
impl MerkleTree {
pub fn new(values: &[Value]) -> Self {
let mut this = Self::with_capacity(values.len());
for value in values.iter().cloned() {
for value in values.iter().copied() {
this.insert(value);
}
this
@ -201,7 +201,7 @@ mod tests {
hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de");
assert_eq!(tree.root(), expected_root);
assert_eq!(tree.capacity, 4);
assert_eq!(tree.length, 4)
assert_eq!(tree.length, 4);
}
#[test]
@ -283,15 +283,15 @@ mod tests {
assert_eq!(tree.length, 0);
assert_eq!(tree.nodes.len(), 15);
for i in 7..15 {
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[0])
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[0]);
}
for i in 3..7 {
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[1])
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[1]);
}
for i in 1..3 {
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[2])
assert_eq!(*tree.get_node(i), default_values::DEFAULT_VALUES[2]);
}
assert_eq!(*tree.get_node(0), default_values::DEFAULT_VALUES[3])
assert_eq!(*tree.get_node(0), default_values::DEFAULT_VALUES[3]);
}
#[test]

View File

@ -21,10 +21,12 @@ use crate::{
pub struct Proof(pub(crate) Vec<u8>);
impl Proof {
#[must_use]
pub fn into_inner(self) -> Vec<u8> {
self.0
}
#[must_use]
pub fn from_inner(inner: Vec<u8>) -> Self {
Self(inner)
}
@ -38,6 +40,7 @@ pub struct ProgramWithDependencies {
}
impl ProgramWithDependencies {
#[must_use]
pub fn new(program: Program, dependencies: HashMap<ProgramId, Program>) -> Self {
Self {
program,
@ -74,7 +77,7 @@ pub fn execute_and_prove(
let initial_call = ChainedCall {
program_id: program.id(),
instruction_data: instruction_data.clone(),
instruction_data,
pre_states,
pda_seeds: vec![],
};
@ -217,7 +220,7 @@ mod tests {
let expected_recipient_post = Account {
program_owner: program.id(),
balance: balance_to_move,
nonce: 0xdeadbeef,
nonce: 0xdead_beef,
data: Data::default(),
};
@ -230,7 +233,7 @@ mod tests {
vec![sender, recipient],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![0, 2],
vec![0xdeadbeef],
vec![0xdead_beef],
vec![(recipient_keys.npk(), shared_secret)],
vec![],
vec![None],
@ -267,7 +270,7 @@ mod tests {
let sender_pre = AccountWithMetadata::new(
Account {
balance: 100,
nonce: 0xdeadbeef,
nonce: 0xdead_beef,
program_owner: program.id(),
data: Data::default(),
},
@ -302,13 +305,13 @@ mod tests {
let expected_private_account_1 = Account {
program_owner: program.id(),
balance: 100 - balance_to_move,
nonce: 0xdeadbeef1,
nonce: 0xdead_beef1,
..Default::default()
};
let expected_private_account_2 = Account {
program_owner: program.id(),
balance: balance_to_move,
nonce: 0xdeadbeef2,
nonce: 0xdead_beef2,
..Default::default()
};
let expected_new_commitments = vec![
@ -326,7 +329,7 @@ mod tests {
vec![sender_pre.clone(), recipient],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(sender_keys.npk(), shared_secret_1),
(recipient_keys.npk(), shared_secret_2),

View File

@ -20,8 +20,8 @@ pub struct EncryptedAccountData {
impl EncryptedAccountData {
fn new(
ciphertext: Ciphertext,
npk: NullifierPublicKey,
vpk: ViewingPublicKey,
npk: &NullifierPublicKey,
vpk: &ViewingPublicKey,
epk: EphemeralPublicKey,
) -> Self {
let view_tag = Self::compute_view_tag(npk, vpk);
@ -33,7 +33,8 @@ impl EncryptedAccountData {
}
/// Computes the tag as the first byte of SHA256("/NSSA/v0.2/ViewTag/" || Npk || vpk)
pub fn compute_view_tag(npk: NullifierPublicKey, vpk: ViewingPublicKey) -> ViewTag {
#[must_use]
pub fn compute_view_tag(npk: &NullifierPublicKey, vpk: &ViewingPublicKey) -> ViewTag {
let mut hasher = Sha256::new();
hasher.update(b"/NSSA/v0.2/ViewTag/");
hasher.update(npk.to_byte_array());
@ -98,7 +99,7 @@ impl Message {
.into_iter()
.zip(public_keys)
.map(|(ciphertext, (npk, vpk, epk))| {
EncryptedAccountData::new(ciphertext, npk, vpk, epk)
EncryptedAccountData::new(ciphertext, &npk, &vpk, epk)
})
.collect();
Ok(Self {
@ -126,6 +127,7 @@ pub mod tests {
privacy_preserving_transaction::message::{EncryptedAccountData, Message},
};
#[must_use]
pub fn message_for_tests() -> Message {
let account1 = Account::default();
let account2 = Account::default();
@ -173,7 +175,7 @@ pub mod tests {
let epk = EphemeralPublicKey::from_scalar(esk);
let ciphertext = EncryptionScheme::encrypt(&account, &shared_secret, &commitment, 2);
let encrypted_account_data =
EncryptedAccountData::new(ciphertext.clone(), npk.clone(), vpk.clone(), epk.clone());
EncryptedAccountData::new(ciphertext.clone(), &npk, &vpk, epk.clone());
let expected_view_tag = {
let mut hasher = Sha256::new();
@ -188,7 +190,7 @@ pub mod tests {
assert_eq!(encrypted_account_data.epk, epk);
assert_eq!(
encrypted_account_data.view_tag,
EncryptedAccountData::compute_view_tag(npk, vpk)
EncryptedAccountData::compute_view_tag(&npk, &vpk)
);
assert_eq!(encrypted_account_data.view_tag, expected_view_tag);
}

View File

@ -21,6 +21,7 @@ pub struct PrivacyPreservingTransaction {
}
impl PrivacyPreservingTransaction {
#[must_use]
pub fn new(message: Message, witness_set: WitnessSet) -> Self {
Self {
message,
@ -119,19 +120,22 @@ impl PrivacyPreservingTransaction {
Ok(message
.public_account_ids
.iter()
.cloned()
.copied()
.zip(message.public_post_states.clone())
.collect())
}
#[must_use]
pub fn message(&self) -> &Message {
&self.message
}
#[must_use]
pub fn witness_set(&self) -> &WitnessSet {
&self.witness_set
}
#[must_use]
pub fn hash(&self) -> [u8; 32] {
let bytes = self.to_bytes();
let mut hasher = sha2::Sha256::new();
@ -147,6 +151,7 @@ impl PrivacyPreservingTransaction {
.collect()
}
#[must_use]
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
let mut acc_set = self
.signer_account_ids()

View File

@ -12,6 +12,7 @@ pub struct WitnessSet {
}
impl WitnessSet {
#[must_use]
pub fn for_message(message: &Message, proof: Proof, private_keys: &[&PrivateKey]) -> Self {
let message_bytes = message.to_bytes();
let signatures_and_public_keys = private_keys
@ -24,11 +25,12 @@ impl WitnessSet {
})
.collect();
Self {
proof,
signatures_and_public_keys,
proof,
}
}
#[must_use]
pub fn signatures_are_valid_for(&self, message: &Message) -> bool {
let message_bytes = message.to_bytes();
for (signature, public_key) in self.signatures_and_public_keys() {
@ -39,18 +41,22 @@ impl WitnessSet {
true
}
#[must_use]
pub fn signatures_and_public_keys(&self) -> &[(Signature, PublicKey)] {
&self.signatures_and_public_keys
}
#[must_use]
pub fn proof(&self) -> &Proof {
&self.proof
}
#[must_use]
pub fn into_raw_parts(self) -> (Vec<(Signature, PublicKey)>, Proof) {
(self.signatures_and_public_keys, self.proof)
}
#[must_use]
pub fn from_raw_parts(
signatures_and_public_keys: Vec<(Signature, PublicKey)>,
proof: Proof,

View File

@ -32,10 +32,12 @@ impl Program {
Ok(Self { elf: bytecode, id })
}
#[must_use]
pub fn id(&self) -> ProgramId {
self.id
}
#[must_use]
pub fn elf(&self) -> &[u8] {
&self.elf
}
@ -85,18 +87,21 @@ impl Program {
Ok(())
}
#[must_use]
pub fn authenticated_transfer_program() -> Self {
// This unwrap won't panic since the `AUTHENTICATED_TRANSFER_ELF` comes from risc0 build of
// `program_methods`
Self::new(AUTHENTICATED_TRANSFER_ELF.to_vec()).unwrap()
}
#[must_use]
pub fn token() -> Self {
// This unwrap won't panic since the `TOKEN_ELF` comes from risc0 build of
// `program_methods`
Self::new(TOKEN_ELF.to_vec()).unwrap()
}
#[must_use]
pub fn amm() -> Self {
Self::new(AMM_ELF.to_vec()).expect("The AMM program must be a valid Risc0 program")
}
@ -104,12 +109,14 @@ impl Program {
// TODO: Testnet only. Refactor to prevent compilation on mainnet.
impl Program {
#[must_use]
pub fn pinata() -> Self {
// This unwrap won't panic since the `PINATA_ELF` comes from risc0 build of
// `program_methods`
Self::new(PINATA_ELF.to_vec()).unwrap()
}
#[must_use]
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")
@ -130,6 +137,7 @@ mod tests {
impl Program {
/// A program that changes the nonce of an account
#[must_use]
pub fn nonce_changer_program() -> Self {
use test_program_methods::{NONCE_CHANGER_ELF, NONCE_CHANGER_ID};
@ -140,6 +148,7 @@ mod tests {
}
/// A program that produces more output accounts than the inputs it received
#[must_use]
pub fn extra_output_program() -> Self {
use test_program_methods::{EXTRA_OUTPUT_ELF, EXTRA_OUTPUT_ID};
@ -150,6 +159,7 @@ mod tests {
}
/// A program that produces less output accounts than the inputs it received
#[must_use]
pub fn missing_output_program() -> Self {
use test_program_methods::{MISSING_OUTPUT_ELF, MISSING_OUTPUT_ID};
@ -160,6 +170,7 @@ mod tests {
}
/// A program that changes the program owner of an account to [0, 1, 2, 3, 4, 5, 6, 7]
#[must_use]
pub fn program_owner_changer() -> Self {
use test_program_methods::{PROGRAM_OWNER_CHANGER_ELF, PROGRAM_OWNER_CHANGER_ID};
@ -170,6 +181,7 @@ mod tests {
}
/// A program that transfers balance without caring about authorizations
#[must_use]
pub fn simple_balance_transfer() -> Self {
use test_program_methods::{SIMPLE_BALANCE_TRANSFER_ELF, SIMPLE_BALANCE_TRANSFER_ID};
@ -180,6 +192,7 @@ mod tests {
}
/// A program that modifies the data of an account
#[must_use]
pub fn data_changer() -> Self {
use test_program_methods::{DATA_CHANGER_ELF, DATA_CHANGER_ID};
@ -190,6 +203,7 @@ mod tests {
}
/// A program that mints balance
#[must_use]
pub fn minter() -> Self {
use test_program_methods::{MINTER_ELF, MINTER_ID};
@ -200,6 +214,7 @@ mod tests {
}
/// A program that burns balance
#[must_use]
pub fn burner() -> Self {
use test_program_methods::{BURNER_ELF, BURNER_ID};
@ -209,6 +224,7 @@ mod tests {
}
}
#[must_use]
pub fn chain_caller() -> Self {
use test_program_methods::{CHAIN_CALLER_ELF, CHAIN_CALLER_ID};
@ -218,6 +234,7 @@ mod tests {
}
}
#[must_use]
pub fn claimer() -> Self {
use test_program_methods::{CLAIMER_ELF, CLAIMER_ID};
@ -227,6 +244,7 @@ mod tests {
}
}
#[must_use]
pub fn changer_claimer() -> Self {
use test_program_methods::{CHANGER_CLAIMER_ELF, CHANGER_CLAIMER_ID};
@ -236,6 +254,7 @@ mod tests {
}
}
#[must_use]
pub fn noop() -> Self {
use test_program_methods::{NOOP_ELF, NOOP_ID};
@ -245,6 +264,7 @@ mod tests {
}
}
#[must_use]
pub fn malicious_authorization_changer() -> Self {
use test_program_methods::{
MALICIOUS_AUTHORIZATION_CHANGER_ELF, MALICIOUS_AUTHORIZATION_CHANGER_ID,
@ -256,6 +276,7 @@ mod tests {
}
}
#[must_use]
pub fn modified_transfer_program() -> Self {
use test_program_methods::MODIFIED_TRANSFER_ELF;
// This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of
@ -267,11 +288,11 @@ mod tests {
#[test]
fn test_program_execution() {
let program = Program::simple_balance_transfer();
let balance_to_move: u128 = 11223344556677;
let balance_to_move: u128 = 11_223_344_556_677;
let instruction_data = Program::serialize_instruction(balance_to_move).unwrap();
let sender = AccountWithMetadata::new(
Account {
balance: 77665544332211,
balance: 77_665_544_332_211,
..Account::default()
},
true,
@ -281,7 +302,7 @@ mod tests {
AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32]));
let expected_sender_post = Account {
balance: 77665544332211 - balance_to_move,
balance: 77_665_544_332_211 - balance_to_move,
..Account::default()
};
let expected_recipient_post = Account {

View File

@ -6,10 +6,12 @@ pub struct Message {
}
impl Message {
#[must_use]
pub fn new(bytecode: Vec<u8>) -> Self {
Self { bytecode }
}
#[must_use]
pub fn into_bytecode(self) -> Vec<u8> {
self.bytecode
}

View File

@ -12,10 +12,12 @@ pub struct ProgramDeploymentTransaction {
}
impl ProgramDeploymentTransaction {
#[must_use]
pub fn new(message: Message) -> Self {
Self { message }
}
#[must_use]
pub fn into_message(self) -> Message {
self.message
}
@ -33,6 +35,7 @@ impl ProgramDeploymentTransaction {
}
}
#[must_use]
pub fn hash(&self) -> [u8; 32] {
let bytes = self.to_bytes();
let mut hasher = sha2::Sha256::new();
@ -40,6 +43,7 @@ impl ProgramDeploymentTransaction {
hasher.finalize_fixed().into()
}
#[must_use]
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
vec![]
}

View File

@ -49,6 +49,7 @@ impl Message {
})
}
#[must_use]
pub fn new_preserialized(
program_id: ProgramId,
account_ids: Vec<AccountId>,

View File

@ -22,6 +22,7 @@ pub struct PublicTransaction {
}
impl PublicTransaction {
#[must_use]
pub fn new(message: Message, witness_set: WitnessSet) -> Self {
Self {
message,
@ -29,10 +30,12 @@ impl PublicTransaction {
}
}
#[must_use]
pub fn message(&self) -> &Message {
&self.message
}
#[must_use]
pub fn witness_set(&self) -> &WitnessSet {
&self.witness_set
}
@ -45,6 +48,7 @@ impl PublicTransaction {
.collect()
}
#[must_use]
pub fn affected_public_account_ids(&self) -> Vec<AccountId> {
let mut acc_set = self
.signer_account_ids()
@ -55,6 +59,7 @@ impl PublicTransaction {
acc_set.into_iter().collect()
}
#[must_use]
pub fn hash(&self) -> [u8; 32] {
let bytes = self.to_bytes();
let mut hasher = sha2::Sha256::new();
@ -353,7 +358,7 @@ pub mod tests {
let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
#[test]
@ -373,7 +378,7 @@ pub mod tests {
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
#[test]
@ -394,7 +399,7 @@ pub mod tests {
witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
#[test]
@ -414,7 +419,7 @@ pub mod tests {
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
#[test]
@ -423,13 +428,13 @@ pub mod tests {
let state = state_for_tests();
let nonces = vec![0, 0];
let instruction = 1337;
let unknown_program_id = [0xdeadbeef; 8];
let unknown_program_id = [0xdead_beef; 8];
let message =
Message::try_new(unknown_program_id, vec![addr1, addr2], nonces, instruction).unwrap();
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
let tx = PublicTransaction::new(message, witness_set);
let result = tx.validate_and_produce_public_state_diff(&state);
assert!(matches!(result, Err(NssaError::InvalidInput(_))))
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
}

View File

@ -8,6 +8,7 @@ pub struct WitnessSet {
}
impl WitnessSet {
#[must_use]
pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self {
let message_bytes = message.to_bytes();
let signatures_and_public_keys = private_keys
@ -24,6 +25,7 @@ impl WitnessSet {
}
}
#[must_use]
pub fn is_valid_for(&self, message: &Message) -> bool {
let message_bytes = message.to_bytes();
for (signature, public_key) in self.signatures_and_public_keys() {
@ -34,14 +36,17 @@ impl WitnessSet {
true
}
#[must_use]
pub fn signatures_and_public_keys(&self) -> &[(Signature, PublicKey)] {
&self.signatures_and_public_keys
}
#[must_use]
pub fn into_raw_parts(self) -> Vec<(Signature, PublicKey)> {
self.signatures_and_public_keys
}
#[must_use]
pub fn from_raw_parts(signatures_and_public_keys: Vec<(Signature, PublicKey)>) -> Self {
Self {
signatures_and_public_keys,

View File

@ -14,7 +14,7 @@ pub struct TestVector {
}
/// Test vectors from
/// https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv
/// <https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv>
//
pub fn test_vectors() -> Vec<TestVector> {
vec![

View File

@ -18,6 +18,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];
OsRng.fill_bytes(&mut aux_random);
@ -39,6 +40,7 @@ impl Signature {
Self { value }
}
#[must_use]
pub fn is_valid_for(&self, bytes: &[u8], public_key: &PublicKey) -> bool {
let pk = secp256k1::XOnlyPublicKey::from_byte_array(*public_key.value()).unwrap();
let secp = secp256k1::Secp256k1::new();

View File

@ -9,14 +9,14 @@ use crate::error::NssaError;
pub struct PrivateKey([u8; 32]);
impl PrivateKey {
#[must_use]
pub fn new_os_random() -> Self {
let mut rng = OsRng;
loop {
match Self::try_new(rng.r#gen()) {
Ok(key) => break key,
Err(_) => continue,
};
if let Ok(key) = Self::try_new(rng.r#gen()) {
break key;
}
}
}
@ -32,6 +32,7 @@ impl PrivateKey {
}
}
#[must_use]
pub fn value(&self) -> &[u8; 32] {
&self.0
}

View File

@ -29,6 +29,7 @@ impl BorshDeserialize for PublicKey {
}
impl PublicKey {
#[must_use]
pub fn new_from_private_key(key: &PrivateKey) -> Self {
let value = {
let secret_key = secp256k1::SecretKey::from_byte_array(*key.value()).unwrap();
@ -47,6 +48,7 @@ impl PublicKey {
Ok(Self(value))
}
#[must_use]
pub fn value(&self) -> &[u8; 32] {
&self.0
}

View File

@ -52,8 +52,8 @@ impl CommitmentSet {
}
/// Initializes an empty `CommitmentSet` with a given capacity.
/// If the capacity is not a power_of_two, then capacity is taken
/// to be the next power_of_two.
/// If the capacity is not a `power_of_two`, then capacity is taken
/// to be the next `power_of_two`.
pub(crate) fn with_capacity(capacity: usize) -> CommitmentSet {
Self {
merkle_tree: MerkleTree::with_capacity(capacity),
@ -114,6 +114,7 @@ pub struct V02State {
}
impl V02State {
#[must_use]
pub fn new_with_genesis_accounts(
initial_data: &[(AccountId, u128)],
initial_commitments: &[nssa_core::Commitment],
@ -159,7 +160,7 @@ impl V02State {
) -> Result<(), NssaError> {
let state_diff = tx.validate_and_produce_public_state_diff(self)?;
for (account_id, post) in state_diff.into_iter() {
for (account_id, post) in state_diff {
let current_account = self.get_account_by_id_mut(account_id);
*current_account = post;
@ -195,7 +196,7 @@ impl V02State {
self.private_state.1.extend(new_nullifiers);
// 4. Update public accounts
for (account_id, post) in public_state_diff.into_iter() {
for (account_id, post) in public_state_diff {
let current_account = self.get_account_by_id_mut(account_id);
*current_account = post;
}
@ -222,6 +223,7 @@ impl V02State {
self.public_state.entry(account_id).or_default()
}
#[must_use]
pub fn get_account_by_id(&self, account_id: AccountId) -> Account {
self.public_state
.get(&account_id)
@ -229,6 +231,7 @@ impl V02State {
.unwrap_or(Account::default())
}
#[must_use]
pub fn get_proof_for_commitment(&self, commitment: &Commitment) -> Option<MembershipProof> {
self.private_state.0.get_proof_for(commitment)
}
@ -237,6 +240,7 @@ impl V02State {
&self.programs
}
#[must_use]
pub fn commitment_set_digest(&self) -> CommitmentSetDigest {
self.private_state.0.digest()
}
@ -245,7 +249,7 @@ impl V02State {
&self,
new_commitments: &[Commitment],
) -> Result<(), NssaError> {
for commitment in new_commitments.iter() {
for commitment in new_commitments {
if self.private_state.0.contains(commitment) {
return Err(NssaError::InvalidInput(
"Commitment already seen".to_string(),
@ -259,7 +263,7 @@ impl V02State {
&self,
new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
) -> Result<(), NssaError> {
for (nullifier, digest) in new_nullifiers.iter() {
for (nullifier, digest) in new_nullifiers {
if self.private_state.1.contains(nullifier) {
return Err(NssaError::InvalidInput(
"Nullifier already seen".to_string(),
@ -284,7 +288,7 @@ impl V02State {
account_id,
Account {
program_owner: Program::pinata().id(),
balance: 1500000,
balance: 1_500_000,
// Difficulty: 3
data: vec![3; 33].try_into().expect("should fit"),
nonce: 0,
@ -339,7 +343,7 @@ pub mod tests {
fn transfer_transaction(
from: AccountId,
from_key: PrivateKey,
from_key: &PrivateKey,
nonce: u128,
to: AccountId,
balance: u128,
@ -349,7 +353,7 @@ pub mod tests {
let program_id = Program::authenticated_transfer_program().id();
let message =
public_transaction::Message::try_new(program_id, account_ids, nonces, balance).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
let witness_set = public_transaction::WitnessSet::for_message(&message, &[from_key]);
PublicTransaction::new(message, witness_set)
}
@ -454,7 +458,7 @@ pub mod tests {
assert_eq!(state.get_account_by_id(to), Account::default());
let balance_to_move = 5;
let tx = transfer_transaction(from, key, 0, to, balance_to_move);
let tx = transfer_transaction(from, &key, 0, to, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_id(from).balance, 95);
@ -475,7 +479,7 @@ pub mod tests {
let balance_to_move = 101;
assert!(state.get_account_by_id(from).balance < balance_to_move);
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
@ -499,7 +503,7 @@ pub mod tests {
assert_ne!(state.get_account_by_id(to), Account::default());
let balance_to_move = 8;
let tx = transfer_transaction(from, from_key, 0, to, balance_to_move);
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_id(from).balance, 192);
@ -519,10 +523,10 @@ pub mod tests {
let account_id3 = AccountId::new([3; 32]);
let balance_to_move = 5;
let tx = transfer_transaction(account_id1, key1, 0, account_id2, balance_to_move);
let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
let balance_to_move = 3;
let tx = transfer_transaction(account_id2, key2, 0, account_id3, balance_to_move);
let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_id(account_id1).balance, 95);
@ -539,6 +543,7 @@ pub mod tests {
}
/// Include test programs in the builtin programs map
#[must_use]
pub fn with_test_programs(mut self) -> Self {
self.insert_program(Program::nonce_changer_program());
self.insert_program(Program::extra_output_program());
@ -555,6 +560,7 @@ pub mod tests {
self
}
#[must_use]
pub fn with_non_default_accounts_but_default_program_owners(mut self) -> Self {
let account_with_default_values_except_balance = Account {
balance: 100,
@ -583,6 +589,7 @@ pub mod tests {
self
}
#[must_use]
pub fn with_account_owned_by_burner_program(mut self) -> Self {
let account = Account {
program_owner: Program::burner().id(),
@ -593,6 +600,7 @@ pub mod tests {
self
}
#[must_use]
pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self {
let commitment = Commitment::new(&keys.npk(), account);
self.private_state.0.extend(&[commitment]);
@ -916,7 +924,7 @@ pub mod tests {
vec![sender, recipient],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![0, 2],
vec![0xdeadbeef],
vec![0xdead_beef],
vec![(recipient_keys.npk(), shared_secret)],
vec![],
vec![None],
@ -1084,7 +1092,7 @@ pub mod tests {
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
nonce: 0xdead_beef,
data: Data::default(),
};
let recipient_keys = test_private_account_keys_2();
@ -1099,7 +1107,7 @@ pub mod tests {
&sender_private_account,
&recipient_keys,
balance_to_move,
[0xcafecafe, 0xfecafeca],
[0xcafe_cafe, 0xfeca_feca],
&state,
);
@ -1107,7 +1115,7 @@ pub mod tests {
&sender_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
nonce: 0xcafecafe,
nonce: 0xcafe_cafe,
balance: sender_private_account.balance - balance_to_move,
data: Data::default(),
},
@ -1121,7 +1129,7 @@ pub mod tests {
&recipient_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
nonce: 0xfecafeca,
nonce: 0xfeca_feca,
balance: balance_to_move,
..Account::default()
},
@ -1150,7 +1158,7 @@ pub mod tests {
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
nonce: 0xdead_beef,
data: Data::default(),
};
let recipient_keys = test_public_account_keys_1();
@ -1174,7 +1182,7 @@ pub mod tests {
&sender_private_account,
&recipient_keys.account_id(),
balance_to_move,
0xcafecafe,
0xcafe_cafe,
&state,
);
@ -1182,7 +1190,7 @@ pub mod tests {
&sender_keys.npk(),
&Account {
program_owner: Program::authenticated_transfer_program().id(),
nonce: 0xcafecafe,
nonce: 0xcafe_cafe,
balance: sender_private_account.balance - balance_to_move,
data: Data::default(),
},
@ -1333,7 +1341,12 @@ pub mod tests {
);
let large_data: Vec<u8> =
vec![0; nssa_core::account::data::DATA_MAX_LENGTH.as_u64() as usize + 1];
vec![
0;
usize::try_from(nssa_core::account::data::DATA_MAX_LENGTH.as_u64())
.expect("DATA_MAX_LENGTH fits in usize")
+ 1
];
let result = execute_and_prove(
vec![public_account],
@ -1343,7 +1356,7 @@ pub mod tests {
vec![],
vec![],
vec![],
&program.to_owned().into(),
&program.clone().into(),
);
assert!(matches!(result, Err(NssaError::ProgramProveFailed(_))));
@ -1531,7 +1544,7 @@ pub mod tests {
AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk());
// Setting only one nonce for an execution with two private accounts.
let private_account_nonces = [0xdeadbeef1];
let private_account_nonces = [0xdead_beef1];
let result = execute_and_prove(
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
@ -1580,7 +1593,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
private_account_keys.to_vec(),
vec![sender_keys.nsk],
vec![Some((0, vec![]))],
@ -1613,7 +1626,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(
sender_keys.npk(),
@ -1655,7 +1668,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(
sender_keys.npk(),
@ -1713,7 +1726,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
private_account_keys.to_vec(),
private_account_nsks.to_vec(),
private_account_membership_proofs.to_vec(),
@ -1751,7 +1764,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(
sender_keys.npk(),
@ -1799,7 +1812,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(
sender_keys.npk(),
@ -1846,7 +1859,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(
sender_keys.npk(),
@ -1882,7 +1895,7 @@ pub mod tests {
let private_account_2 = AccountWithMetadata::new(
Account {
// Non default nonce
nonce: 0xdeadbeef,
nonce: 0xdead_beef,
..Account::default()
},
false,
@ -1893,7 +1906,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(
sender_keys.npk(),
@ -1938,7 +1951,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(
sender_keys.npk(),
@ -2006,7 +2019,7 @@ pub mod tests {
// Setting three new private account nonces for a circuit execution with only two private
// accounts.
let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3];
let private_account_nonces = [0xdead_beef1, 0xdead_beef2, 0xdead_beef3];
let result = execute_and_prove(
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
@ -2067,7 +2080,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
vec![1, 2],
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
private_account_keys.to_vec(),
vec![sender_keys.nsk],
vec![Some((0, vec![]))],
@ -2103,7 +2116,7 @@ pub mod tests {
vec![private_account_1, private_account_2],
Program::serialize_instruction(10u128).unwrap(),
visibility_mask.to_vec(),
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(
sender_keys.npk(),
@ -2128,7 +2141,7 @@ pub mod tests {
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100,
nonce: 0xdeadbeef,
nonce: 0xdead_beef,
data: Data::default(),
};
let recipient_keys = test_private_account_keys_2();
@ -2143,7 +2156,7 @@ pub mod tests {
&sender_private_account,
&recipient_keys,
balance_to_move,
[0xcafecafe, 0xfecafeca],
[0xcafe_cafe, 0xfeca_feca],
&state,
);
@ -2154,7 +2167,7 @@ pub mod tests {
let sender_private_account = Account {
program_owner: Program::authenticated_transfer_program().id(),
balance: 100 - balance_to_move,
nonce: 0xcafecafe,
nonce: 0xcafe_cafe,
data: Data::default(),
};
@ -2199,7 +2212,7 @@ pub mod tests {
vec![private_account_1.clone(), private_account_1],
Program::serialize_instruction(100u128).unwrap(),
visibility_mask.to_vec(),
vec![0xdeadbeef1, 0xdeadbeef2],
vec![0xdead_beef1, 0xdead_beef2],
vec![
(sender_keys.npk(), shared_secret),
(sender_keys.npk(), shared_secret),
@ -2308,7 +2321,8 @@ pub mod tests {
let instruction: (u128, ProgramId, u32, Option<PdaSeed>) = (
amount,
Program::authenticated_transfer_program().id(),
MAX_NUMBER_CHAINED_CALLS as u32 + 1,
u32::try_from(MAX_NUMBER_CHAINED_CALLS).expect("MAX_NUMBER_CHAINED_CALLS fits in u32")
+ 1,
None,
);
@ -3829,18 +3843,18 @@ pub mod tests {
dependencies.insert(auth_transfers.id(), auth_transfers);
let program_with_deps = ProgramWithDependencies::new(chain_caller, dependencies);
let from_new_nonce = 0xdeadbeef1;
let to_new_nonce = 0xdeadbeef2;
let from_new_nonce = 0xdead_beef1;
let to_new_nonce = 0xdead_beef2;
let from_expected_post = Account {
balance: initial_balance - number_of_calls as u128 * amount,
balance: initial_balance - u128::from(number_of_calls) * amount,
nonce: from_new_nonce,
..from_account.account.clone()
};
let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post);
let to_expected_post = Account {
balance: number_of_calls as u128 * amount,
balance: u128::from(number_of_calls) * amount,
nonce: to_new_nonce,
..to_account.account.clone()
};
@ -3948,7 +3962,7 @@ pub mod tests {
state.transition_from_public_transaction(&tx).unwrap();
// Submit a solution to the pinata program to claim the prize
let solution: u128 = 989106;
let solution: u128 = 989_106;
let message = public_transaction::Message::try_new(
pinata_token.id(),
vec![
@ -3994,7 +4008,7 @@ pub mod tests {
let result = state.transition_from_public_transaction(&tx);
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)))
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
}
/// This test ensures that even if a malicious program tries to perform overflow of balances
@ -4083,7 +4097,7 @@ pub mod tests {
// Balance to initialize the account with (0 for a new account)
let balance: u128 = 0;
let nonce = 0xdeadbeef1;
let nonce = 0xdead_beef1;
// Execute and prove the circuit with the authorized account but no commitment proof
let (output, proof) = execute_and_prove(
@ -4136,7 +4150,7 @@ pub mod tests {
let epk = EphemeralPublicKey::from_scalar(esk);
let balance: u128 = 0;
let nonce = 0xdeadbeef1;
let nonce = 0xdead_beef1;
// Step 2: Execute claimer program to claim the account with authentication
let (output, proof) = execute_and_prove(
@ -4184,7 +4198,7 @@ pub mod tests {
let esk2 = [4; 32];
let shared_secret2 = SharedSecretKey::new(&esk2, &private_keys.vpk());
let nonce2 = 0xdeadbeef2;
let nonce2 = 0xdead_beef2;
// Step 3: Try to execute noop program with authentication but without initialization
let res = execute_and_prove(
@ -4341,7 +4355,7 @@ pub mod tests {
dependencies.insert(auth_transfers.id(), auth_transfers);
let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies);
let recipient_new_nonce = 0xdeadbeef1;
let recipient_new_nonce = 0xdead_beef1;
// Act - execute the malicious program - this should fail during proving
let result = execute_and_prove(

View File

@ -7,18 +7,17 @@ use nssa_core::{
/// Initializes a default account under the ownership of this program.
fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState {
let account_to_claim = AccountPostState::new_claimed(pre_state.account.clone());
let account_to_claim = AccountPostState::new_claimed(pre_state.account);
let is_authorized = pre_state.is_authorized;
// Continue only if the account to claim has default values
if account_to_claim.account() != &Account::default() {
panic!("Account must be uninitialized");
}
assert!(
account_to_claim.account() == &Account::default(),
"Account must be uninitialized"
);
// Continue only if the owner authorized this operation
if !is_authorized {
panic!("Account must be authorized");
}
assert!(is_authorized, "Account must be authorized");
account_to_claim
}
@ -30,26 +29,25 @@ fn transfer(
balance_to_move: u128,
) -> Vec<AccountPostState> {
// Continue only if the sender has authorized this operation
if !sender.is_authorized {
panic!("Sender must be authorized");
}
assert!(sender.is_authorized, "Sender must be authorized");
// Continue only if the sender has enough balance
if sender.account.balance < balance_to_move {
panic!("Sender has insufficient 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.clone();
let mut sender_post_account = sender.account;
sender_post_account.balance -= balance_to_move;
AccountPostState::new(sender_post_account)
};
let recipient_post = {
// Modify recipient's balance
let mut recipient_post_account = recipient.account.clone();
let mut recipient_post_account = recipient.account;
recipient_post_account.balance += balance_to_move;
// Claim recipient account if it has default program owner

View File

@ -52,9 +52,8 @@ fn main() {
instruction_words,
) = read_nssa_inputs::<Instruction>();
let [pinata, winner] = match pre_states.try_into() {
Ok(array) => array,
Err(_) => return,
let Ok([pinata, winner]) = <[_; 2]>::try_from(pre_states) else {
return;
};
let data = Challenge::new(&pinata.account.data);

View File

@ -59,13 +59,15 @@ fn main() {
instruction_words,
) = read_nssa_inputs::<Instruction>();
let [
pinata_definition,
pinata_token_holding,
winner_token_holding,
] = match pre_states.try_into() {
Ok(array) => array,
Err(_) => return,
let Ok(
[
pinata_definition,
pinata_token_holding,
winner_token_holding,
],
) = <[_; 3]>::try_from(pre_states)
else {
return;
};
let data = Challenge::new(&pinata_definition.account.data);

View File

@ -113,7 +113,7 @@ impl ExecutionState {
);
execution_state.validate_and_sync_states(
chained_call.program_id,
authorized_pdas,
&authorized_pdas,
program_output.pre_states,
program_output.post_states,
);
@ -153,7 +153,7 @@ impl ExecutionState {
fn validate_and_sync_states(
&mut self,
program_id: ProgramId,
authorized_pdas: HashSet<AccountId>,
authorized_pdas: &HashSet<AccountId>,
pre_states: Vec<AccountWithMetadata>,
post_states: Vec<AccountPostState>,
) {
@ -173,12 +173,12 @@ impl ExecutionState {
.pre_states
.iter()
.find(|acc| acc.account_id == pre_account_id)
.map(|acc| acc.is_authorized)
.unwrap_or_else(|| {
panic!(
.map_or_else(
|| panic!(
"Pre state must exist in execution state for account {pre_account_id:?}",
)
});
),
|acc| acc.is_authorized
);
let is_authorized =
previous_is_authorized || authorized_pdas.contains(&pre_account_id);
@ -379,18 +379,8 @@ fn compute_nullifier_and_set_digest(
npk: &NullifierPublicKey,
nsk: &NullifierSecretKey,
) -> (Nullifier, CommitmentSetDigest) {
membership_proof_opt
.as_ref()
.map(|membership_proof| {
// Compute commitment set digest associated with provided auth path
let commitment_pre = Commitment::new(npk, pre_account);
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
// Compute update nullifier
let nullifier = Nullifier::for_account_update(&commitment_pre, nsk);
(nullifier, set_digest)
})
.unwrap_or_else(|| {
membership_proof_opt.as_ref().map_or_else(
|| {
assert_eq!(
*pre_account,
Account::default(),
@ -400,5 +390,15 @@ fn compute_nullifier_and_set_digest(
// Compute initialization nullifier
let nullifier = Nullifier::for_account_initialization(npk);
(nullifier, DUMMY_COMMITMENT_HASH)
})
},
|membership_proof| {
// Compute commitment set digest associated with provided auth path
let commitment_pre = Commitment::new(npk, pre_account);
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
// Compute update nullifier
let nullifier = Nullifier::for_account_update(&commitment_pre, nsk);
(nullifier, set_digest)
},
)
}

View File

@ -89,7 +89,7 @@ pub struct PoolDefinition {
pub fees: u128,
/// A pool becomes inactive (active = false)
/// once all of its liquidity has been removed (e.g., reserves are emptied and
/// liquidity_pool_supply = 0)
/// `liquidity_pool_supply` = 0)
pub active: bool,
}
@ -113,6 +113,7 @@ impl From<&PoolDefinition> for Data {
}
}
#[must_use]
pub fn compute_pool_pda(
amm_program_id: ProgramId,
definition_token_a_id: AccountId,
@ -124,6 +125,7 @@ pub fn compute_pool_pda(
))
}
#[must_use]
pub fn compute_pool_pda_seed(
definition_token_a_id: AccountId,
definition_token_b_id: AccountId,
@ -151,6 +153,7 @@ pub fn compute_pool_pda_seed(
)
}
#[must_use]
pub fn compute_vault_pda(
amm_program_id: ProgramId,
pool_id: AccountId,
@ -162,6 +165,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};
@ -177,10 +181,12 @@ pub fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId
)
}
#[must_use]
pub fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId) -> AccountId {
AccountId::from((&amm_program_id, &compute_liquidity_token_pda_seed(pool_id)))
}
#[must_use]
pub fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed {
use risc0_zkvm::sha::{Impl, Sha256};

View File

@ -7,6 +7,7 @@ use nssa_core::{
};
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
#[must_use]
pub fn add_liquidity(
pool: AccountWithMetadata,
vault_a: AccountWithMetadata,
@ -123,7 +124,7 @@ pub fn add_liquidity(
);
// 5. Update pool account
let mut pool_post = pool.account.clone();
let mut pool_post = pool.account;
let pool_post_definition = PoolDefinition {
liquidity_pool_supply: pool_def_data.liquidity_pool_supply + delta_lp,
reserve_a: pool_def_data.reserve_a + actual_amount_a,
@ -166,12 +167,12 @@ pub fn add_liquidity(
let post_states = vec![
AccountPostState::new(pool_post),
AccountPostState::new(vault_a.account.clone()),
AccountPostState::new(vault_b.account.clone()),
AccountPostState::new(pool_definition_lp.account.clone()),
AccountPostState::new(user_holding_a.account.clone()),
AccountPostState::new(user_holding_b.account.clone()),
AccountPostState::new(user_holding_lp.account.clone()),
AccountPostState::new(vault_a.account),
AccountPostState::new(vault_b.account),
AccountPostState::new(pool_definition_lp.account),
AccountPostState::new(user_holding_a.account),
AccountPostState::new(user_holding_b.account),
AccountPostState::new(user_holding_lp.account),
];
(post_states, chained_calls)

View File

@ -10,6 +10,7 @@ use nssa_core::{
};
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
#[must_use]
pub fn new_definition(
pool: AccountWithMetadata,
vault_a: AccountWithMetadata,
@ -79,8 +80,20 @@ pub fn new_definition(
// LP Token minting calculation
let initial_lp = (token_a_amount.get() * token_b_amount.get()).isqrt();
// Chain call for liquidity token (TokenLP definition -> User LP Holding)
let instruction = if pool.account == Account::default() {
token_core::Instruction::NewFungibleDefinition {
name: String::from("LP Token"),
total_supply: initial_lp,
}
} else {
token_core::Instruction::Mint {
amount_to_mint: initial_lp,
}
};
// Update pool account
let mut pool_post = pool.account.clone();
let mut pool_post = pool.account;
let pool_post_definition = PoolDefinition {
definition_token_a_id,
definition_token_b_id,
@ -95,11 +108,7 @@ pub fn new_definition(
};
pool_post.data = Data::from(&pool_post_definition);
let pool_post: AccountPostState = if pool.account == Account::default() {
AccountPostState::new_claimed(pool_post.clone())
} else {
AccountPostState::new(pool_post.clone())
};
let pool_post = AccountPostState::new_claimed_if_default(pool_post);
let token_program_id = user_holding_a.account.program_owner;
@ -120,18 +129,6 @@ pub fn new_definition(
},
);
// Chain call for liquidity token (TokenLP definition -> User LP Holding)
let instruction = if pool.account == Account::default() {
token_core::Instruction::NewFungibleDefinition {
name: String::from("LP Token"),
total_supply: initial_lp,
}
} else {
token_core::Instruction::Mint {
amount_to_mint: initial_lp,
}
};
let mut pool_lp_auth = pool_definition_lp.clone();
pool_lp_auth.is_authorized = true;
@ -145,13 +142,13 @@ pub fn new_definition(
let chained_calls = vec![call_token_lp, call_token_b, call_token_a];
let post_states = vec![
pool_post.clone(),
AccountPostState::new(vault_a.account.clone()),
AccountPostState::new(vault_b.account.clone()),
AccountPostState::new(pool_definition_lp.account.clone()),
AccountPostState::new(user_holding_a.account.clone()),
AccountPostState::new(user_holding_b.account.clone()),
AccountPostState::new(user_holding_lp.account.clone()),
pool_post,
AccountPostState::new(vault_a.account),
AccountPostState::new(vault_b.account),
AccountPostState::new(pool_definition_lp.account),
AccountPostState::new(user_holding_a.account),
AccountPostState::new(user_holding_b.account),
AccountPostState::new(user_holding_lp.account),
];
(post_states.clone(), chained_calls)

View File

@ -7,6 +7,7 @@ use nssa_core::{
};
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
#[must_use]
pub fn remove_liquidity(
pool: AccountWithMetadata,
vault_a: AccountWithMetadata,
@ -101,7 +102,7 @@ pub fn remove_liquidity(
let active: bool = pool_def_data.liquidity_pool_supply - delta_lp != 0;
// 5. Update pool account
let mut pool_post = pool.account.clone();
let mut pool_post = pool.account;
let pool_post_definition = PoolDefinition {
liquidity_pool_supply: pool_def_data.liquidity_pool_supply - delta_lp,
reserve_a: pool_def_data.reserve_a - withdraw_amount_a,
@ -153,13 +154,13 @@ pub fn remove_liquidity(
let chained_calls = vec![call_token_lp, call_token_b, call_token_a];
let post_states = vec![
AccountPostState::new(pool_post.clone()),
AccountPostState::new(vault_a.account.clone()),
AccountPostState::new(vault_b.account.clone()),
AccountPostState::new(pool_definition_lp.account.clone()),
AccountPostState::new(user_holding_a.account.clone()),
AccountPostState::new(user_holding_b.account.clone()),
AccountPostState::new(user_holding_lp.account.clone()),
AccountPostState::new(pool_post),
AccountPostState::new(vault_a.account),
AccountPostState::new(vault_b.account),
AccountPostState::new(pool_definition_lp.account),
AccountPostState::new(user_holding_a.account),
AccountPostState::new(user_holding_b.account),
AccountPostState::new(user_holding_lp.account),
];
(post_states, chained_calls)

View File

@ -5,6 +5,7 @@ use nssa_core::{
};
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
#[must_use]
pub fn swap(
pool: AccountWithMetadata,
vault_a: AccountWithMetadata,
@ -95,7 +96,7 @@ pub fn swap(
};
// Update pool account
let mut pool_post = pool.account.clone();
let mut pool_post = pool.account;
let pool_post_definition = PoolDefinition {
reserve_a: pool_def_data.reserve_a + deposit_a - withdraw_a,
reserve_b: pool_def_data.reserve_b + deposit_b - withdraw_b,
@ -105,11 +106,11 @@ pub fn swap(
pool_post.data = Data::from(&pool_post_definition);
let post_states = vec![
AccountPostState::new(pool_post.clone()),
AccountPostState::new(vault_a.account.clone()),
AccountPostState::new(vault_b.account.clone()),
AccountPostState::new(user_holding_a.account.clone()),
AccountPostState::new(user_holding_b.account.clone()),
AccountPostState::new(pool_post),
AccountPostState::new(vault_a.account),
AccountPostState::new(vault_b.account),
AccountPostState::new(user_holding_a.account),
AccountPostState::new(user_holding_b.account),
];
(post_states, chained_calls)
@ -151,7 +152,7 @@ fn swap_logic(
},
));
let mut vault_withdraw = vault_withdraw.clone();
let mut vault_withdraw = vault_withdraw;
vault_withdraw.is_authorized = true;
let pda_seed = compute_vault_pda_seed(

View File

@ -127,6 +127,7 @@ pub enum TokenHolding {
}
impl TokenHolding {
#[must_use]
pub fn zeroized_clone_from(other: &Self) -> Self {
match other {
TokenHolding::Fungible { definition_id, .. } => TokenHolding::Fungible {
@ -144,6 +145,7 @@ impl TokenHolding {
}
}
#[must_use]
pub fn zeroized_from_definition(
definition_id: AccountId,
definition: &TokenDefinition,
@ -160,11 +162,12 @@ impl TokenHolding {
}
}
#[must_use]
pub fn definition_id(&self) -> AccountId {
match self {
TokenHolding::Fungible { definition_id, .. } => *definition_id,
TokenHolding::NftMaster { definition_id, .. } => *definition_id,
TokenHolding::NftPrintedCopy { definition_id, .. } => *definition_id,
TokenHolding::Fungible { definition_id, .. }
| TokenHolding::NftMaster { definition_id, .. }
| TokenHolding::NftPrintedCopy { definition_id, .. } => *definition_id,
}
}
}

View File

@ -4,6 +4,7 @@ use nssa_core::{
};
use token_core::{TokenDefinition, TokenHolding};
#[must_use]
pub fn burn(
definition_account: AccountWithMetadata,
user_holding_account: AccountWithMetadata,

View File

@ -4,6 +4,7 @@ use nssa_core::{
};
use token_core::{TokenDefinition, TokenHolding};
#[must_use]
pub fn initialize_account(
definition_account: AccountWithMetadata,
account_to_initialize: AccountWithMetadata,

View File

@ -4,6 +4,7 @@ use nssa_core::{
};
use token_core::{TokenDefinition, TokenHolding};
#[must_use]
pub fn mint(
definition_account: AccountWithMetadata,
user_holding_account: AccountWithMetadata,

View File

@ -6,6 +6,7 @@ use token_core::{
NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding, TokenMetadata,
};
#[must_use]
pub fn new_fungible_definition(
definition_target_account: AccountWithMetadata,
holding_target_account: AccountWithMetadata,
@ -46,6 +47,7 @@ pub fn new_fungible_definition(
]
}
#[must_use]
pub fn new_definition_with_metadata(
definition_target_account: AccountWithMetadata,
holding_target_account: AccountWithMetadata,
@ -107,13 +109,13 @@ pub fn new_definition_with_metadata(
primary_sale_date: 0u64, // TODO #261: future works to implement this
};
let mut definition_target_account_post = definition_target_account.account.clone();
let mut definition_target_account_post = definition_target_account.account;
definition_target_account_post.data = Data::from(&token_definition);
let mut holding_target_account_post = holding_target_account.account.clone();
let mut holding_target_account_post = holding_target_account.account;
holding_target_account_post.data = Data::from(&token_holding);
let mut metadata_target_account_post = metadata_target_account.account.clone();
let mut metadata_target_account_post = metadata_target_account.account;
metadata_target_account_post.data = Data::from(&token_metadata);
vec![

View File

@ -4,6 +4,7 @@ use nssa_core::{
};
use token_core::TokenHolding;
#[must_use]
pub fn print_nft(
master_account: AccountWithMetadata,
printed_account: AccountWithMetadata,

View File

@ -4,6 +4,7 @@ use nssa_core::{
};
use token_core::TokenHolding;
#[must_use]
pub fn transfer(
sender: AccountWithMetadata,
recipient: AccountWithMetadata,
@ -95,7 +96,7 @@ pub fn transfer(
_ => {
panic!("Mismatched token holding types for transfer");
}
};
}
let mut sender_post = sender.account;
sender_post.data = Data::from(&sender_holding);

View File

@ -77,20 +77,20 @@ pub trait BlockSettlementClientTrait: Clone {
/// A component that posts block data to logos blockchain
#[derive(Clone)]
pub struct BlockSettlementClient {
bedrock_client: BedrockClient,
bedrock_signing_key: Ed25519Key,
bedrock_channel_id: ChannelId,
client: BedrockClient,
signing_key: Ed25519Key,
channel_id: ChannelId,
}
impl BlockSettlementClientTrait for BlockSettlementClient {
fn new(config: &BedrockConfig, bedrock_signing_key: Ed25519Key) -> Result<Self> {
let bedrock_client =
fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result<Self> {
let client =
BedrockClient::new(config.backoff, config.node_url.clone(), config.auth.clone())
.context("Failed to initialize bedrock client")?;
Ok(Self {
bedrock_client,
bedrock_signing_key,
bedrock_channel_id: config.channel_id,
client,
signing_key,
channel_id: config.channel_id,
})
}
@ -99,7 +99,7 @@ impl BlockSettlementClientTrait for BlockSettlementClient {
Some(Op::ChannelInscribe(inscribe)) => (inscribe.parent, inscribe.id()),
_ => panic!("Expected ChannelInscribe op"),
};
self.bedrock_client
self.client
.post_transaction(tx)
.await
.context("Failed to post transaction to Bedrock")?;
@ -110,11 +110,11 @@ impl BlockSettlementClientTrait for BlockSettlementClient {
}
fn bedrock_channel_id(&self) -> ChannelId {
self.bedrock_channel_id
self.channel_id
}
fn bedrock_signing_key(&self) -> &Ed25519Key {
&self.bedrock_signing_key
&self.signing_key
}
}

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