From a3163dbe5856721b9e5013d1321202ef6d74fcb3 Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Wed, 6 May 2026 17:08:15 -0300 Subject: [PATCH] chore(lint): add staged lint baseline --- .github/workflows/ci.yml | 17 ++++++++++- Cargo.toml | 38 +++++++++++++++++++++++++ amm/Cargo.toml | 3 ++ amm/core/Cargo.toml | 3 ++ amm/core/src/lib.rs | 20 +++++++------ amm/methods/Cargo.toml | 3 ++ amm/methods/guest/Cargo.toml | 26 +++++++++++++++++ amm/methods/guest/src/bin/amm.rs | 28 +++++++++++++++++-- amm/src/add.rs | 17 +++++++---- amm/src/new_definition.rs | 9 ++++-- amm/src/remove.rs | 16 ++++++++--- amm/src/swap.rs | 42 +++++++++++++++++++++------- amm/src/tests.rs | 18 ++++++++---- ata/Cargo.toml | 3 ++ ata/core/Cargo.toml | 3 ++ ata/core/src/lib.rs | 14 ++++++---- ata/methods/Cargo.toml | 3 ++ ata/methods/guest/Cargo.toml | 26 +++++++++++++++++ ata/methods/guest/src/bin/ata.rs | 8 ++++-- clippy.toml | 11 ++++++++ integration_tests/Cargo.toml | 3 ++ integration_tests/tests/amm.rs | 11 ++++++++ integration_tests/tests/token.rs | 1 + token/Cargo.toml | 3 ++ token/core/Cargo.toml | 3 ++ token/methods/Cargo.toml | 3 ++ token/methods/guest/Cargo.toml | 26 +++++++++++++++++ token/methods/guest/src/bin/token.rs | 12 ++++++-- token/src/print_nft.rs | 4 ++- token/src/tests.rs | 4 +++ tools/idl-gen/Cargo.toml | 3 ++ tools/idl-gen/src/main.rs | 8 +++++- 32 files changed, 340 insertions(+), 49 deletions(-) create mode 100644 clippy.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3b0b22..570ca37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,22 @@ jobs: ${{ runner.os }}-cargo- - name: Clippy - run: RISC0_SKIP_BUILD=1 cargo clippy --workspace --all-targets -- -D warnings + run: | + if rg -n -U --multiline '#\[(allow|expect)\([\s\S]*?reason\s*=\s*"(TODO|FIXME|fix later|later|temporary|hack)' token amm ata integration_tests tools -g '*.rs'; then + echo "Found non-actionable lint suppression reason" + exit 1 + fi + RISC0_SKIP_BUILD=1 cargo clippy --workspace --all-targets -- -D warnings + + - name: Guest Clippy + run: | + for manifest in \ + token/methods/guest/Cargo.toml \ + amm/methods/guest/Cargo.toml \ + ata/methods/guest/Cargo.toml + do + cargo clippy --manifest-path "$manifest" --all-targets -- -D warnings + done unit-tests: name: Unit Tests diff --git a/Cargo.toml b/Cargo.toml index b7a0a1e..3f8b654 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,3 +39,41 @@ borsh = { version = "1.0", features = ["derive"] } risc0-zkvm = { version = "=3.0.5" } serde_json = "1.0" tokio = { version = "1.28.2", features = ["net", "rt-multi-thread", "sync", "macros"] } + +[workspace.lints.rust] +rust_2018_idioms = { level = "deny", priority = -1 } +unsafe_code = "forbid" + +[workspace.lints.clippy] +all = { level = "deny", priority = -1 } + +# Generated-code / placeholder blockers. +dbg_macro = "deny" +todo = "deny" +unimplemented = "deny" +unwrap_used = "deny" + +# Lint suppression hygiene. +allow_attributes = "warn" +allow_attributes_without_reason = "deny" + +# Determinism, panic-safety, and arithmetic correctness. +arithmetic_side_effects = "deny" +indexing_slicing = "deny" +integer_division = "warn" + +# Cast discipline. +as_conversions = "deny" +cast_possible_truncation = "deny" +cast_possible_wrap = "deny" +cast_precision_loss = "warn" +cast_sign_loss = "deny" + +# API and enum evolution. +large_enum_variant = "deny" +match_wildcard_for_single_variants = "warn" +wildcard_enum_match_arm = "deny" + +# Too noisy for this codebase unless enforced selectively. +module_name_repetitions = "allow" +similar_names = "allow" diff --git a/amm/Cargo.toml b/amm/Cargo.toml index 9ca582f..28dce03 100644 --- a/amm/Cargo.toml +++ b/amm/Cargo.toml @@ -3,6 +3,9 @@ name = "amm_program" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [dependencies] nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] } amm_core = { path = "core" } diff --git a/amm/core/Cargo.toml b/amm/core/Cargo.toml index c832b9b..f45bcbd 100644 --- a/amm/core/Cargo.toml +++ b/amm/core/Cargo.toml @@ -3,6 +3,9 @@ name = "amm_core" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [dependencies] nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] } spel-framework-macros = { git = "https://github.com/logos-co/spel.git", tag = "v0.3.0", package = "spel-framework-macros" } diff --git a/amm/core/src/lib.rs b/amm/core/src/lib.rs index 60366df..b041429 100644 --- a/amm/core/src/lib.rs +++ b/amm/core/src/lib.rs @@ -204,8 +204,9 @@ pub fn compute_pool_pda_seed( }; let mut bytes = [0; 64]; - bytes[0..32].copy_from_slice(&token_1.to_bytes()); - bytes[32..].copy_from_slice(&token_2.to_bytes()); + let (token_1_bytes, token_2_bytes) = bytes.split_at_mut(32); + token_1_bytes.copy_from_slice(&token_1.to_bytes()); + token_2_bytes.copy_from_slice(&token_2.to_bytes()); PdaSeed::new( Impl::hash_bytes(&bytes) @@ -230,8 +231,9 @@ pub fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId use risc0_zkvm::sha::{Impl, Sha256}; let mut bytes = [0; 64]; - bytes[0..32].copy_from_slice(&pool_id.to_bytes()); - bytes[32..].copy_from_slice(&definition_token_id.to_bytes()); + let (pool_bytes, definition_bytes) = bytes.split_at_mut(32); + pool_bytes.copy_from_slice(&pool_id.to_bytes()); + definition_bytes.copy_from_slice(&definition_token_id.to_bytes()); PdaSeed::new( Impl::hash_bytes(&bytes) @@ -249,8 +251,9 @@ pub fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed { use risc0_zkvm::sha::{Impl, Sha256}; let mut bytes = [0; 64]; - bytes[0..32].copy_from_slice(&pool_id.to_bytes()); - bytes[32..].copy_from_slice(&LIQUIDITY_TOKEN_PDA_SEED); + let (pool_bytes, seed_bytes) = bytes.split_at_mut(32); + pool_bytes.copy_from_slice(&pool_id.to_bytes()); + seed_bytes.copy_from_slice(&LIQUIDITY_TOKEN_PDA_SEED); PdaSeed::new( Impl::hash_bytes(&bytes) @@ -268,8 +271,9 @@ pub fn compute_lp_lock_holding_pda_seed(pool_id: AccountId) -> PdaSeed { use risc0_zkvm::sha::{Impl, Sha256}; let mut bytes = [0; 64]; - bytes[0..32].copy_from_slice(&pool_id.to_bytes()); - bytes[32..].copy_from_slice(&LP_LOCK_HOLDING_PDA_SEED); + let (pool_bytes, seed_bytes) = bytes.split_at_mut(32); + pool_bytes.copy_from_slice(&pool_id.to_bytes()); + seed_bytes.copy_from_slice(&LP_LOCK_HOLDING_PDA_SEED); PdaSeed::new( Impl::hash_bytes(&bytes) diff --git a/amm/methods/Cargo.toml b/amm/methods/Cargo.toml index 5bc00df..1139347 100644 --- a/amm/methods/Cargo.toml +++ b/amm/methods/Cargo.toml @@ -3,6 +3,9 @@ name = "amm-methods" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [build-dependencies] risc0-build = "=3.0.5" diff --git a/amm/methods/guest/Cargo.toml b/amm/methods/guest/Cargo.toml index a840422..7050286 100644 --- a/amm/methods/guest/Cargo.toml +++ b/amm/methods/guest/Cargo.toml @@ -5,6 +5,32 @@ edition = "2021" [workspace] +[lints.rust] +rust_2018_idioms = { level = "deny", priority = -1 } +unsafe_code = "forbid" + +[lints.clippy] +all = { level = "deny", priority = -1 } +allow_attributes = "warn" +allow_attributes_without_reason = "deny" +arithmetic_side_effects = "deny" +as_conversions = "deny" +cast_possible_truncation = "deny" +cast_possible_wrap = "deny" +cast_precision_loss = "warn" +cast_sign_loss = "deny" +dbg_macro = "deny" +indexing_slicing = "deny" +integer_division = "warn" +large_enum_variant = "deny" +match_wildcard_for_single_variants = "warn" +module_name_repetitions = "allow" +similar_names = "allow" +todo = "deny" +unimplemented = "deny" +unwrap_used = "deny" +wildcard_enum_match_arm = "deny" + [[bin]] name = "amm" path = "src/bin/amm.rs" diff --git a/amm/methods/guest/src/bin/amm.rs b/amm/methods/guest/src/bin/amm.rs index 99356f1..ff08986 100644 --- a/amm/methods/guest/src/bin/amm.rs +++ b/amm/methods/guest/src/bin/amm.rs @@ -1,4 +1,4 @@ -#![no_main] +#![cfg_attr(not(test), no_main)] use std::num::NonZeroU128; @@ -8,15 +8,23 @@ use nssa_core::{ account::{AccountId, AccountWithMetadata}, }; +#[cfg(not(test))] risc0_zkvm::guest::entry!(main); #[lez_program(instruction = "amm_core::Instruction")] mod amm { - #[allow(unused_imports)] + #[expect( + unused_imports, + reason = "SPEL instruction macro requires importing parent-scope handler types" + )] use super::*; /// Initializes a new Pool (or re-initializes an existing zero-supply Pool). /// A fresh user LP holding must be explicitly authorized by the caller. + #[expect( + clippy::too_many_arguments, + reason = "instruction interface requires explicit pool, vault, mint, lock, and user accounts" + )] #[instruction] pub fn new_definition( ctx: ProgramContext, @@ -52,6 +60,10 @@ mod amm { } /// Adds liquidity to the Pool. + #[expect( + clippy::too_many_arguments, + reason = "instruction interface requires explicit pool, vault, and user accounts" + )] #[instruction] pub fn add_liquidity( pool: AccountWithMetadata, @@ -83,6 +95,10 @@ mod amm { } /// Removes liquidity from the Pool. + #[expect( + clippy::too_many_arguments, + reason = "instruction interface requires explicit pool, vault, and user accounts" + )] #[instruction] pub fn remove_liquidity( pool: AccountWithMetadata, @@ -115,6 +131,10 @@ mod amm { } /// Swap some quantity of tokens while maintaining the pool constant product. + #[expect( + clippy::too_many_arguments, + reason = "instruction interface requires explicit pool, vault, user accounts, and bounds" + )] #[instruction] pub fn swap_exact_input( pool: AccountWithMetadata, @@ -142,6 +162,10 @@ mod amm { } /// Swap tokens specifying the exact desired output amount. + #[expect( + clippy::too_many_arguments, + reason = "instruction interface requires explicit pool, vault, user accounts, and bounds" + )] #[instruction] pub fn swap_exact_output( pool: AccountWithMetadata, diff --git a/amm/src/add.rs b/amm/src/add.rs index 7c18726..c91e092 100644 --- a/amm/src/add.rs +++ b/amm/src/add.rs @@ -9,7 +9,10 @@ use nssa_core::{ program::{AccountPostState, ChainedCall}, }; -#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] +#[expect( + clippy::too_many_arguments, + reason = "instruction surface passes explicit pool, vault, and user accounts" +)] pub fn add_liquidity( pool: AccountWithMetadata, vault_a: AccountWithMetadata, @@ -77,12 +80,14 @@ pub fn add_liquidity( .reserve_a .checked_mul(max_amount_to_add_token_b) .expect("reserve_a * max_amount_b overflows u128") - / pool_def_data.reserve_b; + .checked_div(pool_def_data.reserve_b) + .expect("reserve_b must be nonzero after validation"); let ideal_b: u128 = pool_def_data .reserve_b .checked_mul(max_amount_to_add_token_a) .expect("reserve_b * max_amount_a overflows u128") - / pool_def_data.reserve_a; + .checked_div(pool_def_data.reserve_a) + .expect("reserve_a must be nonzero after validation"); let actual_amount_a = if ideal_a > max_amount_to_add_token_a { max_amount_to_add_token_a @@ -114,12 +119,14 @@ pub fn add_liquidity( .liquidity_pool_supply .checked_mul(actual_amount_a) .expect("liquidity_pool_supply * actual_amount_a overflows u128") - / pool_def_data.reserve_a, + .checked_div(pool_def_data.reserve_a) + .expect("reserve_a must be nonzero after validation"), pool_def_data .liquidity_pool_supply .checked_mul(actual_amount_b) .expect("liquidity_pool_supply * actual_amount_b overflows u128") - / pool_def_data.reserve_b, + .checked_div(pool_def_data.reserve_b) + .expect("reserve_b must be nonzero after validation"), ); assert!(delta_lp != 0, "Payable LP must be nonzero"); diff --git a/amm/src/new_definition.rs b/amm/src/new_definition.rs index b7a45cc..8c47a6a 100644 --- a/amm/src/new_definition.rs +++ b/amm/src/new_definition.rs @@ -12,7 +12,10 @@ use nssa_core::{ }; use token_core::TokenDefinition; -#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] +#[expect( + clippy::too_many_arguments, + reason = "instruction surface passes explicit pool, vault, mint, lock, and user accounts" +)] pub fn new_definition( pool: AccountWithMetadata, vault_a: AccountWithMetadata, @@ -94,7 +97,9 @@ pub fn new_definition( initial_lp > MINIMUM_LIQUIDITY, "Initial liquidity must exceed minimum liquidity lock" ); - let user_lp = initial_lp - MINIMUM_LIQUIDITY; + let user_lp = initial_lp + .checked_sub(MINIMUM_LIQUIDITY) + .expect("initial liquidity must exceed minimum liquidity after validation"); // Update pool account let mut pool_post = pool.account.clone(); diff --git a/amm/src/remove.rs b/amm/src/remove.rs index 078ef04..3a57379 100644 --- a/amm/src/remove.rs +++ b/amm/src/remove.rs @@ -9,7 +9,10 @@ use nssa_core::{ program::{AccountPostState, ChainedCall}, }; -#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] +#[expect( + clippy::too_many_arguments, + reason = "instruction surface passes explicit pool, vault, and user accounts" +)] pub fn remove_liquidity( pool: AccountWithMetadata, vault_a: AccountWithMetadata, @@ -105,7 +108,10 @@ pub fn remove_liquidity( remove_liquidity_amount <= user_lp_balance, "Remove amount exceeds user LP balance" ); - let unlocked_liquidity = pool_def_data.liquidity_pool_supply - MINIMUM_LIQUIDITY; + let unlocked_liquidity = pool_def_data + .liquidity_pool_supply + .checked_sub(MINIMUM_LIQUIDITY) + .expect("liquidity supply must be at least the locked minimum after validation"); // The remove instruction never sees the LP lock account directly, so we must still refuse any // request that would burn through the permanent floor even if ownership is already corrupted. assert!( @@ -117,12 +123,14 @@ pub fn remove_liquidity( .reserve_a .checked_mul(remove_liquidity_amount) .expect("reserve_a * remove_liquidity_amount overflows u128") - / pool_def_data.liquidity_pool_supply; + .checked_div(pool_def_data.liquidity_pool_supply) + .expect("liquidity supply must be nonzero after validation"); let withdraw_amount_b = pool_def_data .reserve_b .checked_mul(remove_liquidity_amount) .expect("reserve_b * remove_liquidity_amount overflows u128") - / pool_def_data.liquidity_pool_supply; + .checked_div(pool_def_data.liquidity_pool_supply) + .expect("liquidity supply must be nonzero after validation"); // 3. Validate and slippage check assert!( diff --git a/amm/src/swap.rs b/amm/src/swap.rs index e8b38a0..f490825 100644 --- a/amm/src/swap.rs +++ b/amm/src/swap.rs @@ -46,7 +46,10 @@ fn validate_swap_setup( } /// Creates post-state and returns reserves after swap. -#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] +#[expect( + clippy::too_many_arguments, + reason = "post-state assembly keeps pool, vault, user account, and delta state explicit" +)] #[expect( clippy::needless_pass_by_value, reason = "consistent with codebase style" @@ -91,7 +94,10 @@ fn create_swap_post_states( ] } -#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] +#[expect( + clippy::too_many_arguments, + reason = "instruction surface passes explicit pool, vault, and user accounts" +)] #[must_use] pub fn swap_exact_input( pool: AccountWithMetadata, @@ -166,7 +172,10 @@ pub fn swap_exact_input( (post_states, chained_calls) } -#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] +#[expect( + clippy::too_many_arguments, + reason = "swap calculation keeps account context and pricing parameters explicit" +)] fn swap_logic( user_deposit: AccountWithMetadata, vault_deposit: AccountWithMetadata, @@ -179,10 +188,14 @@ fn swap_logic( reserve_withdraw_vault_amount: u128, pool_id: AccountId, ) -> (Vec, u128, u128) { + let fee_multiplier = FEE_BPS_DENOMINATOR + .checked_sub(fee_bps) + .expect("fee_bps exceeds fee denominator"); let effective_amount_in = swap_amount_in - .checked_mul(FEE_BPS_DENOMINATOR - fee_bps) + .checked_mul(fee_multiplier) .expect("swap_amount_in * (FEE_BPS_DENOMINATOR - fee_bps) overflows u128") - / FEE_BPS_DENOMINATOR; + .checked_div(FEE_BPS_DENOMINATOR) + .expect("fee denominator must be nonzero"); assert!( effective_amount_in != 0, "Effective swap amount should be nonzero" @@ -194,9 +207,12 @@ fn swap_logic( let withdraw_amount = reserve_withdraw_vault_amount .checked_mul(effective_amount_in) .expect("reserve * effective_amount_in overflows u128") - / reserve_deposit_vault_amount - .checked_add(effective_amount_in) - .expect("reserve + effective_amount_in overflows u128"); + .checked_div( + reserve_deposit_vault_amount + .checked_add(effective_amount_in) + .expect("reserve + effective_amount_in overflows u128"), + ) + .expect("reserve plus effective input must be nonzero"); // Slippage check assert!( @@ -240,7 +256,10 @@ fn swap_logic( (chained_calls, swap_amount_in, withdraw_amount) } -#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] +#[expect( + clippy::too_many_arguments, + reason = "instruction surface passes explicit pool, vault, and user accounts" +)] #[must_use] pub fn swap_exact_output( pool: AccountWithMetadata, @@ -315,7 +334,10 @@ pub fn swap_exact_output( (post_states, chained_calls) } -#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] +#[expect( + clippy::too_many_arguments, + reason = "swap calculation keeps account context and pricing parameters explicit" +)] fn exact_output_swap_logic( user_deposit: AccountWithMetadata, vault_deposit: AccountWithMetadata, diff --git a/amm/src/tests.rs b/amm/src/tests.rs index c845815..bb1831e 100644 --- a/amm/src/tests.rs +++ b/amm/src/tests.rs @@ -1,4 +1,9 @@ #![cfg(test)] +#![expect( + clippy::arithmetic_side_effects, + clippy::integer_division, + reason = "test fixtures use fixed values to lock AMM math boundaries" +)] use std::num::NonZero; @@ -3228,7 +3233,8 @@ fn remove_liquidity_overflow_protection() { AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::user_holding_b(), user_lp, - NonZero::new(2).unwrap(), // remove_amount=2 → reserve_a * 2 overflows + NonZero::new(2).unwrap(), /* remove_amount=2 → reserve_a * 2 + * overflows */ 1, 1, ); @@ -3365,7 +3371,7 @@ fn test_add_liquidity_rejects_user_holding_a_wrong_program() { AccountWithMetadataForTests::user_holding_a_wrong_program(), AccountWithMetadataForTests::user_holding_b(), AccountWithMetadataForTests::user_holding_lp_init(), - NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), + NonZero::new(BalanceForTests::add_min_amount_lp()).expect("test value must be nonzero"), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ); @@ -3382,7 +3388,7 @@ fn test_add_liquidity_rejects_user_holding_b_wrong_program() { AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::user_holding_b_wrong_program(), AccountWithMetadataForTests::user_holding_lp_init(), - NonZero::new(BalanceForTests::add_min_amount_lp()).unwrap(), + NonZero::new(BalanceForTests::add_min_amount_lp()).expect("test value must be nonzero"), BalanceForTests::add_max_amount_a(), BalanceForTests::add_max_amount_b(), ); @@ -3401,7 +3407,7 @@ fn test_remove_liquidity_rejects_user_holding_a_wrong_program() { AccountWithMetadataForTests::user_holding_lp_with_balance( BalanceForTests::remove_amount_lp(), ), - NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), + NonZero::new(BalanceForTests::remove_amount_lp()).expect("test value must be nonzero"), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b_low(), ); @@ -3420,7 +3426,7 @@ fn test_remove_liquidity_rejects_user_holding_b_wrong_program() { AccountWithMetadataForTests::user_holding_lp_with_balance( BalanceForTests::remove_amount_lp(), ), - NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), + NonZero::new(BalanceForTests::remove_amount_lp()).expect("test value must be nonzero"), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b_low(), ); @@ -3438,7 +3444,7 @@ fn test_remove_liquidity_rejects_amount_exceeding_user_lp_balance() { AccountWithMetadataForTests::user_holding_a(), AccountWithMetadataForTests::user_holding_b(), AccountWithMetadataForTests::user_holding_lp_with_balance(lp_balance), - NonZero::new(BalanceForTests::remove_amount_lp()).unwrap(), + NonZero::new(BalanceForTests::remove_amount_lp()).expect("test value must be nonzero"), BalanceForTests::remove_min_amount_a(), BalanceForTests::remove_min_amount_b_low(), ); diff --git a/ata/Cargo.toml b/ata/Cargo.toml index ca47107..0601e65 100644 --- a/ata/Cargo.toml +++ b/ata/Cargo.toml @@ -3,6 +3,9 @@ name = "ata_program" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [dependencies] nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] } ata_core = { path = "core" } diff --git a/ata/core/Cargo.toml b/ata/core/Cargo.toml index 0304639..946bead 100644 --- a/ata/core/Cargo.toml +++ b/ata/core/Cargo.toml @@ -3,6 +3,9 @@ name = "ata_core" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [dependencies] nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] } borsh = { version = "1.5", features = ["derive"] } diff --git a/ata/core/src/lib.rs b/ata/core/src/lib.rs index f4a8ac0..e4be506 100644 --- a/ata/core/src/lib.rs +++ b/ata/core/src/lib.rs @@ -60,12 +60,16 @@ pub fn compute_ata_seed( ) -> PdaSeed { use risc0_zkvm::sha::{Impl, Sha256}; let mut bytes = [0u8; 96]; - for (index, word) in token_program_id.iter().enumerate() { - let offset = index * 4; - bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes()); + let (program_id_bytes, rest) = bytes.split_at_mut(32); + let (owner_bytes, definition_bytes) = rest.split_at_mut(32); + for (chunk, word) in program_id_bytes + .chunks_exact_mut(4) + .zip(token_program_id.iter()) + { + chunk.copy_from_slice(&word.to_le_bytes()); } - bytes[32..64].copy_from_slice(&owner_id.to_bytes()); - bytes[64..96].copy_from_slice(&definition_id.to_bytes()); + owner_bytes.copy_from_slice(&owner_id.to_bytes()); + definition_bytes.copy_from_slice(&definition_id.to_bytes()); PdaSeed::new( Impl::hash_bytes(&bytes) .as_bytes() diff --git a/ata/methods/Cargo.toml b/ata/methods/Cargo.toml index 09d4849..6bb49b0 100644 --- a/ata/methods/Cargo.toml +++ b/ata/methods/Cargo.toml @@ -3,6 +3,9 @@ name = "ata-methods" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [build-dependencies] risc0-build = "=3.0.5" diff --git a/ata/methods/guest/Cargo.toml b/ata/methods/guest/Cargo.toml index d82715b..25a7c07 100644 --- a/ata/methods/guest/Cargo.toml +++ b/ata/methods/guest/Cargo.toml @@ -5,6 +5,32 @@ edition = "2021" [workspace] +[lints.rust] +rust_2018_idioms = { level = "deny", priority = -1 } +unsafe_code = "forbid" + +[lints.clippy] +all = { level = "deny", priority = -1 } +allow_attributes = "warn" +allow_attributes_without_reason = "deny" +arithmetic_side_effects = "deny" +as_conversions = "deny" +cast_possible_truncation = "deny" +cast_possible_wrap = "deny" +cast_precision_loss = "warn" +cast_sign_loss = "deny" +dbg_macro = "deny" +indexing_slicing = "deny" +integer_division = "warn" +large_enum_variant = "deny" +match_wildcard_for_single_variants = "warn" +module_name_repetitions = "allow" +similar_names = "allow" +todo = "deny" +unimplemented = "deny" +unwrap_used = "deny" +wildcard_enum_match_arm = "deny" + [[bin]] name = "ata" path = "src/bin/ata.rs" diff --git a/ata/methods/guest/src/bin/ata.rs b/ata/methods/guest/src/bin/ata.rs index 7d892b2..43e2ef0 100644 --- a/ata/methods/guest/src/bin/ata.rs +++ b/ata/methods/guest/src/bin/ata.rs @@ -1,14 +1,18 @@ -#![no_main] +#![cfg_attr(not(test), no_main)] use spel_framework::prelude::*; use spel_framework::context::ProgramContext; use nssa_core::{account::AccountWithMetadata, program::ProgramId}; +#[cfg(not(test))] risc0_zkvm::guest::entry!(main); #[lez_program(instruction = "ata_core::Instruction")] mod ata { - #[allow(unused_imports)] + #[expect( + unused_imports, + reason = "SPEL instruction macro requires importing parent-scope handler types" + )] use super::*; /// Create the Associated Token Account for (token program, owner, definition). diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..c2d6c32 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,11 @@ +msrv = "1.94.0" + +avoid-breaking-exported-api = false +check-private-items = true +warn-on-all-wildcard-imports = true + +allow-dbg-in-tests = false +allow-expect-in-tests = true +allow-indexing-slicing-in-tests = true +allow-print-in-tests = true +allow-unwrap-in-tests = true diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 3254cdc..5f85b31 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -3,6 +3,9 @@ name = "integration_tests" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [dependencies] nssa = { workspace = true } nssa_core = { workspace = true, features = ["host"] } diff --git a/integration_tests/tests/amm.rs b/integration_tests/tests/amm.rs index ada0f46..bfb6feb 100644 --- a/integration_tests/tests/amm.rs +++ b/integration_tests/tests/amm.rs @@ -1,3 +1,8 @@ +#![expect( + clippy::arithmetic_side_effects, + reason = "integration fixtures use fixed balances to assert AMM state transitions" +)] + use amm_core::{ PoolDefinition, FEE_TIER_BPS_1, FEE_TIER_BPS_100, FEE_TIER_BPS_30, FEE_TIER_BPS_5, MINIMUM_LIQUIDITY, @@ -956,6 +961,7 @@ fn state_for_amm_tests_with_precreated_user_lp_for_new_def() -> V03State { state } +#[cfg(test)] fn try_execute_new_definition( state: &mut V03State, fees: u128, @@ -1009,10 +1015,12 @@ fn try_execute_new_definition( state.transition_from_public_transaction(&tx, 0, 0) } +#[cfg(test)] fn execute_new_definition(state: &mut V03State, fees: u128) { try_execute_new_definition(state, fees, true).unwrap(); } +#[cfg(test)] fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_out: u128) { let instruction = amm_core::Instruction::SwapExactInput { swap_amount_in, @@ -1041,6 +1049,7 @@ fn execute_swap_a_to_b(state: &mut V03State, swap_amount_in: u128, min_amount_ou state.transition_from_public_transaction(&tx, 0, 0).unwrap(); } +#[cfg(test)] fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_out: u128) { let instruction = amm_core::Instruction::SwapExactInput { swap_amount_in, @@ -1069,6 +1078,7 @@ fn execute_swap_b_to_a(state: &mut V03State, swap_amount_in: u128, min_amount_ou state.transition_from_public_transaction(&tx, 0, 0).unwrap(); } +#[cfg(test)] fn execute_add_liquidity( state: &mut V03State, min_amount_liquidity: u128, @@ -1108,6 +1118,7 @@ fn execute_add_liquidity( state.transition_from_public_transaction(&tx, 0, 0).unwrap(); } +#[cfg(test)] fn execute_remove_liquidity( state: &mut V03State, remove_liquidity_amount: u128, diff --git a/integration_tests/tests/token.rs b/integration_tests/tests/token.rs index e0b552b..9e308a6 100644 --- a/integration_tests/tests/token.rs +++ b/integration_tests/tests/token.rs @@ -648,6 +648,7 @@ fn token_program() -> Program { /// Performs a shielded transfer (public → private) of `amount` tokens from /// `Ids::holder()` to a new private account keyed by `PrivateKeys::recipient_*`. /// Returns the resulting private recipient account. +#[cfg(test)] fn shielded_token_transfer(amount: u128, state: &mut V03State) -> Account { let sender_id = Ids::holder(); let sender_account = state.get_account_by_id(sender_id); diff --git a/token/Cargo.toml b/token/Cargo.toml index 0620df3..ac644ff 100644 --- a/token/Cargo.toml +++ b/token/Cargo.toml @@ -3,6 +3,9 @@ name = "token_program" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [dependencies] nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] } token_core = { path = "core" } diff --git a/token/core/Cargo.toml b/token/core/Cargo.toml index 34aaf17..d5d5f3d 100644 --- a/token/core/Cargo.toml +++ b/token/core/Cargo.toml @@ -3,6 +3,9 @@ name = "token_core" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [dependencies] nssa_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0-rc3", features = ["host"] } spel-framework-macros = { git = "https://github.com/logos-co/spel.git", tag = "v0.3.0", package = "spel-framework-macros" } diff --git a/token/methods/Cargo.toml b/token/methods/Cargo.toml index a4897fb..e7007e2 100644 --- a/token/methods/Cargo.toml +++ b/token/methods/Cargo.toml @@ -3,6 +3,9 @@ name = "token-methods" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [build-dependencies] risc0-build = "=3.0.5" diff --git a/token/methods/guest/Cargo.toml b/token/methods/guest/Cargo.toml index f09f6ae..6187f2a 100644 --- a/token/methods/guest/Cargo.toml +++ b/token/methods/guest/Cargo.toml @@ -5,6 +5,32 @@ edition = "2021" [workspace] +[lints.rust] +rust_2018_idioms = { level = "deny", priority = -1 } +unsafe_code = "forbid" + +[lints.clippy] +all = { level = "deny", priority = -1 } +allow_attributes = "warn" +allow_attributes_without_reason = "deny" +arithmetic_side_effects = "deny" +as_conversions = "deny" +cast_possible_truncation = "deny" +cast_possible_wrap = "deny" +cast_precision_loss = "warn" +cast_sign_loss = "deny" +dbg_macro = "deny" +indexing_slicing = "deny" +integer_division = "warn" +large_enum_variant = "deny" +match_wildcard_for_single_variants = "warn" +module_name_repetitions = "allow" +similar_names = "allow" +todo = "deny" +unimplemented = "deny" +unwrap_used = "deny" +wildcard_enum_match_arm = "deny" + [[bin]] name = "token" path = "src/bin/token.rs" diff --git a/token/methods/guest/src/bin/token.rs b/token/methods/guest/src/bin/token.rs index 924b1b7..e3955fc 100644 --- a/token/methods/guest/src/bin/token.rs +++ b/token/methods/guest/src/bin/token.rs @@ -1,14 +1,18 @@ -#![no_main] +#![cfg_attr(not(test), no_main)] use spel_framework::prelude::*; use spel_framework::context::ProgramContext; use nssa_core::account::AccountWithMetadata; +#[cfg(not(test))] risc0_zkvm::guest::entry!(main); #[lez_program(instruction = "token_core::Instruction")] mod token { - #[allow(unused_imports)] + #[expect( + unused_imports, + reason = "SPEL instruction macro requires importing parent-scope handler types" + )] use super::*; /// Transfer tokens from sender to recipient. @@ -48,6 +52,10 @@ mod token { /// Create a new fungible or non-fungible token definition with metadata. /// Definition, holding, and metadata targets must be uninitialized and authorized. + #[expect( + clippy::boxed_local, + reason = "boxed metadata keeps the instruction argument size bounded on the stack" + )] #[instruction] pub fn new_definition_with_metadata( definition_target_account: AccountWithMetadata, diff --git a/token/src/print_nft.rs b/token/src/print_nft.rs index 837fe48..c45e6ad 100644 --- a/token/src/print_nft.rs +++ b/token/src/print_nft.rs @@ -40,7 +40,9 @@ pub fn print_nft( *print_balance > 1, "Insufficient balance to print another NFT copy" ); - *print_balance -= 1; + *print_balance = print_balance + .checked_sub(1) + .expect("print balance must be greater than one after validation"); let mut master_account_post = master_account.account; master_account_post.data = Data::from(&master_account_data); diff --git a/token/src/tests.rs b/token/src/tests.rs index 3c5494c..2df8e5c 100644 --- a/token/src/tests.rs +++ b/token/src/tests.rs @@ -1,4 +1,8 @@ #![cfg(test)] +#![expect( + clippy::arithmetic_side_effects, + reason = "test fixtures use fixed values to lock boundary behavior" +)] use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data, Nonce}, diff --git a/tools/idl-gen/Cargo.toml b/tools/idl-gen/Cargo.toml index dc7f289..ddcdc13 100644 --- a/tools/idl-gen/Cargo.toml +++ b/tools/idl-gen/Cargo.toml @@ -3,6 +3,9 @@ name = "idl-gen" version = "0.1.0" edition = "2021" +[lints] +workspace = true + [[bin]] name = "idl-gen" path = "src/main.rs" diff --git a/tools/idl-gen/src/main.rs b/tools/idl-gen/src/main.rs index 0d508c4..bfedda7 100644 --- a/tools/idl-gen/src/main.rs +++ b/tools/idl-gen/src/main.rs @@ -16,7 +16,13 @@ fn main() { let dep_dirs = find_path_dep_dirs(&path); match spel_framework_core::idl_gen::generate_idl_from_file_with_deps(&path, &dep_dirs) { - Ok(idl) => println!("{}", serde_json::to_string_pretty(&idl).unwrap()), + Ok(idl) => match serde_json::to_string_pretty(&idl) { + Ok(json) => println!("{json}"), + Err(e) => { + eprintln!("Error serializing IDL to JSON: {e}"); + process::exit(1); + } + }, Err(e) => { eprintln!("Error: {e}"); process::exit(1);