Merge pull request #6999 from ethereum/develop

Merge develop into release for 0.5.10
This commit is contained in:
chriseth 2019-06-25 16:03:50 +02:00 committed by GitHub
commit 5a6ea5b197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
244 changed files with 7059 additions and 2423 deletions

21
.circleci/README.md Normal file
View File

@ -0,0 +1,21 @@
## CircleCI integration
### Docker images
The docker images are build locally on the developer machine:
```!sh
cd .circleci/docker/
docker build -t ethereum/solc-buildpack-deps:ubuntu1904 -f Dockerfile.ubuntu1904 .
docker push solidity/solc-buildpack-deps:ubuntu1904
docker build -t ethereum/solc-buildpack-deps:archlinux -f Dockerfile.archlinux .
docker push solidity/solc-buildpack-deps:archlinux
```
which you can find on Dockerhub after the push at:
https://hub.docker.com/r/ethereum/solidity-buildpack-deps
where the image tag reflects the target OS to build Solidity and run its test on.

View File

@ -1,48 +1,72 @@
# vim:ts=2:sw=2:et
# --------------------------------------------------------------------------
# Prefixes used in order to keep CircleCI workflow overview more readable:
# - b: build
# - t: test
# - ubu: ubuntu
# - ems: Emscripten
version: 2
defaults: defaults:
# The default for tags is to not run, so we have to explicitly match a filter.
- build_on_tags: &build_on_tags # --------------------------------------------------------------------------
filters: # Build Templates
tags:
only: /.*/
- setup_prerelease_commit_hash: &setup_prerelease_commit_hash - setup_prerelease_commit_hash: &setup_prerelease_commit_hash
name: Store commit hash and prerelease name: Store commit hash and prerelease
command: | command: |
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
echo -n "$CIRCLE_SHA1" > commit_hash.txt echo -n "$CIRCLE_SHA1" > commit_hash.txt
- run_build: &run_build - run_build: &run_build
name: Build name: Build
command: | command: |
set -ex
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
echo -n "$CIRCLE_SHA1" > commit_hash.txt
mkdir -p build mkdir -p build
cd build cd build
[ -n "$COVERAGE" -a "$CIRCLE_BRANCH" != release -a -z "$CIRCLE_TAG" ] && CMAKE_OPTIONS="$CMAKE_OPTIONS -DCOVERAGE=ON" [ -n "$COVERAGE" -a "$CIRCLE_BRANCH" != release -a -z "$CIRCLE_TAG" ] && CMAKE_OPTIONS="$CMAKE_OPTIONS -DCOVERAGE=ON"
cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS -G "Unix Makefiles"
make -j4 make -j4
- run_build_ossfuzz: &run_build_ossfuzz - run_build_ossfuzz: &run_build_ossfuzz
name: Build_ossfuzz name: Build_ossfuzz
command: | command: |
mkdir -p build mkdir -p build
cd build cd build
/src/LPM/external.protobuf/bin/protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz
cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS
make ossfuzz ossfuzz_proto -j4 make ossfuzz ossfuzz_proto -j4
- run_tests: &run_tests
name: Tests - run_proofs: &run_proofs
command: scripts/tests.sh --junit_report test_results name: Correctness proofs for optimization rules
- run_regressions: &run_regressions command: scripts/run_proofs.sh
name: Regression tests
command: | # --------------------------------------------------------------------------
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" # Artifacts Templates
scripts/regressions.py -o test_results
- solc_artifact: &solc_artifact # the whole build directory
- artifacts_build_dir: &artifacts_build_dir
root: build
paths:
- "*"
# compiled solc executable target
- artifacts_solc: &artifacts_solc
path: build/solc/solc path: build/solc/solc
destination: solc destination: solc
- all_artifacts: &all_artifacts
# compiled executable targets
- artifacts_executables: &artifacts_executables
root: build root: build
paths: paths:
- solc/solc - solc/solc
- test/soltest - test/soltest
- test/tools/solfuzzer - test/tools/solfuzzer
- ossfuzz_artifacts: &ossfuzz_artifacts
# compiled OSSFUZZ targets
- artifacts_executables_ossfuzz: &artifacts_executables_ossfuzz
root: build root: build
paths: paths:
- test/tools/ossfuzz/const_opt_ossfuzz - test/tools/ossfuzz/const_opt_ossfuzz
@ -54,9 +78,280 @@ defaults:
- test/tools/ossfuzz/yul_proto_diff_ossfuzz - test/tools/ossfuzz/yul_proto_diff_ossfuzz
- test/tools/ossfuzz/yul_proto_ossfuzz - test/tools/ossfuzz/yul_proto_ossfuzz
version: 2 # test result output directory
- artifacts_test_results: &artifacts_test_results
path: test_results/
destination: test_results/
# --------------------------------------------------------------------------
# Tests Templates
# store_test_results helper
- store_test_results: &store_test_results
path: test_results/
- run_soltest: &run_soltest
name: soltest
command: ./.circleci/soltest.sh
- run_cmdline_tests: &run_cmdline_tests
name: command line tests
command: ./test/cmdlineTests.sh
- test_steps: &test_steps
- checkout
- attach_workspace:
at: build
- run: *run_soltest
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
- test_ubuntu1904: &test_ubuntu1904
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904
steps: *test_steps
- test_asan: &test_asan
<<: *test_ubuntu1904
steps:
- checkout
- attach_workspace:
at: build
- run:
<<: *run_soltest
no_output_timeout: 30m
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
# --------------------------------------------------------------------------
# Workflow Templates
- workflow_trigger_on_tags: &workflow_trigger_on_tags
filters:
tags:
only: /.*/
- workflow_ubuntu1904: &workflow_ubuntu1904
<<: *workflow_trigger_on_tags
requires:
- b_ubu
- workflow_ubuntu1904_codecov: &workflow_ubuntu1904_codecov
<<: *workflow_trigger_on_tags
requires:
- b_ubu_codecov
- workflow_osx: &workflow_osx
<<: *workflow_trigger_on_tags
requires:
- b_osx
- workflow_ubuntu1904_asan: &workflow_ubuntu1904_asan
<<: *workflow_trigger_on_tags
requires:
- b_ubu_asan
- workflow_emscripten: &workflow_emscripten
<<: *workflow_trigger_on_tags
requires:
- b_ems
- workflow_ubuntu1904_ossfuzz: &workflow_ubuntu1904_ossfuzz
<<: *workflow_trigger_on_tags
requires:
- b_ubu_ossfuzz
# -----------------------------------------------------------------------------------------------
jobs: jobs:
build_emscripten:
chk_spelling:
docker:
- image: circleci/python:3.6
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Install dependencies
command: |
pip install --user codespell
- run:
name: Check spelling
command: ~/.local/bin/codespell -S "*.enc,.git" -I ./scripts/codespell_whitelist.txt
chk_coding_style:
docker:
- image: buildpack-deps:disco
steps:
- checkout
- run:
name: Check for C++ coding style
command: ./scripts/check_style.sh
chk_buglist:
docker:
- image: circleci/node
environment:
TERM: xterm
steps:
- checkout
- run:
name: JS deps
command: |
npm install download
npm install JSONPath
npm install mktemp
- run:
name: Test buglist
command: ./test/buglistTests.js
chk_proofs:
docker:
- image: buildpack-deps:disco
environment:
TERM: xterm
steps:
- checkout
- run:
name: Z3 python deps
command: |
apt-get -qq update
apt-get -qy install python-pip
pip install --user z3-solver
- run: *run_proofs
b_ubu: &build_ubuntu1904
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904
steps:
- checkout
- run: *run_build
- store_artifacts: *artifacts_solc
- persist_to_workspace: *artifacts_executables
b_ubu_codecov:
<<: *build_ubuntu1904
environment:
COVERAGE: ON
CMAKE_BUILD_TYPE: Debug
steps:
- checkout
- run: *run_build
- persist_to_workspace: *artifacts_build_dir
t_ubu_codecov:
<<: *test_ubuntu1904
environment:
EVM: constantinople
OPTIMIZE: 1
steps:
- checkout
- attach_workspace:
at: build
- run:
name: "soltest: Syntax Tests"
command: build/test/soltest -t 'syntaxTest*' -- --no-ipc --testpath test
- run:
name: "Code Coverage: Syntax Tests"
command: codecov --flags syntax --gcov-root build
- run: *run_soltest
- run:
name: "Coverage: All"
command: codecov --flags all --gcov-root build
- store_artifacts: *artifacts_test_results
# Builds in C++17 mode and uses debug build in order to speed up.
# Do *NOT* store any artifacts or workspace as we don't run tests on this build.
b_ubu_cxx17:
<<: *build_ubuntu1904
environment:
CMAKE_BUILD_TYPE: Debug
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx17.cmake -DUSE_CVC4=OFF
steps:
- checkout
- run: *run_build
b_ubu_ossfuzz:
<<: *build_ubuntu1904
environment:
TERM: xterm
CC: /usr/bin/clang-8
CXX: /usr/bin/clang++-8
CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
steps:
- checkout
- run: *setup_prerelease_commit_hash
- run: *run_build_ossfuzz
- persist_to_workspace: *artifacts_executables_ossfuzz
t_ubu_ossfuzz: &t_ubu_ossfuzz
<<: *test_ubuntu1904
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Regression tests
command: |
mkdir -p test_results
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
scripts/regressions.py -o test_results
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
b_archlinux:
docker:
- image: ethereum/solidity-buildpack-deps:archlinux
environment:
TERM: xterm
steps:
- checkout
- run: *run_build
- store_artifacts: *artifacts_solc
- persist_to_workspace: *artifacts_executables
b_osx:
macos:
xcode: "10.0.0"
environment:
TERM: xterm
CMAKE_BUILD_TYPE: Debug
CMAKE_OPTIONS: -DLLL=ON
steps:
- checkout
- run:
name: Install build dependencies
command: |
brew unlink python
brew install z3
brew install boost
brew install cmake
brew install wget
./scripts/install_obsolete_jsoncpp_1_7_4.sh
- run: *run_build
- store_artifacts: *artifacts_solc
- persist_to_workspace: *artifacts_executables
t_osx_cli:
macos:
xcode: "10.0.0"
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Install dependencies
command: |
brew unlink python
brew install z3
- run: *run_cmdline_tests
- store_artifacts: *artifacts_test_results
b_ems:
docker: docker:
- image: trzeci/emscripten:sdk-tag-1.38.22-64bit - image: trzeci/emscripten:sdk-tag-1.38.22-64bit
environment: environment:
@ -78,7 +373,7 @@ jobs:
name: Save Boost build name: Save Boost build
key: *boost-cache-key key: *boost-cache-key
paths: paths:
- boost_1_68_0 - boost_1_70_0_install
- store_artifacts: - store_artifacts:
path: emscripten_build/libsolc/soljson.js path: emscripten_build/libsolc/soljson.js
destination: soljson.js destination: soljson.js
@ -91,7 +386,123 @@ jobs:
- soljson.js - soljson.js
- version.txt - version.txt
test_emscripten_solcjs: # x64 ASAN build, for testing for memory related bugs
b_ubu_asan: &b_ubu_asan
<<: *build_ubuntu1904
environment:
CMAKE_OPTIONS: -DSANITIZE=address
CMAKE_BUILD_TYPE: Release
steps:
- checkout
- run: *run_build
- store_artifacts: *artifacts_solc
- persist_to_workspace: *artifacts_executables
b_docs:
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904
steps:
- checkout
- run: *setup_prerelease_commit_hash
- run:
name: Build documentation
command: ./scripts/docs.sh
- store_artifacts:
path: docs/_build/html/
destination: docs-html
t_ubu_cli: &t_ubu_cli
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: build
- run: *run_cmdline_tests
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
t_ubu_asan_cli:
<<: *t_ubu_cli
environment:
TERM: xterm
ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2
steps:
- checkout
- attach_workspace:
at: build
- run:
<<: *run_cmdline_tests
no_output_timeout: 30m
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
t_ubu_asan_constantinople:
<<: *test_asan
environment:
EVM: constantinople
OPTIMIZE: 0
SOLTEST_IPC: 0
ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2
t_ubu_homestead:
<<: *test_ubuntu1904
environment:
EVM: homestead
OPTIMIZE: 0
t_ubu_homestead_opt:
<<: *test_ubuntu1904
environment:
EVM: homestead
OPTIMIZE: 1
t_ubu_byzantium:
<<: *test_ubuntu1904
environment:
EVM: byzantium
OPTIMIZE: 0
t_ubu_byzantium_opt:
<<: *test_ubuntu1904
environment:
EVM: byzantium
OPTIMIZE: 1
t_ubu_constantinople:
<<: *test_ubuntu1904
environment:
EVM: constantinople
OPTIMIZE: 0
t_ubu_constantinople_opt:
<<: *test_ubuntu1904
environment:
EVM: constantinople
OPTIMIZE: 1
t_ubu_constantinople_opt_abiv2:
<<: *test_ubuntu1904
environment:
EVM: constantinople
OPTIMIZE: 1
ABI_ENCODER_V2: 1
t_ubu_petersburg:
<<: *test_ubuntu1904
environment:
EVM: petersburg
OPTIMIZE: 0
t_ubu_petersburg_opt:
<<: *test_ubuntu1904
environment:
EVM: petersburg
OPTIMIZE: 1
t_ems_solcjs:
docker: docker:
- image: circleci/node:10 - image: circleci/node:10
environment: environment:
@ -101,16 +512,13 @@ jobs:
- attach_workspace: - attach_workspace:
at: /tmp/workspace at: /tmp/workspace
- run: - run:
name: Install external tests deps name: Test solcjs
command: | command: |
node --version node --version
npm --version npm --version
- run:
name: Test solcjs
command: |
test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt)
test_emscripten_external_gnosis: t_ems_external_gnosis:
docker: docker:
- image: circleci/node:10 - image: circleci/node:10
environment: environment:
@ -124,7 +532,7 @@ jobs:
command: | command: |
test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js
test_emscripten_external_zeppelin: t_ems_external_zeppelin:
docker: docker:
- image: circleci/node:10 - image: circleci/node:10
environment: environment:
@ -138,7 +546,7 @@ jobs:
command: | command: |
test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js
test_emscripten_external_colony: t_ems_external_colony:
docker: docker:
- image: circleci/node:10 - image: circleci/node:10
environment: environment:
@ -156,370 +564,51 @@ jobs:
command: | command: |
test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js
build_x86_linux:
docker:
- image: buildpack-deps:bionic
environment:
TERM: xterm
COVERAGE: "ON"
steps:
- checkout
- run:
name: Install build dependencies
command: |
apt-get -qq update
apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\*
- run: *setup_prerelease_commit_hash
- run: *run_build
- store_artifacts: *solc_artifact
- persist_to_workspace:
root: build
paths:
- "*"
build_x86_linux_cxx17:
docker:
- image: buildpack-deps:disco
environment:
TERM: xterm
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx17.cmake
steps:
- checkout
- run:
name: Install build dependencies
command: |
apt-get -qq update
apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\*
- run: *setup_prerelease_commit_hash
- run: *run_build
build_x86_archlinux:
docker:
- image: archlinux/base
environment:
TERM: xterm
steps:
- run:
name: Install build dependencies
command: |
pacman --noconfirm -Syu --noprogressbar --needed base-devel boost cmake z3 cvc4 git openssh tar
- checkout
- run: *setup_prerelease_commit_hash
- run: *run_build
- store_artifacts: *solc_artifact
- persist_to_workspace:
root: build
paths:
- solc/solc
- test/soltest
- test/tools/solfuzzer
build_x86_clang7_asan:
docker:
- image: buildpack-deps:cosmic
environment:
TERM: xterm
CC: /usr/bin/clang-7
CXX: /usr/bin/clang++-7
CMAKE_OPTIONS: -DSANITIZE=address -DCMAKE_BUILD_TYPE=Debug
steps:
- checkout
- run:
name: Install build dependencies
command: |
apt-get -qq update
apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\*
- run: *setup_prerelease_commit_hash
- run: *run_build
- store_artifacts: *solc_artifact
- persist_to_workspace:
root: build
paths:
- solc/solc
- test/soltest
- test/tools/solfuzzer
build_x86_mac:
macos:
xcode: "10.0.0"
environment:
TERM: xterm
CMAKE_OPTIONS: -DLLL=ON
steps:
- checkout
- run:
name: Install build dependencies
command: |
brew unlink python
brew install z3
brew install boost
brew install cmake
brew install wget
./scripts/install_obsolete_jsoncpp_1_7_4.sh
- run: *setup_prerelease_commit_hash
- run: *run_build
- store_artifacts: *solc_artifact
- persist_to_workspace: *all_artifacts
test_check_spelling:
docker:
- image: circleci/python:3.6
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Install dependencies
command: |
pip install --user codespell
- run:
name: Check spelling
command: ~/.local/bin/codespell -S "*.enc,.git" -I ./scripts/codespell_whitelist.txt
test_check_style:
docker:
- image: buildpack-deps:bionic
steps:
- checkout
- run:
name: Check for trailing whitespace
command: ./scripts/check_style.sh
test_buglist:
docker:
- image: circleci/node
environment:
TERM: xterm
steps:
- checkout
- run:
name: JS deps
command: |
npm install download
npm install JSONPath
npm install mktemp
- run:
name: Test buglist
command: ./test/buglistTests.js
test_x86_linux:
docker:
- image: buildpack-deps:bionic
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Install dependencies
command: |
apt-get -qq update
apt-get -qy install libcvc4-dev libleveldb1v5 python-pip
pip install codecov
- run: mkdir -p test_results
- run:
name: Test type checker
command: build/test/soltest -t 'syntaxTest*' -- --no-ipc --testpath test
- run:
name: Coverage of type checker
command: codecov --flags syntax --gcov-root build
- run: *run_tests
- run:
name: Coverage of all
command: codecov --flags all --gcov-root build
- store_test_results:
path: test_results/
- store_artifacts:
path: test_results/
destination: test_results/
test_x86_clang7_asan:
docker:
- image: buildpack-deps:cosmic
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Install dependencies
command: |
apt-get -qq update
apt-get -qy install llvm-7-dev libcvc4-dev libleveldb1v5 python-pip
# This is needed to resolve the symbols. Since we're using clang7 in the build, we must use the appropriate symbolizer.
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1
- run: mkdir -p test_results
- run:
name: Run soltest with ASAN
command: |
ulimit -a
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
ulimit -s 16384
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test
- run:
name: Run commandline tests with ASAN
command: |
ulimit -a
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
ulimit -s 16384
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
test/cmdlineTests.sh
- store_test_results:
path: test_results/
- store_artifacts:
path: test_results/
destination: test_results/
test_x86_archlinux:
docker:
- image: archlinux/base
environment:
TERM: xterm
steps:
- run:
name: Install dependencies
command: |
pacman --noconfirm -Syu --noprogressbar --needed boost z3 cvc4 git openssh tar
- checkout
- attach_workspace:
at: build
- run: mkdir -p test_results
- run: build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test
- store_test_results:
path: test_results/
- store_artifacts:
path: test_results/
destination: test_results/
test_x86_mac:
macos:
xcode: "10.0.0"
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Install dependencies
command: |
brew unlink python
brew install z3
- run: mkdir -p test_results
- run: *run_tests
- store_test_results:
path: test_results/
- store_artifacts:
path: test_results/
destination: test_results/
docs:
docker:
- image: buildpack-deps:bionic
environment:
DEBIAN_FRONTEND: noninteractive
steps:
- checkout
- run:
name: Install build dependencies
command: |
apt-get -qq update
apt-get -qy install python-sphinx python-pip
- run: *setup_prerelease_commit_hash
- run:
name: Build documentation
command: ./scripts/docs.sh
- store_artifacts:
path: docs/_build/html/
destination: docs-html
build_x86_linux_ossfuzz:
docker:
- image: buildpack-deps:disco
environment:
TERM: xterm
CC: /usr/bin/clang-8
CXX: /usr/bin/clang++-8
CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
steps:
- checkout
- run:
name: Install build dependencies
command: |
apt-get -qq update
apt-get -qy install wget clang-8 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\*
./scripts/install_lpm.sh
./scripts/install_libfuzzer.sh
# Install evmone and dependencies (intx and ethash)
./scripts/install_evmone.sh
- run: *setup_prerelease_commit_hash
- run: *run_build_ossfuzz
- persist_to_workspace: *ossfuzz_artifacts
test_x86_ossfuzz_regression:
docker:
- image: buildpack-deps:disco
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Install dependencies
command: |
apt-get -qq update
apt-get -qy install libcvc4-dev llvm-8-dev
./scripts/download_ossfuzz_corpus.sh
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1
- run: mkdir -p test_results
- run: *run_regressions
- store_artifacts:
path: test_results/
destination: test_results/
workflows: workflows:
version: 2 version: 2
build_all:
jobs:
- test_check_spelling: *build_on_tags
- test_check_style: *build_on_tags
- test_buglist: *build_on_tags
- build_emscripten: *build_on_tags
- test_emscripten_solcjs:
<<: *build_on_tags
requires:
- build_emscripten
- build_x86_linux: *build_on_tags
- build_x86_linux_cxx17: *build_on_tags
- build_x86_clang7_asan: *build_on_tags
- build_x86_mac: *build_on_tags
- test_x86_linux:
<<: *build_on_tags
requires:
- build_x86_linux
- test_x86_clang7_asan:
<<: *build_on_tags
requires:
- build_x86_clang7_asan
- test_x86_mac:
<<: *build_on_tags
requires:
- build_x86_mac
- docs: *build_on_tags
- build_x86_archlinux: *build_on_tags
- test_x86_archlinux:
<<: *build_on_tags
requires:
- build_x86_archlinux
- build_x86_linux_ossfuzz: *build_on_tags
test_nightly: main:
jobs:
# basic checks
- chk_spelling: *workflow_trigger_on_tags
- chk_coding_style: *workflow_trigger_on_tags
- chk_buglist: *workflow_trigger_on_tags
- chk_proofs: *workflow_trigger_on_tags
# build-only
- b_docs: *workflow_trigger_on_tags
- b_archlinux: *workflow_trigger_on_tags
- b_ubu_cxx17: *workflow_trigger_on_tags
- b_ubu_ossfuzz: *workflow_trigger_on_tags
# OS/X build and tests
- b_osx: *workflow_trigger_on_tags
- t_osx_cli: *workflow_osx
# Ubuntu 18.10 build and tests
- b_ubu: *workflow_trigger_on_tags
- t_ubu_cli: *workflow_ubuntu1904
- t_ubu_homestead: *workflow_ubuntu1904
- t_ubu_homestead_opt: *workflow_ubuntu1904
- t_ubu_byzantium: *workflow_ubuntu1904
- t_ubu_byzantium_opt: *workflow_ubuntu1904
- t_ubu_constantinople: *workflow_ubuntu1904
- t_ubu_constantinople_opt: *workflow_ubuntu1904
- t_ubu_constantinople_opt_abiv2: *workflow_ubuntu1904
- t_ubu_petersburg: *workflow_ubuntu1904
- t_ubu_petersburg_opt: *workflow_ubuntu1904
# ASan build and tests
- b_ubu_asan: *workflow_trigger_on_tags
- t_ubu_asan_constantinople: *workflow_ubuntu1904_asan
- t_ubu_asan_cli: *workflow_ubuntu1904_asan
# Emscripten build and selected tests
- b_ems: *workflow_trigger_on_tags
- t_ems_solcjs: *workflow_emscripten
nightly:
triggers: triggers:
- schedule: - schedule:
cron: "0 0 * * *" cron: "0 0 * * *"
@ -527,23 +616,19 @@ workflows:
branches: branches:
only: only:
- develop - develop
jobs:
- build_emscripten: *build_on_tags jobs:
- test_emscripten_external_zeppelin: # Emscripten builds and external tests
<<: *build_on_tags - b_ems: *workflow_trigger_on_tags
requires: - t_ems_external_zeppelin: *workflow_emscripten
- build_emscripten - t_ems_external_gnosis: *workflow_emscripten
- test_emscripten_external_gnosis: - t_ems_external_colony: *workflow_emscripten
<<: *build_on_tags
requires: # OSSFUZZ builds and (regression) tests
- build_emscripten - b_ubu_ossfuzz: *workflow_trigger_on_tags
- test_emscripten_external_colony: - t_ubu_ossfuzz: *workflow_ubuntu1904_ossfuzz
<<: *build_on_tags
requires: # Code Coverage enabled build and tests
- build_emscripten - b_ubu_codecov: *workflow_trigger_on_tags
- build_x86_linux_ossfuzz: *build_on_tags - t_ubu_codecov: *workflow_ubuntu1904_codecov
- test_x86_ossfuzz_regression:
<<: *build_on_tags
requires:
- build_x86_linux_ossfuzz

View File

@ -0,0 +1,28 @@
# vim:syntax=dockerfile
#------------------------------------------------------------------------------
# Dockerfile for building and testing Solidity Compiler on CI
# Target: Arch Linux
# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps
#
# This file is part of solidity.
#
# solidity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# solidity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
# (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------
FROM archlinux/base
RUN pacman --noconfirm -Syu --noprogressbar --needed \
base-devel boost cmake z3 cvc4 git openssh tar

View File

