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:
# The default for tags is to not run, so we have to explicitly match a filter.
- build_on_tags: &build_on_tags
filters:
tags:
only: /.*/
# --------------------------------------------------------------------------
# Build Templates
- setup_prerelease_commit_hash: &setup_prerelease_commit_hash
name: Store commit hash and prerelease
command: |
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
- run_build: &run_build
name: Build
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
cd build
[ -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
- run_build_ossfuzz: &run_build_ossfuzz
name: Build_ossfuzz
command: |
mkdir -p build
cd build
/src/LPM/external.protobuf/bin/protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz
cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS
protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz
cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS
make ossfuzz ossfuzz_proto -j4
- run_tests: &run_tests
name: Tests
command: scripts/tests.sh --junit_report test_results
- run_regressions: &run_regressions
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"
scripts/regressions.py -o test_results
- solc_artifact: &solc_artifact
- run_proofs: &run_proofs
name: Correctness proofs for optimization rules
command: scripts/run_proofs.sh
# --------------------------------------------------------------------------
# Artifacts Templates
# 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
destination: solc
- all_artifacts: &all_artifacts
# compiled executable targets
- artifacts_executables: &artifacts_executables
root: build
paths:
- solc/solc
- test/soltest
- test/tools/solfuzzer
- ossfuzz_artifacts: &ossfuzz_artifacts
# compiled OSSFUZZ targets
- artifacts_executables_ossfuzz: &artifacts_executables_ossfuzz
root: build
paths:
- test/tools/ossfuzz/const_opt_ossfuzz
@ -54,9 +78,280 @@ defaults:
- test/tools/ossfuzz/yul_proto_diff_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:
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:
- image: trzeci/emscripten:sdk-tag-1.38.22-64bit
environment:
@ -78,7 +373,7 @@ jobs:
name: Save Boost build
key: *boost-cache-key
paths:
- boost_1_68_0
- boost_1_70_0_install
- store_artifacts:
path: emscripten_build/libsolc/soljson.js
destination: soljson.js
@ -91,7 +386,123 @@ jobs:
- soljson.js
- 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:
- image: circleci/node:10
environment:
@ -101,16 +512,13 @@ jobs:
- attach_workspace:
at: /tmp/workspace
- run:
name: Install external tests deps
name: Test solcjs
command: |
node --version
npm --version
- run:
name: Test solcjs
command: |
test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt)
test_emscripten_external_gnosis:
t_ems_external_gnosis:
docker:
- image: circleci/node:10
environment:
@ -124,7 +532,7 @@ jobs:
command: |
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:
- image: circleci/node:10
environment:
@ -138,7 +546,7 @@ jobs:
command: |
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:
- image: circleci/node:10
environment:
@ -156,370 +564,51 @@ jobs:
command: |
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:
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:
- schedule:
cron: "0 0 * * *"
@ -527,23 +616,19 @@ workflows:
branches:
only:
- develop
jobs:
- build_emscripten: *build_on_tags
- test_emscripten_external_zeppelin:
<<: *build_on_tags
requires:
- build_emscripten
- test_emscripten_external_gnosis:
<<: *build_on_tags
requires:
- build_emscripten
- test_emscripten_external_colony:
<<: *build_on_tags
requires:
- build_emscripten
- build_x86_linux_ossfuzz: *build_on_tags
- test_x86_ossfuzz_regression:
<<: *build_on_tags
requires:
- build_x86_linux_ossfuzz
jobs:
# Emscripten builds and external tests
- b_ems: *workflow_trigger_on_tags
- t_ems_external_zeppelin: *workflow_emscripten
- t_ems_external_gnosis: *workflow_emscripten
- t_ems_external_colony: *workflow_emscripten
# OSSFUZZ builds and (regression) tests
- b_ubu_ossfuzz: *workflow_trigger_on_tags
- t_ubu_ossfuzz: *workflow_ubuntu1904_ossfuzz
# Code Coverage enabled build and tests
- b_ubu_codecov: *workflow_trigger_on_tags
- t_ubu_codecov: *workflow_ubuntu1904_codecov

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:
ccache: true
directories:
- boost_1_68_0
- boost_1_70_0_install
- $HOME/.local
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")
list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR})
@ -10,9 +10,20 @@ include(EthPolicy)
eth_policy()
# 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)
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(SOLC_LINK_STATIC "Link solc 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)
Language Features:
@ -21,7 +47,6 @@ Compiler Features:
* Yul Optimizer: Do not inline recursive functions.
* Yul Optimizer: Do not remove instructions that affect ``msize()`` if ``msize()`` is used.
Bugfixes:
* 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.
@ -61,6 +86,7 @@ Compiler Features:
* Yul: Adds break and continue keywords to for-loop syntax.
* Yul: Support ``.`` as part of identifiers.
* 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:

View File

@ -41,11 +41,11 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
# Additional GCC-specific compiler settings.
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(
COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
if (NOT (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7))
message(FATAL_ERROR "${PROJECT_NAME} requires g++ 4.7 or greater.")
if (NOT (GCC_VERSION VERSION_GREATER 5.0 OR GCC_VERSION VERSION_EQUAL 5.0))
message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.")
endif ()
# 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
# 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)
# 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
@ -41,6 +26,29 @@ set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts)
set(Boost_USE_MULTITHREADED 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)
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)
# Save and clear Z3_FIND_VERSION, since the
# Z3 config module cannot handle version requirements.
set(Z3_FIND_VERSION_ORIG ${Z3_FIND_VERSION})
set(Z3_FIND_VERSION)
# Try to find Z3 using its stock cmake files.
find_package(Z3 QUIET CONFIG)
# Restore Z3_FIND_VERSION for find_package_handle_standard_args.
set(Z3_FIND_VERSION ${Z3_FIND_VERSION_ORIG})
set(Z3_FIND_VERSION_ORIG)
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)
add_library(Z3::Z3 UNKNOWN IMPORTED)
set_property(TARGET Z3::Z3 PROPERTY IMPORTED_LOCATION ${Z3_LIBRARY})
set_property(TARGET Z3::Z3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Z3_INCLUDE_DIR})
if (Z3_FOUND)
set(Z3_VERSION ${Z3_VERSION_STRING})
find_package_handle_standard_args(Z3 CONFIG_MODE)
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()
else()
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
in a context where this is important:
``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
------
@ -595,21 +596,21 @@ of their block is reached.
Conventions in Solidity
-----------------------
In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits,
e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just
treat them as 256-bit numbers and the higher-order bits are only cleaned at the
point where it is necessary, i.e. just shortly before they are written to memory
or before comparisons are performed. This means that if you access such a variable
from within inline assembly, you might have to manually clean the higher order bits
In contrast to EVM assembly, Solidity has types which are narrower than 256 bits,
e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that types can be shorter than 256
bits, and the higher-order bits are cleaned when necessary,
i.e., shortly before they are written to memory or before comparisons are performed.
This means that if you access such a variable
from within inline assembly, you might have to manually clean the higher-order bits
first.
Solidity manages memory in a very simple way: There is a "free memory pointer"
at position ``0x40`` in memory. If you want to allocate memory, just use the memory
starting from where this pointer points at and update it accordingly.
Solidity manages memory in the following way. There is a "free memory pointer"
at position ``0x40`` in memory. If you want to allocate memory, use the memory
starting from where this pointer points at and update it.
There is no guarantee that the memory has not been used before and thus
you cannot assume that its contents are zero bytes.
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 {
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
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
allocation. The 32 bytes after the free memory pointer (i.e., starting at ``0x60``)
are meant to be zero permanently and is used as the initial value for
empty dynamic memory arrays.
This means that the allocatable memory starts at ``0x80``, which is the initial value
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
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.
@ -631,7 +632,7 @@ first slot of the array and followed by the array elements.
.. warning::
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
please do not rely on that.
do not rely on this.
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",
"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": {
"bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -394,6 +395,7 @@
},
"0.4.11": {
"bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -407,6 +409,7 @@
},
"0.4.12": {
"bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -419,6 +422,7 @@
},
"0.4.13": {
"bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -431,6 +435,7 @@
},
"0.4.14": {
"bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -442,6 +447,7 @@
},
"0.4.15": {
"bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -452,6 +458,8 @@
},
"0.4.16": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -463,6 +471,8 @@
},
"0.4.17": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -475,6 +485,8 @@
},
"0.4.18": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -486,6 +498,8 @@
},
"0.4.19": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -514,6 +528,8 @@
},
"0.4.20": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -526,6 +542,8 @@
},
"0.4.21": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -538,6 +556,8 @@
},
"0.4.22": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -550,6 +570,8 @@
},
"0.4.23": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -561,6 +583,8 @@
},
"0.4.24": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -572,6 +596,8 @@
},
"0.4.25": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
@ -581,6 +607,8 @@
},
"0.4.26": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2"
],
"released": "2019-04-29"
@ -647,6 +675,7 @@
},
"0.4.7": {
"bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -661,6 +690,7 @@
},
"0.4.8": {
"bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -675,6 +705,7 @@
},
"0.4.9": {
"bugs": [
"SignedArrayStorageCopy",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -689,6 +720,8 @@
},
"0.5.0": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
@ -698,6 +731,8 @@
},
"0.5.1": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
@ -705,8 +740,14 @@
],
"released": "2018-12-03"
},
"0.5.10": {
"bugs": [],
"released": "2019-06-25"
},
"0.5.2": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
@ -716,6 +757,8 @@
},
"0.5.3": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
@ -725,6 +768,8 @@
},
"0.5.4": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
@ -734,6 +779,8 @@
},
"0.5.5": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
@ -745,6 +792,8 @@
},
"0.5.6": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
@ -755,6 +804,8 @@
},
"0.5.7": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries"
@ -763,12 +814,17 @@
},
"0.5.8": {
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement",
"DynamicConstructorArgumentsClippedABIV2"
],
"released": "2019-04-30"
},
"0.5.9": {
"bugs": [],
"bugs": [
"SignedArrayStorageCopy",
"ABIEncoderV2StorageArrayWithMultiSlotElement"
],
"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.
.. note::
Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve
any payload supplied with the call.
.. warning::
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
@ -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`)
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.
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
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;
// This is the same code as before, just without comments
library Set {
struct Data { mapping(uint => bool) flags; }
struct Data { mapping(uint => bool) flags; }
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
}
contract C {
using Set for Set.Data; // this is the crucial change
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
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
--------------------------------

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.
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
element will be initialized to the default value corresponding to its type. Finally, for dynamically-sized arrays, ``bytes``
and ``string``, the default value is an empty array or string.
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. For the ``enum`` type, the default value is its first member.
Scoping in Solidity follows the widespread scoping rules of C99
(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
======================================================
Solidity uses state-reverting exceptions to handle errors. Such an exception will undo all changes made to the
state in the current call (and all its sub-calls) and also flag 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.
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 flags an error to the caller.
There are two other ways to trigger exceptions: The ``revert`` function can be used to flag an error and
revert the current call. It is possible to provide a string message containing details about the error
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
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``, they return ``false`` as their first return value in case
of an exception instead of "bubbling up".
.. 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
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``.
``assert`` and ``require``
--------------------------
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
(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
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
(or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while
``require``-style exceptions will not consume any gas starting from the Metropolis release.
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.
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)``.
In the above example, ``revert("Not enough Ether provided.");`` will cause the following hexadecimal data be
set as error return data:
The two syntax options are equivalent, it's developer preference which to use.
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::
@ -474,3 +484,7 @@ set as error return data:
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
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 buyer;
enum State { Created, Locked, Inactive }
// The state variable has a default value of the first member, `State.created`
State public state;
// 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 |
+===================================+=======================================================+
| `CMake`_ | Cross-platform build file generator. |
| `CMake`_ (version 3.5+) | Cross-platform build file generator. |
+-----------------------------------+-------------------------------------------------------+
| `Boost`_ (version 1.65+) | C++ libraries. |
+-----------------------------------+-------------------------------------------------------+
@ -201,6 +201,13 @@ The following are dependencies for all builds of Solidity:
.. _CMake: https://cmake.org/download/
.. _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
---------------------
@ -304,10 +311,6 @@ Building Solidity is quite similar on Linux, macOS and other Unices:
cd build
cmake .. && make
.. warning::
BSD builds should work, but are untested by the Solidity team.
or even easier on Linux and macOS, you can run:
.. 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
./scripts/build.sh
.. warning::
BSD builds should work, but are untested by the Solidity team.
And for Windows:
.. 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
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
code that manages the database. In the case of Ethereum, this is always the owning
contract. In this case, the functions ``set`` and ``get`` can be used to modify
code that manages the database. In this example, the contract defines the functions ``set`` and ``get`` that can be used to modify
or retrieve the value of the variable.
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
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::
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.
.. 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
Subcurrency Example
===================
The following contract will implement the simplest form of a
cryptocurrency. It is possible to generate coins out of thin air, but
only the person that created the contract will be able to do that (it is easy
to implement a different issuance scheme).
Furthermore, anyone can send coins to each other without a need for
registering with username and password — all you need is an Ethereum keypair.
The following contract implements the simplest form of a
cryptocurrency. The contract allows only its creator to create new coins (different issuance scheme are possible).
Anyone can send coins to each other without a need for
registering with a username and password, all you need is an Ethereum keypair.
::
pragma solidity >=0.5.0 <0.7.0;
contract Coin {
// The keyword "public" makes those variables
// easily readable from outside.
// The keyword "public" makes variables
// accessible from other contracts
address public minter;
mapping (address => uint) public balances;
// Events allow light clients to react to
// changes efficiently.
// Events allow clients to react to specific
// contract changes you declare
event Sent(address from, address to, uint amount);
// This is the constructor whose code is
// run only when the contract is created.
// Constructor code is only run when the contract
// is created
constructor() public {
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 {
require(msg.sender == minter);
require(amount < 1e60);
balances[receiver] += amount;
}
// Sends an amount of existing coins
// from any caller to an address
function send(address receiver, uint amount) public {
require(amount <= balances[msg.sender], "Insufficient balance.");
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.
The line ``address public minter;`` declares a state variable of type address
that is publicly accessible. The ``address`` type is a 160-bit value
that does not allow any arithmetic operations. It is suitable for
storing addresses of contracts or of keypairs belonging to external
persons. The keyword ``public`` automatically generates a function that
allows you to access the current value of the state variable
from outside of the contract.
Without this keyword, other contracts have no way to access the variable.
The code of the function generated by the compiler is roughly equivalent
The line ``address public minter;`` declares a state variable of type :ref:`address<address>`.
The ``address`` type is a 160-bit value that does not allow any arithmetic operations.
It is suitable for storing addresses of contracts, or a hash of the public half of a keypair belonging to :ref:`external accounts<accounts>`.
The keyword ``public`` automatically generates a function that allows you to access the current value of the state
variable from outside of the contract. Without this keyword, other contracts have no way to access the variable.
The code of the function generated by the compiler is equivalent
to the following (ignore ``external`` and ``view`` for now)::
function minter() external view returns (address) { return minter; }
Of course, adding a function exactly like that will not work
because we would have a
function and a state variable with the same name, but hopefully, you
get the idea - the compiler figures that out for you.
You could add a function like the above yourself, but you would have a function and state variable with the same name.
You do not need to do this, the compiler figures it out for you.
.. index:: mapping
The next line, ``mapping (address => uint) public balances;`` also
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
virtually initialized 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
too far, though, as it is neither possible to obtain a list of all keys of
a mapping, nor a list of all values. So either keep in mind (or
better, keep a list or use a more advanced data type) what you
added to the mapping or use it in a context where this is not needed.
virtually initialised such that every possible key exists from the start and is mapped to a
value whose byte-representation is all zeros. However, it is neither possible to obtain a list of all keys of
a mapping, nor a list of all values. Record what you
added to the mapping, or use it in a context where this is not needed. Or
even better, keep a list, or use a more suitable data type.
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::
function balances(address _account) external view returns (uint) {
return balances[_account];
}
As you see, you can use this function to easily query the balance of a
single account.
You can use this function to query the balance of a single account.
.. index:: event
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
``send``. User interfaces (as well as server applications of course) can
listen for those events being emitted on the blockchain without much
cost. As soon as it is emitted, the listener will also receive the
arguments ``from``, ``to`` and ``amount``, which makes it easy to track
transactions. In order to listen for this event, you would use the following
JavaScript code (which assumes that ``Coin`` is a contract object created via
web3.js or a similar module)::
an :ref:`"event" <events>`, which is emitted in the last line of the function
``send``. Ethereum clients such as web applications can
listen for these events emitted on the blockchain without much
cost. As soon as it is emitted, the listener receives the
arguments ``from``, ``to`` and ``amount``, which makes it possible to track
transactions.
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) {
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
The constructor is a special function which is run during creation of the contract and
cannot be called afterwards. It permanently stores the address of the person creating the
contract: ``msg`` (together with ``tx`` and ``block``) is a special global variable that
contains some properties which allow access to the blockchain. ``msg.sender`` is
The :ref:`constructor<constructor>` is a special function run during the creation of the contract and
cannot be called afterwards. In this case, it permanently stores the address of the person creating the
contract. The ``msg`` variable (together with ``tx`` and ``block``) is a
: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.
Finally, the functions that will actually end up with the contract and can be called
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.
The functions that make up the contract, and that users and contracts can call are ``mint`` and ``send``.
On the other hand, ``send`` can be used by anyone (who already
has some of these coins) to send coins to anyone else. If you do not have
enough coins to send, the ``require`` call will fail and also provide the
user with an appropriate error message string.
The ``mint`` function sends an amount of newly created coins to another address.
The :ref:`require <assert-and-require>` function call defines conditions that reverts all changes if not met.
In this example, ``require(msg.sender == minter);`` ensures that only the creator of the contract can call ``mint``,
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::
If you use
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
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,
but you have to inspect the coin contract address and not the addresses of the
coin owners.
@ -301,6 +296,8 @@ Smart contracts even have limited access to other smart contracts.
.. index:: ! account, address, storage, balance
.. _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.
.. 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::
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.
.. 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:
- 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.
- 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;
contract C {
struct s { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => s)) data;
struct S { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => S)) data;
}
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 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`.
* 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>`__.
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>`__),
even if they are declared public and therefore do affect the ABI. Note:
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.
.. note::
NatSpec currently does NOT apply to public state variables (see
`solidity#3418 <https://github.com/ethereum/solidity/issues/3418>`__),
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
@ -108,7 +112,7 @@ to the end-user as:
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
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"
}

