Skip to content

Commit

Permalink
feat: get calculate functions
Browse files Browse the repository at this point in the history
  • Loading branch information
talfao committed Apr 27, 2024
1 parent 921f6cb commit 9e0d446
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 23 deletions.
71 changes: 53 additions & 18 deletions slither/detectors/oracles/spot_price.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
from slither.slithir.operations import HighLevelCall, Operation, LibraryCall, Assignment
from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.slithir.variables.variable import Variable
from slither.core.cfg.node import Node, recheable
from slither.core.cfg.node import Node, recheable, NodeType
from slither.detectors.abstract_detector import DetectorClassification
from slither.slithir.operations import (
Binary,
BinaryType,
)

from slither.core.declarations.function import Function



# SpotPriceUsage class to store the node and interface
# For better readability of messages
Expand Down Expand Up @@ -47,8 +50,8 @@ class SpotPriceDetector(AbstractDetector):
# SafeMath functions for compatibility with solidity contracts < 0.8.0 version
SAFEMATH_FUNCTIONS = ["mul", "div"]
# Uniswap calculations functions
CALC_FUNCTIONS = ["getAmountOut"]
# Protected functions
CALC_FUNCTIONS = ["getAmountOut", "getAmountsOut"]
# Protected functions -> Indicating TWAP V2 oracles
PROTECTED_FUNCTIONS = ["currentCumulativePrices", "price1CumulativeLast", "price0CumulativeLast"]
# Uniswap interfaces
UNISWAP_INTERFACES = ["IUniswapV3Pool", "IUniswapV2Pair"]
Expand Down Expand Up @@ -96,13 +99,14 @@ def different_address(first_ir: Operation, second_ir: Operation):
return True
return False




# Detect oracle call
def detect_oracle_call(
self, function: FunctionContract, function_names, interface_names
) -> (Node, str):
# Ignore cases with TWAP functions
if function.name in self.PROTECTED_FUNCTIONS:
return []

nodes = []
first_node = None
first_arguments = []
Expand Down Expand Up @@ -147,6 +151,7 @@ def detect_oracle_call(
counter = 0
break
counter += 1


return nodes

Expand Down Expand Up @@ -229,9 +234,25 @@ def track_var(variable, node) -> bool:
for v in node.variables_written:
if str(v) == str(temp_variable):
variable = v
print(variable)
return variable


@staticmethod
# Check if calculations are linked to return, that would indicate only get/calculation function
def are_calcs_linked_to_return(node: Node) -> bool:
function = node.function
variables = node.variables_written
returned_vars = function.returns
for r_var in returned_vars:
for var in variables:
if is_dependent(r_var, var, function):
return function
if node.type == NodeType.RETURN:
return function
for s in node.sons:
if s.type == NodeType.RETURN:
return function
return None
# Check if calculations are made with spot data
def are_calculations_made_with_spot_data(self, node: Node, interface: str) -> Node:

Expand All @@ -242,6 +263,7 @@ def are_calculations_made_with_spot_data(self, node: Node, interface: str) -> No

# Check if the node is used in calculations
nodes = []
return_functions = []
while node:
variables = node[0].variables_written
recheable_nodes = recheable(node[0])
Expand All @@ -260,10 +282,19 @@ def are_calculations_made_with_spot_data(self, node: Node, interface: str) -> No
# Check if the variable is used in calculation functions
elif self.calc_functions(n):
nodes.append(n)

node.pop()
return nodes

for node in nodes:
function = self.are_calcs_linked_to_return(node)
return_functions.append(function)
return nodes, return_functions

@staticmethod
def only_return(function: Function) -> bool:
if function is None:
return False
if function.reachable_from_functions:
return False
return True
# Generate informative messages for the detected spot price usage
@staticmethod
def generate_informative_messages(spot_price_classes):
Expand All @@ -287,9 +318,13 @@ def generate_informative_messages(spot_price_classes):
)
return messages

# Generate message for the node which occured in calculations

# Generate message for the node which occured in calculations
@staticmethod
def generate_calc_messages(node):
def generate_calc_messages(node: Node, only_return: bool) -> str:
if only_return:
return f"Calculations are made with spot price data in {node.source_mapping} but the function is not used anywhere in the contract.\n"

return f"Calculations are made with spot price data in {node.source_mapping}\n"

def _detect(self):
Expand All @@ -299,12 +334,12 @@ def _detect(self):
messages = self.generate_informative_messages(spot_price_usage)

for spot_price in spot_price_usage:
node = self.are_calculations_made_with_spot_data(spot_price.node, spot_price.interface)
if node:
self.IMPACT = DetectorClassification.LOW
self.CONFIDENCE = DetectorClassification.LOW
for n in node:
messages.append(self.generate_calc_messages(n))
nodes, return_functions = self.are_calculations_made_with_spot_data(spot_price.node, spot_price.interface)
if nodes:
for i in range(len(nodes)):
only_return = self.only_return(return_functions[i])
messages.append(self.generate_calc_messages(nodes[i], only_return ))

# It can contain duplication, sorted and unique messages.
# Sorting due to testing purposes
messages = sorted(list(set(messages)))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
Calculations are made with spot price data in tests/e2e/detectors/test_data/oracle-spot-price/0.6.12/spot_price_getReserves.sol#200
Method which could indicate usage of spot price was detected at tests/e2e/detectors/test_data/oracle-spot-price/0.6.12/spot_price_getReserves.sol#196 and tests/e2e/detectors/test_data/oracle-spot-price/0.6.12/spot_price_getReserves.sol#201-203.
NEW VARIABLE _stakeAmount = rewardsYaxisEth.balanceOf(_voter)
NEW VARIABLE _rewardsYaxisAmount = rewardsYaxis.balanceOf(_voter).add(rewardsYaxis.earned(_voter))
Method which could indicate usage of spot price was detected in Uniswap V2 at tests/e2e/detectors/test_data/oracle-spot-price/0.6.12/spot_price_getReserves.sol#197
EXPRESSION (_yaxReserves) = yaxisEthUniswapV2Pair.getReserves()

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Calculations are made with spot price data in tests/e2e/detectors/test_data/oracle-spot-price/0.8.20/spot_price_balanceOf.sol#41
Calculations are made with spot price data in tests/e2e/detectors/test_data/oracle-spot-price/0.8.20/spot_price_balanceOf.sol#41 but the function is not used anywhere in the contract.
Method which could indicate usage of spot price was detected at tests/e2e/detectors/test_data/oracle-spot-price/0.8.20/spot_price_balanceOf.sol#38 and tests/e2e/detectors/test_data/oracle-spot-price/0.8.20/spot_price_balanceOf.sol#39.
NEW VARIABLE usdcBalance = IERC20(USDCAddress).balanceOf(pool)
NEW VARIABLE ethBalance = IERC20(weth).balanceOf(pool)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Calculations are made with spot price data in tests/e2e/detectors/test_data/oracle-spot-price/0.8.20/spot_price_getAmountOut.sol#66-71
Calculations are made with spot price data in tests/e2e/detectors/test_data/oracle-spot-price/0.8.20/spot_price_getAmountOut.sol#66-71 but the function is not used anywhere in the contract.
Method which could indicate usage of spot price was detected in Uniswap V2 at tests/e2e/detectors/test_data/oracle-spot-price/0.8.20/spot_price_getAmountOut.sol#59
EXPRESSION (left,right) = pair.getReserves()

0 comments on commit 9e0d446

Please sign in to comment.