diff --git a/toolchains/solidity/linter/core/solidhunter-lib/src/rules/best_practises/mod.rs b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/best_practises/mod.rs index 5312f37c..8ad9d7d7 100644 --- a/toolchains/solidity/linter/core/solidhunter-lib/src/rules/best_practises/mod.rs +++ b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/best_practises/mod.rs @@ -10,6 +10,7 @@ pub mod global_import; pub mod max_states_count; pub mod no_console; pub mod one_contract_per_file; +pub mod payable_fallback; pub mod reason_string; mod explicit_types; @@ -24,6 +25,7 @@ use crate::rules::best_practises::line_maxlen::LineMaxLen; use crate::rules::best_practises::max_states_count::MaxStatesCount; use crate::rules::best_practises::no_console::NoConsole; use crate::rules::best_practises::one_contract_per_file::OneContractPerFile; +use crate::rules::best_practises::payable_fallback::PayableFallback; use crate::rules::best_practises::reason_string::ReasonString; use crate::rules::RuleBuilder; @@ -39,6 +41,7 @@ pub fn create_default_rules() -> Vec { GlobalImport::create_default(), EmptyBlock::create_default(), ExplicitTypes::create_default(), + PayableFallback::create_default(), ] } @@ -65,6 +68,10 @@ pub fn create_rules() -> RulesMap { rules.insert(empty_block::RULE_ID.to_string(), EmptyBlock::create); rules.insert(explicit_types::RULE_ID.to_string(), ExplicitTypes::create); rules.insert(no_console::RULE_ID.to_string(), NoConsole::create); + rules.insert( + payable_fallback::RULE_ID.to_string(), + PayableFallback::create, + ); rules } diff --git a/toolchains/solidity/linter/core/solidhunter-lib/src/rules/best_practises/payable_fallback.rs b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/best_practises/payable_fallback.rs new file mode 100644 index 00000000..d7a157f8 --- /dev/null +++ b/toolchains/solidity/linter/core/solidhunter-lib/src/rules/best_practises/payable_fallback.rs @@ -0,0 +1,93 @@ +use ast_extractor::retriever::{retrieve_contract_nodes, retrieve_functions_nodes}; +use ast_extractor::{FunctionKind, ItemFunction, Mutability, Spanned}; + +use crate::linter::SolidFile; +use crate::rules::types::*; +use crate::types::*; + +// const DEFAULT_SEVERITY: &str = "warn"; +const DEFAULT_MESSAGE: &str = "Fallback should contains payable attributes"; +pub const RULE_ID: &str = "payable-fallback"; + +pub struct PayableFallback { + _data: RuleEntry, +} + +impl RuleType for PayableFallback { + fn diagnose(&self, _file: &SolidFile, _files: &[SolidFile]) -> Vec { + let mut res = Vec::new(); + let reports = check_fallback_payable(_file); + + for report in reports.into_iter().flatten() { + res.push(LintDiag { + id: RULE_ID.to_string(), + severity: Some(Severity::WARNING), + range: report, + code: None, + source: None, + message: DEFAULT_MESSAGE.to_string(), + uri: _file.path.clone(), + source_file_content: _file.content.clone(), + }); + } + res + } +} + +fn check_fallback_payable(file: &SolidFile) -> Vec> { + let mut res: Vec> = Vec::new(); + + let contracts = retrieve_contract_nodes(&file.data); + for contract in contracts { + let functions = retrieve_functions_nodes(&contract); + + for function in functions { + if FunctionKind::is_fallback(function.kind) || function.name.is_none() { + res = check_attribute(res, function); + } + } + } + res +} +fn check_attribute(mut res: Vec>, function: ItemFunction) -> Vec> { + let mut is_payable = false; + for attributes in function.attributes.iter() { + if attributes.mutability().is_some() + && Mutability::is_payable(attributes.mutability().unwrap()) + { + is_payable = true; + } + } + if !is_payable { + res.push(create_report(function)); + } + res +} + +fn create_report(function: ItemFunction) -> Option { + Some(Range { + start: Position { + line: function.attributes.span().start().line, + character: function.attributes.span().start().column + 1, + }, + end: Position { + line: function.attributes.span().end().line, + character: function.attributes.span().end().column, + }, + }) +} + +impl PayableFallback { + pub fn create(data: RuleEntry) -> Box { + let rule = PayableFallback { _data: data }; + Box::new(rule) + } + + pub fn create_default() -> RuleEntry { + RuleEntry { + id: RULE_ID.to_string(), + severity: Severity::WARNING, + data: vec![], + } + } +} diff --git a/toolchains/solidity/linter/core/solidhunter-lib/testdata/PayableFallback/.solidhunter.json b/toolchains/solidity/linter/core/solidhunter-lib/testdata/PayableFallback/.solidhunter.json new file mode 100644 index 00000000..ee5b30ed --- /dev/null +++ b/toolchains/solidity/linter/core/solidhunter-lib/testdata/PayableFallback/.solidhunter.json @@ -0,0 +1,65 @@ +{ + "name": "solidhunter", + "includes": [], + "plugins": [], + "rules": [ + { + "id": "line-max-len", + "severity": "WARNING", + "data": [ + "80" + ] + }, + { + "id": "max-states-count", + "severity": "WARNING", + "data": [ + "15" + ] + }, + { + "id": "function-max-lines", + "severity": "WARNING", + "data": [ + "20" + ] + }, + { + "id": "reason-string", + "severity": "WARNING", + "data": [ + "32" + ] + }, + { + "id": "contract-name-pascalcase", + "severity": "WARNING", + "data": [] + }, + { + "id": "func-name-camelcase", + "severity": "WARNING", + "data": [] + }, + { + "id": "func-param-name-camelcase", + "severity": "WARNING", + "data": [] + }, + { + "id": "use-forbidden-name", + "severity": "WARNING", + "data": [] + }, + { + "id": "import-on-top", + "severity": "WARNING", + "data": [] + }, + { + "id": "payable-fallback", + "severity": "WARNING", + "data": [] + } + ] +} \ No newline at end of file diff --git a/toolchains/solidity/linter/core/solidhunter-lib/testdata/PayableFallback/file.sol b/toolchains/solidity/linter/core/solidhunter-lib/testdata/PayableFallback/file.sol new file mode 100644 index 00000000..65db5175 --- /dev/null +++ b/toolchains/solidity/linter/core/solidhunter-lib/testdata/PayableFallback/file.sol @@ -0,0 +1,11 @@ +pragma solidity 0.8.19; + +contract Test { + function() public payable {} // Valid + + fallback() external payable {} // Valid + + function() public {} // Not Valid + + fallback() external {} // Not Valid +} diff --git a/toolchains/solidity/linter/core/solidhunter-lib/testdata/PayableFallback/findings.csv b/toolchains/solidity/linter/core/solidhunter-lib/testdata/PayableFallback/findings.csv new file mode 100644 index 00000000..0dc48874 --- /dev/null +++ b/toolchains/solidity/linter/core/solidhunter-lib/testdata/PayableFallback/findings.csv @@ -0,0 +1,2 @@ +payable-fallback:8:16:8:21 +payable-fallback:10:16:10:23 \ No newline at end of file diff --git a/toolchains/solidity/linter/core/solidhunter-lib/tests/linter.rs b/toolchains/solidity/linter/core/solidhunter-lib/tests/linter.rs index 117e661c..29f9ced2 100644 --- a/toolchains/solidity/linter/core/solidhunter-lib/tests/linter.rs +++ b/toolchains/solidity/linter/core/solidhunter-lib/tests/linter.rs @@ -160,5 +160,6 @@ test_directories! { NoConsole, ExplicitTypes, ImplicitTypes, + PayableFallback, GlobalImport }