@ -0,0 +1,124 @@
# vim:syntax=dockerfile
#------------------------------------------------------------------------------
# Dockerfile for building and testing Solidity Compiler on CI
# Target: Ubuntu 19.04 (Disco Dingo)
# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps
#
# This file is part of solidity.
#
# solidity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# solidity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
# (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------
FROM buildpack-deps:disco
ARG DEBIAN_FRONTEND=noninteractive
RUN set -ex; \
apt-get update; \
apt-get install -qqy --no-install-recommends \
build-essential \
software-properties-common \
cmake ninja-build clang++-8 \
libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev \
libboost-program-options-dev \
libjsoncpp-dev \
llvm-8-dev libcvc4-dev libleveldb1d \
; \
apt-get install -qy python-pip python-sphinx; \
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1; \
pip install codecov; \
rm -rf /var/lib/apt/lists/*
# Aleth for end-to-end tests
ARG ALETH_VERSION="1.6.0"
ARG ALETH_HASH="7f7004e1563299bc57882e32b32e4a195747dfb6"
ARG ALETH_URL="https://github.com/ethereum/aleth/releases/download/v${ALETH_VERSION}/aleth-${ALETH_VERSION}-linux-x86_64.tar.gz"
RUN set -ex; \
wget -q -O /tmp/aleth.tar.gz "${ALETH_URL}"; \
test "$(shasum /tmp/aleth.tar.gz)" = "$ALETH_HASH /tmp/aleth.tar.gz"; \
tar -xf /tmp/aleth.tar.gz -C /usr
# Z3
RUN set -ex; \
git clone --depth=1 --branch="Z3-4.8.5" https://github.com/Z3Prover/z3.git /usr/src/z3; \
mkdir /usr/src/z3/build; \
cd /usr/src/z3/build; \
cmake -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="/usr" -G "Ninja" ..; \
ninja; \
ninja install/strip; \
rm -rf /usr/src/z3
# OSSFUZZ: LPM package (do not remove build dirs as solidity compiles/links against that dir)
RUN set -ex; \
mkdir /src; \
cd /src; \
git clone https://github.com/google/libprotobuf-mutator.git; \
cd libprotobuf-mutator; \
git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \
mkdir ../LPM; \
cd ../LPM; \
cmake ../libprotobuf-mutator -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release; \
ninja; \
cp -vpr external.protobuf/bin/* /usr/bin/; \
cp -vpr external.protobuf/include/* /usr/include/; \
cp -vpr external.protobuf/lib/* /usr/lib/; \
ninja install/strip
# OSSFUZZ: libfuzzer
RUN set -ex; \
cd /var/tmp; \
svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer libfuzzer; \
mkdir -p build-libfuzzer; \
cd build-libfuzzer; \
clang++-8 -O1 -stdlib=libstdc++ -std=c++11 -O2 -fPIC -c ../libfuzzer/*.cpp -I../libfuzzer; \
ar r /usr/lib/libFuzzingEngine.a *.o; \
rm -rf /var/lib/libfuzzer
# ETHASH
RUN set -ex; \
cd /usr/src; \
git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \
cd ethash; \
mkdir build; \
cd build; \
cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \
ninja; \
ninja install/strip; \
rm -rf /usr/src/ethash
# INTX
RUN set -ex; \
cd /usr/src; \
git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \
cd intx; \
mkdir build; \
cd build; \
cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \
ninja; \
ninja install/strip; \
rm -rf /usr/src/intx;
# EVMONE
RUN set -ex; \
cd /usr/src; \
git clone --branch="v0.1.0" --recurse-submodules https://github.com/chfast/evmone.git; \
cd evmone; \
mkdir build; \
cd build; \
cmake -G Ninja -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \
ninja; \
ninja install/strip; \
rm -rf /usr/src/evmone

65
.circleci/soltest.sh Executable file
View File

@ -0,0 +1,65 @@
#! /bin/bash
#------------------------------------------------------------------------------
# Bash script to execute the Solidity tests by CircleCI.
#
# The documentation for solidity is hosted at:
#
# https://solidity.readthedocs.org
#
# ------------------------------------------------------------------------------
# Configuration Environment Variables:
#
# EVM=version_string Specifies EVM version to compile for (such as homestead, etc)
# OPTIMIZE=1 Enables backend optimizer
# ABI_ENCODER_V2=1 Enables ABI encoder version 2
#
# ------------------------------------------------------------------------------
# This file is part of solidity.
#
# solidity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# solidity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
# (c) 2016-2019 solidity contributors.
# ------------------------------------------------------------------------------
set -e
OPTIMIZE=${OPTIMIZE:-"0"}
EVM=${EVM:-"invalid"}
WORKDIR=${CIRCLE_WORKING_DIRECTORY:-.}
SOLTEST_IPC=${SOLTEST_IPC:-1}
REPODIR="$(realpath $(dirname $0)/..)"
ALETH_PATH="/usr/bin/aleth"
source "${REPODIR}/scripts/common.sh"
# Test result output directory (CircleCI is reading test results from here)
mkdir -p test_results
ALETH_PID=$(run_aleth)
function cleanup() {
safe_kill $ALETH_PID $ALETH_PATH
}
trap cleanup INT TERM
# in case we run with ASAN enabled, we must increase stck size.
ulimit -s 16384
BOOST_TEST_ARGS="--color_output=no --show_progress=yes --logger=JUNIT,error,test_results/$EVM.xml"
SOLTEST_ARGS="--evm-version=$EVM --ipcpath "${WORKDIR}/geth.ipc" $flags"
test "${SOLTEST_IPC}" = "1" || SOLTEST_ARGS="$SOLTEST_ARGS --no-ipc"
test "${OPTIMIZE}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --optimize"
test "${ABI_ENCODER_V2}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --abiencoderv2 --optimize-yul"
echo "Running ${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS} -- ${SOLTEST_ARGS}"
${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS} -- ${SOLTEST_ARGS}

View File

@ -183,7 +183,7 @@ git:
cache: cache:
ccache: true ccache: true
directories: directories:
- boost_1_68_0 - boost_1_70_0_install
- $HOME/.local - $HOME/.local
install: install:

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0.0) cmake_minimum_required(VERSION 3.5.0)
set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake" CACHE PATH "The the path to the cmake directory") set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake" CACHE PATH "The the path to the cmake directory")
list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR}) list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR})
@ -10,9 +10,20 @@ include(EthPolicy)
eth_policy() eth_policy()
# project name and version should be set after cmake_policy CMP0048 # project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.5.9") set(PROJECT_VERSION "0.5.10")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX) project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX)
if (${CMAKE_VERSION} VERSION_LESS "3.9.0")
# needed for the big endian test for older cmake versions
enable_language(C)
endif()
include(TestBigEndian)
TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
if (IS_BIG_ENDIAN)
message(FATAL_ERROR "${PROJECT_NAME} currently does not support big endian systems.")
endif()
option(LLL "Build LLL" OFF) option(LLL "Build LLL" OFF)
option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF)
option(LLLC_LINK_STATIC "Link lllc executable statically on supported platforms" OFF) option(LLLC_LINK_STATIC "Link lllc executable statically on supported platforms" OFF)

View File

@ -1,3 +1,29 @@
### 0.5.10 (2019-06-25)
Important Bugfixes:
* ABIEncoderV2: Fix incorrect abi encoding of storage array of data type that occupy multiple storage slots
* Code Generator: Properly zero out higher order bits in elements of an array of negative numbers when assigning to storage and converting the type at the same time.
Compiler Features:
* Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch.
* Optimizer: Add rule to simplify ``SUB(~0, X)`` to ``NOT(X)``.
* Yul Optimizer: Make the optimizer work for all dialects of Yul including eWasm.
Bugfixes:
* Type Checker: Set state mutability of the function type members ``gas`` and ``value`` to pure (while their return type inherits state mutability from the function type).
* Yul / Inline Assembly Parser: Disallow trailing commas in function call arguments.
Build System:
* Attempt to use stock Z3 cmake files to find Z3 and only fall back to manual discovery.
* CMake: use imported targets for boost.
* Emscripten build: upgrade to boost 1.70.
* Generate a cmake error for gcc versions older than 5.0.
### 0.5.9 (2019-05-28) ### 0.5.9 (2019-05-28)
Language Features: Language Features:
@ -21,7 +47,6 @@ Compiler Features:
* Yul Optimizer: Do not inline recursive functions. * Yul Optimizer: Do not inline recursive functions.
* Yul Optimizer: Do not remove instructions that affect ``msize()`` if ``msize()`` is used. * Yul Optimizer: Do not remove instructions that affect ``msize()`` if ``msize()`` is used.
Bugfixes: Bugfixes:
* Code Generator: Explicitly turn uninitialized internal function pointers into invalid functions when loaded from storage. * Code Generator: Explicitly turn uninitialized internal function pointers into invalid functions when loaded from storage.
* Code Generator: Fix assertion failure when assigning structs containing array of mapping. * Code Generator: Fix assertion failure when assigning structs containing array of mapping.
@ -61,6 +86,7 @@ Compiler Features:
* Yul: Adds break and continue keywords to for-loop syntax. * Yul: Adds break and continue keywords to for-loop syntax.
* Yul: Support ``.`` as part of identifiers. * Yul: Support ``.`` as part of identifiers.
* Yul Optimizer: Adds steps for detecting and removing of dead code. * Yul Optimizer: Adds steps for detecting and removing of dead code.
* Yul Code Generator: Directly jump over a series of function definitions (instead of jumping over each one)
Bugfixes: Bugfixes:

View File

@ -41,11 +41,11 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
# Additional GCC-specific compiler settings. # Additional GCC-specific compiler settings.
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
# Check that we've got GCC 4.7 or newer. # Check that we've got GCC 5.0 or newer.
execute_process( execute_process(
COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
if (NOT (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)) if (NOT (GCC_VERSION VERSION_GREATER 5.0 OR GCC_VERSION VERSION_EQUAL 5.0))
message(FATAL_ERROR "${PROJECT_NAME} requires g++ 4.7 or greater.") message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.")
endif () endif ()
# Additional Clang-specific compiler settings. # Additional Clang-specific compiler settings.

View File

@ -1,21 +1,6 @@
# all dependencies that are not directly included in the cpp-ethereum distribution are defined here # all dependencies that are not directly included in the cpp-ethereum distribution are defined here
# for this to work, download the dependency via the cmake script in extdep or install them manually! # for this to work, download the dependency via the cmake script in extdep or install them manually!
function(eth_show_dependency DEP NAME)
get_property(DISPLAYED GLOBAL PROPERTY ETH_${DEP}_DISPLAYED)
if (NOT DISPLAYED)
set_property(GLOBAL PROPERTY ETH_${DEP}_DISPLAYED TRUE)
if (NOT("${${DEP}_VERSION}" STREQUAL ""))
message(STATUS "${NAME} version: ${${DEP}_VERSION}")
endif()
message(STATUS "${NAME} headers: ${${DEP}_INCLUDE_DIRS}")
message(STATUS "${NAME} lib : ${${DEP}_LIBRARIES}")
if (NOT("${${DEP}_DLLS}" STREQUAL ""))
message(STATUS "${NAME} dll : ${${DEP}_DLLS}")
endif()
endif()
endfunction()
if (DEFINED MSVC) if (DEFINED MSVC)
# by defining CMAKE_PREFIX_PATH variable, cmake will look for dependencies first in our own repository before looking in system paths like /usr/local/ ... # by defining CMAKE_PREFIX_PATH variable, cmake will look for dependencies first in our own repository before looking in system paths like /usr/local/ ...
# this must be set to point to the same directory as $ETH_DEPENDENCY_INSTALL_DIR in /extdep directory # this must be set to point to the same directory as $ETH_DEPENDENCY_INSTALL_DIR in /extdep directory
@ -41,6 +26,29 @@ set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts)
set(Boost_USE_MULTITHREADED ON) set(Boost_USE_MULTITHREADED ON)
option(Boost_USE_STATIC_LIBS "Link Boost statically" ON) option(Boost_USE_STATIC_LIBS "Link Boost statically" ON)
find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS regex filesystem unit_test_framework program_options system) set(BOOST_COMPONENTS "regex;filesystem;unit_test_framework;program_options;system")
eth_show_dependency(Boost boost) find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS})
# If cmake is older than boost and boost is older than 1.70,
# find_package does not define imported targets, so we have to
# define them manually.
if (NOT TARGET Boost::boost) # header only target
add_library(Boost::boost INTERFACE IMPORTED)
set_property(TARGET Boost::boost APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS})
endif()
get_property(LOCATION TARGET Boost::boost PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
message(STATUS "Found Boost headers in ${LOCATION}")
foreach (BOOST_COMPONENT IN LISTS BOOST_COMPONENTS)
if (NOT TARGET Boost::${BOOST_COMPONENT})
add_library(Boost::${BOOST_COMPONENT} UNKNOWN IMPORTED)
string(TOUPPER ${BOOST_COMPONENT} BOOST_COMPONENT_UPPER)
set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARY})
set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_LINK_LIBRARIES ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARIES})
set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS})
endif()
get_property(LOCATION TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION)
message(STATUS "Found Boost::${BOOST_COMPONENT} at ${LOCATION}")
endforeach()

View File

@ -1,29 +1,45 @@
if (USE_Z3) if (USE_Z3)
find_path(Z3_INCLUDE_DIR NAMES z3++.h PATH_SUFFIXES z3) # Save and clear Z3_FIND_VERSION, since the
find_library(Z3_LIBRARY NAMES z3) # Z3 config module cannot handle version requirements.
find_program(Z3_EXECUTABLE z3 PATH_SUFFIXES bin) set(Z3_FIND_VERSION_ORIG ${Z3_FIND_VERSION})
set(Z3_FIND_VERSION)
if(Z3_INCLUDE_DIR AND Z3_LIBRARY AND Z3_EXECUTABLE) # Try to find Z3 using its stock cmake files.
execute_process (COMMAND ${Z3_EXECUTABLE} -version find_package(Z3 QUIET CONFIG)
OUTPUT_VARIABLE libz3_version_str # Restore Z3_FIND_VERSION for find_package_handle_standard_args.
ERROR_QUIET set(Z3_FIND_VERSION ${Z3_FIND_VERSION_ORIG})
OUTPUT_STRIP_TRAILING_WHITESPACE) set(Z3_FIND_VERSION_ORIG)
string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1"
Z3_VERSION_STRING "${libz3_version_str}")
unset(libz3_version_str)
endif()
mark_as_advanced(Z3_VERSION_STRING z3_DIR)
include(FindPackageHandleStandardArgs) include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Z3
REQUIRED_VARS Z3_LIBRARY Z3_INCLUDE_DIR
VERSION_VAR Z3_VERSION_STRING)
if (NOT TARGET Z3::Z3) if (Z3_FOUND)
add_library(Z3::Z3 UNKNOWN IMPORTED) set(Z3_VERSION ${Z3_VERSION_STRING})
set_property(TARGET Z3::Z3 PROPERTY IMPORTED_LOCATION ${Z3_LIBRARY}) find_package_handle_standard_args(Z3 CONFIG_MODE)
set_property(TARGET Z3::Z3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Z3_INCLUDE_DIR}) else()
find_path(Z3_INCLUDE_DIR NAMES z3++.h PATH_SUFFIXES z3)
find_library(Z3_LIBRARY NAMES z3)
find_program(Z3_EXECUTABLE z3 PATH_SUFFIXES bin)
if(Z3_INCLUDE_DIR AND Z3_LIBRARY AND Z3_EXECUTABLE)
execute_process (COMMAND ${Z3_EXECUTABLE} -version
OUTPUT_VARIABLE libz3_version_str
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1"
Z3_VERSION_STRING "${libz3_version_str}")
unset(libz3_version_str)
endif()
mark_as_advanced(Z3_VERSION_STRING z3_DIR)
find_package_handle_standard_args(Z3
REQUIRED_VARS Z3_LIBRARY Z3_INCLUDE_DIR
VERSION_VAR Z3_VERSION_STRING)
if (NOT TARGET z3::libz3)
add_library(z3::libz3 UNKNOWN IMPORTED)
set_property(TARGET z3::libz3 PROPERTY IMPORTED_LOCATION ${Z3_LIBRARY})
set_property(TARGET z3::libz3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Z3_INCLUDE_DIR})
endif()
endif() endif()
else() else()
set(Z3_FOUND FALSE) set(Z3_FOUND FALSE)

View File

@ -415,7 +415,8 @@ Local Solidity variables are available for assignments, for example:
To be safe, always clear the data properly before you use it To be safe, always clear the data properly before you use it
in a context where this is important: in a context where this is important:
``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }`` ``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }``
To clean signed types, you can use the ``signextend`` opcode. To clean signed types, you can use the ``signextend`` opcode:
``assembly { signextend(<num_bytes_of_x_minus_one>, x) }``
Labels Labels
------ ------
@ -595,21 +596,21 @@ of their block is reached.
Conventions in Solidity Conventions in Solidity
----------------------- -----------------------
In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits, In contrast to EVM assembly, Solidity has types which are narrower than 256 bits,
e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that types can be shorter than 256
treat them as 256-bit numbers and the higher-order bits are only cleaned at the bits, and the higher-order bits are cleaned when necessary,
point where it is necessary, i.e. just shortly before they are written to memory i.e., shortly before they are written to memory or before comparisons are performed.
or before comparisons are performed. This means that if you access such a variable This means that if you access such a variable
from within inline assembly, you might have to manually clean the higher order bits from within inline assembly, you might have to manually clean the higher-order bits
first. first.
Solidity manages memory in a very simple way: There is a "free memory pointer" Solidity manages memory in the following way. There is a "free memory pointer"
at position ``0x40`` in memory. If you want to allocate memory, just use the memory at position ``0x40`` in memory. If you want to allocate memory, use the memory
starting from where this pointer points at and update it accordingly. starting from where this pointer points at and update it.
There is no guarantee that the memory has not been used before and thus There is no guarantee that the memory has not been used before and thus
you cannot assume that its contents are zero bytes. you cannot assume that its contents are zero bytes.
There is no built-in mechanism to release or free allocated memory. There is no built-in mechanism to release or free allocated memory.
Here is an assembly snippet that can be used for allocating memory:: Here is an assembly snippet you can use for allocating memory that follows the process outlined above::
function allocate(length) -> pos { function allocate(length) -> pos {
pos := mload(0x40) pos := mload(0x40)
@ -617,13 +618,13 @@ Here is an assembly snippet that can be used for allocating memory::
} }
The first 64 bytes of memory can be used as "scratch space" for short-term The first 64 bytes of memory can be used as "scratch space" for short-term
allocation. The 32 bytes after the free memory pointer (i.e. starting at ``0x60``) allocation. The 32 bytes after the free memory pointer (i.e., starting at ``0x60``)
is meant to be zero permanently and is used as the initial value for are meant to be zero permanently and is used as the initial value for
empty dynamic memory arrays. empty dynamic memory arrays.
This means that the allocatable memory starts at ``0x80``, which is the initial value This means that the allocatable memory starts at ``0x80``, which is the initial value
of the free memory pointer. of the free memory pointer.
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this is
even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory
arrays are pointers to memory arrays. The length of a dynamic array is stored at the arrays are pointers to memory arrays. The length of a dynamic array is stored at the
first slot of the array and followed by the array elements. first slot of the array and followed by the array elements.
@ -631,7 +632,7 @@ first slot of the array and followed by the array elements.
.. warning:: .. warning::
Statically-sized memory arrays do not have a length field, but it might be added later Statically-sized memory arrays do not have a length field, but it might be added later
to allow better convertibility between statically- and dynamically-sized arrays, so to allow better convertibility between statically- and dynamically-sized arrays, so
please do not rely on that. do not rely on this.
Standalone Assembly Standalone Assembly

View File

@ -1,4 +1,23 @@
[ [
{
"name": "SignedArrayStorageCopy",
"summary": "Assigning an array of signed integers to a storage array of different type can lead to data corruption in that array.",
"description": "In two's complement, negative integers have their higher order bits set. In order to fit into a shared storage slot, these have to be set to zero. When a conversion is done at the same time, the bits to set to zero were incorrectly determined from the source and not the target type. This means that such copy operations can lead to incorrect values being stored.",
"introduced": "0.4.7",
"fixed": "0.5.10",
"severity": "low/medium"
},
{
"name": "ABIEncoderV2StorageArrayWithMultiSlotElement",
"summary": "Storage arrays containing structs or other statically-sized arrays are not read properly when directly encoded in external function calls or in abi.encode*.",
"description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in external function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable or if the storage array only contains value types or dynamically-sized arrays.",
"introduced": "0.4.16",
"fixed": "0.5.10",
"severity": "low",
"conditions": {
"ABIEncoderV2": true
}
},
{ {
"name": "DynamicConstructorArgumentsClippedABIV2", "name": "DynamicConstructorArgumentsClippedABIV2",
"summary": "A contract's constructor that takes structs or arrays that contain dynamically-sized arrays reverts or decodes to invalid data.", "summary": "A contract's constructor that takes structs or arrays that contain dynamically-sized arrays reverts or decodes to invalid data.",

View File

@ -380,6 +380,7 @@
}, },
"0.4.10": { "0.4.10": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -394,6 +395,7 @@
}, },
"0.4.11": { "0.4.11": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -407,6 +409,7 @@
}, },
"0.4.12": { "0.4.12": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -419,6 +422,7 @@
}, },
"0.4.13": { "0.4.13": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -431,6 +435,7 @@
}, },
"0.4.14": { "0.4.14": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -442,6 +447,7 @@
}, },
"0.4.15": { "0.4.15": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -452,6 +458,8 @@
}, },
"0.4.16": { "0.4.16": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -463,6 +471,8 @@
}, },
"0.4.17": { "0.4.17": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -475,6 +485,8 @@
}, },
"0.4.18": { "0.4.18": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -486,6 +498,8 @@
}, },
"0.4.19": { "0.4.19": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -514,6 +528,8 @@
}, },
"0.4.20": { "0.4.20": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -526,6 +542,8 @@
}, },
"0.4.21": { "0.4.21": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -538,6 +556,8 @@
}, },
"0.4.22": { "0.4.22": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -550,6 +570,8 @@
}, },
"0.4.23": { "0.4.23": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -561,6 +583,8 @@
}, },
"0.4.24": { "0.4.24": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -572,6 +596,8 @@
}, },
"0.4.25": { "0.4.25": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
@ -581,6 +607,8 @@
}, },
"0.4.26": { "0.4.26": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2" "DynamicConstructorArgumentsClippedABIV2"
], ],
"released": "2019-04-29" "released": "2019-04-29"
@ -647,6 +675,7 @@
}, },
"0.4.7": { "0.4.7": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -661,6 +690,7 @@
}, },
"0.4.8": { "0.4.8": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -675,6 +705,7 @@
}, },
"0.4.9": { "0.4.9": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x", "UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup", "ExpExponentCleanup",
@ -689,6 +720,8 @@
}, },
"0.5.0": { "0.5.0": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
@ -698,6 +731,8 @@
}, },
"0.5.1": { "0.5.1": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
@ -705,8 +740,14 @@
], ],
"released": "2018-12-03" "released": "2018-12-03"
}, },
"0.5.10": {
"bugs": [],
"released": "2019-06-25"
},
"0.5.2": { "0.5.2": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
@ -716,6 +757,8 @@
}, },
"0.5.3": { "0.5.3": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
@ -725,6 +768,8 @@
}, },
"0.5.4": { "0.5.4": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
@ -734,6 +779,8 @@
}, },
"0.5.5": { "0.5.5": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
@ -745,6 +792,8 @@
}, },
"0.5.6": { "0.5.6": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries", "IncorrectEventSignatureInLibraries",
@ -755,6 +804,8 @@
}, },
"0.5.7": { "0.5.7": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2", "DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor", "UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries" "IncorrectEventSignatureInLibraries"
@ -763,12 +814,17 @@
}, },
"0.5.8": { "0.5.8": {
"bugs": [ "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2" "DynamicConstructorArgumentsClippedABIV2"
], ],
"released": "2019-04-30" "released": "2019-04-30"
}, },
"0.5.9": { "0.5.9": {
"bugs": [], "bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement"
],
"released": "2019-05-28" "released": "2019-05-28"
} }
} }

View File

@ -252,10 +252,6 @@ will consume more gas than the 2300 gas stipend:
Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it. Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it.
.. note::
Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve
any payload supplied with the call.
.. warning:: .. warning::
The fallback function is also executed if the caller meant to call The fallback function is also executed if the caller meant to call
a function that is not available. If you want to implement the fallback a function that is not available. If you want to implement the fallback
@ -273,6 +269,10 @@ Like any function, the fallback function can execute complex operations as long
A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`) A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`)
or as a destination of a ``selfdestruct``. or as a destination of a ``selfdestruct``.
.. note::
Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve
any payload supplied with the call.
A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it. A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it.
It also means that ``address(this).balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function). It also means that ``address(this).balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function).

View File

@ -34,3 +34,8 @@ Contracts can inherit interfaces as they would inherit other contracts.
Types defined inside interfaces and other contract-like structures Types defined inside interfaces and other contract-like structures
can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``. can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``.
.. warning:
Interfaces have supported ``enum`` types since :doc:`Solidity version 0.5.0 <050-breaking-changes>`, make
sure the pragma version specifies this version as a minimum.

View File

@ -33,39 +33,41 @@ Let us rewrite the set example from the
pragma solidity >=0.4.16 <0.7.0; pragma solidity >=0.4.16 <0.7.0;
// This is the same code as before, just without comments // This is the same code as before, just without comments
library Set { library Set {
struct Data { mapping(uint => bool) flags; } struct Data { mapping(uint => bool) flags; }
function insert(Data storage self, uint value) function insert(Data storage self, uint value)
public public
returns (bool) returns (bool)
{ {
if (self.flags[value]) if (self.flags[value])
return false; // already there return false; // already there
self.flags[value] = true; self.flags[value] = true;
return true; return true;
} }
function remove(Data storage self, uint value) function remove(Data storage self, uint value)
public public
returns (bool) returns (bool)
{ {
if (!self.flags[value]) if (!self.flags[value])
return false; // not there return false; // not there
self.flags[value] = false; self.flags[value] = false;
return true; return true;
} }
function contains(Data storage self, uint value) function contains(Data storage self, uint value)
public public
view view
returns (bool) returns (bool)
{ {
return self.flags[value]; return self.flags[value];
} }
} }
contract C { contract C {
using Set for Set.Data; // this is the crucial change using Set for Set.Data; // this is the crucial change
Set.Data knownValues; Set.Data knownValues;

View File

@ -130,7 +130,7 @@ The CI runs additional tests (including ``solc-js`` and testing third party Soli
Some versions of ``aleth`` can not be used for testing. We suggest using Some versions of ``aleth`` can not be used for testing. We suggest using
the same version that the Solidity continuous integration tests use. the same version that the Solidity continuous integration tests use.
Currently the CI uses version ``1.5.0-alpha.7`` of ``aleth``. Currently the CI uses version ``1.6.0`` of ``aleth``.
Writing and running syntax tests Writing and running syntax tests
-------------------------------- --------------------------------

View File

@ -298,8 +298,8 @@ Scoping and Declarations
A variable which is declared will have an initial default value whose byte-representation is all zeros. A variable which is declared will have an initial default value whose byte-representation is all zeros.
The "default values" of variables are the typical "zero-state" of whatever the type is. For example, the default value for a ``bool`` The "default values" of variables are the typical "zero-state" of whatever the type is. For example, the default value for a ``bool``
is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For statically-sized arrays and ``bytes1`` to ``bytes32``, each individual is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For statically-sized arrays and ``bytes1`` to ``bytes32``, each individual
element will be initialized to the default value corresponding to its type. Finally, for dynamically-sized arrays, ``bytes`` element will be initialized to the default value corresponding to its type. For dynamically-sized arrays, ``bytes``
and ``string``, the default value is an empty array or string. and ``string``, the default value is an empty array or string. For the ``enum`` type, the default value is its first member.
Scoping in Solidity follows the widespread scoping rules of C99 Scoping in Solidity follows the widespread scoping rules of C99
(and many other languages): Variables are visible from the point right after their declaration (and many other languages): Variables are visible from the point right after their declaration
@ -373,33 +373,53 @@ In any case, you will get a warning about the outer variable being shadowed.
Error handling: Assert, Require, Revert and Exceptions Error handling: Assert, Require, Revert and Exceptions
====================================================== ======================================================
Solidity uses state-reverting exceptions to handle errors. Such an exception will undo all changes made to the Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the
state in the current call (and all its sub-calls) and also flag an error to the caller. state in the current call (and all its sub-calls) and flags an error to the caller.
The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception
if the condition is not met. The ``assert`` function should only be used to test for internal errors, and to check invariants.
The ``require`` function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts.
If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix.
There are two other ways to trigger exceptions: The ``revert`` function can be used to flag an error and When exceptions happen in a sub-call, they "bubble up" (i.e., exceptions are rethrown) automatically. Exceptions to this rule are ``send``
revert the current call. It is possible to provide a string message containing details about the error and the low-level functions ``call``, ``delegatecall`` and ``staticcall``, they return ``false`` as their first return value in case
that will be passed back to the caller.
.. note::
There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which
was deprecated in version 0.4.13 and removed in version 0.5.0.
When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send``
and the low-level functions ``call``, ``delegatecall`` and ``staticcall`` -- those return ``false`` as their first return value in case
of an exception instead of "bubbling up". of an exception instead of "bubbling up".
.. warning:: .. warning::
The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the called account is non-existent, as part of the design of EVM. Existence must be checked prior to calling if desired. The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the account called is non-existent, as part of the design of EVM. Existence must be checked prior to calling if needed.
Catching exceptions is not yet possible. It is not yet possible to catch exceptions with Solidity.
In the following example, you can see how ``require`` can be used to easily check conditions on inputs ``assert`` and ``require``
and how ``assert`` can be used for internal error checking. Note that you can optionally provide --------------------------
a message string for ``require``, but not for ``assert``.
The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception
if the condition is not met.
The ``assert`` function should only be used to test for internal errors, and to check invariants. Properly functioning code should never reach a failing ``assert`` statement; if this happens there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``.
An ``assert``-style exception is generated in the following situations:
#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
#. If you access a fixed-length ``bytesN`` at a too large or negative index.
#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
#. If you shift by a negative amount.
#. If you convert a value too big or negative into an enum type.
#. If you call a zero-initialized variable of internal function type.
#. If you call ``assert`` with an argument that evaluates to false.
The ``require`` function should be used to ensure valid conditions that cannot be detected until execution time.
These conditions include inputs, or contract state variables are met, or to validate return values from calls to external contracts.
A ``require``-style exception is generated in the following situations:
#. Calling ``require`` with an argument that evaluates to ``false``.
#. If you call a function via a message call but it does not finish properly (i.e., it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``.
#. If you create a contract using the ``new`` keyword but the contract creation :ref:`does not finish properly<creating-contracts>`.
#. If you perform an external function call targeting a contract that contains no code.
#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
#. If your contract receives Ether via a public getter function.
#. If a ``.transfer()`` fails.
You can optionally provide a message string for ``require``, but not for ``assert``.
The following example shows how you can use ``require`` to check conditions on inputs
and ``assert`` for internal error checking.
:: ::
@ -418,34 +438,23 @@ a message string for ``require``, but not for ``assert``.
} }
} }
An ``assert``-style exception is generated in the following situations:
#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
#. If you access a fixed-length ``bytesN`` at a too large or negative index.
#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
#. If you shift by a negative amount.
#. If you convert a value too big or negative into an enum type.
#. If you call a zero-initialized variable of internal function type.
#. If you call ``assert`` with an argument that evaluates to false.
A ``require``-style exception is generated in the following situations:
#. Calling ``require`` with an argument that evaluates to ``false``.
#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``.
#. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly").
#. If you perform an external function call targeting a contract that contains no code.
#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
#. If your contract receives Ether via a public getter function.
#. If a ``.transfer()`` fails.
Internally, Solidity performs a revert operation (instruction ``0xfd``) for a ``require``-style exception and executes an invalid operation Internally, Solidity performs a revert operation (instruction ``0xfd``) for a ``require``-style exception and executes an invalid operation
(instruction ``0xfe``) to throw an ``assert``-style exception. In both cases, this causes (instruction ``0xfe``) to throw an ``assert``-style exception. In both cases, this causes
the EVM to revert all changes made to the state. The reason for reverting is that there is no safe way to continue execution, because an expected effect the EVM to revert all changes made to the state. The reason for reverting is that there is no safe way to continue execution, because an expected effect
did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction did not occur. Because we want to keep the atomicity of transactions, the safest action is to revert all changes and make the whole transaction
(or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while (or at least call) without effect.
``require``-style exceptions will not consume any gas starting from the Metropolis release.
The following example shows how an error string can be used together with revert and require: .. note::
``assert``-style exceptions consume all gas available to the call, while ``require``-style exceptions do not consume any gas starting from the Metropolis release.
``revert``
----------
The ``revert`` function is another way to trigger exceptions from within other code blocks to flag an error and
revert the current call. The function takes an optional string message containing details about the error that is passed back to the caller.
The following example shows how to use an error string together with ``revert`` and the equivalent ``require``:
:: ::
@ -464,9 +473,10 @@ The following example shows how an error string can be used together with revert
} }
} }
The provided string will be :ref:`abi-encoded <ABI>` as if it were a call to a function ``Error(string)``. The two syntax options are equivalent, it's developer preference which to use.
In the above example, ``revert("Not enough Ether provided.");`` will cause the following hexadecimal data be
set as error return data: The provided string is :ref:`abi-encoded <ABI>` as if it were a call to a function ``Error(string)``.
In the above example, ``revert("Not enough Ether provided.");`` returns the following hexadecimal as error return data:
.. code:: .. code::
@ -474,3 +484,7 @@ set as error return data:
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset 0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length 0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data 0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data
.. note::
There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which
was deprecated in version 0.4.13 and removed in version 0.5.0.

View File

@ -13,6 +13,7 @@ Safe Remote Purchase
address payable public seller; address payable public seller;
address payable public buyer; address payable public buyer;
enum State { Created, Locked, Inactive } enum State { Created, Locked, Inactive }
// The state variable has a default value of the first member, `State.created`
State public state; State public state;
// Ensure that `msg.value` is an even number. // Ensure that `msg.value` is an even number.

View File

@ -184,7 +184,7 @@ The following are dependencies for all builds of Solidity:
+-----------------------------------+-------------------------------------------------------+ +-----------------------------------+-------------------------------------------------------+
| Software | Notes | | Software | Notes |
+===================================+=======================================================+ +===================================+=======================================================+
| `CMake`_ | Cross-platform build file generator. | | `CMake`_ (version 3.5+) | Cross-platform build file generator. |
+-----------------------------------+-------------------------------------------------------+ +-----------------------------------+-------------------------------------------------------+
| `Boost`_ (version 1.65+) | C++ libraries. | | `Boost`_ (version 1.65+) | C++ libraries. |
+-----------------------------------+-------------------------------------------------------+ +-----------------------------------+-------------------------------------------------------+
@ -201,6 +201,13 @@ The following are dependencies for all builds of Solidity:
.. _CMake: https://cmake.org/download/ .. _CMake: https://cmake.org/download/
.. _z3: https://github.com/Z3Prover/z3 .. _z3: https://github.com/Z3Prover/z3
.. note::
Solidity versions prior to 0.5.10 can fail to correctly link against Boost versions 1.70+.
A possible workaround is to temporarily rename ``<Boost install path>/lib/cmake/Boost-1.70.0``
prior to running the cmake command to configure solidity.
Starting from 0.5.10 linking against Boost 1.70+ should work without manual intervention.
Prerequisites - macOS Prerequisites - macOS
--------------------- ---------------------
@ -304,10 +311,6 @@ Building Solidity is quite similar on Linux, macOS and other Unices:
cd build cd build
cmake .. && make cmake .. && make
.. warning::
BSD builds should work, but are untested by the Solidity team.
or even easier on Linux and macOS, you can run: or even easier on Linux and macOS, you can run:
.. code-block:: bash .. code-block:: bash
@ -315,6 +318,10 @@ or even easier on Linux and macOS, you can run:
#note: this will install binaries solc and soltest at usr/local/bin #note: this will install binaries solc and soltest at usr/local/bin
./scripts/build.sh ./scripts/build.sh
.. warning::
BSD builds should work, but are untested by the Solidity team.
And for Windows: And for Windows:
.. code-block:: bash .. code-block:: bash

View File

@ -42,8 +42,7 @@ data (its *state*) that resides at a specific address on the Ethereum
blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of
type ``uint`` (*u*\nsigned *int*\eger of *256* bits). You can think of it as a single slot type ``uint`` (*u*\nsigned *int*\eger of *256* bits). You can think of it as a single slot
in a database that you can query and alter by calling functions of the in a database that you can query and alter by calling functions of the
code that manages the database. In the case of Ethereum, this is always the owning code that manages the database. In this example, the contract defines the functions ``set`` and ``get`` that can be used to modify
contract. In this case, the functions ``set`` and ``get`` can be used to modify
or retrieve the value of the variable. or retrieve the value of the variable.
To access a state variable, you do not need the prefix ``this.`` as is common in To access a state variable, you do not need the prefix ``this.`` as is common in
@ -57,53 +56,54 @@ and overwrite your number, but the number is still stored in the history
of the blockchain. Later, you will see how you can impose access restrictions of the blockchain. Later, you will see how you can impose access restrictions
so that only you can alter the number. so that only you can alter the number.
.. note::
All identifiers (contract names, function names and variable names) are restricted to
the ASCII character set. It is possible to store UTF-8 encoded data in string variables.
.. warning:: .. warning::
Be careful with using Unicode text, as similar looking (or even identical) characters can Be careful with using Unicode text, as similar looking (or even identical) characters can
have different code points and as such are encoded as a different byte array. have different code points and as such are encoded as a different byte array.
.. note::
All identifiers (contract names, function names and variable names) are restricted to
the ASCII character set. It is possible to store UTF-8 encoded data in string variables.
.. index:: ! subcurrency .. index:: ! subcurrency
Subcurrency Example Subcurrency Example
=================== ===================
The following contract will implement the simplest form of a The following contract implements the simplest form of a
cryptocurrency. It is possible to generate coins out of thin air, but cryptocurrency. The contract allows only its creator to create new coins (different issuance scheme are possible).
only the person that created the contract will be able to do that (it is easy Anyone can send coins to each other without a need for
to implement a different issuance scheme). registering with a username and password, all you need is an Ethereum keypair.
Furthermore, anyone can send coins to each other without a need for
registering with username and password — all you need is an Ethereum keypair.
:: ::
pragma solidity >=0.5.0 <0.7.0; pragma solidity >=0.5.0 <0.7.0;
contract Coin { contract Coin {
// The keyword "public" makes those variables // The keyword "public" makes variables
// easily readable from outside. // accessible from other contracts
address public minter; address public minter;
mapping (address => uint) public balances; mapping (address => uint) public balances;
// Events allow light clients to react to // Events allow clients to react to specific
// changes efficiently. // contract changes you declare
event Sent(address from, address to, uint amount); event Sent(address from, address to, uint amount);
// This is the constructor whose code is // Constructor code is only run when the contract
// run only when the contract is created. // is created
constructor() public { constructor() public {
minter = msg.sender; minter = msg.sender;
} }
// Sends an amount of newly created coins to an address
// Can only be called by the contract creator
function mint(address receiver, uint amount) public { function mint(address receiver, uint amount) public {
require(msg.sender == minter); require(msg.sender == minter);
require(amount < 1e60); require(amount < 1e60);
balances[receiver] += amount; balances[receiver] += amount;
} }
// Sends an amount of existing coins
// from any caller to an address
function send(address receiver, uint amount) public { function send(address receiver, uint amount) public {
require(amount <= balances[msg.sender], "Insufficient balance."); require(amount <= balances[msg.sender], "Insufficient balance.");
balances[msg.sender] -= amount; balances[msg.sender] -= amount;
@ -114,58 +114,56 @@ registering with username and password — all you need is an Ethereum keypair.
This contract introduces some new concepts, let us go through them one by one. This contract introduces some new concepts, let us go through them one by one.
The line ``address public minter;`` declares a state variable of type address The line ``address public minter;`` declares a state variable of type :ref:`address<address>`.
that is publicly accessible. The ``address`` type is a 160-bit value The ``address`` type is a 160-bit value that does not allow any arithmetic operations.
that does not allow any arithmetic operations. It is suitable for It is suitable for storing addresses of contracts, or a hash of the public half of a keypair belonging to :ref:`external accounts<accounts>`.
storing addresses of contracts or of keypairs belonging to external
persons. The keyword ``public`` automatically generates a function that The keyword ``public`` automatically generates a function that allows you to access the current value of the state
allows you to access the current value of the state variable variable from outside of the contract. Without this keyword, other contracts have no way to access the variable.
from outside of the contract. The code of the function generated by the compiler is equivalent
Without this keyword, other contracts have no way to access the variable.
The code of the function generated by the compiler is roughly equivalent
to the following (ignore ``external`` and ``view`` for now):: to the following (ignore ``external`` and ``view`` for now)::
function minter() external view returns (address) { return minter; } function minter() external view returns (address) { return minter; }
Of course, adding a function exactly like that will not work You could add a function like the above yourself, but you would have a function and state variable with the same name.
because we would have a You do not need to do this, the compiler figures it out for you.
function and a state variable with the same name, but hopefully, you
get the idea - the compiler figures that out for you.
.. index:: mapping .. index:: mapping
The next line, ``mapping (address => uint) public balances;`` also The next line, ``mapping (address => uint) public balances;`` also
creates a public state variable, but it is a more complex datatype. creates a public state variable, but it is a more complex datatype.
The type maps addresses to unsigned integers. The :ref:`mapping <mapping-types>` type maps addresses to :ref:`unsigned integers <integers>`.
Mappings can be seen as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_ which are Mappings can be seen as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_ which are
virtually initialized such that every possible key exists from the start and is mapped to a virtually initialised such that every possible key exists from the start and is mapped to a
value whose byte-representation is all zeros. This analogy does not go value whose byte-representation is all zeros. However, it is neither possible to obtain a list of all keys of
too far, though, as it is neither possible to obtain a list of all keys of a mapping, nor a list of all values. Record what you
a mapping, nor a list of all values. So either keep in mind (or added to the mapping, or use it in a context where this is not needed. Or
better, keep a list or use a more advanced data type) what you even better, keep a list, or use a more suitable data type.
added to the mapping or use it in a context where this is not needed.
The :ref:`getter function<getter-functions>` created by the ``public`` keyword The :ref:`getter function<getter-functions>` created by the ``public`` keyword
is a bit more complex in this case. It roughly looks like the is more complex in the case of a mapping. It looks like the
following:: following::
function balances(address _account) external view returns (uint) { function balances(address _account) external view returns (uint) {
return balances[_account]; return balances[_account];
} }
As you see, you can use this function to easily query the balance of a You can use this function to query the balance of a single account.
single account.
.. index:: event .. index:: event
The line ``event Sent(address from, address to, uint amount);`` declares The line ``event Sent(address from, address to, uint amount);`` declares
a so-called "event" which is emitted in the last line of the function an :ref:`"event" <events>`, which is emitted in the last line of the function
``send``. User interfaces (as well as server applications of course) can ``send``. Ethereum clients such as web applications can
listen for those events being emitted on the blockchain without much listen for these events emitted on the blockchain without much
cost. As soon as it is emitted, the listener will also receive the cost. As soon as it is emitted, the listener receives the
arguments ``from``, ``to`` and ``amount``, which makes it easy to track arguments ``from``, ``to`` and ``amount``, which makes it possible to track
transactions. In order to listen for this event, you would use the following transactions.
JavaScript code (which assumes that ``Coin`` is a contract object created via
web3.js or a similar module):: To listen for this event, you could use the following
JavaScript code, which uses `web3.js <https://github.com/ethereum/web3.js/>`_ to create the ``Coin`` contract object,
and any user interface calls the automatically generated ``balances`` function from above::
Coin.Sent().watch({}, '', function(error, result) { Coin.Sent().watch({}, '', function(error, result) {
if (!error) { if (!error) {
@ -178,36 +176,33 @@ web3.js or a similar module)::
} }
}) })
Note how the automatically generated function ``balances`` is called from
the user interface.
.. index:: coin .. index:: coin
The constructor is a special function which is run during creation of the contract and The :ref:`constructor<constructor>` is a special function run during the creation of the contract and
cannot be called afterwards. It permanently stores the address of the person creating the cannot be called afterwards. In this case, it permanently stores the address of the person creating the
contract: ``msg`` (together with ``tx`` and ``block``) is a special global variable that contract. The ``msg`` variable (together with ``tx`` and ``block``) is a
contains some properties which allow access to the blockchain. ``msg.sender`` is :ref:`special global variable <special-variables-functions>` that
contains properties which allow access to the blockchain. ``msg.sender`` is
always the address where the current (external) function call came from. always the address where the current (external) function call came from.
Finally, the functions that will actually end up with the contract and can be called The functions that make up the contract, and that users and contracts can call are ``mint`` and ``send``.
by users and contracts alike are ``mint`` and ``send``.
If ``mint`` is called by anyone except the account that created the contract,
nothing will happen. This is ensured by the special function ``require`` which
causes all changes to be reverted if its argument evaluates to false.
The second call to ``require`` ensures that there will not be too many coins,
which could cause overflow errors later.
On the other hand, ``send`` can be used by anyone (who already The ``mint`` function sends an amount of newly created coins to another address.
has some of these coins) to send coins to anyone else. If you do not have The :ref:`require <assert-and-require>` function call defines conditions that reverts all changes if not met.
enough coins to send, the ``require`` call will fail and also provide the In this example, ``require(msg.sender == minter);`` ensures that only the creator of the contract can call ``mint``,
user with an appropriate error message string. and ``require(amount < 1e60);`` ensures a maximum amount of tokens, without which could cause overflow errors in the future.
The ``send`` function can be used by anyone (who already
has some of these coins) to send coins to anyone else. If the sender does not have
enough coins to send, the ``require`` call fails and provides the
sender with an appropriate error message string.
.. note:: .. note::
If you use If you use
this contract to send coins to an address, you will not see anything when you this contract to send coins to an address, you will not see anything when you
look at that address on a blockchain explorer, because the fact that you sent look at that address on a blockchain explorer, because the record that you sent
coins and the changed balances are only stored in the data storage of this coins and the changed balances are only stored in the data storage of this
particular coin contract. By the use of events it is relatively easy to create particular coin contract. By using events, you can create
a "blockchain explorer" that tracks transactions and balances of your new coin, a "blockchain explorer" that tracks transactions and balances of your new coin,
but you have to inspect the coin contract address and not the addresses of the but you have to inspect the coin contract address and not the addresses of the
coin owners. coin owners.
@ -301,6 +296,8 @@ Smart contracts even have limited access to other smart contracts.
.. index:: ! account, address, storage, balance .. index:: ! account, address, storage, balance
.. _accounts:
Accounts Accounts
======== ========
@ -513,10 +510,10 @@ Deactivate and Self-destruct
The only way to remove code from the blockchain is when a contract at that address performs the ``selfdestruct`` operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed from the state. Removing the contract in theory sounds like a good idea, but it is potentially dangerous, as if someone sends Ether to removed contracts, the Ether is forever lost. The only way to remove code from the blockchain is when a contract at that address performs the ``selfdestruct`` operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed from the state. Removing the contract in theory sounds like a good idea, but it is potentially dangerous, as if someone sends Ether to removed contracts, the Ether is forever lost.
.. warning::
Even if a contract is removed by "selfdestruct", it is still part of the history of the blockchain and probably retained by most Ethereum nodes. So using "selfdestruct" is not the same as deleting data from a hard disk.
.. note:: .. note::
Even if a contract's code does not contain a call to ``selfdestruct``, it can still perform that operation using ``delegatecall`` or ``callcode``. Even if a contract's code does not contain a call to ``selfdestruct``, it can still perform that operation using ``delegatecall`` or ``callcode``.
If you want to deactivate your contracts, you should instead **disable** them by changing some internal state which causes all functions to revert. This makes it impossible to use the contract, as it returns Ether immediately. If you want to deactivate your contracts, you should instead **disable** them by changing some internal state which causes all functions to revert. This makes it impossible to use the contract, as it returns Ether immediately.
.. warning::
Even if a contract is removed by "selfdestruct", it is still part of the history of the blockchain and probably retained by most Ethereum nodes. So using "selfdestruct" is not the same as deleting data from a hard disk.

View File

@ -11,7 +11,7 @@ Layout of State Variables in Storage
Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position ``0``. Multiple, contiguous items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules: Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position ``0``. Multiple, contiguous items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules:
- The first item in a storage slot is stored lower-order aligned. - The first item in a storage slot is stored lower-order aligned.
- Elementary types use only that many bytes that are necessary to store them. - Elementary types use only as many bytes as are necessary to store them.
- If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot. - If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot.
- Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules). - Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules).
@ -64,10 +64,11 @@ So for the following contract snippet::
pragma solidity >=0.4.0 <0.7.0; pragma solidity >=0.4.0 <0.7.0;
contract C { contract C {
struct s { uint a; uint b; } struct S { uint a; uint b; }
uint x; uint x;
mapping(uint => mapping(uint => s)) data; mapping(uint => mapping(uint => S)) data;
} }
The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``. The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``.
@ -268,7 +269,7 @@ Tips and Tricks
* Use ``delete`` on arrays to delete all its elements. * Use ``delete`` on arrays to delete all its elements.
* Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple ``SSTORE`` operations might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check! * Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple ``SSTORE`` operations might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check!
* Make your state variables public - the compiler will create :ref:`getters <visibility-and-getters>` for you automatically. * Make your state variables public - the compiler creates :ref:`getters <visibility-and-getters>` for you automatically.
* If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`. * If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`.
* Initialize storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});`` * Initialize storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});``

View File

@ -36,12 +36,16 @@ Documentation is inserted above each ``class``, ``interface`` and
documentation <https://vyper.readthedocs.io/en/latest/structure-of-a-contract.html#natspec-metadata>`__. documentation <https://vyper.readthedocs.io/en/latest/structure-of-a-contract.html#natspec-metadata>`__.
The following example shows a contract and a function using all available tags. The following example shows a contract and a function using all available tags.
Note: NatSpec currently does NOT apply to public state variables (see
`solidity#3418 <https://github.com/ethereum/solidity/issues/3418>`__), .. note::
even if they are declared public and therefore do affect the ABI. Note:
The Solidity compiler only interprets tags if they are external or NatSpec currently does NOT apply to public state variables (see
public. You are welcome to use similar comments for your internal and `solidity#3418 <https://github.com/ethereum/solidity/issues/3418>`__),
private functions, but those will not be parsed. even if they are declared public and therefore do affect the ABI.
The Solidity compiler only interprets tags if they are external or
public. You are welcome to use similar comments for your internal and
private functions, but those will not be parsed.
.. code:: solidity .. code:: solidity
@ -108,7 +112,7 @@ to the end-user as:
This function will multiply 10 by 7 This function will multiply 10 by 7
if a function is being called and the input ``a`` is assigned a value of 7. if a function is being called and the input ``a`` is assigned a value of 10.
Specifying these dynamic expressions is outside the scope of the Solidity Specifying these dynamic expressions is outside the scope of the Solidity
documentation and you may read more at documentation and you may read more at
@ -196,4 +200,3 @@ file should also be produced and should look like this:
}, },
"title" : "A simulator for trees" "title" : "A simulator for trees"
} }

View File

@ -757,20 +757,26 @@ No::
pragma solidity >=0.4.0 <0.7.0; pragma solidity >=0.4.0 <0.7.0;
// Base contracts just to make this compile // Base contracts just to make this compile
contract B { contract B {
constructor(uint) public { constructor(uint) public {
} }
} }
contract C { contract C {
constructor(uint, uint) public { constructor(uint, uint) public {
} }
} }
contract D { contract D {
constructor(uint) public { constructor(uint) public {
} }
} }
contract A is B, C, D { contract A is B, C, D {
uint x; uint x;
@ -778,12 +784,12 @@ No::
B(param1) B(param1)
C(param2, param3) C(param2, param3)
D(param4) D(param4)
public public {
{
x = param5; x = param5;
} }
} }
contract X is B, C, D { contract X is B, C, D {
uint x; uint x;
@ -792,10 +798,11 @@ No::
C(param2, param3) C(param2, param3)
D(param4) D(param4)
public { public {
x = param5; x = param5;
} }
} }
When declaring short functions with a single statement, it is permissible to do it on a single line. When declaring short functions with a single statement, it is permissible to do it on a single line.
Permissible:: Permissible::
@ -973,27 +980,32 @@ Yes::
pragma solidity >=0.4.0 <0.7.0; pragma solidity >=0.4.0 <0.7.0;
// Owned.sol // Owned.sol
contract Owned { contract Owned {
address public owner; address public owner;
constructor() public { constructor() public {
owner = msg.sender; owner = msg.sender;
} }
modifier onlyOwner { modifier onlyOwner {
require(msg.sender == owner); require(msg.sender == owner);
_; _;
} }
function transferOwnership(address newOwner) public onlyOwner { function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner; owner = newOwner;
} }
} }
// Congress.sol and in ``Congress.sol``::
pragma solidity >=0.4.0 <0.7.0;
import "./Owned.sol"; import "./Owned.sol";
contract Congress is Owned, TokenRecipient { contract Congress is Owned, TokenRecipient {
//... //...
} }
@ -1002,32 +1014,34 @@ No::
pragma solidity >=0.4.0 <0.7.0; pragma solidity >=0.4.0 <0.7.0;
// owned.sol // owned.sol
contract owned { contract owned {
address public owner; address public owner;
constructor() public { constructor() public {
owner = msg.sender; owner = msg.sender;
} }
modifier onlyOwner { modifier onlyOwner {
require(msg.sender == owner); require(msg.sender == owner);
_; _;
} }
function transferOwnership(address newOwner) public onlyOwner { function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner; owner = newOwner;
} }
} }
// Congress.sol and in ``Congress.sol``::
import "./owned.sol"; import "./owned.sol";
contract Congress is owned, tokenRecipient { contract Congress is owned, tokenRecipient {
//... //...
} }
Struct Names Struct Names
========================== ==========================
@ -1104,6 +1118,7 @@ added looks like the one below::
pragma solidity >=0.4.0 <0.7.0; pragma solidity >=0.4.0 <0.7.0;
/// @author The Solidity Team /// @author The Solidity Team
/// @title A simple storage example /// @title A simple storage example
contract SimpleStorage { contract SimpleStorage {
@ -1126,4 +1141,4 @@ added looks like the one below::
It is recommended that Solidity contracts are fully annontated using `NatSpec <natspec>`_ for all public interfaces (everything in the ABI). It is recommended that Solidity contracts are fully annontated using `NatSpec <natspec>`_ for all public interfaces (everything in the ABI).
Please see the sectian about `NatSpec <natspec>`_ for a detailed explanation. Please see the section about `NatSpec <natspec>`_ for a detailed explanation.

View File

@ -8,25 +8,28 @@ Conversions between Elementary Types
Implicit Conversions Implicit Conversions
-------------------- --------------------
If an operator is applied to different types, the compiler tries to If an operator is applied to different types, the compiler tries to implicitly
implicitly convert one of the operands to the type of the other (the same is convert one of the operands to the type of the other (the same is true for assignments).
true for assignments). In general, an implicit conversion between value-types This means that operations are always performed in the type of one of the operands.
is possible if it In general, an implicit conversion between value-types is possible if it makes
makes sense semantically and no information is lost: ``uint8`` is convertible to sense semantically and no information is lost.
``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256``
(because ``uint256`` cannot hold e.g. ``-1``). For example, ``uint8`` is convertible to
``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256``,
because ``uint256`` cannot hold values such as ``-1``.
For more details, please consult the sections about the types themselves. For more details, please consult the sections about the types themselves.
Explicit Conversions Explicit Conversions
-------------------- --------------------
If the compiler does not allow implicit conversion but you know what you are If the compiler does not allow implicit conversion but you are confident a conversion will work,
doing, an explicit type conversion is sometimes possible. Note that this may an explicit type conversion is sometimes possible. This may
give you some unexpected behaviour and allows you to bypass some security result in unexpected behaviour and allows you to bypass some security
features of the compiler, so be sure to test that the features of the compiler, so be sure to test that the
result is what you want! Take the following example where you are converting result is what you want and expect!
a negative ``int`` to a ``uint``:
Take the following example that converts a negative ``int`` to a ``uint``:
:: ::
@ -42,7 +45,7 @@ cut off::
uint32 a = 0x12345678; uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now uint16 b = uint16(a); // b will be 0x5678 now
If an integer is explicitly converted to a larger type, it is padded on the left (i.e. at the higher order end). If an integer is explicitly converted to a larger type, it is padded on the left (i.e., at the higher order end).
The result of the conversion will compare equal to the original integer:: The result of the conversion will compare equal to the original integer::
uint16 a = 0x1234; uint16 a = 0x1234;

View File

@ -25,7 +25,7 @@ in functions, or as parameters for library functions.
They cannot be used as parameters or return parameters They cannot be used as parameters or return parameters
of contract functions that are publicly visible. of contract functions that are publicly visible.
You can mark variables of mapping type as ``public`` and Solidity creates a You can mark state variables of mapping type as ``public`` and Solidity creates a
:ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a :ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a
parameter for the getter. If ``_ValueType`` is a value type or a struct, parameter for the getter. If ``_ValueType`` is a value type or a struct,
the getter returns ``_ValueType``. the getter returns ``_ValueType``.

View File

@ -26,6 +26,7 @@ Operators:
The operators ``||`` and ``&&`` apply the common short-circuiting rules. This means that in the expression ``f(x) || g(y)``, if ``f(x)`` evaluates to ``true``, ``g(y)`` will not be evaluated even if it may have side-effects. The operators ``||`` and ``&&`` apply the common short-circuiting rules. This means that in the expression ``f(x) || g(y)``, if ``f(x)`` evaluates to ``true``, ``g(y)`` will not be evaluated even if it may have side-effects.
.. index:: ! uint, ! int, ! integer .. index:: ! uint, ! int, ! integer
.. _integers:
Integers Integers
-------- --------
@ -332,7 +333,7 @@ type and this type is also used in the :ref:`ABI<ABI>`.
Contracts do not support any operators. Contracts do not support any operators.
The members of contract types are the external functions of the contract The members of contract types are the external functions of the contract
including public state variables. including any state variables marked as ``public``.
For a contract ``C`` you can use ``type(C)`` to access For a contract ``C`` you can use ``type(C)`` to access
:ref:`type information<meta-type>` about the contract. :ref:`type information<meta-type>` about the contract.
@ -427,6 +428,9 @@ long as the operands are integers. If any of the two is fractional, bit operatio
and exponentiation is disallowed if the exponent is fractional (because that might result in and exponentiation is disallowed if the exponent is fractional (because that might result in
a non-rational number). a non-rational number).
.. warning::
Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``.
.. note:: .. note::
Solidity has a number literal type for each rational number. Solidity has a number literal type for each rational number.
Integer literals and rational number literals belong to number literal types. Integer literals and rational number literals belong to number literal types.
@ -435,8 +439,6 @@ a non-rational number).
types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both
belong to the same number literal type for the rational number three. belong to the same number literal type for the rational number three.
.. warning::
Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``.
.. note:: .. note::
Number literal expressions are converted into a non-literal type as soon as they are used with non-literal Number literal expressions are converted into a non-literal type as soon as they are used with non-literal
@ -510,7 +512,7 @@ Enums
Enums are one way to create a user-defined type in Solidity. They are explicitly convertible Enums are one way to create a user-defined type in Solidity. They are explicitly convertible
to and from all integer types but implicit conversion is not allowed. The explicit conversion to and from all integer types but implicit conversion is not allowed. The explicit conversion
from integer checks at runtime that the value lies inside the range of the enum and causes a failing assert otherwise. from integer checks at runtime that the value lies inside the range of the enum and causes a failing assert otherwise.
Enums needs at least one member. Enums require at least one member, and its default value when declared is the first member.
The data representation is the same as for enums in C: The options are represented by The data representation is the same as for enums in C: The options are represented by
subsequent unsigned integer values starting from ``0``. subsequent unsigned integer values starting from ``0``.
@ -624,100 +626,116 @@ Example that shows how to use the members::
pragma solidity >=0.4.16 <0.7.0; pragma solidity >=0.4.16 <0.7.0;
contract Example { contract Example {
function f() public payable returns (bytes4) { function f() public payable returns (bytes4) {
return this.f.selector; return this.f.selector;
} }
function g() public {
this.f.gas(10).value(800)(); function g() public {
} this.f.gas(10).value(800)();
}
} }
Example that shows how to use internal function types:: Example that shows how to use internal function types::
pragma solidity >=0.4.16 <0.7.0; pragma solidity >=0.4.16 <0.7.0;
library ArrayUtils { library ArrayUtils {
// internal functions can be used in internal library functions because // internal functions can be used in internal library functions because
// they will be part of the same code context // they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f) function map(uint[] memory self, function (uint) pure returns (uint) f)
internal internal
pure pure
returns (uint[] memory r) returns (uint[] memory r)
{ {
r = new uint[](self.length); r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) { for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]); r[i] = f(self[i]);
}
} }
}
function reduce( function reduce(
uint[] memory self, uint[] memory self,
function (uint, uint) pure returns (uint) f function (uint, uint) pure returns (uint) f
) )
internal internal
pure pure
returns (uint r) returns (uint r)
{ {
r = self[0]; r = self[0];
for (uint i = 1; i < self.length; i++) { for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]); r = f(r, self[i]);
}
} }
}
function range(uint length) internal pure returns (uint[] memory r) { function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length); r = new uint[](length);
for (uint i = 0; i < r.length; i++) { for (uint i = 0; i < r.length; i++) {
r[i] = i; r[i] = i;
}
} }
}
} }
contract Pyramid { contract Pyramid {
using ArrayUtils for *; using ArrayUtils for *;
function pyramid(uint l) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum); function pyramid(uint l) public pure returns (uint) {
} return ArrayUtils.range(l).map(square).reduce(sum);
function square(uint x) internal pure returns (uint) { }
return x * x;
} function square(uint x) internal pure returns (uint) {
function sum(uint x, uint y) internal pure returns (uint) { return x * x;
return x + y; }
}
function sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
} }
Another example that uses external function types:: Another example that uses external function types::
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.7.0;
contract Oracle { contract Oracle {
struct Request { struct Request {
bytes data; bytes data;
function(uint) external callback; function(uint) external callback;
} }
Request[] requests;
event NewRequest(uint); Request[] private requests;
function query(bytes memory data, function(uint) external callback) public { event NewRequest(uint);
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1); function query(bytes memory data, function(uint) external callback) public {
} requests.push(Request(data, callback));
function reply(uint requestID, uint response) public { emit NewRequest(requests.length - 1);
// Here goes the check that the reply comes from a trusted source }
requests[requestID].callback(response);
} function reply(uint requestID, uint response) public {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
} }
contract OracleUser { contract OracleUser {
Oracle constant oracle = Oracle(0x1234567); // known contract Oracle constant private ORACLE_CONST = Oracle(0x1234567); // known contract
uint exchangeRate; uint private exchangeRate;
function buySomething() public {
oracle.query("USD", this.oracleResponse); function buySomething() public {
} ORACLE_CONST.query("USD", this.oracleResponse);
function oracleResponse(uint response) public { }
require(
msg.sender == address(oracle), function oracleResponse(uint response) public {
"Only oracle can call this." require(
); msg.sender == address(ORACLE_CONST),
exchangeRate = response; "Only oracle can call this."
} );
exchangeRate = response;
}
} }
.. note:: .. note::

View File

@ -52,6 +52,8 @@ interpret a function parameter in days, you can in the following way::
} }
} }
.. _special-variables-functions:
Special Variables and Functions Special Variables and Functions
=============================== ===============================

View File

@ -12,6 +12,7 @@ set(sources
FixedHash.h FixedHash.h
IndentedWriter.cpp IndentedWriter.cpp
IndentedWriter.h IndentedWriter.h
InvertibleMap.h
IpfsHash.cpp IpfsHash.cpp
IpfsHash.h IpfsHash.h
JSON.cpp JSON.cpp
@ -33,7 +34,6 @@ set(sources
) )
add_library(devcore ${sources}) add_library(devcore ${sources})
target_link_libraries(devcore PUBLIC jsoncpp ${Boost_FILESYSTEM_LIBRARIES} ${Boost_REGEX_LIBRARIES} ${Boost_SYSTEM_LIBRARIES} Threads::Threads) target_link_libraries(devcore PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::regex Boost::system Threads::Threads)
target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}") target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}")
target_include_directories(devcore SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
add_dependencies(devcore solidity_BuildInfo.h) add_dependencies(devcore solidity_BuildInfo.h)

View File

@ -81,6 +81,22 @@ inline std::vector<T> operator+(std::vector<T>&& _a, std::vector<T>&& _b)
ret += std::move(_b); ret += std::move(_b);
return ret; return ret;
} }
/// Concatenate something to a sets of elements.
template <class T, class U>
inline std::set<T> operator+(std::set<T> const& _a, U&& _b)
{
std::set<T> ret(_a);
ret += std::forward<U>(_b);
return ret;
}
/// Concatenate something to a sets of elements, move variant.
template <class T, class U>
inline std::set<T> operator+(std::set<T>&& _a, U&& _b)
{
std::set<T> ret(std::move(_a));
ret += std::forward<U>(_b);
return ret;
}
namespace dev namespace dev
{ {

View File

@ -0,0 +1,93 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <map>
#include <set>
/**
* Data structure that keeps track of values and keys of a mapping.
*/
template <class K, class V>
struct InvertibleMap
{
std::map<K, V> values;
// references[x] == {y | values[y] == x}
std::map<V, std::set<K>> references;
void set(K _key, V _value)
{
if (values.count(_key))
references[values[_key]].erase(_key);
values[_key] = _value;
references[_value].insert(_key);
}
void eraseKey(K _key)
{
if (values.count(_key))
references[values[_key]].erase(_key);
values.erase(_key);
}
void eraseValue(V _value)
{
if (references.count(_value))
{
for (V v: references[_value])
values.erase(v);
references.erase(_value);
}
}
void clear()
{
values.clear();
references.clear();
}
};
template <class T>
struct InvertibleRelation
{
/// forward[x] contains y <=> backward[y] contains x
std::map<T, std::set<T>> forward;
std::map<T, std::set<T>> backward;
void insert(T _key, T _value)
{
forward[_key].insert(_value);
backward[_value].insert(_key);
}
void set(T _key, std::set<T> _values)
{
for (T v: forward[_key])
backward[v].erase(_key);
for (T v: _values)
backward[v].insert(_key);
forward[_key] = std::move(_values);
}
void eraseKey(T _key)
{
for (auto const& v: forward[_key])
backward[v].erase(_key);
forward.erase(_key);
}
};

