Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/rules/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub use rule_engine::{
extract_struct_fields, find_variable_usage, Rule, RuleEngine, RuleViolation, ViolationSeverity,
};
pub use security::{HardcodedAddressesRule, MissingDomainSeparationRule, defi::MissingSlippageValidationRule};
pub use solidity::{StateVariablePackingRule, MappingIterationRule};
pub use solidity::{StateVariablePackingRule, MappingIterationRule, AbiEncodingRule};
pub use optimization::storage::detect_mapping_iteration;
pub use unused_state_variables::UnusedStateVariablesRule;

Expand Down
112 changes: 112 additions & 0 deletions packages/rules/src/optimization/encoding/abi_encode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use gasguard_ast::{Language, UnifiedAST};
use crate::rule_engine::{RuleViolation, ViolationSeverity};

/// Detects inefficient use of `abi.encode` that could be replaced with `abi.encodePacked`.
pub fn detect_abi_encoding_inefficiencies(ast: &UnifiedAST) -> Vec<RuleViolation> {
let mut violations = Vec::new();

if ast.language != Language::Solidity {
return violations;
}

for contract in &ast.contracts {
for func in &contract.functions {
let body = &func.body_raw;

// Simple check for abi.encode usage while trying to avoid false positives
// like abi.encodePacked, abi.encodeWithSignature, etc.

// Basic search strategy:
// Find occurrences of "abi.encode" and ensure the next character is "(" or whitespace then "("
let mut search_start = 0;
while let Some(index) = body[search_start..].find("abi.encode") {
let absolute_index = search_start + index;
let remainder = &body[absolute_index + "abi.encode".len()..];

// Check if the next non-whitespace char is '('
if let Some(next_char) = remainder.chars().find(|c| !c.is_whitespace()) {
if next_char == '(' {
violations.push(RuleViolation {
rule_name: "abi-encoding-inefficiency".to_string(),
description: "Detected potentially inefficient use of abi.encode".to_string(),
severity: ViolationSeverity::Medium,
line_number: func.line_number, // We use the function's start line as a proxy
column_number: 1,
variable_name: "abi.encode".to_string(),
suggestion: "Consider using abi.encodePacked to save gas, unless you require 32-byte padding (e.g., to prevent hash collisions with multiple dynamic types or interfacing with standard ABI requirements).".to_string(),
});
}
}

search_start = absolute_index + "abi.encode".len();
}
}
}

violations
}

#[cfg(test)]
mod tests {
use super::*;
use gasguard_ast::{ContractNode, FunctionNode, Visibility};

#[test]
fn test_detect_abi_encoding() {
let ast = UnifiedAST {
language: Language::Solidity,
source: "".to_string(),
file_path: "".to_string(),
structs: vec![],
enums: vec![],
contracts: vec![ContractNode {
name: "Test".to_string(),
line_number: 1,
state_variables: vec![],
functions: vec![
FunctionNode {
name: "testInefficient".to_string(),
params: vec![],
return_type: None,
visibility: Visibility::Public,
decorators: vec![],
is_constructor: false,
is_external: false,
is_payable: false,
line_number: 5,
body_raw: "bytes memory data = abi.encode(1, 2);".to_string(),
},
FunctionNode {
name: "testEfficient".to_string(),
params: vec![],
return_type: None,
visibility: Visibility::Public,
decorators: vec![],
is_constructor: false,
is_external: false,
is_payable: false,
line_number: 10,
body_raw: "bytes memory data = abi.encodePacked(1, 2);".to_string(),
},
FunctionNode {
name: "testEncodeWithSelector".to_string(),
params: vec![],
return_type: None,
visibility: Visibility::Public,
decorators: vec![],
is_constructor: false,
is_external: false,
is_payable: false,
line_number: 15,
body_raw: "bytes memory data = abi.encodeWithSelector(bytes4(keccak256(\"foo()\")));".to_string(),
}
],
}],
};

let violations = detect_abi_encoding_inefficiencies(&ast);
assert_eq!(violations.len(), 1);
assert_eq!(violations[0].line_number, 5);
assert_eq!(violations[0].variable_name, "abi.encode");
}
}
3 changes: 3 additions & 0 deletions packages/rules/src/optimization/encoding/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod abi_encode;

pub use abi_encode::detect_abi_encoding_inefficiencies;
2 changes: 2 additions & 0 deletions packages/rules/src/optimization/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod deployment;
pub mod encoding;
pub mod storage;

pub use storage::{
Expand All @@ -7,3 +8,4 @@ pub use storage::{
};

pub use deployment::{estimate_bytecode_size, ExcessiveContractSizeRule};
pub use encoding::detect_abi_encoding_inefficiencies;
43 changes: 43 additions & 0 deletions packages/rules/src/solidity/abi_encode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::optimization::encoding::detect_abi_encoding_inefficiencies;
use crate::rule_engine::{Rule, RuleViolation};
use gasguard_ast::{Language, UnifiedAST};
use syn::Item;

pub struct AbiEncodingRule;

impl Rule for AbiEncodingRule {
fn name(&self) -> &str {
"abi-encoding-inefficiency"
}

fn description(&self) -> &str {
"Detects inefficient use of abi.encode that could be replaced with abi.encodePacked"
}

fn check(&self, _ast: &[Item]) -> Vec<RuleViolation> {
// Handled via analyze
Vec::new()
}
}

impl AbiEncodingRule {
pub fn analyze(&self, ast: &UnifiedAST) -> Vec<RuleViolation> {
if ast.language != Language::Solidity {
return Vec::new();
}

detect_abi_encoding_inefficiencies(ast)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_abi_encoding_rule_info() {
let rule = AbiEncodingRule;
assert_eq!(rule.name(), "abi-encoding-inefficiency");
assert!(rule.description().contains("abi.encode"));
}
}
2 changes: 2 additions & 0 deletions packages/rules/src/solidity/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod state_variable_packing;
pub mod uint8_vs_uint256;
pub mod mapping_iteration;
pub mod abi_encode;

pub use state_variable_packing::StateVariablePackingRule;
pub use mapping_iteration::MappingIterationRule;
pub use abi_encode::AbiEncodingRule;
4 changes: 4 additions & 0 deletions pr_body.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Closes #342

### Summary
This PR adds the MappingIterationRule to detect gas-heavy mapping iteration workarounds where developers use an unbounded helper array to simulate mapping iterability on-chain. This rule will alert developers and suggest off-chain solutions or pagination.
Loading