mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6999 from ethereum/develop
Merge develop into release for 0.5.10
This commit is contained in:
commit
5a6ea5b197
21
.circleci/README.md
Normal file
21
.circleci/README.md
Normal 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.
|
@ -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
|
||||
|
||||
|
28
.circleci/docker/Dockerfile.archlinux
Normal file
28
.circleci/docker/Dockerfile.archlinux
Normal 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
|
||||
|
124
.circleci/docker/Dockerfile.ubuntu1904
Normal file
124
.circleci/docker/Dockerfile.ubuntu1904
Normal 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
65
.circleci/soltest.sh
Executable 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}
|
@ -183,7 +183,7 @@ git:
|
||||
cache:
|
||||
ccache: true
|
||||
directories:
|
||||
- boost_1_68_0
|
||||
- boost_1_70_0_install
|
||||
- $HOME/.local
|
||||
|
||||
install:
|
||||
|
@ -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)
|
||||
|
28
Changelog.md
28
Changelog.md
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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).
|
||||
|
@ -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.
|
@ -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;
|
||||
|
@ -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
|
||||
--------------------------------
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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});``
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
|
@ -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.
|
@ -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;
|
||||
|
@ -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``.
|
||||
|
@ -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::
|
||||
|
@ -52,6 +52,8 @@ interpret a function parameter in days, you can in the following way::
|
||||
}
|
||||
}
|
||||
|
||||
.. _special-variables-functions:
|
||||
|
||||
Special Variables and Functions
|
||||
===============================
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
{
|
||||
|
93
libdevcore/InvertibleMap.h
Normal file
93
libdevcore/InvertibleMap.h
Normal 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);
|
||||
}
|
||||
};
|
@ -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]);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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},
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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})
|
||||
|
@ -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.";
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -414,7 +414,8 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
|
||||
fromArrayType.isByteArray() ||
|
||||
*fromArrayType.baseType() == *TypeProvider::uint256() ||
|
||||
*fromArrayType.baseType() == FixedBytesType(32),
|
||||
"");
|
||||
""
|
||||
);
|
||||
solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), "");
|
||||
|
||||
solAssert(
|
||||
|
@ -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,
|
||||
|
@ -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, "");
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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;
|
||||
//@}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 "";
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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!");
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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&,
|
||||
|
@ -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);
|
||||
|
123
libyul/backends/evm/EVMMetrics.cpp
Normal file
123
libyul/backends/evm/EVMMetrics.cpp
Normal 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();
|
||||
}
|
101
libyul/backends/evm/EVMMetrics.h
Normal file
101
libyul/backends/evm/EVMMetrics.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user