Skip to content

Commit

Permalink
Merge branch 'dev' into unused-errors-detector
Browse files Browse the repository at this point in the history
  • Loading branch information
gvladika committed Oct 10, 2024
2 parents 9af60e6 + d10f7eb commit 65d6943
Show file tree
Hide file tree
Showing 34 changed files with 1,451 additions and 2 deletions.
3 changes: 1 addition & 2 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
* @montyly @0xalpharush @smonicas
/slither/tools/read_storage/ @0xalpharush
* @montyly @smonicas
/slither/tools/doctor/ @elopez
/slither/slithir/ @montyly
/slither/analyses/ @montyly
Expand Down
14 changes: 14 additions & 0 deletions slither/core/variables/state_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class StateVariable(ContractLevel, Variable):
def __init__(self) -> None:
super().__init__()
self._node_initialization: Optional["Node"] = None
self._location: Optional[str] = None

def is_declared_by(self, contract: "Contract") -> bool:
"""
Expand All @@ -21,6 +22,19 @@ def is_declared_by(self, contract: "Contract") -> bool:
"""
return self.contract == contract

def set_location(self, loc: str) -> None:
self._location = loc

@property
def location(self) -> Optional[str]:
"""
Variable Location
Can be default or transient
Returns:
(str)
"""
return self._location

# endregion
###################################################################################
###################################################################################
Expand Down
7 changes: 7 additions & 0 deletions slither/detectors/all_detectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@
from .statements.tautological_compare import TautologicalCompare
from .statements.return_bomb import ReturnBomb
from .functions.out_of_order_retryable import OutOfOrderRetryable
from .functions.gelato_unprotected_randomness import GelatoUnprotectedRandomness
from .statements.chronicle_unchecked_price import ChronicleUncheckedPrice
from .statements.pyth_unchecked_confidence import PythUncheckedConfidence
from .statements.pyth_unchecked_publishtime import PythUncheckedPublishTime
from .functions.chainlink_feed_registry import ChainlinkFeedRegistry
from .functions.pyth_deprecated_functions import PythDeprecatedFunctions
from .functions.optimism_deprecation import OptimismDeprecation
from .statements.unused_custom_errors import UnusedCustomErrors

# from .statements.unused_import import UnusedImport
102 changes: 102 additions & 0 deletions slither/detectors/functions/chainlink_feed_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from typing import List

from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output


class ChainlinkFeedRegistry(AbstractDetector):

ARGUMENT = "chainlink-feed-registry"
HELP = "Detect when chainlink feed registry is used"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH

WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#chainlink-feed-registry"

WIKI_TITLE = "Chainlink Feed Registry usage"
WIKI_DESCRIPTION = "Detect when Chainlink Feed Registry is used. At the moment is only available on Ethereum Mainnet."

# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
import "chainlink/contracts/src/v0.8/interfaces/FeedRegistryInteface.sol"
contract A {
FeedRegistryInterface public immutable registry;
constructor(address _registry) {
registry = _registry;
}
function getPrice(address base, address quote) public return(uint256) {
(, int256 price,,,) = registry.latestRoundData(base, quote);
// Do price validation
return uint256(price);
}
}
```
If the contract is deployed on a different chain than Ethereum Mainnet the `getPrice` function will revert.
"""
# endregion wiki_exploit_scenario

WIKI_RECOMMENDATION = "Do not use Chainlink Feed Registry outside of Ethereum Mainnet."

def _detect(self) -> List[Output]:
# https://github.com/smartcontractkit/chainlink/blob/8ca41fc8f722accfccccb4b1778db2df8fef5437/contracts/src/v0.8/interfaces/FeedRegistryInterface.sol
registry_functions = [
"decimals",
"description",
"versiom",
"latestRoundData",
"getRoundData",
"latestAnswer",
"latestTimestamp",
"latestRound",
"getAnswer",
"getTimestamp",
"getFeed",
"getPhaseFeed",
"isFeedEnabled",
"getPhase",
"getRoundFeed",
"getPhaseRange",
"getPreviousRoundId",
"getNextRoundId",
"proposeFeed",
"confirmFeed",
"getProposedFeed",
"proposedGetRoundData",
"proposedLatestRoundData",
"getCurrentPhaseId",
]
results = []

for contract in self.compilation_unit.contracts_derived:
nodes = []
for target, ir in contract.all_high_level_calls:
if (
target.name == "FeedRegistryInterface"
and ir.function_name in registry_functions
):
nodes.append(ir.node)
# Sort so output is deterministic
nodes.sort(key=lambda x: (x.node_id, x.function.full_name))

if len(nodes) > 0:
info: DETECTOR_INFO = [
"The Chainlink Feed Registry is used in the ",
contract.name,
" contract. It's only available on Ethereum Mainnet, consider to not use it if the contract needs to be deployed on other chains.\n",
]

for node in nodes:
info.extend(["\t - ", node, "\n"])

res = self.generate_result(info)
results.append(res)

return results
78 changes: 78 additions & 0 deletions slither/detectors/functions/gelato_unprotected_randomness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from typing import List

from slither.slithir.operations.internal_call import InternalCall
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output


class GelatoUnprotectedRandomness(AbstractDetector):
"""
Unprotected Gelato VRF requests
"""

ARGUMENT = "gelato-unprotected-randomness"
HELP = "Call to _requestRandomness within an unprotected function"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM

WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#gelato-unprotected-randomness"

WIKI_TITLE = "Gelato unprotected randomness"
WIKI_DESCRIPTION = "Detect calls to `_requestRandomness` within an unprotected function."

# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C is GelatoVRFConsumerBase {
function _fulfillRandomness(
uint256 randomness,
uint256,
bytes memory extraData
) internal override {
// Do something with the random number
}
function bad() public {
_requestRandomness(abi.encode(msg.sender));
}
}
```
The function `bad` is uprotected and requests randomness."""
# endregion wiki_exploit_scenario

WIKI_RECOMMENDATION = (
"Function that request randomness should be allowed only to authorized users."
)

def _detect(self) -> List[Output]:
results = []

for contract in self.compilation_unit.contracts_derived:
if "GelatoVRFConsumerBase" in [c.name for c in contract.inheritance]:
for function in contract.functions_entry_points:
if not function.is_protected() and (
nodes_request := [
ir.node
for ir in function.all_internal_calls()
if isinstance(ir, InternalCall)
and ir.function_name == "_requestRandomness"
]
):
# Sort so output is deterministic
nodes_request.sort(key=lambda x: (x.node_id, x.function.full_name))

for node in nodes_request:
info: DETECTOR_INFO = [
function,
" is unprotected and request randomness from Gelato VRF\n\t- ",
node,
"\n",
]
res = self.generate_result(info)
results.append(res)

return results
92 changes: 92 additions & 0 deletions slither/detectors/functions/optimism_deprecation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from typing import List

from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.core.cfg.node import Node
from slither.core.variables.variable import Variable
from slither.core.expressions import TypeConversion, Literal
from slither.utils.output import Output


class OptimismDeprecation(AbstractDetector):

ARGUMENT = "optimism-deprecation"
HELP = "Detect when deprecated Optimism predeploy or function is used."
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH

WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#optimism-deprecation"

WIKI_TITLE = "Optimism deprecated predeploy or function"
WIKI_DESCRIPTION = "Detect when deprecated Optimism predeploy or function is used."

# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
interface GasPriceOracle {
function scalar() external view returns (uint256);
}
contract Test {
GasPriceOracle constant OPT_GAS = GasPriceOracle(0x420000000000000000000000000000000000000F);
function a() public {
OPT_GAS.scalar();
}
}
```
The call to the `scalar` function of the Optimism GasPriceOracle predeploy always revert.
"""
# endregion wiki_exploit_scenario