View File

@ -37,6 +37,7 @@ Whiskers::Whiskers(string _template):
Whiskers& Whiskers::operator()(string _parameter, string _value) Whiskers& Whiskers::operator()(string _parameter, string _value)
{ {
checkParameterValid(_parameter);
checkParameterUnknown(_parameter); checkParameterUnknown(_parameter);
m_parameters[move(_parameter)] = move(_value); m_parameters[move(_parameter)] = move(_value);
return *this; return *this;
@ -44,6 +45,7 @@ Whiskers& Whiskers::operator()(string _parameter, string _value)
Whiskers& Whiskers::operator()(string _parameter, bool _value) Whiskers& Whiskers::operator()(string _parameter, bool _value)
{ {
checkParameterValid(_parameter);
checkParameterUnknown(_parameter); checkParameterUnknown(_parameter);
m_conditions[move(_parameter)] = _value; m_conditions[move(_parameter)] = _value;
return *this; return *this;
@ -54,7 +56,11 @@ Whiskers& Whiskers::operator()(
vector<map<string, string>> _values vector<map<string, string>> _values
) )
{ {
checkParameterValid(_listParameter);
checkParameterUnknown(_listParameter); checkParameterUnknown(_listParameter);
for (auto const& element: _values)
for (auto const& val: element)
checkParameterValid(val.first);
m_listParameters[move(_listParameter)] = move(_values); m_listParameters[move(_listParameter)] = move(_values);
return *this; return *this;
} }
@ -64,7 +70,17 @@ string Whiskers::render() const
return replace(m_template, m_parameters, m_conditions, m_listParameters); return replace(m_template, m_parameters, m_conditions, m_listParameters);
} }
void Whiskers::checkParameterUnknown(string const& _parameter) void Whiskers::checkParameterValid(string const& _parameter) const
{
static boost::regex validParam("^" + paramRegex() + "$");
assertThrow(
boost::regex_match(_parameter, validParam),
WhiskersError,
"Parameter" + _parameter + " contains invalid characters."
);
}
void Whiskers::checkParameterUnknown(string const& _parameter) const
{ {
assertThrow( assertThrow(
!m_parameters.count(_parameter), !m_parameters.count(_parameter),
@ -91,7 +107,7 @@ string Whiskers::replace(
) )
{ {
using namespace boost; using namespace boost;
static regex listOrTag("<([^#/?!>]+)>|<#([^>]+)>(.*?)</\\2>|<\\?([^>]+)>(.*?)(<!\\4>(.*?))?</\\4>"); static regex listOrTag("<(" + paramRegex() + ")>|<#(" + paramRegex() + ")>(.*?)</\\2>|<\\?(" + paramRegex() + ")>(.*?)(<!\\4>(.*?))?</\\4>");
return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string
{ {
string tagName(_match[1]); string tagName(_match[1]);

View File

@ -85,7 +85,8 @@ public:
std::string render() const; std::string render() const;
private: private:
void checkParameterUnknown(std::string const& _parameter); void checkParameterValid(std::string const& _parameter) const;
void checkParameterUnknown(std::string const& _parameter) const;
static std::string replace( static std::string replace(
std::string const& _template, std::string const& _template,
@ -94,6 +95,8 @@ private:
StringListMap const& _listParameters = StringListMap() StringListMap const& _listParameters = StringListMap()
); );
static std::string paramRegex() { return "[a-zA-Z0-9_$-]+"; }
/// Joins the two maps throwing an exception if two keys are equal. /// Joins the two maps throwing an exception if two keys are equal.
static StringMap joinMaps(StringMap const& _a, StringMap const& _b); static StringMap joinMaps(StringMap const& _a, StringMap const& _b);

View File

@ -89,7 +89,6 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1(
{{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }, false}, {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }, false},
{{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }, false}, {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }, false},
{{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }, false}, {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }, false},
{{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }, false},
{{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 {
if (A.d() >= 31) if (A.d() >= 31)
return B.d(); return B.d();
@ -124,6 +123,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2(
{{Instruction::ADD, {X, 0}}, [=]{ return X; }, false}, {{Instruction::ADD, {X, 0}}, [=]{ return X; }, false},
{{Instruction::ADD, {0, X}}, [=]{ return X; }, false}, {{Instruction::ADD, {0, X}}, [=]{ return X; }, false},
{{Instruction::SUB, {X, 0}}, [=]{ return X; }, false}, {{Instruction::SUB, {X, 0}}, [=]{ return X; }, false},
{{Instruction::SUB, {~u256(0), X}}, [=]() -> Pattern { return {Instruction::NOT, {X}}; }, false},
{{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }, true}, {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }, true},
{{Instruction::MUL, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::MUL, {0, X}}, [=]{ return u256(0); }, true},
{{Instruction::MUL, {X, 1}}, [=]{ return X; }, false}, {{Instruction::MUL, {X, 1}}, [=]{ return X; }, false},

View File

@ -73,6 +73,13 @@ char CharStream::rollback(size_t _amount)
return get(); return get();
} }
char CharStream::setPosition(size_t _location)
{
solAssert(_location <= m_source.size(), "Attempting to set position past end of source.");
m_position = _location;
return get();
}
string CharStream::lineAtPosition(int _position) const string CharStream::lineAtPosition(int _position) const
{ {
// if _position points to \n, it returns the line before the \n // if _position points to \n, it returns the line before the \n
@ -106,5 +113,3 @@ tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const
} }
return tuple<int, int>(lineNumber, searchPosition - lineStart); return tuple<int, int>(lineNumber, searchPosition - lineStart);
} }

View File

@ -76,7 +76,13 @@ public:
char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; } char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; }
char advanceAndGet(size_t _chars = 1); char advanceAndGet(size_t _chars = 1);
/// Sets scanner position to @ _amount characters backwards in source text.
/// @returns The character of the current location after update is returned.
char rollback(size_t _amount); char rollback(size_t _amount);
/// Sets scanner position to @ _location if it refers a valid offset in m_source.
/// If not, nothing is done.
/// @returns The character of the current location after update is returned.
char setPosition(size_t _location);
void reset() { m_position = 0; } void reset() { m_position = 0; }

View File

@ -86,6 +86,11 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, Se
m_errorList.push_back(err); m_errorList.push_back(err);
} }
bool ErrorReporter::hasExcessiveErrors() const
{
return m_errorCount > c_maxErrorsAllowed;
}
bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
{ {
if (_type == Error::Type::Warning) if (_type == Error::Type::Warning)

View File

@ -118,6 +118,9 @@ public:
return m_errorCount > 0; return m_errorCount > 0;
} }
// @returns true if the maximum error count has been reached.
bool hasExcessiveErrors() const;
private: private:
void error( void error(
Error::Type _type, Error::Type _type,
@ -149,4 +152,3 @@ private:
}; };
} }

