mirror of https://github.com/crytic/slither
- Use an abstract class, similar pattern than slither's detectors - Every issue is documented (description/exploit scenario/recommendation)pull/410/head
parent
e7608a1a44
commit
1d87059712
@ -1,138 +0,0 @@ |
||||
import logging |
||||
|
||||
from slither.slithir.operations import InternalCall |
||||
from slither.utils.output import Output |
||||
from slither.utils.colors import red, yellow, green |
||||
|
||||
logger = logging.getLogger("Slither-check-upgradeability") |
||||
|
||||
class MultipleInitTarget(Exception): |
||||
pass |
||||
|
||||
def _get_initialize_functions(contract): |
||||
return [f for f in contract.functions if f.name == 'initialize' and f.is_implemented] |
||||
|
||||
def _get_all_internal_calls(function): |
||||
all_ir = function.all_slithir_operations() |
||||
return [i.function for i in all_ir if isinstance(i, InternalCall) and i.function_name == "initialize"] |
||||
|
||||
|
||||
def _get_most_derived_init(contract): |
||||
init_functions = [f for f in contract.functions if not f.is_shadowed and f.name == 'initialize'] |
||||
if len(init_functions) > 1: |
||||
if len([f for f in init_functions if f.contract_declarer == contract]) == 1: |
||||
return next((f for f in init_functions if f.contract_declarer == contract)) |
||||
raise MultipleInitTarget |
||||
if init_functions: |
||||
return init_functions[0] |
||||
return None |
||||
|
||||
def check_initialization(contract): |
||||
|
||||
results = { |
||||
'Initializable-present': False, |
||||
'Initializable-inherited': False, |
||||
'Initializable.initializer()-present': False, |
||||
'missing-initializer-modifier': [], |
||||
'initialize_target': {}, |
||||
'missing-calls': [], |
||||
'multiple-calls': [] |
||||
} |
||||
|
||||
error_found = False |
||||
|
||||
logger.info(green( |
||||
'\n## Run initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialization-checks)')) |
||||
|
||||
# Check if the Initializable contract is present |
||||
initializable = contract.slither.get_contract_from_name('Initializable') |
||||
if initializable is None: |
||||
logger.info(yellow('Initializable contract not found, the contract does not follow a standard initalization schema.')) |
||||
return results |
||||
results['Initializable-present'] = True |
||||
|
||||
# Check if the Initializable contract is inherited |
||||
if initializable not in contract.inheritance: |
||||
logger.info( |
||||
yellow('The logic contract does not call the initializer.')) |
||||
return results |
||||
results['Initializable-inherited'] = True |
||||
|
||||
# Check if the Initializable contract is inherited |
||||
initializer = contract.get_modifier_from_canonical_name('Initializable.initializer()') |
||||
if initializer is None: |
||||
logger.info( |
||||
yellow('Initializable.initializer() does not exist')) |
||||
return results |
||||
results['Initializable.initializer()-present'] = True |
||||
|
||||
# Check if a init function lacks the initializer modifier |
||||
initializer_modifier_missing = False |
||||
all_init_functions = _get_initialize_functions(contract) |
||||
for f in all_init_functions: |
||||
if not initializer in f.modifiers: |
||||
initializer_modifier_missing = True |
||||
info = f'{f.canonical_name} does not call the initializer modifier' |
||||
logger.info(red(info)) |
||||
res = Output(info) |
||||
res.add(f) |
||||
results['missing-initializer-modifier'].append(res.data) |
||||
|
||||
if not initializer_modifier_missing: |
||||
logger.info(green('All the init functions have the initializer modifier')) |
||||
|
||||
# Check if we can determine the initialize function that will be called |
||||
# TODO: handle MultipleInitTarget |
||||
try: |
||||
most_derived_init = _get_most_derived_init(contract) |
||||
except MultipleInitTarget: |
||||
logger.info(red('Too many init targets')) |
||||
return results |
||||
|
||||
if most_derived_init is None: |
||||
init_info = f'{contract.name} has no initialize function\n' |
||||
logger.info(green(init_info)) |
||||
results['initialize_target'] = {} |
||||
return results |
||||
# results['initialize_target'] is set at the end, as we want to print it last |
||||
|
||||
# Check if an initialize function is not called from the most_derived_init function |
||||
missing_call = False |
||||
all_init_functions_called = _get_all_internal_calls(most_derived_init) + [most_derived_init] |
||||
missing_calls = [f for f in all_init_functions if not f in all_init_functions_called] |
||||
for f in missing_calls: |
||||
info = f'Missing call to {f.canonical_name} in {most_derived_init.canonical_name}' |
||||
logger.info(red(info)) |
||||
res = Output(info) |
||||
res.add(f, {"is_most_derived_init_function": False}) |
||||
res.add(most_derived_init, {"is_most_derived_init_function": True}) |
||||
results['missing-calls'].append(res.data) |
||||
missing_call = True |
||||
if not missing_call: |
||||
logger.info(green('No missing call to an init function found')) |
||||
|
||||
# Check if an init function is called multiple times |
||||
double_calls = list(set([f for f in all_init_functions_called if all_init_functions_called.count(f) > 1])) |
||||
double_calls_found = False |
||||
for f in double_calls: |
||||
info = f'{f.canonical_name} is called multiple times in {most_derived_init.full_name}' |
||||
logger.info(red(info)) |
||||
res = Output(info) |
||||
res.add(f) |
||||
results['multiple-calls'].append(res.data) |
||||
double_calls_found = True |
||||
if not double_calls_found: |
||||
logger.info(green('No double call to init functions found')) |
||||
|
||||
# Print the initialize_target info |
||||
|
||||
init_info = f'{contract.name} needs to be initialized by {most_derived_init.full_name}\n' |
||||
logger.info(green('Check the deployement script to ensure that these functions are called:\n' + init_info)) |
||||
res = Output(init_info) |
||||
res.add(most_derived_init) |
||||
results['initialize_target'] = res.data |
||||
|
||||
if not error_found: |
||||
logger.info(green('No error found')) |
||||
|
||||
return results |
@ -1,31 +0,0 @@ |
||||
import logging |
||||
|
||||
from slither.utils import output |
||||
from slither.utils.colors import red, green |
||||
|
||||
logger = logging.getLogger("Slither-check-upgradeability") |
||||
|
||||
|
||||
def check_variable_initialization(contract): |
||||
results = { |
||||
'variables-initialized': [] |
||||
} |
||||
|
||||
logger.info(green( |
||||
'\n## Run variable initialization checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks)')) |
||||
|
||||
error_found = False |
||||
|
||||
for s in contract.state_variables: |
||||
if s.initialized and not s.is_constant: |
||||
info = f'{s.canonical_name} has an initial value ({s.source_mapping_str})' |
||||
logger.info(red(info)) |
||||
res = output.Output(info) |
||||
res.add(s) |
||||
results['variables-initialized'].append(res.data) |
||||
error_found = True |
||||
|
||||
if not error_found: |
||||
logger.info(green('No error found')) |
||||
|
||||
return results |
@ -0,0 +1,127 @@ |
||||
import abc |
||||
|
||||
from slither.utils.colors import green, yellow, red |
||||
from slither.utils.output import Output |
||||
|
||||
|
||||
class IncorrectCheckInitialization(Exception): |
||||
pass |
||||
|
||||
|
||||
class CheckClassification: |
||||
HIGH = 0 |
||||
MEDIUM = 1 |
||||
LOW = 2 |
||||
INFORMATIONAL = 3 |
||||
|
||||
|
||||
classification_colors = { |
||||
CheckClassification.INFORMATIONAL: green, |
||||
CheckClassification.LOW: yellow, |
||||
CheckClassification.MEDIUM: yellow, |
||||
CheckClassification.HIGH: red |
||||
} |
||||
|
||||
classification_txt = { |
||||
CheckClassification.INFORMATIONAL: 'Informational', |
||||
CheckClassification.LOW: 'Low', |
||||
CheckClassification.MEDIUM: 'Medium', |
||||
CheckClassification.HIGH: 'High', |
||||
} |
||||
|
||||
|
||||
class AbstractCheck(metaclass=abc.ABCMeta): |
||||
ARGUMENT = '' |
||||
HELP = '' |
||||
IMPACT = None |
||||
|
||||
WIKI = '' |
||||
|
||||
WIKI_TITLE = '' |
||||
WIKI_DESCRIPTION = '' |
||||
WIKI_EXPLOIT_SCENARIO = '' |
||||
WIKI_RECOMMENDATION = '' |
||||
|
||||
REQUIRE_CONTRACT = False |
||||
REQUIRE_PROXY = False |
||||
REQUIRE_CONTRACT_V2 = False |
||||
|
||||
def __init__(self, logger, contract, proxy=None, contract_v2=None): |
||||
self.logger = logger |
||||
self.contract = contract |
||||
self.proxy = proxy |
||||
self.contract_v2 = contract_v2 |
||||
|
||||
if not self.ARGUMENT: |
||||
raise IncorrectCheckInitialization('NAME is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
if not self.HELP: |
||||
raise IncorrectCheckInitialization('HELP is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
if not self.WIKI: |
||||
raise IncorrectCheckInitialization('WIKI is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
if not self.WIKI_TITLE: |
||||
raise IncorrectCheckInitialization('WIKI_TITLE is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
if not self.WIKI_DESCRIPTION: |
||||
raise IncorrectCheckInitialization('WIKI_DESCRIPTION is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [CheckClassification.INFORMATIONAL]: |
||||
raise IncorrectCheckInitialization('WIKI_EXPLOIT_SCENARIO is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
if not self.WIKI_RECOMMENDATION: |
||||
raise IncorrectCheckInitialization('WIKI_RECOMMENDATION is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
if self.REQUIRE_PROXY and self.REQUIRE_CONTRACT_V2: |
||||
# This is not a fundatemenal issues |
||||
# But it requires to change __main__ to avoid running two times the detectors |
||||
txt = 'REQUIRE_PROXY and REQUIRE_CONTRACT_V2 needs change in __main___ {}'.format(self.__class__.__name__) |
||||
raise IncorrectCheckInitialization(txt) |
||||
|
||||
if self.IMPACT not in [CheckClassification.LOW, |
||||
CheckClassification.MEDIUM, |
||||
CheckClassification.HIGH, |
||||
CheckClassification.INFORMATIONAL]: |
||||
raise IncorrectCheckInitialization('IMPACT is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
if self.REQUIRE_CONTRACT_V2 and contract_v2 is None: |
||||
raise IncorrectCheckInitialization('ContractV2 is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
if self.REQUIRE_PROXY and proxy is None: |
||||
raise IncorrectCheckInitialization('Proxy is not initialized {}'.format(self.__class__.__name__)) |
||||
|
||||
@abc.abstractmethod |
||||
def _check(self): |
||||
"""TODO Documentation""" |
||||
return [] |
||||
|
||||
def check(self): |
||||
all_results = self._check() |
||||
# Keep only dictionaries |
||||
all_results = [r.data for r in all_results] |
||||
if all_results: |
||||
if self.logger: |
||||
info = '\n' |
||||
for idx, result in enumerate(all_results): |
||||
info += result['description'] |
||||
info += 'Reference: {}'.format(self.WIKI) |
||||
self._log(info) |
||||
return all_results |
||||
|
||||
def generate_result(self, info, additional_fields=None): |
||||
output = Output(info, |
||||
additional_fields, |
||||
markdown_root=self.contract.slither.markdown_root) |
||||
|
||||
output.data['check'] = self.ARGUMENT |
||||
|
||||
return output |
||||
|
||||
def _log(self, info): |
||||
if self.logger: |
||||
self.logger.info(self.color(info)) |
||||
|
||||
@property |
||||
def color(self): |
||||
return classification_colors[self.IMPACT] |
@ -0,0 +1,11 @@ |
||||
from .initialization import (InitializablePresent, InitializableInherited, |
||||
InitializableInitializer, MissingInitializerModifier, MissingCalls, MultipleCalls, InitializeTarget) |
||||
|
||||
from .functions_ids import IDCollision, FunctionShadowing |
||||
|
||||
from .variable_initialization import VariableWithInit |
||||
|
||||
from .variables_order import (MissingVariable, DifferentVariableContractProxy, |
||||
DifferentVariableContractNewContract, ExtraVariablesProxy, ExtraVariablesNewContract) |
||||
|
||||
from .constant import WereConstant, BecameConstant |
@ -0,0 +1,147 @@ |
||||
from slither.exceptions import SlitherError |
||||
from slither.tools.upgradeability.checks.abstract_checks import AbstractCheck, CheckClassification |
||||
from slither.utils.function import get_function_id |
||||
|
||||
|
||||
def get_signatures(c): |
||||
functions = c.functions |
||||
functions = [f.full_name for f in functions if f.visibility in ['public', 'external'] and |
||||
not f.is_constructor and not f.is_fallback] |
||||
|
||||
variables = c.state_variables |
||||
variables = [variable.name + '()' for variable in variables if variable.visibility in ['public']] |
||||
return list(set(functions + variables)) |
||||
|
||||
|
||||
def _get_function_or_variable(contract, signature): |
||||
f = contract.get_function_from_signature(signature) |
||||
|
||||
if f: |
||||
return f |
||||
|
||||
for variable in contract.state_variables: |
||||
# Todo: can lead to incorrect variable in case of shadowing |
||||
if variable.visibility in ['public']: |
||||
if variable.name + '()' == signature: |
||||
return variable |
||||
|
||||
raise SlitherError(f'Function id checks: {signature} not found in {contract.name}') |
||||
|
||||
|
||||
class IDCollision(AbstractCheck): |
||||
ARGUMENT = 'function-id-collision' |
||||
IMPACT = CheckClassification.HIGH |
||||
|
||||
HELP = 'Functions ids collision' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-collisions' |
||||
WIKI_TITLE = 'Functions ids collisions' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Detect function id collision between the contract and the proxy. |
||||
''' |
||||
|
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract Contract{ |
||||
function gsf() public { |
||||
// ... |
||||
} |
||||
} |
||||
|
||||
contract Proxy{ |
||||
function tgeo() public { |
||||
// ... |
||||
} |
||||
} |
||||
``` |
||||
`Proxy.tgeo()` and `Contract.gsf()` have the same function id (0x67e43e43). |
||||
As a result, `Proxy.tgeo()` will shadow Contract.gsf()`. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Rename the function. Avoid public functions in the proxy. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
REQUIRE_PROXY = True |
||||
|
||||
def _check(self): |
||||
signatures_implem = get_signatures(self.contract) |
||||
signatures_proxy = get_signatures(self.proxy) |
||||
|
||||
signatures_ids_implem = {get_function_id(s): s for s in signatures_implem} |
||||
signatures_ids_proxy = {get_function_id(s): s for s in signatures_proxy} |
||||
|
||||
results = [] |
||||
|
||||
for (k, _) in signatures_ids_implem.items(): |
||||
if k in signatures_ids_proxy: |
||||
if signatures_ids_implem[k] != signatures_ids_proxy[k]: |
||||
implem_function = _get_function_or_variable(self.contract, signatures_ids_implem[k]) |
||||
proxy_function = _get_function_or_variable(self.proxy, signatures_ids_proxy[k]) |
||||
|
||||
info = ['Function id collision found: ', implem_function, |
||||
' ', proxy_function, '\n'] |
||||
json = self.generate_result(info) |
||||
results.append(json) |
||||
|
||||
return results |
||||
|
||||
|
||||
class FunctionShadowing(AbstractCheck): |
||||
ARGUMENT = 'function-shadowing' |
||||
IMPACT = CheckClassification.HIGH |
||||
|
||||
HELP = 'Functions shadowing' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-shadowing' |
||||
WIKI_TITLE = 'Functions shadowing' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Detect function shadowing between the contract and the proxy. |
||||
''' |
||||
|
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract Contract{ |
||||
function get() public { |
||||
// ... |
||||
} |
||||
} |
||||
|
||||
contract Proxy{ |
||||
function get() public { |
||||
// ... |
||||
} |
||||
} |
||||
``` |
||||
`Proxy.get` will shadow any call to `get()`. As a result `get()` is never executed in the logic contract and cannot be updated. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Rename the function. Avoid public functions in the proxy. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
REQUIRE_PROXY = True |
||||
|
||||
def _check(self): |
||||
signatures_implem = get_signatures(self.contract) |
||||
signatures_proxy = get_signatures(self.proxy) |
||||
|
||||
signatures_ids_implem = {get_function_id(s): s for s in signatures_implem} |
||||
signatures_ids_proxy = {get_function_id(s): s for s in signatures_proxy} |
||||
|
||||
results = [] |
||||
|
||||
for (k, _) in signatures_ids_implem.items(): |
||||
if k in signatures_ids_proxy: |
||||
if signatures_ids_implem[k] == signatures_ids_proxy[k]: |
||||
implem_function = _get_function_or_variable(self.contract, signatures_ids_implem[k]) |
||||
proxy_function = _get_function_or_variable(self.proxy, signatures_ids_proxy[k]) |
||||
|
||||
info = ['Function shadowing found: ', implem_function, |
||||
' ', proxy_function, '\n'] |
||||
json = self.generate_result(info) |
||||
results.append(json) |
||||
|
||||
return results |
@ -0,0 +1,333 @@ |
||||
import logging |
||||
|
||||
from slither.slithir.operations import InternalCall |
||||
from slither.tools.upgradeability.checks.abstract_checks import AbstractCheck, CheckClassification |
||||
from slither.utils.output import Output |
||||
from slither.utils.colors import red, yellow, green |
||||
|
||||
logger = logging.getLogger("Slither-check-upgradeability") |
||||
|
||||
|
||||
class MultipleInitTarget(Exception): |
||||
pass |
||||
|
||||
|
||||
def _get_initialize_functions(contract): |
||||
return [f for f in contract.functions if f.name == 'initialize' and f.is_implemented] |
||||
|
||||
|
||||
def _get_all_internal_calls(function): |
||||
all_ir = function.all_slithir_operations() |
||||
return [i.function for i in all_ir if isinstance(i, InternalCall) and i.function_name == "initialize"] |
||||
|
||||
|
||||
def _get_most_derived_init(contract): |
||||
init_functions = [f for f in contract.functions if not f.is_shadowed and f.name == 'initialize'] |
||||
if len(init_functions) > 1: |
||||
if len([f for f in init_functions if f.contract_declarer == contract]) == 1: |
||||
return next((f for f in init_functions if f.contract_declarer == contract)) |
||||
raise MultipleInitTarget |
||||
if init_functions: |
||||
return init_functions[0] |
||||
return None |
||||
|
||||
|
||||
class InitializablePresent(AbstractCheck): |
||||
ARGUMENT = 'init-missing' |
||||
IMPACT = CheckClassification.INFORMATIONAL |
||||
|
||||
HELP = 'Initializable is missing' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing' |
||||
WIKI_TITLE = 'Initializable is missing' |
||||
WIKI_DESCRIPTION = ''' |
||||
Detect if a contract `Initializable` is present. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Review manually the contract's initialization.. |
||||
Consider using a `Initializable` contract to follow [standard practice](https://docs.openzeppelin.com/upgrades/2.7/writing-upgradeable). |
||||
''' |
||||
|
||||
def _check(self): |
||||
initializable = self.contract.slither.get_contract_from_name('Initializable') |
||||
if initializable is None: |
||||
info = ["Initializable contract not found, the contract does not follow a standard initalization schema.\n"] |
||||
json = self.generate_result(info) |
||||
return [json] |
||||
return [] |
||||
|
||||
|
||||
class InitializableInherited(AbstractCheck): |
||||
ARGUMENT = 'init-inherited' |
||||
IMPACT = CheckClassification.INFORMATIONAL |
||||
|
||||
HELP = 'Initializable is not inherited' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-not-inherited' |
||||
WIKI_TITLE = 'Initializable is not inherited' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Detect if `Initializable` is inherited. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Review manually the contract's initialization. Consider inheriting `Initializable`. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
|
||||
def _check(self): |
||||
initializable = self.contract.slither.get_contract_from_name('Initializable') |
||||
# See InitializablePresent |
||||
if initializable is None: |
||||
return [] |
||||
if initializable not in self.contract.inheritance: |
||||
info = [self.contract, ' does not inherit from ', initializable, '.\n'] |
||||
json = self.generate_result(info) |
||||
return [json] |
||||
return [] |
||||
|
||||
|
||||
class InitializableInitializer(AbstractCheck): |
||||
ARGUMENT = 'initializer-missing' |
||||
IMPACT = CheckClassification.INFORMATIONAL |
||||
|
||||
HELP = 'initializer() is missing' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializer-is-missing' |
||||
WIKI_TITLE = 'initializer() is missing' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Detect the lack of `Initializable.initializer()` modifier. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Review manually the contract's initialization. Consider inheriting a `Initializable.initializer()` modifier. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
|
||||
def _check(self): |
||||
initializable = self.contract.slither.get_contract_from_name('Initializable') |
||||
# See InitializablePresent |
||||
if initializable is None: |
||||
return [] |
||||
# See InitializableInherited |
||||
if initializable not in self.contract.inheritance: |
||||
return [] |
||||
|
||||
initializer = self.contract.get_modifier_from_canonical_name('Initializable.initializer()') |
||||
if initializer is None: |
||||
info = ['Initializable.initializer() does not exist.\n'] |
||||
json = self.generate_result(info) |
||||
return [json] |
||||
return [] |
||||
|
||||
|
||||
class MissingInitializerModifier(AbstractCheck): |
||||
ARGUMENT = 'missing-init-modifier' |
||||
IMPACT = CheckClassification.HIGH |
||||
|
||||
HELP = 'initializer() is not called' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializer-is-not-called' |
||||
WIKI_TITLE = 'initializer() is not called' |
||||
WIKI_DESCRIPTION = ''' |
||||
Detect if `Initializable.initializer()` is called. |
||||
''' |
||||
|
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract Contract{ |
||||
function initialize() public{ |
||||
/// |
||||
} |
||||
} |
||||
|
||||
``` |
||||
`initialize` should have the `initializer` modifier to prevent someone from initializing the contract multiple times. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Use `Initializable.initializer()`. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
|
||||
def _check(self): |
||||
initializable = self.contract.slither.get_contract_from_name('Initializable') |
||||
# See InitializablePresent |
||||
if initializable is None: |
||||
return [] |
||||
# See InitializableInherited |
||||
if initializable not in self.contract.inheritance: |
||||
return [] |
||||
initializer = self.contract.get_modifier_from_canonical_name('Initializable.initializer()') |
||||
# InitializableInitializer |
||||
if initializer is None: |
||||
return [] |
||||
|
||||
results = [] |
||||
all_init_functions = _get_initialize_functions(self.contract) |
||||
for f in all_init_functions: |
||||
if initializer not in f.modifiers: |
||||
info = [f, ' does not call the initializer modifier.\n'] |
||||
json = self.generate_result(info) |
||||
results.append(json) |
||||
return results |
||||
|
||||
|
||||
class MissingCalls(AbstractCheck): |
||||
ARGUMENT = 'missing-calls' |
||||
IMPACT = CheckClassification.HIGH |
||||
|
||||
HELP = 'Missing calls to init functions' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialize-functions-are-not-called' |
||||
WIKI_TITLE = 'Initialize functions are not called' |
||||
WIKI_DESCRIPTION = ''' |
||||
Detect missing calls to initialize functions. |
||||
''' |
||||
|
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract Base{ |
||||
function initialize() public{ |
||||
/// |
||||
} |
||||
} |
||||
contract Derived is Base{ |
||||
function initialize() public{ |
||||
/// |
||||
} |
||||
} |
||||
|
||||
``` |
||||
`Derived.initialize` does not call `Base.initialize` leading the contract to not be correctly initialized. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Ensure all the initialize functions are reached by the most derived initialize function. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
|
||||
def _check(self): |
||||
results = [] |
||||
|
||||
# TODO: handle MultipleInitTarget |
||||
try: |
||||
most_derived_init = _get_most_derived_init(self.contract) |
||||
except MultipleInitTarget: |
||||
logger.error(red(f'Too many init targets in {self.contract}')) |
||||
return [] |
||||
|
||||
if most_derived_init is None: |
||||
return [] |
||||
|
||||
all_init_functions = _get_initialize_functions(self.contract) |
||||
all_init_functions_called = _get_all_internal_calls(most_derived_init) + [most_derived_init] |
||||
missing_calls = [f for f in all_init_functions if not f in all_init_functions_called] |
||||
for f in missing_calls: |
||||
info = ['Missing call to ', f, ' in ', most_derived_init, '.\n'] |
||||
json = self.generate_result(info) |
||||
results.append(json) |
||||
return results |
||||
|
||||
|
||||
class MultipleCalls(AbstractCheck): |
||||
ARGUMENT = 'multiple-calls' |
||||
IMPACT = CheckClassification.HIGH |
||||
|
||||
HELP = 'Init functions called multiple times' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialize-functions-are-called-multiple-times' |
||||
WIKI_TITLE = 'Initialize functions are called multiple times' |
||||
WIKI_DESCRIPTION = ''' |
||||
Detect multiple calls to a initialize function. |
||||
''' |
||||
|
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract Base{ |
||||
function initialize(uint) public{ |
||||
/// |
||||
} |
||||
} |
||||
contract Derived is Base{ |
||||
function initialize(uint a, uint b) public{ |
||||
initialize(a); |
||||
} |
||||
} |
||||
|
||||
contract DerivedDerived is Derived{ |
||||
function initialize() public{ |
||||
initialize(0); |
||||
initialize(0, 1 ); |
||||
} |
||||
} |
||||
|
||||
``` |
||||
`Base.initialize(uint)` is called two times in `DerivedDerived.initiliaze` execution, leading to a potential corruption. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Call only one time every initialize function. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
|
||||
def _check(self): |
||||
results = [] |
||||
|
||||
# TODO: handle MultipleInitTarget |
||||
try: |
||||
most_derived_init = _get_most_derived_init(self.contract) |
||||
except MultipleInitTarget: |
||||
# Should be already reported by MissingCalls |
||||
#logger.error(red(f'Too many init targets in {self.contract}')) |
||||
return [] |
||||
|
||||
if most_derived_init is None: |
||||
return [] |
||||
|
||||
all_init_functions_called = _get_all_internal_calls(most_derived_init) + [most_derived_init] |
||||
double_calls = list(set([f for f in all_init_functions_called if all_init_functions_called.count(f) > 1])) |
||||
for f in double_calls: |
||||
info = [f, ' is called multiple times in ', most_derived_init, '.\n'] |
||||
json = self.generate_result(info) |
||||
results.append(json) |
||||
|
||||
return results |
||||
|
||||
class InitializeTarget(AbstractCheck): |
||||
ARGUMENT = 'initialize-target' |
||||
IMPACT = CheckClassification.INFORMATIONAL |
||||
|
||||
HELP = 'Initialize function that must be called' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#initialize-function' |
||||
WIKI_TITLE = 'Initialize function' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Show the function that must be called at deployment. |
||||
|
||||
This finding does not have an immediate security impact and is informative. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Ensure that the function is called at deployment. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
|
||||
def _check(self): |
||||
|
||||
# TODO: handle MultipleInitTarget |
||||
try: |
||||
most_derived_init = _get_most_derived_init(self.contract) |
||||
except MultipleInitTarget: |
||||
# Should be already reported by MissingCalls |
||||
#logger.error(red(f'Too many init targets in {self.contract}')) |
||||
return [] |
||||
|
||||
if most_derived_init is None: |
||||
return [] |
||||
|
||||
info = [self.contract, f' needs to be initialized by ', most_derived_init, '.\n'] |
||||
json = self.generate_result(info) |
||||
return [json] |
@ -0,0 +1,38 @@ |
||||
from slither.tools.upgradeability.checks.abstract_checks import CheckClassification, AbstractCheck |
||||
|
||||
|
||||
class VariableWithInit(AbstractCheck): |
||||
ARGUMENT = 'variables-initialized' |
||||
IMPACT = CheckClassification.HIGH |
||||
|
||||
HELP = 'State variables with an initial value' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#state-variable-initialized' |
||||
WIKI_TITLE = 'State variable initialized' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Detect state variables that are initialized. |
||||
''' |
||||
|
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract Contract{ |
||||
uint variable = 10; |
||||
} |
||||
``` |
||||
Using `Contract` will the delegatecall proxy pattern will lead `variable` to be 0 when called through the proxy. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Using initialize functions to write initial values in state variables. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
|
||||
def _check(self): |
||||
results = [] |
||||
for s in self.contract.state_variables: |
||||
if s.initialized and not s.is_constant: |
||||
info = [s, ' is a state variable with an initial value.\n'] |
||||
json = self.generate_result(info) |
||||
results.append(json) |
||||
return results |
@ -0,0 +1,238 @@ |
||||
from slither.tools.upgradeability.checks.abstract_checks import CheckClassification, AbstractCheck |
||||
|
||||
|
||||
class MissingVariable(AbstractCheck): |
||||
ARGUMENT = 'missing-variables' |
||||
IMPACT = CheckClassification.MEDIUM |
||||
|
||||
HELP = 'Variable missing in the v2' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#missing-variables' |
||||
WIKI_TITLE = 'Missing variables' |
||||
WIKI_DESCRIPTION = ''' |
||||
Detect variables that were present in the original contracts but are not in the updated one. |
||||
''' |
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract V1{ |
||||
uint variable1; |
||||
uint variable2; |
||||
} |
||||
|
||||
contract V2{ |
||||
uint variable1; |
||||
} |
||||
``` |
||||
The new version, `V2` does not contain `variable1`. |
||||
If a new variable is added in an update of `V2`, this variable will hold the latest value of `variable2` and |
||||
will be corrupted. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Do not change the order of the state variables in the updated contract. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
REQUIRE_CONTRACT_V2 = True |
||||
|
||||
def _check(self): |
||||
contract1 = self.contract |
||||
contract2 = self.contract_v2 |
||||
order1 = [variable for variable in contract1.state_variables if not variable.is_constant] |
||||
order2 = [variable for variable in contract2.state_variables if not variable.is_constant] |
||||
|
||||
results = [] |
||||
for idx in range(0, len(order1)): |
||||
variable1 = order1[idx] |
||||
if len(order2) <= idx: |
||||
info = ['Variable missing in ', contract2, ': ', variable1, '\n'] |
||||
json = self.generate_result(info) |
||||
results.append(json) |
||||
|
||||
return results |
||||
|
||||
|
||||
class DifferentVariableContractProxy(AbstractCheck): |
||||
ARGUMENT = 'order-vars-proxy' |
||||
IMPACT = CheckClassification.HIGH |
||||
|
||||
HELP = 'Incorrect vars order with the proxy' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#incorrect-variables-with-the-proxy' |
||||
WIKI_TITLE = 'Incorrect variables with the proxy' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Detect variables that are different between the contract and the proxy. |
||||
''' |
||||
|
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract Contract{ |
||||
uint variable1; |
||||
} |
||||
|
||||
contract Proxy{ |
||||
address variable1; |
||||
} |
||||
``` |
||||
`Contract` and `Proxy` do not have the same storage layout. As a result the storage of both contracts can be corrupted. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Avoid variables in the proxy. If a variable is in the proxy, ensure it has the same layout than in the contract. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
REQUIRE_PROXY = True |
||||
|
||||
def _contract1(self): |
||||
return self.contract |
||||
|
||||
def _contract2(self): |
||||
return self.proxy |
||||
|
||||
def _check(self): |
||||
contract1 = self._contract1() |
||||
contract2 = self._contract2() |
||||
order1 = [variable for variable in contract1.state_variables if not variable.is_constant] |
||||
order2 = [variable for variable in contract2.state_variables if not variable.is_constant] |
||||
|
||||
results = [] |
||||
for idx in range(0, len(order1)): |
||||
if len(order2) <= idx: |
||||
# Handle by MissingVariable |
||||
return results |
||||
|
||||
variable1 = order1[idx] |
||||
variable2 = order2[idx] |
||||
if (variable1.name != variable2.name) or (variable1.type != variable2.type): |
||||
info = ['Different variables between ', contract1, ' and ', contract2, '\n'] |
||||
info += [f'\t ', variable1, '\n'] |
||||
info += [f'\t ', variable2, '\n'] |
||||
json = self.generate_result(info) |
||||
results.append(json) |
||||
|
||||
return results |
||||
|
||||
|
||||
class DifferentVariableContractNewContract(DifferentVariableContractProxy): |
||||
ARGUMENT = 'order-vars-contracts' |
||||
|
||||
HELP = 'Incorrect vars order with the v2' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#incorrect-variables-with-the-v2' |
||||
WIKI_TITLE = 'Incorrect variables with the v2' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Detect variables that are different between the original contract and the updated one. |
||||
''' |
||||
|
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract Contract{ |
||||
uint variable1; |
||||
} |
||||
|
||||
contract ContractV2{ |
||||
address variable1; |
||||
} |
||||
``` |
||||
`Contract` and `ContractV2` do not have the same storage layout. As a result the storage of both contracts can be corrupted. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Respect the variable order of the original contract in the updated contract. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
REQUIRE_PROXY = False |
||||
REQUIRE_CONTRACT_V2 = True |
||||
|
||||
def _contract2(self): |
||||
return self.contract_v2 |
||||
|
||||
|
||||
class ExtraVariablesProxy(AbstractCheck): |
||||
ARGUMENT = 'extra-vars-proxy' |
||||
IMPACT = CheckClassification.MEDIUM |
||||
|
||||
HELP = 'Extra vars in the proxy' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#extra-variables-in-the-proxy' |
||||
WIKI_TITLE = 'Extra variables in the proxy' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Detect variables that are in the proxy and not in the contract. |
||||
''' |
||||
|
||||
WIKI_EXPLOIT_SCENARIO = ''' |
||||
```solidity |
||||
contract Contract{ |
||||
uint variable1; |
||||
} |
||||
|
||||
contract Proxy{ |
||||
uint variable1; |
||||
uint variable2; |
||||
} |
||||
``` |
||||
`Proxy` contains additional variables. A future update of `Contract` is likely to corrupt the proxy. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Avoid variables in the proxy. If a variable is in the proxy, ensure it has the same layout than in the contract. |
||||
''' |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
REQUIRE_PROXY = True |
||||
|
||||
def _contract1(self): |
||||
return self.contract |
||||
|
||||
def _contract2(self): |
||||
return self.proxy |
||||
|
||||
def _check(self): |
||||
contract1 = self._contract1() |
||||
contract2 = self._contract2() |
||||
order1 = [variable for variable in contract1.state_variables if not variable.is_constant] |
||||
order2 = [variable for variable in contract2.state_variables if not variable.is_constant] |
||||
|
||||
results = [] |
||||
|
||||
if len(order2) <= len(order1): |
||||
return [] |
||||
|
||||
idx = len(order2) - len(order1) |
||||
|
||||
while idx < len(order2): |
||||
variable2 = order2[idx] |
||||
info = ['Extra variables in ', contract2, ': ', variable2, '\n'] |
||||
json = self.generate_result(info) |
||||
results.append(json) |
||||
idx = idx + 1 |
||||
|
||||
return results |
||||
|
||||
|
||||
class ExtraVariablesNewContract(ExtraVariablesProxy): |
||||
ARGUMENT = 'extra-vars-v2' |
||||
|
||||
HELP = 'Extra vars in the v2' |
||||
WIKI = 'https://github.com/crytic/slither/wiki/Upgradeability-Checks#extra-variables-in-the-v2' |
||||
WIKI_TITLE = 'Extra variables in the v2' |
||||
|
||||
WIKI_DESCRIPTION = ''' |
||||
Show new variables in the updated contract. |
||||
|
||||
This finding does not have an immediate security impact and is informative. |
||||
''' |
||||
|
||||
WIKI_RECOMMENDATION = ''' |
||||
Ensure that all the new variables are expected. |
||||
''' |
||||
|
||||
IMPACT = CheckClassification.INFORMATIONAL |
||||
|
||||
REQUIRE_CONTRACT = True |
||||
REQUIRE_PROXY = False |
||||
REQUIRE_CONTRACT_V2 = True |
||||
|
||||
def _contract2(self): |
||||
return self.contract_v2 |
@ -1,86 +0,0 @@ |
||||
''' |
||||
Check for functions collisions between a proxy and the implementation |
||||
More for information: https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357 |
||||
''' |
||||
|
||||
import logging |
||||
from slither.core.declarations import Function |
||||
from slither.exceptions import SlitherError |
||||
from slither.utils.output import Output |
||||
from slither.utils.function import get_function_id |
||||
from slither.utils.colors import red, green |
||||
|
||||
logger = logging.getLogger("Slither-check-upgradeability") |
||||
|
||||
def get_signatures(c): |
||||
functions = c.functions |
||||
functions = [f.full_name for f in functions if f.visibility in ['public', 'external'] and |
||||
not f.is_constructor and not f.is_fallback] |
||||
|
||||
variables = c.state_variables |
||||
variables = [variable.name+ '()' for variable in variables if variable.visibility in ['public']] |
||||
return list(set(functions+variables)) |
||||
|
||||
|
||||
def _get_function_or_variable(contract, signature): |
||||
f = contract.get_function_from_signature(signature) |
||||
|
||||
if f: |
||||
return f |
||||
|
||||
for variable in contract.state_variables: |
||||
# Todo: can lead to incorrect variable in case of shadowing |
||||
if variable.visibility in ['public']: |
||||
if variable.name + '()' == signature: |
||||
return variable |
||||
|
||||
raise SlitherError(f'Function id checks: {signature} not found in {contract.name}') |
||||
|
||||
def compare_function_ids(implem, proxy): |
||||
|
||||
results = { |
||||
'function-id-collision':[], |
||||
'shadowing':[], |
||||
} |
||||
|
||||
logger.info(green('\n## Run function ids checks... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#functions-ids-checks)')) |
||||
|
||||
signatures_implem = get_signatures(implem) |
||||
signatures_proxy = get_signatures(proxy) |
||||
|
||||
signatures_ids_implem = {get_function_id(s): s for s in signatures_implem} |
||||
signatures_ids_proxy = {get_function_id(s): s for s in signatures_proxy} |
||||
|
||||
error_found = False |
||||
for (k, _) in signatures_ids_implem.items(): |
||||
if k in signatures_ids_proxy: |
||||
error_found = True |
||||
if signatures_ids_implem[k] != signatures_ids_proxy[k]: |
||||
|
||||
implem_function = _get_function_or_variable(implem, signatures_ids_implem[k]) |
||||
proxy_function = _get_function_or_variable(proxy, signatures_ids_proxy[k]) |
||||
|
||||
info = f'Function id collision found: {implem_function.canonical_name} ({implem_function.source_mapping_str}) {proxy_function.canonical_name} ({proxy_function.source_mapping_str})' |
||||
logger.info(red(info)) |
||||
res = Output(info) |
||||
res.add(implem_function) |
||||
res.add(proxy_function) |
||||
results['function-id-collision'].append(res.data) |
||||
|
||||
else: |
||||
|
||||
implem_function = _get_function_or_variable(implem, signatures_ids_implem[k]) |
||||
proxy_function = _get_function_or_variable(proxy, signatures_ids_proxy[k]) |
||||
|
||||
info = f'Shadowing between {implem_function.canonical_name} ({implem_function.source_mapping_str}) and {proxy_function.canonical_name} ({proxy_function.source_mapping_str})' |
||||
logger.info(red(info)) |
||||
|
||||
res = Output(info) |
||||
res.add(implem_function) |
||||
res.add(proxy_function) |
||||
results['shadowing'].append(res.data) |
||||
|
||||
if not error_found: |
||||
logger.info(green('No error found')) |
||||
|
||||
return results |
@ -1,73 +0,0 @@ |
||||
''' |
||||
Check if the variables respect the same ordering |
||||
''' |
||||
import logging |
||||
|
||||
from slither.utils.output import Output |
||||
from slither.utils.colors import red, green, yellow |
||||
|
||||
logger = logging.getLogger("Slither-check-upgradeability") |
||||
|
||||
|
||||
def compare_variables_order(contract1, contract2, missing_variable_check=True): |
||||
|
||||
results = { |
||||
'missing_variables': [], |
||||
'different-variables': [], |
||||
'extra-variables': [] |
||||
} |
||||
|
||||
logger.info(green( |
||||
f'\n## Run variables ordering checks between {contract1.name} and {contract2.name}... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks#variables-order-checks)')) |
||||
|
||||
order1 = [variable for variable in contract1.state_variables if not variable.is_constant] |
||||
order2 = [variable for variable in contract2.state_variables if not variable.is_constant] |
||||
|
||||
error_found = False |
||||
idx = 0 |
||||
for idx in range(0, len(order1)): |
||||
variable1 = order1[idx] |
||||
if len(order2) <= idx: |
||||
if missing_variable_check: |
||||
info = f'Variable only in {contract1.name}: {variable1.name} ({variable1.source_mapping_str})' |
||||
logger.info(yellow(info)) |
||||
|
||||
res = Output(info) |
||||
res.add(variable1) |
||||
results['missing_variables'].append(res.data) |
||||
|
||||
error_found = True |
||||
continue |
||||
|
||||
variable2 = order2[idx] |
||||
|
||||
if (variable1.name != variable2.name) or (variable1.type != variable2.type): |
||||
info = f'Different variables between {contract1.name} and {contract2.name}:\n' |
||||
info += f'\t Variable {idx} in {contract1.name}: {variable1.name} {variable1.type} ({variable1.source_mapping_str})\n' |
||||
info += f'\t Variable {idx} in {contract2.name}: {variable2.name} {variable2.type} ({variable2.source_mapping_str})\n' |
||||
logger.info(red(info)) |
||||
|
||||
res = Output(info, additional_fields={'index': idx}) |
||||
res.add(variable1) |
||||
res.add(variable2) |
||||
results['different-variables'].append(res.data) |
||||
|
||||
error_found = True |
||||
|
||||
idx = idx + 1 |
||||
|
||||
while idx < len(order2): |
||||
variable2 = order2[idx] |
||||
|
||||
info = f'Extra variables in {contract2.name}: {variable2.name} ({variable2.source_mapping_str})\n' |
||||
logger.info(yellow(info)) |
||||
res = Output(info, additional_fields={'index': idx}) |
||||
res.add(variable2) |
||||
results['extra-variables'].append(res.data) |
||||
idx = idx + 1 |
||||
|
||||
if not error_found: |
||||
logger.info(green('No error found')) |
||||
|
||||
return results |
||||
|
@ -1,85 +0,0 @@ |
||||
import logging |
||||
|
||||
from slither.utils.output import Output |
||||
from slither.utils.colors import red, yellow, green |
||||
|
||||
logger = logging.getLogger("Slither-check-upgradeability") |
||||
|
||||
def constant_conformance_check(contract_v1, contract_v2): |
||||
|
||||
results = { |
||||
"became_constants": [], |
||||
"were_constants": [], |
||||
"not_found_in_v2": [], |
||||
} |
||||
|
||||
logger.info(green( |
||||
'\n## Run variable constants conformance check... (see https://github.com/crytic/slither/wiki/Upgradeability-Checks)')) |
||||
error_found = False |
||||
|
||||
state_variables_v1 = contract_v1.state_variables |
||||
state_variables_v2 = contract_v2.state_variables |
||||
|
||||
v2_additional_variables = len(state_variables_v2) - len(state_variables_v1) |
||||
if v2_additional_variables < 0: |
||||
v2_additional_variables = 0 |
||||
|
||||
# We keep two index, because we need to have them out of sync if v2 |
||||
# has additional non constant variables |
||||
idx_v1 = 0 |
||||
idx_v2 = 0 |
||||
while idx_v1 < len(state_variables_v1): |
||||
|
||||
state_v1 = contract_v1.state_variables[idx_v1] |
||||
if len(state_variables_v2) <= idx_v2: |
||||
break |
||||
|
||||
state_v2 = contract_v2.state_variables[idx_v2] |
||||
|
||||
if state_v2: |
||||
if state_v1.is_constant: |
||||
if not state_v2.is_constant: |
||||
|
||||
# If v2 has additional non constant variables, we need to skip them |
||||
if (state_v1.name != state_v2.name or state_v1.type != state_v2.type) and v2_additional_variables>0: |
||||
v2_additional_variables -= 1 |
||||
idx_v2 += 1 |
||||
continue |
||||
|
||||
info = f'{state_v1.canonical_name} ({state_v1.source_mapping_str}) was constant and {state_v2.canonical_name} is not ({state_v2.source_mapping_str})' |
||||
logger.info(red(info)) |
||||
|
||||
res = Output(info) |
||||
res.add(state_v1) |
||||
res.add(state_v2) |
||||
results['were_constants'].append(res.data) |
||||
error_found = True |
||||
|
||||
elif state_v2.is_constant: |
||||
info = f'{state_v1.canonical_name} ({state_v1.source_mapping_str}) was not constant but {state_v2.canonical_name} is ({state_v2.source_mapping_str})' |
||||
logger.info(red(info)) |
||||
|
||||
res = Output(info) |
||||
res.add(state_v1) |
||||
res.add(state_v2) |
||||
results['became_constants'].append(res.data) |
||||
error_found = True |
||||
|
||||
else: |
||||
info = f'{state_v1.canonical_name} not found in {contract_v2.name}, not check was done' |
||||
logger.info(yellow(info)) |
||||
|
||||
res = Output(info) |
||||
res.add(state_v1) |
||||
res.add(contract_v2) |
||||
results['not_found_in_v2'].append(res.data) |
||||
|
||||
error_found = True |
||||
|
||||
idx_v1 += 1 |
||||
idx_v2 += 1 |
||||
|
||||
if not error_found: |
||||
logger.info(green('No error found')) |
||||
|
||||
return results |
@ -0,0 +1,126 @@ |
||||
from prettytable import PrettyTable |
||||
|
||||
from slither.tools.upgradeability.checks.abstract_checks import classification_txt |
||||
|
||||
|
||||
def output_wiki(detector_classes, filter_wiki): |
||||
# Sort by impact, confidence, and name |
||||
detectors_list = sorted(detector_classes, key=lambda element: (element.IMPACT, element.ARGUMENT)) |
||||
|
||||
for detector in detectors_list: |
||||
if filter_wiki not in detector.WIKI: |
||||
continue |
||||
argument = detector.ARGUMENT |
||||
impact = classification_txt[detector.IMPACT] |
||||
title = detector.WIKI_TITLE |
||||
description = detector.WIKI_DESCRIPTION |
||||
exploit_scenario = detector.WIKI_EXPLOIT_SCENARIO |
||||
recommendation = detector.WIKI_RECOMMENDATION |
||||
|
||||
print('\n## {}'.format(title)) |
||||
print('### Configuration') |
||||
print('* Check: `{}`'.format(argument)) |
||||
print('* Severity: `{}`'.format(impact)) |
||||
print('\n### Description') |
||||
print(description) |
||||
if exploit_scenario: |
||||
print('\n### Exploit Scenario:') |
||||
print(exploit_scenario) |
||||
print('\n### Recommendation') |
||||
print(recommendation) |
||||
|
||||
|
||||
def output_detectors(detector_classes): |
||||
detectors_list = [] |
||||
for detector in detector_classes: |
||||
argument = detector.ARGUMENT |
||||
help_info = detector.HELP |
||||
impact = detector.IMPACT |
||||
require_proxy = detector.REQUIRE_PROXY |
||||
require_v2 = detector.REQUIRE_CONTRACT_V2 |
||||
detectors_list.append((argument, help_info, impact, require_proxy, require_v2)) |
||||
table = PrettyTable(["Num", |
||||
"Check", |
||||
"What it Detects", |
||||
"Impact", |
||||
"Proxy", |
||||
"Contract V2"]) |
||||
|
||||
# Sort by impact, confidence, and name |
||||
detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[0])) |
||||
idx = 1 |
||||
for (argument, help_info, impact, proxy, v2) in detectors_list: |
||||
table.add_row([idx, argument, help_info, classification_txt[impact], 'X' if proxy else '', 'X' if v2 else '']) |
||||
idx = idx + 1 |
||||
print(table) |
||||
|
||||
|
||||
def output_to_markdown(detector_classes, filter_wiki): |
||||
def extract_help(cls): |
||||
if cls.WIKI == '': |
||||
return cls.HELP |
||||
return '[{}]({})'.format(cls.HELP, cls.WIKI) |
||||
|
||||
detectors_list = [] |
||||
for detector in detector_classes: |
||||
argument = detector.ARGUMENT |
||||
help_info = extract_help(detector) |
||||
impact = detector.IMPACT |
||||
require_proxy = detector.REQUIRE_PROXY |
||||
require_v2 = detector.REQUIRE_CONTRACT_V2 |
||||
detectors_list.append((argument, help_info, impact, require_proxy, require_v2)) |
||||
table = PrettyTable(["Num", |
||||
"Check", |
||||
"What it Detects", |
||||
"Impact", |
||||
"Proxy", |
||||
"Contract V2"]) |
||||
|
||||
# Sort by impact, confidence, and name |
||||
detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[0])) |
||||
idx = 1 |
||||
for (argument, help_info, impact, proxy, v2) in detectors_list: |
||||
print('{} | `{}` | {} | {} | {} | {}'.format(idx, |
||||
argument, |
||||
help_info, |
||||
classification_txt[impact], |
||||
'X' if proxy else '', |
||||
'X' if v2 else '')) |
||||
idx = idx + 1 |
||||
|
||||
|
||||
|
||||
|
||||
def output_detectors_json(detector_classes): |
||||
detectors_list = [] |
||||
for detector in detector_classes: |
||||
argument = detector.ARGUMENT |
||||
help_info = detector.HELP |
||||
impact = detector.IMPACT |
||||
wiki_url = detector.WIKI |
||||
wiki_description = detector.WIKI_DESCRIPTION |
||||
wiki_exploit_scenario = detector.WIKI_EXPLOIT_SCENARIO |
||||
wiki_recommendation = detector.WIKI_RECOMMENDATION |
||||
detectors_list.append((argument, |
||||
help_info, |
||||
impact, |
||||
wiki_url, |
||||
wiki_description, |
||||
wiki_exploit_scenario, |
||||
wiki_recommendation)) |
||||
|
||||
# Sort by impact, confidence, and name |
||||
detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[0])) |
||||
idx = 1 |
||||
table = [] |
||||
for (argument, help_info, impact, wiki_url, description, exploit, recommendation) in detectors_list: |
||||
table.append({'index': idx, |
||||
'detector': argument, |
||||
'title': help_info, |
||||
'impact': classification_txt[impact], |
||||
'wiki_url': wiki_url, |
||||
'description': description, |
||||
'exploit_scenario':exploit, |
||||
'recommendation':recommendation}) |
||||
idx = idx + 1 |
||||
return table |
Loading…
Reference in new issue