WIKI_RECOMMENDATION = "Do not use the deprecated components."

def _detect(self) -> List[Output]:
results = []

deprecated_predeploys = [
"0x4200000000000000000000000000000000000000", # LegacyMessagePasser
"0x4200000000000000000000000000000000000001", # L1MessageSender
"0x4200000000000000000000000000000000000002", # DeployerWhitelist
"0x4200000000000000000000000000000000000013", # L1BlockNumber
]

for contract in self.compilation_unit.contracts_derived:
use_deprecated: List[Node] = []

for _, ir in contract.all_high_level_calls:
# To avoid FPs we assume predeploy contracts are always assigned to a constant and typecasted to an interface
# and we check the target address of a high level call.
if (
isinstance(ir.destination, Variable)
and isinstance(ir.destination.expression, TypeConversion)
and isinstance(ir.destination.expression.expression, Literal)
):
if ir.destination.expression.expression.value in deprecated_predeploys:
use_deprecated.append(ir.node)

if (
ir.destination.expression.expression.value
== "0x420000000000000000000000000000000000000F"
and ir.function_name in ("overhead", "scalar", "getL1GasUsed")
):
use_deprecated.append(ir.node)
# Sort so output is deterministic
use_deprecated.sort(key=lambda x: (x.node_id, x.function.full_name))
if len(use_deprecated) > 0:
info: DETECTOR_INFO = [
"A deprecated Optimism predeploy or function is used in the ",
contract.name,
" contract.\n",
]

for node in use_deprecated:
info.extend(["\t - ", node, "\n"])

res = self.generate_result(info)
results.append(res)

return results
73 changes: 73 additions & 0 deletions slither/detectors/functions/pyth_deprecated_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import List

from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output


class PythDeprecatedFunctions(AbstractDetector):
"""
Documentation: This detector finds deprecated Pyth function calls
"""

ARGUMENT = "pyth-deprecated-functions"
HELP = "Detect Pyth deprecated functions"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH

WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#pyth-deprecated-functions"
WIKI_TITLE = "Pyth deprecated functions"
WIKI_DESCRIPTION = "Detect when a Pyth deprecated function is used"
WIKI_RECOMMENDATION = (
"Do not use deprecated Pyth functions. Visit https://api-reference.pyth.network/."
)

WIKI_EXPLOIT_SCENARIO = """
```solidity
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
contract C {
IPyth pyth;
constructor(IPyth _pyth) {
pyth = _pyth;
}
function A(bytes32 priceId) public {
PythStructs.Price memory price = pyth.getPrice(priceId);
...
}
}
```
The function `A` uses the deprecated `getPrice` Pyth function.
"""

def _detect(self):
DEPRECATED_PYTH_FUNCTIONS = [
"getValidTimePeriod",
"getEmaPrice",
"getPrice",
]
results: List[Output] = []

for contract in self.compilation_unit.contracts_derived:
for target_contract, ir in contract.all_high_level_calls:
if (
target_contract.name == "IPyth"
and ir.function_name in DEPRECATED_PYTH_FUNCTIONS
):
info: DETECTOR_INFO = [
"The following Pyth deprecated function is used\n\t- ",
ir.node,
"\n",
]

res = self.generate_result(info)
results.append(res)

return results
Loading

0 comments on commit 65d6943

Please sign in to comment.