View File

@ -47,7 +47,7 @@ Token ParserBase::peekNextToken() const
return m_scanner->peekNextToken(); return m_scanner->peekNextToken();
} }
std::string ParserBase::currentLiteral() const string ParserBase::currentLiteral() const
{ {
return m_scanner->currentLiteral(); return m_scanner->currentLiteral();
} }
@ -57,38 +57,87 @@ Token ParserBase::advance()
return m_scanner->next(); return m_scanner->next();
} }
string ParserBase::tokenName(Token _token)
{
if (_token == Token::Identifier)
return "identifier";
else if (_token == Token::EOS)
return "end of source";
else if (TokenTraits::isReservedKeyword(_token))
return "reserved keyword '" + TokenTraits::friendlyName(_token) + "'";
else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
{
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
return "'" + elemTypeName.toString() + "'";
}
else
return "'" + TokenTraits::friendlyName(_token) + "'";
}
void ParserBase::expectToken(Token _value, bool _advance) void ParserBase::expectToken(Token _value, bool _advance)
{ {
Token tok = m_scanner->currentToken(); Token tok = m_scanner->currentToken();
if (tok != _value) if (tok != _value)
{ {
auto tokenName = [this](Token _token) string const expectedToken = ParserBase::tokenName(_value);
{ if (m_parserErrorRecovery)
if (_token == Token::Identifier) parserError("Expected " + expectedToken + " but got " + tokenName(tok));
return string("identifier"); else
else if (_token == Token::EOS) fatalParserError("Expected " + expectedToken + " but got " + tokenName(tok));
return string("end of source"); // Do not advance so that recovery can sync or make use of the current token.
else if (TokenTraits::isReservedKeyword(_token)) // This is especially useful if the expected token
return string("reserved keyword '") + TokenTraits::friendlyName(_token) + "'"; // is the only one that is missing and is at the end of a construct.
else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting // "{ ... ; }" is such an example.
{ // ^
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); _advance = false;
return string("'") + elemTypeName.toString() + "'";
}
else
return string("'") + TokenTraits::friendlyName(_token) + "'";
};
fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok));
} }
if (_advance) if (_advance)
m_scanner->next(); m_scanner->next();
} }
void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentNodeName, bool _advance)
{
Token tok = m_scanner->currentToken();
if (tok != _value)
{
int startPosition = position();
SourceLocation errorLoc = SourceLocation{startPosition, endPosition(), source()};
while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS)
m_scanner->next();
string const expectedToken = ParserBase::tokenName(_value);
string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + " instead.";
if (m_scanner->currentToken() == Token::EOS)
{
// rollback to where the token started, and raise exception to be caught at a higher level.
m_scanner->setPosition(startPosition);
m_inParserRecovery = true;
fatalParserError(errorLoc, msg);
}
else
{
if (m_inParserRecovery)
parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + ".");
else
parserError(errorLoc, msg + "Recovered at next " + expectedToken);
m_inParserRecovery = false;
}
}
else if (m_inParserRecovery)
{
string expectedToken = ParserBase::tokenName(_value);
parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + ".");
m_inParserRecovery = false;
}
if (_advance)
m_scanner->next();
}
void ParserBase::increaseRecursionDepth() void ParserBase::increaseRecursionDepth()
{ {
m_recursionDepth++; m_recursionDepth++;
if (m_recursionDepth >= 2560) if (m_recursionDepth >= 1200)
fatalParserError("Maximum recursion depth reached during parsing."); fatalParserError("Maximum recursion depth reached during parsing.");
} }
@ -98,12 +147,27 @@ void ParserBase::decreaseRecursionDepth()
m_recursionDepth--; m_recursionDepth--;
} }
void ParserBase::parserWarning(string const& _description)
{
m_errorReporter.warning(SourceLocation{position(), endPosition(), source()}, _description);
}
void ParserBase::parserError(SourceLocation const& _location, string const& _description)
{
m_errorReporter.parserError(_location, _description);
}
void ParserBase::parserError(string const& _description) void ParserBase::parserError(string const& _description)
{ {
m_errorReporter.parserError(SourceLocation{position(), endPosition(), source()}, _description); parserError(SourceLocation{position(), endPosition(), source()}, _description);
} }
void ParserBase::fatalParserError(string const& _description) void ParserBase::fatalParserError(string const& _description)
{ {
m_errorReporter.fatalParserError(SourceLocation{position(), endPosition(), source()}, _description); fatalParserError(SourceLocation{position(), endPosition(), source()}, _description);
}
void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description)
{
m_errorReporter.fatalParserError(_location, _description);
} }

View File

@ -36,7 +36,14 @@ class Scanner;
class ParserBase class ParserBase
{ {
public: public:
explicit ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {} /// Set @a _parserErrorRecovery to true for additional error
/// recovery. This is experimental and intended for use
/// by front-end tools that need partial AST information even
/// when errors occur.
explicit ParserBase(ErrorReporter& errorReporter, bool _parserErrorRecovery = false): m_errorReporter(errorReporter)
{
m_parserErrorRecovery = _parserErrorRecovery;
}
std::shared_ptr<CharStream> source() const { return m_scanner->charStream(); } std::shared_ptr<CharStream> source() const { return m_scanner->charStream(); }
@ -62,10 +69,17 @@ protected:
///@{ ///@{
///@name Helper functions ///@name Helper functions
/// If current token value is not _value, throw exception otherwise advance token. /// If current token value is not @a _value, throw exception otherwise advance token
// @a if _advance is true and error recovery is in effect.
void expectToken(Token _value, bool _advance = true); void expectToken(Token _value, bool _advance = true);
/// Like expectToken but if there is an error ignores tokens until
/// the expected token or EOS is seen. If EOS is encountered, back up to the error point,
/// and throw an exception so that a higher grammar rule has an opportunity to recover.
void expectTokenOrConsumeUntil(Token _value, std::string const& _currentNodeName, bool _advance = true);
Token currentToken() const; Token currentToken() const;
Token peekNextToken() const; Token peekNextToken() const;
std::string tokenName(Token _token);
std::string currentLiteral() const; std::string currentLiteral() const;
Token advance(); Token advance();
///@} ///@}
@ -77,16 +91,26 @@ protected:
/// Creates a @ref ParserError and annotates it with the current position and the /// Creates a @ref ParserError and annotates it with the current position and the
/// given @a _description. /// given @a _description.
void parserError(std::string const& _description); void parserError(std::string const& _description);
void parserError(SourceLocation const& _location, std::string const& _description);
/// Creates a @ref ParserWarning and annotates it with the current position and the
/// given @a _description.
void parserWarning(std::string const& _description);
/// Creates a @ref ParserError and annotates it with the current position and the /// Creates a @ref ParserError and annotates it with the current position and the
/// given @a _description. Throws the FatalError. /// given @a _description. Throws the FatalError.
void fatalParserError(std::string const& _description); void fatalParserError(std::string const& _description);
void fatalParserError(SourceLocation const& _location, std::string const& _description);
std::shared_ptr<Scanner> m_scanner; std::shared_ptr<Scanner> m_scanner;
/// The reference to the list of errors and warning to add errors/warnings during parsing /// The reference to the list of errors and warning to add errors/warnings during parsing
ErrorReporter& m_errorReporter; ErrorReporter& m_errorReporter;
/// Current recursion depth during parsing. /// Current recursion depth during parsing.
size_t m_recursionDepth = 0; size_t m_recursionDepth = 0;
/// True if we are in parser error recovery. Usually this means we are scanning for
/// a synchronization token like ';', or '}'. We use this to reduce cascaded error messages.
bool m_inParserRecovery = false;
bool m_parserErrorRecovery = false;
}; };
} }

View File

@ -156,6 +156,13 @@ void Scanner::reset()
next(); next();
} }
void Scanner::setPosition(size_t _offset)
{
m_char = m_source->setPosition(_offset);
scanToken();
next();
}
void Scanner::supportPeriodInIdentifier(bool _value) void Scanner::supportPeriodInIdentifier(bool _value)
{ {
m_supportPeriodInIdentifier = _value; m_supportPeriodInIdentifier = _value;

View File

@ -110,6 +110,9 @@ public:
/// @returns the next token and advances input /// @returns the next token and advances input
Token next(); Token next();
/// Set scanner to a specific offset. This is used in error recovery.
void setPosition(size_t _offset);
///@{ ///@{
///@name Information about the current token ///@name Information about the current token

View File

@ -137,10 +137,10 @@ if (NOT (${Z3_FOUND} OR ${CVC4_FOUND}))
endif() endif()
add_library(solidity ${sources} ${z3_SRCS} ${cvc4_SRCS}) add_library(solidity ${sources} ${z3_SRCS} ${cvc4_SRCS})
target_link_libraries(solidity PUBLIC yul evmasm langutil devcore ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) target_link_libraries(solidity PUBLIC yul evmasm langutil devcore Boost::boost Boost::filesystem Boost::system)
if (${Z3_FOUND}) if (${Z3_FOUND})
target_link_libraries(solidity PUBLIC Z3::Z3) target_link_libraries(solidity PUBLIC z3::libz3)
endif() endif()
if (${CVC4_FOUND}) if (${CVC4_FOUND})

View File

@ -2065,6 +2065,11 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
{ {
if (funType->kind() == FunctionType::Kind::Creation) if (funType->kind() == FunctionType::Kind::Creation)
errorMsg = "Constructor for " + t.front()->toString() + " must be payable for member \"value\" to be available."; errorMsg = "Constructor for " + t.front()->toString() + " must be payable for member \"value\" to be available.";
else if (
funType->kind() == FunctionType::Kind::DelegateCall ||
funType->kind() == FunctionType::Kind::BareDelegateCall
)
errorMsg = "Member \"value\" is not allowed in delegated calls due to \"msg.value\" persisting.";
else else
errorMsg = "Member \"value\" is only available for payable functions."; errorMsg = "Member \"value\" is only available for payable functions.";
} }

View File

@ -456,15 +456,13 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node)
bool ASTJsonConverter::visit(InlineAssembly const& _node) bool ASTJsonConverter::visit(InlineAssembly const& _node)
{ {
Json::Value externalReferences(Json::arrayValue); Json::Value externalReferences(Json::arrayValue);
for (auto const& it : _node.annotation().externalReferences) for (auto const& it: _node.annotation().externalReferences)
{
if (it.first) if (it.first)
{ {
Json::Value tuple(Json::objectValue); Json::Value tuple(Json::objectValue);
tuple[it.first->name.str()] = inlineAssemblyIdentifierToJson(it); tuple[it.first->name.str()] = inlineAssemblyIdentifierToJson(it);
externalReferences.append(tuple); externalReferences.append(tuple);
} }
}
setJsonNode(_node, "InlineAssembly", { setJsonNode(_node, "InlineAssembly", {
make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))), make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))),
make_pair("externalReferences", std::move(externalReferences)) make_pair("externalReferences", std::move(externalReferences))

View File

@ -2912,7 +2912,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
strings(1, ""), strings(1, ""),
Kind::SetValue, Kind::SetValue,
false, false,
StateMutability::NonPayable, StateMutability::Pure,
nullptr, nullptr,
m_gasSet, m_gasSet,
m_valueSet m_valueSet
@ -2929,7 +2929,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
strings(1, ""), strings(1, ""),
Kind::SetGas, Kind::SetGas,
false, false,
StateMutability::NonPayable, StateMutability::Pure,
nullptr, nullptr,
m_gasSet, m_gasSet,
m_valueSet m_valueSet

View File

@ -414,7 +414,8 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
fromArrayType.isByteArray() || fromArrayType.isByteArray() ||
*fromArrayType.baseType() == *TypeProvider::uint256() || *fromArrayType.baseType() == *TypeProvider::uint256() ||
*fromArrayType.baseType() == FixedBytesType(32), *fromArrayType.baseType() == FixedBytesType(32),
""); ""
);
solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), ""); solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), "");
solAssert( solAssert(

View File

@ -154,7 +154,7 @@ private:
EncodingOptions const& _options EncodingOptions const& _options
); );
/// Part of @a abiEncodingFunction for array target type and given memory array or /// Part of @a abiEncodingFunction for array target type and given memory array or
/// a given storage array with one item per slot. /// a given storage array with every item occupies one or multiple full slots.
std::string abiEncodingFunctionSimpleArray( std::string abiEncodingFunctionSimpleArray(
ArrayType const& _givenType, ArrayType const& _givenType,
ArrayType const& _targetType, ArrayType const& _targetType,

View File

@ -1055,28 +1055,27 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b
switch (location) switch (location)
{ {
case DataLocation::Memory: case DataLocation::Memory:
// stack: <base_ref> <index>
if (!_arrayType.isByteArray())
m_context << u256(_arrayType.memoryHeadSize()) << Instruction::MUL;
if (_arrayType.isDynamicallySized())
m_context << u256(32) << Instruction::ADD;
if (_keepReference)
m_context << Instruction::DUP2;
m_context << Instruction::ADD;
break;
case DataLocation::CallData: case DataLocation::CallData:
if (!_arrayType.isByteArray()) if (!_arrayType.isByteArray())
{ {
if (location == DataLocation::CallData) if (_arrayType.baseType()->isDynamicallyEncoded())
{ m_context << u256(0x20);
if (_arrayType.baseType()->isDynamicallyEncoded())
m_context << u256(0x20);
else
m_context << _arrayType.baseType()->calldataEncodedSize();
}
else else
m_context << u256(_arrayType.memoryHeadSize()); m_context << _arrayType.baseType()->calldataEncodedSize();
m_context << Instruction::MUL; m_context << Instruction::MUL;
} }
// stack: <base_ref> <index * size> // stack: <base_ref> <index * size>
if (location == DataLocation::Memory && _arrayType.isDynamicallySized())
m_context << u256(32) << Instruction::ADD;
if (_keepReference) if (_keepReference)
m_context << Instruction::DUP2; m_context << Instruction::DUP2;
m_context << Instruction::ADD; m_context << Instruction::ADD;
break; break;
case DataLocation::Storage: case DataLocation::Storage:
@ -1132,6 +1131,50 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b
} }
} }
void ArrayUtils::accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck) const
{
solAssert(_arrayType.location() == DataLocation::CallData, "");
if (_arrayType.baseType()->isDynamicallyEncoded())
{
// stack layout: <base_ref> <length> <index>
ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck, true);
// stack layout: <base_ref> <ptr_to_tail>
CompilerUtils(m_context).accessCalldataTail(*_arrayType.baseType());
// stack layout: <tail_ref> [length]
}
else
{
ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck);
if (_arrayType.baseType()->isValueType())
{
solAssert(_arrayType.baseType()->storageBytes() <= 32, "");
if (
!_arrayType.isByteArray() &&
_arrayType.baseType()->storageBytes() < 32 &&
m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
)
{
m_context << u256(32);
CompilerUtils(m_context).abiDecodeV2({_arrayType.baseType()}, false);
}
else
CompilerUtils(m_context).loadFromMemoryDynamic(
*_arrayType.baseType(),
true,
!_arrayType.isByteArray(),
false
);
}
else
solAssert(
_arrayType.baseType()->category() == Type::Category::Struct ||
_arrayType.baseType()->category() == Type::Category::Array,
"Invalid statically sized non-value base type on array access."
);
}
}
void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const
{ {
solAssert(_byteSize < 32, ""); solAssert(_byteSize < 32, "");

View File

@ -104,6 +104,10 @@ public:
/// Stack post (storage): [reference] storage_slot byte_offset /// Stack post (storage): [reference] storage_slot byte_offset
/// Stack post: [reference] memory/calldata_offset /// Stack post: [reference] memory/calldata_offset
void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true, bool _keepReference = false) const; void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true, bool _keepReference = false) const;
/// Access calldata array's element and put it on stack.
/// Stack pre: reference [length] index
/// Stack post: value
void accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck = true) const;
private: private:
/// Adds the given number of bytes to a storage byte offset counter and also increments /// Adds the given number of bytes to a storage byte offset counter and also increments

