Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solidity State Corpus Study #295

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
2 changes: 2 additions & 0 deletions corpus_studies/solidity_state_study/corpus/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The corpus was gathered using the Google BigData query found in https://arxiv.org/pdf/1910.10601.pdf. In order to run smaller versions, the query size was cut down to 1000 and 10000 for the small and medium corpus respectively.
The small corpus was then filtered for addresses that had code in a .sol format that could be compiled with solc.
100,000 changes: 100,000 additions & 0 deletions corpus_studies/solidity_state_study/corpus/etherscan_corpus.txt

Large diffs are not rendered by default.

10,000 changes: 10,000 additions & 0 deletions corpus_studies/solidity_state_study/corpus/medium_etherscan_corpus.txt

Large diffs are not rendered by default.

629 changes: 629 additions & 0 deletions corpus_studies/solidity_state_study/corpus/small_etherscan_corpus.txt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
0xdac17f958d2ee523a2206206994597c13d831ec7
0x8d12a197cb00d4747a1fe03395095ce2a5cc6819
0x2a0c0dbecc7e4d658f48e01e3fa353f44050c208
0x06012c8cf97bead5deae237070f9587f8e7a266d
0xd1ceeeeee83f8bcf3bedad437202b6154e9f5405
0xf230b790e05390fc8295f4d3f60332c93bed42e2
0xd26114cd6ee289accf82350c8d8487fedb8a0c07
0xa3c1e324ca1ce40db73ed6026c4a177f099b5770
0x8e766f57f7d16ca50b4a0b90b88f6468a09b0439
0x6090a6e47849629b7245dfa1ca21d94cd15878ef
0x0d8775f648430679a709e98d2b0cb6250d2887ef
0xa15c7ebe1f07caf6bff097d8a589fb8ac49ae5b3
0xb1690c08e213a35ed9bab7b318de14420fb57d8c
0x39755357759ce0d7f32dc8dc45414cca409ae24e
0x74fd51a98a4a1ecbef8cc43be801cce630e260bd
0x1f0480a66883de97d2b054929252aae8f664c15c
0x8a88f04e0c905054d2f33b26bb3a46d7091a039a
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
0x8fdcc30eda7e94f1c12ce0280df6cd531e8365c5
0x0e50e6d6bb434938d8fe670a2d7a14cd128eb50f
0x21ab6c9fac80c59d401b37cb43f81ea9dde7fe34
0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
0xb8c77482e45f1f44de1745f52c74426c631bdd52
0x93e682107d1e9defb0b5ee701c71707a4b2e46bc
0x0d152b9ee87ebae179f64c067a966dd716c50742
0xd3ebdaea9aeac98de723f640bce4aa07e2e44192
2 changes: 2 additions & 0 deletions corpus_studies/solidity_state_study/slither_plugins/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
To use these plugins with slither, first follow the instructions at https://github.com/crytic/slither/wiki/Developer-installation to set up the slither development environment.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pycache files appear to be derived binaries; remove them from the PR. You can add them to .gitignore.

Then, create a symlink in <path-to-slither>/slither/slither/detectors to this directory and update all_detectors.py to include the detector names.
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
153 changes: 153 additions & 0 deletions corpus_studies/solidity_state_study/slither_plugins/hasstate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.expressions import *
from functools import reduce
from slither.core.declarations.solidity_variables import SOLIDITY_VARIABLES,SOLIDITY_VARIABLES_COMPOSED

# Solidity variables that are used as state variables instead of
# local variables, and thus must be explicitly checked for when
# checking for state usage.
SOLIDITY_VARIABLE_WHITELIST = ["now", "this", "block.number", "block.timestamp"]

