From 8034576b38655b1e40350af2b84d2b08d9af28d2 Mon Sep 17 00:00:00 2001 From: jiangyuer018 <2877716584@qq.com> Date: Fri, 23 Oct 2026 15:04:36 +0800 Subject: [PATCH] fix(checks): restore storage mutation and admin auth detection --- crates/checks/src/admin.rs | 19 +++++++++++++++---- crates/checks/src/missing_nonce.rs | 11 ++++------- docs/checks.md | 2 +- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/crates/checks/src/admin.rs b/crates/checks/src/admin.rs index 3319163..eb1f2d9 100644 --- a/crates/checks/src/admin.rs +++ b/crates/checks/src/admin.rs @@ -7,7 +7,7 @@ use crate::util::{ use crate::{Check, Finding, Severity}; use syn::spanned::Spanned; use syn::visit::{self, Visit}; -use syn::{Block, ExprIf, ExprMethodCall, File, Visibility}; +use syn::{Block, Expr, ExprIf, ExprMethodCall, File, Visibility}; const CHECK_NAME: &str = "unprotected-admin"; @@ -131,7 +131,10 @@ fn expr_diverges(expr: &Expr) -> bool { Expr::Return(_) => true, Expr::Macro(expr) => expr.mac.path.is_ident("panic"), Expr::MethodCall(expr) => { - matches!(expr.method.to_string().as_str(), "require_auth" | "require_auth_for_args") + matches!( + expr.method.to_string().as_str(), + "require_auth" | "require_auth_for_args" + ) } Expr::Block(expr) => block_diverges(&expr.block), Expr::If(expr) => { @@ -197,7 +200,12 @@ impl<'ast> Visit<'ast> for AuthGateScan { fn visit_expr_if(&mut self, i: &'ast ExprIf) { let had_tracked_var = !self.storage_read_vars.is_empty(); visit::visit_expr_if(self, i); - if had_tracked_var && !self.storage_read_and_conditional { + let branch_diverges = block_diverges(&i.then_branch) + || i + .else_branch + .as_ref() + .is_some_and(|(_, branch)| expr_diverges(branch)); + if had_tracked_var && branch_diverges && !self.storage_read_and_conditional { let all_names: Vec = self .storage_read_vars .iter() @@ -230,7 +238,10 @@ fn expr_references_any(expr: &syn::Expr, names: &std::collections::HashSet Visit<'ast> for StorageWriteVisitor { fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) { - if matches!( - node.method.to_string().as_str(), - "set" | "remove" | "append" | "push" | "push_back" - ) && receiver_chain_contains_storage(&node.receiver) - { + if is_storage_mutation_call(node) { self.found_write = true; } visit::visit_expr_method_call(self, node); @@ -273,7 +269,8 @@ impl C { let findings = check.run(&file, src); assert!( findings.is_empty(), - "tx_nonce should be recognised as nonce protection, got: {:?}", findings + "tx_nonce should be recognised as nonce protection, got: {:?}", + findings ); Ok(()) } diff --git a/docs/checks.md b/docs/checks.md index 2fb87cd..dfaff75 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -656,7 +656,7 @@ Soroban contracts run under a fixed compute-budget cap. An unbounded loop can ex Public methods in `#[contractimpl]` that: 1. Accept at least one `Address` parameter, and -2. Perform a storage write (`set`, `remove`, `append`, `push`, or `push_back`), and +2. Perform a storage write (`set`, `remove`, or `append`), and 3. Contain no reference to a nonce or replay-protection identifier — specifically, no identifier matching `nonce`, `sequence`, `seq_num`, or `replay` in the function body. **Why it matters**