View File

@ -32,8 +32,8 @@
#include <libyul/AsmAnalysisInfo.h> #include <libyul/AsmAnalysisInfo.h>
#include <libyul/backends/evm/AsmCodeGen.h> #include <libyul/backends/evm/AsmCodeGen.h>
#include <libyul/backends/evm/EVMDialect.h> #include <libyul/backends/evm/EVMDialect.h>
#include <libyul/backends/evm/EVMMetrics.h>
#include <libyul/optimiser/Suite.h> #include <libyul/optimiser/Suite.h>
#include <libyul/optimiser/Metrics.h>
#include <libyul/YulString.h> #include <libyul/YulString.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
@ -422,9 +422,10 @@ void CompilerContext::appendInlineAssembly(
if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) if (_optimiserSettings.runYulOptimiser && _localVariables.empty())
{ {
bool const isCreation = m_runtimeContext != nullptr; bool const isCreation = m_runtimeContext != nullptr;
yul::GasMeter meter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment);
yul::OptimiserSuite::run( yul::OptimiserSuite::run(
dialect, dialect,
yul::GasMeter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment), &meter,
*parserResult, *parserResult,
analysisInfo, analysisInfo,
_optimiserSettings.optimizeStackAllocation, _optimiserSettings.optimizeStackAllocation,

View File

@ -816,7 +816,12 @@ void CompilerUtils::convertType(
} }
else else
{ {
solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract || targetTypeCategory == Type::Category::Address, ""); solAssert(
targetTypeCategory == Type::Category::Integer ||
targetTypeCategory == Type::Category::Contract ||
targetTypeCategory == Type::Category::Address,
""
);
IntegerType addressType(160); IntegerType addressType(160);
IntegerType const& targetType = targetTypeCategory == Type::Category::Integer IntegerType const& targetType = targetTypeCategory == Type::Category::Integer
? dynamic_cast<IntegerType const&>(_targetType) : addressType; ? dynamic_cast<IntegerType const&>(_targetType) : addressType;
@ -841,9 +846,9 @@ void CompilerUtils::convertType(
cleanHigherOrderBits(targetType); cleanHigherOrderBits(targetType);
if (chopSignBitsPending) if (chopSignBitsPending)
{ {
if (typeOnStack.numBits() < 256) if (targetType.numBits() < 256)
m_context m_context
<< ((u256(1) << typeOnStack.numBits()) - 1) << ((u256(1) << targetType.numBits()) - 1)
<< Instruction::AND; << Instruction::AND;
chopSignBitsPending = false; chopSignBitsPending = false;
} }
@ -923,7 +928,6 @@ void CompilerUtils::convertType(
// stack: <mem start> <source ref> (variably sized) <length> <mem data pos> // stack: <mem start> <source ref> (variably sized) <length> <mem data pos>
if (targetType.baseType()->isValueType()) if (targetType.baseType()->isValueType())
{ {
solAssert(typeOnStack.baseType()->isValueType(), "");
copyToStackTop(2 + stackSize, stackSize); copyToStackTop(2 + stackSize, stackSize);
ArrayUtils(m_context).copyArrayToMemory(typeOnStack); ArrayUtils(m_context).copyArrayToMemory(typeOnStack);
} }
@ -957,10 +961,11 @@ void CompilerUtils::convertType(
} }
case DataLocation::CallData: case DataLocation::CallData:
solAssert( solAssert(
targetType.isByteArray() && targetType.isByteArray() &&
typeOnStack.isByteArray() && typeOnStack.isByteArray() &&
typeOnStack.location() == DataLocation::CallData, typeOnStack.location() == DataLocation::CallData,
"Invalid conversion to calldata type."); "Invalid conversion to calldata type."
);
break; break;
} }
break; break;

View File

@ -54,7 +54,13 @@ class StackHeightChecker
public: public:
explicit StackHeightChecker(CompilerContext const& _context): explicit StackHeightChecker(CompilerContext const& _context):
m_context(_context), stackHeight(m_context.stackHeight()) {} m_context(_context), stackHeight(m_context.stackHeight()) {}
void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight)); } void check()
{
solAssert(
m_context.stackHeight() == stackHeight,
std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight)
);
}
private: private:
CompilerContext const& m_context; CompilerContext const& m_context;
unsigned stackHeight; unsigned stackHeight;
@ -893,9 +899,9 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar
// Local variable slots are reserved when their declaration is visited, // Local variable slots are reserved when their declaration is visited,
// and freed in the end of their scope. // and freed in the end of their scope.
for (auto _decl: _variableDeclarationStatement.declarations()) for (auto decl: _variableDeclarationStatement.declarations())
if (_decl) if (decl)
appendStackVariableInitialisation(*_decl); appendStackVariableInitialisation(*decl);
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
if (Expression const* expression = _variableDeclarationStatement.initialValue()) if (Expression const* expression = _variableDeclarationStatement.initialValue())

View File

@ -1588,45 +1588,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
setLValue<MemoryItem>(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); setLValue<MemoryItem>(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray());
break; break;
case DataLocation::CallData: case DataLocation::CallData:
if (arrayType.baseType()->isDynamicallyEncoded()) ArrayUtils(m_context).accessCallDataArrayElement(arrayType);
{
// stack layout: <base_ref> <length> <index>
ArrayUtils(m_context).accessIndex(arrayType, true, true);
// stack layout: <base_ref> <ptr_to_tail>
CompilerUtils(m_context).accessCalldataTail(*arrayType.baseType());
// stack layout: <tail_ref> [length]
}
else
{
ArrayUtils(m_context).accessIndex(arrayType, true);
if (arrayType.baseType()->isValueType())
{
solAssert(arrayType.baseType()->storageBytes() <= 32, "");
if (
!arrayType.isByteArray() &&
arrayType.baseType()->storageBytes() < 32 &&
m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)
)
{
m_context << u256(32);
CompilerUtils(m_context).abiDecodeV2({arrayType.baseType()}, false);
}
else
CompilerUtils(m_context).loadFromMemoryDynamic(
*arrayType.baseType(),
true,
!arrayType.isByteArray(),
false
);
}
else
solAssert(
arrayType.baseType()->category() == Type::Category::Struct ||
arrayType.baseType()->category() == Type::Category::Array,
"Invalid statically sized non-value base type on array access."
);
}
break; break;
} }
} }

View File

@ -224,34 +224,45 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
solAssert(_numBits < 256, ""); solAssert(_numBits < 256, "");
string functionName = "shift_left_" + to_string(_numBits); string functionName = "shift_left_" + to_string(_numBits);
if (m_evmVersion.hasBitwiseShifting()) return m_functionCollector->createFunction(functionName, [&]() {
{ return
return m_functionCollector->createFunction(functionName, [&]() { Whiskers(R"(
return function <functionName>(value) -> newValue {
Whiskers(R"( newValue :=
function <functionName>(value) -> newValue { <?hasShifts>
newValue := shl(<numBits>, value) shl(<numBits>, value)
} <!hasShifts>
)") mul(value, <multiplier>)
("functionName", functionName) </hasShifts>
("numBits", to_string(_numBits)) }
.render(); )")
}); ("functionName", functionName)
} ("numBits", to_string(_numBits))
else ("hasShifts", m_evmVersion.hasBitwiseShifting())
{ ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
return m_functionCollector->createFunction(functionName, [&]() { .render();
return });
Whiskers(R"( }
function <functionName>(value) -> newValue {
newValue := mul(value, <multiplier>) string YulUtilFunctions::shiftLeftFunctionDynamic()
} {
)") string functionName = "shift_left_dynamic";
("functionName", functionName) return m_functionCollector->createFunction(functionName, [&]() {
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) return
.render(); Whiskers(R"(
}); function <functionName>(bits, value) -> newValue {
} newValue :=
<?hasShifts>
shl(bits, value)
<!hasShifts>
mul(value, exp(2, bits))
</hasShifts>
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
.render();
});
} }
string YulUtilFunctions::shiftRightFunction(size_t _numBits) string YulUtilFunctions::shiftRightFunction(size_t _numBits)
@ -261,7 +272,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
// Note that if this is extended with signed shifts, // Note that if this is extended with signed shifts,
// the opcodes SAR and SDIV behave differently with regards to rounding! // the opcodes SAR and SDIV behave differently with regards to rounding!
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned_" + m_evmVersion.name(); string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
@ -282,6 +293,30 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
}); });
} }
string YulUtilFunctions::shiftRightFunctionDynamic()
{
// Note that if this is extended with signed shifts,
// the opcodes SAR and SDIV behave differently with regards to rounding!
string const functionName = "shift_right_unsigned_dynamic";
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(bits, value) -> newValue {
newValue :=
<?hasShifts>
shr(bits, value)
<!hasShifts>
div(value, exp(2, bits))
</hasShifts>
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
.render();
});
}
string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes)
{ {
solAssert(_numBytes <= 32, ""); solAssert(_numBytes <= 32, "");
@ -306,6 +341,29 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift
}); });
} }
string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
{
solAssert(_numBytes <= 32, "");
size_t numBits = _numBytes * 8;
string functionName = "update_byte_slice_dynamic" + to_string(_numBytes);
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, shiftBytes, toInsert) -> result {
let shiftBits := mul(shiftBytes, 8)
let mask := <shl>(shiftBits, <mask>)
toInsert := <shl>(shiftBits, toInsert)
value := and(value, not(mask))
result := or(value, and(toInsert, mask))
}
)")
("functionName", functionName)
("mask", formatNumber((bigint(1) << numBits) - 1))
("shl", shiftLeftFunctionDynamic())
.render();
});
}
string YulUtilFunctions::roundUpFunction() string YulUtilFunctions::roundUpFunction()
{ {
string functionName = "round_up_to_mul_of_32"; string functionName = "round_up_to_mul_of_32";
@ -321,63 +379,70 @@ string YulUtilFunctions::roundUpFunction()
}); });
} }
string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits) string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
{ {
solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); string functionName = "checked_add_" + _type.identifier();
string functionName = "checked_add_uint_" + to_string(_bits); // TODO: Consider to add a special case for unsigned 256-bit integers
// and use the following instead:
// sum := add(x, y) if lt(sum, x) { revert(0, 0) }
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(x, y) -> sum { function <functionName>(x, y) -> sum {
<?shortType> <?signed>
let mask := <mask> // overflow, if x >= 0 and y > (maxValue - x)
sum := add(and(x, mask), and(y, mask)) if and(iszero(slt(x, 0)), sgt(y, sub(<maxValue>, x))) { revert(0, 0) }
if and(sum, not(mask)) { revert(0, 0) } // underflow, if x < 0 and y < (minValue - x)
<!shortType> if and(slt(x, 0), slt(y, sub(<minValue>, x))) { revert(0, 0) }
sum := add(x, y) <!signed>
if lt(sum, x) { revert(0, 0) } // overflow, if x > (maxValue - y)
</shortType> if gt(x, sub(<maxValue>, y)) { revert(0, 0) }
</signed>
sum := add(x, y)
} }
)") )")
("shortType", _bits < 256)
("functionName", functionName) ("functionName", functionName)
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) ("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
.render(); .render();
}); });
} }
string YulUtilFunctions::overflowCheckedUIntMulFunction(size_t _bits) string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
{ {
solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); string functionName = "checked_mul_" + _type.identifier();
string functionName = "checked_mul_uint_" + to_string(_bits);
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
return return
// - The current overflow check *before* the multiplication could // Multiplication by zero could be treated separately and directly return zero.
// be replaced by the following check *after* the multiplication:
// if and(iszero(iszero(x)), iszero(eq(div(product, x), y))) { revert(0, 0) }
// - The case the x equals 0 could be treated separately and directly return zero.
Whiskers(R"( Whiskers(R"(
function <functionName>(x, y) -> product { function <functionName>(x, y) -> product {
if and(iszero(iszero(x)), lt(div(<mask>, x), y)) { revert(0, 0) } <?signed>
<?shortType> // overflow, if x > 0, y > 0 and x > (maxValue / y)
product := mulmod(x, y, <powerOfTwo>) if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { revert(0, 0) }
<!shortType> // underflow, if x > 0, y < 0 and y < (minValue / x)
product := mul(x, y) if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(<minValue>, x))) { revert(0, 0) }
</shortType> // underflow, if x < 0, y > 0 and x < (minValue / y)
if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(<minValue>, y))) { revert(0, 0) }
// overflow, if x < 0, y < 0 and x < (maxValue / y)
if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(<maxValue>, y))) { revert(0, 0) }
<!signed>
// overflow, if x != 0 and y > (maxValue / x)
if and(iszero(iszero(x)), gt(y, div(<maxValue>, x))) { revert(0, 0) }
</signed>
product := mul(x, y)
} }
)") )")
("shortType", _bits < 256) ("functionName", functionName)
("functionName", functionName) ("signed", _type.isSigned())
("powerOfTwo", toCompactHexWithPrefix(u256(1) << _bits)) ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) ("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
.render(); .render();
}); });
} }
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{ {
unsigned bits = _type.numBits();
solAssert(0 < bits && bits <= 256 && bits % 8 == 0, "");
string functionName = "checked_div_" + _type.identifier(); string functionName = "checked_div_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
return return
@ -385,7 +450,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
function <functionName>(x, y) -> r { function <functionName>(x, y) -> r {
if iszero(y) { revert(0, 0) } if iszero(y) { revert(0, 0) }
<?signed> <?signed>
// x / -1 == x // overflow for minVal / -1
if and( if and(
eq(x, <minVal>), eq(x, <minVal>),
eq(y, sub(0, 1)) eq(y, sub(0, 1))
@ -394,25 +459,52 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
r := <?signed>s</signed>div(x, y) r := <?signed>s</signed>div(x, y)
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("signed", _type.isSigned()) ("signed", _type.isSigned())
("minVal", (0 - (u256(1) << (bits - 1))).str()) ("minVal", toCompactHexWithPrefix(u256(_type.minValue())))
.render(); .render();
}); });
} }
string YulUtilFunctions::overflowCheckedUIntSubFunction() string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
{ {
string functionName = "checked_sub_uint"; string functionName = "checked_mod_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> r {
if iszero(y) { revert(0, 0) }
r := <?signed>s</signed>mod(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
.render();
});
}
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
{
string functionName = "checked_sub_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&] { return m_functionCollector->createFunction(functionName, [&] {
return return
Whiskers(R"( Whiskers(R"(
function <functionName>(x, y) -> diff { function <functionName>(x, y) -> diff {
if lt(x, y) { revert(0, 0) } <?signed>
// underflow, if y >= 0 and x < (minValue + y)
if and(iszero(slt(y, 0)), slt(x, add(<minValue>, y))) { revert(0, 0) }
// overflow, if y < 0 and x > (maxValue + y)
if and(slt(y, 0), sgt(x, add(<maxValue>, y))) { revert(0, 0) }
<!signed>
if lt(x, y) { revert(0, 0) }
</signed>
diff := sub(x, y) diff := sub(x, y)
} }
)") )")
("functionName", functionName) ("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
.render(); .render();
}); });
} }
@ -459,6 +551,120 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
}); });
} }
std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->isValueType(), "...");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "...");
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
string functionName = "resize_array_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, newLen) {
if gt(newLen, <maxArrayLength>) {
invalid()
}
let oldLen := <fetchLength>(array)
// Store new length
sstore(array, newLen)
// Size was reduced, clear end of array
if lt(newLen, oldLen) {
let oldSlotCount := <convertToSize>(oldLen)
let newSlotCount := <convertToSize>(newLen)
let arrayDataStart := <dataPosition>(array)
let deleteStart := add(arrayDataStart, newSlotCount)
let deleteEnd := add(arrayDataStart, oldSlotCount)
<clearStorageRange>(deleteStart, deleteEnd)
}
})")
("functionName", functionName)
("fetchLength", arrayLengthFunction(_type))
("convertToSize", arrayConvertLengthToSize(_type))
("dataPosition", arrayDataAreaFunction(_type))
("clearStorageRange", clearStorageRangeFunction(*_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
.render();
});
}
string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
{
string functionName = "clear_storage_range_" + _type.identifier();
solUnimplementedAssert(_type.isValueType(), "...");
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(start, end) {
for {} lt(start, end) { start := add(start, 1) }
{
sstore(start, 0)
}
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{
string functionName = "array_convert_length_to_size_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
Type const& baseType = *_type.baseType();
switch (_type.location())
{
case DataLocation::Storage:
{
unsigned const baseStorageBytes = baseType.storageBytes();
solAssert(baseStorageBytes > 0, "");
solAssert(32 / baseStorageBytes > 0, "");
return Whiskers(R"(
function <functionName>(length) -> size {
size := length
<?multiSlot>
size := <mul>(<storageSize>, length)
<!multiSlot>
// Number of slots rounded up
size := div(add(length, sub(<itemsPerSlot>, 1)), <itemsPerSlot>)
</multiSlot>
})")
("functionName", functionName)
("multiSlot", baseType.storageSize() > 1)
("itemsPerSlot", to_string(32 / baseStorageBytes))
("storageSize", baseType.storageSize().str())
("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256()))
.render();
}
case DataLocation::CallData: // fallthrough
case DataLocation::Memory:
return Whiskers(R"(
function <functionName>(length) -> size {
<?byteArray>
size := length
<!byteArray>
size := <mul>(length, <elementSize>)
</byteArray>
})")
("functionName", functionName)
("elementSize", _type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize())
("byteArray", _type.isByteArray())
("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256()))
.render();
default:
solAssert(false, "");
}
});
}
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
{ {
solAssert(_type.dataStoredIn(DataLocation::Memory), ""); solAssert(_type.dataStoredIn(DataLocation::Memory), "");
@ -518,6 +724,36 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
}); });
} }
string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
{
solUnimplementedAssert(_type.baseType()->storageBytes() > 16, "");
string functionName = "storage_array_index_access_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, index) -> slot, offset {
if iszero(lt(index, <arrayLen>(array))) {
invalid()
}
let data := <dataAreaFunc>(array)
<?multipleItemsPerSlot>
<!multipleItemsPerSlot>
slot := add(data, mul(index, <storageSize>))
offset := 0
</multipleItemsPerSlot>
}
)")
("functionName", functionName)
("arrayLen", arrayLengthFunction(_type))
("dataAreaFunc", arrayDataAreaFunction(_type))
("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16)
("storageSize", _type.baseType()->storageSize().str())
.render();
});
}
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
{ {
solAssert(!_type.isByteArray(), ""); solAssert(!_type.isByteArray(), "");
@ -531,18 +767,29 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
} }
)"); )");
templ("functionName", functionName); templ("functionName", functionName);
if (_type.location() == DataLocation::Memory) switch (_type.location())
{
case DataLocation::Memory:
templ("advance", "0x20"); templ("advance", "0x20");
else if (_type.location() == DataLocation::Storage) break;
templ("advance", "1"); case DataLocation::Storage:
else if (_type.location() == DataLocation::CallData) {
templ("advance", toCompactHexWithPrefix( u256 size = _type.baseType()->storageSize();
solAssert(size >= 1, "");
templ("advance", toCompactHexWithPrefix(size));
break;
}
case DataLocation::CallData:
{
u256 size =
_type.baseType()->isDynamicallyEncoded() ? _type.baseType()->isDynamicallyEncoded() ?
32 : 32 :
_type.baseType()->calldataEncodedSize() _type.baseType()->calldataEncodedSize();
)); solAssert(size >= 32 && size % 32 == 0, "");
else templ("advance", toCompactHexWithPrefix(size));
solAssert(false, ""); break;
}
}
return templ.render(); return templ.render();
}); });
} }
@ -613,6 +860,89 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
}); });
} }
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_dynamic" +
string(_splitFunctionTypes ? "split_" : "") +
"_" +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"(
function <functionName>(slot, offset) -> value {
value := <extract>(sload(slot), offset)
}
)")
("functionName", functionName)
("extract", extractFromStorageValueDynamic(_type, _splitFunctionTypes))
.render();
});
}
string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const _offset)
{
string const functionName =
"update_storage_value_" +
(_offset.is_initialized() ? ("offset_" + to_string(*_offset)) : "") +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
if (_type.isValueType())
{
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(_type.storageBytes() > 0, "Invalid storage bytes size.");
return Whiskers(R"(
function <functionName>(slot, <offset>value) {
sstore(slot, <update>(sload(slot), <offset><prepare>(value)))
}
)")
("functionName", functionName)
("update",
_offset.is_initialized() ?
updateByteSliceFunction(_type.storageBytes(), *_offset) :
updateByteSliceFunctionDynamic(_type.storageBytes())
)
("offset", _offset.is_initialized() ? "" : "offset, ")
("prepare", prepareStoreFunction(_type))
.render();
}
else
{
if (_type.category() == Type::Category::Array)
solUnimplementedAssert(false, "");
else if (_type.category() == Type::Category::Struct)
solUnimplementedAssert(false, "");
else
solAssert(false, "Invalid non-value type for assignment.");
}
});
}
string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"extract_from_storage_value_dynamic" +
string(_splitFunctionTypes ? "split_" : "") +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot_value, offset) -> value {
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
}
)")
("functionName", functionName)
("shr", shiftRightFunctionDynamic())
("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes))
.render();
});
}
string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes) string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{ {
solUnimplementedAssert(!_splitFunctionTypes, ""); solUnimplementedAssert(!_splitFunctionTypes, "");
@ -631,7 +961,7 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs
)") )")
("functionName", functionName) ("functionName", functionName)
("shr", shiftRightFunction(_offset * 8)) ("shr", shiftRightFunction(_offset * 8))
("cleanupStorage", cleanupFromStorageFunction(_type, false)) ("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes))
.render(); .render();
}); });
} }

View File

@ -74,31 +74,64 @@ public:
std::string leftAlignFunction(Type const& _type); std::string leftAlignFunction(Type const& _type);
std::string shiftLeftFunction(size_t _numBits); std::string shiftLeftFunction(size_t _numBits);
std::string shiftLeftFunctionDynamic();
std::string shiftRightFunction(size_t _numBits); std::string shiftRightFunction(size_t _numBits);
std::string shiftRightFunctionDynamic();
/// @returns the name of a function f(value, toInsert) -> newValue which replaces the /// @returns the name of a function which replaces the
/// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant
/// byte) by the _numBytes least significant bytes of `toInsert`. /// byte) by the _numBytes least significant bytes of `toInsert`.
/// signature: (value, toInsert) -> result
std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes);
/// signature: (value, shiftBytes, toInsert) -> result
std::string updateByteSliceFunctionDynamic(size_t _numBytes);
/// @returns the name of a function that rounds its input to the next multiple /// @returns the name of a function that rounds its input to the next multiple
/// of 32 or the input if it is a multiple of 32. /// of 32 or the input if it is a multiple of 32.
/// signature: (value) -> result
std::string roundUpFunction(); std::string roundUpFunction();
std::string overflowCheckedUIntAddFunction(size_t _bits); /// signature: (x, y) -> sum
std::string overflowCheckedIntAddFunction(IntegerType const& _type);
std::string overflowCheckedUIntMulFunction(size_t _bits); /// signature: (x, y) -> product
std::string overflowCheckedIntMulFunction(IntegerType const& _type);
/// @returns name of function to perform division on integers. /// @returns name of function to perform division on integers.
/// Checks for division by zero and the special case of /// Checks for division by zero and the special case of
/// signed division of the smallest number by -1. /// signed division of the smallest number by -1.
std::string overflowCheckedIntDivFunction(IntegerType const& _type); std::string overflowCheckedIntDivFunction(IntegerType const& _type);
/// @returns name of function to perform modulo on integers.
/// Reverts for modulo by zero.
std::string checkedIntModFunction(IntegerType const& _type);
/// @returns computes the difference between two values. /// @returns computes the difference between two values.
/// Assumes the input to be in range for the type. /// Assumes the input to be in range for the type.
std::string overflowCheckedUIntSubFunction(); /// signature: (x, y) -> diff
std::string overflowCheckedIntSubFunction(IntegerType const& _type);
/// @returns the name of a function that fetches the length of the given
/// array
/// signature: (array) -> length
std::string arrayLengthFunction(ArrayType const& _type); std::string arrayLengthFunction(ArrayType const& _type);
/// @returns the name of a function that resizes a storage array
/// signature: (array, newLen)
std::string resizeDynamicArrayFunction(ArrayType const& _type);
/// @returns the name of a function that will clear the storage area given
/// by the start and end (exclusive) parameters (slots). Only works for value types.
/// signature: (start, end)
std::string clearStorageRangeFunction(Type const& _type);
/// Returns the name of a function that will convert a given length to the
/// size in memory (number of storage slots or calldata/memory bytes) it
/// will require.
/// signature: (length) -> size
std::string arrayConvertLengthToSize(ArrayType const& _type);
/// @returns the name of a function that computes the number of bytes required /// @returns the name of a function that computes the number of bytes required
/// to store an array in memory given its length (internally encoded, not ABI encoded). /// to store an array in memory given its length (internally encoded, not ABI encoded).
/// The function reverts for too large lengths. /// The function reverts for too large lengths.
@ -107,8 +140,14 @@ public:
/// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer /// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
/// for the data position of an array which is stored in that slot / memory area / calldata area. /// for the data position of an array which is stored in that slot / memory area / calldata area.
std::string arrayDataAreaFunction(ArrayType const& _type); std::string arrayDataAreaFunction(ArrayType const& _type);
/// @returns the name of a function that returns the slot and offset for the
/// given array and index
/// signature: (array, index) -> slot, offset
std::string storageArrayIndexAccessFunction(ArrayType const& _type);
/// @returns the name of a function that advances an array data pointer to the next element. /// @returns the name of a function that advances an array data pointer to the next element.
/// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot. /// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots.
std::string nextArrayElementFunction(ArrayType const& _type); std::string nextArrayElementFunction(ArrayType const& _type);
/// @returns the name of a function that performs index access for mappings. /// @returns the name of a function that performs index access for mappings.
@ -121,6 +160,7 @@ public:
/// @param _splitFunctionTypes if false, returns the address and function signature in a /// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable. /// single variable.
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes); std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
std::string readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes);
/// @returns a function that extracts a value type from storage slot that has been /// @returns a function that extracts a value type from storage slot that has been
/// retrieved already. /// retrieved already.
@ -128,6 +168,13 @@ public:
/// @param _splitFunctionTypes if false, returns the address and function signature in a /// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable. /// single variable.
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes); std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
std::string extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes);
/// Returns the name of a function will write the given value to
/// the specified slot and offset. If offset is not given, it is expected as
/// runtime parameter.
/// signature: (slot, [offset,] value)
std::string updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const _offset = boost::optional<unsigned>());
/// Performs cleanup after reading from a potentially compressed storage slot. /// Performs cleanup after reading from a potentially compressed storage slot.
/// The function does not perform any validation, it just masks or sign-extends /// The function does not perform any validation, it just masks or sign-extends