View File

@ -757,20 +757,26 @@ No::
pragma solidity >=0.4.0 <0.7.0;
// Base contracts just to make this compile
contract B {
constructor(uint) public {
}
}
contract C {
constructor(uint, uint) public {
}
}
contract D {
constructor(uint) public {
}
}
contract A is B, C, D {
uint x;
@ -778,12 +784,12 @@ No::
B(param1)
C(param2, param3)
D(param4)
public
{
public {
x = param5;
}
}
contract X is B, C, D {
uint x;
@ -792,10 +798,11 @@ No::
C(param2, param3)
D(param4)
public {
x = param5;
}
x = param5;
}
}
When declaring short functions with a single statement, it is permissible to do it on a single line.
Permissible::
@ -973,27 +980,32 @@ Yes::
pragma solidity >=0.4.0 <0.7.0;
// Owned.sol
contract Owned {
address public owner;
address public owner;
constructor() public {
owner = msg.sender;
}
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
}
// Congress.sol
and in ``Congress.sol``::
pragma solidity >=0.4.0 <0.7.0;
import "./Owned.sol";
contract Congress is Owned, TokenRecipient {
//...
}
@ -1002,32 +1014,34 @@ No::
pragma solidity >=0.4.0 <0.7.0;
// owned.sol
contract owned {
address public owner;
address public owner;
constructor() public {
owner = msg.sender;
}
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner;
}
}
// Congress.sol
and in ``Congress.sol``::
import "./owned.sol";
contract Congress is owned, tokenRecipient {
//...
}
Struct Names
==========================
@ -1104,6 +1118,7 @@ added looks like the one below::
pragma solidity >=0.4.0 <0.7.0;
/// @author The Solidity Team
/// @title A simple storage example
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).
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
--------------------
If an operator is applied to different types, the compiler tries to
implicitly convert one of the operands to the type of the other (the same is
true for assignments). In general, an implicit conversion between value-types
is possible if it
makes sense semantically and no information is lost: ``uint8`` is convertible to
``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256``
(because ``uint256`` cannot hold e.g. ``-1``).
If an operator is applied to different types, the compiler tries to implicitly
convert one of the operands to the type of the other (the same is true for assignments).
This means that operations are always performed in the type of one of the operands.
In general, an implicit conversion between value-types is possible if it makes
sense semantically and no information is lost.
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.
Explicit Conversions
--------------------
If the compiler does not allow implicit conversion but you know what you are
doing, an explicit type conversion is sometimes possible. Note that this may
give you some unexpected behaviour and allows you to bypass some security
If the compiler does not allow implicit conversion but you are confident a conversion will work,
an explicit type conversion is sometimes possible. This may
result in unexpected behaviour and allows you to bypass some security
features of the compiler, so be sure to test that the
result is what you want! Take the following example where you are converting
a negative ``int`` to a ``uint``:
result is what you want and expect!
Take the following example that converts a negative ``int`` to a ``uint``:
::
@ -42,7 +45,7 @@ cut off::
uint32 a = 0x12345678;
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::
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
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
parameter for the getter. If ``_ValueType`` is a value type or a struct,
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.
.. index:: ! uint, ! int, ! integer
.. _integers:
Integers
--------
@ -332,7 +333,7 @@ type and this type is also used in the :ref:`ABI<ABI>`.
Contracts do not support any operators.
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
: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
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::
Solidity has a number literal type for each rational number.
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
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::
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
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.
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
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;
contract Example {
function f() public payable returns (bytes4) {
return this.f.selector;
}
function g() public {
this.f.gas(10).value(800)();
}
function f() public payable returns (bytes4) {
return this.f.selector;
}
function g() public {
this.f.gas(10).value(800)();
}
}
Example that shows how to use internal function types::
pragma solidity >=0.4.16 <0.7.0;
library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
}
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
}
function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
}
contract Pyramid {
using ArrayUtils for *;
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 sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
using ArrayUtils for *;
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 sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
}
Another example that uses external function types::
pragma solidity >=0.4.22 <0.7.0;
contract Oracle {
struct Request {
bytes data;
function(uint) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes memory data, function(uint) external callback) public {
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, uint response) public {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
struct Request {
bytes data;
function(uint) external callback;
}
Request[] private requests;
event NewRequest(uint);
function query(bytes memory data, function(uint) external callback) public {
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
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 {
Oracle constant oracle = Oracle(0x1234567); // known contract
uint exchangeRate;
function buySomething() public {
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(uint response) public {
require(
msg.sender == address(oracle),
"Only oracle can call this."
);
exchangeRate = response;
}
Oracle constant private ORACLE_CONST = Oracle(0x1234567); // known contract
uint private exchangeRate;
function buySomething() public {
ORACLE_CONST.query("USD", this.oracleResponse);
}
function oracleResponse(uint response) public {
require(
msg.sender == address(ORACLE_CONST),
"Only oracle can call this."
);
exchangeRate = response;
}
}
.. 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
===============================

View File

@ -12,6 +12,7 @@ set(sources
FixedHash.h
IndentedWriter.cpp
IndentedWriter.h
InvertibleMap.h
IpfsHash.cpp
IpfsHash.h
JSON.cpp
@ -33,7 +34,6 @@ set(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 SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
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);
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
{

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)
{
checkParameterValid(_parameter);
checkParameterUnknown(_parameter);
m_parameters[move(_parameter)] = move(_value);
return *this;
@ -44,6 +45,7 @@ Whiskers& Whiskers::operator()(string _parameter, string _value)
Whiskers& Whiskers::operator()(string _parameter, bool _value)
{
checkParameterValid(_parameter);
checkParameterUnknown(_parameter);
m_conditions[move(_parameter)] = _value;
return *this;
@ -54,7 +56,11 @@ Whiskers& Whiskers::operator()(
vector<map<string, string>> _values
)
{
checkParameterValid(_listParameter);
checkParameterUnknown(_listParameter);
for (auto const& element: _values)
for (auto const& val: element)
checkParameterValid(val.first);
m_listParameters[move(_listParameter)] = move(_values);
return *this;
}
@ -64,7 +70,17 @@ string Whiskers::render() const
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(
!m_parameters.count(_parameter),
@ -91,7 +107,7 @@ string Whiskers::replace(
)
{
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
{
string tagName(_match[1]);

View File

@ -85,7 +85,8 @@ public:
std::string render() const;
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(
std::string const& _template,
@ -94,6 +95,8 @@ private:
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.
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::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 A.d() * B.d(); }, false},
{{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 {
if (A.d() >= 31)
return B.d();
@ -124,6 +123,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2(
{{Instruction::ADD, {X, 0}}, [=]{ return X; }, false},
{{Instruction::ADD, {0, X}}, [=]{ 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, {0, X}}, [=]{ return u256(0); }, true},
{{Instruction::MUL, {X, 1}}, [=]{ return X; }, false},

View File

@ -73,6 +73,13 @@ char CharStream::rollback(size_t _amount)
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
{
// 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);
}

View File

@ -76,7 +76,13 @@ public:
char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; }
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);
/// 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; }

View File

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

View File

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

View File

@ -47,7 +47,7 @@ Token ParserBase::peekNextToken() const
return m_scanner->peekNextToken();
}
std::string ParserBase::currentLiteral() const
string ParserBase::currentLiteral() const
{
return m_scanner->currentLiteral();
}
@ -57,38 +57,87 @@ Token ParserBase::advance()
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)
{
Token tok = m_scanner->currentToken();
if (tok != _value)
{
auto tokenName = [this](Token _token)
{
if (_token == Token::Identifier)
return string("identifier");
else if (_token == Token::EOS)
return string("end of source");
else if (TokenTraits::isReservedKeyword(_token))
return string("reserved keyword '") + TokenTraits::friendlyName(_token) + "'";
else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
{
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
return string("'") + elemTypeName.toString() + "'";
}
else
return string("'") + TokenTraits::friendlyName(_token) + "'";
};
fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok));
string const expectedToken = ParserBase::tokenName(_value);
if (m_parserErrorRecovery)
parserError("Expected " + expectedToken + " but got " + tokenName(tok));
else
fatalParserError("Expected " + expectedToken + " but got " + tokenName(tok));
// Do not advance so that recovery can sync or make use of the current token.
// This is especially useful if the expected token
// is the only one that is missing and is at the end of a construct.
// "{ ... ; }" is such an example.
// ^
_advance = false;
}
if (_advance)
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()
{
m_recursionDepth++;
if (m_recursionDepth >= 2560)
if (m_recursionDepth >= 1200)
fatalParserError("Maximum recursion depth reached during parsing.");
}
@ -98,12 +147,27 @@ void ParserBase::decreaseRecursionDepth()
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)
{
m_errorReporter.parserError(SourceLocation{position(), endPosition(), source()}, _description);
parserError(SourceLocation{position(), endPosition(), source()}, _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
{
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(); }
@ -62,10 +69,17 @@ protected:
///@{
///@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);
/// 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 peekNextToken() const;
std::string tokenName(Token _token);
std::string currentLiteral() const;
Token advance();
///@}
@ -77,16 +91,26 @@ protected:
/// Creates a @ref ParserError and annotates it with the current position and the
/// given @a _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
/// given @a _description. Throws the FatalError.
void fatalParserError(std::string const& _description);
void fatalParserError(SourceLocation const& _location, std::string const& _description);
std::shared_ptr<Scanner> m_scanner;
/// The reference to the list of errors and warning to add errors/warnings during parsing
ErrorReporter& m_errorReporter;
/// Current recursion depth during parsing.
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();
}
void Scanner::setPosition(size_t _offset)
{
m_char = m_source->setPosition(_offset);
scanToken();
next();
}
void Scanner::supportPeriodInIdentifier(bool _value)
{
m_supportPeriodInIdentifier = _value;

View File

@ -110,6 +110,9 @@ public:
/// @returns the next token and advances input
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

View File

@ -137,10 +137,10 @@ if (NOT (${Z3_FOUND} OR ${CVC4_FOUND}))
endif()
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})
target_link_libraries(solidity PUBLIC Z3::Z3)
target_link_libraries(solidity PUBLIC z3::libz3)
endif()
if (${CVC4_FOUND})

View File

@ -2065,6 +2065,11 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
{
if (funType->kind() == FunctionType::Kind::Creation)
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
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)
{
Json::Value externalReferences(Json::arrayValue);
for (auto const& it : _node.annotation().externalReferences)
{
for (auto const& it: _node.annotation().externalReferences)
if (it.first)
{
Json::Value tuple(Json::objectValue);
tuple[it.first->name.str()] = inlineAssemblyIdentifierToJson(it);
externalReferences.append(tuple);
}
}
setJsonNode(_node, "InlineAssembly", {
make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))),
make_pair("externalReferences", std::move(externalReferences))

View File

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

View File

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

View File

@ -154,7 +154,7 @@ private:
EncodingOptions const& _options
);
/// 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(
ArrayType const& _givenType,
ArrayType const& _targetType,

View File

@ -1055,28 +1055,27 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b
switch (location)
{
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:
if (!_arrayType.isByteArray())
{
if (location == DataLocation::CallData)
{
if (_arrayType.baseType()->isDynamicallyEncoded())
m_context << u256(0x20);
else
m_context << _arrayType.baseType()->calldataEncodedSize();
}
if (_arrayType.baseType()->isDynamicallyEncoded())
m_context << u256(0x20);
else
m_context << u256(_arrayType.memoryHeadSize());
m_context << _arrayType.baseType()->calldataEncodedSize();
m_context << Instruction::MUL;
}
// stack: <base_ref> <index * size>
if (location == DataLocation::Memory && _arrayType.isDynamicallySized())
m_context << u256(32) << Instruction::ADD;
if (_keepReference)
m_context << Instruction::DUP2;
m_context << Instruction::ADD;
break;
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
{
solAssert(_byteSize < 32, "");

View File

@ -104,6 +104,10 @@ public:
/// Stack post (storage): [reference] storage_slot byte_offset
/// Stack post: [reference] memory/calldata_offset
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:
/// 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/backends/evm/AsmCodeGen.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/backends/evm/EVMMetrics.h>
#include <libyul/optimiser/Suite.h>
#include <libyul/optimiser/Metrics.h>
#include <libyul/YulString.h>
#include <liblangutil/ErrorReporter.h>
@ -422,9 +422,10 @@ void CompilerContext::appendInlineAssembly(
if (_optimiserSettings.runYulOptimiser && _localVariables.empty())
{
bool const isCreation = m_runtimeContext != nullptr;
yul::GasMeter meter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment);
yul::OptimiserSuite::run(
dialect,
yul::GasMeter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment),
&meter,
*parserResult,
analysisInfo,
_optimiserSettings.optimizeStackAllocation,

View File

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

View File

@ -54,7 +54,13 @@ class StackHeightChecker
public:
explicit StackHeightChecker(CompilerContext const& _context):
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:
CompilerContext const& m_context;
unsigned stackHeight;
@ -893,9 +899,9 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar
// Local variable slots are reserved when their declaration is visited,
// and freed in the end of their scope.
for (auto _decl: _variableDeclarationStatement.declarations())
if (_decl)
appendStackVariableInitialisation(*_decl);
for (auto decl: _variableDeclarationStatement.declarations())
if (decl)
appendStackVariableInitialisation(*decl);
StackHeightChecker checker(m_context);
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());
break;
case DataLocation::CallData:
if (arrayType.baseType()->isDynamicallyEncoded())
{
// 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."
);
}
ArrayUtils(m_context).accessCallDataArrayElement(arrayType);
break;
}
}

View File

@ -224,34 +224,45 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
solAssert(_numBits < 256, "");
string functionName = "shift_left_" + to_string(_numBits);
if (m_evmVersion.hasBitwiseShifting())
{
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := shl(<numBits>, value)
}
)")
("functionName", functionName)
("numBits", to_string(_numBits))
.render();
});
}
else
{
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := mul(value, <multiplier>)
}
)")
("functionName", functionName)
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue :=
<?hasShifts>
shl(<numBits>, value)
<!hasShifts>
mul(value, <multiplier>)
</hasShifts>
}
)")
("functionName", functionName)
("numBits", to_string(_numBits))
("hasShifts", m_evmVersion.hasBitwiseShifting())
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
string YulUtilFunctions::shiftLeftFunctionDynamic()
{
string functionName = "shift_left_dynamic";
return m_functionCollector->createFunction(functionName, [&]() {
return
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)
@ -261,7 +272,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
// Note that if this is extended with signed shifts,
// 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
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)
{
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 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_uint_" + to_string(_bits);
string functionName = "checked_add_" + _type.identifier();
// 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
Whiskers(R"(
function <functionName>(x, y) -> sum {
<?shortType>
let mask := <mask>
sum := add(and(x, mask), and(y, mask))
if and(sum, not(mask)) { revert(0, 0) }
<!shortType>
sum := add(x, y)
if lt(sum, x) { revert(0, 0) }
</shortType>
<?signed>
// overflow, if x >= 0 and y > (maxValue - x)
if and(iszero(slt(x, 0)), sgt(y, sub(<maxValue>, x))) { revert(0, 0) }
// underflow, if x < 0 and y < (minValue - x)
if and(slt(x, 0), slt(y, sub(<minValue>, x))) { revert(0, 0) }
<!signed>
// overflow, if x > (maxValue - y)
if gt(x, sub(<maxValue>, y)) { revert(0, 0) }
</signed>
sum := add(x, y)
}
)")
("shortType", _bits < 256)
("functionName", functionName)
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
.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_uint_" + to_string(_bits);
string functionName = "checked_mul_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return
// - The current overflow check *before* the multiplication could
// 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.
// Multiplication by zero could be treated separately and directly return zero.
Whiskers(R"(
function <functionName>(x, y) -> product {
if and(iszero(iszero(x)), lt(div(<mask>, x), y)) { revert(0, 0) }
<?shortType>
product := mulmod(x, y, <powerOfTwo>)
<!shortType>
product := mul(x, y)
</shortType>
<?signed>
// overflow, if x > 0, y > 0 and x > (maxValue / y)
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { revert(0, 0) }
// underflow, if x > 0, y < 0 and y < (minValue / x)
if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(<minValue>, x))) { revert(0, 0) }
// 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)
("powerOfTwo", toCompactHexWithPrefix(u256(1) << _bits))
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
.render();
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{
unsigned bits = _type.numBits();
solAssert(0 < bits && bits <= 256 && bits % 8 == 0, "");
string functionName = "checked_div_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return
@ -385,7 +450,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
function <functionName>(x, y) -> r {
if iszero(y) { revert(0, 0) }
<?signed>
// x / -1 == x
// overflow for minVal / -1
if and(
eq(x, <minVal>),
eq(y, sub(0, 1))
@ -394,25 +459,52 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
r := <?signed>s</signed>div(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("minVal", (0 - (u256(1) << (bits - 1))).str())
.render();
("functionName", functionName)
("signed", _type.isSigned())
("minVal", toCompactHexWithPrefix(u256(_type.minValue())))
.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
Whiskers(R"(
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)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
.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)
{
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)
{
solAssert(!_type.isByteArray(), "");
@ -531,18 +767,29 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
}
)");
templ("functionName", functionName);
if (_type.location() == DataLocation::Memory)
switch (_type.location())
{
case DataLocation::Memory:
templ("advance", "0x20");
else if (_type.location() == DataLocation::Storage)
templ("advance", "1");
else if (_type.location() == DataLocation::CallData)
templ("advance", toCompactHexWithPrefix(
break;
case DataLocation::Storage:
{
u256 size = _type.baseType()->storageSize();
solAssert(size >= 1, "");
templ("advance", toCompactHexWithPrefix(size));
break;
}
case DataLocation::CallData:
{
u256 size =
_type.baseType()->isDynamicallyEncoded() ?
32 :
_type.baseType()->calldataEncodedSize()
));
else
solAssert(false, "");
_type.baseType()->calldataEncodedSize();
solAssert(size >= 32 && size % 32 == 0, "");
templ("advance", toCompactHexWithPrefix(size));
break;
}
}
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)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
@ -631,7 +961,7 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs
)")
("functionName", functionName)
("shr", shiftRightFunction(_offset * 8))
("cleanupStorage", cleanupFromStorageFunction(_type, false))
("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes))
.render();
});
}

