mirror of https://github.com/crytic/slither
commit
e496570123
@ -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 |
@ -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 |
@ -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 |
@ -0,0 +1,3 @@ |
||||
The Chainlink Feed Registry is used in the A contract. It's only available on Ethereum Mainnet, consider to not use it if the contract needs to be deployed on other chains. |
||||
- (None,price,None,None,None) = registry.latestRoundData(base,quote) (tests/e2e/detectors/test_data/chainlink-feed-registry/0.8.20/chainlink_feed_registry.sol#25) |
||||
|
@ -0,0 +1,4 @@ |
||||
A deprecated Optimism predeploy or function is used in the Test contract. |
||||
- OPT_GAS.scalar() (tests/e2e/detectors/test_data/optimism-deprecation/0.8.20/optimism_deprecation.sol#15) |
||||
- L1_BLOCK_NUMBER.q() (tests/e2e/detectors/test_data/optimism-deprecation/0.8.20/optimism_deprecation.sol#19) |
||||
|
@ -0,0 +1,3 @@ |
||||
The following Pyth deprecated function is used |
||||
- price = pyth.getPrice(priceId) (tests/e2e/detectors/test_data/pyth-deprecated-functions/0.8.20/pyth_deprecated_functions.sol#23) |
||||
|
@ -0,0 +1,37 @@ |
||||
interface FeedRegistryInterface { |
||||
function latestRoundData( |
||||
address base, |
||||
address quote |
||||
) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); |
||||
} |
||||
|
||||
interface MyInterface { |
||||
function latestRoundData( |
||||
address base, |
||||
address quote |
||||
) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); |
||||
} |
||||
|
||||
contract A { |
||||
FeedRegistryInterface public immutable registry; |
||||
MyInterface public immutable my_interface; |
||||
|
||||
constructor(FeedRegistryInterface _registry, MyInterface _my_interface) { |
||||
registry = _registry; |
||||
my_interface = _my_interface; |
||||
} |
||||
|
||||
function getPriceBad(address base, address quote) public returns (uint256) { |
||||
(, int256 price,,,) = registry.latestRoundData(base, quote); |
||||
// Do price validation |
||||
return uint256(price); |
||||
} |
||||
|
||||
function getPriceGood(address base, address quote) public returns (uint256) { |
||||
(, int256 price,,,) = my_interface.latestRoundData(base, quote); |
||||
// Do price validation |
||||
return uint256(price); |
||||
} |
||||
|
||||
|
||||
} |
Binary file not shown.
@ -0,0 +1,27 @@ |
||||
interface GasPriceOracle { |
||||
function scalar() external view returns (uint256); |
||||
function baseFee() external view returns (uint256); |
||||
} |
||||
|
||||
interface L1BlockNumber { |
||||
function q() external view returns (uint256); |
||||
} |
||||
|
||||
contract Test { |
||||
GasPriceOracle constant OPT_GAS = GasPriceOracle(0x420000000000000000000000000000000000000F); |
||||
L1BlockNumber constant L1_BLOCK_NUMBER = L1BlockNumber(0x4200000000000000000000000000000000000013); |
||||
|
||||
function bad() public { |
||||
OPT_GAS.scalar(); |
||||
} |
||||
|
||||
function bad2() public { |
||||
L1_BLOCK_NUMBER.q(); |
||||
} |
||||
|
||||
function good() public { |
||||
OPT_GAS.baseFee(); |
||||
} |
||||
|
||||
|
||||
} |
Binary file not shown.
@ -0,0 +1,35 @@ |
||||
|
||||
// Fake Pyth interface |
||||
interface IPyth { |
||||
function getPrice(bytes32 id) external returns (uint256 price); |
||||
function notDeprecated(bytes32 id) external returns (uint256 price); |
||||
} |
||||
|
||||
interface INotPyth { |
||||
function getPrice(bytes32 id) external returns (uint256 price); |
||||
} |
||||
|
||||
contract C { |
||||
|
||||
IPyth pyth; |
||||
INotPyth notPyth; |
||||
|
||||
constructor(IPyth _pyth, INotPyth _notPyth) { |
||||
pyth = _pyth; |
||||
notPyth = _notPyth; |
||||
} |
||||
|
||||
function Deprecated(bytes32 priceId) public { |
||||
uint256 price = pyth.getPrice(priceId); |
||||
} |
||||
|
||||
function notDeprecated(bytes32 priceId) public { |
||||
uint256 price = pyth.notDeprecated(priceId); |
||||
} |
||||
|
||||
function notPythCall(bytes32 priceId) public { |
||||
uint256 price = notPyth.getPrice(priceId); |
||||
} |
||||
|
||||
|
||||
} |
Binary file not shown.
Loading…
Reference in new issue