View File

@ -30,6 +30,7 @@
#include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/codegen/CompilerUtils.h>
#include <libyul/AssemblyStack.h> #include <libyul/AssemblyStack.h>
#include <libyul/Utilities.h>
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
#include <libdevcore/Whiskers.h> #include <libdevcore/Whiskers.h>
@ -45,8 +46,7 @@ using namespace dev::solidity;
pair<string, string> IRGenerator::run(ContractDefinition const& _contract) pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
{ {
// TODO Would be nice to pretty-print this while retaining comments. string const ir = yul::reindent(generate(_contract));
string ir = generate(_contract);
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
if (!asmStack.parseAndAnalyze("", ir)) if (!asmStack.parseAndAnalyze("", ir))
@ -54,7 +54,7 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
string errorMessage; string errorMessage;
for (auto const& error: asmStack.errors()) for (auto const& error: asmStack.errors())
errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error); errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error);
solAssert(false, "Invalid IR generated:\n" + errorMessage + "\n" + ir); solAssert(false, ir + "\n\nInvalid IR generated:\n" + errorMessage + "\n");
} }
asmStack.optimize(); asmStack.optimize();

View File

@ -56,6 +56,35 @@ struct CopyTranslate: public yul::ASTCopier
using ASTCopier::operator(); using ASTCopier::operator();
yul::Expression operator()(yul::Identifier const& _identifier) override
{
if (m_references.count(&_identifier))
{
auto const& reference = m_references.at(&_identifier);
auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
solUnimplementedAssert(varDecl, "");
if (reference.isOffset || reference.isSlot)
{
solAssert(reference.isOffset != reference.isSlot, "");
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(*varDecl);
string const value = reference.isSlot ?
slot_offset.first.str() :
to_string(slot_offset.second);
return yul::Literal{
_identifier.location,
yul::LiteralKind::Number,
yul::YulString{value},
yul::YulString{"uint256"}
};
}
}
return ASTCopier::operator()(_identifier);
}
yul::YulString translateIdentifier(yul::YulString _name) override yul::YulString translateIdentifier(yul::YulString _name) override
{ {
// Strictly, the dialect used by inline assembly (m_dialect) could be different // Strictly, the dialect used by inline assembly (m_dialect) could be different
@ -76,9 +105,10 @@ struct CopyTranslate: public yul::ASTCopier
auto const& reference = m_references.at(&_identifier); auto const& reference = m_references.at(&_identifier);
auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration); auto const varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
solUnimplementedAssert(varDecl, ""); solUnimplementedAssert(varDecl, "");
solUnimplementedAssert(
solAssert(
reference.isOffset == false && reference.isSlot == false, reference.isOffset == false && reference.isSlot == false,
"" "Should not be called for offset/slot"
); );
return yul::Identifier{ return yul::Identifier{
@ -550,13 +580,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert"); solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert");
solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert"); solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert");
Type const* messageArgumentType = arguments.size() > 1 ? arguments[1]->annotation().type : nullptr;
string requireOrAssertFunction = m_utils.requireOrAssertFunction( string requireOrAssertFunction = m_utils.requireOrAssertFunction(
functionType->kind() == FunctionType::Kind::Assert, functionType->kind() == FunctionType::Kind::Assert,
arguments.size() > 1 ? arguments[1]->annotation().type : nullptr messageArgumentType
); );
m_code << move(requireOrAssertFunction) << "(" << m_context.variable(*arguments[0]); m_code << move(requireOrAssertFunction) << "(" << m_context.variable(*arguments[0]);
if (arguments.size() > 1) if (messageArgumentType && messageArgumentType->sizeOnStack() > 0)
m_code << ", " << m_context.variable(*arguments[1]); m_code << ", " << m_context.variable(*arguments[1]);
m_code << ")\n"; m_code << ")\n";
@ -702,7 +733,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
} }
case Type::Category::Array: case Type::Category::Array:
{ {
solUnimplementedAssert(false, ""); auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
solAssert(member == "length", "");
if (!type.isDynamicallySized())
defineExpression(_memberAccess) << type.length() << "\n";
else
switch (type.location())
{
case DataLocation::CallData:
solUnimplementedAssert(false, "");
//m_context << Instruction::SWAP1 << Instruction::POP;
break;
case DataLocation::Storage:
setLValue(_memberAccess, make_unique<IRStorageArrayLength>(
m_context,
m_context.variable(_memberAccess.expression()),
*_memberAccess.annotation().type,
type
));
break;
case DataLocation::Memory:
solUnimplementedAssert(false, "");
//m_context << Instruction::MLOAD;
break;
}
break;
} }
case Type::Category::FixedBytes: case Type::Category::FixedBytes:
{ {
@ -761,7 +820,45 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
)); ));
} }
else if (baseType.category() == Type::Category::Array) else if (baseType.category() == Type::Category::Array)
solUnimplementedAssert(false, ""); {
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
switch (arrayType.location())
{
case DataLocation::Storage:
{
string slot = m_context.newYulVariable();
string offset = m_context.newYulVariable();
m_code << Whiskers(R"(
let <slot>, <offset> := <indexFunc>(<array>, <index>)
)")
("slot", slot)
("offset", offset)
("indexFunc", m_utils.storageArrayIndexAccessFunction(arrayType))
("array", m_context.variable(_indexAccess.baseExpression()))
("index", m_context.variable(*_indexAccess.indexExpression()))
.render();
setLValue(_indexAccess, make_unique<IRStorageItem>(
m_context,
slot,
offset,
*_indexAccess.annotation().type
));
break;
}
case DataLocation::Memory:
solUnimplementedAssert(false, "");
break;
case DataLocation::CallData:
solUnimplementedAssert(false, "");
break;
}
}
else if (baseType.category() == Type::Category::FixedBytes) else if (baseType.category() == Type::Category::FixedBytes)
solUnimplementedAssert(false, ""); solUnimplementedAssert(false, "");
else if (baseType.category() == Type::Category::TypeType) else if (baseType.category() == Type::Category::TypeType)
@ -1097,18 +1194,28 @@ string IRGeneratorForStatements::binaryOperation(
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type)) if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
{ {
string fun; string fun;
// TODO: Only division is implemented for signed integers for now. // TODO: Implement all operations for signed and unsigned types.
if (!type->isSigned()) switch (_operator)
{ {
if (_operator == Token::Add) case Token::Add:
fun = m_utils.overflowCheckedUIntAddFunction(type->numBits()); fun = m_utils.overflowCheckedIntAddFunction(*type);
else if (_operator == Token::Sub) break;
fun = m_utils.overflowCheckedUIntSubFunction(); case Token::Sub:
else if (_operator == Token::Mul) fun = m_utils.overflowCheckedIntSubFunction(*type);
fun = m_utils.overflowCheckedUIntMulFunction(type->numBits()); break;
case Token::Mul:
fun = m_utils.overflowCheckedIntMulFunction(*type);
break;
case Token::Div:
fun = m_utils.overflowCheckedIntDivFunction(*type);
break;
case Token::Mod:
fun = m_utils.checkedIntModFunction(*type);
break;
default:
break;
} }
if (_operator == Token::Div)
fun = m_utils.overflowCheckedIntDivFunction(*type);
solUnimplementedAssert(!fun.empty(), ""); solUnimplementedAssert(!fun.empty(), "");
return fun + "(" + _left + ", " + _right + ")\n"; return fun + "(" + _left + ", " + _right + ")\n";
} }

View File

@ -55,25 +55,36 @@ IRStorageItem::IRStorageItem(
IRGenerationContext& _context, IRGenerationContext& _context,
VariableDeclaration const& _varDecl VariableDeclaration const& _varDecl
): ):
IRLValue(_context, _varDecl.annotation().type) IRStorageItem(
_context,
*_varDecl.annotation().type,
_context.storageLocationOfVariable(_varDecl)
)
{ }
IRStorageItem::IRStorageItem(
IRGenerationContext& _context,
Type const& _type,
std::pair<u256, unsigned> slot_offset
):
IRLValue(_context, &_type),
m_slot(toCompactHexWithPrefix(slot_offset.first)),
m_offset(slot_offset.second)
{ {
u256 slot;
unsigned offset;
std::tie(slot, offset) = _context.storageLocationOfVariable(_varDecl);
m_slot = toCompactHexWithPrefix(slot);
m_offset = offset;
} }
IRStorageItem::IRStorageItem( IRStorageItem::IRStorageItem(
IRGenerationContext& _context, IRGenerationContext& _context,
string _slot, string _slot,
unsigned _offset, boost::variant<string, unsigned> _offset,
Type const& _type Type const& _type
): ):
IRLValue(_context, &_type), IRLValue(_context, &_type),
m_slot(move(_slot)), m_slot(move(_slot)),
m_offset(_offset) m_offset(std::move(_offset))
{ {
solAssert(!m_offset.empty(), "");
solAssert(!m_slot.empty(), "");
} }
string IRStorageItem::retrieveValue() const string IRStorageItem::retrieveValue() const
@ -81,42 +92,77 @@ string IRStorageItem::retrieveValue() const
if (!m_type->isValueType()) if (!m_type->isValueType())
return m_slot; return m_slot;
solUnimplementedAssert(m_type->category() != Type::Category::Function, ""); solUnimplementedAssert(m_type->category() != Type::Category::Function, "");
return m_context.utils().readFromStorage(*m_type, m_offset, false) + "(" + m_slot + ")"; if (m_offset.type() == typeid(string))
return
m_context.utils().readFromStorageDynamic(*m_type, false) +
"(" +
m_slot +
", " +
boost::get<string>(m_offset) +
")";
else if (m_offset.type() == typeid(unsigned))
return
m_context.utils().readFromStorage(*m_type, boost::get<unsigned>(m_offset), false) +
"(" +
m_slot +
")";
solAssert(false, "");
} }
string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const
{ {
if (m_type->isValueType()) if (m_type->isValueType())
{
solAssert(m_type->storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(m_type->storageBytes() > 0, "Invalid storage bytes size.");
solAssert(m_type->storageBytes() + m_offset <= 32, "");
solAssert(_sourceType == *m_type, "Different type, but might not be an error."); solAssert(_sourceType == *m_type, "Different type, but might not be an error.");
return Whiskers("sstore(<slot>, <update>(sload(<slot>), <prepare>(<value>)))\n") boost::optional<unsigned> offset;
("slot", m_slot)
("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset)) if (m_offset.type() == typeid(unsigned))
("prepare", m_context.utils().prepareStoreFunction(*m_type)) offset = get<unsigned>(m_offset);
("value", _value)
.render(); return
} m_context.utils().updateStorageValueFunction(*m_type, offset) +
else "(" +
{ m_slot +
solAssert( (m_offset.type() == typeid(string) ?
_sourceType.category() == m_type->category(), (", " + get<string>(m_offset)) :
"Wrong type conversation for assignment." ""
); ) +
if (m_type->category() == Type::Category::Array) ", " +
solUnimplementedAssert(false, ""); _value +
else if (m_type->category() == Type::Category::Struct) ")\n";
solUnimplementedAssert(false, "");
else
solAssert(false, "Invalid non-value type for assignment.");
}
} }
string IRStorageItem::setToZero() const string IRStorageItem::setToZero() const
{ {
solUnimplemented("Delete for storage location not yet implemented"); solUnimplementedAssert(m_type->isValueType(), "");
return storeValue(m_context.utils().zeroValueFunction(*m_type) + "()", *m_type);
}
IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string _slot, Type const& _type, ArrayType const& _arrayType):
IRLValue(_context, &_type), m_arrayType(_arrayType), m_slot(move(_slot))
{
solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!");
}
string IRStorageArrayLength::retrieveValue() const
{
return m_context.utils().arrayLengthFunction(m_arrayType) + "(" + m_slot + ")\n";
}
string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const
{
solAssert(_type == *m_type, "Different type, but might not be an error.");
return m_context.utils().resizeDynamicArrayFunction(m_arrayType) +
"(" +
m_slot +
", " +
_value +
")\n";
}
string IRStorageArrayLength::setToZero() const
{
return storeValue("0", *TypeProvider::uint256());
} }

View File

@ -20,8 +20,11 @@
#pragma once #pragma once
#include <libdevcore/Common.h>
#include <string> #include <string>
#include <ostream> #include <ostream>
#include <boost/variant.hpp>
namespace dev namespace dev
{ {
@ -31,6 +34,7 @@ namespace solidity
class VariableDeclaration; class VariableDeclaration;
class IRGenerationContext; class IRGenerationContext;
class Type; class Type;
class ArrayType;
/** /**
* Abstract class used to retrieve, delete and store data in LValues. * Abstract class used to retrieve, delete and store data in LValues.
@ -83,7 +87,7 @@ public:
IRStorageItem( IRStorageItem(
IRGenerationContext& _context, IRGenerationContext& _context,
std::string _slot, std::string _slot,
unsigned _offset, boost::variant<std::string, unsigned> _offset,
Type const& _type Type const& _type
); );
std::string retrieveValue() const override; std::string retrieveValue() const override;
@ -91,10 +95,37 @@ public:
std::string setToZero() const override; std::string setToZero() const override;
private: private:
std::string m_slot; IRStorageItem(
unsigned m_offset; IRGenerationContext& _context,
Type const& _type,
std::pair<u256, unsigned> slot_offset
);
std::string const m_slot;
/// unsigned: Used when the offset is known at compile time, uses optimized
/// functions
/// string: Used when the offset is determined at run time
boost::variant<std::string, unsigned> const m_offset;
}; };
/**
* Reference to the "length" member of a dynamically-sized storage array. This is an LValue with special
* semantics since assignments to it might reduce its length and thus the array's members have to be
* deleted.
*/
class IRStorageArrayLength: public IRLValue
{
public:
IRStorageArrayLength(IRGenerationContext& _context, std::string _slot, Type const& _type, ArrayType const& _arrayType);
std::string retrieveValue() const override;
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
ArrayType const& m_arrayType;
std::string const m_slot;
};
} }
} }

View File

@ -60,17 +60,21 @@ void CVC4Interface::addAssertion(Expression const& _expr)
{ {
m_solver.assertFormula(toCVC4Expr(_expr)); m_solver.assertFormula(toCVC4Expr(_expr));
} }
catch (CVC4::TypeCheckingException const&) catch (CVC4::TypeCheckingException const& _e)
{ {
solAssert(false, ""); solAssert(false, _e.what());
} }
catch (CVC4::LogicException const&) catch (CVC4::LogicException const& _e)
{ {
solAssert(false, ""); solAssert(false, _e.what());
} }
catch (CVC4::UnsafeInterruptException const&) catch (CVC4::UnsafeInterruptException const& _e)
{ {
solAssert(false, ""); solAssert(false, _e.what());
}
catch (CVC4::Exception const& _e)
{
solAssert(false, _e.what());
} }
} }
@ -120,58 +124,82 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
for (auto const& arg: _expr.arguments) for (auto const& arg: _expr.arguments)
arguments.push_back(toCVC4Expr(arg)); arguments.push_back(toCVC4Expr(arg));
string const& n = _expr.name; try
// Function application
if (!arguments.empty() && m_variables.count(_expr.name))
return m_context.mkExpr(CVC4::kind::APPLY_UF, m_variables.at(n), arguments);
// Literal
else if (arguments.empty())
{ {
if (n == "true") string const& n = _expr.name;
return m_context.mkConst(true); // Function application
else if (n == "false") if (!arguments.empty() && m_variables.count(_expr.name))
return m_context.mkConst(false); return m_context.mkExpr(CVC4::kind::APPLY_UF, m_variables.at(n), arguments);
else // Literal
// We assume it is an integer... else if (arguments.empty())
return m_context.mkConst(CVC4::Rational(n)); {
if (n == "true")
return m_context.mkConst(true);
else if (n == "false")
return m_context.mkConst(false);
else
try
{
return m_context.mkConst(CVC4::Rational(n));
}
catch (CVC4::TypeCheckingException const& _e)
{
solAssert(false, _e.what());
}
catch (CVC4::Exception const& _e)
{
solAssert(false, _e.what());
}
}
solAssert(_expr.hasCorrectArity(), "");
if (n == "ite")
return arguments[0].iteExpr(arguments[1], arguments[2]);
else if (n == "not")
return arguments[0].notExpr();
else if (n == "and")
return arguments[0].andExpr(arguments[1]);
else if (n == "or")
return arguments[0].orExpr(arguments[1]);
else if (n == "implies")
return m_context.mkExpr(CVC4::kind::IMPLIES, arguments[0], arguments[1]);
else if (n == "=")
return m_context.mkExpr(CVC4::kind::EQUAL, arguments[0], arguments[1]);
else if (n == "<")
return m_context.mkExpr(CVC4::kind::LT, arguments[0], arguments[1]);
else if (n == "<=")
return m_context.mkExpr(CVC4::kind::LEQ, arguments[0], arguments[1]);
else if (n == ">")
return m_context.mkExpr(CVC4::kind::GT, arguments[0], arguments[1]);
else if (n == ">=")
return m_context.mkExpr(CVC4::kind::GEQ, arguments[0], arguments[1]);
else if (n == "+")
return m_context.mkExpr(CVC4::kind::PLUS, arguments[0], arguments[1]);
else if (n == "-")
return m_context.mkExpr(CVC4::kind::MINUS, arguments[0], arguments[1]);
else if (n == "*")
return m_context.mkExpr(CVC4::kind::MULT, arguments[0], arguments[1]);
else if (n == "/")
return m_context.mkExpr(CVC4::kind::INTS_DIVISION_TOTAL, arguments[0], arguments[1]);
else if (n == "mod")
return m_context.mkExpr(CVC4::kind::INTS_MODULUS, arguments[0], arguments[1]);
else if (n == "select")
return m_context.mkExpr(CVC4::kind::SELECT, arguments[0], arguments[1]);
else if (n == "store")
return m_context.mkExpr(CVC4::kind::STORE, arguments[0], arguments[1], arguments[2]);
solAssert(false, "");
}
catch (CVC4::TypeCheckingException const& _e)
{
solAssert(false, _e.what());
}
catch (CVC4::Exception const& _e)
{
solAssert(false, _e.what());
} }
solAssert(_expr.hasCorrectArity(), "");
if (n == "ite")
return arguments[0].iteExpr(arguments[1], arguments[2]);
else if (n == "not")
return arguments[0].notExpr();
else if (n == "and")
return arguments[0].andExpr(arguments[1]);
else if (n == "or")
return arguments[0].orExpr(arguments[1]);
else if (n == "=")
return m_context.mkExpr(CVC4::kind::EQUAL, arguments[0], arguments[1]);
else if (n == "<")
return m_context.mkExpr(CVC4::kind::LT, arguments[0], arguments[1]);
else if (n == "<=")
return m_context.mkExpr(CVC4::kind::LEQ, arguments[0], arguments[1]);
else if (n == ">")
return m_context.mkExpr(CVC4::kind::GT, arguments[0], arguments[1]);
else if (n == ">=")
return m_context.mkExpr(CVC4::kind::GEQ, arguments[0], arguments[1]);
else if (n == "+")
return m_context.mkExpr(CVC4::kind::PLUS, arguments[0], arguments[1]);
else if (n == "-")
return m_context.mkExpr(CVC4::kind::MINUS, arguments[0], arguments[1]);
else if (n == "*")
return m_context.mkExpr(CVC4::kind::MULT, arguments[0], arguments[1]);
else if (n == "/")
return m_context.mkExpr(CVC4::kind::INTS_DIVISION_TOTAL, arguments[0], arguments[1]);
else if (n == "mod")
return m_context.mkExpr(CVC4::kind::INTS_MODULUS, arguments[0], arguments[1]);
else if (n == "select")
return m_context.mkExpr(CVC4::kind::SELECT, arguments[0], arguments[1]);
else if (n == "store")
return m_context.mkExpr(CVC4::kind::STORE, arguments[0], arguments[1], arguments[2]);
// Cannot reach here.
solAssert(false, ""); solAssert(false, "");
return arguments[0];
} }
CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort) CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort)

View File

@ -23,15 +23,15 @@ using namespace std;
using namespace dev; using namespace dev;
using namespace dev::solidity::smt; using namespace dev::solidity::smt;
EncodingContext::EncodingContext(SolverInterface& _solver): EncodingContext::EncodingContext(std::shared_ptr<SolverInterface> _solver):
m_solver(_solver), m_thisAddress(make_unique<SymbolicAddressVariable>("this", *_solver)),
m_thisAddress(make_unique<SymbolicAddressVariable>("this", m_solver)) m_solver(_solver)
{ {
auto sort = make_shared<ArraySort>( auto sort = make_shared<ArraySort>(
make_shared<Sort>(Kind::Int), make_shared<Sort>(Kind::Int),
make_shared<Sort>(Kind::Int) make_shared<Sort>(Kind::Int)
); );
m_balances = make_unique<SymbolicVariable>(sort, "balances", m_solver); m_balances = make_unique<SymbolicVariable>(sort, "balances", *m_solver);
} }
void EncodingContext::reset() void EncodingContext::reset()
@ -41,6 +41,7 @@ void EncodingContext::reset()
m_globalContext.clear(); m_globalContext.clear();
m_thisAddress->increaseIndex(); m_thisAddress->increaseIndex();
m_balances->increaseIndex(); m_balances->increaseIndex();
m_assertions.clear();
} }
/// Variables. /// Variables.
@ -55,7 +56,7 @@ bool EncodingContext::createVariable(solidity::VariableDeclaration const& _varDe
{ {
solAssert(!knownVariable(_varDecl), ""); solAssert(!knownVariable(_varDecl), "");
auto const& type = _varDecl.type(); auto const& type = _varDecl.type();
auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), m_solver); auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), *m_solver);
m_variables.emplace(&_varDecl, result.second); m_variables.emplace(&_varDecl, result.second);
return result.first; return result.first;
} }
@ -105,7 +106,7 @@ void EncodingContext::setZeroValue(solidity::VariableDeclaration const& _decl)
void EncodingContext::setZeroValue(SymbolicVariable& _variable) void EncodingContext::setZeroValue(SymbolicVariable& _variable)
{ {
setSymbolicZeroValue(_variable, m_solver); setSymbolicZeroValue(_variable, *m_solver);
} }
void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl) void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl)
@ -116,7 +117,7 @@ void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl
void EncodingContext::setUnknownValue(SymbolicVariable& _variable) void EncodingContext::setUnknownValue(SymbolicVariable& _variable)
{ {
setSymbolicUnknownValue(_variable, m_solver); setSymbolicUnknownValue(_variable, *m_solver);
} }
/// Expressions /// Expressions
@ -143,7 +144,7 @@ bool EncodingContext::createExpression(solidity::Expression const& _e, shared_pt
} }
else else
{ {
auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), m_solver); auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), *m_solver);
m_expressions.emplace(&_e, result.second); m_expressions.emplace(&_e, result.second);
return result.first; return result.first;
} }
@ -165,7 +166,7 @@ shared_ptr<SymbolicVariable> EncodingContext::globalSymbol(string const& _name)
bool EncodingContext::createGlobalSymbol(string const& _name, solidity::Expression const& _expr) bool EncodingContext::createGlobalSymbol(string const& _name, solidity::Expression const& _expr)
{ {
solAssert(!knownGlobalSymbol(_name), ""); solAssert(!knownGlobalSymbol(_name), "");
auto result = newSymbolicVariable(*_expr.annotation().type, _name, m_solver); auto result = newSymbolicVariable(*_expr.annotation().type, _name, *m_solver);
m_globalContext.emplace(_name, result.second); m_globalContext.emplace(_name, result.second);
setUnknownValue(*result.second); setUnknownValue(*result.second);
return result.first; return result.first;
@ -207,9 +208,40 @@ void EncodingContext::transfer(Expression _from, Expression _to, Expression _val
m_balances->valueAtIndex(indexBefore), m_balances->valueAtIndex(indexBefore),
m_balances->valueAtIndex(indexAfter) m_balances->valueAtIndex(indexAfter)
); );
m_solver.addAssertion(m_balances->currentValue() == newBalances); m_solver->addAssertion(m_balances->currentValue() == newBalances);
} }
/// Solver.
Expression EncodingContext::assertions()
{
if (m_assertions.empty())
return Expression(true);
return m_assertions.back();
}
void EncodingContext::pushSolver()
{
m_assertions.push_back(assertions());
}
void EncodingContext::popSolver()
{
solAssert(!m_assertions.empty(), "");
m_assertions.pop_back();
}
void EncodingContext::addAssertion(Expression const& _expr)
{
if (m_assertions.empty())
m_assertions.push_back(_expr);
else
m_assertions.back() = _expr && move(m_assertions.back());
}
/// Private helpers.
void EncodingContext::addBalance(Expression _address, Expression _value) void EncodingContext::addBalance(Expression _address, Expression _value)
{ {
auto newBalances = Expression::store( auto newBalances = Expression::store(
@ -218,5 +250,5 @@ void EncodingContext::addBalance(Expression _address, Expression _value)
balance(_address) + move(_value) balance(_address) + move(_value)
); );
m_balances->increaseIndex(); m_balances->increaseIndex();
m_solver.addAssertion(newBalances == m_balances->currentValue()); m_solver->addAssertion(newBalances == m_balances->currentValue());
} }

View File

@ -36,12 +36,12 @@ namespace smt
class EncodingContext class EncodingContext
{ {
public: public:
EncodingContext(SolverInterface& _solver); EncodingContext(std::shared_ptr<SolverInterface> _solver);
/// Resets the entire context. /// Resets the entire context.
void reset(); void reset();
/// Methods related to variables. /// Variables.
//@{ //@{
/// @returns the symbolic representation of a program variable. /// @returns the symbolic representation of a program variable.
std::shared_ptr<SymbolicVariable> variable(solidity::VariableDeclaration const& _varDecl); std::shared_ptr<SymbolicVariable> variable(solidity::VariableDeclaration const& _varDecl);
@ -74,7 +74,7 @@ public:
void setUnknownValue(SymbolicVariable& _variable); void setUnknownValue(SymbolicVariable& _variable);
//@} //@}
/// Methods related to expressions. /// Expressions.
////@{ ////@{
/// @returns the symbolic representation of an AST node expression. /// @returns the symbolic representation of an AST node expression.
std::shared_ptr<SymbolicVariable> expression(solidity::Expression const& _e); std::shared_ptr<SymbolicVariable> expression(solidity::Expression const& _e);
@ -88,12 +88,13 @@ public:
bool knownExpression(solidity::Expression const& _e) const; bool knownExpression(solidity::Expression const& _e) const;
//@} //@}
/// Methods related to global variables and functions. /// Global variables and functions.
//@{ //@{
/// Global variables and functions. /// Global variables and functions.
std::shared_ptr<SymbolicVariable> globalSymbol(std::string const& _name); std::shared_ptr<SymbolicVariable> globalSymbol(std::string const& _name);
/// @returns all symbolic variables. /// @returns all symbolic globals.
std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> const& globalSymbols() const { return m_globalContext; } std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> const& globalSymbols() const { return m_globalContext; }
/// Defines a new global variable or function /// Defines a new global variable or function
/// and @returns true if type was abstracted. /// and @returns true if type was abstracted.
bool createGlobalSymbol(std::string const& _name, solidity::Expression const& _expr); bool createGlobalSymbol(std::string const& _name, solidity::Expression const& _expr);
@ -101,7 +102,7 @@ public:
bool knownGlobalSymbol(std::string const& _var) const; bool knownGlobalSymbol(std::string const& _var) const;
//@} //@}
/// Blockchain related methods. /// Blockchain.
//@{ //@{
/// Value of `this` address. /// Value of `this` address.
Expression thisAddress(); Expression thisAddress();
@ -113,12 +114,22 @@ public:
void transfer(Expression _from, Expression _to, Expression _value); void transfer(Expression _from, Expression _to, Expression _value);
//@} //@}
/// Solver.
//@{
/// @returns conjunction of all added assertions.
Expression assertions();
void pushSolver();
void popSolver();
void addAssertion(Expression const& _e);
std::shared_ptr<SolverInterface> solver() { return m_solver; }
//@}
private: private:
/// Adds _value to _account's balance. /// Adds _value to _account's balance.
void addBalance(Expression _account, Expression _value); void addBalance(Expression _account, Expression _value);
SolverInterface& m_solver; /// Symbolic expressions.
//{@
/// Symbolic variables. /// Symbolic variables.
std::unordered_map<solidity::VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables; std::unordered_map<solidity::VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables;
@ -134,6 +145,16 @@ private:
/// Symbolic balances. /// Symbolic balances.
std::unique_ptr<SymbolicVariable> m_balances; std::unique_ptr<SymbolicVariable> m_balances;
//@}
/// Solver related.
//@{
/// Solver can be SMT solver or Horn solver in the future.
std::shared_ptr<SolverInterface> m_solver;
/// Assertion stack.
std::vector<Expression> m_assertions;
//@}
}; };
} }

