From 5273e5c31f15a5c3d333fed18094ea944fee9283 Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Mon, 29 Jul 2024 18:58:22 +0530 Subject: [PATCH 01/15] Detector: Unchecked `send()` on address (#611) Co-authored-by: Alex Roan --- aderyn_core/src/ast/impls/node/statements.rs | 7 ++ aderyn_core/src/detect/detector.rs | 3 + aderyn_core/src/detect/high/mod.rs | 2 + aderyn_core/src/detect/high/unchecked_send.rs | 113 +++++++++++++++++ .../src/detect/high/weak_randomness.rs | 3 + .../adhoc-sol-files-highs-only-report.json | 1 + reports/report.json | 66 +++++++++- reports/report.md | 117 +++++++++++++----- reports/report.sarif | 97 +++++++++++++++ .../contract-playground/src/UncheckedSend.sol | 31 +++++ 10 files changed, 409 insertions(+), 31 deletions(-) create mode 100644 aderyn_core/src/detect/high/unchecked_send.rs create mode 100644 tests/contract-playground/src/UncheckedSend.sol diff --git a/aderyn_core/src/ast/impls/node/statements.rs b/aderyn_core/src/ast/impls/node/statements.rs index 5ceb70fea..9f1bb74c1 100644 --- a/aderyn_core/src/ast/impls/node/statements.rs +++ b/aderyn_core/src/ast/impls/node/statements.rs @@ -40,9 +40,16 @@ impl Node for ExpressionStatement { fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> { if visitor.visit_expression_statement(self)? { self.expression.accept(visitor)?; + self.accept_metadata(visitor)?; } visitor.end_visit_expression_statement(self) } + fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> { + if let Some(child_id) = self.expression.get_node_id() { + visitor.visit_immediate_children(self.id, vec![child_id])?; + } + Ok(()) + } } impl Node for VariableDeclarationStatement { diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index 49ad141fa..eef406e72 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -58,6 +58,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -121,6 +122,7 @@ pub(crate) enum IssueDetectorNamePool { IncorrectCaretOperator, YulReturn, StateVariableShadowing, + UncheckedSend, MisusedBoolean, SendEtherNoChecks, DelegateCallUncheckedAddress, @@ -252,6 +254,7 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::UncheckedSend => Some(Box::::default()), IssueDetectorNamePool::MisusedBoolean => Some(Box::::default()), IssueDetectorNamePool::SendEtherNoChecks => { Some(Box::::default()) diff --git a/aderyn_core/src/detect/high/mod.rs b/aderyn_core/src/detect/high/mod.rs index f5b609722..1e9e9912f 100644 --- a/aderyn_core/src/detect/high/mod.rs +++ b/aderyn_core/src/detect/high/mod.rs @@ -22,6 +22,7 @@ pub(crate) mod state_variable_shadowing; pub(crate) mod storage_array_edit_with_memory; pub(crate) mod tautological_compare; pub(crate) mod unchecked_return; +pub(crate) mod unchecked_send; pub(crate) mod uninitialized_state_variable; pub(crate) mod unprotected_init_function; pub(crate) mod unsafe_casting; @@ -52,6 +53,7 @@ pub use state_variable_shadowing::StateVariableShadowingDetector; pub use storage_array_edit_with_memory::StorageArrayEditWithMemoryDetector; pub use tautological_compare::TautologicalCompareDetector; pub use unchecked_return::UncheckedReturnDetector; +pub use unchecked_send::UncheckedSendDetector; pub use uninitialized_state_variable::UninitializedStateVariableDetector; pub use unprotected_init_function::UnprotectedInitializerDetector; pub use unsafe_casting::UnsafeCastingDetector; diff --git a/aderyn_core/src/detect/high/unchecked_send.rs b/aderyn_core/src/detect/high/unchecked_send.rs new file mode 100644 index 000000000..977272b30 --- /dev/null +++ b/aderyn_core/src/detect/high/unchecked_send.rs @@ -0,0 +1,113 @@ +use std::collections::BTreeMap; +use std::error::Error; + +use crate::ast::{ASTNode, NodeID, NodeType}; + +use crate::capture; +use crate::context::browser::GetImmediateParent; +use crate::detect::detector::IssueDetectorNamePool; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; +use eyre::Result; + +#[derive(Default)] +pub struct UncheckedSendDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for UncheckedSendDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + for member_access in context.member_accesses() { + if member_access.member_name == "send" + && member_access + .expression + .type_descriptions() + .is_some_and(|type_desc| { + type_desc + .type_string + .as_ref() + .is_some_and(|type_string| type_string.starts_with("address")) + }) + { + if let Some(ASTNode::FunctionCall(func_call)) = member_access.parent(context) { + if func_call + .parent(context) + .is_some_and(|node| node.node_type() == NodeType::Block) + { + capture!(self, context, func_call); + } + } + } + } + + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::High + } + + fn title(&self) -> String { + String::from("Unchecked `bool success` value for send call.") + } + + fn description(&self) -> String { + String::from("The transaction `address(payable?).send(address)` may fail because of reasons like out-of-gas, \ + invalid receipient address or revert from the recipient. Therefore, the boolean returned by this function call must be checked \ + to be `true` in order to verify that the transaction was successful") + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + IssueDetectorNamePool::UncheckedSend.to_string() + } +} + +#[cfg(test)] +mod unchecked_send_tests { + use serial_test::serial; + + use crate::detect::{detector::IssueDetector, high::unchecked_send::UncheckedSendDetector}; + + #[test] + #[serial] + fn test_unchecked_send() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/UncheckedSend.sol", + ); + + let mut detector = UncheckedSendDetector::default(); + let found = detector.detect(&context).unwrap(); + + println!("Instances {:#?}", detector.instances()); + + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 1); + // assert the severity is high + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::High + ); + // assert the title is correct + assert_eq!( + detector.title(), + String::from("Unchecked `bool success` value for send call.") + ); + // assert the description is correct + assert_eq!( + detector.description(), + String::from("The transaction `address(payable?).send(address)` may fail because of reasons like out-of-gas, \ + invalid receipient address or revert from the recipient. Therefore, the boolean returned by this function call must be checked \ + to be `true` in order to verify that the transaction was successful") + ); + } +} diff --git a/aderyn_core/src/detect/high/weak_randomness.rs b/aderyn_core/src/detect/high/weak_randomness.rs index 079fd2cc2..16280ee4a 100644 --- a/aderyn_core/src/detect/high/weak_randomness.rs +++ b/aderyn_core/src/detect/high/weak_randomness.rs @@ -160,9 +160,12 @@ fn check_operand(operand: &Expression) -> bool { #[cfg(test)] mod weak_randomness_detector_tests { + use serial_test::serial; + use crate::detect::{detector::IssueDetector, high::weak_randomness::WeakRandomnessDetector}; #[test] + #[serial] fn test_weak_randomness_detector() { let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/WeakRandomness.sol", diff --git a/reports/adhoc-sol-files-highs-only-report.json b/reports/adhoc-sol-files-highs-only-report.json index 5b6f13587..3f591e527 100644 --- a/reports/adhoc-sol-files-highs-only-report.json +++ b/reports/adhoc-sol-files-highs-only-report.json @@ -181,6 +181,7 @@ "incorrect-caret-operator", "yul-return", "state-variable-shadowing", + "unchecked-send", "misused-boolean", "send-ether-no-checks", "delegate-call-unchecked-address", diff --git a/reports/report.json b/reports/report.json index 1e7161940..caa2ecc85 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 67, - "total_sloc": 1904 + "total_source_units": 68, + "total_sloc": 1922 }, "files_details": { "files_details": [ @@ -165,6 +165,10 @@ "file_path": "src/UncheckedReturn.sol", "n_sloc": 33 }, + { + "file_path": "src/UncheckedSend.sol", + "n_sloc": 18 + }, { "file_path": "src/UninitializedStateVariable.sol", "n_sloc": 29 @@ -276,7 +280,7 @@ ] }, "issue_count": { - "high": 29, + "high": 30, "low": 23 }, "high_issues": { @@ -1380,6 +1384,19 @@ } ] }, + { + "title": "Unchecked `bool success` value for send call.", + "description": "The transaction `address(payable?).send(address)` may fail because of reasons like out-of-gas, invalid receipient address or revert from the recipient. Therefore, the boolean returned by this function call must be checked to be `true` in order to verify that the transaction was successful", + "detector_name": "unchecked-send", + "instances": [ + { + "contract_path": "src/UncheckedSend.sol", + "line_no": 24, + "src": "815:22", + "src_char": "815:22" + } + ] + }, { "title": "Misused boolean with logical operators", "description": "The patterns `if (… || true)` and `if (.. && false)` will always evaluate to true and false respectively.", @@ -1476,6 +1493,30 @@ "src": "1795:5", "src_char": "1795:5" }, + { + "contract_path": "src/UncheckedSend.sol", + "line_no": 6, + "src": "85:246", + "src_char": "85:246" + }, + { + "contract_path": "src/UncheckedSend.sol", + "line_no": 12, + "src": "337:190", + "src_char": "337:190" + }, + { + "contract_path": "src/UncheckedSend.sol", + "line_no": 17, + "src": "533:184", + "src_char": "533:184" + }, + { + "contract_path": "src/UncheckedSend.sol", + "line_no": 22, + "src": "723:186", + "src_char": "723:186" + }, { "contract_path": "src/UninitializedStateVariable.sol", "line_no": 17, @@ -1938,6 +1979,12 @@ "src": "32:23", "src_char": "32:23" }, + { + "contract_path": "src/UncheckedSend.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/UsingSelfdestruct.sol", "line_no": 2, @@ -2932,6 +2979,12 @@ "src": "1795:5", "src_char": "1795:5" }, + { + "contract_path": "src/UncheckedSend.sol", + "line_no": 27, + "src": "915:65", + "src_char": "915:65" + }, { "contract_path": "src/auditor_mode/PublicFunctionsWithoutSenderCheck.sol", "line_no": 11, @@ -3197,6 +3250,12 @@ "line_no": 17, "src": "388:11", "src_char": "388:11" + }, + { + "contract_path": "src/UncheckedSend.sol", + "line_no": 27, + "src": "915:65", + "src_char": "915:65" } ] }, @@ -3466,6 +3525,7 @@ "incorrect-caret-operator", "yul-return", "state-variable-shadowing", + "unchecked-send", "misused-boolean", "send-ether-no-checks", "delegate-call-unchecked-address", diff --git a/reports/report.md b/reports/report.md index d2cd2eeca..dba746648 100644 --- a/reports/report.md +++ b/reports/report.md @@ -27,16 +27,17 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [H-17: Incorrect use of caret operator on a non hexadcimal constant](#h-17-incorrect-use-of-caret-operator-on-a-non-hexadcimal-constant) - [H-18: Yul block contains `return` function call.](#h-18-yul-block-contains-return-function-call) - [H-19: High Issue Title](#h-19-high-issue-title) - - [H-20: Misused boolean with logical operators](#h-20-misused-boolean-with-logical-operators) - - [H-21: Sending native Eth is not protected from these functions.](#h-21-sending-native-eth-is-not-protected-from-these-functions) - - [H-22: Delegatecall made by the function without checks on any adress.](#h-22-delegatecall-made-by-the-function-without-checks-on-any-adress) - - [H-23: Tautological comparison.](#h-23-tautological-comparison) - - [H-24: RTLO character detected in file. \u{202e}](#h-24-rtlo-character-detected-in-file-u202e) - - [H-25: Return value of the function call is not checked.](#h-25-return-value-of-the-function-call-is-not-checked) - - [H-26: Dangerous unary operator found in assignment.](#h-26-dangerous-unary-operator-found-in-assignment) - - [H-27: Weak Randomness](#h-27-weak-randomness) - - [H-28: Usage of variable before declaration.](#h-28-usage-of-variable-before-declaration) - - [H-29: Deletion from a nested mappping.](#h-29-deletion-from-a-nested-mappping) + - [H-20: Unchecked `bool success` value for send call.](#h-20-unchecked-bool-success-value-for-send-call) + - [H-21: Misused boolean with logical operators](#h-21-misused-boolean-with-logical-operators) + - [H-22: Sending native Eth is not protected from these functions.](#h-22-sending-native-eth-is-not-protected-from-these-functions) + - [H-23: Delegatecall made by the function without checks on any adress.](#h-23-delegatecall-made-by-the-function-without-checks-on-any-adress) + - [H-24: Tautological comparison.](#h-24-tautological-comparison) + - [H-25: RTLO character detected in file. \u{202e}](#h-25-rtlo-character-detected-in-file-u202e) + - [H-26: Return value of the function call is not checked.](#h-26-return-value-of-the-function-call-is-not-checked) + - [H-27: Dangerous unary operator found in assignment.](#h-27-dangerous-unary-operator-found-in-assignment) + - [H-28: Weak Randomness](#h-28-weak-randomness) + - [H-29: Usage of variable before declaration.](#h-29-usage-of-variable-before-declaration) + - [H-30: Deletion from a nested mappping.](#h-30-deletion-from-a-nested-mappping) - [Low Issues](#low-issues) - [L-1: Centralization Risk for trusted owners](#l-1-centralization-risk-for-trusted-owners) - [L-2: Solmate's SafeTransferLib does not check for token contract's existence](#l-2-solmates-safetransferlib-does-not-check-for-token-contracts-existence) @@ -69,8 +70,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 67 | -| Total nSLOC | 1904 | +| .sol Files | 68 | +| Total nSLOC | 1922 | ## Files Details @@ -117,6 +118,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/TautologicalCompare.sol | 17 | | src/TestERC20.sol | 62 | | src/UncheckedReturn.sol | 33 | +| src/UncheckedSend.sol | 18 | | src/UninitializedStateVariable.sol | 29 | | src/UnprotectedInitialize.sol | 25 | | src/UnsafeERC721Mint.sol | 18 | @@ -144,14 +146,14 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **1904** | +| **Total** | **1922** | ## Issue Summary | Category | No. of Issues | | --- | --- | -| High | 29 | +| High | 30 | | Low | 23 | @@ -1334,7 +1336,24 @@ Description of the high issue. -## H-20: Misused boolean with logical operators +## H-20: Unchecked `bool success` value for send call. + +The transaction `address(payable?).send(address)` may fail because of reasons like out-of-gas, invalid receipient address or revert from the recipient. Therefore, the boolean returned by this function call must be checked to be `true` in order to verify that the transaction was successful + +
1 Found Instances + + +- Found in src/UncheckedSend.sol [Line: 24](../tests/contract-playground/src/UncheckedSend.sol#L24) + + ```solidity + recipient.send(amount); // parent of Send FunctionCall is Block (return value is unused) + ``` + +
+ + + +## H-21: Misused boolean with logical operators The patterns `if (… || true)` and `if (.. && false)` will always evaluate to true and false respectively. @@ -1405,11 +1424,11 @@ The patterns `if (… || true)` and `if (.. && false)` will always evaluate to t -## H-21: Sending native Eth is not protected from these functions. +## H-22: Sending native Eth is not protected from these functions. Introduce checks for `msg.sender` in the function -
5 Found Instances +
9 Found Instances - Found in src/CallGraphTests.sol [Line: 38](../tests/contract-playground/src/CallGraphTests.sol#L38) @@ -1436,6 +1455,30 @@ Introduce checks for `msg.sender` in the function function func1(address x) external mod1(x) { ``` +- Found in src/UncheckedSend.sol [Line: 6](../tests/contract-playground/src/UncheckedSend.sol#L6) + + ```solidity + function send1(address payable recipient, uint256 amount) external { + ``` + +- Found in src/UncheckedSend.sol [Line: 12](../tests/contract-playground/src/UncheckedSend.sol#L12) + + ```solidity + function send2(address payable recipient, uint256 amount) external { + ``` + +- Found in src/UncheckedSend.sol [Line: 17](../tests/contract-playground/src/UncheckedSend.sol#L17) + + ```solidity + function send3(address payable recipient, uint256 amount) external returns(bool) { + ``` + +- Found in src/UncheckedSend.sol [Line: 22](../tests/contract-playground/src/UncheckedSend.sol#L22) + + ```solidity + function send4(address payable recipient, uint256 amount) external { + ``` + - Found in src/UninitializedStateVariable.sol [Line: 17](../tests/contract-playground/src/UninitializedStateVariable.sol#L17) ```solidity @@ -1446,7 +1489,7 @@ Introduce checks for `msg.sender` in the function -## H-22: Delegatecall made by the function without checks on any adress. +## H-23: Delegatecall made by the function without checks on any adress. Introduce checks on the address @@ -1475,7 +1518,7 @@ Introduce checks on the address -## H-23: Tautological comparison. +## H-24: Tautological comparison. The left hand side and the right hand side of the binary operation has the same value. This makes the condition always true or always false. @@ -1510,7 +1553,7 @@ The left hand side and the right hand side of the binary operation has the same -## H-24: RTLO character detected in file. \u{202e} +## H-25: RTLO character detected in file. \u{202e} Right to left override character may be misledaing and cause potential attacks by visually misordering method arguments! @@ -1527,7 +1570,7 @@ Right to left override character may be misledaing and cause potential attacks b -## H-25: Return value of the function call is not checked. +## H-26: Return value of the function call is not checked. Function returns a value but it is ignored. @@ -1550,7 +1593,7 @@ Function returns a value but it is ignored. -## H-26: Dangerous unary operator found in assignment. +## H-27: Dangerous unary operator found in assignment. Potentially mistakened `=+` for `+=` or `=-` for `-=`. Please include a space in between. @@ -1573,7 +1616,7 @@ Potentially mistakened `=+` for `+=` or `=-` for `-=`. Please include a space in -## H-27: Weak Randomness +## H-28: Weak Randomness The use of keccak256 hash functions on predictable values like block.timestamp, block.number, or similar data, including modulo operations on these values, should be avoided for generating randomness, as they are easily predictable and manipulable. The `PREVRANDAO` opcode also should not be used as a source of randomness. Instead, utilize Chainlink VRF for cryptographically secure and provably random values to ensure protocol integrity. @@ -1638,7 +1681,7 @@ The use of keccak256 hash functions on predictable values like block.timestamp, -## H-28: Usage of variable before declaration. +## H-29: Usage of variable before declaration. This is a bad practice that may lead to unintended consequences. Please declare the variable before using it. @@ -1655,7 +1698,7 @@ This is a bad practice that may lead to unintended consequences. Please declare -## H-29: Deletion from a nested mappping. +## H-30: Deletion from a nested mappping. A deletion in a structure containing a mapping will not delete the mapping. The remaining data may be used to compromise the contract. @@ -1904,7 +1947,7 @@ ERC20 functions may not behave as expected. For example: return values are not a Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;` -
15 Found Instances +
16 Found Instances - Found in src/ContractWithTodo.sol [Line: 2](../tests/contract-playground/src/ContractWithTodo.sol#L2) @@ -1955,6 +1998,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid pragma solidity ^0.4.0; ``` +- Found in src/UncheckedSend.sol [Line: 2](../tests/contract-playground/src/UncheckedSend.sol#L2) + + ```solidity + pragma solidity ^0.7.0; + ``` + - Found in src/UsingSelfdestruct.sol [Line: 2](../tests/contract-playground/src/UsingSelfdestruct.sol#L2) ```solidity @@ -2878,7 +2927,7 @@ Solc compiler version 0.8.20 switches the default target EVM version to Shanghai Consider removing empty blocks. -
25 Found Instances +
26 Found Instances - Found in src/AdminContract.sol [Line: 14](../tests/contract-playground/src/AdminContract.sol#L14) @@ -2977,6 +3026,12 @@ Consider removing empty blocks. function func1(address x) external mod1(x) { ``` +- Found in src/UncheckedSend.sol [Line: 27](../tests/contract-playground/src/UncheckedSend.sol#L27) + + ```solidity + function doSomething(bool success) internal pure { + ``` + - Found in src/auditor_mode/PublicFunctionsWithoutSenderCheck.sol [Line: 11](../tests/contract-playground/src/auditor_mode/PublicFunctionsWithoutSenderCheck.sol#L11) ```solidity @@ -3182,7 +3237,7 @@ Use `e` notation, for example: `1e18`, instead of its full numeric value. Instead of separating the logic into a separate function, consider inlining the logic into the calling function. This can reduce the number of function calls and improve readability. -
11 Found Instances +
12 Found Instances - Found in src/CallGraphTests.sol [Line: 6](../tests/contract-playground/src/CallGraphTests.sol#L6) @@ -3251,6 +3306,12 @@ Instead of separating the logic into a separate function, consider inlining the function editStorage(uint[1] storage arr) internal { ``` +- Found in src/UncheckedSend.sol [Line: 27](../tests/contract-playground/src/UncheckedSend.sol#L27) + + ```solidity + function doSomething(bool success) internal pure { + ``` +
diff --git a/reports/report.sarif b/reports/report.sarif index 914985a7e..ddc8f5e5d 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -1946,6 +1946,26 @@ }, "ruleId": "state-variable-shadowing" }, + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/UncheckedSend.sol" + }, + "region": { + "byteLength": 22, + "byteOffset": 815 + } + } + } + ], + "message": { + "text": "The transaction `address(payable?).send(address)` may fail because of reasons like out-of-gas, invalid receipient address or revert from the recipient. Therefore, the boolean returned by this function call must be checked to be `true` in order to verify that the transaction was successful" + }, + "ruleId": "unchecked-send" + }, { "level": "warning", "locations": [ @@ -2112,6 +2132,50 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/UncheckedSend.sol" + }, + "region": { + "byteLength": 246, + "byteOffset": 85 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/UncheckedSend.sol" + }, + "region": { + "byteLength": 190, + "byteOffset": 337 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/UncheckedSend.sol" + }, + "region": { + "byteLength": 184, + "byteOffset": 533 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/UncheckedSend.sol" + }, + "region": { + "byteLength": 186, + "byteOffset": 723 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -2898,6 +2962,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/UncheckedSend.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -4682,6 +4757,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/UncheckedSend.sol" + }, + "region": { + "byteLength": 65, + "byteOffset": 915 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -5161,6 +5247,17 @@ "byteOffset": 388 } } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/UncheckedSend.sol" + }, + "region": { + "byteLength": 65, + "byteOffset": 915 + } + } } ], "message": { diff --git a/tests/contract-playground/src/UncheckedSend.sol b/tests/contract-playground/src/UncheckedSend.sol new file mode 100644 index 000000000..6822b75fa --- /dev/null +++ b/tests/contract-playground/src/UncheckedSend.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract SendExample { + + function send1(address payable recipient, uint256 amount) external { + // GOOD + bool success = recipient.send(amount); // parent of Send FunctionCall is VariableDeclarationStatement + require(success, "Send successful!"); + } + + function send2(address payable recipient, uint256 amount) external { + // GOOD + doSomething(recipient.send(amount)); // parent of Send FunctionCall is another FunctionCall + } + + function send3(address payable recipient, uint256 amount) external returns(bool) { + // GOOD + return recipient.send(amount); // parent of Send FunctionCall is return + } + + function send4(address payable recipient, uint256 amount) external { + // BAD + recipient.send(amount); // parent of Send FunctionCall is Block (return value is unused) + } + + function doSomething(bool success) internal pure { + + } + +} From 25a368c11b5df5e093b8955f1cb0ee37d85afa2a Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Mon, 29 Jul 2024 19:45:28 +0530 Subject: [PATCH 02/15] Detector: Public variable read in an external context (#619) Co-authored-by: Alex Roan --- aderyn_core/src/detect/detector.rs | 5 + aderyn_core/src/detect/low/mod.rs | 2 + ...ublic_variable_read_in_external_context.rs | 170 ++++++++++++++++++ reports/report.json | 48 ++++- reports/report.md | 53 +++++- reports/report.sarif | 64 +++++++ .../PublicVariableReadInExternalContext.sol | 50 ++++++ 7 files changed, 384 insertions(+), 8 deletions(-) create mode 100644 aderyn_core/src/detect/low/public_variable_read_in_external_context.rs create mode 100644 tests/contract-playground/src/PublicVariableReadInExternalContext.sol diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index eef406e72..80ed73457 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -66,6 +66,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -131,6 +132,7 @@ pub(crate) enum IssueDetectorNamePool { RTLO, UncheckedReturn, DangerousUnaryOperator, + PublicVariableReadInExternalContext, WeakRandomness, PreDeclaredLocalVariableUsage, DeleteNestedMapping, @@ -270,6 +272,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::PublicVariableReadInExternalContext => { + Some(Box::::default()) + } IssueDetectorNamePool::WeakRandomness => Some(Box::::default()), IssueDetectorNamePool::PreDeclaredLocalVariableUsage => { Some(Box::::default()) diff --git a/aderyn_core/src/detect/low/mod.rs b/aderyn_core/src/detect/low/mod.rs index 6b8270129..a20f696f5 100644 --- a/aderyn_core/src/detect/low/mod.rs +++ b/aderyn_core/src/detect/low/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod empty_blocks; pub(crate) mod inconsistent_type_names; pub(crate) mod large_literal_value; pub(crate) mod non_reentrant_before_others; +pub(crate) mod public_variable_read_in_external_context; pub(crate) mod push_0_opcode; pub(crate) mod require_with_string; pub(crate) mod reverts_and_requries_in_loops; @@ -32,6 +33,7 @@ pub use empty_blocks::EmptyBlockDetector; pub use inconsistent_type_names::InconsistentTypeNamesDetector; pub use large_literal_value::LargeLiteralValueDetector; pub use non_reentrant_before_others::NonReentrantBeforeOthersDetector; +pub use public_variable_read_in_external_context::PublicVariableReadInExternalContextDetector; pub use push_0_opcode::PushZeroOpcodeDetector; pub use require_with_string::RequireWithStringDetector; pub use reverts_and_requries_in_loops::RevertsAndRequiresInLoopsDetector; diff --git a/aderyn_core/src/detect/low/public_variable_read_in_external_context.rs b/aderyn_core/src/detect/low/public_variable_read_in_external_context.rs new file mode 100644 index 000000000..a0c95573f --- /dev/null +++ b/aderyn_core/src/detect/low/public_variable_read_in_external_context.rs @@ -0,0 +1,170 @@ +use std::collections::{BTreeMap, HashSet}; +use std::error::Error; + +use crate::ast::{ + ASTNode, ContractDefinition, Expression, Identifier, MemberAccess, NodeID, Visibility, +}; + +use crate::capture; +use crate::context::browser::{ExtractFunctionCalls, ExtractVariableDeclarations}; +use crate::detect::detector::IssueDetectorNamePool; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; +use eyre::{eyre, Result}; + +#[derive(Default)] +pub struct PublicVariableReadInExternalContextDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for PublicVariableReadInExternalContextDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + for contract in context.contract_definitions() { + // Public state variables including the base contracts + if let Ok(public_state_variable_names) = + find_all_public_state_variables_names_for_contract(context, contract) + { + // Find all the `X`s that appear with the pattern `this.X()` + let this_member_accesses = + find_all_public_member_names_called_using_this_keyword_in_contract( + context, contract, + ); + + for member_access in this_member_accesses { + if public_state_variable_names.contains(&member_access.member_name) { + capture!(self, context, member_access); + } + } + } + } + + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::Low + } + + fn title(&self) -> String { + String::from("Public variables of a contract read in an external context (using `this`).") + } + + fn description(&self) -> String { + String::from( + "The contract reads it's own variable using `this` which adds an unnecessary STATICCALL. Remove `this` and access the variable like storage.", + ) + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + IssueDetectorNamePool::PublicVariableReadInExternalContext.to_string() + } +} + +fn find_all_public_member_names_called_using_this_keyword_in_contract<'a>( + context: &'a WorkspaceContext, + contract: &ContractDefinition, +) -> Vec<&'a MemberAccess> { + let mut member_names = vec![]; + + let function_calls = ExtractFunctionCalls::from(contract).extracted; + for function_call in function_calls { + if let Expression::MemberAccess(MemberAccess { id, expression, .. }) = + function_call.expression.as_ref() + { + if let Expression::Identifier(Identifier { name, .. }) = expression.as_ref() { + if name == "this" { + if let Some(ASTNode::MemberAccess(member_acess)) = context.nodes.get(id) { + member_names.push(member_acess) + } + } + } + } + } + + member_names +} + +// Scans the linearized base contracts and returns a list of all the NodeIDs of public variable declarations +fn find_all_public_state_variables_names_for_contract( + context: &WorkspaceContext, + contract: &ContractDefinition, +) -> Result, Box> { + let inheritance_ancestors = contract + .linearized_base_contracts + .as_ref() + .ok_or(eyre!("base contracts not found!"))?; + + Ok(inheritance_ancestors + .iter() + .flat_map(|ancestor_id| { + if let Some(ancestor) = context.nodes.get(ancestor_id) { + let public_variable_declaraions = + ExtractVariableDeclarations::from(ancestor).extracted; + return Some( + public_variable_declaraions + .into_iter() + .filter(|declaration| { + declaration.state_variable + && declaration.visibility == Visibility::Public + }) + .collect::>(), + ); + } + None + }) + .flatten() + .map(|v| v.name.clone()) + .collect()) +} + +#[cfg(test)] +mod public_variable_read_in_external_context_detector_tests { + use serial_test::serial; + + use crate::detect::{ + detector::IssueDetector, + low::public_variable_read_in_external_context::PublicVariableReadInExternalContextDetector, + }; + + #[test] + #[serial] + fn test_public_variable_read_in_external_context() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/PublicVariableReadInExternalContext.sol", + ); + + let mut detector = PublicVariableReadInExternalContextDetector::default(); + let found = detector.detect(&context).unwrap(); + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 4); + // assert the severity is low + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::Low + ); + // assert the title is correct + assert_eq!( + detector.title(), + String::from( + "Public variables of a contract read in an external context (using `this`)." + ) + ); + // assert the description is correct + assert_eq!( + detector.description(), + String::from( + "The contract reads it's own variable using `this` which adds an unnecessary STATICCALL. Remove `this` and access the variable like storage.", + ) + ); + } +} diff --git a/reports/report.json b/reports/report.json index caa2ecc85..c941b7001 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 68, - "total_sloc": 1922 + "total_source_units": 69, + "total_sloc": 1954 }, "files_details": { "files_details": [ @@ -121,6 +121,10 @@ "file_path": "src/PreDeclaredVarUsage.sol", "n_sloc": 9 }, + { + "file_path": "src/PublicVariableReadInExternalContext.sol", + "n_sloc": 32 + }, { "file_path": "src/RTLO.sol", "n_sloc": 7 @@ -281,7 +285,7 @@ }, "issue_count": { "high": 30, - "low": 23 + "low": 24 }, "high_issues": { "issues": [ @@ -1271,6 +1275,12 @@ "src": "355:7", "src_char": "355:7" }, + { + "contract_path": "src/PublicVariableReadInExternalContext.sol", + "line_no": 6, + "src": "130:26", + "src_char": "130:26" + }, { "contract_path": "src/StateShadowing.sol", "line_no": 5, @@ -3479,6 +3489,37 @@ "src_char": "541:5" } ] + }, + { + "title": "Public variables of a contract read in an external context (using `this`).", + "description": "The contract reads it's own variable using `this` which adds an unnecessary STATICCALL. Remove `this` and access the variable like storage.", + "detector_name": "public-variable-read-in-external-context", + "instances": [ + { + "contract_path": "src/PublicVariableReadInExternalContext.sol", + "line_no": 12, + "src": "355:14", + "src_char": "355:14" + }, + { + "contract_path": "src/PublicVariableReadInExternalContext.sol", + "line_no": 16, + "src": "457:16", + "src_char": "457:16" + }, + { + "contract_path": "src/PublicVariableReadInExternalContext.sol", + "line_no": 20, + "src": "553:12", + "src_char": "553:12" + }, + { + "contract_path": "src/PublicVariableReadInExternalContext.sol", + "line_no": 42, + "src": "1175:14", + "src_char": "1175:14" + } + ] } ] }, @@ -3533,6 +3574,7 @@ "rtlo", "unchecked-return", "dangerous-unary-operator", + "public-variable-read-in-external-context", "weak-randomness", "pre-declared-local-variable-usage", "delete-nested-mapping" diff --git a/reports/report.md b/reports/report.md index dba746648..b813542af 100644 --- a/reports/report.md +++ b/reports/report.md @@ -62,6 +62,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [L-21: Unused Custom Error](#l-21-unused-custom-error) - [L-22: Loop contains `require`/`revert` statements](#l-22-loop-contains-requirerevert-statements) - [L-23: Incorrect Order of Division and Multiplication](#l-23-incorrect-order-of-division-and-multiplication) + - [L-24: Public variables of a contract read in an external context (using `this`).](#l-24-public-variables-of-a-contract-read-in-an-external-context-using-this) # Summary @@ -70,8 +71,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 68 | -| Total nSLOC | 1922 | +| .sol Files | 69 | +| Total nSLOC | 1954 | ## Files Details @@ -107,6 +108,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/MultipleConstructorSchemes.sol | 10 | | src/OnceModifierExample.sol | 8 | | src/PreDeclaredVarUsage.sol | 9 | +| src/PublicVariableReadInExternalContext.sol | 32 | | src/RTLO.sol | 7 | | src/RevertsAndRequriesInLoops.sol | 27 | | src/SendEtherNoChecks.sol | 58 | @@ -146,7 +148,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **1922** | +| **Total** | **1954** | ## Issue Summary @@ -154,7 +156,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Category | No. of Issues | | --- | --- | | High | 30 | -| Low | 23 | +| Low | 24 | # High Issues @@ -1176,7 +1178,7 @@ If the length of a dynamic array (storage variable) directly assigned to, it may Solidity does initialize variables by default when you declare them, however it's good practice to explicitly declare an initial value. For example, if you transfer money to an address we must make sure that the address has been initialized. -
13 Found Instances +
14 Found Instances - Found in src/AssemblyExample.sol [Line: 5](../tests/contract-playground/src/AssemblyExample.sol#L5) @@ -1209,6 +1211,12 @@ Solidity does initialize variables by default when you declare them, however it' uint256 private s_first; ``` +- Found in src/PublicVariableReadInExternalContext.sol [Line: 6](../tests/contract-playground/src/PublicVariableReadInExternalContext.sol#L6) + + ```solidity + uint256 public testUint256; + ``` + - Found in src/StateShadowing.sol [Line: 5](../tests/contract-playground/src/StateShadowing.sol#L5) ```solidity @@ -3557,3 +3565,38 @@ Division operations followed directly by multiplication operations can lead to p +## L-24: Public variables of a contract read in an external context (using `this`). + +The contract reads it's own variable using `this` which adds an unnecessary STATICCALL. Remove `this` and access the variable like storage. + +
4 Found Instances + + +- Found in src/PublicVariableReadInExternalContext.sol [Line: 12](../tests/contract-playground/src/PublicVariableReadInExternalContext.sol#L12) + + ```solidity + return this.testArray(0); + ``` + +- Found in src/PublicVariableReadInExternalContext.sol [Line: 16](../tests/contract-playground/src/PublicVariableReadInExternalContext.sol#L16) + + ```solidity + return this.testUint256(); + ``` + +- Found in src/PublicVariableReadInExternalContext.sol [Line: 20](../tests/contract-playground/src/PublicVariableReadInExternalContext.sol#L20) + + ```solidity + return this.testMap(0); + ``` + +- Found in src/PublicVariableReadInExternalContext.sol [Line: 42](../tests/contract-playground/src/PublicVariableReadInExternalContext.sol#L42) + + ```solidity + return this.testArray(0); + ``` + +
+ + + diff --git a/reports/report.sarif b/reports/report.sarif index ddc8f5e5d..3d415b864 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -1748,6 +1748,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/PublicVariableReadInExternalContext.sol" + }, + "region": { + "byteLength": 26, + "byteOffset": 130 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -5650,6 +5661,59 @@ "text": "Division operations followed directly by multiplication operations can lead to precision loss due to the way integer arithmetic is handled in Solidity." }, "ruleId": "division-before-multiplication" + }, + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/PublicVariableReadInExternalContext.sol" + }, + "region": { + "byteLength": 14, + "byteOffset": 355 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/PublicVariableReadInExternalContext.sol" + }, + "region": { + "byteLength": 16, + "byteOffset": 457 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/PublicVariableReadInExternalContext.sol" + }, + "region": { + "byteLength": 12, + "byteOffset": 553 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/PublicVariableReadInExternalContext.sol" + }, + "region": { + "byteLength": 14, + "byteOffset": 1175 + } + } + } + ], + "message": { + "text": "The contract reads it's own variable using `this` which adds an unnecessary STATICCALL. Remove `this` and access the variable like storage." + }, + "ruleId": "public-variable-read-in-external-context" } ], "tool": { diff --git a/tests/contract-playground/src/PublicVariableReadInExternalContext.sol b/tests/contract-playground/src/PublicVariableReadInExternalContext.sol new file mode 100644 index 000000000..7635d4db9 --- /dev/null +++ b/tests/contract-playground/src/PublicVariableReadInExternalContext.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.5.0; + +contract PublicVariableReadUsingThis { + string[] public testArray; + uint256 public testUint256; + mapping(uint256 => bool) public testMap; + + // BAD ways of reading (using this.) + + function readStringArray() external view returns (string memory) { + return this.testArray(0); + } + + function readUint256() external view returns (uint256) { + return this.testUint256(); + } + + function readMap() external view returns (bool) { + return this.testMap(0); + } + + // GOOD ways of reading (using ) + + function readStringArrayGood() external view returns (string memory) { + return testArray[0]; + } + + function readUint256Good() external view returns (uint256) { + return testUint256; + } + + function readMapGood() external view returns (bool) { + return testMap[0]; + } +} + +contract DerivedFromPublicVariableReadUsingThis is PublicVariableReadUsingThis { + // BAD ways of reading (using this.) + + function readStringArray() external view returns (string memory) { + return this.testArray(0); + } + + // GOOD ways of reading (using ) + + function readStringArrayGood() external view returns (string memory) { + return testArray[0]; + } +} From 63786210bb74a8ad3723a4dee91f3a8fffe884d9 Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Mon, 29 Jul 2024 19:59:58 +0530 Subject: [PATCH 03/15] Detector Redundant statements (#621) Co-authored-by: Alex Roan --- aderyn_core/src/detect/detector.rs | 32 +++++ aderyn_core/src/detect/low/mod.rs | 2 + .../src/detect/low/redundant_statements.rs | 112 ++++++++++++++++++ reports/report.json | 60 +++++++++- reports/report.md | 69 +++++++++-- reports/report.sarif | 86 ++++++++++++++ reports/templegold-report.md | 20 +++- .../src/RedundantStatements.sol | 17 +++ 8 files changed, 387 insertions(+), 11 deletions(-) create mode 100644 aderyn_core/src/detect/low/redundant_statements.rs create mode 100644 tests/contract-playground/src/RedundantStatements.sol diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index 80ed73457..c2f5dd464 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -5,6 +5,33 @@ use crate::{ ast::NodeID, context::workspace_context::WorkspaceContext, detect::{high::*, low::*}, + detect::{ + high::{ + ArbitraryTransferFromDetector, AvoidAbiEncodePackedDetector, + BlockTimestampDeadlineDetector, DangerousUnaryOperatorDetector, + DelegateCallInLoopDetector, DynamicArrayLengthAssignmentDetector, + EnumerableLoopRemovalDetector, ExperimentalEncoderDetector, + IncorrectShiftOrderDetector, IncorrectUseOfCaretOperatorDetector, + MultipleConstructorsDetector, NestedStructInMappingDetector, RTLODetector, + ReusedContractNameDetector, SelfdestructIdentifierDetector, + StateVariableShadowingDetector, StorageArrayEditWithMemoryDetector, + TautologicalCompareDetector, UncheckedReturnDetector, + UninitializedStateVariableDetector, UnprotectedInitializerDetector, + UnsafeCastingDetector, YulReturnDetector, + }, + low::{ + CentralizationRiskDetector, ConstantsInsteadOfLiteralsDetector, + ContractsWithTodosDetector, DeprecatedOZFunctionsDetector, + DivisionBeforeMultiplicationDetector, EcrecoverDetector, EmptyBlockDetector, + InconsistentTypeNamesDetector, LargeLiteralValueDetector, + NonReentrantBeforeOthersDetector, PushZeroOpcodeDetector, RedundantStatementsDetector, + RequireWithStringDetector, RevertsAndRequiresInLoopsDetector, + SolmateSafeTransferLibDetector, UnindexedEventsDetector, UnsafeERC20FunctionsDetector, + UnsafeERC721MintDetector, UnspecificSolidityPragmaDetector, UselessErrorDetector, + UselessInternalFunctionDetector, UselessModifierDetector, + UselessPublicFunctionDetector, ZeroAddressCheckDetector, + }, + }, }; use std::{ @@ -66,6 +93,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -132,6 +160,7 @@ pub(crate) enum IssueDetectorNamePool { RTLO, UncheckedReturn, DangerousUnaryOperator, + RedundantStatements, PublicVariableReadInExternalContext, WeakRandomness, PreDeclaredLocalVariableUsage, @@ -272,6 +301,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::RedundantStatements => { + Some(Box::::default()) + } IssueDetectorNamePool::PublicVariableReadInExternalContext => { Some(Box::::default()) } diff --git a/aderyn_core/src/detect/low/mod.rs b/aderyn_core/src/detect/low/mod.rs index a20f696f5..aeef32d20 100644 --- a/aderyn_core/src/detect/low/mod.rs +++ b/aderyn_core/src/detect/low/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod large_literal_value; pub(crate) mod non_reentrant_before_others; pub(crate) mod public_variable_read_in_external_context; pub(crate) mod push_0_opcode; +pub(crate) mod redundant_statements; pub(crate) mod require_with_string; pub(crate) mod reverts_and_requries_in_loops; pub(crate) mod solmate_safe_transfer_lib; @@ -35,6 +36,7 @@ pub use large_literal_value::LargeLiteralValueDetector; pub use non_reentrant_before_others::NonReentrantBeforeOthersDetector; pub use public_variable_read_in_external_context::PublicVariableReadInExternalContextDetector; pub use push_0_opcode::PushZeroOpcodeDetector; +pub use redundant_statements::RedundantStatementsDetector; pub use require_with_string::RequireWithStringDetector; pub use reverts_and_requries_in_loops::RevertsAndRequiresInLoopsDetector; pub use solmate_safe_transfer_lib::SolmateSafeTransferLibDetector; diff --git a/aderyn_core/src/detect/low/redundant_statements.rs b/aderyn_core/src/detect/low/redundant_statements.rs new file mode 100644 index 000000000..1ff8c3430 --- /dev/null +++ b/aderyn_core/src/detect/low/redundant_statements.rs @@ -0,0 +1,112 @@ +use std::collections::BTreeMap; +use std::error::Error; + +use crate::ast::{Expression, NodeID, NodeType}; + +use crate::capture; +use crate::context::browser::GetImmediateParent; +use crate::detect::detector::IssueDetectorNamePool; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; +use eyre::Result; + +// HOW TO USE THIS TEMPLATE: +// 1. Copy this file and rename it to the snake_case version of the issue you are detecting. +// 2. Rename the RedundantStatementsDetector struct and impl to your new issue name. +// 3. Add this file and detector struct to the mod.rs file in the same directory. +// 4. Implement the detect function to find instances of the issue. + +#[derive(Default)] +pub struct RedundantStatementsDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for RedundantStatementsDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + for expression_statement in context.expression_statements() { + if let Some(parent) = expression_statement.expression.parent(context) { + if parent.node_type() != NodeType::Block { + continue; + } + + match &expression_statement.expression { + Expression::Identifier(identifier) => { + capture!(self, context, identifier); + } + Expression::ElementaryTypeNameExpression(elementary_type_expression) => { + capture!(self, context, elementary_type_expression); + } + _ => (), + }; + } + } + + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::Low + } + + fn title(&self) -> String { + String::from("Redundant statements have no effect.") + } + + fn description(&self) -> String { + String::from("Remove the redundant statements because no code will be generated and it just congests the codebase.") + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + IssueDetectorNamePool::RedundantStatements.to_string() + } +} + +#[cfg(test)] +mod redundant_statements_detector { + use serial_test::serial; + + use crate::detect::{ + detector::IssueDetector, low::redundant_statements::RedundantStatementsDetector, + }; + + #[test] + #[serial] + fn test_redundant_statements() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/RedundantStatements.sol", + ); + + let mut detector = RedundantStatementsDetector::default(); + let found = detector.detect(&context).unwrap(); + // assert that the detector found an issue + assert!(found); + + println!("{:#?}", detector.instances()); + + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 6); + // assert the severity is low + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::Low + ); + // assert the title is correct + assert_eq!( + detector.title(), + String::from("Redundant statements have no effect.") + ); + // assert the description is correct + assert_eq!( + detector.description(), + String::from("Remove the redundant statements because no code will be generated and it just congests the codebase.") + ); + } +} diff --git a/reports/report.json b/reports/report.json index c941b7001..6581acfc1 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 69, - "total_sloc": 1954 + "total_source_units": 70, + "total_sloc": 1968 }, "files_details": { "files_details": [ @@ -129,6 +129,10 @@ "file_path": "src/RTLO.sol", "n_sloc": 7 }, + { + "file_path": "src/RedundantStatements.sol", + "n_sloc": 14 + }, { "file_path": "src/RevertsAndRequriesInLoops.sol", "n_sloc": 27 @@ -285,7 +289,7 @@ }, "issue_count": { "high": 30, - "low": 24 + "low": 25 }, "high_issues": { "issues": [ @@ -1989,6 +1993,12 @@ "src": "32:23", "src_char": "32:23" }, + { + "contract_path": "src/RedundantStatements.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/UncheckedSend.sol", "line_no": 2, @@ -3490,6 +3500,49 @@ } ] }, + { + "title": "Redundant statements have no effect.", + "description": "Remove the redundant statements because no code will be generated and it just congests the codebase.", + "detector_name": "redundant-statements", + "instances": [ + { + "contract_path": "src/RedundantStatements.sol", + "line_no": 6, + "src": "131:4", + "src_char": "131:4" + }, + { + "contract_path": "src/RedundantStatements.sol", + "line_no": 7, + "src": "169:4", + "src_char": "169:4" + }, + { + "contract_path": "src/RedundantStatements.sol", + "line_no": 8, + "src": "207:27", + "src_char": "207:27" + }, + { + "contract_path": "src/RedundantStatements.sol", + "line_no": 12, + "src": "309:4", + "src_char": "309:4" + }, + { + "contract_path": "src/RedundantStatements.sol", + "line_no": 13, + "src": "347:6", + "src_char": "347:6" + }, + { + "contract_path": "src/RedundantStatements.sol", + "line_no": 14, + "src": "377:4", + "src_char": "377:4" + } + ] + }, { "title": "Public variables of a contract read in an external context (using `this`).", "description": "The contract reads it's own variable using `this` which adds an unnecessary STATICCALL. Remove `this` and access the variable like storage.", @@ -3574,6 +3627,7 @@ "rtlo", "unchecked-return", "dangerous-unary-operator", + "redundant-statements", "public-variable-read-in-external-context", "weak-randomness", "pre-declared-local-variable-usage", diff --git a/reports/report.md b/reports/report.md index b813542af..bd66f40d1 100644 --- a/reports/report.md +++ b/reports/report.md @@ -62,7 +62,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [L-21: Unused Custom Error](#l-21-unused-custom-error) - [L-22: Loop contains `require`/`revert` statements](#l-22-loop-contains-requirerevert-statements) - [L-23: Incorrect Order of Division and Multiplication](#l-23-incorrect-order-of-division-and-multiplication) - - [L-24: Public variables of a contract read in an external context (using `this`).](#l-24-public-variables-of-a-contract-read-in-an-external-context-using-this) + - [L-24: Redundant statements have no effect.](#l-24-redundant-statements-have-no-effect) + - [L-25: Public variables of a contract read in an external context (using `this`).](#l-25-public-variables-of-a-contract-read-in-an-external-context-using-this) # Summary @@ -71,8 +72,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 69 | -| Total nSLOC | 1954 | +| .sol Files | 70 | +| Total nSLOC | 1968 | ## Files Details @@ -110,6 +111,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/PreDeclaredVarUsage.sol | 9 | | src/PublicVariableReadInExternalContext.sol | 32 | | src/RTLO.sol | 7 | +| src/RedundantStatements.sol | 14 | | src/RevertsAndRequriesInLoops.sol | 27 | | src/SendEtherNoChecks.sol | 58 | | src/StateShadowing.sol | 17 | @@ -148,7 +150,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **1954** | +| **Total** | **1968** | ## Issue Summary @@ -156,7 +158,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Category | No. of Issues | | --- | --- | | High | 30 | -| Low | 24 | +| Low | 25 | # High Issues @@ -1955,7 +1957,7 @@ ERC20 functions may not behave as expected. For example: return values are not a Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;` -
16 Found Instances +
17 Found Instances - Found in src/ContractWithTodo.sol [Line: 2](../tests/contract-playground/src/ContractWithTodo.sol#L2) @@ -2006,6 +2008,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid pragma solidity ^0.4.0; ``` +- Found in src/RedundantStatements.sol [Line: 2](../tests/contract-playground/src/RedundantStatements.sol#L2) + + ```solidity + pragma solidity ^0.4.0; + ``` + - Found in src/UncheckedSend.sol [Line: 2](../tests/contract-playground/src/UncheckedSend.sol#L2) ```solidity @@ -3565,7 +3573,54 @@ Division operations followed directly by multiplication operations can lead to p -## L-24: Public variables of a contract read in an external context (using `this`). +## L-24: Redundant statements have no effect. + +Remove the redundant statements because no code will be generated and it just congests the codebase. + +
6 Found Instances + + +- Found in src/RedundantStatements.sol [Line: 6](../tests/contract-playground/src/RedundantStatements.sol#L6) + + ```solidity + uint; // Elementary Type Name + ``` + +- Found in src/RedundantStatements.sol [Line: 7](../tests/contract-playground/src/RedundantStatements.sol#L7) + + ```solidity + bool; // Elementary Type Name + ``` + +- Found in src/RedundantStatements.sol [Line: 8](../tests/contract-playground/src/RedundantStatements.sol#L8) + + ```solidity + RedundantStatementsContract; // Identifier + ``` + +- Found in src/RedundantStatements.sol [Line: 12](../tests/contract-playground/src/RedundantStatements.sol#L12) + + ```solidity + uint; // Elementary Type Name + ``` + +- Found in src/RedundantStatements.sol [Line: 13](../tests/contract-playground/src/RedundantStatements.sol#L13) + + ```solidity + assert; // Identifier + ``` + +- Found in src/RedundantStatements.sol [Line: 14](../tests/contract-playground/src/RedundantStatements.sol#L14) + + ```solidity + test; // Identifier + ``` + +
+ + + +## L-25: Public variables of a contract read in an external context (using `this`). The contract reads it's own variable using `this` which adds an unnecessary STATICCALL. Remove `this` and access the variable like storage. diff --git a/reports/report.sarif b/reports/report.sarif index 3d415b864..c9f3f841a 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -2973,6 +2973,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/RedundantStatements.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -5662,6 +5673,81 @@ }, "ruleId": "division-before-multiplication" }, + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/RedundantStatements.sol" + }, + "region": { + "byteLength": 4, + "byteOffset": 131 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/RedundantStatements.sol" + }, + "region": { + "byteLength": 4, + "byteOffset": 169 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/RedundantStatements.sol" + }, + "region": { + "byteLength": 27, + "byteOffset": 207 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/RedundantStatements.sol" + }, + "region": { + "byteLength": 4, + "byteOffset": 309 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/RedundantStatements.sol" + }, + "region": { + "byteLength": 6, + "byteOffset": 347 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/RedundantStatements.sol" + }, + "region": { + "byteLength": 4, + "byteOffset": 377 + } + } + } + ], + "message": { + "text": "Remove the redundant statements because no code will be generated and it just congests the codebase." + }, + "ruleId": "redundant-statements" + }, { "level": "note", "locations": [ diff --git a/reports/templegold-report.md b/reports/templegold-report.md index b7a152e8d..e6f22785c 100644 --- a/reports/templegold-report.md +++ b/reports/templegold-report.md @@ -35,6 +35,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [L-16: Unused Custom Error](#l-16-unused-custom-error) - [L-17: Loop contains `require`/`revert` statements](#l-17-loop-contains-requirerevert-statements) - [L-18: Incorrect Order of Division and Multiplication](#l-18-incorrect-order-of-division-and-multiplication) + - [L-19: Redundant statements have no effect.](#l-19-redundant-statements-have-no-effect) # Summary @@ -188,7 +189,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Category | No. of Issues | | --- | --- | | High | 8 | -| Low | 18 | +| Low | 19 | # High Issues @@ -8569,3 +8570,20 @@ Division operations followed directly by multiplication operations can lead to p +## L-19: Redundant statements have no effect. + +Remove the redundant statements because no code will be generated and it just congests the codebase. + +
1 Found Instances + + +- Found in contracts/deprecated/InstantExitQueue.sol [Line: 28](../tests/2024-07-templegold/protocol/contracts/deprecated/InstantExitQueue.sol#L28) + + ```solidity + _exiter; + ``` + +
+ + + diff --git a/tests/contract-playground/src/RedundantStatements.sol b/tests/contract-playground/src/RedundantStatements.sol new file mode 100644 index 000000000..73c5b1159 --- /dev/null +++ b/tests/contract-playground/src/RedundantStatements.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.4.0; + +contract RedundantStatementsContract { + constructor() public { + uint; // Elementary Type Name + bool; // Elementary Type Name + RedundantStatementsContract; // Identifier + } + + function test() public returns (uint) { + uint; // Elementary Type Name + assert; // Identifier + test; // Identifier + return 777; + } +} From 2aea4d068fd280bbd0a1a9cdc67e4d1ca75028ce Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Tue, 30 Jul 2024 18:24:20 +0530 Subject: [PATCH 04/15] Detector: Storage signed integer array (#624) Co-authored-by: Alex Roan --- Cargo.lock | 24 ++ aderyn_core/Cargo.toml | 3 +- aderyn_core/src/detect/detector.rs | 32 +-- aderyn_core/src/detect/high/mod.rs | 2 + .../high/storage_signed_integer_array.rs | 206 ++++++++++++++++++ .../adhoc-sol-files-highs-only-report.json | 1 + reports/report.json | 42 +++- reports/report.md | 61 +++++- reports/report.sarif | 53 +++++ .../CompilerBugStorageSignedIntegerArray.sol | 17 ++ 10 files changed, 398 insertions(+), 43 deletions(-) create mode 100644 aderyn_core/src/detect/high/storage_signed_integer_array.rs create mode 100644 tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol diff --git a/Cargo.lock b/Cargo.lock index 6db72ae99..d7ac1aa65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,7 @@ dependencies = [ "derive_more", "eyre", "ignore", + "lazy-regex", "once_cell", "phf", "prettytable", @@ -1968,6 +1969,29 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "lazy-regex" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576c8060ecfdf2e56995cf3274b4f2d71fa5e4fa3607c1c0b63c10180ee58741" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9efb9e65d4503df81c615dc33ff07042a9408ac7f26b45abee25566f7fbfd12c" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.67", +] + [[package]] name = "lazy_static" version = "1.5.0" diff --git a/aderyn_core/Cargo.toml b/aderyn_core/Cargo.toml index 5465f15b1..008e4c1d5 100644 --- a/aderyn_core/Cargo.toml +++ b/aderyn_core/Cargo.toml @@ -23,8 +23,9 @@ serde_repr = "0.1.12" strum = { version = "0.26", features = ["derive"] } toml = "0.8.2" cyfrin-foundry-compilers = { version = "0.3.20-aderyn", features = ["svm-solc"] } +lazy-regex = "3.2.0" derive_more = "0.99.18" [dev-dependencies] serial_test = "3.0.0" -once_cell = "1.19.0" \ No newline at end of file +once_cell = "1.19.0" diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index c2f5dd464..048fdef7e 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -5,33 +5,6 @@ use crate::{ ast::NodeID, context::workspace_context::WorkspaceContext, detect::{high::*, low::*}, - detect::{ - high::{ - ArbitraryTransferFromDetector, AvoidAbiEncodePackedDetector, - BlockTimestampDeadlineDetector, DangerousUnaryOperatorDetector, - DelegateCallInLoopDetector, DynamicArrayLengthAssignmentDetector, - EnumerableLoopRemovalDetector, ExperimentalEncoderDetector, - IncorrectShiftOrderDetector, IncorrectUseOfCaretOperatorDetector, - MultipleConstructorsDetector, NestedStructInMappingDetector, RTLODetector, - ReusedContractNameDetector, SelfdestructIdentifierDetector, - StateVariableShadowingDetector, StorageArrayEditWithMemoryDetector, - TautologicalCompareDetector, UncheckedReturnDetector, - UninitializedStateVariableDetector, UnprotectedInitializerDetector, - UnsafeCastingDetector, YulReturnDetector, - }, - low::{ - CentralizationRiskDetector, ConstantsInsteadOfLiteralsDetector, - ContractsWithTodosDetector, DeprecatedOZFunctionsDetector, - DivisionBeforeMultiplicationDetector, EcrecoverDetector, EmptyBlockDetector, - InconsistentTypeNamesDetector, LargeLiteralValueDetector, - NonReentrantBeforeOthersDetector, PushZeroOpcodeDetector, RedundantStatementsDetector, - RequireWithStringDetector, RevertsAndRequiresInLoopsDetector, - SolmateSafeTransferLibDetector, UnindexedEventsDetector, UnsafeERC20FunctionsDetector, - UnsafeERC721MintDetector, UnspecificSolidityPragmaDetector, UselessErrorDetector, - UselessInternalFunctionDetector, UselessModifierDetector, - UselessPublicFunctionDetector, ZeroAddressCheckDetector, - }, - }, }; use std::{ @@ -93,6 +66,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -160,6 +134,7 @@ pub(crate) enum IssueDetectorNamePool { RTLO, UncheckedReturn, DangerousUnaryOperator, + SignedStorageArray, RedundantStatements, PublicVariableReadInExternalContext, WeakRandomness, @@ -301,6 +276,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::SignedStorageArray => { + Some(Box::::default()) + } IssueDetectorNamePool::RedundantStatements => { Some(Box::::default()) } diff --git a/aderyn_core/src/detect/high/mod.rs b/aderyn_core/src/detect/high/mod.rs index 1e9e9912f..2021881f2 100644 --- a/aderyn_core/src/detect/high/mod.rs +++ b/aderyn_core/src/detect/high/mod.rs @@ -20,6 +20,7 @@ pub(crate) mod selfdestruct; pub(crate) mod send_ether_no_checks; pub(crate) mod state_variable_shadowing; pub(crate) mod storage_array_edit_with_memory; +pub(crate) mod storage_signed_integer_array; pub(crate) mod tautological_compare; pub(crate) mod unchecked_return; pub(crate) mod unchecked_send; @@ -51,6 +52,7 @@ pub use selfdestruct::SelfdestructIdentifierDetector; pub use send_ether_no_checks::SendEtherNoChecksDetector; pub use state_variable_shadowing::StateVariableShadowingDetector; pub use storage_array_edit_with_memory::StorageArrayEditWithMemoryDetector; +pub use storage_signed_integer_array::StorageSignedIntegerArrayDetector; pub use tautological_compare::TautologicalCompareDetector; pub use unchecked_return::UncheckedReturnDetector; pub use unchecked_send::UncheckedSendDetector; diff --git a/aderyn_core/src/detect/high/storage_signed_integer_array.rs b/aderyn_core/src/detect/high/storage_signed_integer_array.rs new file mode 100644 index 000000000..e1acde91d --- /dev/null +++ b/aderyn_core/src/detect/high/storage_signed_integer_array.rs @@ -0,0 +1,206 @@ +use std::collections::BTreeMap; +use std::error::Error; +use std::str::FromStr; + +use crate::ast::{ + ASTNode, Expression, Identifier, NodeID, TupleExpression, TypeDescriptions, UnaryOperation, +}; + +use crate::capture; +use crate::context::browser::{ + ExtractPragmaDirectives, ExtractTupleExpressions, GetImmediateParent, +}; +use crate::detect::detector::IssueDetectorNamePool; +use crate::detect::helpers; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; +use eyre::Result; +use lazy_regex::regex; +use semver::{Version, VersionReq}; + +#[derive(Default)] +pub struct StorageSignedIntegerArrayDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for StorageSignedIntegerArrayDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + for source_unit in context.source_units() { + let tuple_expressions = ExtractTupleExpressions::from(source_unit).extracted; + let pragma_directives = ExtractPragmaDirectives::from(source_unit).extracted; + + if let Some(pragma_directive) = pragma_directives.first() { + if let Ok(pragma_semver) = helpers::pragma_directive_to_semver(pragma_directive) { + if version_req_allows_below_0_5_10(&pragma_semver) { + // Search for a literal array with one negative value in it + for tuple_expression in tuple_expressions + .into_iter() + .filter(|tuple_expression| tuple_expression.is_inline_array) + { + // First, make sure it's being assigned to an array pointer to storage + if !is_tuple_being_assigned_to_storage_array(&tuple_expression, context) + { + continue; + } + + // Now, make sure there is at least 1 negative value in the tuple array + let negative_component_present = + tuple_expression.components.iter().any(|c| { + if let Some(Expression::UnaryOperation(UnaryOperation { + operator, + .. + })) = c + { + return operator == "-"; + } + false + }); + + if negative_component_present { + capture!(self, context, tuple_expression); + } + } + } + } + } + } + + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::High + } + + fn title(&self) -> String { + String::from( + "Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`", + ) + } + + fn description(&self) -> String { + String::from("If you want to leverage signed arrays in storage by assigning a literal array with at least one negative \ + number, then you mus use solidity version 0.5.10 or above. This is because of a bug in older compilers.") + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + IssueDetectorNamePool::SignedStorageArray.to_string() + } +} + +fn version_req_allows_below_0_5_10(version_req: &VersionReq) -> bool { + // If it matches any 0.4.0 to 0.4.26, return true + for i in 0..=26 { + let version = Version::from_str(&format!("0.4.{}", i)).unwrap(); + if version_req.matches(&version) { + return true; + } + } + + // If it matches any 0.5.0 to 0.5.9 return true + for i in 0..=9 { + let version = Version::from_str(&format!("0.5.{}", i)).unwrap(); + if version_req.matches(&version) { + return true; + } + } + + // Else, return false + false +} + +// Build a regular expression to catch type names that correspond to pointers to storage arrays +static SIGNED_STORAGE_ARRAY_POINTER: &lazy_regex::Lazy = + regex!(r"^int[0-9]*\[[0-9]*] storage ref$"); + +fn is_tuple_being_assigned_to_storage_array( + tuple_expression: &TupleExpression, + context: &WorkspaceContext, +) -> bool { + if let Some(ASTNode::Assignment(assignment)) = tuple_expression.parent(context) { + if let Expression::Identifier(Identifier { + type_descriptions: + TypeDescriptions { + type_string: Some(type_string), + .. + }, + .. + }) = assignment.left_hand_side.as_ref() + { + if SIGNED_STORAGE_ARRAY_POINTER.is_match(type_string) { + return true; + } + } + } + false +} + +#[cfg(test)] +mod storage_signed_array_detector { + use serial_test::serial; + + use crate::detect::{ + detector::IssueDetector, + high::storage_signed_integer_array::{ + StorageSignedIntegerArrayDetector, SIGNED_STORAGE_ARRAY_POINTER, + }, + }; + + #[test] + #[serial] + fn test_storage_signed_array() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol", + ); + + let mut detector = StorageSignedIntegerArrayDetector::default(); + let found = detector.detect(&context).unwrap(); + + println!("{:?}", detector.instances()); + + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 1); + // assert the severity is high + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::High + ); + // assert the title is correct + assert_eq!( + detector.title(), + String::from( + "Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`", + ) + ); + // assert the description is correct + assert_eq!( + detector.description(), + String::from("If you want to leverage signed arrays in storage by assigning a literal array with at least one negative \ + number, then you mus use solidity version 0.5.10 or above. This is because of a bug in older compilers.") + ); + } + + #[test] + fn test_regular_expression_works() { + // TARGET signed storage array references + + assert!(SIGNED_STORAGE_ARRAY_POINTER.is_match("int256[3] storage ref")); + assert!(SIGNED_STORAGE_ARRAY_POINTER.is_match("int[1300] storage ref")); + assert!(SIGNED_STORAGE_ARRAY_POINTER.is_match("int8[] storage ref")); + assert!(SIGNED_STORAGE_ARRAY_POINTER.is_match("int[] storage ref")); + assert!(!SIGNED_STORAGE_ARRAY_POINTER.is_match("uint256[3] storage ref")); + assert!(!SIGNED_STORAGE_ARRAY_POINTER.is_match("uint[1300] storage ref")); + assert!(!SIGNED_STORAGE_ARRAY_POINTER.is_match("uint8[] storage ref")); + assert!(!SIGNED_STORAGE_ARRAY_POINTER.is_match("uint[] storage ref")); + } +} diff --git a/reports/adhoc-sol-files-highs-only-report.json b/reports/adhoc-sol-files-highs-only-report.json index 3f591e527..2363e9be8 100644 --- a/reports/adhoc-sol-files-highs-only-report.json +++ b/reports/adhoc-sol-files-highs-only-report.json @@ -189,6 +189,7 @@ "rtlo", "unchecked-return", "dangerous-unary-operator", + "signed-storage-array", "weak-randomness", "pre-declared-local-variable-usage", "delete-nested-mapping" diff --git a/reports/report.json b/reports/report.json index 6581acfc1..37c0f9e16 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 70, - "total_sloc": 1968 + "total_source_units": 71, + "total_sloc": 1981 }, "files_details": { "files_details": [ @@ -29,6 +29,10 @@ "file_path": "src/Casting.sol", "n_sloc": 126 }, + { + "file_path": "src/CompilerBugStorageSignedIntegerArray.sol", + "n_sloc": 13 + }, { "file_path": "src/ConstantsLiterals.sol", "n_sloc": 28 @@ -288,7 +292,7 @@ ] }, "issue_count": { - "high": 30, + "high": 31, "low": 25 }, "high_issues": { @@ -1646,6 +1650,19 @@ } ] }, + { + "title": "Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`", + "description": "If you want to leverage signed arrays in storage by assigning a literal array with at least one negative number, then you mus use solidity version 0.5.10 or above. This is because of a bug in older compilers.", + "detector_name": "signed-storage-array", + "instances": [ + { + "contract_path": "src/CompilerBugStorageSignedIntegerArray.sol", + "line_no": 9, + "src": "230:10", + "src_char": "230:10" + } + ] + }, { "title": "Weak Randomness", "description": "The use of keccak256 hash functions on predictable values like block.timestamp, block.number, or similar data, including modulo operations on these values, should be avoided for generating randomness, as they are easily predictable and manipulable. The `PREVRANDAO` opcode also should not be used as a source of randomness. Instead, utilize Chainlink VRF for cryptographically secure and provably random values to ensure protocol integrity.", @@ -1945,6 +1962,12 @@ "description": "Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;`", "detector_name": "unspecific-solidity-pragma", "instances": [ + { + "contract_path": "src/CompilerBugStorageSignedIntegerArray.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/ContractWithTodo.sol", "line_no": 2, @@ -2266,6 +2289,18 @@ "src": "2103:18", "src_char": "2103:18" }, + { + "contract_path": "src/CompilerBugStorageSignedIntegerArray.sol", + "line_no": 9, + "src": "235:1", + "src_char": "235:1" + }, + { + "contract_path": "src/CompilerBugStorageSignedIntegerArray.sol", + "line_no": 14, + "src": "352:1", + "src_char": "352:1" + }, { "contract_path": "src/ConstantsLiterals.sol", "line_no": 25, @@ -3627,6 +3662,7 @@ "rtlo", "unchecked-return", "dangerous-unary-operator", + "signed-storage-array", "redundant-statements", "public-variable-read-in-external-context", "weak-randomness", diff --git a/reports/report.md b/reports/report.md index bd66f40d1..345aa4778 100644 --- a/reports/report.md +++ b/reports/report.md @@ -35,9 +35,10 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [H-25: RTLO character detected in file. \u{202e}](#h-25-rtlo-character-detected-in-file-u202e) - [H-26: Return value of the function call is not checked.](#h-26-return-value-of-the-function-call-is-not-checked) - [H-27: Dangerous unary operator found in assignment.](#h-27-dangerous-unary-operator-found-in-assignment) - - [H-28: Weak Randomness](#h-28-weak-randomness) - - [H-29: Usage of variable before declaration.](#h-29-usage-of-variable-before-declaration) - - [H-30: Deletion from a nested mappping.](#h-30-deletion-from-a-nested-mappping) + - [H-28: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`](#h-28-compiler-bug-signed-array-in-storage-detected-for-compiler-version-0510) + - [H-29: Weak Randomness](#h-29-weak-randomness) + - [H-30: Usage of variable before declaration.](#h-30-usage-of-variable-before-declaration) + - [H-31: Deletion from a nested mappping.](#h-31-deletion-from-a-nested-mappping) - [Low Issues](#low-issues) - [L-1: Centralization Risk for trusted owners](#l-1-centralization-risk-for-trusted-owners) - [L-2: Solmate's SafeTransferLib does not check for token contract's existence](#l-2-solmates-safetransferlib-does-not-check-for-token-contracts-existence) @@ -72,8 +73,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 70 | -| Total nSLOC | 1968 | +| .sol Files | 71 | +| Total nSLOC | 1981 | ## Files Details @@ -86,6 +87,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/AssemblyExample.sol | 9 | | src/CallGraphTests.sol | 49 | | src/Casting.sol | 126 | +| src/CompilerBugStorageSignedIntegerArray.sol | 13 | | src/ConstantsLiterals.sol | 28 | | src/ContractWithTodo.sol | 7 | | src/Counter.sol | 20 | @@ -150,14 +152,14 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **1968** | +| **Total** | **1981** | ## Issue Summary | Category | No. of Issues | | --- | --- | -| High | 30 | +| High | 31 | | Low | 25 | @@ -1626,7 +1628,24 @@ Potentially mistakened `=+` for `+=` or `=-` for `-=`. Please include a space in -## H-28: Weak Randomness +## H-28: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10` + +If you want to leverage signed arrays in storage by assigning a literal array with at least one negative number, then you mus use solidity version 0.5.10 or above. This is because of a bug in older compilers. + +
1 Found Instances + + +- Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 9](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L9) + + ```solidity + affectedArray = [-1, 5, 2]; + ``` + +
+ + + +## H-29: Weak Randomness The use of keccak256 hash functions on predictable values like block.timestamp, block.number, or similar data, including modulo operations on these values, should be avoided for generating randomness, as they are easily predictable and manipulable. The `PREVRANDAO` opcode also should not be used as a source of randomness. Instead, utilize Chainlink VRF for cryptographically secure and provably random values to ensure protocol integrity. @@ -1691,7 +1710,7 @@ The use of keccak256 hash functions on predictable values like block.timestamp, -## H-29: Usage of variable before declaration. +## H-30: Usage of variable before declaration. This is a bad practice that may lead to unintended consequences. Please declare the variable before using it. @@ -1708,7 +1727,7 @@ This is a bad practice that may lead to unintended consequences. Please declare -## H-30: Deletion from a nested mappping. +## H-31: Deletion from a nested mappping. A deletion in a structure containing a mapping will not delete the mapping. The remaining data may be used to compromise the contract. @@ -1957,9 +1976,15 @@ ERC20 functions may not behave as expected. For example: return values are not a Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;` -
17 Found Instances +
18 Found Instances +- Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 2](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L2) + + ```solidity + pragma solidity ^0.4.0; + ``` + - Found in src/ContractWithTodo.sol [Line: 2](../tests/contract-playground/src/ContractWithTodo.sol#L2) ```solidity @@ -2266,7 +2291,7 @@ Instead of marking a function as `public`, consider marking it as `external` if If the same constant literal value is used multiple times, create a constant state variable and reference it throughout the contract. -
34 Found Instances +
36 Found Instances - Found in src/Casting.sol [Line: 16](../tests/contract-playground/src/Casting.sol#L16) @@ -2293,6 +2318,18 @@ If the same constant literal value is used multiple times, create a constant sta int unspecificInt = -0x1234567890abcdef; ``` +- Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 9](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L9) + + ```solidity + affectedArray = [-1, 5, 2]; + ``` + +- Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 14](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L14) + + ```solidity + unaffectedArray[1] = 5; + ``` + - Found in src/ConstantsLiterals.sol [Line: 25](../tests/contract-playground/src/ConstantsLiterals.sol#L25) ```solidity diff --git a/reports/report.sarif b/reports/report.sarif index c9f3f841a..12a857671 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -2381,6 +2381,26 @@ }, "ruleId": "dangerous-unary-operator" }, + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/CompilerBugStorageSignedIntegerArray.sol" + }, + "region": { + "byteLength": 10, + "byteOffset": 230 + } + } + } + ], + "message": { + "text": "If you want to leverage signed arrays in storage by assigning a literal array with at least one negative number, then you mus use solidity version 0.5.10 or above. This is because of a bug in older compilers." + }, + "ruleId": "signed-storage-array" + }, { "level": "warning", "locations": [ @@ -2885,6 +2905,17 @@ { "level": "note", "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/CompilerBugStorageSignedIntegerArray.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -3462,6 +3493,28 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/CompilerBugStorageSignedIntegerArray.sol" + }, + "region": { + "byteLength": 1, + "byteOffset": 235 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/CompilerBugStorageSignedIntegerArray.sol" + }, + "region": { + "byteLength": 1, + "byteOffset": 352 + } + } + }, { "physicalLocation": { "artifactLocation": { diff --git a/tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol b/tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol new file mode 100644 index 000000000..27bbe2ab3 --- /dev/null +++ b/tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.4.0; + +contract CompilerBugStorageSignedIntegerArray { + int256[3] affectedArray; + int256[4] unaffectedArray; + + function assignBadValue() private { + affectedArray = [-1, 5, 2]; + } + + function assignGoodValue() private { + unaffectedArray[0] = -1; + unaffectedArray[1] = 5; + unaffectedArray[2] = 2; + } +} From ee2f4640be987b34baf1de14311121bf4cd625bd Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Tue, 30 Jul 2024 18:36:42 +0530 Subject: [PATCH 05/15] Detector: Strict Equality Check on Contracts' balances (#625) Co-authored-by: Alex Roan --- aderyn_core/src/detect/detector.rs | 5 + .../high/dangerous_strict_equality_balance.rs | 127 ++++++++++++++++++ aderyn_core/src/detect/high/mod.rs | 2 + .../adhoc-sol-files-highs-only-report.json | 1 + reports/report.json | 64 ++++++++- reports/report.md | 86 +++++++++--- reports/report.sarif | 86 ++++++++++++ .../src/DangerousStrictEquality1.sol | 8 ++ .../src/DangerousStrictEquality2.sol | 12 ++ 9 files changed, 373 insertions(+), 18 deletions(-) create mode 100644 aderyn_core/src/detect/high/dangerous_strict_equality_balance.rs create mode 100644 tests/contract-playground/src/DangerousStrictEquality1.sol create mode 100644 tests/contract-playground/src/DangerousStrictEquality2.sol diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index 048fdef7e..a8c366c9d 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -66,6 +66,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -134,6 +135,7 @@ pub(crate) enum IssueDetectorNamePool { RTLO, UncheckedReturn, DangerousUnaryOperator, + DangerousStrictEquailtyOnContractBalance, SignedStorageArray, RedundantStatements, PublicVariableReadInExternalContext, @@ -276,6 +278,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::DangerousStrictEquailtyOnContractBalance => { + Some(Box::::default()) + } IssueDetectorNamePool::SignedStorageArray => { Some(Box::::default()) } diff --git a/aderyn_core/src/detect/high/dangerous_strict_equality_balance.rs b/aderyn_core/src/detect/high/dangerous_strict_equality_balance.rs new file mode 100644 index 000000000..8e5098bf6 --- /dev/null +++ b/aderyn_core/src/detect/high/dangerous_strict_equality_balance.rs @@ -0,0 +1,127 @@ +use std::collections::BTreeMap; +use std::error::Error; + +use crate::ast::{Expression, NodeID}; + +use crate::capture; +use crate::detect::detector::IssueDetectorNamePool; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; +use eyre::Result; + +#[derive(Default)] +pub struct DangerousStrictEqualityOnBalanceDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for DangerousStrictEqualityOnBalanceDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + // When you have found an instance of the issue, + // use the following macro to add it to `found_instances`: + // + // capture!(self, context, item); + + for binary_operation in context + .binary_operations() + .into_iter() + .filter(|&op| op.operator == "==" || op.operator == "!=") + { + for expr in [ + binary_operation.left_expression.as_ref(), + binary_operation.right_expression.as_ref(), + ] { + if let Expression::MemberAccess(member_access) = expr { + if member_access.member_name == "balance" + && member_access + .expression + .as_ref() + .type_descriptions() + .is_some_and(|type_desc| { + type_desc.type_string.as_ref().is_some_and(|type_string| { + // For older solc versions when you say this.balance, "this" is of type contract XXX + type_string.starts_with("contract ") + // In newers solidity versions, you say adddress(this).balance or payable(address(this)).balance + || type_string == "address" + || type_string == "address payable" + }) + }) + { + capture!(self, context, binary_operation); + } + } + } + } + + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::High + } + + fn title(&self) -> String { + String::from("Dangerous strict equality checks on contract balances.") + } + + fn description(&self) -> String { + String::from("A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality.") + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + IssueDetectorNamePool::DangerousStrictEquailtyOnContractBalance.to_string() + } +} + +#[cfg(test)] +mod dangerous_strict_equality_balance_tests { + use crate::detect::{ + detector::IssueDetector, + high::dangerous_strict_equality_balance::DangerousStrictEqualityOnBalanceDetector, + }; + + #[test] + fn test_dangerous_strict_equality_balance1() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/DangerousStrictEquality1.sol", + ); + + let mut detector = DangerousStrictEqualityOnBalanceDetector::default(); + let found = detector.detect(&context).unwrap(); + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 1); + // assert the severity is high + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::High + ); + } + + #[test] + fn test_dangerous_strict_equality_balance2() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/DangerousStrictEquality2.sol", + ); + + let mut detector = DangerousStrictEqualityOnBalanceDetector::default(); + let found = detector.detect(&context).unwrap(); + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 2); + // assert the severity is high + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::High + ); + } +} diff --git a/aderyn_core/src/detect/high/mod.rs b/aderyn_core/src/detect/high/mod.rs index 2021881f2..abd5620de 100644 --- a/aderyn_core/src/detect/high/mod.rs +++ b/aderyn_core/src/detect/high/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod arbitrary_transfer_from; pub(crate) mod avoid_abi_encode_packed; pub(crate) mod block_timestamp_deadline; +pub(crate) mod dangerous_strict_equality_balance; pub(crate) mod dangerous_unary_operator; pub(crate) mod delegate_call_in_loop; pub(crate) mod delegate_call_no_address_check; @@ -33,6 +34,7 @@ pub(crate) mod yul_return; pub use arbitrary_transfer_from::ArbitraryTransferFromDetector; pub use avoid_abi_encode_packed::AvoidAbiEncodePackedDetector; pub use block_timestamp_deadline::BlockTimestampDeadlineDetector; +pub use dangerous_strict_equality_balance::DangerousStrictEqualityOnBalanceDetector; pub use dangerous_unary_operator::DangerousUnaryOperatorDetector; pub use delegate_call_in_loop::DelegateCallInLoopDetector; pub use delegate_call_no_address_check::DelegateCallOnUncheckedAddressDetector; diff --git a/reports/adhoc-sol-files-highs-only-report.json b/reports/adhoc-sol-files-highs-only-report.json index 2363e9be8..801747b5a 100644 --- a/reports/adhoc-sol-files-highs-only-report.json +++ b/reports/adhoc-sol-files-highs-only-report.json @@ -189,6 +189,7 @@ "rtlo", "unchecked-return", "dangerous-unary-operator", + "dangerous-strict-equailty-on-contract-balance", "signed-storage-array", "weak-randomness", "pre-declared-local-variable-usage", diff --git a/reports/report.json b/reports/report.json index 37c0f9e16..3380ece53 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 71, - "total_sloc": 1981 + "total_source_units": 73, + "total_sloc": 1996 }, "files_details": { "files_details": [ @@ -49,6 +49,14 @@ "file_path": "src/CrazyPragma.sol", "n_sloc": 4 }, + { + "file_path": "src/DangerousStrictEquality1.sol", + "n_sloc": 6 + }, + { + "file_path": "src/DangerousStrictEquality2.sol", + "n_sloc": 9 + }, { "file_path": "src/DangerousUnaryOperator.sol", "n_sloc": 13 @@ -292,7 +300,7 @@ ] }, "issue_count": { - "high": 31, + "high": 32, "low": 25 }, "high_issues": { @@ -1650,6 +1658,31 @@ } ] }, + { + "title": "Dangerous strict equality checks on contract balances.", + "description": "A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality.", + "detector_name": "dangerous-strict-equailty-on-contract-balance", + "instances": [ + { + "contract_path": "src/DangerousStrictEquality1.sol", + "line_no": 6, + "src": "177:25", + "src_char": "177:25" + }, + { + "contract_path": "src/DangerousStrictEquality2.sol", + "line_no": 6, + "src": "177:34", + "src_char": "177:34" + }, + { + "contract_path": "src/DangerousStrictEquality2.sol", + "line_no": 10, + "src": "305:43", + "src_char": "305:43" + } + ] + }, { "title": "Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`", "description": "If you want to leverage signed arrays in storage by assigning a literal array with at least one negative number, then you mus use solidity version 0.5.10 or above. This is because of a bug in older compilers.", @@ -1986,6 +2019,12 @@ "src": "32:32", "src_char": "32:32" }, + { + "contract_path": "src/DangerousStrictEquality1.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/DangerousUnaryOperator.sol", "line_no": 2, @@ -2343,6 +2382,18 @@ "src": "1275:66", "src_char": "1275:66" }, + { + "contract_path": "src/DangerousStrictEquality2.sol", + "line_no": 6, + "src": "202:9", + "src_char": "202:9" + }, + { + "contract_path": "src/DangerousStrictEquality2.sol", + "line_no": 10, + "src": "339:9", + "src_char": "339:9" + }, { "contract_path": "src/DelegateCallWithoutAddressCheck.sol", "line_no": 24, @@ -2714,6 +2765,12 @@ "src": "32:32", "src_char": "32:32" }, + { + "contract_path": "src/DangerousStrictEquality2.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/DelegateCallWithoutAddressCheck.sol", "line_no": 2, @@ -3662,6 +3719,7 @@ "rtlo", "unchecked-return", "dangerous-unary-operator", + "dangerous-strict-equailty-on-contract-balance", "signed-storage-array", "redundant-statements", "public-variable-read-in-external-context", diff --git a/reports/report.md b/reports/report.md index 345aa4778..d6af919d3 100644 --- a/reports/report.md +++ b/reports/report.md @@ -35,10 +35,11 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [H-25: RTLO character detected in file. \u{202e}](#h-25-rtlo-character-detected-in-file-u202e) - [H-26: Return value of the function call is not checked.](#h-26-return-value-of-the-function-call-is-not-checked) - [H-27: Dangerous unary operator found in assignment.](#h-27-dangerous-unary-operator-found-in-assignment) - - [H-28: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`](#h-28-compiler-bug-signed-array-in-storage-detected-for-compiler-version-0510) - - [H-29: Weak Randomness](#h-29-weak-randomness) - - [H-30: Usage of variable before declaration.](#h-30-usage-of-variable-before-declaration) - - [H-31: Deletion from a nested mappping.](#h-31-deletion-from-a-nested-mappping) + - [H-28: Dangerous strict equality checks on contract balances.](#h-28-dangerous-strict-equality-checks-on-contract-balances) + - [H-29: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`](#h-29-compiler-bug-signed-array-in-storage-detected-for-compiler-version-0510) + - [H-30: Weak Randomness](#h-30-weak-randomness) + - [H-31: Usage of variable before declaration.](#h-31-usage-of-variable-before-declaration) + - [H-32: Deletion from a nested mappping.](#h-32-deletion-from-a-nested-mappping) - [Low Issues](#low-issues) - [L-1: Centralization Risk for trusted owners](#l-1-centralization-risk-for-trusted-owners) - [L-2: Solmate's SafeTransferLib does not check for token contract's existence](#l-2-solmates-safetransferlib-does-not-check-for-token-contracts-existence) @@ -73,8 +74,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 71 | -| Total nSLOC | 1981 | +| .sol Files | 73 | +| Total nSLOC | 1996 | ## Files Details @@ -92,6 +93,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/ContractWithTodo.sol | 7 | | src/Counter.sol | 20 | | src/CrazyPragma.sol | 4 | +| src/DangerousStrictEquality1.sol | 6 | +| src/DangerousStrictEquality2.sol | 9 | | src/DangerousUnaryOperator.sol | 13 | | src/DelegateCallWithoutAddressCheck.sol | 31 | | src/DeletionNestedMappingStructureContract.sol | 11 | @@ -152,14 +155,14 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **1981** | +| **Total** | **1996** | ## Issue Summary | Category | No. of Issues | | --- | --- | -| High | 31 | +| High | 32 | | Low | 25 | @@ -1628,7 +1631,36 @@ Potentially mistakened `=+` for `+=` or `=-` for `-=`. Please include a space in -## H-28: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10` +## H-28: Dangerous strict equality checks on contract balances. + +A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality. + +
3 Found Instances + + +- Found in src/DangerousStrictEquality1.sol [Line: 6](../tests/contract-playground/src/DangerousStrictEquality1.sol#L6) + + ```solidity + return this.balance == 100 ether; + ``` + +- Found in src/DangerousStrictEquality2.sol [Line: 6](../tests/contract-playground/src/DangerousStrictEquality2.sol#L6) + + ```solidity + return address(this).balance == 100 ether; + ``` + +- Found in src/DangerousStrictEquality2.sol [Line: 10](../tests/contract-playground/src/DangerousStrictEquality2.sol#L10) + + ```solidity + return payable(address(this)).balance == 100 ether; + ``` + +
+ + + +## H-29: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10` If you want to leverage signed arrays in storage by assigning a literal array with at least one negative number, then you mus use solidity version 0.5.10 or above. This is because of a bug in older compilers. @@ -1645,7 +1677,7 @@ If you want to leverage signed arrays in storage by assigning a literal array wi -## H-29: Weak Randomness +## H-30: Weak Randomness The use of keccak256 hash functions on predictable values like block.timestamp, block.number, or similar data, including modulo operations on these values, should be avoided for generating randomness, as they are easily predictable and manipulable. The `PREVRANDAO` opcode also should not be used as a source of randomness. Instead, utilize Chainlink VRF for cryptographically secure and provably random values to ensure protocol integrity. @@ -1710,7 +1742,7 @@ The use of keccak256 hash functions on predictable values like block.timestamp, -## H-30: Usage of variable before declaration. +## H-31: Usage of variable before declaration. This is a bad practice that may lead to unintended consequences. Please declare the variable before using it. @@ -1727,7 +1759,7 @@ This is a bad practice that may lead to unintended consequences. Please declare -## H-31: Deletion from a nested mappping. +## H-32: Deletion from a nested mappping. A deletion in a structure containing a mapping will not delete the mapping. The remaining data may be used to compromise the contract. @@ -1976,7 +2008,7 @@ ERC20 functions may not behave as expected. For example: return values are not a Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;` -
18 Found Instances +
19 Found Instances - Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 2](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L2) @@ -2003,6 +2035,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid pragma solidity >=0.8.19 <0.9.1; ``` +- Found in src/DangerousStrictEquality1.sol [Line: 2](../tests/contract-playground/src/DangerousStrictEquality1.sol#L2) + + ```solidity + pragma solidity ^0.4.0; + ``` + - Found in src/DangerousUnaryOperator.sol [Line: 2](../tests/contract-playground/src/DangerousUnaryOperator.sol#L2) ```solidity @@ -2291,7 +2329,7 @@ Instead of marking a function as `public`, consider marking it as `external` if If the same constant literal value is used multiple times, create a constant state variable and reference it throughout the contract. -
36 Found Instances +
38 Found Instances - Found in src/Casting.sol [Line: 16](../tests/contract-playground/src/Casting.sol#L16) @@ -2372,6 +2410,18 @@ If the same constant literal value is used multiple times, create a constant sta bytes32 multipleUseOfBytes32 = 0x8a1b3dbe6301650442bfa765d4de23775fc9a4ec4329ebb5995ec7f1e3777dc4; ``` +- Found in src/DangerousStrictEquality2.sol [Line: 6](../tests/contract-playground/src/DangerousStrictEquality2.sol#L6) + + ```solidity + return address(this).balance == 100 ether; + ``` + +- Found in src/DangerousStrictEquality2.sol [Line: 10](../tests/contract-playground/src/DangerousStrictEquality2.sol#L10) + + ```solidity + return payable(address(this)).balance == 100 ether; + ``` + - Found in src/DelegateCallWithoutAddressCheck.sol [Line: 24](../tests/contract-playground/src/DelegateCallWithoutAddressCheck.sol#L24) ```solidity @@ -2724,7 +2774,7 @@ Using `ERC721::_mint()` can mint ERC721 tokens to addresses which don't support Solc compiler version 0.8.20 switches the default target EVM version to Shanghai, which means that the generated bytecode will include PUSH0 opcodes. Be sure to select the appropriate EVM version in case you intend to deploy on a chain other than mainnet like L2 chains that may not support PUSH0, otherwise deployment of your contracts will fail. -
27 Found Instances +
28 Found Instances - Found in src/AdminContract.sol [Line: 2](../tests/contract-playground/src/AdminContract.sol#L2) @@ -2751,6 +2801,12 @@ Solc compiler version 0.8.20 switches the default target EVM version to Shanghai pragma solidity >=0.8.19 <0.9.1; ``` +- Found in src/DangerousStrictEquality2.sol [Line: 2](../tests/contract-playground/src/DangerousStrictEquality2.sol#L2) + + ```solidity + pragma solidity 0.8.20; + ``` + - Found in src/DelegateCallWithoutAddressCheck.sol [Line: 2](../tests/contract-playground/src/DelegateCallWithoutAddressCheck.sol#L2) ```solidity diff --git a/reports/report.sarif b/reports/report.sarif index 12a857671..c60ae1436 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -2381,6 +2381,48 @@ }, "ruleId": "dangerous-unary-operator" }, + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/DangerousStrictEquality1.sol" + }, + "region": { + "byteLength": 25, + "byteOffset": 177 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/DangerousStrictEquality2.sol" + }, + "region": { + "byteLength": 34, + "byteOffset": 177 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/DangerousStrictEquality2.sol" + }, + "region": { + "byteLength": 43, + "byteOffset": 305 + } + } + } + ], + "message": { + "text": "A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality." + }, + "ruleId": "dangerous-strict-equailty-on-contract-balance" + }, { "level": "warning", "locations": [ @@ -2949,6 +2991,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/DangerousStrictEquality1.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -3592,6 +3645,28 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/DangerousStrictEquality2.sol" + }, + "region": { + "byteLength": 9, + "byteOffset": 202 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/DangerousStrictEquality2.sol" + }, + "region": { + "byteLength": 9, + "byteOffset": 339 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -4253,6 +4328,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/DangerousStrictEquality2.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { diff --git a/tests/contract-playground/src/DangerousStrictEquality1.sol b/tests/contract-playground/src/DangerousStrictEquality1.sol new file mode 100644 index 000000000..e41b87c3e --- /dev/null +++ b/tests/contract-playground/src/DangerousStrictEquality1.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.4.0; + +contract DangerousStrictEquality1 { + function makeStrictBalanceCheck() external view returns (bool) { + return this.balance == 100 ether; + } +} diff --git a/tests/contract-playground/src/DangerousStrictEquality2.sol b/tests/contract-playground/src/DangerousStrictEquality2.sol new file mode 100644 index 000000000..908e09def --- /dev/null +++ b/tests/contract-playground/src/DangerousStrictEquality2.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +contract DangerousStrictEquality2 { + function makeStrictBalanceCheck() external view returns (bool) { + return address(this).balance == 100 ether; + } + + function makeStrictBalanceCheck2() external view returns (bool) { + return payable(address(this)).balance == 100 ether; + } +} From b0c2ed7820e3eeeefd16043d4a200b4ed3b9c684 Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Fri, 2 Aug 2024 14:37:28 +0530 Subject: [PATCH 06/15] Detector: Tautology or contradiction (#623) Co-authored-by: Alex Roan --- Cargo.lock | 452 +++++++------ aderyn_core/Cargo.toml | 8 +- aderyn_core/src/detect/detector.rs | 5 + aderyn_core/src/detect/high/mod.rs | 2 + .../detect/high/tautology_or_contradiction.rs | 611 ++++++++++++++++++ .../adhoc-sol-files-highs-only-report.json | 1 + reports/report.json | 60 +- reports/report.md | 91 ++- reports/report.sarif | 86 +++ .../src/TautologyOrContradiction.sol | 18 + 10 files changed, 1099 insertions(+), 235 deletions(-) create mode 100644 aderyn_core/src/detect/high/tautology_or_contradiction.rs create mode 100644 tests/contract-playground/src/TautologyOrContradiction.sol diff --git a/Cargo.lock b/Cargo.lock index d7ac1aa65..a8d018b2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "strum 0.26.2", + "strum 0.26.3", ] [[package]] @@ -46,6 +46,8 @@ dependencies = [ "eyre", "ignore", "lazy-regex", + "num-bigint", + "num-traits", "once_cell", "phf", "prettytable", @@ -56,7 +58,7 @@ dependencies = [ "serde_json", "serde_repr", "serial_test", - "strum 0.26.2", + "strum 0.26.3", "toml", ] @@ -98,7 +100,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -118,20 +120,20 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e9a1892803b02f53e25bea3e414ddd0501f12d97456c9d5ade4edf88f9516f" +checksum = "47ff94ce0f141c2671c23d02c7b88990dd432856639595c5d010663d017c2c58" dependencies = [ "num_enum", "serde", - "strum 0.26.2", + "strum 0.26.3", ] [[package]] name = "alloy-json-abi" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaeaccd50238126e3a0ff9387c7c568837726ad4f4e399b528ca88104d6c25ef" +checksum = "bc05b04ac331a9f07e3a4036ef7926e49a8bf84a99a1ccfc7e2ab55a5fcbb372" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -141,9 +143,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" +checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" dependencies = [ "alloy-rlp", "bytes", @@ -164,9 +166,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b155716bab55763c95ba212806cf43d05bcc70e5f35b02bad20cf5ec7fe11fed" +checksum = "a43b18702501396fa9bcdeecd533bc85fac75150d308fc0f6800a01e6234a003" dependencies = [ "arrayvec", "bytes", @@ -174,11 +176,12 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baa2fbd22d353d8685bd9fee11ba2d8b5c3b1d11e56adb3265fcf1f32bfdf404" +checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" dependencies = [ - "winnow 0.6.13", + "serde", + "winnow 0.6.18", ] [[package]] @@ -189,9 +192,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -204,33 +207,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -407,7 +410,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -472,9 +475,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -499,9 +502,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", @@ -527,9 +530,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.16.1" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" [[package]] name = "byteorder" @@ -539,9 +542,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" dependencies = [ "serde", ] @@ -554,9 +557,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.100" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c891175c3fb232128f48de6590095e59198bbeb8620c310be349bfc3afd12c7b" +checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" [[package]] name = "cfg-if" @@ -593,9 +596,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -603,9 +606,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", @@ -615,27 +618,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "const-hex" @@ -878,7 +881,7 @@ dependencies = [ "solang-parser", "thiserror", "toml", - "toml_edit 0.22.14", + "toml_edit 0.22.20", "tracing", "walkdir", ] @@ -947,7 +950,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -991,7 +994,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -1065,7 +1068,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -1096,9 +1099,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -1136,13 +1139,13 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "enumn" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -1323,7 +1326,7 @@ dependencies = [ "svm-rs-builds", "thiserror", "tracing", - "winnow 0.6.13", + "winnow 0.6.18", "yansi", ] @@ -1637,9 +1640,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -1666,9 +1669,9 @@ checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -1704,9 +1707,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" dependencies = [ "bytes", "futures-channel", @@ -1782,9 +1785,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown", @@ -1841,9 +1844,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" @@ -1989,7 +1992,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -2016,7 +2019,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", ] @@ -2044,9 +2047,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "md-5" @@ -2109,6 +2112,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -2121,7 +2136,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "crossbeam-channel", "filetime", "fsevent-sys", @@ -2129,7 +2144,7 @@ dependencies = [ "kqueue", "libc", "log", - "mio", + "mio 0.8.11", "walkdir", "windows-sys 0.48.0", ] @@ -2150,9 +2165,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -2177,34 +2192,24 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -2215,9 +2220,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.36.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] @@ -2230,9 +2235,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "openssl-probe" @@ -2290,9 +2295,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.3", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -2327,7 +2332,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -2338,9 +2343,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.10" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", "thiserror", @@ -2387,7 +2392,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -2425,7 +2430,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -2480,9 +2485,12 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f" +dependencies = [ + "zerocopy 0.6.6", +] [[package]] name = "precomputed-hash" @@ -2497,7 +2505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -2551,7 +2559,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", "version_check", "yansi", ] @@ -2564,7 +2572,7 @@ checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.5.0", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand", @@ -2678,14 +2686,13 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" dependencies = [ "libc", "once_cell", "socket2", - "tracing", "windows-sys 0.52.0", ] @@ -2774,11 +2781,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -2873,7 +2880,7 @@ checksum = "902184a7a781550858d4b96707098da357429f1e4545806fd5b589f455555cf2" dependencies = [ "alloy-primitives", "auto_impl", - "bitflags 2.5.0", + "bitflags 2.6.0", "bitvec", "cfg-if", "dyn-clone", @@ -2989,7 +2996,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2998,9 +3005,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "once_cell", "ring", @@ -3012,9 +3019,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -3041,9 +3048,9 @@ checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", "rustls-pki-types", @@ -3085,9 +3092,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.1.1" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ad2bbb0ae5100a07b7a6f2ed7ab5fd0045551a4c507989b7a620046ea3efdc" +checksum = "05ccfb12511cdb770157ace92d7dda771e498445b78f9886e8cdbc5140a4eced" dependencies = [ "sdd", ] @@ -3136,9 +3143,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdd" -version = "0.2.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" +checksum = "177258b64c0faaa9ffd3c65cd3262c2bc7e2588dbbd9c1641d0346145c1bbda8" [[package]] name = "sec1" @@ -3156,11 +3163,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -3169,9 +3176,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -3206,9 +3213,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -3229,29 +3236,30 @@ dependencies = [ "serde_json", "strum 0.25.0", "strum_macros 0.24.3", - "syn 2.0.67", + "syn 2.0.72", "thiserror", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "indexmap", "itoa", + "memchr", "ryu", "serde", ] @@ -3274,14 +3282,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -3320,7 +3328,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -3460,9 +3468,9 @@ checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" [[package]] name = "strum" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros 0.26.4", ] @@ -3490,14 +3498,14 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "subtle" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svm-rs" @@ -3545,9 +3553,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.67" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -3568,9 +3576,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.14" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" @@ -3597,22 +3605,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -3636,9 +3644,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -3651,18 +3659,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.1", "pin-project-lite", "socket2", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3678,22 +3685,22 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.14", + "toml_edit 0.22.20", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -3711,15 +3718,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.18", ] [[package]] @@ -3768,7 +3775,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -3905,9 +3912,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" @@ -3964,7 +3971,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -3998,7 +4005,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4074,7 +4081,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -4094,18 +4101,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -4116,9 +4123,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -4128,9 +4135,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -4140,15 +4147,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -4158,9 +4165,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -4170,9 +4177,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -4182,9 +4189,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -4194,9 +4201,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -4209,9 +4216,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -4243,22 +4250,43 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" dependencies = [ - "zerocopy-derive", + "byteorder", + "zerocopy-derive 0.6.6", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy-derive" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] @@ -4278,14 +4306,14 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.72", ] [[package]] name = "zip" -version = "2.1.3" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" +checksum = "40dd8c92efc296286ce1fbd16657c5dbefff44f1b4ca01cc5f517d8b7b3d3e2e" dependencies = [ "arbitrary", "crc32fast", diff --git a/aderyn_core/Cargo.toml b/aderyn_core/Cargo.toml index 008e4c1d5..59f8b6274 100644 --- a/aderyn_core/Cargo.toml +++ b/aderyn_core/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" crossbeam-channel = "0.5.9" eyre = "0.6.12" ignore = "0.4.21" -phf = {version = "0.11.2", features = ["macros"]} +phf = { version = "0.11.2", features = ["macros"] } prettytable = "0.10.0" rayon = "1.8.0" semver = "1.0.20" @@ -22,7 +22,11 @@ serde-sarif = "0.4.2" serde_repr = "0.1.12" strum = { version = "0.26", features = ["derive"] } toml = "0.8.2" -cyfrin-foundry-compilers = { version = "0.3.20-aderyn", features = ["svm-solc"] } +cyfrin-foundry-compilers = { version = "0.3.20-aderyn", features = [ + "svm-solc", +] } +num-bigint = "0.4" +num-traits = "0.2" lazy-regex = "3.2.0" derive_more = "0.99.18" diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index a8c366c9d..d45c5d23e 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -66,6 +66,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -135,6 +136,7 @@ pub(crate) enum IssueDetectorNamePool { RTLO, UncheckedReturn, DangerousUnaryOperator, + TautologyOrContradiction, DangerousStrictEquailtyOnContractBalance, SignedStorageArray, RedundantStatements, @@ -278,6 +280,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::TautologyOrContradiction => { + Some(Box::::default()) + } IssueDetectorNamePool::DangerousStrictEquailtyOnContractBalance => { Some(Box::::default()) } diff --git a/aderyn_core/src/detect/high/mod.rs b/aderyn_core/src/detect/high/mod.rs index abd5620de..66c8902cc 100644 --- a/aderyn_core/src/detect/high/mod.rs +++ b/aderyn_core/src/detect/high/mod.rs @@ -23,6 +23,7 @@ pub(crate) mod state_variable_shadowing; pub(crate) mod storage_array_edit_with_memory; pub(crate) mod storage_signed_integer_array; pub(crate) mod tautological_compare; +pub(crate) mod tautology_or_contradiction; pub(crate) mod unchecked_return; pub(crate) mod unchecked_send; pub(crate) mod uninitialized_state_variable; @@ -56,6 +57,7 @@ pub use state_variable_shadowing::StateVariableShadowingDetector; pub use storage_array_edit_with_memory::StorageArrayEditWithMemoryDetector; pub use storage_signed_integer_array::StorageSignedIntegerArrayDetector; pub use tautological_compare::TautologicalCompareDetector; +pub use tautology_or_contradiction::TautologyOrContraditionDetector; pub use unchecked_return::UncheckedReturnDetector; pub use unchecked_send::UncheckedSendDetector; pub use uninitialized_state_variable::UninitializedStateVariableDetector; diff --git a/aderyn_core/src/detect/high/tautology_or_contradiction.rs b/aderyn_core/src/detect/high/tautology_or_contradiction.rs new file mode 100644 index 000000000..953c14c34 --- /dev/null +++ b/aderyn_core/src/detect/high/tautology_or_contradiction.rs @@ -0,0 +1,611 @@ +use std::collections::BTreeMap; +use std::error::Error; + +use crate::ast::{BinaryOperation, NodeID, TypeDescriptions}; + +use crate::capture; +use crate::detect::detector::IssueDetectorNamePool; +use crate::detect::helpers::get_literal_value_or_constant_variable_value; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; +use eyre::Result; +use solidity_integer_helper::{ + does_operation_make_sense_with_lhs_value, does_operation_make_sense_with_rhs_value, +}; + +#[derive(Default)] +pub struct TautologyOrContraditionDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for TautologyOrContraditionDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + for binary_operation in context.binary_operations() { + if let Some(is_tautlogy_or_contradiction) = + binary_operation.is_tautology_or_contradiction(context) + { + if is_tautlogy_or_contradiction { + capture!(self, context, binary_operation); + } + } + } + + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::High + } + + fn title(&self) -> String { + String::from("Tautology or Contradiction in comparison.") + } + + fn description(&self) -> String { + String::from("The condition has been determined to be either always true or always false due to the integer range in which we're operating.") + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + IssueDetectorNamePool::TautologyOrContradiction.to_string() + } +} + +#[cfg(test)] +mod tautology_or_contradiction_tests { + use serial_test::serial; + + use crate::detect::{ + detector::IssueDetector, high::tautology_or_contradiction::TautologyOrContraditionDetector, + }; + + #[test] + #[serial] + fn test_tautology_or_contradiction_detector() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/TautologyOrContradiction.sol", + ); + + let mut detector = TautologyOrContraditionDetector::default(); + let found = detector.detect(&context).unwrap(); + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 2); + // assert the severity is high + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::High + ); + // assert the title is correct + assert_eq!( + detector.title(), + String::from("Tautology or Contradiction in comparison.") + ); + // assert the description is correct + assert_eq!( + detector.description(), + String::from("The condition has been determined to be either always true or always false due to the integer range in which we're operating.") + ); + } +} + +pub trait OperationIsTautologyOrContradiction { + fn is_tautology_or_contradiction(&self, context: &WorkspaceContext) -> Option; +} + +impl OperationIsTautologyOrContradiction for BinaryOperation { + fn is_tautology_or_contradiction(&self, context: &WorkspaceContext) -> Option { + if let ( + Some(TypeDescriptions { + type_string: Some(lhs_type_string), + .. + }), + Some(TypeDescriptions { + type_string: Some(rhs_type_string), + .. + }), + operator, + ) = ( + self.left_expression.as_ref().type_descriptions(), + self.right_expression.as_ref().type_descriptions(), + self.operator.clone(), + ) { + let supported_operators = [">", ">=", "<", "<="]; + if supported_operators.into_iter().all(|op| op != operator) { + return None; + } + + if let Some(lhs_value) = get_literal_value_or_constant_variable_value( + self.left_expression.get_node_id()?, + context, + ) { + if let Some(makes_sense) = + does_operation_make_sense_with_lhs_value(&lhs_value, &operator, rhs_type_string) + { + if !makes_sense { + return Some(true); + } + } + } + + if let Some(rhs_value) = get_literal_value_or_constant_variable_value( + self.right_expression.get_node_id()?, + context, + ) { + if let Some(makes_sense) = + does_operation_make_sense_with_rhs_value(lhs_type_string, &operator, &rhs_value) + { + if !makes_sense { + return Some(true); + } + } + } + } + + None + } +} + +pub mod solidity_integer_helper { + use num_bigint::BigInt; + use num_traits::One; + use std::{error::Error, ops::Neg}; + + /// This data type is big enough to handle the extreme values of uint256 and int256 + /// (Tests below) + #[derive(PartialEq, Debug, Clone)] + pub struct SolidityNumberRange { + min_val: BigInt, + max_val: BigInt, + } + + impl SolidityNumberRange { + fn fully_contains(&self, other_solidity_number_range: &SolidityNumberRange) -> bool { + (self.min_val <= other_solidity_number_range.min_val) + && (self.max_val >= other_solidity_number_range.max_val) + } + + fn fully_excludes(&self, other_solidity_number_range: &SolidityNumberRange) -> bool { + (other_solidity_number_range.max_val < self.min_val) + || (other_solidity_number_range.min_val > self.max_val) + } + } + + /// Does this make sense? (boolean answer) + /// ```no_code + /// if(?uintX, operator, value) + /// ``` + /// + /// Example (ways to call this function) + /// + /// Say x is uint8: + /// Then, when we come across a binary operation like follows: + /// x >= 300 + /// we can determine if it makes sense by calling this function + /// does_operation_make_sense_with_rhs_value("uint8", ">=", "300") + /// + /// This function checks for the range of integer values of uint8 and returns true if it is neither a tautology + /// nor a contradiction. + /// + /// Here, I define tautology as the condition where the range Ex: (>=300) FULLY COVERS thr Range of Uint8 + /// Contradiction: When the range Ex:(>=300) fully excludes the Range of Uint8 + /// + /// Notice how in the above example, the value is on the right hand side. + /// Hence this function is called "does_...rhs_value". + /// + /// + pub fn does_operation_make_sense_with_rhs_value( + type_string: &str, + operator: &str, + value: &str, + ) -> Option { + let allowed_range = get_range_for_type_string(type_string).ok()?; + let allowed_min_val = allowed_range.min_val.clone(); + let allowed_max_val = allowed_range.max_val.clone(); + + let value_as_big_int = BigInt::parse_bytes(value.as_bytes(), 10)?; + + // First and foremost if the value is out of range it's 100% either a tautology or a contradiction. + // Hence, return false. + if value_as_big_int < allowed_min_val || value_as_big_int > allowed_max_val { + return Some(false); + } + // At this point, we know that the value we are comparing to, is in the allowed range. + // Now, we can get the represented range, and see if it fully contains the allowed range + // (tatutology) or fully excludes the allowed range (contradiction) + let represented_range = { + match operator { + ">=" => Some(SolidityNumberRange { + min_val: value_as_big_int.clone(), + max_val: allowed_max_val.clone(), + }), + ">" => Some(SolidityNumberRange { + min_val: value_as_big_int.clone() + BigInt::one(), + max_val: allowed_max_val.clone(), + }), + "<=" => Some(SolidityNumberRange { + min_val: allowed_min_val.clone(), + max_val: value_as_big_int.clone(), + }), + "<" => Some(SolidityNumberRange { + min_val: allowed_min_val.clone(), + max_val: value_as_big_int.clone() - BigInt::one(), + }), + &_ => None, + } + }; + + if let Some(represented_range) = represented_range { + return Some( + !(represented_range.fully_contains(&allowed_range) + || represented_range.fully_excludes(&allowed_range)), + ); + } + + None + } + + /// Does this make sense? (boolean answer) + /// ```no_code + /// if(value, operator, uint8?) + /// ``` + /// + /// Take advantage of the above method by reusing code + /// + /// Example (ways to call this function) + /// + /// Say x is uint8: + /// Then, when we come across a binary operation like follows: + /// 300 >= x + /// we can determine if it makes sense by calling this function + /// does_operation_make_sense_with_lhs_value("300", ">=", "uint8") + /// + /// Notice, here the value 300 is on the left hand side. + /// + pub fn does_operation_make_sense_with_lhs_value( + value: &str, + operator: &str, + type_string: &str, + ) -> Option { + let inverse_operator = { + match operator { + ">=" => Some("<="), + "<=" => Some(">="), + ">" => Some("<"), + "<" => Some(">"), + _ => None, + } + }?; + does_operation_make_sense_with_rhs_value(type_string, inverse_operator, value) + } + + /// Accept the type string to calculate the range. + pub fn get_range_for_type_string( + type_string: &str, + ) -> Result> { + if type_string.starts_with("uint") { + if let Some((_, num_of_bits)) = &type_string.split_once("uint") { + let num_of_bits = num_of_bits.parse::()?; + return Ok(SolidityNumberRange { + min_val: find_uint_min(num_of_bits), + max_val: find_uint_max(num_of_bits), + }); + } + } else if type_string.starts_with("int") { + if let Some((_, num_of_bits)) = &type_string.split_once("int") { + let num_of_bits = num_of_bits.parse::()?; + return Ok(SolidityNumberRange { + min_val: find_int_min(num_of_bits), + max_val: find_int_max(num_of_bits), + }); + } + } + Err("Invalid type string provided!".into()) + } + + // Helpers to calculate min and max for uint types like uint8, uint16, uint24, and so on . . . + + fn find_uint_max(num_of_bits: u32) -> BigInt { + BigInt::parse_bytes(b"2", 10).unwrap().pow(num_of_bits) - BigInt::one() + } + + fn find_uint_min(_: u32) -> BigInt { + BigInt::ZERO + } + + // Helpers to calculate min and max for int types like int8, int16, int24, and so on . . . + + fn find_int_max(num_of_bits: u32) -> BigInt { + BigInt::parse_bytes(b"2", 10).unwrap().pow(num_of_bits - 1) - BigInt::one() + } + + fn find_int_min(num_of_bits: u32) -> BigInt { + BigInt::parse_bytes(b"2", 10) + .unwrap() + .pow(num_of_bits - 1) + .neg() + } + + #[cfg(test)] + mod test_num_bigint_primitives { + + use std::ops::Neg; + + use num_bigint::BigInt; + use num_traits::{FromPrimitive, One}; + + use crate::detect::high::tautology_or_contradiction::solidity_integer_helper::{ + does_operation_make_sense_with_rhs_value, find_int_max, find_int_min, find_uint_max, + }; + + use super::{ + does_operation_make_sense_with_lhs_value, get_range_for_type_string, + SolidityNumberRange, + }; + + /* + Tests to ensure that num_bigint crate holds the capacity to work with numbers + in the range that Solidity language operates in. + */ + + #[test] + fn test_2_raised_to_3() { + let two_raised_to_three = BigInt::parse_bytes(b"2", 10).unwrap().pow(3); + assert_eq!(two_raised_to_three, BigInt::from_u8(8).unwrap()); + } + + #[test] + fn can_find_max_of_uint256() { + // This test shows that we can calculate the biggest possible number in Solidity for uint + // which is 2^256 - 1. + // hence we conclude that because we can represent 2^256 - 1, we can easily cover all + // the smaller variants of uint that is uint8, uint16, .... all the ay upto uint256 because they + // are lesser than 2^256 - 1 + let uint256_max = BigInt::parse_bytes(b"2", 10).unwrap().pow(256) - BigInt::one(); + assert_eq!( + uint256_max, + BigInt::parse_bytes( + b"115792089237316195423570985008687907853269984665640564039457584007913129639935", + 10 + ) + .unwrap() + ); + } + + #[test] + fn can_find_min_of_int256() { + let int_256_min = BigInt::parse_bytes(b"2", 10).unwrap().pow(255).neg(); + assert_eq!( + int_256_min, + BigInt::parse_bytes( + b"-57896044618658097711785492504343953926634992332820282019728792003956564819968", + 10 + ) + .unwrap() + ); + } + + #[test] + fn can_find_max_of_int256() { + let int_256_max = BigInt::parse_bytes(b"2", 10).unwrap().pow(255) - BigInt::one(); + assert_eq!( + int_256_max, + BigInt::parse_bytes( + b"57896044618658097711785492504343953926634992332820282019728792003956564819967", + 10 + ) + .unwrap() + ); + } + + /* + Tests that our helper methods work which will ensure that min and max value can be calculated + for every bit range from 0 to 256 . + */ + + #[test] + fn helper_method_can_find_max_of_uint256() { + let uint256_max = find_uint_max(256); + assert_eq!( + uint256_max, + BigInt::parse_bytes( + b"115792089237316195423570985008687907853269984665640564039457584007913129639935", + 10 + ) + .unwrap() + ); + } + + #[test] + fn helper_method_can_find_min_of_int256() { + let int_256_min = find_int_min(256); + assert_eq!( + int_256_min, + BigInt::parse_bytes( + b"-57896044618658097711785492504343953926634992332820282019728792003956564819968", + 10 + ) + .unwrap() + ); + } + + #[test] + fn helper_method_can_find_max_of_int256() { + let int_256_max = find_int_max(256); + assert_eq!( + int_256_max, + BigInt::parse_bytes( + b"57896044618658097711785492504343953926634992332820282019728792003956564819967", + 10 + ) + .unwrap() + ); + } + + #[test] + fn helper_method_can_find_range_for_int176() { + let actual_range = get_range_for_type_string("int176").unwrap(); + let expected_range = SolidityNumberRange { + min_val: BigInt::parse_bytes( + b"-47890485652059026823698344598447161988085597568237568", + 10, + ) + .unwrap(), + max_val: BigInt::parse_bytes( + b"47890485652059026823698344598447161988085597568237567", + 10, + ) + .unwrap(), + }; + assert_eq!(actual_range, expected_range); + } + + #[test] + fn helper_method_can_find_range_for_int248() { + let actual_range = get_range_for_type_string("int248").unwrap(); + let expected_range = SolidityNumberRange { + min_val: BigInt::parse_bytes( + b"-226156424291633194186662080095093570025917938800079226639565593765455331328", + 10, + ) + .unwrap(), + max_val: BigInt::parse_bytes( + b"226156424291633194186662080095093570025917938800079226639565593765455331327", + 10, + ) + .unwrap(), + }; + assert_eq!(actual_range, expected_range); + } + + #[test] + fn helper_method_can_find_range_for_int24() { + let actual_range = get_range_for_type_string("int24").unwrap(); + let expected_range = SolidityNumberRange { + min_val: BigInt::parse_bytes(b"-8388608", 10).unwrap(), + max_val: BigInt::parse_bytes(b"8388607", 10).unwrap(), + }; + assert_eq!(actual_range, expected_range); + } + + #[test] + fn helper_method_can_find_range_for_uint144() { + let actual_range = get_range_for_type_string("uint144").unwrap(); + let expected_range = SolidityNumberRange { + min_val: BigInt::ZERO, + max_val: BigInt::parse_bytes(b"22300745198530623141535718272648361505980415", 10) + .unwrap(), + }; + assert_eq!(actual_range, expected_range); + } + + #[test] + fn helper_method_can_find_range_for_uint232() { + let actual_range = get_range_for_type_string("uint232").unwrap(); + let expected_range = SolidityNumberRange { + min_val: BigInt::ZERO, + max_val: BigInt::parse_bytes( + b"6901746346790563787434755862277025452451108972170386555162524223799295", + 10, + ) + .unwrap(), + }; + assert_eq!(actual_range, expected_range); + } + + #[test] + fn helper_method_can_find_range_for_uint256() { + let actual_range = get_range_for_type_string("uint256").unwrap(); + let expected_range = SolidityNumberRange { + min_val: BigInt::ZERO, + max_val: BigInt::parse_bytes( + b"115792089237316195423570985008687907853269984665640564039457584007913129639935", + 10, + ) + .unwrap(), + }; + assert_eq!(actual_range, expected_range); + } + + #[test] + fn helper_method_can_find_range_for_uint8() { + let actual_range = get_range_for_type_string("uint8").unwrap(); + let expected_range = SolidityNumberRange { + min_val: BigInt::ZERO, + max_val: BigInt::parse_bytes(b"255", 10).unwrap(), + }; + assert_eq!(actual_range, expected_range); + } + + #[test] + fn does_operation_make_sense_lhs_uint8_part1() { + let does_not_make_sense = + !does_operation_make_sense_with_lhs_value("256", ">=", "uint8").unwrap(); + assert!(does_not_make_sense); + } + + #[test] + fn does_operation_make_sense_rhs_uint8_part1() { + let does_make_sense = + does_operation_make_sense_with_rhs_value("uint8", "<", "255").unwrap(); + assert!(does_make_sense); + } + + #[test] + fn does_operation_make_sense_lhs_uint8_part2() { + let does_not_make_sense = + !does_operation_make_sense_with_lhs_value("255", ">=", "uint8").unwrap(); + assert!(does_not_make_sense); + } + + #[test] + fn does_operation_make_sense_lhs_uint8_part3() { + let does_make_sense = + does_operation_make_sense_with_lhs_value("245", ">=", "uint8").unwrap(); + assert!(does_make_sense); + } + + #[test] + fn does_operation_make_sense_rhs_uint8_part2() { + let does_make_sense = + does_operation_make_sense_with_rhs_value("uint8", "<", "89").unwrap(); + assert!(does_make_sense); + } + + #[test] + fn does_operation_make_sense_rhs_uint256_part3() { + let does_make_sense = + does_operation_make_sense_with_rhs_value("uint256", ">", "0").unwrap(); + assert!(does_make_sense); + } + + #[test] + fn does_operation_make_sense_rhs_uint256_part4() { + let does_not_make_sense = + !does_operation_make_sense_with_rhs_value("uint256", ">=", "0").unwrap(); + assert!(does_not_make_sense); + } + + #[test] + fn does_operation_make_sense_rhs_uint72_part5() { + let does_not_make_sense = + !does_operation_make_sense_with_rhs_value("uint72", "<", "0").unwrap(); + assert!(does_not_make_sense); + } + + #[test] + fn does_operation_make_sense_rhs_uint8_part6() { + let does_not_make_sense = + !does_operation_make_sense_with_rhs_value("uint8", ">", "258").unwrap(); + assert!(does_not_make_sense); + } + } +} diff --git a/reports/adhoc-sol-files-highs-only-report.json b/reports/adhoc-sol-files-highs-only-report.json index 801747b5a..32906d660 100644 --- a/reports/adhoc-sol-files-highs-only-report.json +++ b/reports/adhoc-sol-files-highs-only-report.json @@ -189,6 +189,7 @@ "rtlo", "unchecked-return", "dangerous-unary-operator", + "tautology-or-contradiction", "dangerous-strict-equailty-on-contract-balance", "signed-storage-array", "weak-randomness", diff --git a/reports/report.json b/reports/report.json index 3380ece53..08bf01e31 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 73, - "total_sloc": 1996 + "total_source_units": 74, + "total_sloc": 2007 }, "files_details": { "files_details": [ @@ -177,6 +177,10 @@ "file_path": "src/TautologicalCompare.sol", "n_sloc": 17 }, + { + "file_path": "src/TautologyOrContradiction.sol", + "n_sloc": 11 + }, { "file_path": "src/TestERC20.sol", "n_sloc": 62 @@ -300,7 +304,7 @@ ] }, "issue_count": { - "high": 32, + "high": 33, "low": 25 }, "high_issues": { @@ -1321,6 +1325,18 @@ "src": "282:18", "src_char": "282:18" }, + { + "contract_path": "src/TautologyOrContradiction.sol", + "line_no": 6, + "src": "133:6", + "src_char": "133:6" + }, + { + "contract_path": "src/TautologyOrContradiction.sol", + "line_no": 7, + "src": "145:9", + "src_char": "145:9" + }, { "contract_path": "src/UninitializedStateVariable.sol", "line_no": 7, @@ -1658,6 +1674,25 @@ } ] }, + { + "title": "Tautology or Contradiction in comparison.", + "description": "The condition has been determined to be either always true or always false due to the integer range in which we're operating.", + "detector_name": "tautology-or-contradiction", + "instances": [ + { + "contract_path": "src/TautologyOrContradiction.sol", + "line_no": 13, + "src": "296:7", + "src_char": "296:7" + }, + { + "contract_path": "src/TautologyOrContradiction.sol", + "line_no": 16, + "src": "369:11", + "src_char": "369:11" + } + ] + }, { "title": "Dangerous strict equality checks on contract balances.", "description": "A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality.", @@ -2061,6 +2096,12 @@ "src": "32:23", "src_char": "32:23" }, + { + "contract_path": "src/TautologyOrContradiction.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/UncheckedSend.sol", "line_no": 2, @@ -3091,6 +3132,12 @@ "src": "1795:5", "src_char": "1795:5" }, + { + "contract_path": "src/TautologyOrContradiction.sol", + "line_no": 9, + "src": "161:229", + "src_char": "161:229" + }, { "contract_path": "src/UncheckedSend.sol", "line_no": 27, @@ -3455,6 +3502,12 @@ "src": "186:1", "src_char": "186:1" }, + { + "contract_path": "src/TautologyOrContradiction.sol", + "line_no": 6, + "src": "133:6", + "src_char": "133:6" + }, { "contract_path": "src/eth2/DepositContract.sol", "line_no": 59, @@ -3719,6 +3772,7 @@ "rtlo", "unchecked-return", "dangerous-unary-operator", + "tautology-or-contradiction", "dangerous-strict-equailty-on-contract-balance", "signed-storage-array", "redundant-statements", diff --git a/reports/report.md b/reports/report.md index d6af919d3..c1cfba5ec 100644 --- a/reports/report.md +++ b/reports/report.md @@ -35,11 +35,12 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [H-25: RTLO character detected in file. \u{202e}](#h-25-rtlo-character-detected-in-file-u202e) - [H-26: Return value of the function call is not checked.](#h-26-return-value-of-the-function-call-is-not-checked) - [H-27: Dangerous unary operator found in assignment.](#h-27-dangerous-unary-operator-found-in-assignment) - - [H-28: Dangerous strict equality checks on contract balances.](#h-28-dangerous-strict-equality-checks-on-contract-balances) - - [H-29: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`](#h-29-compiler-bug-signed-array-in-storage-detected-for-compiler-version-0510) - - [H-30: Weak Randomness](#h-30-weak-randomness) - - [H-31: Usage of variable before declaration.](#h-31-usage-of-variable-before-declaration) - - [H-32: Deletion from a nested mappping.](#h-32-deletion-from-a-nested-mappping) + - [H-28: Tautology or Contradiction in comparison.](#h-28-tautology-or-contradiction-in-comparison) + - [H-29: Dangerous strict equality checks on contract balances.](#h-29-dangerous-strict-equality-checks-on-contract-balances) + - [H-30: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`](#h-30-compiler-bug-signed-array-in-storage-detected-for-compiler-version-0510) + - [H-31: Weak Randomness](#h-31-weak-randomness) + - [H-32: Usage of variable before declaration.](#h-32-usage-of-variable-before-declaration) + - [H-33: Deletion from a nested mappping.](#h-33-deletion-from-a-nested-mappping) - [Low Issues](#low-issues) - [L-1: Centralization Risk for trusted owners](#l-1-centralization-risk-for-trusted-owners) - [L-2: Solmate's SafeTransferLib does not check for token contract's existence](#l-2-solmates-safetransferlib-does-not-check-for-token-contracts-existence) @@ -74,8 +75,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 73 | -| Total nSLOC | 1996 | +| .sol Files | 74 | +| Total nSLOC | 2007 | ## Files Details @@ -125,6 +126,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/StorageParameters.sol | 16 | | src/T11sTranferer.sol | 8 | | src/TautologicalCompare.sol | 17 | +| src/TautologyOrContradiction.sol | 11 | | src/TestERC20.sol | 62 | | src/UncheckedReturn.sol | 33 | | src/UncheckedSend.sol | 18 | @@ -155,14 +157,14 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **1996** | +| **Total** | **2007** | ## Issue Summary | Category | No. of Issues | | --- | --- | -| High | 32 | +| High | 33 | | Low | 25 | @@ -1185,7 +1187,7 @@ If the length of a dynamic array (storage variable) directly assigned to, it may Solidity does initialize variables by default when you declare them, however it's good practice to explicitly declare an initial value. For example, if you transfer money to an address we must make sure that the address has been initialized. -
14 Found Instances +
16 Found Instances - Found in src/AssemblyExample.sol [Line: 5](../tests/contract-playground/src/AssemblyExample.sol#L5) @@ -1248,6 +1250,18 @@ Solidity does initialize variables by default when you declare them, however it' uint256 public staticPublicNumber; ``` +- Found in src/TautologyOrContradiction.sol [Line: 6](../tests/contract-playground/src/TautologyOrContradiction.sol#L6) + + ```solidity + uint x; + ``` + +- Found in src/TautologyOrContradiction.sol [Line: 7](../tests/contract-playground/src/TautologyOrContradiction.sol#L7) + + ```solidity + uint256 y; + ``` + - Found in src/UninitializedStateVariable.sol [Line: 7](../tests/contract-playground/src/UninitializedStateVariable.sol#L7) ```solidity @@ -1631,7 +1645,30 @@ Potentially mistakened `=+` for `+=` or `=-` for `-=`. Please include a space in -## H-28: Dangerous strict equality checks on contract balances. +## H-28: Tautology or Contradiction in comparison. + +The condition has been determined to be either always true or always false due to the integer range in which we're operating. + +
2 Found Instances + + +- Found in src/TautologyOrContradiction.sol [Line: 13](../tests/contract-playground/src/TautologyOrContradiction.sol#L13) + + ```solidity + if (a > 258) {} + ``` + +- Found in src/TautologyOrContradiction.sol [Line: 16](../tests/contract-playground/src/TautologyOrContradiction.sol#L16) + + ```solidity + if (map[67] < 0) {} + ``` + +
+ + + +## H-29: Dangerous strict equality checks on contract balances. A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality. @@ -1660,7 +1697,7 @@ A contract's balance can be forcibly manipulated by another selfdestructing cont -## H-29: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10` +## H-30: Compiler Bug: Signed array in storage detected for compiler version `<0.5.10` If you want to leverage signed arrays in storage by assigning a literal array with at least one negative number, then you mus use solidity version 0.5.10 or above. This is because of a bug in older compilers. @@ -1677,7 +1714,7 @@ If you want to leverage signed arrays in storage by assigning a literal array wi -## H-30: Weak Randomness +## H-31: Weak Randomness The use of keccak256 hash functions on predictable values like block.timestamp, block.number, or similar data, including modulo operations on these values, should be avoided for generating randomness, as they are easily predictable and manipulable. The `PREVRANDAO` opcode also should not be used as a source of randomness. Instead, utilize Chainlink VRF for cryptographically secure and provably random values to ensure protocol integrity. @@ -1742,7 +1779,7 @@ The use of keccak256 hash functions on predictable values like block.timestamp, -## H-31: Usage of variable before declaration. +## H-32: Usage of variable before declaration. This is a bad practice that may lead to unintended consequences. Please declare the variable before using it. @@ -1759,7 +1796,7 @@ This is a bad practice that may lead to unintended consequences. Please declare -## H-32: Deletion from a nested mappping. +## H-33: Deletion from a nested mappping. A deletion in a structure containing a mapping will not delete the mapping. The remaining data may be used to compromise the contract. @@ -2008,7 +2045,7 @@ ERC20 functions may not behave as expected. For example: return values are not a Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;` -
19 Found Instances +
20 Found Instances - Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 2](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L2) @@ -2077,6 +2114,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid pragma solidity ^0.4.0; ``` +- Found in src/TautologyOrContradiction.sol [Line: 2](../tests/contract-playground/src/TautologyOrContradiction.sol#L2) + + ```solidity + pragma solidity ^0.5.0; + ``` + - Found in src/UncheckedSend.sol [Line: 2](../tests/contract-playground/src/UncheckedSend.sol#L2) ```solidity @@ -3036,7 +3079,7 @@ Solc compiler version 0.8.20 switches the default target EVM version to Shanghai Consider removing empty blocks. -
26 Found Instances +
27 Found Instances - Found in src/AdminContract.sol [Line: 14](../tests/contract-playground/src/AdminContract.sol#L14) @@ -3135,6 +3178,12 @@ Consider removing empty blocks. function func1(address x) external mod1(x) { ``` +- Found in src/TautologyOrContradiction.sol [Line: 9](../tests/contract-playground/src/TautologyOrContradiction.sol#L9) + + ```solidity + function makeUselessComparisons() external view { + ``` + - Found in src/UncheckedSend.sol [Line: 27](../tests/contract-playground/src/UncheckedSend.sol#L27) ```solidity @@ -3452,7 +3501,7 @@ Contract contains comments with TODOS Consider keeping the naming convention consistent in a given contract. Explicit size declarations are preferred (uint256, int256) over implicit ones (uint, int) to avoid confusion. -
20 Found Instances +
21 Found Instances - Found in src/Casting.sol [Line: 31](../tests/contract-playground/src/Casting.sol#L31) @@ -3515,6 +3564,12 @@ Consider keeping the naming convention consistent in a given contract. Explicit function check(uint a) external pure returns(bool){ ``` +- Found in src/TautologyOrContradiction.sol [Line: 6](../tests/contract-playground/src/TautologyOrContradiction.sol#L6) + + ```solidity + uint x; + ``` + - Found in src/eth2/DepositContract.sol [Line: 59](../tests/contract-playground/src/eth2/DepositContract.sol#L59) ```solidity diff --git a/reports/report.sarif b/reports/report.sarif index c60ae1436..864647451 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -1803,6 +1803,28 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TautologyOrContradiction.sol" + }, + "region": { + "byteLength": 6, + "byteOffset": 133 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TautologyOrContradiction.sol" + }, + "region": { + "byteLength": 9, + "byteOffset": 145 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -2381,6 +2403,37 @@ }, "ruleId": "dangerous-unary-operator" }, + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TautologyOrContradiction.sol" + }, + "region": { + "byteLength": 7, + "byteOffset": 296 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TautologyOrContradiction.sol" + }, + "region": { + "byteLength": 11, + "byteOffset": 369 + } + } + } + ], + "message": { + "text": "The condition has been determined to be either always true or always false due to the integer range in which we're operating." + }, + "ruleId": "tautology-or-contradiction" + }, { "level": "warning", "locations": [ @@ -3068,6 +3121,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TautologyOrContradiction.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -4918,6 +4982,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TautologyOrContradiction.sol" + }, + "region": { + "byteLength": 229, + "byteOffset": 161 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -5570,6 +5645,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TautologyOrContradiction.sol" + }, + "region": { + "byteLength": 6, + "byteOffset": 133 + } + } + }, { "physicalLocation": { "artifactLocation": { diff --git a/tests/contract-playground/src/TautologyOrContradiction.sol b/tests/contract-playground/src/TautologyOrContradiction.sol new file mode 100644 index 000000000..0f5082e65 --- /dev/null +++ b/tests/contract-playground/src/TautologyOrContradiction.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.5.0; + +contract TautologyOrContradiction { + mapping(uint256 => uint72) map; + uint x; + uint256 y; + + function makeUselessComparisons() external view { + uint8 a = 103; + + // BAD because max value of a is 2^8 - 1 + if (a > 258) {} + + // BAD because min value of uint72 is 0 + if (map[67] < 0) {} + } +} From 82d4877ae41478e420492c7e41301e81b042eeb5 Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Fri, 2 Aug 2024 14:46:35 +0530 Subject: [PATCH 07/15] Fix title & desc - state variable shadowing (#629) Co-authored-by: Alex Roan --- .../detect/high/state_variable_shadowing.rs | 21 +++++++++++++++---- reports/report.json | 4 ++-- reports/report.md | 6 +++--- reports/report.sarif | 2 +- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/aderyn_core/src/detect/high/state_variable_shadowing.rs b/aderyn_core/src/detect/high/state_variable_shadowing.rs index cf55a790f..2823a4823 100644 --- a/aderyn_core/src/detect/high/state_variable_shadowing.rs +++ b/aderyn_core/src/detect/high/state_variable_shadowing.rs @@ -179,11 +179,16 @@ impl IssueDetector for StateVariableShadowingDetector { } fn title(&self) -> String { - String::from("High Issue Title") + String::from("Shadowed State Variables in Inheritance Hierarchy") } fn description(&self) -> String { - String::from("Description of the high issue.") + String::from( + "This vulnerability arises when a derived contract unintentionally shadows a state variable from \ + a parent contract by declaring a variable with the same name. This can be misleading. \ + To prevent this, ensure variable names \ + are unique across the inheritance hierarchy or use proper visibility and scope controls." + ) } fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { @@ -220,11 +225,19 @@ mod state_variable_shadowing_detector_tests { crate::detect::detector::IssueSeverity::High ); // assert the title is correct - assert_eq!(detector.title(), String::from("High Issue Title")); + assert_eq!( + detector.title(), + String::from("Shadowed State Variables in Inheritance Hierarchy") + ); // assert the description is correct assert_eq!( detector.description(), - String::from("Description of the high issue.") + String::from( + "This vulnerability arises when a derived contract unintentionally shadows a state variable from \ + a parent contract by declaring a variable with the same name. This can be misleading. \ + To prevent this, ensure variable names \ + are unique across the inheritance hierarchy or use proper visibility and scope controls." + ) ); } } diff --git a/reports/report.json b/reports/report.json index 08bf01e31..44d3abfce 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1414,8 +1414,8 @@ ] }, { - "title": "High Issue Title", - "description": "Description of the high issue.", + "title": "Shadowed State Variables in Inheritance Hierarchy", + "description": "This vulnerability arises when a derived contract unintentionally shadows a state variable from a parent contract by declaring a variable with the same name. This can be misleading. To prevent this, ensure variable names are unique across the inheritance hierarchy or use proper visibility and scope controls.", "detector_name": "state-variable-shadowing", "instances": [ { diff --git a/reports/report.md b/reports/report.md index c1cfba5ec..939710410 100644 --- a/reports/report.md +++ b/reports/report.md @@ -26,7 +26,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [H-16: Uninitialized State Variables](#h-16-uninitialized-state-variables) - [H-17: Incorrect use of caret operator on a non hexadcimal constant](#h-17-incorrect-use-of-caret-operator-on-a-non-hexadcimal-constant) - [H-18: Yul block contains `return` function call.](#h-18-yul-block-contains-return-function-call) - - [H-19: High Issue Title](#h-19-high-issue-title) + - [H-19: Shadowed State Variables in Inheritance Hierarchy](#h-19-shadowed-state-variables-in-inheritance-hierarchy) - [H-20: Unchecked `bool success` value for send call.](#h-20-unchecked-bool-success-value-for-send-call) - [H-21: Misused boolean with logical operators](#h-21-misused-boolean-with-logical-operators) - [H-22: Sending native Eth is not protected from these functions.](#h-22-sending-native-eth-is-not-protected-from-these-functions) @@ -1348,9 +1348,9 @@ Remove this, as this causes execution to halt. Nothing after that call will exec -## H-19: High Issue Title +## H-19: Shadowed State Variables in Inheritance Hierarchy -Description of the high issue. +This vulnerability arises when a derived contract unintentionally shadows a state variable from a parent contract by declaring a variable with the same name. This can be misleading. To prevent this, ensure variable names are unique across the inheritance hierarchy or use proper visibility and scope controls.
1 Found Instances diff --git a/reports/report.sarif b/reports/report.sarif index 864647451..c3891be16 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -1975,7 +1975,7 @@ } ], "message": { - "text": "Description of the high issue." + "text": "This vulnerability arises when a derived contract unintentionally shadows a state variable from a parent contract by declaring a variable with the same name. This can be misleading. To prevent this, ensure variable names are unique across the inheritance hierarchy or use proper visibility and scope controls." }, "ruleId": "state-variable-shadowing" }, From 2230dae92de69b6e1e1690eca886838f0adbacb2 Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Fri, 2 Aug 2024 14:56:06 +0530 Subject: [PATCH 08/15] Detector: Contract that locks ether (#630) Co-authored-by: Alex Roan --- aderyn_core/src/detect/detector.rs | 5 + aderyn_core/src/detect/helpers.rs | 9 +- .../src/detect/high/contract_locks_ether.rs | 182 ++++++++++ aderyn_core/src/detect/high/mod.rs | 2 + .../adhoc-sol-files-highs-only-report.json | 3 +- reports/report.json | 194 ++++++++++- reports/report.md | 207 ++++++++++- reports/report.sarif | 328 ++++++++++++++++++ reports/templegold-report.md | 80 ++++- .../src/ContractLocksEther.sol | 210 +++++++++++ 10 files changed, 1195 insertions(+), 25 deletions(-) create mode 100644 aderyn_core/src/detect/high/contract_locks_ether.rs create mode 100644 tests/contract-playground/src/ContractLocksEther.sol diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index d45c5d23e..fa7152c9e 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -74,6 +74,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), ] } @@ -144,6 +145,7 @@ pub(crate) enum IssueDetectorNamePool { WeakRandomness, PreDeclaredLocalVariableUsage, DeleteNestedMapping, + ContractLocksEther, // NOTE: `Undecided` will be the default name (for new bots). // If it's accepted, a new variant will be added to this enum before normalizing it in aderyn Undecided, @@ -302,6 +304,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::ContractLocksEther => { + Some(Box::::default()) + } IssueDetectorNamePool::Undecided => None, } } diff --git a/aderyn_core/src/detect/helpers.rs b/aderyn_core/src/detect/helpers.rs index 97851e33d..de986d566 100644 --- a/aderyn_core/src/detect/helpers.rs +++ b/aderyn_core/src/detect/helpers.rs @@ -116,6 +116,9 @@ pub fn has_calls_that_sends_native_eth(ast_node: &ASTNode) -> bool { if let Expression::Literal(literal) = c { return literal.value.is_some(); } + if let Expression::Identifier(_) = c { + return true; + } false }); if !call_carries_value { @@ -189,9 +192,9 @@ pub fn has_binary_checks_on_some_address(ast_node: &ASTNode) -> bool { binary_operations.into_iter().any(|op| { [op.left_expression, op.right_expression].iter().any(|op| { op.as_ref().type_descriptions().is_some_and(|desc| { - desc.type_string - .as_ref() - .is_some_and(|type_string| type_string == "address") + desc.type_string.as_ref().is_some_and(|type_string| { + type_string == "address" || type_string == "address payable" + }) }) }) }) diff --git a/aderyn_core/src/detect/high/contract_locks_ether.rs b/aderyn_core/src/detect/high/contract_locks_ether.rs new file mode 100644 index 000000000..00c7618dc --- /dev/null +++ b/aderyn_core/src/detect/high/contract_locks_ether.rs @@ -0,0 +1,182 @@ +use std::collections::BTreeMap; + +use std::convert::identity; +use std::error::Error; + +use crate::ast::NodeID; + +use crate::capture; +use crate::detect::detector::IssueDetectorNamePool; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; + +use eyre::Result; + +#[derive(Default)] +pub struct ContractLocksEtherDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for ContractLocksEtherDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + for contract in context.contract_definitions() { + // If a contract can accept eth, but doesn't allow for withdrawal capture it! + if contract.can_accept_eth(context).is_some_and(identity) + && !contract + .allows_withdrawal_of_eth(context) + .is_some_and(identity) + { + capture!(self, context, contract); + } + } + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::High + } + + fn title(&self) -> String { + String::from("Contract locks Ether without a withdraw function.") + } + + fn description(&self) -> String { + String::from( + "It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, \ + which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function \ + that allows for the withdrawal of Ether from the contract." + ) + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + IssueDetectorNamePool::ContractLocksEther.to_string() + } +} + +/// Handles tasks related to contract level analysis for eth +mod contract_eth_helper { + use crate::{ + ast::{ASTNode, ContractDefinition, StateMutability, Visibility}, + context::{ + browser::ExtractFunctionDefinitions, investigator::*, + workspace_context::WorkspaceContext, + }, + detect::helpers, + }; + + impl ContractDefinition { + pub fn can_accept_eth(&self, context: &WorkspaceContext) -> Option { + let contracts = self.linearized_base_contracts.as_ref()?; + for contract_id in contracts { + let funcs = + ExtractFunctionDefinitions::from(context.nodes.get(contract_id)?).extracted; + let num_payable_funcs = funcs + .into_iter() + .filter(|f| f.implemented && *f.state_mutability() == StateMutability::Payable) + .count(); + if num_payable_funcs > 0 { + return Some(true); + } + } + Some(false) + } + + pub fn allows_withdrawal_of_eth(&self, context: &WorkspaceContext) -> Option { + /* + For all the contracts in the hirearchy try and see if there is exists a public/external function that + can be called which takes the execution flow in a path where there is possibility to send back eth away from + the contract using the low level `call{value: XXX}` or `transfer` or `send`. + */ + let contracts = self.linearized_base_contracts.as_ref()?; + for contract_id in contracts { + if let ASTNode::ContractDefinition(contract) = context.nodes.get(contract_id)? { + let funcs = contract + .function_definitions() + .into_iter() + .filter(|f| { + f.implemented + && (f.visibility == Visibility::Public + || f.visibility == Visibility::External) + }) + .map(|f| f.into()) + .collect::>(); + + let mut tracker = EthWithdrawalAllowerTracker::default(); + + let investigator = StandardInvestigator::new( + context, + funcs.iter().collect::>().as_slice(), + StandardInvestigationStyle::Downstream, + ) + .ok()?; + + investigator.investigate(context, &mut tracker).ok()?; + + if tracker.has_calls_that_sends_native_eth { + return Some(true); + } + } + } + // At this point we have successfully gone through all the contracts in the inheritance heirarchy + // but tracker has determined that none of them have have calls that sends native eth Even if they are by + // some chance, they are not reachable from external & public functions + Some(false) + } + } + + #[derive(Default)] + struct EthWithdrawalAllowerTracker { + has_calls_that_sends_native_eth: bool, + } + + impl StandardInvestigatorVisitor for EthWithdrawalAllowerTracker { + fn visit_any(&mut self, ast_node: &ASTNode) -> eyre::Result<()> { + if !self.has_calls_that_sends_native_eth + && helpers::has_calls_that_sends_native_eth(ast_node) + { + self.has_calls_that_sends_native_eth = true; + } + Ok(()) + } + } +} + +#[cfg(test)] +mod contract_locks_ether_detector_tests { + use serial_test::serial; + + use crate::detect::{ + detector::IssueDetector, high::contract_locks_ether::ContractLocksEtherDetector, + }; + + #[test] + #[serial] + fn test_contract_locks_ether() { + let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + "../tests/contract-playground/src/ContractLocksEther.sol", + ); + + let mut detector = ContractLocksEtherDetector::default(); + let found = detector.detect(&context).unwrap(); + + println!("{:#?}", detector.instances()); + + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 2); + // assert the severity is high + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::High + ); + } +} diff --git a/aderyn_core/src/detect/high/mod.rs b/aderyn_core/src/detect/high/mod.rs index 66c8902cc..047342ff4 100644 --- a/aderyn_core/src/detect/high/mod.rs +++ b/aderyn_core/src/detect/high/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod arbitrary_transfer_from; pub(crate) mod avoid_abi_encode_packed; pub(crate) mod block_timestamp_deadline; +pub(crate) mod contract_locks_ether; pub(crate) mod dangerous_strict_equality_balance; pub(crate) mod dangerous_unary_operator; pub(crate) mod delegate_call_in_loop; @@ -35,6 +36,7 @@ pub(crate) mod yul_return; pub use arbitrary_transfer_from::ArbitraryTransferFromDetector; pub use avoid_abi_encode_packed::AvoidAbiEncodePackedDetector; pub use block_timestamp_deadline::BlockTimestampDeadlineDetector; +pub use contract_locks_ether::ContractLocksEtherDetector; pub use dangerous_strict_equality_balance::DangerousStrictEqualityOnBalanceDetector; pub use dangerous_unary_operator::DangerousUnaryOperatorDetector; pub use delegate_call_in_loop::DelegateCallInLoopDetector; diff --git a/reports/adhoc-sol-files-highs-only-report.json b/reports/adhoc-sol-files-highs-only-report.json index 32906d660..e5a591225 100644 --- a/reports/adhoc-sol-files-highs-only-report.json +++ b/reports/adhoc-sol-files-highs-only-report.json @@ -194,6 +194,7 @@ "signed-storage-array", "weak-randomness", "pre-declared-local-variable-usage", - "delete-nested-mapping" + "delete-nested-mapping", + "contract-locks-ether" ] } \ No newline at end of file diff --git a/reports/report.json b/reports/report.json index 44d3abfce..1dc1f1452 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 74, - "total_sloc": 2007 + "total_source_units": 75, + "total_sloc": 2128 }, "files_details": { "files_details": [ @@ -37,6 +37,10 @@ "file_path": "src/ConstantsLiterals.sol", "n_sloc": 28 }, + { + "file_path": "src/ContractLocksEther.sol", + "n_sloc": 121 + }, { "file_path": "src/ContractWithTodo.sol", "n_sloc": 7 @@ -304,7 +308,7 @@ ] }, "issue_count": { - "high": 33, + "high": 34, "low": 25 }, "high_issues": { @@ -1517,6 +1521,30 @@ "src": "686:16", "src_char": "686:16" }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 94, + "src": "2981:11", + "src_char": "2981:11" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 131, + "src": "4205:11", + "src_char": "4205:11" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 167, + "src": "5373:11", + "src_char": "5373:11" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 194, + "src": "6342:11", + "src_char": "6342:11" + }, { "contract_path": "src/SendEtherNoChecks.sol", "line_no": 53, @@ -1817,6 +1845,43 @@ "src_char": "426:25" } ] + }, + { + "title": "Contract locks Ether without a withdraw function.", + "description": "It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function that allows for the withdrawal of Ether from the contract.", + "detector_name": "contract-locks-ether", + "instances": [ + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 5, + "src": "73:10", + "src_char": "73:10" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 31, + "src": "822:11", + "src_char": "822:11" + }, + { + "contract_path": "src/EmptyBlocks.sol", + "line_no": 20, + "src": "344:39", + "src_char": "344:39" + }, + { + "contract_path": "src/EmptyBlocks.sol", + "line_no": 44, + "src": "630:11", + "src_char": "630:11" + }, + { + "contract_path": "src/eth2/DepositContract.sol", + "line_no": 58, + "src": "4547:15", + "src_char": "3059:15" + } + ] } ] }, @@ -1975,6 +2040,12 @@ "src": "1517:20", "src_char": "1517:20" }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 162, + "src": "5185:18", + "src_char": "5185:18" + }, { "contract_path": "src/DeprecatedOZFunctions.sol", "line_no": 32, @@ -2036,6 +2107,12 @@ "src": "32:23", "src_char": "32:23" }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/ContractWithTodo.sol", "line_no": 2, @@ -2212,6 +2289,42 @@ "src": "113:1", "src_char": "113:1" }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 20, + "src": "539:10", + "src_char": "539:10" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 49, + "src": "1379:10", + "src_char": "1379:10" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 81, + "src": "2414:10", + "src_char": "2414:10" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 118, + "src": "3653:10", + "src_char": "3653:10" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 155, + "src": "4877:10", + "src_char": "4877:10" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 181, + "src": "5775:10", + "src_char": "5775:10" + }, { "contract_path": "src/ContractWithTodo.sol", "line_no": 13, @@ -2580,6 +2693,72 @@ "description": "Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.", "detector_name": "unindexed-events", "instances": [ + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 7, + "src": "119:56", + "src_char": "119:56" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 33, + "src": "869:56", + "src_char": "869:56" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 36, + "src": "961:54", + "src_char": "961:54" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 65, + "src": "1904:56", + "src_char": "1904:56" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 68, + "src": "1996:54", + "src_char": "1996:54" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 102, + "src": "3143:56", + "src_char": "3143:56" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 105, + "src": "3235:54", + "src_char": "3235:54" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 139, + "src": "4367:56", + "src_char": "4367:56" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 142, + "src": "4459:54", + "src_char": "4459:54" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 175, + "src": "5563:56", + "src_char": "5563:56" + }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 178, + "src": "5655:54", + "src_char": "5655:54" + }, { "contract_path": "src/TestERC20.sol", "line_no": 14, @@ -2788,6 +2967,12 @@ "src": "32:23", "src_char": "32:23" }, + { + "contract_path": "src/ContractLocksEther.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/ContractWithTodo.sol", "line_no": 2, @@ -3779,6 +3964,7 @@ "public-variable-read-in-external-context", "weak-randomness", "pre-declared-local-variable-usage", - "delete-nested-mapping" + "delete-nested-mapping", + "contract-locks-ether" ] } \ No newline at end of file diff --git a/reports/report.md b/reports/report.md index 939710410..11584a82e 100644 --- a/reports/report.md +++ b/reports/report.md @@ -41,6 +41,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [H-31: Weak Randomness](#h-31-weak-randomness) - [H-32: Usage of variable before declaration.](#h-32-usage-of-variable-before-declaration) - [H-33: Deletion from a nested mappping.](#h-33-deletion-from-a-nested-mappping) + - [H-34: Contract locks Ether without a withdraw function.](#h-34-contract-locks-ether-without-a-withdraw-function) - [Low Issues](#low-issues) - [L-1: Centralization Risk for trusted owners](#l-1-centralization-risk-for-trusted-owners) - [L-2: Solmate's SafeTransferLib does not check for token contract's existence](#l-2-solmates-safetransferlib-does-not-check-for-token-contracts-existence) @@ -75,8 +76,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 74 | -| Total nSLOC | 2007 | +| .sol Files | 75 | +| Total nSLOC | 2128 | ## Files Details @@ -91,6 +92,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/Casting.sol | 126 | | src/CompilerBugStorageSignedIntegerArray.sol | 13 | | src/ConstantsLiterals.sol | 28 | +| src/ContractLocksEther.sol | 121 | | src/ContractWithTodo.sol | 7 | | src/Counter.sol | 20 | | src/CrazyPragma.sol | 4 | @@ -157,14 +159,14 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **2007** | +| **Total** | **2128** | ## Issue Summary | Category | No. of Issues | | --- | --- | -| High | 33 | +| High | 34 | | Low | 25 | @@ -1457,7 +1459,7 @@ The patterns `if (… || true)` and `if (.. && false)` will always evaluate to t Introduce checks for `msg.sender` in the function -
9 Found Instances +
13 Found Instances - Found in src/CallGraphTests.sol [Line: 38](../tests/contract-playground/src/CallGraphTests.sol#L38) @@ -1466,6 +1468,30 @@ Introduce checks for `msg.sender` in the function function enterTenthFloor2(address x) external passThroughNinthFloor2(x) { ``` +- Found in src/ContractLocksEther.sol [Line: 94](../tests/contract-playground/src/ContractLocksEther.sol#L94) + + ```solidity + function takeEthBack(uint256 amount) external { + ``` + +- Found in src/ContractLocksEther.sol [Line: 131](../tests/contract-playground/src/ContractLocksEther.sol#L131) + + ```solidity + function takeEthBack(uint256 amount) external { + ``` + +- Found in src/ContractLocksEther.sol [Line: 167](../tests/contract-playground/src/ContractLocksEther.sol#L167) + + ```solidity + function takeEthBack(uint256 amount) external { + ``` + +- Found in src/ContractLocksEther.sol [Line: 194](../tests/contract-playground/src/ContractLocksEther.sol#L194) + + ```solidity + function takeEthBack(uint256 amount) external { + ``` + - Found in src/SendEtherNoChecks.sol [Line: 53](../tests/contract-playground/src/SendEtherNoChecks.sol#L53) ```solidity @@ -1813,6 +1839,47 @@ A deletion in a structure containing a mapping will not delete the mapping. The +## H-34: Contract locks Ether without a withdraw function. + +It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function that allows for the withdrawal of Ether from the contract. + +
5 Found Instances + + +- Found in src/ContractLocksEther.sol [Line: 5](../tests/contract-playground/src/ContractLocksEther.sol#L5) + + ```solidity + contract NoWithdraw { + ``` + +- Found in src/ContractLocksEther.sol [Line: 31](../tests/contract-playground/src/ContractLocksEther.sol#L31) + + ```solidity + contract NoWithdraw2 { + ``` + +- Found in src/EmptyBlocks.sol [Line: 20](../tests/contract-playground/src/EmptyBlocks.sol#L20) + + ```solidity + contract EmptyBlocksNestedInReceiverAndFallbacks { + ``` + +- Found in src/EmptyBlocks.sol [Line: 44](../tests/contract-playground/src/EmptyBlocks.sol#L44) + + ```solidity + contract EmptyBlocks { + ``` + +- Found in src/eth2/DepositContract.sol [Line: 58](../tests/contract-playground/src/eth2/DepositContract.sol#L58) + + ```solidity + contract DepositContract is IDepositContract, ERC165 { + ``` + +
+ + + # Low Issues ## L-1: Centralization Risk for trusted owners @@ -1968,7 +2035,7 @@ Openzeppelin has deprecated several functions and replaced with newer versions. ERC20 functions may not behave as expected. For example: return values are not always meaningful. It is recommended to use OpenZeppelin's SafeERC20 library. -
11 Found Instances +
12 Found Instances - Found in src/ArbitraryTransferFrom.sol [Line: 16](../tests/contract-playground/src/ArbitraryTransferFrom.sol#L16) @@ -1989,6 +2056,12 @@ ERC20 functions may not behave as expected. For example: return values are not a s_token.transferFrom(msg.sender, to, amount); ``` +- Found in src/ContractLocksEther.sol [Line: 162](../tests/contract-playground/src/ContractLocksEther.sol#L162) + + ```solidity + recipient.transfer(amount); + ``` + - Found in src/DeprecatedOZFunctions.sol [Line: 32](../tests/contract-playground/src/DeprecatedOZFunctions.sol#L32) ```solidity @@ -2045,7 +2118,7 @@ ERC20 functions may not behave as expected. For example: return values are not a Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;` -
20 Found Instances +
21 Found Instances - Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 2](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L2) @@ -2054,6 +2127,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid pragma solidity ^0.4.0; ``` +- Found in src/ContractLocksEther.sol [Line: 2](../tests/contract-playground/src/ContractLocksEther.sol#L2) + + ```solidity + pragma solidity ^0.8.0; + ``` + - Found in src/ContractWithTodo.sol [Line: 2](../tests/contract-playground/src/ContractWithTodo.sol#L2) ```solidity @@ -2223,7 +2302,7 @@ Check for `address(0)` when assigning values to address state variables. Instead of marking a function as `public`, consider marking it as `external` if it is not used internally. -
23 Found Instances +
29 Found Instances - Found in src/ArbitraryTransferFrom.sol [Line: 28](../tests/contract-playground/src/ArbitraryTransferFrom.sol#L28) @@ -2238,6 +2317,42 @@ Instead of marking a function as `public`, consider marking it as `external` if function f(uint x) public view returns (uint r) { ``` +- Found in src/ContractLocksEther.sol [Line: 20](../tests/contract-playground/src/ContractLocksEther.sol#L20) + + ```solidity + function getBalance() public view returns (uint256) { + ``` + +- Found in src/ContractLocksEther.sol [Line: 49](../tests/contract-playground/src/ContractLocksEther.sol#L49) + + ```solidity + function getBalance() public view returns (uint256) { + ``` + +- Found in src/ContractLocksEther.sol [Line: 81](../tests/contract-playground/src/ContractLocksEther.sol#L81) + + ```solidity + function getBalance() public view returns (uint256) { + ``` + +- Found in src/ContractLocksEther.sol [Line: 118](../tests/contract-playground/src/ContractLocksEther.sol#L118) + + ```solidity + function getBalance() public view returns (uint256) { + ``` + +- Found in src/ContractLocksEther.sol [Line: 155](../tests/contract-playground/src/ContractLocksEther.sol#L155) + + ```solidity + function getBalance() public view returns (uint256) { + ``` + +- Found in src/ContractLocksEther.sol [Line: 181](../tests/contract-playground/src/ContractLocksEther.sol#L181) + + ```solidity + function getBalance() public view returns (uint256) { + ``` + - Found in src/ContractWithTodo.sol [Line: 13](../tests/contract-playground/src/ContractWithTodo.sol#L13) ```solidity @@ -2599,8 +2714,74 @@ If the same constant literal value is used multiple times, create a constant sta Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed. -
7 Found Instances +
18 Found Instances + + +- Found in src/ContractLocksEther.sol [Line: 7](../tests/contract-playground/src/ContractLocksEther.sol#L7) + + ```solidity + event Deposited(address indexed sender, uint256 amount); + ``` + +- Found in src/ContractLocksEther.sol [Line: 33](../tests/contract-playground/src/ContractLocksEther.sol#L33) + + ```solidity + event Deposited(address indexed sender, uint256 amount); + ``` + +- Found in src/ContractLocksEther.sol [Line: 36](../tests/contract-playground/src/ContractLocksEther.sol#L36) + + ```solidity + event Transferred(address indexed to, uint256 amount); + ``` + +- Found in src/ContractLocksEther.sol [Line: 65](../tests/contract-playground/src/ContractLocksEther.sol#L65) + + ```solidity + event Deposited(address indexed sender, uint256 amount); + ``` +- Found in src/ContractLocksEther.sol [Line: 68](../tests/contract-playground/src/ContractLocksEther.sol#L68) + + ```solidity + event Transferred(address indexed to, uint256 amount); + ``` + +- Found in src/ContractLocksEther.sol [Line: 102](../tests/contract-playground/src/ContractLocksEther.sol#L102) + + ```solidity + event Deposited(address indexed sender, uint256 amount); + ``` + +- Found in src/ContractLocksEther.sol [Line: 105](../tests/contract-playground/src/ContractLocksEther.sol#L105) + + ```solidity + event Transferred(address indexed to, uint256 amount); + ``` + +- Found in src/ContractLocksEther.sol [Line: 139](../tests/contract-playground/src/ContractLocksEther.sol#L139) + + ```solidity + event Deposited(address indexed sender, uint256 amount); + ``` + +- Found in src/ContractLocksEther.sol [Line: 142](../tests/contract-playground/src/ContractLocksEther.sol#L142) + + ```solidity + event Transferred(address indexed to, uint256 amount); + ``` + +- Found in src/ContractLocksEther.sol [Line: 175](../tests/contract-playground/src/ContractLocksEther.sol#L175) + + ```solidity + event Deposited(address indexed sender, uint256 amount); + ``` + +- Found in src/ContractLocksEther.sol [Line: 178](../tests/contract-playground/src/ContractLocksEther.sol#L178) + + ```solidity + event Transferred(address indexed to, uint256 amount); + ``` - Found in src/TestERC20.sol [Line: 14](../tests/contract-playground/src/TestERC20.sol#L14) @@ -2817,7 +2998,7 @@ Using `ERC721::_mint()` can mint ERC721 tokens to addresses which don't support Solc compiler version 0.8.20 switches the default target EVM version to Shanghai, which means that the generated bytecode will include PUSH0 opcodes. Be sure to select the appropriate EVM version in case you intend to deploy on a chain other than mainnet like L2 chains that may not support PUSH0, otherwise deployment of your contracts will fail. -
28 Found Instances +
29 Found Instances - Found in src/AdminContract.sol [Line: 2](../tests/contract-playground/src/AdminContract.sol#L2) @@ -2826,6 +3007,12 @@ Solc compiler version 0.8.20 switches the default target EVM version to Shanghai pragma solidity 0.8.20; ``` +- Found in src/ContractLocksEther.sol [Line: 2](../tests/contract-playground/src/ContractLocksEther.sol#L2) + + ```solidity + pragma solidity ^0.8.0; + ``` + - Found in src/ContractWithTodo.sol [Line: 2](../tests/contract-playground/src/ContractWithTodo.sol#L2) ```solidity diff --git a/reports/report.sarif b/reports/report.sarif index c3891be16..2ef90121c 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -2132,6 +2132,50 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 11, + "byteOffset": 2981 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 11, + "byteOffset": 4205 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 11, + "byteOffset": 5373 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 11, + "byteOffset": 6342 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -2644,6 +2688,70 @@ }, "ruleId": "delete-nested-mapping" }, + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 10, + "byteOffset": 73 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 11, + "byteOffset": 822 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/EmptyBlocks.sol" + }, + "region": { + "byteLength": 39, + "byteOffset": 344 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/EmptyBlocks.sol" + }, + "region": { + "byteLength": 11, + "byteOffset": 630 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/eth2/DepositContract.sol" + }, + "region": { + "byteLength": 15, + "byteOffset": 4547 + } + } + } + ], + "message": { + "text": "It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function that allows for the withdrawal of Ether from the contract." + }, + "ruleId": "contract-locks-ether" + }, { "level": "note", "locations": [ @@ -2903,6 +3011,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 18, + "byteOffset": 5185 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -3011,6 +3130,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -3326,6 +3456,72 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 10, + "byteOffset": 539 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 10, + "byteOffset": 1379 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 10, + "byteOffset": 2414 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 10, + "byteOffset": 3653 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 10, + "byteOffset": 4877 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 10, + "byteOffset": 5775 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -3993,6 +4189,127 @@ { "level": "note", "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 56, + "byteOffset": 119 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 56, + "byteOffset": 869 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 54, + "byteOffset": 961 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 56, + "byteOffset": 1904 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 54, + "byteOffset": 1996 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 56, + "byteOffset": 3143 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 54, + "byteOffset": 3235 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 56, + "byteOffset": 4367 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 54, + "byteOffset": 4459 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 56, + "byteOffset": 5563 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 54, + "byteOffset": 5655 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -4359,6 +4676,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ContractLocksEther.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { diff --git a/reports/templegold-report.md b/reports/templegold-report.md index e6f22785c..278751712 100644 --- a/reports/templegold-report.md +++ b/reports/templegold-report.md @@ -13,9 +13,11 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [H-3: Unsafe Casting](#h-3-unsafe-casting) - [H-4: Contract Name Reused in Different Files](#h-4-contract-name-reused-in-different-files) - [H-5: Uninitialized State Variables](#h-5-uninitialized-state-variables) - - [H-6: Return value of the function call is not checked.](#h-6-return-value-of-the-function-call-is-not-checked) - - [H-7: Weak Randomness](#h-7-weak-randomness) - - [H-8: Deletion from a nested mappping.](#h-8-deletion-from-a-nested-mappping) + - [H-6: Sending native Eth is not protected from these functions.](#h-6-sending-native-eth-is-not-protected-from-these-functions) + - [H-7: Return value of the function call is not checked.](#h-7-return-value-of-the-function-call-is-not-checked) + - [H-8: Weak Randomness](#h-8-weak-randomness) + - [H-9: Deletion from a nested mappping.](#h-9-deletion-from-a-nested-mappping) + - [H-10: Contract locks Ether without a withdraw function.](#h-10-contract-locks-ether-without-a-withdraw-function) - [Low Issues](#low-issues) - [L-1: Centralization Risk for trusted owners](#l-1-centralization-risk-for-trusted-owners) - [L-2: `ecrecover` is susceptible to signature malleability](#l-2-ecrecover-is-susceptible-to-signature-malleability) @@ -188,7 +190,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Category | No. of Issues | | --- | --- | -| High | 8 | +| High | 10 | | Low | 19 | @@ -399,7 +401,36 @@ Solidity does initialize variables by default when you declare them, however it' -## H-6: Return value of the function call is not checked. +## H-6: Sending native Eth is not protected from these functions. + +Introduce checks for `msg.sender` in the function + +
3 Found Instances + + +- Found in contracts/amm/TempleStableAMMRouter.sol [Line: 226](../tests/2024-07-templegold/protocol/contracts/amm/TempleStableAMMRouter.sol#L226) + + ```solidity + function withdraw(address token, address to, uint256 amount) external onlyOwner { + ``` + +- Found in contracts/core/VaultProxy.sol [Line: 125](../tests/2024-07-templegold/protocol/contracts/core/VaultProxy.sol#L125) + + ```solidity + function withdraw(address token, address to, uint256 amount) external onlyOwner { + ``` + +- Found in contracts/core/VaultedTemple.sol [Line: 52](../tests/2024-07-templegold/protocol/contracts/core/VaultedTemple.sol#L52) + + ```solidity + function withdraw(address token, address to, uint256 amount) external onlyOwner { + ``` + +
+ + + +## H-7: Return value of the function call is not checked. Function returns a value but it is ignored. @@ -488,7 +519,7 @@ Function returns a value but it is ignored. -## H-7: Weak Randomness +## H-8: Weak Randomness The use of keccak256 hash functions on predictable values like block.timestamp, block.number, or similar data, including modulo operations on these values, should be avoided for generating randomness, as they are easily predictable and manipulable. The `PREVRANDAO` opcode also should not be used as a source of randomness. Instead, utilize Chainlink VRF for cryptographically secure and provably random values to ensure protocol integrity. @@ -505,7 +536,7 @@ The use of keccak256 hash functions on predictable values like block.timestamp, -## H-8: Deletion from a nested mappping. +## H-9: Deletion from a nested mappping. A deletion in a structure containing a mapping will not delete the mapping. The remaining data may be used to compromise the contract. @@ -522,6 +553,41 @@ A deletion in a structure containing a mapping will not delete the mapping. The +## H-10: Contract locks Ether without a withdraw function. + +It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function that allows for the withdrawal of Ether from the contract. + +
4 Found Instances + + +- Found in contracts/fakes/UniswapV2Router02NoEth.sol [Line: 21](../tests/2024-07-templegold/protocol/contracts/fakes/UniswapV2Router02NoEth.sol#L21) + + ```solidity + contract UniswapV2Router02NoEth is IUniswapV2Router02 { + ``` + +- Found in contracts/fakes/templegold/TempleGoldMock.sol [Line: 27](../tests/2024-07-templegold/protocol/contracts/fakes/templegold/TempleGoldMock.sol#L27) + + ```solidity + contract TempleGoldMock is OFT { + ``` + +- Found in contracts/templegold/TempleGold.sol [Line: 29](../tests/2024-07-templegold/protocol/contracts/templegold/TempleGold.sol#L29) + + ```solidity + contract TempleGold is ITempleGold, OFT { + ``` + +- Found in contracts/templegold/TempleTeleporter.sol [Line: 19](../tests/2024-07-templegold/protocol/contracts/templegold/TempleTeleporter.sol#L19) + + ```solidity + contract TempleTeleporter is ITempleTeleporter, OApp { + ``` + +
+ + + # Low Issues ## L-1: Centralization Risk for trusted owners diff --git a/tests/contract-playground/src/ContractLocksEther.sol b/tests/contract-playground/src/ContractLocksEther.sol new file mode 100644 index 000000000..4e607cbdc --- /dev/null +++ b/tests/contract-playground/src/ContractLocksEther.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// BAD +contract NoWithdraw { + // Event to log deposits + event Deposited(address indexed sender, uint256 amount); + + // Public payable function to receive Ether + receive() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Public payable fallback function to handle any data sent with Ether + fallback() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Function to get the balance of the contract + function getBalance() public view returns (uint256) { + return address(this).balance; + } +} + +/* + Even though the `NoWithdraw2` has an internal function that can send eth away, it's not reachable + by any public / external function hence it is a bad contract. +*/ + +// BAD +contract NoWithdraw2 { + // Event to log deposits + event Deposited(address indexed sender, uint256 amount); + + // Event to log transfers + event Transferred(address indexed to, uint256 amount); + + // Public payable function to receive Ether + receive() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Public payable fallback function to handle any data sent with Ether + fallback() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Function to get the balance of the contract + function getBalance() public view returns (uint256) { + return address(this).balance; + } + + // Internal function to send Ether to a given address + function _sendEther(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Insufficient balance"); + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Transfer failed"); + emit Transferred(recipient, amount); + } +} + +// GOOD +contract CanWithdraw { + // Event to log deposits + event Deposited(address indexed sender, uint256 amount); + + // Event to log transfers + event Transferred(address indexed to, uint256 amount); + + // Public payable function to receive Ether + receive() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Public payable fallback function to handle any data sent with Ether + fallback() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Function to get the balance of the contract + function getBalance() public view returns (uint256) { + return address(this).balance; + } + + // Internal function to send Ether to a given address + function _sendEther(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Insufficient balance"); + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Transfer failed"); + emit Transferred(recipient, amount); + } + + // This function allows for the withdrawal of eth. Hence this contract is a GOOD contract. + function takeEthBack(uint256 amount) external { + _sendEther(payable(msg.sender), amount); + } +} + +// GOOD +contract CanWithdraw2 { + // Event to log deposits + event Deposited(address indexed sender, uint256 amount); + + // Event to log transfers + event Transferred(address indexed to, uint256 amount); + + // Public payable function to receive Ether + receive() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Public payable fallback function to handle any data sent with Ether + fallback() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Function to get the balance of the contract + function getBalance() public view returns (uint256) { + return address(this).balance; + } + + // Internal function to send Ether to a given address + function _sendEther(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Insufficient balance"); + bool success = recipient.send(amount); + require(success, "Transfer failed"); + emit Transferred(recipient, amount); + } + + // This function allows for the withdrawal of eth. Hence this contract is a GOOD contract. + function takeEthBack(uint256 amount) external { + _sendEther(payable(msg.sender), amount); + } +} + +// GOOD +contract CanWithdraw3 { + // Event to log deposits + event Deposited(address indexed sender, uint256 amount); + + // Event to log transfers + event Transferred(address indexed to, uint256 amount); + + // Public payable function to receive Ether + receive() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Public payable fallback function to handle any data sent with Ether + fallback() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Function to get the balance of the contract + function getBalance() public view returns (uint256) { + return address(this).balance; + } + + // Internal function to send Ether to a given address + function _sendEther(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Insufficient balance"); + recipient.transfer(amount); + emit Transferred(recipient, amount); + } + + // This function allows for the withdrawal of eth. Hence this contract is a GOOD contract. + function takeEthBack(uint256 amount) external { + _sendEther(payable(msg.sender), amount); + } +} + +// GOOD (no payable functions) +contract CanWithdrawParent { + // Event to log deposits + event Deposited(address indexed sender, uint256 amount); + + // Event to log transfers + event Transferred(address indexed to, uint256 amount); + + // Function to get the balance of the contract + function getBalance() public view returns (uint256) { + return address(this).balance; + } + + // Internal function to send Ether to a given address + function _sendEther(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Insufficient balance"); + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Transfer failed"); + emit Transferred(recipient, amount); + } + + // This function allows for the withdrawal of eth. Hence this contract is a GOOD contract. + function takeEthBack(uint256 amount) external { + _sendEther(payable(msg.sender), amount); + } +} + +// GOOD (It has payable functions, but we can withdraw (look at the parent's contract) +contract CanWithdrawChild is CanWithdrawParent { + // Public payable function to receive Ether + receive() external payable { + emit Deposited(msg.sender, msg.value); + } + + // Public payable fallback function to handle any data sent with Ether + fallback() external payable { + emit Deposited(msg.sender, msg.value); + } +} From 7b5a1d064a4f48abe779a06a9b107cd7213f9c95 Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Fri, 2 Aug 2024 15:20:28 +0530 Subject: [PATCH 09/15] Fix: `load_solidity_with_source_unit` should load callgraphs by default (#637) Co-authored-by: Alex Roan --- .../context/investigator/callgraph_tests.rs | 22 +++++++++++++------ .../src/detect/high/contract_locks_ether.rs | 2 +- .../high/delegate_call_no_address_check.rs | 2 +- .../src/detect/high/send_ether_no_checks.rs | 2 +- .../src/detect/test_utils/load_source_unit.rs | 16 +------------- aderyn_core/src/detect/test_utils/mod.rs | 1 - 6 files changed, 19 insertions(+), 26 deletions(-) diff --git a/aderyn_core/src/context/investigator/callgraph_tests.rs b/aderyn_core/src/context/investigator/callgraph_tests.rs index faa0a1ccc..3f5c1b268 100644 --- a/aderyn_core/src/context/investigator/callgraph_tests.rs +++ b/aderyn_core/src/context/investigator/callgraph_tests.rs @@ -12,6 +12,7 @@ mod callgraph_tests { }, }; + use serial_test::serial; use StandardInvestigationStyle::*; fn get_function_by_name(context: &WorkspaceContext, name: &str) -> ASTNode { @@ -35,8 +36,9 @@ mod callgraph_tests { } #[test] + #[serial] fn test_callgraph_is_not_none() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/CallGraphTests.sol", ); assert!(context.forward_callgraph.is_some()); @@ -44,8 +46,9 @@ mod callgraph_tests { } #[test] + #[serial] fn test_tower1_modifier_has_no_downstream() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/CallGraphTests.sol", ); @@ -62,8 +65,9 @@ mod callgraph_tests { } #[test] + #[serial] fn test_tower1_modifier_has_upstream() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/CallGraphTests.sol", ); @@ -80,8 +84,9 @@ mod callgraph_tests { } #[test] + #[serial] fn test_tower2_modifier_has_both_upstream_and_downstream() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/CallGraphTests.sol", ); @@ -99,8 +104,9 @@ mod callgraph_tests { } #[test] + #[serial] fn test_tower3_modifier_has_both_upstream_and_downstream() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/CallGraphTests.sol", ); @@ -120,8 +126,9 @@ mod callgraph_tests { } #[test] + #[serial] fn test_tower3_functions_has_upstream() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/CallGraphTests.sol", ); @@ -137,8 +144,9 @@ mod callgraph_tests { } #[test] + #[serial] fn test_tower4_functions_has_upstream_and_downstream() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/CallGraphTests.sol", ); diff --git a/aderyn_core/src/detect/high/contract_locks_ether.rs b/aderyn_core/src/detect/high/contract_locks_ether.rs index 00c7618dc..808e0df02 100644 --- a/aderyn_core/src/detect/high/contract_locks_ether.rs +++ b/aderyn_core/src/detect/high/contract_locks_ether.rs @@ -160,7 +160,7 @@ mod contract_locks_ether_detector_tests { #[test] #[serial] fn test_contract_locks_ether() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/ContractLocksEther.sol", ); diff --git a/aderyn_core/src/detect/high/delegate_call_no_address_check.rs b/aderyn_core/src/detect/high/delegate_call_no_address_check.rs index 19ec1e134..19a698cc0 100644 --- a/aderyn_core/src/detect/high/delegate_call_no_address_check.rs +++ b/aderyn_core/src/detect/high/delegate_call_no_address_check.rs @@ -100,7 +100,7 @@ mod delegate_call_no_address_check_tests { #[test] #[serial] fn test_delegate_call_without_checks() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/DelegateCallWithoutAddressCheck.sol", ); diff --git a/aderyn_core/src/detect/high/send_ether_no_checks.rs b/aderyn_core/src/detect/high/send_ether_no_checks.rs index a20f35667..964c694a8 100644 --- a/aderyn_core/src/detect/high/send_ether_no_checks.rs +++ b/aderyn_core/src/detect/high/send_ether_no_checks.rs @@ -74,7 +74,7 @@ mod send_ether_no_checks_detector_tests { #[test] #[serial] fn test_send_ether_no_checks() { - let context = crate::detect::test_utils::load_solidity_source_unit_with_callgraphs( + let context = crate::detect::test_utils::load_solidity_source_unit( "../tests/contract-playground/src/SendEtherNoChecks.sol", ); diff --git a/aderyn_core/src/detect/test_utils/load_source_unit.rs b/aderyn_core/src/detect/test_utils/load_source_unit.rs index 31844c086..c52a15f29 100644 --- a/aderyn_core/src/detect/test_utils/load_source_unit.rs +++ b/aderyn_core/src/detect/test_utils/load_source_unit.rs @@ -13,19 +13,8 @@ use crate::{context::graph::WorkspaceCallGraph, visitor::ast_visitor::Node}; use super::ensure_valid_solidity_file; -#[cfg(test)] -pub fn load_solidity_source_unit_with_callgraphs(filepath: &str) -> WorkspaceContext { - _load_solidity_source_unit(filepath, true) -} - #[cfg(test)] pub fn load_solidity_source_unit(filepath: &str) -> WorkspaceContext { - println!("WARNING: Callgraph won't be loaded. Please use `load_solidity_source_unit_with_callgraphs` if callgraph is required!"); - _load_solidity_source_unit(filepath, false) -} - -#[cfg(test)] -fn _load_solidity_source_unit(filepath: &str, should_load_callgraphs: bool) -> WorkspaceContext { let solidity_file = &ensure_valid_solidity_file(filepath); let solidity_content = std::fs::read_to_string(solidity_file).unwrap(); @@ -95,10 +84,6 @@ fn _load_solidity_source_unit(filepath: &str, should_load_callgraphs: bool) -> W absorb_ast_content_into_context(&ast_content, solidity_content.clone(), &mut context); } - if should_load_callgraphs { - load_callgraphs(&mut context); - } - context } else { eprintln!("Error running solc command"); @@ -128,6 +113,7 @@ fn absorb_ast_content_into_context( eprintln!("{:?}", err); std::process::exit(1); }); + load_callgraphs(context); } fn is_demarcation_line(line: &str, file_args: Vec<&str>) -> (bool, Option) { diff --git a/aderyn_core/src/detect/test_utils/mod.rs b/aderyn_core/src/detect/test_utils/mod.rs index b10ddc7df..10a76c7fd 100644 --- a/aderyn_core/src/detect/test_utils/mod.rs +++ b/aderyn_core/src/detect/test_utils/mod.rs @@ -7,7 +7,6 @@ use std::path::PathBuf; // Using `solc` to read AST given a source unit (i.e Solidity file) pub use load_source_unit::load_multiple_solidity_source_units_into_single_context; pub use load_source_unit::load_solidity_source_unit; -pub use load_source_unit::load_solidity_source_unit_with_callgraphs; pub(crate) fn take_loader_lock() -> impl Drop { static LOCK: Lazy> = Lazy::new(|| std::sync::Mutex::new(())); From c9c1bd132c0279aa63ef2a753b9f1da5eaec2b8f Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Fri, 2 Aug 2024 18:10:16 +0530 Subject: [PATCH 10/15] Detector: `msg.value` used in loop (#636) --- aderyn_core/src/ast/ast.rs | 46 +++++ aderyn_core/src/detect/detector.rs | 3 + aderyn_core/src/detect/high/mod.rs | 2 + .../src/detect/high/msg_value_in_loops.rs | 139 +++++++++++++++ .../adhoc-sol-files-highs-only-report.json | 1 + reports/report.json | 102 ++++++++++- reports/report.md | 117 +++++++++++-- reports/report.sarif | 163 ++++++++++++++++++ .../src/MsgValueInLoop.sol | 80 +++++++++ 9 files changed, 640 insertions(+), 13 deletions(-) create mode 100644 aderyn_core/src/detect/high/msg_value_in_loops.rs create mode 100644 tests/contract-playground/src/MsgValueInLoop.sol diff --git a/aderyn_core/src/ast/ast.rs b/aderyn_core/src/ast/ast.rs index 1daade1a9..acb12531a 100644 --- a/aderyn_core/src/ast/ast.rs +++ b/aderyn_core/src/ast/ast.rs @@ -146,3 +146,49 @@ impl From for ASTNode { } } } + +impl From for ASTNode { + fn from(value: Statement) -> Self { + match value { + Statement::Block(node) => node.into(), + Statement::Break(node) => node.into(), + Statement::Continue(node) => node.into(), + Statement::DoWhileStatement(node) => node.into(), + Statement::PlaceholderStatement(node) => node.into(), + Statement::VariableDeclarationStatement(node) => node.into(), + Statement::IfStatement(node) => node.into(), + Statement::ForStatement(node) => node.into(), + Statement::WhileStatement(node) => node.into(), + Statement::EmitStatement(node) => node.into(), + Statement::TryStatement(node) => node.into(), + Statement::UncheckedBlock(node) => node.into(), + Statement::Return(node) => node.into(), + Statement::RevertStatement(node) => node.into(), + Statement::ExpressionStatement(node) => node.into(), + Statement::InlineAssembly(node) => node.into(), + } + } +} + +impl From<&Statement> for ASTNode { + fn from(value: &Statement) -> Self { + match value { + Statement::Block(node) => node.into(), + Statement::Break(node) => node.into(), + Statement::Continue(node) => node.into(), + Statement::DoWhileStatement(node) => node.into(), + Statement::PlaceholderStatement(node) => node.into(), + Statement::VariableDeclarationStatement(node) => node.into(), + Statement::IfStatement(node) => node.into(), + Statement::ForStatement(node) => node.into(), + Statement::WhileStatement(node) => node.into(), + Statement::EmitStatement(node) => node.into(), + Statement::TryStatement(node) => node.into(), + Statement::UncheckedBlock(node) => node.into(), + Statement::Return(node) => node.into(), + Statement::RevertStatement(node) => node.into(), + Statement::ExpressionStatement(node) => node.into(), + Statement::InlineAssembly(node) => node.into(), + } + } +} diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index fa7152c9e..8c27cfaca 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -74,6 +74,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), ] } @@ -145,6 +146,7 @@ pub(crate) enum IssueDetectorNamePool { WeakRandomness, PreDeclaredLocalVariableUsage, DeleteNestedMapping, + MsgValueInLoop, ContractLocksEther, // NOTE: `Undecided` will be the default name (for new bots). // If it's accepted, a new variant will be added to this enum before normalizing it in aderyn @@ -304,6 +306,7 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::MsgValueInLoop => Some(Box::::default()), IssueDetectorNamePool::ContractLocksEther => { Some(Box::::default()) } diff --git a/aderyn_core/src/detect/high/mod.rs b/aderyn_core/src/detect/high/mod.rs index 047342ff4..b33b9f716 100644 --- a/aderyn_core/src/detect/high/mod.rs +++ b/aderyn_core/src/detect/high/mod.rs @@ -13,6 +13,7 @@ pub(crate) mod experimental_encoder; pub(crate) mod incorrect_caret_operator; pub(crate) mod incorrect_shift_order; pub(crate) mod misused_boolean; +pub(crate) mod msg_value_in_loops; pub(crate) mod multiple_constructors; pub(crate) mod nested_struct_in_mapping; pub(crate) mod pre_declared_variable_usage; @@ -48,6 +49,7 @@ pub use experimental_encoder::ExperimentalEncoderDetector; pub use incorrect_caret_operator::IncorrectUseOfCaretOperatorDetector; pub use incorrect_shift_order::IncorrectShiftOrderDetector; pub use misused_boolean::MisusedBooleanDetector; +pub use msg_value_in_loops::MsgValueUsedInLoopDetector; pub use multiple_constructors::MultipleConstructorsDetector; pub use nested_struct_in_mapping::NestedStructInMappingDetector; pub use pre_declared_variable_usage::PreDeclaredLocalVariableUsageDetector; diff --git a/aderyn_core/src/detect/high/msg_value_in_loops.rs b/aderyn_core/src/detect/high/msg_value_in_loops.rs new file mode 100644 index 000000000..b95ed64db --- /dev/null +++ b/aderyn_core/src/detect/high/msg_value_in_loops.rs @@ -0,0 +1,139 @@ +use std::collections::BTreeMap; +use std::convert::identity; +use std::error::Error; + +use crate::ast::{ASTNode, Expression, NodeID}; + +use crate::capture; +use crate::context::browser::ExtractMemberAccesses; +use crate::context::investigator::{ + StandardInvestigationStyle, StandardInvestigator, StandardInvestigatorVisitor, +}; +use crate::detect::detector::IssueDetectorNamePool; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; +use eyre::Result; + +#[derive(Default)] +pub struct MsgValueUsedInLoopDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for MsgValueUsedInLoopDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + // Investigate for loops to check for usage of `msg.value` + for for_statement in context.for_statements() { + if uses_msg_value(context, &(for_statement.into())).is_some_and(identity) { + capture!(self, context, for_statement); + } + } + + // Investigate while loops to check for usage of `msg.value` + for while_statement in context.while_statements() { + if uses_msg_value(context, &(while_statement.into())).is_some_and(identity) { + capture!(self, context, while_statement); + } + } + + // Investigate the do while loops to check for usage of `msg.value` + for do_while_statement in context.do_while_statements() { + if uses_msg_value(context, &(do_while_statement.into())).is_some_and(identity) { + capture!(self, context, do_while_statement); + } + } + + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::High + } + + fn title(&self) -> String { + String::from("Loop contains `msg.value`.") + } + + fn description(&self) -> String { + String::from("Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`.") + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + IssueDetectorNamePool::MsgValueInLoop.to_string() + } +} + +fn uses_msg_value(context: &WorkspaceContext, ast_node: &ASTNode) -> Option { + let mut tracker = MsgValueTracker::default(); + let investigator = + StandardInvestigator::new(context, &[ast_node], StandardInvestigationStyle::Downstream) + .ok()?; + + investigator.investigate(context, &mut tracker).ok()?; + Some(tracker.has_msg_value) +} + +#[derive(Default)] +struct MsgValueTracker { + has_msg_value: bool, +} + +impl StandardInvestigatorVisitor for MsgValueTracker { + fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> { + if !self.has_msg_value + && ExtractMemberAccesses::from(node) + .extracted + .iter() + .any(|member_access| { + member_access.member_name == "value" + && if let Expression::Identifier(identifier) = + member_access.expression.as_ref() + { + identifier.name == "msg" + } else { + false + } + }) + { + self.has_msg_value = true; + } + + Ok(()) + } +} + +#[cfg(test)] +mod msg_value_in_loop_detector { + use serial_test::serial; + + use crate::detect::{ + detector::IssueDetector, high::msg_value_in_loops::MsgValueUsedInLoopDetector, + }; + + #[test] + #[serial] + fn test_msg_value_in_loop() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/MsgValueInLoop.sol", + ); + + let mut detector = MsgValueUsedInLoopDetector::default(); + let found = detector.detect(&context).unwrap(); + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 4); + // assert the severity is high + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::High + ); + } +} diff --git a/reports/adhoc-sol-files-highs-only-report.json b/reports/adhoc-sol-files-highs-only-report.json index e5a591225..6cf4758ea 100644 --- a/reports/adhoc-sol-files-highs-only-report.json +++ b/reports/adhoc-sol-files-highs-only-report.json @@ -195,6 +195,7 @@ "weak-randomness", "pre-declared-local-variable-usage", "delete-nested-mapping", + "msg-value-in-loop", "contract-locks-ether" ] } \ No newline at end of file diff --git a/reports/report.json b/reports/report.json index 1dc1f1452..60aa6c2e3 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 75, - "total_sloc": 2128 + "total_source_units": 76, + "total_sloc": 2183 }, "files_details": { "files_details": [ @@ -125,6 +125,10 @@ "file_path": "src/MisusedBoolean.sol", "n_sloc": 67 }, + { + "file_path": "src/MsgValueInLoop.sol", + "n_sloc": 55 + }, { "file_path": "src/MultipleConstructorSchemes.sol", "n_sloc": 10 @@ -308,7 +312,7 @@ ] }, "issue_count": { - "high": 34, + "high": 35, "low": 25 }, "high_issues": { @@ -1846,6 +1850,37 @@ } ] }, + { + "title": "Loop contains `msg.value`.", + "description": "Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`.", + "detector_name": "msg-value-in-loop", + "instances": [ + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 12, + "src": "289:107", + "src_char": "289:107" + }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 38, + "src": "988:94", + "src_char": "988:94" + }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 54, + "src": "1415:93", + "src_char": "1415:93" + }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 71, + "src": "1844:97", + "src_char": "1844:97" + } + ] + }, { "title": "Contract locks Ether without a withdraw function.", "description": "It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function that allows for the withdrawal of Ether from the contract.", @@ -1875,6 +1910,36 @@ "src": "630:11", "src_char": "630:11" }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 7, + "src": "103:15", + "src_char": "103:15" + }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 19, + "src": "423:19", + "src_char": "423:19" + }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 33, + "src": "831:15", + "src_char": "831:15" + }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 48, + "src": "1233:15", + "src_char": "1233:15" + }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 65, + "src": "1659:15", + "src_char": "1659:15" + }, { "contract_path": "src/eth2/DepositContract.sol", "line_no": 58, @@ -2161,6 +2226,12 @@ "src": "0:24", "src_char": "0:24" }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/PreDeclaredVarUsage.sol", "line_no": 2, @@ -3027,6 +3098,12 @@ "src": "32:23", "src_char": "32:23" }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/StateVariables.sol", "line_no": 2, @@ -3559,6 +3636,24 @@ "src": "693:12", "src_char": "693:12" }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 43, + "src": "1103:8", + "src_char": "1103:8" + }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 60, + "src": "1529:8", + "src_char": "1529:8" + }, + { + "contract_path": "src/MsgValueInLoop.sol", + "line_no": 77, + "src": "1962:8", + "src_char": "1962:8" + }, { "contract_path": "src/SendEtherNoChecks.sol", "line_no": 9, @@ -3965,6 +4060,7 @@ "weak-randomness", "pre-declared-local-variable-usage", "delete-nested-mapping", + "msg-value-in-loop", "contract-locks-ether" ] } \ No newline at end of file diff --git a/reports/report.md b/reports/report.md index 11584a82e..ad62f5932 100644 --- a/reports/report.md +++ b/reports/report.md @@ -41,7 +41,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [H-31: Weak Randomness](#h-31-weak-randomness) - [H-32: Usage of variable before declaration.](#h-32-usage-of-variable-before-declaration) - [H-33: Deletion from a nested mappping.](#h-33-deletion-from-a-nested-mappping) - - [H-34: Contract locks Ether without a withdraw function.](#h-34-contract-locks-ether-without-a-withdraw-function) + - [H-34: Loop contains `msg.value`.](#h-34-loop-contains-msgvalue) + - [H-35: Contract locks Ether without a withdraw function.](#h-35-contract-locks-ether-without-a-withdraw-function) - [Low Issues](#low-issues) - [L-1: Centralization Risk for trusted owners](#l-1-centralization-risk-for-trusted-owners) - [L-2: Solmate's SafeTransferLib does not check for token contract's existence](#l-2-solmates-safetransferlib-does-not-check-for-token-contracts-existence) @@ -76,8 +77,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 75 | -| Total nSLOC | 2128 | +| .sol Files | 76 | +| Total nSLOC | 2183 | ## Files Details @@ -114,6 +115,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/InternalFunctions.sol | 22 | | src/KeccakContract.sol | 21 | | src/MisusedBoolean.sol | 67 | +| src/MsgValueInLoop.sol | 55 | | src/MultipleConstructorSchemes.sol | 10 | | src/OnceModifierExample.sol | 8 | | src/PreDeclaredVarUsage.sol | 9 | @@ -159,14 +161,14 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **2128** | +| **Total** | **2183** | ## Issue Summary | Category | No. of Issues | | --- | --- | -| High | 34 | +| High | 35 | | Low | 25 | @@ -1839,11 +1841,46 @@ A deletion in a structure containing a mapping will not delete the mapping. The -## H-34: Contract locks Ether without a withdraw function. +## H-34: Loop contains `msg.value`. + +Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`. + +
4 Found Instances + + +- Found in src/MsgValueInLoop.sol [Line: 12](../tests/contract-playground/src/MsgValueInLoop.sol#L12) + + ```solidity + for (uint256 i = 0; i < receivers.length; i++) { + ``` + +- Found in src/MsgValueInLoop.sol [Line: 38](../tests/contract-playground/src/MsgValueInLoop.sol#L38) + + ```solidity + for (uint256 i = 0; i < receivers.length; i++) { + ``` + +- Found in src/MsgValueInLoop.sol [Line: 54](../tests/contract-playground/src/MsgValueInLoop.sol#L54) + + ```solidity + while (i < receivers.length) { + ``` + +- Found in src/MsgValueInLoop.sol [Line: 71](../tests/contract-playground/src/MsgValueInLoop.sol#L71) + + ```solidity + do { + ``` + +
+ + + +## H-35: Contract locks Ether without a withdraw function. It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function that allows for the withdrawal of Ether from the contract. -
5 Found Instances +
10 Found Instances - Found in src/ContractLocksEther.sol [Line: 5](../tests/contract-playground/src/ContractLocksEther.sol#L5) @@ -1870,6 +1907,36 @@ It appears that the contract includes a payable function to accept Ether but lac contract EmptyBlocks { ``` +- Found in src/MsgValueInLoop.sol [Line: 7](../tests/contract-playground/src/MsgValueInLoop.sol#L7) + + ```solidity + contract MsgValueInLoop1 { + ``` + +- Found in src/MsgValueInLoop.sol [Line: 19](../tests/contract-playground/src/MsgValueInLoop.sol#L19) + + ```solidity + contract MsgValueOutsideLoop { + ``` + +- Found in src/MsgValueInLoop.sol [Line: 33](../tests/contract-playground/src/MsgValueInLoop.sol#L33) + + ```solidity + contract MsgValueInLoop2 { + ``` + +- Found in src/MsgValueInLoop.sol [Line: 48](../tests/contract-playground/src/MsgValueInLoop.sol#L48) + + ```solidity + contract MsgValueInLoop3 { + ``` + +- Found in src/MsgValueInLoop.sol [Line: 65](../tests/contract-playground/src/MsgValueInLoop.sol#L65) + + ```solidity + contract MsgValueInLoop4 { + ``` + - Found in src/eth2/DepositContract.sol [Line: 58](../tests/contract-playground/src/eth2/DepositContract.sol#L58) ```solidity @@ -2118,7 +2185,7 @@ ERC20 functions may not behave as expected. For example: return values are not a Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;` -
21 Found Instances +
22 Found Instances - Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 2](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L2) @@ -2181,6 +2248,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid pragma solidity ^0.8.24; ``` +- Found in src/MsgValueInLoop.sol [Line: 2](../tests/contract-playground/src/MsgValueInLoop.sol#L2) + + ```solidity + pragma solidity ^0.8.0; + ``` + - Found in src/PreDeclaredVarUsage.sol [Line: 2](../tests/contract-playground/src/PreDeclaredVarUsage.sol#L2) ```solidity @@ -2998,7 +3071,7 @@ Using `ERC721::_mint()` can mint ERC721 tokens to addresses which don't support Solc compiler version 0.8.20 switches the default target EVM version to Shanghai, which means that the generated bytecode will include PUSH0 opcodes. Be sure to select the appropriate EVM version in case you intend to deploy on a chain other than mainnet like L2 chains that may not support PUSH0, otherwise deployment of your contracts will fail. -
29 Found Instances +
30 Found Instances - Found in src/AdminContract.sol [Line: 2](../tests/contract-playground/src/AdminContract.sol#L2) @@ -3067,6 +3140,12 @@ Solc compiler version 0.8.20 switches the default target EVM version to Shanghai pragma solidity 0.8.20; ``` +- Found in src/MsgValueInLoop.sol [Line: 2](../tests/contract-playground/src/MsgValueInLoop.sol#L2) + + ```solidity + pragma solidity ^0.8.0; + ``` + - Found in src/StateVariables.sol [Line: 2](../tests/contract-playground/src/StateVariables.sol#L2) ```solidity @@ -3582,7 +3661,7 @@ Use `e` notation, for example: `1e18`, instead of its full numeric value. Instead of separating the logic into a separate function, consider inlining the logic into the calling function. This can reduce the number of function calls and improve readability. -
12 Found Instances +
15 Found Instances - Found in src/CallGraphTests.sol [Line: 6](../tests/contract-playground/src/CallGraphTests.sol#L6) @@ -3615,6 +3694,24 @@ Instead of separating the logic into a separate function, consider inlining the function internalSet2(uint256 _newValue) internal { ``` +- Found in src/MsgValueInLoop.sol [Line: 43](../tests/contract-playground/src/MsgValueInLoop.sol#L43) + + ```solidity + function addToBal(address[] memory receivers, uint256 index) internal { + ``` + +- Found in src/MsgValueInLoop.sol [Line: 60](../tests/contract-playground/src/MsgValueInLoop.sol#L60) + + ```solidity + function addToBal(address[] memory receivers, uint256 index) internal { + ``` + +- Found in src/MsgValueInLoop.sol [Line: 77](../tests/contract-playground/src/MsgValueInLoop.sol#L77) + + ```solidity + function addToBal(address[] memory receivers, uint256 index) internal { + ``` + - Found in src/SendEtherNoChecks.sol [Line: 9](../tests/contract-playground/src/SendEtherNoChecks.sol#L9) ```solidity diff --git a/reports/report.sarif b/reports/report.sarif index 2ef90121c..9f48e2311 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -2688,6 +2688,59 @@ }, "ruleId": "delete-nested-mapping" }, + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 107, + "byteOffset": 289 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 94, + "byteOffset": 988 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 93, + "byteOffset": 1415 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 97, + "byteOffset": 1844 + } + } + } + ], + "message": { + "text": "Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`." + }, + "ruleId": "msg-value-in-loop" + }, { "level": "warning", "locations": [ @@ -2735,6 +2788,61 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 15, + "byteOffset": 103 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 19, + "byteOffset": 423 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 15, + "byteOffset": 831 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 15, + "byteOffset": 1233 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 15, + "byteOffset": 1659 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -3229,6 +3337,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -4786,6 +4905,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -5746,6 +5876,39 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 8, + "byteOffset": 1103 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 8, + "byteOffset": 1529 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/MsgValueInLoop.sol" + }, + "region": { + "byteLength": 8, + "byteOffset": 1962 + } + } + }, { "physicalLocation": { "artifactLocation": { diff --git a/tests/contract-playground/src/MsgValueInLoop.sol b/tests/contract-playground/src/MsgValueInLoop.sol new file mode 100644 index 000000000..b8f014f9b --- /dev/null +++ b/tests/contract-playground/src/MsgValueInLoop.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// COPIED from Slither Wiki + +// BAD +contract MsgValueInLoop1 { + mapping(address => uint256) balances; + + function bad(address[] memory receivers) external payable { + // BAD for loop (uses msg.value inside loop) + for (uint256 i = 0; i < receivers.length; i++) { + balances[receivers[i]] += msg.value; + } + } +} + +// GOOD +contract MsgValueOutsideLoop { + mapping(address => uint256) balances; + + function good(address[] memory receivers) external payable { + // GOOD for loop (does not use msg.value inside loop) + uint256 total = msg.value; + for (uint256 i = 0; i < receivers.length; i++) { + balances[receivers[i]] += total / receivers.length; + } + } +} + +///// MORE BAD EXAMPLES ////// + +contract MsgValueInLoop2 { + mapping(address => uint256) balances; + + function bad(address[] memory receivers) external payable { + // BAD for loop + for (uint256 i = 0; i < receivers.length; i++) { + addToBal(receivers, i); + } + } + + function addToBal(address[] memory receivers, uint256 index) internal { + balances[receivers[index]] += msg.value; + } +} + +contract MsgValueInLoop3 { + mapping(address => uint256) balances; + + function bad(address[] memory receivers) external payable { + // BAD while loop + uint256 i = 0; + while (i < receivers.length) { + addToBal(receivers, i); + i++; + } + } + + function addToBal(address[] memory receivers, uint256 index) internal { + balances[receivers[index]] += msg.value; + } +} + +contract MsgValueInLoop4 { + mapping(address => uint256) balances; + + function bad(address[] memory receivers) external payable { + // BAD do while loop + uint256 i = 0; + do { + addToBal(receivers, i); + i++; + } while (i < receivers.length); + } + + function addToBal(address[] memory receivers, uint256 index) internal { + balances[receivers[index]] += msg.value; + } +} From 4454c90ff8041354ed4c259e0b2b7689f5c35678 Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Fri, 2 Aug 2024 18:54:30 +0530 Subject: [PATCH 11/15] Detector: Bad use of `tx.origin` (#642) Co-authored-by: Alex Roan --- aderyn_core/src/detect/detector.rs | 5 + aderyn_core/src/detect/high/mod.rs | 2 + .../detect/high/tx_origin_used_for_auth.rs | 171 ++++++++++++++++++ .../adhoc-sol-files-highs-only-report.json | 1 + reports/report.json | 48 ++++- reports/report.md | 63 ++++++- reports/report.sarif | 64 +++++++ .../src/TxOriginUsedForAuth.sol | 62 +++++++ 8 files changed, 403 insertions(+), 13 deletions(-) create mode 100644 aderyn_core/src/detect/high/tx_origin_used_for_auth.rs create mode 100644 tests/contract-playground/src/TxOriginUsedForAuth.sol diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index 8c27cfaca..2f764df71 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -74,6 +74,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), ] @@ -146,6 +147,7 @@ pub(crate) enum IssueDetectorNamePool { WeakRandomness, PreDeclaredLocalVariableUsage, DeleteNestedMapping, + TxOriginUsedForAuth, MsgValueInLoop, ContractLocksEther, // NOTE: `Undecided` will be the default name (for new bots). @@ -306,6 +308,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::TxOriginUsedForAuth => { + Some(Box::::default()) + } IssueDetectorNamePool::MsgValueInLoop => Some(Box::::default()), IssueDetectorNamePool::ContractLocksEther => { Some(Box::::default()) diff --git a/aderyn_core/src/detect/high/mod.rs b/aderyn_core/src/detect/high/mod.rs index b33b9f716..782707503 100644 --- a/aderyn_core/src/detect/high/mod.rs +++ b/aderyn_core/src/detect/high/mod.rs @@ -26,6 +26,7 @@ pub(crate) mod storage_array_edit_with_memory; pub(crate) mod storage_signed_integer_array; pub(crate) mod tautological_compare; pub(crate) mod tautology_or_contradiction; +pub(crate) mod tx_origin_used_for_auth; pub(crate) mod unchecked_return; pub(crate) mod unchecked_send; pub(crate) mod uninitialized_state_variable; @@ -62,6 +63,7 @@ pub use storage_array_edit_with_memory::StorageArrayEditWithMemoryDetector; pub use storage_signed_integer_array::StorageSignedIntegerArrayDetector; pub use tautological_compare::TautologicalCompareDetector; pub use tautology_or_contradiction::TautologyOrContraditionDetector; +pub use tx_origin_used_for_auth::TxOriginUsedForAuthDetector; pub use unchecked_return::UncheckedReturnDetector; pub use unchecked_send::UncheckedSendDetector; pub use uninitialized_state_variable::UninitializedStateVariableDetector; diff --git a/aderyn_core/src/detect/high/tx_origin_used_for_auth.rs b/aderyn_core/src/detect/high/tx_origin_used_for_auth.rs new file mode 100644 index 000000000..f733de663 --- /dev/null +++ b/aderyn_core/src/detect/high/tx_origin_used_for_auth.rs @@ -0,0 +1,171 @@ +use std::collections::BTreeMap; +use std::error::Error; + +use crate::ast::{ASTNode, Expression, Identifier, NodeID}; + +use crate::capture; +use crate::context::browser::ExtractMemberAccesses; +use crate::context::investigator::{ + StandardInvestigationStyle, StandardInvestigator, StandardInvestigatorVisitor, +}; +use crate::detect::detector::IssueDetectorNamePool; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; +use eyre::Result; + +#[derive(Default)] +pub struct TxOriginUsedForAuthDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for TxOriginUsedForAuthDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + for if_statement in context.if_statements() { + // Check within the condition block only + let ast_node: ASTNode = if_statement.condition.clone().into(); + self.check_eligibility_and_capture(context, &[&ast_node], &(if_statement.into()))?; + } + + for function_call in context.function_calls() { + if let Expression::Identifier(Identifier { name, .. }) = + function_call.expression.as_ref() + { + if name != "require" { + continue; + } + + // Now, check for arguments of the `require(..., "message")` function call + let arguments = function_call + .arguments + .clone() + .into_iter() + .map(|n| n.into()) + .collect::>(); + + let ast_nodes: &[&ASTNode] = &(arguments.iter().collect::>()); + self.check_eligibility_and_capture(context, ast_nodes, &(function_call.into()))?; + } + } + + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::High + } + + fn title(&self) -> String { + String::from("Potential use of `tx.origin` for authentication.") + } + + fn description(&self) -> String { + String::from("Using `tx.origin` may lead to problems when users are interacting via smart contract with your \ + protocol. It is recommended to use `msg.sender` for authentication.") + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + format!("{}", IssueDetectorNamePool::TxOriginUsedForAuth) + } +} + +impl TxOriginUsedForAuthDetector { + fn check_eligibility_and_capture( + &mut self, + context: &WorkspaceContext, + check_nodes: &[&ASTNode], + capture_node: &ASTNode, + ) -> Result<(), Box> { + // Boilerplate + let mut tracker = MsgSenderAndTxOriginTracker::default(); + let investigator = StandardInvestigator::new( + context, + check_nodes, + StandardInvestigationStyle::Downstream, + )?; + investigator.investigate(context, &mut tracker)?; + + if tracker.satisifed() { + capture!(self, context, capture_node); + } + Ok(()) + } +} + +#[derive(Default)] +struct MsgSenderAndTxOriginTracker { + reads_msg_sender: bool, + reads_tx_origin: bool, +} + +impl MsgSenderAndTxOriginTracker { + /// To avoid FP (msg.sender == tx.origin) we require that tx.origin is present and msg.sender is absent + /// for it to be considered satisfied + fn satisifed(&self) -> bool { + self.reads_tx_origin && !self.reads_msg_sender + } +} + +impl StandardInvestigatorVisitor for MsgSenderAndTxOriginTracker { + fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> { + let member_accesses = ExtractMemberAccesses::from(node).extracted; + + let has_msg_sender = member_accesses.iter().any(|member_access| { + member_access.member_name == "sender" + && if let Expression::Identifier(identifier) = member_access.expression.as_ref() { + identifier.name == "msg" + } else { + false + } + }); + self.reads_msg_sender = self.reads_msg_sender || has_msg_sender; + + let has_tx_origin = member_accesses.iter().any(|member_access| { + member_access.member_name == "origin" + && if let Expression::Identifier(identifier) = member_access.expression.as_ref() { + identifier.name == "tx" + } else { + false + } + }); + self.reads_tx_origin = self.reads_tx_origin || has_tx_origin; + + Ok(()) + } +} + +#[cfg(test)] +mod tx_origin_used_for_auth_detector { + use serial_test::serial; + + use crate::detect::{ + detector::IssueDetector, high::tx_origin_used_for_auth::TxOriginUsedForAuthDetector, + }; + + #[test] + #[serial] + fn test_tx_origin_used_for_auth() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/TxOriginUsedForAuth.sol", + ); + + let mut detector = TxOriginUsedForAuthDetector::default(); + let found = detector.detect(&context).unwrap(); + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 3); + // assert the severity is high + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::High + ); + } +} diff --git a/reports/adhoc-sol-files-highs-only-report.json b/reports/adhoc-sol-files-highs-only-report.json index 6cf4758ea..ad6e9c156 100644 --- a/reports/adhoc-sol-files-highs-only-report.json +++ b/reports/adhoc-sol-files-highs-only-report.json @@ -195,6 +195,7 @@ "weak-randomness", "pre-declared-local-variable-usage", "delete-nested-mapping", + "tx-origin-used-for-auth", "msg-value-in-loop", "contract-locks-ether" ] diff --git a/reports/report.json b/reports/report.json index 60aa6c2e3..0eb69bb87 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 76, - "total_sloc": 2183 + "total_source_units": 77, + "total_sloc": 2225 }, "files_details": { "files_details": [ @@ -193,6 +193,10 @@ "file_path": "src/TestERC20.sol", "n_sloc": 62 }, + { + "file_path": "src/TxOriginUsedForAuth.sol", + "n_sloc": 42 + }, { "file_path": "src/UncheckedReturn.sol", "n_sloc": 33 @@ -312,7 +316,7 @@ ] }, "issue_count": { - "high": 35, + "high": 36, "low": 25 }, "high_issues": { @@ -1850,6 +1854,31 @@ } ] }, + { + "title": "Potential use of `tx.origin` for authentication.", + "description": "Using `tx.origin` may lead to problems when users are interacting via smart contract with your protocol. It is recommended to use `msg.sender` for authentication.", + "detector_name": "tx-origin-used-for-auth", + "instances": [ + { + "contract_path": "src/TxOriginUsedForAuth.sol", + "line_no": 40, + "src": "1117:183", + "src_char": "1117:183" + }, + { + "contract_path": "src/TxOriginUsedForAuth.sol", + "line_no": 51, + "src": "1431:90", + "src_char": "1431:90" + }, + { + "contract_path": "src/TxOriginUsedForAuth.sol", + "line_no": 59, + "src": "1610:68", + "src_char": "1610:68" + } + ] + }, { "title": "Loop contains `msg.value`.", "description": "Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`.", @@ -2250,6 +2279,12 @@ "src": "32:23", "src_char": "32:23" }, + { + "contract_path": "src/TxOriginUsedForAuth.sol", + "line_no": 2, + "src": "32:24", + "src_char": "32:24" + }, { "contract_path": "src/UncheckedSend.sol", "line_no": 2, @@ -3128,6 +3163,12 @@ "src": "32:23", "src_char": "32:23" }, + { + "contract_path": "src/TxOriginUsedForAuth.sol", + "line_no": 2, + "src": "32:24", + "src_char": "32:24" + }, { "contract_path": "src/UnsafeERC721Mint.sol", "line_no": 2, @@ -4060,6 +4101,7 @@ "weak-randomness", "pre-declared-local-variable-usage", "delete-nested-mapping", + "tx-origin-used-for-auth", "msg-value-in-loop", "contract-locks-ether" ] diff --git a/reports/report.md b/reports/report.md index ad62f5932..9ef473456 100644 --- a/reports/report.md +++ b/reports/report.md @@ -41,8 +41,9 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [H-31: Weak Randomness](#h-31-weak-randomness) - [H-32: Usage of variable before declaration.](#h-32-usage-of-variable-before-declaration) - [H-33: Deletion from a nested mappping.](#h-33-deletion-from-a-nested-mappping) - - [H-34: Loop contains `msg.value`.](#h-34-loop-contains-msgvalue) - - [H-35: Contract locks Ether without a withdraw function.](#h-35-contract-locks-ether-without-a-withdraw-function) + - [H-34: Potential use of `tx.origin` for authentication.](#h-34-potential-use-of-txorigin-for-authentication) + - [H-35: Loop contains `msg.value`.](#h-35-loop-contains-msgvalue) + - [H-36: Contract locks Ether without a withdraw function.](#h-36-contract-locks-ether-without-a-withdraw-function) - [Low Issues](#low-issues) - [L-1: Centralization Risk for trusted owners](#l-1-centralization-risk-for-trusted-owners) - [L-2: Solmate's SafeTransferLib does not check for token contract's existence](#l-2-solmates-safetransferlib-does-not-check-for-token-contracts-existence) @@ -77,8 +78,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 76 | -| Total nSLOC | 2183 | +| .sol Files | 77 | +| Total nSLOC | 2225 | ## Files Details @@ -132,6 +133,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/TautologicalCompare.sol | 17 | | src/TautologyOrContradiction.sol | 11 | | src/TestERC20.sol | 62 | +| src/TxOriginUsedForAuth.sol | 42 | | src/UncheckedReturn.sol | 33 | | src/UncheckedSend.sol | 18 | | src/UninitializedStateVariable.sol | 29 | @@ -161,14 +163,14 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **2183** | +| **Total** | **2225** | ## Issue Summary | Category | No. of Issues | | --- | --- | -| High | 35 | +| High | 36 | | Low | 25 | @@ -1841,7 +1843,36 @@ A deletion in a structure containing a mapping will not delete the mapping. The -## H-34: Loop contains `msg.value`. +## H-34: Potential use of `tx.origin` for authentication. + +Using `tx.origin` may lead to problems when users are interacting via smart contract with your protocol. It is recommended to use `msg.sender` for authentication. + +
3 Found Instances + + +- Found in src/TxOriginUsedForAuth.sol [Line: 40](../tests/contract-playground/src/TxOriginUsedForAuth.sol#L40) + + ```solidity + if (tx.origin == owner) { + ``` + +- Found in src/TxOriginUsedForAuth.sol [Line: 51](../tests/contract-playground/src/TxOriginUsedForAuth.sol#L51) + + ```solidity + if (tx.origin == owner || authorizedUsers[tx.origin]) { + ``` + +- Found in src/TxOriginUsedForAuth.sol [Line: 59](../tests/contract-playground/src/TxOriginUsedForAuth.sol#L59) + + ```solidity + require(tx.origin == owner, "Not authorized to perform this action"); + ``` + +
+ + + +## H-35: Loop contains `msg.value`. Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`. @@ -1876,7 +1907,7 @@ Provide an explicit array of amounts alongside the receivers array, and check th -## H-35: Contract locks Ether without a withdraw function. +## H-36: Contract locks Ether without a withdraw function. It appears that the contract includes a payable function to accept Ether but lacks a corresponding function to withdraw it, which leads to the Ether being locked in the contract. To resolve this issue, please implement a public or external function that allows for the withdrawal of Ether from the contract. @@ -2185,7 +2216,7 @@ ERC20 functions may not behave as expected. For example: return values are not a Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;` -
22 Found Instances +
23 Found Instances - Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 2](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L2) @@ -2272,6 +2303,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid pragma solidity ^0.5.0; ``` +- Found in src/TxOriginUsedForAuth.sol [Line: 2](../tests/contract-playground/src/TxOriginUsedForAuth.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + - Found in src/UncheckedSend.sol [Line: 2](../tests/contract-playground/src/UncheckedSend.sol#L2) ```solidity @@ -3071,7 +3108,7 @@ Using `ERC721::_mint()` can mint ERC721 tokens to addresses which don't support Solc compiler version 0.8.20 switches the default target EVM version to Shanghai, which means that the generated bytecode will include PUSH0 opcodes. Be sure to select the appropriate EVM version in case you intend to deploy on a chain other than mainnet like L2 chains that may not support PUSH0, otherwise deployment of your contracts will fail. -
30 Found Instances +
31 Found Instances - Found in src/AdminContract.sol [Line: 2](../tests/contract-playground/src/AdminContract.sol#L2) @@ -3170,6 +3207,12 @@ Solc compiler version 0.8.20 switches the default target EVM version to Shanghai pragma solidity 0.8.20; ``` +- Found in src/TxOriginUsedForAuth.sol [Line: 2](../tests/contract-playground/src/TxOriginUsedForAuth.sol#L2) + + ```solidity + pragma solidity ^0.8.20; + ``` + - Found in src/UnsafeERC721Mint.sol [Line: 2](../tests/contract-playground/src/UnsafeERC721Mint.sol#L2) ```solidity diff --git a/reports/report.sarif b/reports/report.sarif index 9f48e2311..240ed9ec5 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -2688,6 +2688,48 @@ }, "ruleId": "delete-nested-mapping" }, + { + "level": "warning", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TxOriginUsedForAuth.sol" + }, + "region": { + "byteLength": 183, + "byteOffset": 1117 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TxOriginUsedForAuth.sol" + }, + "region": { + "byteLength": 90, + "byteOffset": 1431 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TxOriginUsedForAuth.sol" + }, + "region": { + "byteLength": 68, + "byteOffset": 1610 + } + } + } + ], + "message": { + "text": "Using `tx.origin` may lead to problems when users are interacting via smart contract with your protocol. It is recommended to use `msg.sender` for authentication." + }, + "ruleId": "tx-origin-used-for-auth" + }, { "level": "warning", "locations": [ @@ -3381,6 +3423,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TxOriginUsedForAuth.sol" + }, + "region": { + "byteLength": 24, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -4960,6 +5013,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/TxOriginUsedForAuth.sol" + }, + "region": { + "byteLength": 24, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { diff --git a/tests/contract-playground/src/TxOriginUsedForAuth.sol b/tests/contract-playground/src/TxOriginUsedForAuth.sol new file mode 100644 index 000000000..0be0f8e02 --- /dev/null +++ b/tests/contract-playground/src/TxOriginUsedForAuth.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract VulnerableContract { + address public owner; + mapping(address => bool) public authorizedUsers; + + event Authenticated(address indexed user); + event UnauthorizedAccess(address indexed user); + + constructor() { + owner = msg.sender; + } + + // GOOD + function authorizeUser(address _user) external { + require(msg.sender == owner, "Only owner can authorize users"); + authorizedUsers[_user] = true; + } + + // GOOD + function revokeUser(address _user) external { + require(msg.sender == owner, "Only owner can revoke users"); + authorizedUsers[_user] = false; + } + + // GOOD + function edgeCase() external { + // This uses both `tx.origin` as well as `msg.sender` so, it's OK (Same heuristic as slither) + require( + tx.origin == msg.sender && msg.sender == owner, + "Not authorized to perform this action" + ); + emit Authenticated(msg.sender); + } + + // BAD + function secureAction() external { + // Vulnerable use of tx.origin + if (tx.origin == owner) { + emit Authenticated(msg.sender); + } else { + emit UnauthorizedAccess(msg.sender); + revert("Not authorized"); + } + } + + // BAD + function checkAuthorization() external view returns (bool) { + // Vulnerable use of tx.origin + if (tx.origin == owner || authorizedUsers[tx.origin]) { + return true; + } + return false; + } + + // BAD + function performAction() external { + require(tx.origin == owner, "Not authorized to perform this action"); + emit Authenticated(msg.sender); + } +} From dcefbeab7041fbdd530dd30dd53bc969751ecd12 Mon Sep 17 00:00:00 2001 From: Alex Roan Date: Mon, 5 Aug 2024 10:03:41 +0100 Subject: [PATCH 12/15] Check Github releases for upgraded version, instead of crates.io (#647) --- aderyn/README.md | 5 +++++ aderyn/src/lib.rs | 9 +++------ aderyn/src/main.rs | 4 +--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/aderyn/README.md b/aderyn/README.md index 2894e358a..43a3c4e9b 100644 --- a/aderyn/README.md +++ b/aderyn/README.md @@ -1,4 +1,9 @@ +> ⚠️ **Installing via crates is no longer fully supported. `cyfrinup` is the preferred installation method.**. +> +> For the best experience, please remove the legacy crate installation by running `cargo uninstall aderyn`, and use `cyfrinup` instead. +> +> Full install instructions are [here](#installation).


diff --git a/aderyn/src/lib.rs b/aderyn/src/lib.rs index 44d3fdc19..8d9f4b45b 100644 --- a/aderyn/src/lib.rs +++ b/aderyn/src/lib.rs @@ -114,15 +114,12 @@ pub fn aderyn_is_currently_running_newest_version() -> Result()?; - - let newest_version = data["crates"][0]["newest_version"].to_string(); - let newest_version = &newest_version[1..newest_version.len() - 1]; - - let newest = Version::parse(newest_version).unwrap(); + let newest = + Version::parse(data["tag_name"].as_str().unwrap().replace("v", "").as_str()).unwrap(); let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); Ok(current >= newest) diff --git a/aderyn/src/main.rs b/aderyn/src/main.rs index 4b808a120..48927d716 100644 --- a/aderyn/src/main.rs +++ b/aderyn/src/main.rs @@ -151,9 +151,7 @@ fn main() { if let Ok(yes) = aderyn_is_currently_running_newest_version() { if !yes { println!(); - println!( - "NEW VERSION OF ADERYN AVAILABLE! Please run `cargo install aderyn` to fully upgrade the current version" - ); + println!("NEW VERSION OF ADERYN AVAILABLE! Please run `cyfrinup` to upgrade."); } } } From 155a9d6ca2c6f9e2b4fc36f7e547ff2d109a48ca Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Mon, 5 Aug 2024 14:35:50 +0530 Subject: [PATCH 13/15] Detector: Boolean equality (#633) --- aderyn_core/src/detect/detector.rs | 3 + .../src/detect/low/boolean_equality.rs | 50 ++++++++ aderyn_core/src/detect/low/mod.rs | 2 + reports/report.json | 78 +++++++++++- reports/report.md | 83 +++++++++++- reports/report.sarif | 119 ++++++++++++++++++ reports/templegold-report.md | 26 +++- .../src/BooleanEquality.sol | 31 +++++ 8 files changed, 383 insertions(+), 9 deletions(-) create mode 100644 aderyn_core/src/detect/low/boolean_equality.rs create mode 100644 tests/contract-playground/src/BooleanEquality.sol diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index 2f764df71..7c55aa3c9 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -74,6 +74,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -147,6 +148,7 @@ pub(crate) enum IssueDetectorNamePool { WeakRandomness, PreDeclaredLocalVariableUsage, DeleteNestedMapping, + BooleanEquality, TxOriginUsedForAuth, MsgValueInLoop, ContractLocksEther, @@ -308,6 +310,7 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::BooleanEquality => Some(Box::::default()), IssueDetectorNamePool::TxOriginUsedForAuth => { Some(Box::::default()) } diff --git a/aderyn_core/src/detect/low/boolean_equality.rs b/aderyn_core/src/detect/low/boolean_equality.rs new file mode 100644 index 000000000..e967d933c --- /dev/null +++ b/aderyn_core/src/detect/low/boolean_equality.rs @@ -0,0 +1,50 @@ +use crate::detect::helpers::is_constant_boolean; +use crate::issue_detector; +use eyre::Result; + +issue_detector! { + BooleanEqualityDetector; + + severity: Low, + title: "Boolean equality is not required.", + desc: "If `x` is a boolean, there is no need to do `if(x == true)` or `if(x == false)`. Just use `if(x)` and `if(!x)` respectively.", + name: BooleanEquality, + + |context| { + for binary_operation in context.binary_operations() { + if binary_operation.operator == "==" + && [ + binary_operation.left_expression.as_ref(), + binary_operation.right_expression.as_ref(), + ] + .iter() + .any(|&operand| is_constant_boolean(context, operand)) + { + grab!(binary_operation); + } + } + } + +} + +#[cfg(test)] +mod boolean_equality_tests { + use serial_test::serial; + + use crate::detect::{detector::IssueDetector, low::boolean_equality::BooleanEqualityDetector}; + + #[test] + #[serial] + fn test_boolean_equality_by_loading_contract_directly() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/BooleanEquality.sol", + ); + + let mut detector = BooleanEqualityDetector::default(); + let found = detector.detect(&context).unwrap(); + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 4); + } +} diff --git a/aderyn_core/src/detect/low/mod.rs b/aderyn_core/src/detect/low/mod.rs index aeef32d20..c0aadcf8a 100644 --- a/aderyn_core/src/detect/low/mod.rs +++ b/aderyn_core/src/detect/low/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod boolean_equality; pub(crate) mod centralization_risk; pub(crate) mod constants_instead_of_literals; pub(crate) mod contracts_with_todos; @@ -24,6 +25,7 @@ pub(crate) mod useless_modifier; pub(crate) mod useless_public_function; pub(crate) mod zero_address_check; +pub use boolean_equality::BooleanEqualityDetector; pub use centralization_risk::CentralizationRiskDetector; pub use constants_instead_of_literals::ConstantsInsteadOfLiteralsDetector; pub use contracts_with_todos::ContractsWithTodosDetector; diff --git a/reports/report.json b/reports/report.json index 0eb69bb87..b9f9d63f8 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 77, - "total_sloc": 2225 + "total_source_units": 78, + "total_sloc": 2252 }, "files_details": { "files_details": [ @@ -21,6 +21,10 @@ "file_path": "src/AssemblyExample.sol", "n_sloc": 9 }, + { + "file_path": "src/BooleanEquality.sol", + "n_sloc": 27 + }, { "file_path": "src/CallGraphTests.sol", "n_sloc": 49 @@ -317,7 +321,7 @@ }, "issue_count": { "high": 36, - "low": 25 + "low": 26 }, "high_issues": { "issues": [ @@ -2564,6 +2568,42 @@ "description": "If the same constant literal value is used multiple times, create a constant state variable and reference it throughout the contract.", "detector_name": "constants-instead-of-literals", "instances": [ + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 6, + "src": "170:3", + "src_char": "170:3" + }, + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 13, + "src": "330:3", + "src_char": "330:3" + }, + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 15, + "src": "360:3", + "src_char": "360:3" + }, + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 20, + "src": "492:3", + "src_char": "492:3" + }, + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 27, + "src": "653:3", + "src_char": "653:3" + }, + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 29, + "src": "683:3", + "src_char": "683:3" + }, { "contract_path": "src/Casting.sol", "line_no": 16, @@ -4039,6 +4079,37 @@ "src_char": "1175:14" } ] + }, + { + "title": "Boolean equality is not required.", + "description": "If `x` is a boolean, there is no need to do `if(x == true)` or `if(x == false)`. Just use `if(x)` and `if(!x)` respectively.", + "detector_name": "boolean-equality", + "instances": [ + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 5, + "src": "133:14", + "src_char": "133:14" + }, + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 12, + "src": "292:15", + "src_char": "292:15" + }, + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 19, + "src": "454:15", + "src_char": "454:15" + }, + { + "contract_path": "src/BooleanEquality.sol", + "line_no": 26, + "src": "614:16", + "src_char": "614:16" + } + ] } ] }, @@ -4101,6 +4172,7 @@ "weak-randomness", "pre-declared-local-variable-usage", "delete-nested-mapping", + "boolean-equality", "tx-origin-used-for-auth", "msg-value-in-loop", "contract-locks-ether" diff --git a/reports/report.md b/reports/report.md index 9ef473456..b856fd2a7 100644 --- a/reports/report.md +++ b/reports/report.md @@ -70,6 +70,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [L-23: Incorrect Order of Division and Multiplication](#l-23-incorrect-order-of-division-and-multiplication) - [L-24: Redundant statements have no effect.](#l-24-redundant-statements-have-no-effect) - [L-25: Public variables of a contract read in an external context (using `this`).](#l-25-public-variables-of-a-contract-read-in-an-external-context-using-this) + - [L-26: Boolean equality is not required.](#l-26-boolean-equality-is-not-required) # Summary @@ -78,8 +79,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 77 | -| Total nSLOC | 2225 | +| .sol Files | 78 | +| Total nSLOC | 2252 | ## Files Details @@ -90,6 +91,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/AdminContract.sol | 11 | | src/ArbitraryTransferFrom.sol | 37 | | src/AssemblyExample.sol | 9 | +| src/BooleanEquality.sol | 27 | | src/CallGraphTests.sol | 49 | | src/Casting.sol | 126 | | src/CompilerBugStorageSignedIntegerArray.sol | 13 | @@ -163,7 +165,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **2225** | +| **Total** | **2252** | ## Issue Summary @@ -171,7 +173,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Category | No. of Issues | | --- | --- | | High | 36 | -| Low | 25 | +| Low | 26 | # High Issues @@ -2597,9 +2599,45 @@ Instead of marking a function as `public`, consider marking it as `external` if If the same constant literal value is used multiple times, create a constant state variable and reference it throughout the contract. -

38 Found Instances +
44 Found Instances +- Found in src/BooleanEquality.sol [Line: 6](../tests/contract-playground/src/BooleanEquality.sol#L6) + + ```solidity + return 100; + ``` + +- Found in src/BooleanEquality.sol [Line: 13](../tests/contract-playground/src/BooleanEquality.sol#L13) + + ```solidity + return 200; + ``` + +- Found in src/BooleanEquality.sol [Line: 15](../tests/contract-playground/src/BooleanEquality.sol#L15) + + ```solidity + return 130; + ``` + +- Found in src/BooleanEquality.sol [Line: 20](../tests/contract-playground/src/BooleanEquality.sol#L20) + + ```solidity + return 100; + ``` + +- Found in src/BooleanEquality.sol [Line: 27](../tests/contract-playground/src/BooleanEquality.sol#L27) + + ```solidity + return 200; + ``` + +- Found in src/BooleanEquality.sol [Line: 29](../tests/contract-playground/src/BooleanEquality.sol#L29) + + ```solidity + return 130; + ``` + - Found in src/Casting.sol [Line: 16](../tests/contract-playground/src/Casting.sol#L16) ```solidity @@ -4130,3 +4168,38 @@ The contract reads it's own variable using `this` which adds an unnecessary STAT +## L-26: Boolean equality is not required. + +If `x` is a boolean, there is no need to do `if(x == true)` or `if(x == false)`. Just use `if(x)` and `if(!x)` respectively. + +
4 Found Instances + + +- Found in src/BooleanEquality.sol [Line: 5](../tests/contract-playground/src/BooleanEquality.sol#L5) + + ```solidity + if (isEven == true) { + ``` + +- Found in src/BooleanEquality.sol [Line: 12](../tests/contract-playground/src/BooleanEquality.sol#L12) + + ```solidity + if (isEven == !true) { + ``` + +- Found in src/BooleanEquality.sol [Line: 19](../tests/contract-playground/src/BooleanEquality.sol#L19) + + ```solidity + if (isEven == false) { + ``` + +- Found in src/BooleanEquality.sol [Line: 26](../tests/contract-playground/src/BooleanEquality.sol#L26) + + ```solidity + if (isEven == !false) { + ``` + +
+ + + diff --git a/reports/report.sarif b/reports/report.sarif index 240ed9ec5..a9df86e6b 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -3934,6 +3934,72 @@ { "level": "note", "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 3, + "byteOffset": 170 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 3, + "byteOffset": 330 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 3, + "byteOffset": 360 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 3, + "byteOffset": 492 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 3, + "byteOffset": 653 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 3, + "byteOffset": 683 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -6580,6 +6646,59 @@ "text": "The contract reads it's own variable using `this` which adds an unnecessary STATICCALL. Remove `this` and access the variable like storage." }, "ruleId": "public-variable-read-in-external-context" + }, + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 14, + "byteOffset": 133 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 15, + "byteOffset": 292 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 15, + "byteOffset": 454 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/BooleanEquality.sol" + }, + "region": { + "byteLength": 16, + "byteOffset": 614 + } + } + } + ], + "message": { + "text": "If `x` is a boolean, there is no need to do `if(x == true)` or `if(x == false)`. Just use `if(x)` and `if(!x)` respectively." + }, + "ruleId": "boolean-equality" } ], "tool": { diff --git a/reports/templegold-report.md b/reports/templegold-report.md index 278751712..bb3f1c2ac 100644 --- a/reports/templegold-report.md +++ b/reports/templegold-report.md @@ -38,6 +38,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [L-17: Loop contains `require`/`revert` statements](#l-17-loop-contains-requirerevert-statements) - [L-18: Incorrect Order of Division and Multiplication](#l-18-incorrect-order-of-division-and-multiplication) - [L-19: Redundant statements have no effect.](#l-19-redundant-statements-have-no-effect) + - [L-20: Boolean equality is not required.](#l-20-boolean-equality-is-not-required) # Summary @@ -191,7 +192,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Category | No. of Issues | | --- | --- | | High | 10 | -| Low | 19 | +| Low | 20 | # High Issues @@ -8653,3 +8654,26 @@ Remove the redundant statements because no code will be generated and it just co +## L-20: Boolean equality is not required. + +If `x` is a boolean, there is no need to do `if(x == true)` or `if(x == false)`. Just use `if(x)` and `if(!x)` respectively. + +
2 Found Instances + + +- Found in contracts/deprecated/Faith.sol [Line: 32](../tests/2024-07-templegold/protocol/contracts/deprecated/Faith.sol#L32) + + ```solidity + require(canManageFaith[msg.sender] == true, "Faith: caller cannot manage faith"); + ``` + +- Found in contracts/deprecated/Faith.sol [Line: 40](../tests/2024-07-templegold/protocol/contracts/deprecated/Faith.sol#L40) + + ```solidity + require(canManageFaith[msg.sender] == true, "Faith: caller cannot manage faith"); + ``` + +
+ + + diff --git a/tests/contract-playground/src/BooleanEquality.sol b/tests/contract-playground/src/BooleanEquality.sol new file mode 100644 index 000000000..c8e8bd646 --- /dev/null +++ b/tests/contract-playground/src/BooleanEquality.sol @@ -0,0 +1,31 @@ +pragma solidity 0.4.22; + +contract BooleanEquality { + function badCheck(bool isEven) external pure returns (uint256) { + if (isEven == true) { + return 100; + } + return 0; + } + + function badCheck2(bool isEven) external pure returns (uint256) { + if (isEven == !true) { + return 200; + } + return 130; + } + + function badCheck3(bool isEven) external pure returns (uint256) { + if (isEven == false) { + return 100; + } + return 0; + } + + function badCheck4(bool isEven) external pure returns (uint256) { + if (isEven == !false) { + return 200; + } + return 130; + } +} From ada7f0fd5cd1844f86ae6994a6a87c3604017fc0 Mon Sep 17 00:00:00 2001 From: Tilak Madichetti Date: Mon, 5 Aug 2024 14:49:15 +0530 Subject: [PATCH 14/15] Detector: constant functions contains assembly (#641) Co-authored-by: Alex Roan --- aderyn_core/src/detect/detector.rs | 5 + .../src/detect/low/constant_funcs_assembly.rs | 166 ++++++++++++++++++ aderyn_core/src/detect/low/mod.rs | 2 + cli/reportgen.sh | 6 +- reports/report.json | 54 +++++- reports/report.md | 67 ++++++- reports/report.sarif | 75 ++++++++ .../src/ConstantFuncsAssembly.sol | 39 ++++ 8 files changed, 399 insertions(+), 15 deletions(-) create mode 100644 aderyn_core/src/detect/low/constant_funcs_assembly.rs create mode 100644 tests/contract-playground/src/ConstantFuncsAssembly.sol diff --git a/aderyn_core/src/detect/detector.rs b/aderyn_core/src/detect/detector.rs index 7c55aa3c9..74305116a 100644 --- a/aderyn_core/src/detect/detector.rs +++ b/aderyn_core/src/detect/detector.rs @@ -74,6 +74,7 @@ pub fn get_all_issue_detectors() -> Vec> { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -148,6 +149,7 @@ pub(crate) enum IssueDetectorNamePool { WeakRandomness, PreDeclaredLocalVariableUsage, DeleteNestedMapping, + ConstantFunctionsAssembly, BooleanEquality, TxOriginUsedForAuth, MsgValueInLoop, @@ -310,6 +312,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option { Some(Box::::default()) } + IssueDetectorNamePool::ConstantFunctionsAssembly => { + Some(Box::::default()) + } IssueDetectorNamePool::BooleanEquality => Some(Box::::default()), IssueDetectorNamePool::TxOriginUsedForAuth => { Some(Box::::default()) diff --git a/aderyn_core/src/detect/low/constant_funcs_assembly.rs b/aderyn_core/src/detect/low/constant_funcs_assembly.rs new file mode 100644 index 000000000..59b1f4c18 --- /dev/null +++ b/aderyn_core/src/detect/low/constant_funcs_assembly.rs @@ -0,0 +1,166 @@ +use std::collections::BTreeMap; +use std::error::Error; +use std::str::FromStr; + +use crate::ast::{ASTNode, NodeID, NodeType, StateMutability}; + +use crate::capture; +use crate::context::browser::{ + ExtractInlineAssemblys, ExtractPragmaDirectives, GetClosestAncestorOfTypeX, +}; +use crate::context::investigator::{ + StandardInvestigationStyle, StandardInvestigator, StandardInvestigatorVisitor, +}; +use crate::detect::detector::IssueDetectorNamePool; +use crate::detect::helpers::{self, pragma_directive_to_semver}; +use crate::{ + context::workspace_context::WorkspaceContext, + detect::detector::{IssueDetector, IssueSeverity}, +}; +use eyre::Result; +use semver::{Version, VersionReq}; + +#[derive(Default)] +pub struct ConstantFunctionContainsAssemblyDetector { + // Keys are: [0] source file name, [1] line number, [2] character location of node. + // Do not add items manually, use `capture!` to add nodes to this BTreeMap. + found_instances: BTreeMap<(String, usize, String), NodeID>, +} + +impl IssueDetector for ConstantFunctionContainsAssemblyDetector { + fn detect(&mut self, context: &WorkspaceContext) -> Result> { + for function in helpers::get_implemented_external_and_public_functions(context) { + // First, check the eligibility for this function by checking + if let Some(ASTNode::SourceUnit(source_unit)) = + function.closest_ancestor_of_type(context, NodeType::SourceUnit) + { + // Store the extracted directives in a variable to extend its lifetime + let extracted_directives = ExtractPragmaDirectives::from(source_unit).extracted; + let pragma_directive = extracted_directives.first(); + + if let Some(pragma_directive) = pragma_directive { + let version_req = pragma_directive_to_semver(pragma_directive); + if let Ok(version_req) = version_req { + if version_req_allows_below_0_5_0(&version_req) { + // Only run the logic if pragma is allowed to run on solc <0.5.0 + + if function.state_mutability() == &StateMutability::View + || function.state_mutability() == &StateMutability::Pure + { + let mut tracker = AssemblyTracker { + has_assembly: false, + }; + let investigator = StandardInvestigator::new( + context, + &[&(function.into())], + StandardInvestigationStyle::Downstream, + )?; + investigator.investigate(context, &mut tracker)?; + + if tracker.has_assembly { + capture!(self, context, function); + } + } + } + } + } + } + } + + Ok(!self.found_instances.is_empty()) + } + + fn severity(&self) -> IssueSeverity { + IssueSeverity::Low + } + + fn title(&self) -> String { + String::from("Functions declared `pure` / `view` but contains assembly") + } + + fn description(&self) -> String { + String::from("If the assembly code contains bugs or unintended side effects, it can lead to incorrect results \ + or vulnerabilities, which are hard to debug and resolve, especially when the function is meant to be simple \ + and predictable.") + } + + fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> { + self.found_instances.clone() + } + + fn name(&self) -> String { + format!("{}", IssueDetectorNamePool::ConstantFunctionsAssembly) + } +} + +fn version_req_allows_below_0_5_0(version_req: &VersionReq) -> bool { + // If it matches any 0.4.0 to 0.4.26, return true + for i in 0..=26 { + let version: semver::Version = Version::from_str(&format!("0.4.{}", i)).unwrap(); + if version_req.matches(&version) { + return true; + } + } + + // Else, return false + false +} + +struct AssemblyTracker { + has_assembly: bool, +} + +impl StandardInvestigatorVisitor for AssemblyTracker { + fn visit_any(&mut self, node: &crate::ast::ASTNode) -> eyre::Result<()> { + // If we are already satisifed, do not bother checking + if self.has_assembly { + return Ok(()); + } + + if let ASTNode::FunctionDefinition(function) = node { + // Ignore checking functions that start with `_` + // Example - templegold contains math functions like `_rpow()`, etc that are used by view functions + // That should be okay .. I guess? (idk ... it's open for dicussion) + if function.name.starts_with("_") { + return Ok(()); + } + } + + // Check if this node has assembly code + let assemblies = ExtractInlineAssemblys::from(node).extracted; + if !assemblies.is_empty() { + self.has_assembly = true; + } + Ok(()) + } +} + +#[cfg(test)] +mod constant_functions_assembly_detector { + use serial_test::serial; + + use crate::detect::{ + detector::IssueDetector, + low::constant_funcs_assembly::ConstantFunctionContainsAssemblyDetector, + }; + + #[test] + #[serial] + fn test_constant_functions_assembly() { + let context = crate::detect::test_utils::load_solidity_source_unit( + "../tests/contract-playground/src/ConstantFuncsAssembly.sol", + ); + + let mut detector = ConstantFunctionContainsAssemblyDetector::default(); + let found = detector.detect(&context).unwrap(); + // assert that the detector found an issue + assert!(found); + // assert that the detector found the correct number of instances + assert_eq!(detector.instances().len(), 3); + // assert the severity is low + assert_eq!( + detector.severity(), + crate::detect::detector::IssueSeverity::Low + ); + } +} diff --git a/aderyn_core/src/detect/low/mod.rs b/aderyn_core/src/detect/low/mod.rs index c0aadcf8a..7d7663e41 100644 --- a/aderyn_core/src/detect/low/mod.rs +++ b/aderyn_core/src/detect/low/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod boolean_equality; pub(crate) mod centralization_risk; +pub(crate) mod constant_funcs_assembly; pub(crate) mod constants_instead_of_literals; pub(crate) mod contracts_with_todos; pub(crate) mod deprecated_oz_functions; @@ -27,6 +28,7 @@ pub(crate) mod zero_address_check; pub use boolean_equality::BooleanEqualityDetector; pub use centralization_risk::CentralizationRiskDetector; +pub use constant_funcs_assembly::ConstantFunctionContainsAssemblyDetector; pub use constants_instead_of_literals::ConstantsInsteadOfLiteralsDetector; pub use contracts_with_todos::ContractsWithTodosDetector; pub use deprecated_oz_functions::DeprecatedOZFunctionsDetector; diff --git a/cli/reportgen.sh b/cli/reportgen.sh index 28e7deca1..90afc66f2 100755 --- a/cli/reportgen.sh +++ b/cli/reportgen.sh @@ -2,10 +2,10 @@ #### MARKDOWN REPORTS ###### -# Basic report.md +# Basic report.md cargo run -- -i src/ -x lib/ ./tests/contract-playground -o ./reports/report.md --skip-update-check & -# Adhoc sol files report.md +# Adhoc sol files report.md cargo run -- ./tests/adhoc-sol-files -o ./reports/adhoc-sol-files-report.md --skip-update-check & # Aderyn.toml with nested root @@ -41,4 +41,4 @@ cargo run -- ./tests/adhoc-sol-files -o ./reports/adhoc-sol-files-highs-only-re # Basic report.sarif cargo run -- ./tests/contract-playground -o ./reports/report.sarif --skip-update-check & -wait \ No newline at end of file +wait diff --git a/reports/report.json b/reports/report.json index b9f9d63f8..f2eda5e64 100644 --- a/reports/report.json +++ b/reports/report.json @@ -1,7 +1,7 @@ { "files_summary": { - "total_source_units": 78, - "total_sloc": 2252 + "total_source_units": 79, + "total_sloc": 2278 }, "files_details": { "files_details": [ @@ -37,6 +37,10 @@ "file_path": "src/CompilerBugStorageSignedIntegerArray.sol", "n_sloc": 13 }, + { + "file_path": "src/ConstantFuncsAssembly.sol", + "n_sloc": 26 + }, { "file_path": "src/ConstantsLiterals.sol", "n_sloc": 28 @@ -321,7 +325,7 @@ }, "issue_count": { "high": 36, - "low": 26 + "low": 27 }, "high_issues": { "issues": [ @@ -1287,6 +1291,12 @@ "src": "97:1", "src_char": "97:1" }, + { + "contract_path": "src/ConstantFuncsAssembly.sol", + "line_no": 6, + "src": "110:20", + "src_char": "110:20" + }, { "contract_path": "src/DelegateCallWithoutAddressCheck.sol", "line_no": 9, @@ -2205,6 +2215,12 @@ "src": "32:23", "src_char": "32:23" }, + { + "contract_path": "src/ConstantFuncsAssembly.sol", + "line_no": 2, + "src": "32:23", + "src_char": "32:23" + }, { "contract_path": "src/ContractLocksEther.sol", "line_no": 2, @@ -3711,6 +3727,12 @@ "src": "1206:18", "src_char": "1206:18" }, + { + "contract_path": "src/ConstantFuncsAssembly.sol", + "line_no": 26, + "src": "651:232", + "src_char": "651:232" + }, { "contract_path": "src/InternalFunctions.sol", "line_no": 28, @@ -4080,6 +4102,31 @@ } ] }, + { + "title": "Functions declared `pure` / `view` but contains assembly", + "description": "If the assembly code contains bugs or unintended side effects, it can lead to incorrect results or vulnerabilities, which are hard to debug and resolve, especially when the function is meant to be simple and predictable.", + "detector_name": "constant-functions-assembly", + "instances": [ + { + "contract_path": "src/ConstantFuncsAssembly.sol", + "line_no": 9, + "src": "182:175", + "src_char": "182:175" + }, + { + "contract_path": "src/ConstantFuncsAssembly.sol", + "line_no": 17, + "src": "408:237", + "src_char": "408:237" + }, + { + "contract_path": "src/ConstantFuncsAssembly.sol", + "line_no": 36, + "src": "934:98", + "src_char": "934:98" + } + ] + }, { "title": "Boolean equality is not required.", "description": "If `x` is a boolean, there is no need to do `if(x == true)` or `if(x == false)`. Just use `if(x)` and `if(!x)` respectively.", @@ -4172,6 +4219,7 @@ "weak-randomness", "pre-declared-local-variable-usage", "delete-nested-mapping", + "constant-functions-assembly", "boolean-equality", "tx-origin-used-for-auth", "msg-value-in-loop", diff --git a/reports/report.md b/reports/report.md index b856fd2a7..bf625ccd5 100644 --- a/reports/report.md +++ b/reports/report.md @@ -70,7 +70,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati - [L-23: Incorrect Order of Division and Multiplication](#l-23-incorrect-order-of-division-and-multiplication) - [L-24: Redundant statements have no effect.](#l-24-redundant-statements-have-no-effect) - [L-25: Public variables of a contract read in an external context (using `this`).](#l-25-public-variables-of-a-contract-read-in-an-external-context-using-this) - - [L-26: Boolean equality is not required.](#l-26-boolean-equality-is-not-required) + - [L-26: Functions declared `pure` / `view` but contains assembly](#l-26-functions-declared-pure--view-but-contains-assembly) + - [L-27: Boolean equality is not required.](#l-27-boolean-equality-is-not-required) # Summary @@ -79,8 +80,8 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Key | Value | | --- | --- | -| .sol Files | 78 | -| Total nSLOC | 2252 | +| .sol Files | 79 | +| Total nSLOC | 2278 | ## Files Details @@ -95,6 +96,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/CallGraphTests.sol | 49 | | src/Casting.sol | 126 | | src/CompilerBugStorageSignedIntegerArray.sol | 13 | +| src/ConstantFuncsAssembly.sol | 26 | | src/ConstantsLiterals.sol | 28 | | src/ContractLocksEther.sol | 121 | | src/ContractWithTodo.sol | 7 | @@ -165,7 +167,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | src/reused_contract_name/ContractB.sol | 7 | | src/uniswap/UniswapV2Swapper.sol | 50 | | src/uniswap/UniswapV3Swapper.sol | 150 | -| **Total** | **2252** | +| **Total** | **2278** | ## Issue Summary @@ -173,7 +175,7 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati | Category | No. of Issues | | --- | --- | | High | 36 | -| Low | 26 | +| Low | 27 | # High Issues @@ -1195,7 +1197,7 @@ If the length of a dynamic array (storage variable) directly assigned to, it may Solidity does initialize variables by default when you declare them, however it's good practice to explicitly declare an initial value. For example, if you transfer money to an address we must make sure that the address has been initialized. -
16 Found Instances +
17 Found Instances - Found in src/AssemblyExample.sol [Line: 5](../tests/contract-playground/src/AssemblyExample.sol#L5) @@ -1204,6 +1206,12 @@ Solidity does initialize variables by default when you declare them, however it' uint b; ``` +- Found in src/ConstantFuncsAssembly.sol [Line: 6](../tests/contract-playground/src/ConstantFuncsAssembly.sol#L6) + + ```solidity + uint256 public value; + ``` + - Found in src/DelegateCallWithoutAddressCheck.sol [Line: 9](../tests/contract-playground/src/DelegateCallWithoutAddressCheck.sol#L9) ```solidity @@ -2218,7 +2226,7 @@ ERC20 functions may not behave as expected. For example: return values are not a Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;` -
23 Found Instances +
24 Found Instances - Found in src/CompilerBugStorageSignedIntegerArray.sol [Line: 2](../tests/contract-playground/src/CompilerBugStorageSignedIntegerArray.sol#L2) @@ -2227,6 +2235,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid pragma solidity ^0.4.0; ``` +- Found in src/ConstantFuncsAssembly.sol [Line: 2](../tests/contract-playground/src/ConstantFuncsAssembly.sol#L2) + + ```solidity + pragma solidity ^0.4.0; + ``` + - Found in src/ContractLocksEther.sol [Line: 2](../tests/contract-playground/src/ContractLocksEther.sol#L2) ```solidity @@ -3742,7 +3756,7 @@ Use `e` notation, for example: `1e18`, instead of its full numeric value. Instead of separating the logic into a separate function, consider inlining the logic into the calling function. This can reduce the number of function calls and improve readability. -
15 Found Instances +
16 Found Instances - Found in src/CallGraphTests.sol [Line: 6](../tests/contract-playground/src/CallGraphTests.sol#L6) @@ -3769,6 +3783,12 @@ Instead of separating the logic into a separate function, consider inlining the function visitSeventhFloor3() internal { ``` +- Found in src/ConstantFuncsAssembly.sol [Line: 26](../tests/contract-playground/src/ConstantFuncsAssembly.sol#L26) + + ```solidity + function useAssembly() internal pure returns (uint256) { + ``` + - Found in src/InternalFunctions.sol [Line: 28](../tests/contract-playground/src/InternalFunctions.sol#L28) ```solidity @@ -4168,7 +4188,36 @@ The contract reads it's own variable using `this` which adds an unnecessary STAT -## L-26: Boolean equality is not required. +## L-26: Functions declared `pure` / `view` but contains assembly + +If the assembly code contains bugs or unintended side effects, it can lead to incorrect results or vulnerabilities, which are hard to debug and resolve, especially when the function is meant to be simple and predictable. + +
3 Found Instances + + +- Found in src/ConstantFuncsAssembly.sol [Line: 9](../tests/contract-playground/src/ConstantFuncsAssembly.sol#L9) + + ```solidity + function setValue(uint256 _value) external view { + ``` + +- Found in src/ConstantFuncsAssembly.sol [Line: 17](../tests/contract-playground/src/ConstantFuncsAssembly.sol#L17) + + ```solidity + function getConstantValue() external pure returns (uint256) { + ``` + +- Found in src/ConstantFuncsAssembly.sol [Line: 36](../tests/contract-playground/src/ConstantFuncsAssembly.sol#L36) + + ```solidity + function getConstantValue2() external pure returns (uint256) { + ``` + +
+ + + +## L-27: Boolean equality is not required. If `x` is a boolean, there is no need to do `if(x == true)` or `if(x == false)`. Just use `if(x)` and `if(!x)` respectively. diff --git a/reports/report.sarif b/reports/report.sarif index a9df86e6b..53ddef2c4 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -1704,6 +1704,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ConstantFuncsAssembly.sol" + }, + "region": { + "byteLength": 20, + "byteOffset": 110 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -3280,6 +3291,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ConstantFuncsAssembly.sol" + }, + "region": { + "byteLength": 23, + "byteOffset": 32 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -5995,6 +6017,17 @@ } } }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ConstantFuncsAssembly.sol" + }, + "region": { + "byteLength": 232, + "byteOffset": 651 + } + } + }, { "physicalLocation": { "artifactLocation": { @@ -6647,6 +6680,48 @@ }, "ruleId": "public-variable-read-in-external-context" }, + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ConstantFuncsAssembly.sol" + }, + "region": { + "byteLength": 175, + "byteOffset": 182 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ConstantFuncsAssembly.sol" + }, + "region": { + "byteLength": 237, + "byteOffset": 408 + } + } + }, + { + "physicalLocation": { + "artifactLocation": { + "uri": "src/ConstantFuncsAssembly.sol" + }, + "region": { + "byteLength": 98, + "byteOffset": 934 + } + } + } + ], + "message": { + "text": "If the assembly code contains bugs or unintended side effects, it can lead to incorrect results or vulnerabilities, which are hard to debug and resolve, especially when the function is meant to be simple and predictable." + }, + "ruleId": "constant-functions-assembly" + }, { "level": "note", "locations": [ diff --git a/tests/contract-playground/src/ConstantFuncsAssembly.sol b/tests/contract-playground/src/ConstantFuncsAssembly.sol new file mode 100644 index 000000000..f18a50b39 --- /dev/null +++ b/tests/contract-playground/src/ConstantFuncsAssembly.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.4.0; + +contract AssemblyExample { + // State variable + uint256 public value; + + // BAD (view function contains assembly) + function setValue(uint256 _value) external view { + assembly { + // Load the location of the 'value' storage slot + sstore(0, _value) + } + } + + // BAD (pure function contains assembly) + function getConstantValue() external pure returns (uint256) { + uint256 result; + assembly { + // Inline assembly to set the result to a constant value + result := 42 + } + return result; + } + + function useAssembly() internal pure returns (uint256) { + uint256 result; + assembly { + // Inline assembly to set the result to a constant value + result := 42 + } + return result; + } + + // BAD (pure function contains assembly) + function getConstantValue2() external pure returns (uint256) { + return useAssembly(); + } +} From 37522efbe8c6fa885aec8c5a4cd98d5bca7fcd9d Mon Sep 17 00:00:00 2001 From: Alex Roan Date: Mon, 5 Aug 2024 10:27:12 +0100 Subject: [PATCH 15/15] Bump version to 0.1.9 (#652) --- Cargo.lock | 8 ++++---- aderyn/Cargo.toml | 4 ++-- aderyn_core/Cargo.toml | 2 +- aderyn_driver/Cargo.toml | 4 ++-- aderyn_py/Cargo.toml | 4 ++-- reports/report.sarif | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8d018b2a..22d861c9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,7 @@ dependencies = [ [[package]] name = "aderyn" -version = "0.1.8" +version = "0.1.9" dependencies = [ "aderyn_driver", "clap", @@ -38,7 +38,7 @@ dependencies = [ [[package]] name = "aderyn_core" -version = "0.1.8" +version = "0.1.9" dependencies = [ "crossbeam-channel", "cyfrin-foundry-compilers", @@ -64,7 +64,7 @@ dependencies = [ [[package]] name = "aderyn_driver" -version = "0.1.8" +version = "0.1.9" dependencies = [ "aderyn_core", "criterion", @@ -79,7 +79,7 @@ dependencies = [ [[package]] name = "aderyn_py" -version = "0.1.8" +version = "0.1.9" dependencies = [ "aderyn_driver", "pyo3", diff --git a/aderyn/Cargo.toml b/aderyn/Cargo.toml index 447696bd0..7ff9c2be0 100644 --- a/aderyn/Cargo.toml +++ b/aderyn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aderyn" -version = "0.1.8" +version = "0.1.9" edition = "2021" authors = ["Cyfrin "] description = "Rust based Solidity AST analyzer" @@ -10,7 +10,7 @@ default-run = "aderyn" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -aderyn_driver = { path = "../aderyn_driver", version = "0.1.8" } +aderyn_driver = { path = "../aderyn_driver", version = "0.1.9" } clap = { version = "4.4.6", features = ["derive"] } reqwest = { version = "0.12.2", default-features = false, features = ["blocking", "json", "rustls-tls"] } semver = "1.0.22" diff --git a/aderyn_core/Cargo.toml b/aderyn_core/Cargo.toml index 59f8b6274..815ad2fa7 100644 --- a/aderyn_core/Cargo.toml +++ b/aderyn_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aderyn_core" -version = "0.1.8" +version = "0.1.9" edition = "2021" authors = ["Cyfrin "] description = "Rust based Solidity AST analyzer backend" diff --git a/aderyn_driver/Cargo.toml b/aderyn_driver/Cargo.toml index edebe3121..5f7d32ef9 100644 --- a/aderyn_driver/Cargo.toml +++ b/aderyn_driver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aderyn_driver" -version = "0.1.8" +version = "0.1.9" edition = "2021" authors = ["Cyfrin "] description = "Rust based Solidity AST analyzer driver" @@ -9,7 +9,7 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -aderyn_core = { path = "../aderyn_core", version = "0.1.8" } +aderyn_core = { path = "../aderyn_core", version = "0.1.9" } rayon = "1.8.0" cyfrin-foundry-compilers = { version = "0.3.20-aderyn", features = ["svm-solc"] } serde_json = { version = "1.0.96", features = ["preserve_order"] } diff --git a/aderyn_py/Cargo.toml b/aderyn_py/Cargo.toml index 90a44c0e8..36299a119 100644 --- a/aderyn_py/Cargo.toml +++ b/aderyn_py/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aderyn_py" -version = "0.1.8" +version = "0.1.9" edition = "2021" authors = ["Cyfrin "] description = "Rust based Solidity AST analyzer python bindings" @@ -14,7 +14,7 @@ name = "aderynpy" crate-type = ["cdylib"] [dependencies] -aderyn_driver = { path = "../aderyn_driver", version = "0.1.8" } +aderyn_driver = { path = "../aderyn_driver", version = "0.1.9" } [dependencies.pyo3] version = "0.19.0" diff --git a/reports/report.sarif b/reports/report.sarif index 53ddef2c4..89ab1bb2e 100644 --- a/reports/report.sarif +++ b/reports/report.sarif @@ -6782,8 +6782,8 @@ "informationUri": "https://github.com/Cyfrin/aderyn", "name": "Aderyn", "organization": "Cyfrin", - "semanticVersion": "0.1.8", - "version": "0.1.8" + "semanticVersion": "0.1.9", + "version": "0.1.9" } } }