Merge branch 'dev' of github.com:crytic/slither into dev

pull/1559/head^2
Feist Josselin 2 years ago
commit cee26bcccb
  1. 9
      .github/workflows/IR.yml
  2. 5
      .github/workflows/ci.yml
  3. 3
      .github/workflows/detectors.yml
  4. 3
      .github/workflows/features.yml
  5. 5
      Dockerfile
  6. 26
      slither/solc_parsing/slither_compilation_unit_solc.py
  7. 7
      slither/tools/documentation/README.md
  8. 160
      slither/tools/documentation/__main__.py
  9. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-compact.zip
  10. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-legacy.zip
  11. 1
      tests/ast-parsing/complex_imports/import_aliases_issue_1319/import.sol
  12. 9
      tests/ast-parsing/complex_imports/import_aliases_issue_1319/test.sol
  13. 9
      tests/ast-parsing/complex_imports/import_aliases_issue_1319/test_fail.sol
  14. 6
      tests/ast-parsing/expected/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-compact.json
  15. 6
      tests/ast-parsing/expected/complex_imports/import_aliases_issue_1319/test.sol-0.5.12-legacy.json
  16. 3
      tests/source_unit/README.md
  17. 1
      tests/source_unit/lib/forge-std
  18. 1
      tests/test_ast_parsing.py
  19. 12
      tests/test_source_unit.py

@ -34,8 +34,13 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
pip install ".[dev]" pip install ".[dev]"
solc-select install all solc-select install 0.5.0
solc-select use 0.8.11 solc-select use 0.8.11 --always-install
- name: Install old solc
if: matrix.os == 'ubuntu-latest'
run: solc-select install 0.4.0
- name: Test with pytest - name: Test with pytest
run: | run: |

@ -55,8 +55,9 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
pip install ".[dev]" pip install ".[dev]"
solc-select install all solc-select use 0.4.25 --always-install
solc-select use 0.5.1 solc-select use 0.8.0 --always-install
solc-select use 0.5.1 --always-install
pip install typing_extensions==4.1.1 pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3 pip install importlib_metadata==4.8.3

@ -35,8 +35,7 @@ jobs:
run: | run: |
pip install ".[dev]" pip install ".[dev]"
solc-select install all solc-select use 0.7.3 --always-install
solc-select use 0.7.3
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest tests/test_detectors.py pytest tests/test_detectors.py

@ -35,8 +35,7 @@ jobs:
run: | run: |
pip install ".[dev]" pip install ".[dev]"
solc-select install all solc-select use 0.8.0 --always-install
solc-select use 0.8.0
cd tests/test_node_modules/ cd tests/test_node_modules/
npm install hardhat npm install hardhat