# This is a function used to check for state in multiple plugins,
# and thus belongs on this level for easy code reuse.
#
# It takes a Solidity AST used inside of a conditional check, exp,
# and traverses it to find all of the variables used in the check.
# It converts the variables to strings and returns them in a set.
def get_vars_used(exp) :
if isinstance(exp, Identifier) :
return {str(exp)}
elif isinstance(exp, CallExpression) :
vars = [get_vars_used(arg) for arg in exp.arguments]
return reduce(lambda s,x : s.union(x), vars, set())
elif isinstance(exp, UnaryOperation) :
return get_vars_used(exp.expression)
elif isinstance(exp, BinaryOperation) :
return (get_vars_used(exp.expression_left)
.union(get_vars_used(exp.expression_right))
)
elif isinstance(exp, Literal) :
return set()
elif isinstance(exp, IndexAccess) :
return (get_vars_used(exp.expression_left)
.union(get_vars_used(exp.expression_right))
)
elif isinstance(exp, MemberAccess) :
return {str(exp)}
elif isinstance(exp, TupleExpression) :
# There should be exactly one element in the tuple
# (that is, they should act as parenthesis).
# Comparing tuples for equality as a Solidity language feature
# does not exist at the time of writing.
return get_vars_used(exp.expressions[0])
return set()

# Class implemented as a detector plugin for Slither. This detector detects,
# for the given contracts, which contracts have state checks and
# thus are stateful
class HasState(AbstractDetector):

STATEFUL_MESSAGE = "%s is stateful\n"

# Variables declared for use in Slither
ARGUMENT = 'hasstate'
HELP = 'Help printed by slither'
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH

WIKI = 'STATE TEST'

WIKI_TITLE = 'TODO'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO?

WIKI_DESCRIPTION = 'Detects whether or not a contract is stateful'
WIKI_EXPLOIT_SCENARIO = '''
contract Pausable {
event Pause();
event Unpause();

bool public paused = false;

modifier whenNotPaused() {
require(!paused);
_;
}

modifier whenPaused {
require(paused);
_;
}

function pause() whenNotPaused returns (bool) {
paused = true;
Pause();
return true;
}

function unpause() whenPaused returns (bool) {
paused = false;
Unpause();
return true;
}
}

In the above contract, there are two states checks,
"require(paused)" and "require(!paused)". This contract is
therefore stateful, and the detector will print out:
%s
''' % (STATEFUL_MESSAGE % "Pausable")

WIKI_RECOMMENDATION = 'This is just a check, it does not indicate an error'

# Checks if any contracts that contract inherits were already determined
# to be stateful. Returns the truth value of that check.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we haven't processed those contracts yet? Does Slither process in order of the inheritance hierarchy?

def inherited_state(self, contract, stateful) :
inherited = [str(c) for c in contract.inheritance]
return bool([elem for elem in inherited if elem in stateful])

# Checks if the node makes a stateful check, and gives the result of
# that check
def is_stateful_node(self, node, state_vars) :
str_vars = [str(sv) for sv in state_vars]
constant_vars = [str(sv) for sv in state_vars if sv.is_constant]
nonconstant_vars = [str(sv) for sv in state_vars if not sv.is_constant]
argument = None
if node.contains_require_or_assert() :
#require and assert both only have one argument.
argument = node.expression.arguments[0]
elif node.contains_if() :
argument = node.expression

if argument :
vars = get_vars_used(argument)
uses_state = False
for var in vars :
if not (var in str_vars or var in SOLIDITY_VARIABLE_WHITELIST) :
return False
elif var in nonconstant_vars :
uses_state = True
return uses_state

return False

# Checks if the function has a stateful check, returns the result of that check
def is_stateful_function(self, func, state_vars) :
nodes = list(map(lambda n : self.is_stateful_node(n, state_vars), func.nodes))
return True in nodes

# Checks if the contract has a function or modifier that makes a stateful check.
def is_stateful_contract(self, contract) :
state_vars = [sv for sv in contract.state_variables]
functions = list(map(lambda f : self.is_stateful_function(f, state_vars), contract.modifiers + contract.functions))
return True in functions

# Function called by Slither framework. Checks all contracts in scope of
# the detector and gives the result of the check on those contracts.
def _detect(self):
stateful_contracts = []
for c in self.contracts :
if self.is_stateful_contract(c) or self.inherited_state(c, stateful_contracts):
stateful_contracts.append(c.name)

stateful_contracts = [self.STATEFUL_MESSAGE % sc for sc in stateful_contracts]
if stateful_contracts :
return [self.generate_result(stateful_contracts)]
return []
Loading