View File

@ -74,31 +74,64 @@ public:
std::string leftAlignFunction(Type const& _type);
std::string shiftLeftFunction(size_t _numBits);
std::string shiftLeftFunctionDynamic();
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
/// byte) by the _numBytes least significant bytes of `toInsert`.
/// signature: (value, toInsert) -> result
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
/// of 32 or the input if it is a multiple of 32.
/// signature: (value) -> result
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.
/// Checks for division by zero and the special case of
/// signed division of the smallest number by -1.
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.
/// 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);
/// @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
/// to store an array in memory given its length (internally encoded, not ABI encoded).
/// 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
/// for the data position of an array which is stored in that slot / memory area / calldata area.
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.
/// 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);
/// @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
/// single variable.
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
/// retrieved already.
@ -128,6 +168,13 @@ public:
/// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable.
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.
/// 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 <libyul/AssemblyStack.h>
#include <libyul/Utilities.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/Whiskers.h>
@ -45,8 +46,7 @@ using namespace dev::solidity;
pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
{
// TODO Would be nice to pretty-print this while retaining comments.
string ir = generate(_contract);
string const ir = yul::reindent(generate(_contract));
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
if (!asmStack.parseAndAnalyze("", ir))
@ -54,7 +54,7 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
string errorMessage;
for (auto const& error: asmStack.errors())
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();

View File

@ -56,6 +56,35 @@ struct CopyTranslate: public yul::ASTCopier
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
{
// 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 varDecl = dynamic_cast<VariableDeclaration const*>(reference.declaration);
solUnimplementedAssert(varDecl, "");
solUnimplementedAssert(
solAssert(
reference.isOffset == false && reference.isSlot == false,
""
"Should not be called for offset/slot"
);
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() <= 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(
functionType->kind() == FunctionType::Kind::Assert,
arguments.size() > 1 ? arguments[1]->annotation().type : nullptr
messageArgumentType
);
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 << ")\n";
@ -702,7 +733,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
}
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:
{
@ -761,7 +820,45 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
));
}
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)
solUnimplementedAssert(false, "");
else if (baseType.category() == Type::Category::TypeType)
@ -1097,18 +1194,28 @@ string IRGeneratorForStatements::binaryOperation(
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
{
string fun;
// TODO: Only division is implemented for signed integers for now.
if (!type->isSigned())
// TODO: Implement all operations for signed and unsigned types.
switch (_operator)
{
if (_operator == Token::Add)
fun = m_utils.overflowCheckedUIntAddFunction(type->numBits());
else if (_operator == Token::Sub)
fun = m_utils.overflowCheckedUIntSubFunction();
else if (_operator == Token::Mul)
fun = m_utils.overflowCheckedUIntMulFunction(type->numBits());
case Token::Add:
fun = m_utils.overflowCheckedIntAddFunction(*type);
break;
case Token::Sub:
fun = m_utils.overflowCheckedIntSubFunction(*type);
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(), "");
return fun + "(" + _left + ", " + _right + ")\n";
}

View File

@ -55,25 +55,36 @@ IRStorageItem::IRStorageItem(
IRGenerationContext& _context,
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(
IRGenerationContext& _context,
string _slot,
unsigned _offset,
boost::variant<string, unsigned> _offset,
Type const& _type
):
IRLValue(_context, &_type),
m_slot(move(_slot)),
m_offset(_offset)
m_offset(std::move(_offset))
{
solAssert(!m_offset.empty(), "");
solAssert(!m_slot.empty(), "");
}
string IRStorageItem::retrieveValue() const
@ -81,42 +92,77 @@ string IRStorageItem::retrieveValue() const
if (!m_type->isValueType())
return m_slot;
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
{
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.");
return Whiskers("sstore(<slot>, <update>(sload(<slot>), <prepare>(<value>)))\n")
("slot", m_slot)
("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset))
("prepare", m_context.utils().prepareStoreFunction(*m_type))
("value", _value)
.render();
}
else
{
solAssert(
_sourceType.category() == m_type->category(),
"Wrong type conversation for assignment."
);
if (m_type->category() == Type::Category::Array)
solUnimplementedAssert(false, "");
else if (m_type->category() == Type::Category::Struct)
solUnimplementedAssert(false, "");
else
solAssert(false, "Invalid non-value type for assignment.");
}
boost::optional<unsigned> offset;
if (m_offset.type() == typeid(unsigned))
offset = get<unsigned>(m_offset);
return
m_context.utils().updateStorageValueFunction(*m_type, offset) +
"(" +
m_slot +
(m_offset.type() == typeid(string) ?
(", " + get<string>(m_offset)) :
""
) +
", " +
_value +
")\n";
}
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
#include <libdevcore/Common.h>
#include <string>
#include <ostream>
#include <boost/variant.hpp>
namespace dev
{
@ -31,6 +34,7 @@ namespace solidity
class VariableDeclaration;
class IRGenerationContext;
class Type;
class ArrayType;
/**
* Abstract class used to retrieve, delete and store data in LValues.
@ -83,7 +87,7 @@ public:
IRStorageItem(
IRGenerationContext& _context,
std::string _slot,
unsigned _offset,
boost::variant<std::string, unsigned> _offset,
Type const& _type
);
std::string retrieveValue() const override;
@ -91,10 +95,37 @@ public:
std::string setToZero() const override;
private:
std::string m_slot;
unsigned m_offset;
IRStorageItem(
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));
}
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)
arguments.push_back(toCVC4Expr(arg));
string const& n = _expr.name;
// 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())
try
{
if (n == "true")
return m_context.mkConst(true);
else if (n == "false")
return m_context.mkConst(false);
else
// We assume it is an integer...
return m_context.mkConst(CVC4::Rational(n));
string const& n = _expr.name;
// 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")
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, "");
return arguments[0];
}
CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort)

View File

@ -23,15 +23,15 @@ using namespace std;
using namespace dev;
using namespace dev::solidity::smt;
EncodingContext::EncodingContext(SolverInterface& _solver):
m_solver(_solver),
m_thisAddress(make_unique<SymbolicAddressVariable>("this", m_solver))
EncodingContext::EncodingContext(std::shared_ptr<SolverInterface> _solver):
m_thisAddress(make_unique<SymbolicAddressVariable>("this", *_solver)),
m_solver(_solver)
{
auto sort = make_shared<ArraySort>(
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()
@ -41,6 +41,7 @@ void EncodingContext::reset()
m_globalContext.clear();
m_thisAddress->increaseIndex();
m_balances->increaseIndex();
m_assertions.clear();
}
/// Variables.
@ -55,7 +56,7 @@ bool EncodingContext::createVariable(solidity::VariableDeclaration const& _varDe
{
solAssert(!knownVariable(_varDecl), "");
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);
return result.first;
}
@ -105,7 +106,7 @@ void EncodingContext::setZeroValue(solidity::VariableDeclaration const& _decl)
void EncodingContext::setZeroValue(SymbolicVariable& _variable)
{
setSymbolicZeroValue(_variable, m_solver);
setSymbolicZeroValue(_variable, *m_solver);
}
void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl)
@ -116,7 +117,7 @@ void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl
void EncodingContext::setUnknownValue(SymbolicVariable& _variable)
{
setSymbolicUnknownValue(_variable, m_solver);
setSymbolicUnknownValue(_variable, *m_solver);
}
/// Expressions
@ -143,7 +144,7 @@ bool EncodingContext::createExpression(solidity::Expression const& _e, shared_pt
}
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);
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)
{
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);
setUnknownValue(*result.second);
return result.first;
@ -207,9 +208,40 @@ void EncodingContext::transfer(Expression _from, Expression _to, Expression _val
m_balances->valueAtIndex(indexBefore),
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)
{
auto newBalances = Expression::store(
@ -218,5 +250,5 @@ void EncodingContext::addBalance(Expression _address, Expression _value)
balance(_address) + move(_value)
);
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
{
public:
EncodingContext(SolverInterface& _solver);
EncodingContext(std::shared_ptr<SolverInterface> _solver);
/// Resets the entire context.
void reset();
/// Methods related to variables.
/// Variables.
//@{
/// @returns the symbolic representation of a program variable.
std::shared_ptr<SymbolicVariable> variable(solidity::VariableDeclaration const& _varDecl);
@ -74,7 +74,7 @@ public:
void setUnknownValue(SymbolicVariable& _variable);
//@}
/// Methods related to expressions.
/// Expressions.
////@{
/// @returns the symbolic representation of an AST node expression.
std::shared_ptr<SymbolicVariable> expression(solidity::Expression const& _e);
@ -88,12 +88,13 @@ public:
bool knownExpression(solidity::Expression const& _e) const;
//@}
/// Methods related to global variables and functions.
/// Global variables and functions.
//@{
/// Global variables and functions.
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; }
/// Defines a new global variable or function
/// and @returns true if type was abstracted.
bool createGlobalSymbol(std::string const& _name, solidity::Expression const& _expr);
@ -101,7 +102,7 @@ public:
bool knownGlobalSymbol(std::string const& _var) const;
//@}
/// Blockchain related methods.
/// Blockchain.
//@{
/// Value of `this` address.
Expression thisAddress();
@ -113,12 +114,22 @@ public:
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:
/// Adds _value to _account's balance.
void addBalance(Expression _account, Expression _value);
SolverInterface& m_solver;
/// Symbolic expressions.
//{@
/// Symbolic variables.
std::unordered_map<solidity::VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables;
@ -134,6 +145,16 @@ private:
/// Symbolic 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;
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_errorReporter(m_smtErrors),
m_context(*m_interface)
m_context(m_interface)
{
#if defined (HAVE_Z3) || defined (HAVE_CVC4)
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)
{
if (!_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
return;
m_scanner = _scanner;
if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
_source.accept(*this);
_source.accept(*this);
solAssert(m_interface->solvers() > 0, "");
// 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/SolverInterface.h>
#include <libsolidity/formal/SMTPortfolio.h>
#include <libsolidity/formal/SymbolicVariables.h>
#include <libsolidity/formal/VariableUsage.h>
@ -270,7 +270,7 @@ private:
/// @returns the VariableDeclaration referenced by an Identifier or nullptr.
VariableDeclaration const* identifierToVariable(Expression const& _expr);
std::unique_ptr<smt::SolverInterface> m_interface;
std::shared_ptr<smt::SolverInterface> m_interface;
smt::VariableUsage m_variableUsage;
bool m_loopExecutionHappened = false;
bool m_arrayAssignmentHappened = false;

View File

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

View File

@ -133,6 +133,7 @@ public:
{"not", 1},
{"and", 2},
{"or", 2},
{"implies", 2},
{"=", 2},
{"<", 2},
{"<=", 2},
@ -160,7 +161,12 @@ public:
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.

View File

@ -116,61 +116,77 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
for (auto const& arg: _expr.arguments)
arguments.push_back(toZ3Expr(arg));
string const& n = _expr.name;
if (m_functions.count(n))
return m_functions.at(n)(arguments);
else if (m_constants.count(n))
try
{
solAssert(arguments.empty(), "");
return m_constants.at(n);
string const& n = _expr.name;
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")
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(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 == "=")
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, "");
return arguments[0];
}
z3::sort Z3Interface::z3Sort(Sort const& _sort)

View File

@ -217,7 +217,7 @@ bool CompilerStack::parse()
string const& path = sourcesToParse[i];
Source& source = m_sources[path];
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)
solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error.");
else

View File

@ -132,6 +132,14 @@ public:
/// Must be set before parsing.
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.
/// When called without an argument it will revert to the default version.
/// Must be set before parsing.
@ -386,6 +394,7 @@ private:
langutil::ErrorList m_errorList;
langutil::ErrorReporter m_errorReporter;
bool m_metadataLiteralSources = false;
bool m_parserErrorRecovery = false;
State m_stackState = Empty;
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)
{
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");
}
@ -576,6 +576,13 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
if (auto result = checkSettingsKeys(settings))
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["evmVersion"].isString())
@ -675,6 +682,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses)
compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second);
compilerStack.setEVMVersion(_inputsAndSettings.evmVersion);
compilerStack.setParserErrorRecovery(_inputsAndSettings.parserErrorRecovery);
compilerStack.setRemappings(_inputsAndSettings.remappings);
compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings));
compilerStack.setLibraries(_inputsAndSettings.libraries);
@ -769,6 +777,15 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
"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 (...)
{
errors.append(formatError(

View File

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

View File

@ -258,57 +258,75 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
ASTPointer<ASTString> name = nullptr;
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;
if (m_scanner->currentToken() == Token::Is)
do
{
m_scanner->next();
baseContracts.push_back(parseInheritanceSpecifier());
}
while (m_scanner->currentToken() == Token::Comma);
vector<ASTPointer<ASTNode>> subNodes;
expectToken(Token::LBrace);
while (true)
ContractDefinition::ContractKind contractKind = ContractDefinition::ContractKind::Contract;
try
{
Token currentTokenValue = m_scanner->currentToken();
if (currentTokenValue == Token::RBrace)
break;
else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor)
// 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)
)
if (m_scanner->currentCommentLiteral() != "")
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
contractKind = parseContractKind();
name = expectIdentifierToken();
if (m_scanner->currentToken() == Token::Is)
do
{
m_scanner->next();
baseContracts.push_back(parseInheritanceSpecifier());
}
while (m_scanner->currentToken() == Token::Comma);
expectToken(Token::LBrace);
while (true)
{
VarDeclParserOptions options;
options.isStateVariable = true;
options.allowInitialValue = true;
subNodes.push_back(parseVariableDeclaration(options));
expectToken(Token::Semicolon);
Token currentTokenValue = m_scanner->currentToken();
if (currentTokenValue == Token::RBrace)
break;
else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor)
// 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());
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."));
}
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;
}
nodeFactory.markEndPosition();
expectToken(Token::RBrace);
if (m_inParserRecovery)
expectTokenOrConsumeUntil(Token::RBrace, "ContractDefinition");
else
expectToken(Token::RBrace);
return nodeFactory.createNode<ContractDefinition>(
name,
docString,
@ -959,10 +977,26 @@ ASTPointer<Block> Parser::parseBlock(ASTPointer<ASTString> const& _docString)
ASTNodeFactory nodeFactory(*this);
expectToken(Token::LBrace);
vector<ASTPointer<Statement>> statements;
while (m_scanner->currentToken() != Token::RBrace)
statements.push_back(parseStatement());
nodeFactory.markEndPosition();
expectToken(Token::RBrace);
try
{
while (m_scanner->currentToken() != 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);
}
@ -970,67 +1004,83 @@ ASTPointer<Statement> Parser::parseStatement()
{
RecursionGuard recursionGuard(*this);
ASTPointer<ASTString> docString;
if (m_scanner->currentCommentLiteral() != "")
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
ASTPointer<Statement> statement;
switch (m_scanner->currentToken())
try
{
case Token::If:
return parseIfStatement(docString);
case Token::While:
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)
if (m_scanner->currentCommentLiteral() != "")
docString = make_shared<ASTString>(m_scanner->currentCommentLiteral());
switch (m_scanner->currentToken())
{
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);
case Token::If:
return parseIfStatement(docString);
case Token::While:
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();
}
else
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();
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);
break;
default:
statement = parseSimpleStatement(docString);
break;
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;
}

View File

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

View File

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

View File

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

View File

@ -31,6 +31,7 @@
#include <libyul/backends/evm/EVMCodeTransform.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/backends/evm/EVMObjectCompiler.h>
#include <libyul/backends/evm/EVMMetrics.h>
#include <libyul/backends/wasm/WasmDialect.h>
#include <libyul/backends/wasm/EWasmObjectCompiler.h>
#include <libyul/optimiser/Metrics.h>
@ -93,8 +94,6 @@ void AssemblyStack::optimize()
if (!m_optimiserSettings.runYulOptimiser)
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.");
m_analysisSuccessful = false;
@ -146,11 +145,14 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
for (auto& subNode: _object.subObjects)
if (auto subObject = dynamic_cast<Object*>(subNode.get()))
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(
dialect,
meter,
meter.get(),
*_object.code,
*_object.analysisInfo,
m_optimiserSettings.optimizeStackAllocation

View File

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

View File

@ -43,27 +43,31 @@ map<YulString, int> CompilabilityChecker::run(
solAssert(_dialect.flavour == AsmFlavour::Strict, "");
solAssert(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
if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
{
transform(_ast);
}
catch (StackTooDeepError const&)
{
solAssert(!transform.stackErrors().empty(), "Got stack too deep exception that was not stored.");
}
NoOutputEVMDialect noOutputDialect(*evmDialect);
BuiltinContext builtinContext;
std::map<YulString, int> functions;
for (StackTooDeepError const& error: transform.stackErrors())
functions[error.functionName] = max(error.depth, functions[error.functionName]);
yul::AsmAnalysisInfo analysisInfo =
yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _ast);
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 <vector>
#include <set>
namespace yul
{
@ -56,6 +57,12 @@ struct BuiltinFunction
bool sideEffectFreeIfNoMSize = false;
/// If true, this is the msize instruction.
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.
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.
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) {}
virtual ~Dialect() = default;

View File

@ -26,10 +26,57 @@
#include <libdevcore/CommonData.h>
#include <libdevcore/FixedHash.h>
#include <boost/algorithm/string.hpp>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>
using namespace std;
using namespace dev;
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)
{
yulAssert(_literal.kind == LiteralKind::Number, "Expected number literal!");

View File

@ -26,6 +26,8 @@
namespace yul
{
std::string reindent(std::string const& _code);
dev::u256 valueOfNumberLiteral(Literal const& _literal);
dev::u256 valueOfStringLiteral(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
/// at the beginning.
virtual int stackHeight() const = 0;
virtual void setStackHeight(int height) = 0;
/// Append an EVM instruction.
virtual void appendInstruction(dev::eth::Instruction _instruction) = 0;
/// Append a constant.

View File

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

View File

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

View File

@ -18,14 +18,12 @@
* 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/Metrics.h>
#include <libyul/backends/evm/EVMMetrics.h>
#include <libyul/AsmData.h>
#include <libyul/AsmPrinter.h>
#include <libyul/Utilities.h>
#include <libyul/AsmParser.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
/// at the beginning.
int stackHeight() const override { return m_stackHeight; }
void setStackHeight(int height) override { m_stackHeight = height; }
/// Append an EVM instruction.
void appendInstruction(dev::eth::Instruction _instruction) override;
/// Append a constant.

View File

@ -490,19 +490,15 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
}
m_assembly.setSourceLocation(_function.location);
int stackHeightBefore = m_assembly.stackHeight();
AbstractAssembly::LabelID afterFunction = m_assembly.newLabelId();
int const stackHeightBefore = m_assembly.stackHeight();
if (m_evm15)
{
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore);
m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.parameters.size());
}
else
{
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height);
m_assembly.appendLabel(functionEntryID(_function.name, function));
}
m_assembly.setStackHeight(height);
m_stackAdjustment += localStackAdjustment;
for (auto const& v: _function.returnVariables)
@ -592,8 +588,8 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
else
m_assembly.appendJump(stackHeightBefore - _function.returnVariables.size());
m_stackAdjustment -= localStackAdjustment;
m_assembly.appendLabel(afterFunction);
checkStackHeight(&_function);
m_assembly.setStackHeight(stackHeightBefore);
}
void CodeTransform::operator()(ForLoop const& _forLoop)
@ -727,11 +723,32 @@ void CodeTransform::visitExpression(Expression const& _expression)
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)
{
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);
}
// we may have a leftover jumpTarget
if (jumpTarget)
m_assembly.appendLabel(*jumpTarget);
freeUnusedVariables();
}

View File

@ -54,6 +54,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction);
f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction);
f.isMSize = _instruction == dev::eth::Instruction::MSIZE;
f.invalidatesStorage = eth::SemanticInformation::invalidatesStorage(_instruction);
f.invalidatesMemory = eth::SemanticInformation::invalidatesMemory(_instruction);
f.literalArguments = false;
f.instruction = _instruction;
f.generateCode = [_instruction](
@ -76,6 +78,8 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
bool _movable,
bool _sideEffectFree,
bool _sideEffectFreeIfNoMSize,
bool _invalidatesStorage,
bool _invalidatesMemory,
bool _literalArguments,
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode
)
@ -90,6 +94,8 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
f.sideEffectFree = _sideEffectFree;
f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize;
f.isMSize = false;
f.invalidatesStorage = _invalidatesStorage;
f.invalidatesMemory = _invalidatesMemory;
f.instruction = {};
f.generateCode = std::move(_generateCode);
return {name, f};
@ -110,7 +116,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
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,
AbstractAssembly& _assembly,
BuiltinContext& _context,
@ -131,7 +137,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_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,
AbstractAssembly& _assembly,
BuiltinContext& _context,
@ -152,7 +158,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_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&,
AbstractAssembly& _assembly,
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.
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& strictAssemblyForEVM(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 {}
int stackHeight() const override { return m_stackHeight; }
void setStackHeight(int height) override { m_stackHeight = height; }
void appendInstruction(dev::eth::Instruction _instruction) override;
void appendConstant(dev::u256 const& _constant) override;
void appendLabel(LabelID _labelId) override;

View File

@ -115,9 +115,9 @@ wasm::Expression EWasmCodeTransform::operator()(Label const&)
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 {};
}

View File

@ -34,9 +34,18 @@ string EWasmToText::run(
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)
ret += " (global $" + g.variableName + " (mut i64) (i64.const 0))\n";
ret += "\n";
for (auto const& f: _functions)
ret += transform(f) + "\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.sideEffectFreeIfNoMSize = false;
f.isMSize = false;
f.invalidatesStorage = true;
f.invalidatesMemory = true;
f.literalArguments = false;
}

View File

@ -45,6 +45,10 @@ struct WasmDialect: public Dialect
WasmDialect();
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();

View File

@ -18,6 +18,8 @@
#include <libyul/AsmData.h>
#include <libyul/backends/wasm/WordSizeTransform.h>
#include <libyul/Utilities.h>
#include <libyul/Dialect.h>
#include <libyul/optimiser/NameDisplacer.h>
#include <libdevcore/CommonData.h>
@ -41,6 +43,10 @@ void WordSizeTransform::operator()(FunctionalInstruction& _ins)
void WordSizeTransform::operator()(FunctionCall& _fc)
{
if (BuiltinFunction const* fun = m_inputDialect.builtin(_fc.functionName.name))
if (fun->literalArguments)
return;
rewriteFunctionCallArguments(_fc.arguments);
}
@ -48,7 +54,7 @@ void WordSizeTransform::operator()(If& _if)
{
_if.condition = make_unique<Expression>(FunctionCall{
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)
});
(*this)(_if.body);
@ -59,6 +65,18 @@ void WordSizeTransform::operator()(Switch&)
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)
{
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)

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