@ -2,6 +2,7 @@
FROM ubuntu:jammy AS python-wheels FROM ubuntu:jammy AS python-wheels
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gcc \ gcc \
git \
python3-dev \ python3-dev \
python3-pip \ python3-pip \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@ -9,7 +10,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
COPY . /slither COPY . /slither
RUN cd /slither && \ RUN cd /slither && \
echo pip3 install --no-cache-dir --upgrade pip && \ pip3 install --no-cache-dir --upgrade pip && \
pip3 wheel -w /wheels . solc-select pip setuptools wheel pip3 wheel -w /wheels . solc-select pip setuptools wheel
@ -44,7 +45,7 @@ ENV PATH="/home/slither/.local/bin:${PATH}"
# no-index ensures we install the freshly-built wheels # no-index ensures we install the freshly-built wheels
RUN --mount=type=bind,target=/mnt,source=/wheels,from=python-wheels \ RUN --mount=type=bind,target=/mnt,source=/wheels,from=python-wheels \
pip3 install --user --no-cache-dir --upgrade --no-index --find-links /mnt pip slither-analyzer solc-select pip3 install --user --no-cache-dir --upgrade --no-index --find-links /mnt --no-deps /mnt/*.whl
RUN solc-select install 0.4.25 && solc-select use 0.4.25 RUN solc-select install 0.4.25 && solc-select use 0.4.25

@ -48,16 +48,22 @@ def _handle_import_aliases(
""" """
for symbol_alias in symbol_aliases: for symbol_alias in symbol_aliases:
if ( if "foreign" in symbol_alias and "local" in symbol_alias:
"foreign" in symbol_alias if isinstance(symbol_alias["foreign"], dict) and "name" in symbol_alias["foreign"]:
and "name" in symbol_alias["foreign"]
and "local" in symbol_alias original_name = symbol_alias["foreign"]["name"]
): local_name = symbol_alias["local"]
original_name = symbol_alias["foreign"]["name"] import_directive.renaming[local_name] = original_name
local_name = symbol_alias["local"] # Assuming that two imports cannot collide in renaming
import_directive.renaming[local_name] = original_name scope.renaming[local_name] = original_name
# Assuming that two imports cannot collide in renaming
scope.renaming[local_name] = original_name # This path should only be hit for the malformed AST of solc 0.5.12 where
# the foreign identifier cannot be found but is required to resolve the alias.
# see https://github.com/crytic/slither/issues/1319
elif symbol_alias["local"]:
raise SlitherException(
"Cannot resolve local alias for import directive due to malformed AST. Please upgrade to solc 0.6.0 or higher."
)
class SlitherCompilationUnitSolc(CallerContextExpression): class SlitherCompilationUnitSolc(CallerContextExpression):

@ -1,6 +1,5 @@
# Demo # slither-documentation
This directory contains an example of Slither utility. `slither-documentation` uses [codex](https://beta.openai.com) to generate natspec documenation.
See the [utility documentation](https://github.com/crytic/slither/wiki/Adding-a-new-utility)
This tool is experimental. See [solmate documentation](https://github.com/montyly/solmate/pull/1) for an example of usage.

@ -36,7 +36,12 @@ def parse_args() -> argparse.Namespace:
default=False, default=False,
) )
parser.add_argument("--retry", help="Retry failed query (default 1). Each retry increases the temperature by 0.1", action="store", default=1) parser.add_argument(
"--retry",
help="Retry failed query (default 1). Each retry increases the temperature by 0.1",
action="store",
default=1,
)
# Add default arguments from crytic-compile # Add default arguments from crytic-compile
cryticparser.init(parser) cryticparser.init(parser)
@ -122,7 +127,75 @@ def _handle_codex(
return None return None
# pylint: disable=too-many-locals # pylint: disable=too-many-locals,too-many-arguments
def _handle_function(
function: Function,
overwrite: bool,
all_patches: Dict,
logging_file: Optional[str],
slither: Slither,
retry: int,
force: bool,
) -> bool:
if (
function.source_mapping.is_dependency
or function.has_documentation
or function.is_constructor_variables
):
return overwrite
prompt = "Create a natpsec documentation for this solidity code with only notice and dev.\n"
src_mapping = function.source_mapping
content = function.compilation_unit.core.source_code[src_mapping.filename.absolute]
start = src_mapping.start
end = src_mapping.start + src_mapping.length
prompt += content[start:end]
use_tab = _use_tab(content[start - 1])
if use_tab is None and src_mapping.starting_column > 1:
logger.info(f"Non standard space indentation found {content[start - 1:end]}")
if overwrite:
logger.info("Disable overwrite to avoid mistakes")
overwrite = False
openai = codex.openai_module() # type: ignore
if openai is None:
raise ImportError
if logging_file:
codex.log_codex(logging_file, "Q: " + prompt)
tentative = 0
answer_processed: Optional[str] = None
while tentative < retry:
tentative += 1
answer = openai.Completion.create( # type: ignore
prompt=prompt,
model=slither.codex_model,
temperature=min(slither.codex_temperature + tentative * 0.1, 1),
max_tokens=slither.codex_max_tokens,
)
if logging_file:
codex.log_codex(logging_file, "A: " + str(answer))
answer_processed = _handle_codex(answer, src_mapping.starting_column, use_tab, force)
if answer_processed:
break
logger.info(
f"Codex could not generate a well formatted answer for {function.canonical_name}"
)
logger.info(answer)
if not answer_processed:
return overwrite
create_patch(all_patches, src_mapping.filename.absolute, start, start, "", answer_processed)
return overwrite
def _handle_compilation_unit( def _handle_compilation_unit(
slither: Slither, slither: Slither,
compilation_unit: SlitherCompilationUnit, compilation_unit: SlitherCompilationUnit,
@ -130,12 +203,15 @@ def _handle_compilation_unit(
force: bool, force: bool,
retry: int, retry: int,
) -> None: ) -> None:
logging_file: Optional[str]
logging_file = str(uuid.uuid4()) if slither.codex_log:
logging_file = str(uuid.uuid4())
else:
logging_file = None
for scope in compilation_unit.scopes.values(): for scope in compilation_unit.scopes.values():
# TODO remove hardcoded filtering # Dont send tests file
if ( if (
".t.sol" in scope.filename.absolute ".t.sol" in scope.filename.absolute
or "mock" in scope.filename.absolute.lower() or "mock" in scope.filename.absolute.lower()
@ -153,63 +229,8 @@ def _handle_compilation_unit(
all_patches: Dict = {} all_patches: Dict = {}
for function in functions_target: for function in functions_target:
overwrite = _handle_function(
if function.source_mapping.is_dependency or function.has_documentation or function.is_constructor_variables: function, overwrite, all_patches, logging_file, slither, retry, force
continue
prompt = (
"Create a natpsec documentation for this solidity code with only notice and dev.\n"
)
src_mapping = function.source_mapping
content = compilation_unit.core.source_code[src_mapping.filename.absolute]
start = src_mapping.start
end = src_mapping.start + src_mapping.length
prompt += content[start:end]
use_tab = _use_tab(content[start - 1])
if use_tab is None and src_mapping.starting_column > 1:
logger.info(f"Non standard space indentation found {content[start-1:end]}")
if overwrite:
logger.info("Disable overwrite to avoid mistakes")
overwrite = False
openai = codex.openai_module() # type: ignore
if openai is None:
return
if slither.codex_log:
codex.log_codex(logging_file, "Q: " + prompt)
tentative = 0
answer_processed: Optional[str] = None
while tentative < retry:
tentative += 1
answer = openai.Completion.create( # type: ignore
prompt=prompt,
model=slither.codex_model,
temperature=min(slither.codex_temperature + tentative*0.1, 1),
max_tokens=slither.codex_max_tokens,
)
if slither.codex_log:
codex.log_codex(logging_file, "A: " + str(answer))
answer_processed = _handle_codex(
answer, src_mapping.starting_column, use_tab, force
)
if answer_processed:
break
logger.info(
f"Codex could not generate a well formatted answer for {function.canonical_name}"
)
logger.info(answer)
if not answer_processed:
continue
create_patch(
all_patches, src_mapping.filename.absolute, start, start, "", answer_processed
) )
# all_patches["patches"] should have only 1 file # all_patches["patches"] should have only 1 file
@ -242,10 +263,17 @@ def main() -> None:
logger.info("Be aware of OpenAI ToS: https://openai.com/api/policies/terms/") logger.info("Be aware of OpenAI ToS: https://openai.com/api/policies/terms/")
slither = Slither(args.project, **vars(args)) slither = Slither(args.project, **vars(args))
for compilation_unit in slither.compilation_units: try:
_handle_compilation_unit( for compilation_unit in slither.compilation_units:
slither, compilation_unit, args.overwrite, args.force_answer_parsing, int(args.retry) _handle_compilation_unit(
) slither,
compilation_unit,
args.overwrite,
args.force_answer_parsing,
int(args.retry),
)
except ImportError:
pass
if __name__ == "__main__": if __name__ == "__main__":

@ -0,0 +1,9 @@
pragma solidity 0.5.12;
import {A} from "./import.sol";
contract Z is A {
function test() public pure returns (uint) {
return 1;
}
}

@ -0,0 +1,9 @@
pragma solidity 0.5.12;
import {A as X, A as Y} from "./import.sol";
contract Z is X {
function test() public pure returns (uint) {
return 1;
}
}

@ -0,0 +1,6 @@
{
"A": {},
"Z": {
"test()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
}
}

@ -0,0 +1,6 @@
{
"A": {},
"Z": {
"test()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
}
}

@ -0,0 +1,3 @@
# README
Before using this project, run `forge init --no-git --no-commit --force` to initialize submodules

@ -1 +0,0 @@
Subproject commit eb980e1d4f0e8173ec27da77297ae411840c8ccb

@ -438,6 +438,7 @@ ALL_TESTS = [
Test("using-for-global-0.8.0.sol", ["0.8.15"]), Test("using-for-global-0.8.0.sol", ["0.8.15"]),
Test("library_event-0.8.16.sol", ["0.8.16"]), Test("library_event-0.8.16.sol", ["0.8.16"]),
Test("top-level-struct-0.8.0.sol", ["0.8.0"]), Test("top-level-struct-0.8.0.sol", ["0.8.0"]),
Test("complex_imports/import_aliases_issue_1319/test.sol", ["0.5.12"]),
] ]
# create the output folder if needed # create the output folder if needed
try: try:

@ -1,6 +1,18 @@
from pathlib import Path
import shutil
import pytest
from slither import Slither from slither import Slither
# NB: read tests/source_unit/README.md for setup before using this test
foundry_available = shutil.which("forge") is not None
project_ready = Path("./tests/source_unit/lib/forge-std").exists()
@pytest.mark.skipif(
not foundry_available or not project_ready, reason="requires Foundry and project setup"
)
def test_contract_info() -> None: def test_contract_info() -> None:
slither = Slither("./tests/source_unit") slither = Slither("./tests/source_unit")

Loading…
Cancel
Save