View File

@ -34,10 +34,10 @@ using namespace langutil;
using namespace dev::solidity; using namespace dev::solidity;
SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map<h256, string> const& _smtlib2Responses): SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map<h256, string> const& _smtlib2Responses):
m_interface(make_unique<smt::SMTPortfolio>(_smtlib2Responses)), m_interface(make_shared<smt::SMTPortfolio>(_smtlib2Responses)),
m_errorReporterReference(_errorReporter), m_errorReporterReference(_errorReporter),
m_errorReporter(m_smtErrors), m_errorReporter(m_smtErrors),
m_context(*m_interface) m_context(m_interface)
{ {
#if defined (HAVE_Z3) || defined (HAVE_CVC4) #if defined (HAVE_Z3) || defined (HAVE_CVC4)
if (!_smtlib2Responses.empty()) if (!_smtlib2Responses.empty())
@ -52,9 +52,12 @@ SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map<h256, string> const& _
void SMTChecker::analyze(SourceUnit const& _source, shared_ptr<Scanner> const& _scanner) void SMTChecker::analyze(SourceUnit const& _source, shared_ptr<Scanner> const& _scanner)
{ {
if (!_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
return;
m_scanner = _scanner; m_scanner = _scanner;
if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
_source.accept(*this); _source.accept(*this);
solAssert(m_interface->solvers() > 0, ""); solAssert(m_interface->solvers() > 0, "");
// If this check is true, Z3 and CVC4 are not available // If this check is true, Z3 and CVC4 are not available

View File

@ -19,7 +19,7 @@
#include <libsolidity/formal/EncodingContext.h> #include <libsolidity/formal/EncodingContext.h>
#include <libsolidity/formal/SolverInterface.h> #include <libsolidity/formal/SMTPortfolio.h>
#include <libsolidity/formal/SymbolicVariables.h> #include <libsolidity/formal/SymbolicVariables.h>
#include <libsolidity/formal/VariableUsage.h> #include <libsolidity/formal/VariableUsage.h>
@ -270,7 +270,7 @@ private:
/// @returns the VariableDeclaration referenced by an Identifier or nullptr. /// @returns the VariableDeclaration referenced by an Identifier or nullptr.
VariableDeclaration const* identifierToVariable(Expression const& _expr); VariableDeclaration const* identifierToVariable(Expression const& _expr);
std::unique_ptr<smt::SolverInterface> m_interface; std::shared_ptr<smt::SolverInterface> m_interface;
smt::VariableUsage m_variableUsage; smt::VariableUsage m_variableUsage;
bool m_loopExecutionHappened = false; bool m_loopExecutionHappened = false;
bool m_arrayAssignmentHappened = false; bool m_arrayAssignmentHappened = false;

View File

@ -52,6 +52,7 @@ public:
void declareVariable(std::string const&, Sort const&) override; void declareVariable(std::string const&, Sort const&) override;
void addAssertion(Expression const& _expr) override; void addAssertion(Expression const& _expr) override;
std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override; std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
std::vector<std::string> unhandledQueries() override; std::vector<std::string> unhandledQueries() override;
@ -60,6 +61,8 @@ private:
static bool solverAnswered(CheckResult result); static bool solverAnswered(CheckResult result);
std::vector<std::unique_ptr<smt::SolverInterface>> m_solvers; std::vector<std::unique_ptr<smt::SolverInterface>> m_solvers;
std::vector<Expression> m_assertions;
}; };
} }

View File

@ -133,6 +133,7 @@ public:
{"not", 1}, {"not", 1},
{"and", 2}, {"and", 2},
{"or", 2}, {"or", 2},
{"implies", 2},
{"=", 2}, {"=", 2},
{"<", 2}, {"<", 2},
{"<=", 2}, {"<=", 2},
@ -160,7 +161,12 @@ public:
static Expression implies(Expression _a, Expression _b) static Expression implies(Expression _a, Expression _b)
{ {
return !std::move(_a) || std::move(_b); return Expression(
"implies",
std::move(_a),
std::move(_b),
Kind::Bool
);
} }
/// select is the SMT representation of an array index access. /// select is the SMT representation of an array index access.

View File

@ -116,61 +116,77 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
for (auto const& arg: _expr.arguments) for (auto const& arg: _expr.arguments)
arguments.push_back(toZ3Expr(arg)); arguments.push_back(toZ3Expr(arg));
string const& n = _expr.name; try
if (m_functions.count(n))
return m_functions.at(n)(arguments);
else if (m_constants.count(n))
{ {
solAssert(arguments.empty(), ""); string const& n = _expr.name;
return m_constants.at(n); if (m_functions.count(n))
return m_functions.at(n)(arguments);
else if (m_constants.count(n))
{
solAssert(arguments.empty(), "");
return m_constants.at(n);
}
else if (arguments.empty())
{
if (n == "true")
return m_context.bool_val(true);
else if (n == "false")
return m_context.bool_val(false);
else
try
{
return m_context.int_val(n.c_str());
}
catch (z3::exception const& _e)
{
solAssert(false, _e.msg());
}
}
solAssert(_expr.hasCorrectArity(), "");
if (n == "ite")
return z3::ite(arguments[0], arguments[1], arguments[2]);
else if (n == "not")
return !arguments[0];
else if (n == "and")
return arguments[0] && arguments[1];
else if (n == "or")
return arguments[0] || arguments[1];
else if (n == "implies")
return z3::implies(arguments[0], arguments[1]);
else if (n == "=")
return arguments[0] == arguments[1];
else if (n == "<")
return arguments[0] < arguments[1];
else if (n == "<=")
return arguments[0] <= arguments[1];
else if (n == ">")
return arguments[0] > arguments[1];
else if (n == ">=")
return arguments[0] >= arguments[1];
else if (n == "+")
return arguments[0] + arguments[1];
else if (n == "-")
return arguments[0] - arguments[1];
else if (n == "*")
return arguments[0] * arguments[1];
else if (n == "/")
return arguments[0] / arguments[1];
else if (n == "mod")
return z3::mod(arguments[0], arguments[1]);
else if (n == "select")
return z3::select(arguments[0], arguments[1]);
else if (n == "store")
return z3::store(arguments[0], arguments[1], arguments[2]);
solAssert(false, "");
} }
else if (arguments.empty()) catch (z3::exception const& _e)
{ {
if (n == "true") solAssert(false, _e.msg());
return m_context.bool_val(true);
else if (n == "false")
return m_context.bool_val(false);
else
// We assume it is an integer...
return m_context.int_val(n.c_str());
} }
solAssert(_expr.hasCorrectArity(), "");
if (n == "ite")
return z3::ite(arguments[0], arguments[1], arguments[2]);
else if (n == "not")
return !arguments[0];
else if (n == "and")
return arguments[0] && arguments[1];
else if (n == "or")
return arguments[0] || arguments[1];
else if (n == "=")
return arguments[0] == arguments[1];
else if (n == "<")
return arguments[0] < arguments[1];
else if (n == "<=")
return arguments[0] <= arguments[1];
else if (n == ">")
return arguments[0] > arguments[1];
else if (n == ">=")
return arguments[0] >= arguments[1];
else if (n == "+")
return arguments[0] + arguments[1];
else if (n == "-")
return arguments[0] - arguments[1];
else if (n == "*")
return arguments[0] * arguments[1];
else if (n == "/")
return arguments[0] / arguments[1];
else if (n == "mod")
return z3::mod(arguments[0], arguments[1]);
else if (n == "select")
return z3::select(arguments[0], arguments[1]);
else if (n == "store")
return z3::store(arguments[0], arguments[1], arguments[2]);
// Cannot reach here.
solAssert(false, ""); solAssert(false, "");
return arguments[0];
} }
z3::sort Z3Interface::z3Sort(Sort const& _sort) z3::sort Z3Interface::z3Sort(Sort const& _sort)

View File

@ -217,7 +217,7 @@ bool CompilerStack::parse()
string const& path = sourcesToParse[i]; string const& path = sourcesToParse[i];
Source& source = m_sources[path]; Source& source = m_sources[path];
source.scanner->reset(); source.scanner->reset();
source.ast = Parser(m_errorReporter, m_evmVersion).parse(source.scanner); source.ast = Parser(m_errorReporter, m_evmVersion, m_parserErrorRecovery).parse(source.scanner);
if (!source.ast) if (!source.ast)
solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error."); solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error.");
else else

View File

@ -132,6 +132,14 @@ public:
/// Must be set before parsing. /// Must be set before parsing.
void setOptimiserSettings(OptimiserSettings _settings); void setOptimiserSettings(OptimiserSettings _settings);
/// Set whether or not parser error is desired.
/// When called without an argument it will revert to the default.
/// Must be set before parsing.
void setParserErrorRecovery(bool _wantErrorRecovery = false)
{
m_parserErrorRecovery = _wantErrorRecovery;
}
/// Set the EVM version used before running compile. /// Set the EVM version used before running compile.
/// When called without an argument it will revert to the default version. /// When called without an argument it will revert to the default version.
/// Must be set before parsing. /// Must be set before parsing.
@ -386,6 +394,7 @@ private:
langutil::ErrorList m_errorList; langutil::ErrorList m_errorList;
langutil::ErrorReporter m_errorReporter; langutil::ErrorReporter m_errorReporter;
bool m_metadataLiteralSources = false; bool m_metadataLiteralSources = false;
bool m_parserErrorRecovery = false;
State m_stackState = Empty; State m_stackState = Empty;
bool m_release = VersionIsRelease; bool m_release = VersionIsRelease;
}; };

View File

@ -301,7 +301,7 @@ boost::optional<Json::Value> checkAuxiliaryInputKeys(Json::Value const& _input)
boost::optional<Json::Value> checkSettingsKeys(Json::Value const& _input) boost::optional<Json::Value> checkSettingsKeys(Json::Value const& _input)
{ {
static set<string> keys{"evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"}; static set<string> keys{"parserErrorRecovery", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"};
return checkKeys(_input, keys, "settings"); return checkKeys(_input, keys, "settings");
} }
@ -576,6 +576,13 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
if (auto result = checkSettingsKeys(settings)) if (auto result = checkSettingsKeys(settings))
return *result; return *result;
if (settings.isMember("parserErrorRecovery"))
{
if (!settings["parserErrorRecovery"].isBool())
return formatFatalError("JSONError", "\"settings.parserErrorRecovery\" must be a Boolean.");
ret.parserErrorRecovery = settings["parserErrorRecovery"].asBool();
}
if (settings.isMember("evmVersion")) if (settings.isMember("evmVersion"))
{ {
if (!settings["evmVersion"].isString()) if (!settings["evmVersion"].isString())
@ -675,6 +682,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses) for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses)
compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second); compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second);
compilerStack.setEVMVersion(_inputsAndSettings.evmVersion); compilerStack.setEVMVersion(_inputsAndSettings.evmVersion);
compilerStack.setParserErrorRecovery(_inputsAndSettings.parserErrorRecovery);
compilerStack.setRemappings(_inputsAndSettings.remappings); compilerStack.setRemappings(_inputsAndSettings.remappings);
compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings));
compilerStack.setLibraries(_inputsAndSettings.libraries); compilerStack.setLibraries(_inputsAndSettings.libraries);
@ -769,6 +777,15 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
"Exception during compilation: " + boost::diagnostic_information(_exception) "Exception during compilation: " + boost::diagnostic_information(_exception)
)); ));
} }
catch (std::exception const& _e)
{
errors.append(formatError(
false,
"Exception",
"general",
"Unknown exception during compilation" + (_e.what() ? ": " + string(_e.what()) : ".")
));
}
catch (...) catch (...)
{ {
errors.append(formatError( errors.append(formatError(

View File

@ -60,6 +60,7 @@ private:
{ {
std::string language; std::string language;
Json::Value errors; Json::Value errors;
bool parserErrorRecovery = false;
std::map<std::string, std::string> sources; std::map<std::string, std::string> sources;
std::map<h256, std::string> smtLib2Responses; std::map<h256, std::string> smtLib2Responses;
langutil::EVMVersion evmVersion; langutil::EVMVersion evmVersion;

View File

@ -258,57 +258,75 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> name = nullptr;
ASTPointer<ASTString> docString; ASTPointer<ASTString> docString;
if (m_scanner->currentCommentLiteral() != "")
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
ContractDefinition::ContractKind contractKind = parseContractKind();
ASTPointer<ASTString> name = expectIdentifierToken();
vector<ASTPointer<InheritanceSpecifier>> baseContracts; vector<ASTPointer<InheritanceSpecifier>> baseContracts;
if (m_scanner->currentToken() == Token::Is)
do
{
m_scanner->next();
baseContracts.push_back(parseInheritanceSpecifier());
}
while (m_scanner->currentToken() == Token::Comma);
vector<ASTPointer<ASTNode>> subNodes; vector<ASTPointer<ASTNode>> subNodes;
expectToken(Token::LBrace); ContractDefinition::ContractKind contractKind = ContractDefinition::ContractKind::Contract;
while (true) try
{ {
Token currentTokenValue = m_scanner->currentToken(); if (m_scanner->currentCommentLiteral() != "")
if (currentTokenValue == Token::RBrace) docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
break; contractKind = parseContractKind();
else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor) name = expectIdentifierToken();
// This can be a function or a state variable of function type (especially if (m_scanner->currentToken() == Token::Is)
// complicated to distinguish fallback function from function type state variable) do
subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable()); {
else if (currentTokenValue == Token::Struct) m_scanner->next();
subNodes.push_back(parseStructDefinition()); baseContracts.push_back(parseInheritanceSpecifier());
else if (currentTokenValue == Token::Enum) }
subNodes.push_back(parseEnumDefinition()); while (m_scanner->currentToken() == Token::Comma);
else if ( expectToken(Token::LBrace);
currentTokenValue == Token::Identifier || while (true)
currentTokenValue == Token::Mapping ||
TokenTraits::isElementaryTypeName(currentTokenValue)
)
{ {
VarDeclParserOptions options; Token currentTokenValue = m_scanner->currentToken();
options.isStateVariable = true; if (currentTokenValue == Token::RBrace)
options.allowInitialValue = true; break;
subNodes.push_back(parseVariableDeclaration(options)); else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor)
expectToken(Token::Semicolon); // This can be a function or a state variable of function type (especially
// complicated to distinguish fallback function from function type state variable)
subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable());
else if (currentTokenValue == Token::Struct)
subNodes.push_back(parseStructDefinition());
else if (currentTokenValue == Token::Enum)
subNodes.push_back(parseEnumDefinition());
else if (
currentTokenValue == Token::Identifier ||
currentTokenValue == Token::Mapping ||
TokenTraits::isElementaryTypeName(currentTokenValue)
)
{
VarDeclParserOptions options;
options.isStateVariable = true;
options.allowInitialValue = true;
subNodes.push_back(parseVariableDeclaration(options));
expectToken(Token::Semicolon);
}
else if (currentTokenValue == Token::Modifier)
subNodes.push_back(parseModifierDefinition());
else if (currentTokenValue == Token::Event)
subNodes.push_back(parseEventDefinition());
else if (currentTokenValue == Token::Using)
subNodes.push_back(parseUsingDirective());
else
fatalParserError(string("Function, variable, struct or modifier declaration expected."));
} }
else if (currentTokenValue == Token::Modifier) }
subNodes.push_back(parseModifierDefinition()); catch (FatalError const&)
else if (currentTokenValue == Token::Event) {
subNodes.push_back(parseEventDefinition()); if (
else if (currentTokenValue == Token::Using) !m_errorReporter.hasErrors() ||
subNodes.push_back(parseUsingDirective()); !m_parserErrorRecovery ||
else m_errorReporter.hasExcessiveErrors()
fatalParserError(string("Function, variable, struct or modifier declaration expected.")); )
BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */
m_inParserRecovery = true;
} }
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
expectToken(Token::RBrace); if (m_inParserRecovery)
expectTokenOrConsumeUntil(Token::RBrace, "ContractDefinition");
else
expectToken(Token::RBrace);
return nodeFactory.createNode<ContractDefinition>( return nodeFactory.createNode<ContractDefinition>(
name, name,
docString, docString,
@ -959,10 +977,26 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString)
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
expectToken(Token::LBrace); expectToken(Token::LBrace);
vector<ASTPointer<Statement>> statements; vector<ASTPointer<Statement>> statements;
while (m_scanner->currentToken() != Token::RBrace) try
statements.push_back(parseStatement()); {
nodeFactory.markEndPosition(); while (m_scanner->currentToken() != Token::RBrace)
expectToken(Token::RBrace); statements.push_back(parseStatement());
nodeFactory.markEndPosition();
}
catch (FatalError const&)
{
if (
!m_errorReporter.hasErrors() ||
!m_parserErrorRecovery ||
m_errorReporter.hasExcessiveErrors()
)
BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */
m_inParserRecovery = true;
}
if (m_parserErrorRecovery)
expectTokenOrConsumeUntil(Token::RBrace, "Block");
else
expectToken(Token::RBrace);
return nodeFactory.createNode<Block>(_docString, statements); return nodeFactory.createNode<Block>(_docString, statements);
} }
@ -970,67 +1004,83 @@ ASTPointer<Statement> Parser::parseStatement()
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTPointer<ASTString> docString; ASTPointer<ASTString> docString;
if (m_scanner->currentCommentLiteral() != "")
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
ASTPointer<Statement> statement; ASTPointer<Statement> statement;
switch (m_scanner->currentToken()) try
{ {
case Token::If: if (m_scanner->currentCommentLiteral() != "")
return parseIfStatement(docString); docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
case Token::While: switch (m_scanner->currentToken())
return parseWhileStatement(docString);
case Token::Do:
return parseDoWhileStatement(docString);
case Token::For:
return parseForStatement(docString);
case Token::LBrace:
return parseBlock(docString);
// starting from here, all statements must be terminated by a semicolon
case Token::Continue:
statement = ASTNodeFactory(*this).createNode<Continue>(docString);
m_scanner->next();
break;
case Token::Break:
statement = ASTNodeFactory(*this).createNode<Break>(docString);
m_scanner->next();
break;
case Token::Return:
{
ASTNodeFactory nodeFactory(*this);
ASTPointer<Expression> expression;
if (m_scanner->next() != Token::Semicolon)
{ {
expression = parseExpression(); case Token::If:
nodeFactory.setEndPositionFromNode(expression); return parseIfStatement(docString);
} case Token::While:
statement = nodeFactory.createNode<Return>(docString, expression); return parseWhileStatement(docString);
break; case Token::Do:
} return parseDoWhileStatement(docString);
case Token::Throw: case Token::For:
{ return parseForStatement(docString);
statement = ASTNodeFactory(*this).createNode<Throw>(docString); case Token::LBrace:
m_scanner->next(); return parseBlock(docString);
break; // starting from here, all statements must be terminated by a semicolon
} case Token::Continue:
case Token::Assembly: statement = ASTNodeFactory(*this).createNode<Continue>(docString);
return parseInlineAssembly(docString);
case Token::Emit:
statement = parseEmitStatement(docString);
break;
case Token::Identifier:
if (m_insideModifier && m_scanner->currentLiteral() == "_")
{
statement = ASTNodeFactory(*this).createNode<PlaceholderStatement>(docString);
m_scanner->next(); m_scanner->next();
} break;
else case Token::Break:
statement = ASTNodeFactory(*this).createNode<Break>(docString);
m_scanner->next();
break;
case Token::Return:
{
ASTNodeFactory nodeFactory(*this);
ASTPointer<Expression> expression;
if (m_scanner->next() != Token::Semicolon)
{
expression = parseExpression();
nodeFactory.setEndPositionFromNode(expression);
}
statement = nodeFactory.createNode<Return>(docString, expression);
break;
}
case Token::Throw:
{
statement = ASTNodeFactory(*this).createNode<Throw>(docString);
m_scanner->next();
break;
}
case Token::Assembly:
return parseInlineAssembly(docString);
case Token::Emit:
statement = parseEmitStatement(docString);
break;
case Token::Identifier:
if (m_insideModifier && m_scanner->currentLiteral() == "_")
{
statement = ASTNodeFactory(*this).createNode<PlaceholderStatement>(docString);
m_scanner->next();
}
else
statement = parseSimpleStatement(docString);
break;
default:
statement = parseSimpleStatement(docString); statement = parseSimpleStatement(docString);
break; break;
default: }
statement = parseSimpleStatement(docString);
break;
} }
expectToken(Token::Semicolon); catch (FatalError const&)
{
if (
!m_errorReporter.hasErrors() ||
!m_parserErrorRecovery ||
m_errorReporter.hasExcessiveErrors()
)
BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */
m_inParserRecovery = true;
}
if (m_inParserRecovery)
expectTokenOrConsumeUntil(Token::Semicolon, "Statement");
else
expectToken(Token::Semicolon);
return statement; return statement;
} }

View File

@ -41,9 +41,10 @@ class Parser: public langutil::ParserBase
public: public:
explicit Parser( explicit Parser(
langutil::ErrorReporter& _errorReporter, langutil::ErrorReporter& _errorReporter,
langutil::EVMVersion _evmVersion langutil::EVMVersion _evmVersion,
bool _errorRecovery = false
): ):
ParserBase(_errorReporter), ParserBase(_errorReporter, _errorRecovery),
m_evmVersion(_evmVersion) m_evmVersion(_evmVersion)
{} {}

View File

@ -384,17 +384,10 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
case Token::Identifier: case Token::Identifier:
case Token::Return: case Token::Return:
case Token::Byte: case Token::Byte:
case Token::Bool:
case Token::Address: case Token::Address:
{ {
YulString literal; YulString literal{currentLiteral()};
if (currentToken() == Token::Return)
literal = "return"_yulstring;
else if (currentToken() == Token::Byte)
literal = "byte"_yulstring;
else if (currentToken() == Token::Address)
literal = "address"_yulstring;
else
literal = YulString{currentLiteral()};
// first search the set of builtins, then the instructions. // first search the set of builtins, then the instructions.
if (m_dialect.builtin(literal)) if (m_dialect.builtin(literal))
{ {
@ -609,12 +602,14 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
else else
ret = std::move(boost::get<FunctionCall>(_initialOp)); ret = std::move(boost::get<FunctionCall>(_initialOp));
expectToken(Token::LParen); expectToken(Token::LParen);
while (currentToken() != Token::RParen) if (currentToken() != Token::RParen)
{ {
ret.arguments.emplace_back(parseExpression()); ret.arguments.emplace_back(parseExpression());
if (currentToken() == Token::RParen) while (currentToken() != Token::RParen)
break; {
expectToken(Token::Comma); expectToken(Token::Comma);
ret.arguments.emplace_back(parseExpression());
}
} }
ret.location.end = endPosition(); ret.location.end = endPosition();
expectToken(Token::RParen); expectToken(Token::RParen);
@ -646,26 +641,25 @@ TypedName Parser::parseTypedName()
YulString Parser::expectAsmIdentifier() YulString Parser::expectAsmIdentifier()
{ {
YulString name = YulString{currentLiteral()}; YulString name{currentLiteral()};
if (m_dialect.flavour == AsmFlavour::Yul) switch (currentToken())
{ {
switch (currentToken()) case Token::Return:
{ case Token::Byte:
case Token::Return: case Token::Address:
case Token::Byte: case Token::Bool:
case Token::Address: case Token::Identifier:
case Token::Bool: break;
advance(); default:
return name; expectToken(Token::Identifier);
default: break;
break;
}
} }
else if (m_dialect.builtin(name))
if (m_dialect.builtin(name))
fatalParserError("Cannot use builtin function name \"" + name.str() + "\" as identifier name."); fatalParserError("Cannot use builtin function name \"" + name.str() + "\" as identifier name.");
else if (instructions().count(name.str())) else if (m_dialect.flavour == AsmFlavour::Loose && instructions().count(name.str()))
fatalParserError("Cannot use instruction names for identifier names."); fatalParserError("Cannot use instruction name \"" + name.str() + "\" as identifier name.");
expectToken(Token::Identifier); advance();
return name; return name;
} }

View File

@ -266,7 +266,7 @@ string AsmPrinter::formatTypedName(TypedName _variable) const
string AsmPrinter::appendTypeName(YulString _type) const string AsmPrinter::appendTypeName(YulString _type) const
{ {
if (m_yul) if (m_yul && !_type.empty())
return ":" + _type.str(); return ":" + _type.str();
return ""; return "";
} }

View File

@ -31,6 +31,7 @@
#include <libyul/backends/evm/EVMCodeTransform.h> #include <libyul/backends/evm/EVMCodeTransform.h>
#include <libyul/backends/evm/EVMDialect.h> #include <libyul/backends/evm/EVMDialect.h>
#include <libyul/backends/evm/EVMObjectCompiler.h> #include <libyul/backends/evm/EVMObjectCompiler.h>
#include <libyul/backends/evm/EVMMetrics.h>
#include <libyul/backends/wasm/WasmDialect.h> #include <libyul/backends/wasm/WasmDialect.h>
#include <libyul/backends/wasm/EWasmObjectCompiler.h> #include <libyul/backends/wasm/EWasmObjectCompiler.h>
#include <libyul/optimiser/Metrics.h> #include <libyul/optimiser/Metrics.h>
@ -93,8 +94,6 @@ void AssemblyStack::optimize()
if (!m_optimiserSettings.runYulOptimiser) if (!m_optimiserSettings.runYulOptimiser)
return; return;
if (m_language != Language::StrictAssembly)
solUnimplemented("Optimizer for both loose assembly and Yul is not yet implemented");
solAssert(m_analysisSuccessful, "Analysis was not successful."); solAssert(m_analysisSuccessful, "Analysis was not successful.");
m_analysisSuccessful = false; m_analysisSuccessful = false;
@ -146,11 +145,14 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
for (auto& subNode: _object.subObjects) for (auto& subNode: _object.subObjects)
if (auto subObject = dynamic_cast<Object*>(subNode.get())) if (auto subObject = dynamic_cast<Object*>(subNode.get()))
optimize(*subObject, false); optimize(*subObject, false);
EVMDialect const& dialect = dynamic_cast<EVMDialect const&>(languageToDialect(m_language, m_evmVersion));
GasMeter meter(dialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); Dialect const& dialect = languageToDialect(m_language, m_evmVersion);
unique_ptr<GasMeter> meter;
if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&dialect))
meter = make_unique<GasMeter>(*evmDialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment);
OptimiserSuite::run( OptimiserSuite::run(
dialect, dialect,
meter, meter.get(),
*_object.code, *_object.code,
*_object.analysisInfo, *_object.analysisInfo,
m_optimiserSettings.optimizeStackAllocation m_optimiserSettings.optimizeStackAllocation

View File

@ -28,6 +28,8 @@ add_library(yul
backends/evm/AbstractAssembly.h backends/evm/AbstractAssembly.h
backends/evm/AsmCodeGen.h backends/evm/AsmCodeGen.h
backends/evm/AsmCodeGen.cpp backends/evm/AsmCodeGen.cpp
backends/evm/ConstantOptimiser.cpp
backends/evm/ConstantOptimiser.h
backends/evm/EVMAssembly.cpp backends/evm/EVMAssembly.cpp
backends/evm/EVMAssembly.h backends/evm/EVMAssembly.h
backends/evm/EVMCodeTransform.cpp backends/evm/EVMCodeTransform.cpp
@ -36,6 +38,8 @@ add_library(yul
backends/evm/EVMDialect.h backends/evm/EVMDialect.h
backends/evm/EVMObjectCompiler.cpp backends/evm/EVMObjectCompiler.cpp
backends/evm/EVMObjectCompiler.h backends/evm/EVMObjectCompiler.h
backends/evm/EVMMetrics.cpp
backends/evm/EVMMetrics.h
backends/evm/NoOutputAssembly.h backends/evm/NoOutputAssembly.h
backends/evm/NoOutputAssembly.cpp backends/evm/NoOutputAssembly.cpp
backends/wasm/EWasmCodeTransform.cpp backends/wasm/EWasmCodeTransform.cpp
@ -58,8 +62,6 @@ add_library(yul
optimiser/BlockHasher.h optimiser/BlockHasher.h
optimiser/CommonSubexpressionEliminator.cpp optimiser/CommonSubexpressionEliminator.cpp
optimiser/CommonSubexpressionEliminator.h optimiser/CommonSubexpressionEliminator.h
optimiser/ConstantOptimiser.cpp
optimiser/ConstantOptimiser.h
optimiser/ControlFlowSimplifier.cpp optimiser/ControlFlowSimplifier.cpp
optimiser/ControlFlowSimplifier.h optimiser/ControlFlowSimplifier.h
optimiser/DataFlowAnalyzer.cpp optimiser/DataFlowAnalyzer.cpp
@ -92,6 +94,10 @@ add_library(yul
optimiser/FunctionHoister.h optimiser/FunctionHoister.h
optimiser/InlinableExpressionFunctionFinder.cpp optimiser/InlinableExpressionFunctionFinder.cpp
optimiser/InlinableExpressionFunctionFinder.h optimiser/InlinableExpressionFunctionFinder.h
optimiser/KnowledgeBase.cpp
optimiser/KnowledgeBase.h
optimiser/LoadResolver.cpp
optimiser/LoadResolver.h
optimiser/MainFunction.cpp optimiser/MainFunction.cpp
optimiser/MainFunction.h optimiser/MainFunction.h
optimiser/Metrics.cpp optimiser/Metrics.cpp
@ -100,6 +106,8 @@ add_library(yul
optimiser/NameCollector.h optimiser/NameCollector.h
optimiser/NameDispenser.cpp optimiser/NameDispenser.cpp
optimiser/NameDispenser.h optimiser/NameDispenser.h
optimiser/NameDisplacer.cpp
optimiser/NameDisplacer.h
optimiser/OptimizerUtilities.cpp optimiser/OptimizerUtilities.cpp
optimiser/OptimizerUtilities.h optimiser/OptimizerUtilities.h
optimiser/RedundantAssignEliminator.cpp optimiser/RedundantAssignEliminator.cpp

View File

@ -43,27 +43,31 @@ map<YulString, int> CompilabilityChecker::run(
solAssert(_dialect.flavour == AsmFlavour::Strict, ""); solAssert(_dialect.flavour == AsmFlavour::Strict, "");
solAssert(dynamic_cast<EVMDialect const*>(&_dialect), ""); if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
NoOutputEVMDialect noOutputDialect(dynamic_cast<EVMDialect const&>(_dialect));
BuiltinContext builtinContext;
yul::AsmAnalysisInfo analysisInfo =
yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _ast);
NoOutputAssembly assembly;
CodeTransform transform(assembly, analysisInfo, _ast, noOutputDialect, builtinContext, _optimizeStackAllocation);
try
{ {
transform(_ast); NoOutputEVMDialect noOutputDialect(*evmDialect);
} BuiltinContext builtinContext;
catch (StackTooDeepError const&)
{
solAssert(!transform.stackErrors().empty(), "Got stack too deep exception that was not stored.");
}
std::map<YulString, int> functions; yul::AsmAnalysisInfo analysisInfo =
for (StackTooDeepError const& error: transform.stackErrors()) yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _ast);
functions[error.functionName] = max(error.depth, functions[error.functionName]);
return functions; NoOutputAssembly assembly;
CodeTransform transform(assembly, analysisInfo, _ast, noOutputDialect, builtinContext, _optimizeStackAllocation);
try
{
transform(_ast);
}
catch (StackTooDeepError const&)
{
solAssert(!transform.stackErrors().empty(), "Got stack too deep exception that was not stored.");
}
std::map<YulString, int> functions;
for (StackTooDeepError const& error: transform.stackErrors())
functions[error.functionName] = max(error.depth, functions[error.functionName]);
return functions;
}
else
return {};
} }

View File

@ -25,6 +25,7 @@
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <vector> #include <vector>
#include <set>
namespace yul namespace yul
{ {
@ -56,6 +57,12 @@ struct BuiltinFunction
bool sideEffectFreeIfNoMSize = false; bool sideEffectFreeIfNoMSize = false;
/// If true, this is the msize instruction. /// If true, this is the msize instruction.
bool isMSize = false; bool isMSize = false;
/// If false, storage of the current contract before and after the function is the same
/// under every circumstance. If the function does not return, this can be false.
bool invalidatesStorage = true;
/// If false, memory before and after the function is the same under every circumstance.
/// If the function does not return, this can be false.
bool invalidatesMemory = true;
/// If true, can only accept literals as arguments and they cannot be moved to variables. /// If true, can only accept literals as arguments and they cannot be moved to variables.
bool literalArguments = false; bool literalArguments = false;
}; };
@ -66,6 +73,11 @@ struct Dialect: boost::noncopyable
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function. /// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; } virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; }
virtual BuiltinFunction const* discardFunction() const { return nullptr; }
virtual BuiltinFunction const* equalityFunction() const { return nullptr; }
virtual std::set<YulString> fixedFunctionNames() const { return {}; }
Dialect(AsmFlavour _flavour): flavour(_flavour) {} Dialect(AsmFlavour _flavour): flavour(_flavour) {}
virtual ~Dialect() = default; virtual ~Dialect() = default;

View File

@ -26,10 +26,57 @@
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
#include <libdevcore/FixedHash.h> #include <libdevcore/FixedHash.h>
#include <boost/algorithm/string.hpp>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>
using namespace std; using namespace std;
using namespace dev; using namespace dev;
using namespace yul; using namespace yul;
using boost::split;
using boost::is_any_of;
string yul::reindent(string const& _code)
{
auto const static countBraces = [](string const& _s) noexcept -> int
{
auto const i = _s.find("//");
auto const e = i == _s.npos ? end(_s) : next(begin(_s), i);
auto const opening = count_if(begin(_s), e, [](auto ch) { return ch == '{' || ch == '('; });
auto const closing = count_if(begin(_s), e, [](auto ch) { return ch == '}' || ch == ')'; });
return opening - closing;
};
vector<string> lines;
split(lines, _code, is_any_of("\n"));
for (string& line: lines)
boost::trim(line);
stringstream out;
int depth = 0;
for (string const& line: lines)
{
int const diff = countBraces(line);
if (diff < 0)
depth += diff;
for (int i = 0; i < depth; ++i)
out << '\t';
out << line << '\n';
if (diff > 0)
depth += diff;
}
return out.str();
}
u256 yul::valueOfNumberLiteral(Literal const& _literal) u256 yul::valueOfNumberLiteral(Literal const& _literal)
{ {
yulAssert(_literal.kind == LiteralKind::Number, "Expected number literal!"); yulAssert(_literal.kind == LiteralKind::Number, "Expected number literal!");

View File

@ -26,6 +26,8 @@
namespace yul namespace yul
{ {
std::string reindent(std::string const& _code);
dev::u256 valueOfNumberLiteral(Literal const& _literal); dev::u256 valueOfNumberLiteral(Literal const& _literal);
dev::u256 valueOfStringLiteral(Literal const& _literal); dev::u256 valueOfStringLiteral(Literal const& _literal);
dev::u256 valueOfBoolLiteral(Literal const& _literal); dev::u256 valueOfBoolLiteral(Literal const& _literal);

View File

@ -62,6 +62,7 @@ public:
/// Retrieve the current height of the stack. This does not have to be zero /// Retrieve the current height of the stack. This does not have to be zero
/// at the beginning. /// at the beginning.
virtual int stackHeight() const = 0; virtual int stackHeight() const = 0;
virtual void setStackHeight(int height) = 0;
/// Append an EVM instruction. /// Append an EVM instruction.
virtual void appendInstruction(dev::eth::Instruction _instruction) = 0; virtual void appendInstruction(dev::eth::Instruction _instruction) = 0;
/// Append a constant. /// Append a constant.

View File

@ -57,6 +57,11 @@ int EthAssemblyAdapter::stackHeight() const
return m_assembly.deposit(); return m_assembly.deposit();
} }
void EthAssemblyAdapter::setStackHeight(int height)
{
m_assembly.setDeposit(height);
}
void EthAssemblyAdapter::appendInstruction(dev::eth::Instruction _instruction) void EthAssemblyAdapter::appendInstruction(dev::eth::Instruction _instruction)
{ {
m_assembly.append(_instruction); m_assembly.append(_instruction);

View File

@ -44,6 +44,7 @@ public:
explicit EthAssemblyAdapter(dev::eth::Assembly& _assembly); explicit EthAssemblyAdapter(dev::eth::Assembly& _assembly);
void setSourceLocation(langutil::SourceLocation const& _location) override; void setSourceLocation(langutil::SourceLocation const& _location) override;
int stackHeight() const override; int stackHeight() const override;
void setStackHeight(int height) override;
void appendInstruction(dev::eth::Instruction _instruction) override; void appendInstruction(dev::eth::Instruction _instruction) override;
void appendConstant(dev::u256 const& _constant) override; void appendConstant(dev::u256 const& _constant) override;
void appendLabel(LabelID _labelId) override; void appendLabel(LabelID _labelId) override;

View File

@ -18,14 +18,12 @@
* Optimisation stage that replaces constants by expressions that compute them. * Optimisation stage that replaces constants by expressions that compute them.
*/ */
#include <libyul/optimiser/ConstantOptimiser.h> #include <libyul/backends/evm/ConstantOptimiser.h>
#include <libyul/optimiser/ASTCopier.h> #include <libyul/optimiser/ASTCopier.h>
#include <libyul/optimiser/Metrics.h> #include <libyul/backends/evm/EVMMetrics.h>
#include <libyul/AsmData.h> #include <libyul/AsmData.h>
#include <libyul/AsmPrinter.h>
#include <libyul/Utilities.h> #include <libyul/Utilities.h>
#include <libyul/AsmParser.h>
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>

View File

@ -45,6 +45,7 @@ public:
/// Retrieve the current height of the stack. This does not have to be zero /// Retrieve the current height of the stack. This does not have to be zero
/// at the beginning. /// at the beginning.
int stackHeight() const override { return m_stackHeight; } int stackHeight() const override { return m_stackHeight; }
void setStackHeight(int height) override { m_stackHeight = height; }
/// Append an EVM instruction. /// Append an EVM instruction.
void appendInstruction(dev::eth::Instruction _instruction) override; void appendInstruction(dev::eth::Instruction _instruction) override;
/// Append a constant. /// Append a constant.

View File

@ -490,19 +490,15 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
} }
m_assembly.setSourceLocation(_function.location); m_assembly.setSourceLocation(_function.location);
int stackHeightBefore = m_assembly.stackHeight(); int const stackHeightBefore = m_assembly.stackHeight();
AbstractAssembly::LabelID afterFunction = m_assembly.newLabelId();
if (m_evm15) if (m_evm15)
{
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore);
m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.parameters.size()); m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.parameters.size());
}
else else
{
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height);
m_assembly.appendLabel(functionEntryID(_function.name, function)); m_assembly.appendLabel(functionEntryID(_function.name, function));
}
m_assembly.setStackHeight(height);
m_stackAdjustment += localStackAdjustment; m_stackAdjustment += localStackAdjustment;
for (auto const& v: _function.returnVariables) for (auto const& v: _function.returnVariables)
@ -592,8 +588,8 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
else else
m_assembly.appendJump(stackHeightBefore - _function.returnVariables.size()); m_assembly.appendJump(stackHeightBefore - _function.returnVariables.size());
m_stackAdjustment -= localStackAdjustment; m_stackAdjustment -= localStackAdjustment;
m_assembly.appendLabel(afterFunction);
checkStackHeight(&_function); checkStackHeight(&_function);
m_assembly.setStackHeight(stackHeightBefore);
} }
void CodeTransform::operator()(ForLoop const& _forLoop) void CodeTransform::operator()(ForLoop const& _forLoop)
@ -727,11 +723,32 @@ void CodeTransform::visitExpression(Expression const& _expression)
void CodeTransform::visitStatements(vector<Statement> const& _statements) void CodeTransform::visitStatements(vector<Statement> const& _statements)
{ {
// Workaround boost bug:
// https://www.boost.org/doc/libs/1_63_0/libs/optional/doc/html/boost_optional/tutorial/gotchas/false_positive_with__wmaybe_uninitialized.html
boost::optional<AbstractAssembly::LabelID> jumpTarget = boost::make_optional(false, AbstractAssembly::LabelID());
for (auto const& statement: _statements) for (auto const& statement: _statements)
{ {
freeUnusedVariables(); freeUnusedVariables();
auto const* functionDefinition = boost::get<FunctionDefinition>(&statement);
if (functionDefinition && !jumpTarget)
{
m_assembly.setSourceLocation(locationOf(statement));
jumpTarget = m_assembly.newLabelId();
m_assembly.appendJumpTo(*jumpTarget, 0);
}
else if (!functionDefinition && jumpTarget)
{
m_assembly.appendLabel(*jumpTarget);
jumpTarget = boost::none;
}
boost::apply_visitor(*this, statement); boost::apply_visitor(*this, statement);
} }
// we may have a leftover jumpTarget
if (jumpTarget)
m_assembly.appendLabel(*jumpTarget);
freeUnusedVariables(); freeUnusedVariables();
} }

View File

@ -54,6 +54,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction); f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction);
f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction); f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction);
f.isMSize = _instruction == dev::eth::Instruction::MSIZE; f.isMSize = _instruction == dev::eth::Instruction::MSIZE;
f.invalidatesStorage = eth::SemanticInformation::invalidatesStorage(_instruction);
f.invalidatesMemory = eth::SemanticInformation::invalidatesMemory(_instruction);
f.literalArguments = false; f.literalArguments = false;
f.instruction = _instruction; f.instruction = _instruction;
f.generateCode = [_instruction]( f.generateCode = [_instruction](
@ -76,6 +78,8 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
bool _movable, bool _movable,
bool _sideEffectFree, bool _sideEffectFree,
bool _sideEffectFreeIfNoMSize, bool _sideEffectFreeIfNoMSize,
bool _invalidatesStorage,
bool _invalidatesMemory,
bool _literalArguments, bool _literalArguments,
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode
) )
@ -90,6 +94,8 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
f.sideEffectFree = _sideEffectFree; f.sideEffectFree = _sideEffectFree;
f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize; f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize;
f.isMSize = false; f.isMSize = false;
f.invalidatesStorage = _invalidatesStorage;
f.invalidatesMemory = _invalidatesMemory;
f.instruction = {}; f.instruction = {};
f.generateCode = std::move(_generateCode); f.generateCode = std::move(_generateCode);
return {name, f}; return {name, f};
@ -110,7 +116,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
if (_objectAccess) if (_objectAccess)
{ {
builtins.emplace(createFunction("datasize", 1, 1, true, true, true, true, []( builtins.emplace(createFunction("datasize", 1, 1, true, true, true, false, false, true, [](
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
@ -131,7 +137,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendDataSize(_context.subIDs.at(dataName)); _assembly.appendDataSize(_context.subIDs.at(dataName));
} }
})); }));
builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, true, []( builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, false, false, true, [](
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
@ -152,7 +158,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendDataOffset(_context.subIDs.at(dataName)); _assembly.appendDataOffset(_context.subIDs.at(dataName));
} }
})); }));
builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, []( builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, true, false, [](
FunctionCall const&, FunctionCall const&,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext&, BuiltinContext&,

View File

@ -68,6 +68,9 @@ struct EVMDialect: public Dialect
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function. /// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
BuiltinFunctionForEVM const* builtin(YulString _name) const override; BuiltinFunctionForEVM const* builtin(YulString _name) const override;
BuiltinFunctionForEVM const* discardFunction() const override { return builtin("pop"_yulstring); }
BuiltinFunctionForEVM const* equalityFunction() const override { return builtin("eq"_yulstring); }
static EVMDialect const& looseAssemblyForEVM(langutil::EVMVersion _version); static EVMDialect const& looseAssemblyForEVM(langutil::EVMVersion _version);
static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version); static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version);
static EVMDialect const& strictAssemblyForEVMObjects(langutil::EVMVersion _version); static EVMDialect const& strictAssemblyForEVMObjects(langutil::EVMVersion _version);

View File

@ -0,0 +1,123 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Module providing metrics for the EVM optimizer.
*/
#include <libyul/backends/evm/EVMMetrics.h>
#include <libyul/AsmData.h>
#include <libyul/Exceptions.h>
#include <libyul/Utilities.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/GasMeter.h>
#include <libdevcore/Visitor.h>
#include <libdevcore/CommonData.h>
using namespace std;
using namespace dev;
using namespace yul;
size_t GasMeter::costs(Expression const& _expression) const
{
return combineCosts(GasMeterVisitor::costs(_expression, m_dialect, m_isCreation));
}
size_t GasMeter::instructionCosts(eth::Instruction _instruction) const
{
return combineCosts(GasMeterVisitor::instructionCosts(_instruction, m_dialect, m_isCreation));
}
size_t GasMeter::combineCosts(std::pair<size_t, size_t> _costs) const
{
return _costs.first * m_runs + _costs.second;
}
pair<size_t, size_t> GasMeterVisitor::costs(
Expression const& _expression,
EVMDialect const& _dialect,
bool _isCreation
)
{
GasMeterVisitor gmv(_dialect, _isCreation);
gmv.visit(_expression);
return {gmv.m_runGas, gmv.m_dataGas};
}
pair<size_t, size_t> GasMeterVisitor::instructionCosts(
dev::eth::Instruction _instruction,
EVMDialect const& _dialect,
bool _isCreation
)
{
GasMeterVisitor gmv(_dialect, _isCreation);
gmv.instructionCostsInternal(_instruction);
return {gmv.m_runGas, gmv.m_dataGas};
}
void GasMeterVisitor::operator()(FunctionCall const& _funCall)
{
ASTWalker::operator()(_funCall);
if (BuiltinFunctionForEVM const* f = m_dialect.builtin(_funCall.functionName.name))
if (f->instruction)
{
instructionCostsInternal(*f->instruction);
return;
}
yulAssert(false, "Functions not implemented.");
}
void GasMeterVisitor::operator()(FunctionalInstruction const& _fun)
{
ASTWalker::operator()(_fun);
instructionCostsInternal(_fun.instruction);
}
void GasMeterVisitor::operator()(Literal const& _lit)
{
m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::PUSH1);
m_dataGas +=
singleByteDataGas() +
size_t(dev::eth::GasMeter::dataGas(dev::toCompactBigEndian(valueOfLiteral(_lit), 1), m_isCreation));
}
void GasMeterVisitor::operator()(Identifier const&)
{
m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::DUP1);
m_dataGas += singleByteDataGas();
}
size_t GasMeterVisitor::singleByteDataGas() const
{
if (m_isCreation)
return dev::eth::GasCosts::txDataNonZeroGas;
else
return dev::eth::GasCosts::createDataGas;
}
void GasMeterVisitor::instructionCostsInternal(dev::eth::Instruction _instruction)
{
if (_instruction == eth::Instruction::EXP)
m_runGas += dev::eth::GasCosts::expGas + dev::eth::GasCosts::expByteGas(m_dialect.evmVersion());
else
m_runGas += dev::eth::GasMeter::runGas(_instruction);
m_dataGas += singleByteDataGas();
}

View File

@ -0,0 +1,101 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Module providing metrics for the optimizer.
*/
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <liblangutil/EVMVersion.h>
#include <libevmasm/Instruction.h>
namespace yul
{
struct EVMDialect;
/**
* Gas meter for expressions only involving literals, identifiers and
* EVM instructions.
*
* Assumes that EXP is not used with exponents larger than a single byte.
* Is not particularly exact for anything apart from arithmetic.
*/
class GasMeter
{
public:
GasMeter(EVMDialect const& _dialect, bool _isCreation, size_t _runs):
m_dialect(_dialect),
m_isCreation{_isCreation},
m_runs(_runs)
{}
/// @returns the full combined costs of deploying and evaluating the expression.
size_t costs(Expression const& _expression) const;
/// @returns the combined costs of deploying and running the instruction, not including
/// the costs for its arguments.
size_t instructionCosts(dev::eth::Instruction _instruction) const;
private:
size_t combineCosts(std::pair<size_t, size_t> _costs) const;
EVMDialect const& m_dialect;
bool m_isCreation = false;
size_t m_runs;
};
class GasMeterVisitor: public ASTWalker
{
public:
static std::pair<size_t, size_t> costs(
Expression const& _expression,
EVMDialect const& _dialect,
bool _isCreation
);
static std::pair<size_t, size_t> instructionCosts(
dev::eth::Instruction _instruction,
EVMDialect const& _dialect,
bool _isCreation = false
);
public:
GasMeterVisitor(EVMDialect const& _dialect, bool _isCreation):
m_dialect(_dialect),
m_isCreation{_isCreation}
{}
void operator()(FunctionCall const& _funCall) override;
void operator()(FunctionalInstruction const& _instr) override;
void operator()(Literal const& _literal) override;
void operator()(Identifier const& _identifier) override;
private:
size_t singleByteDataGas() const;
/// Computes the cost of storing and executing the single instruction (excluding its arguments).
/// For EXP, it assumes that the exponent is at most 255.
/// Does not work particularly exact for anything apart from arithmetic.
void instructionCostsInternal(dev::eth::Instruction _instruction);
EVMDialect const& m_dialect;
bool m_isCreation = false;
size_t m_runGas = 0;
size_t m_dataGas = 0;
};
}

View File

@ -49,6 +49,7 @@ public:
void setSourceLocation(langutil::SourceLocation const&) override {} void setSourceLocation(langutil::SourceLocation const&) override {}
int stackHeight() const override { return m_stackHeight; } int stackHeight() const override { return m_stackHeight; }
void setStackHeight(int height) override { m_stackHeight = height; }
void appendInstruction(dev::eth::Instruction _instruction) override; void appendInstruction(dev::eth::Instruction _instruction) override;
void appendConstant(dev::u256 const& _constant) override; void appendConstant(dev::u256 const& _constant) override;
void appendLabel(LabelID _labelId) override; void appendLabel(LabelID _labelId) override;

View File

@ -115,9 +115,9 @@ wasm::Expression EWasmCodeTransform::operator()(Label const&)
return {}; return {};
} }
wasm::Expression EWasmCodeTransform::operator()(FunctionalInstruction const&) wasm::Expression EWasmCodeTransform::operator()(FunctionalInstruction const& _f)
{ {
yulAssert(false, ""); yulAssert(false, "EVM instruction in ewasm code: " + eth::instructionInfo(_f.instruction).name);
return {}; return {};
} }

View File

@ -34,9 +34,18 @@ string EWasmToText::run(
vector<wasm::FunctionDefinition> const& _functions vector<wasm::FunctionDefinition> const& _functions
) )
{ {
string ret = "(module\n\n"; string ret = "(module\n";
// TODO Add all the interface functions:
// ret += " (import \"ethereum\" \"getBalance\" (func $getBalance (param i32 i32)))\n";
// allocate one 64k page of memory and make it available to the Ethereum client
ret += " (memory $memory (export \"memory\") 1)\n";
// export the main function
ret += " (export \"main\" (func $main))\n";
for (auto const& g: _globals) for (auto const& g: _globals)
ret += " (global $" + g.variableName + " (mut i64) (i64.const 0))\n"; ret += " (global $" + g.variableName + " (mut i64) (i64.const 0))\n";
ret += "\n";
for (auto const& f: _functions) for (auto const& f: _functions)
ret += transform(f) + "\n"; ret += transform(f) + "\n";
return move(ret) + ")\n"; return move(ret) + ")\n";

View File

@ -83,5 +83,7 @@ void WasmDialect::addFunction(string _name, size_t _params, size_t _returns)
f.sideEffectFree = false; f.sideEffectFree = false;
f.sideEffectFreeIfNoMSize = false; f.sideEffectFreeIfNoMSize = false;
f.isMSize = false; f.isMSize = false;
f.invalidatesStorage = true;
f.invalidatesMemory = true;
f.literalArguments = false; f.literalArguments = false;
} }

View File

@ -45,6 +45,10 @@ struct WasmDialect: public Dialect
WasmDialect(); WasmDialect();
BuiltinFunction const* builtin(YulString _name) const override; BuiltinFunction const* builtin(YulString _name) const override;
BuiltinFunction const* discardFunction() const override { return builtin("drop"_yulstring); }
BuiltinFunction const* equalityFunction() const override { return builtin("i64.eq"_yulstring); }
std::set<YulString> fixedFunctionNames() const override { return {"main"_yulstring}; }
static WasmDialect const& instance(); static WasmDialect const& instance();

View File

@ -18,6 +18,8 @@
#include <libyul/AsmData.h> #include <libyul/AsmData.h>
#include <libyul/backends/wasm/WordSizeTransform.h> #include <libyul/backends/wasm/WordSizeTransform.h>
#include <libyul/Utilities.h> #include <libyul/Utilities.h>
#include <libyul/Dialect.h>
#include <libyul/optimiser/NameDisplacer.h>
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
@ -41,6 +43,10 @@ void WordSizeTransform::operator()(FunctionalInstruction& _ins)
void WordSizeTransform::operator()(FunctionCall& _fc) void WordSizeTransform::operator()(FunctionCall& _fc)
{ {
if (BuiltinFunction const* fun = m_inputDialect.builtin(_fc.functionName.name))
if (fun->literalArguments)
return;
rewriteFunctionCallArguments(_fc.arguments); rewriteFunctionCallArguments(_fc.arguments);
} }
@ -48,7 +54,7 @@ void WordSizeTransform::operator()(If& _if)
{ {
_if.condition = make_unique<Expression>(FunctionCall{ _if.condition = make_unique<Expression>(FunctionCall{
locationOf(*_if.condition), locationOf(*_if.condition),
Identifier{locationOf(*_if.condition), "or_bool"_yulstring}, // TODO make sure this is not used Identifier{locationOf(*_if.condition), "or_bool"_yulstring},
expandValueToVector(*_if.condition) expandValueToVector(*_if.condition)
}); });
(*this)(_if.body); (*this)(_if.body);
@ -59,6 +65,18 @@ void WordSizeTransform::operator()(Switch&)
yulAssert(false, "Switch statement not implemented."); yulAssert(false, "Switch statement not implemented.");
} }
void WordSizeTransform::operator()(ForLoop& _for)
{
(*this)(_for.pre);
_for.condition = make_unique<Expression>(FunctionCall{
locationOf(*_for.condition),
Identifier{locationOf(*_for.condition), "or_bool"_yulstring},
expandValueToVector(*_for.condition)
});
(*this)(_for.post);
(*this)(_for.body);
}
void WordSizeTransform::operator()(Block& _block) void WordSizeTransform::operator()(Block& _block)
{ {
iterateReplacing( iterateReplacing(
@ -142,9 +160,11 @@ void WordSizeTransform::operator()(Block& _block)
); );
} }
void WordSizeTransform::run(Block& _ast, NameDispenser& _nameDispenser) void WordSizeTransform::run(Dialect const& _inputDialect, Block& _ast, NameDispenser& _nameDispenser)
{ {
WordSizeTransform{_nameDispenser}(_ast); // Free the name `or_bool`.
NameDisplacer{_nameDispenser, {"or_bool"_yulstring}}(_ast);
WordSizeTransform{_inputDialect, _nameDispenser}(_ast);
} }
void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList) void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList)

Some files were not shown because too many files have changed in this diff Show More