Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
Marenz 2022-01-20 16:38:18 +01:00
commit 7cb129b16b
184 changed files with 3105 additions and 1191 deletions

View File

@ -9,20 +9,20 @@ version: 2.1
parameters: parameters:
ubuntu-2004-docker-image: ubuntu-2004-docker-image:
type: string type: string
# solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-9 # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-10
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:3d8a912e8e78e98cd217955d06d98608ad60adc67728d4c3a569991235fa1abb" default: "solbuildpackpusher/solidity-buildpack-deps@sha256:e61939751ff9777307857361f712b581bfc8a8aaae75fab7b50febc764710587"
ubuntu-2004-clang-docker-image: ubuntu-2004-clang-docker-image:
type: string type: string
# solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-9 # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-10
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:a1ba002cae17279d1396a898b04e4e9c45602ad881295db3e2f484a7e24f6f43" default: "solbuildpackpusher/solidity-buildpack-deps@sha256:0de8c68f084120b2a165406e3a0c2aab58b32f5b7182c2322245245f1d243b8d"
ubuntu-1604-clang-ossfuzz-docker-image: ubuntu-1604-clang-ossfuzz-docker-image:
type: string type: string
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-14 # solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-15
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:f353823cce2f6cd2f9f1459d86cd76fdfc551a0261d87626615ea6c1d8f90587" default: "solbuildpackpusher/solidity-buildpack-deps@sha256:87f1a57586eec194a6217ab624efc69d3d9af2f7ac8ca36497ad57488c2f08ae"
emscripten-docker-image: emscripten-docker-image:
type: string type: string
# solbuildpackpusher/solidity-buildpack-deps:emscripten-8 # solbuildpackpusher/solidity-buildpack-deps:emscripten-9
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:842d6074e0e7e5355c89122c1cafc1fdb59696596750e7d56e5f35c0d883ad59" default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d51534dfdd05ece86f69ed7beafd68c15b88606da00a4b7fe2873ccfbd0dce24"
evm-version: evm-version:
type: string type: string
default: london default: london
@ -53,8 +53,11 @@ commands:
workflow_info=$(curl --silent "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}") || true workflow_info=$(curl --silent "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}") || true
workflow_name=$(echo "$workflow_info" | grep -E '"\s*name"\s*:\s*".*"' | cut -d \" -f 4 || echo "$CIRCLE_WORKFLOW_ID") workflow_name=$(echo "$workflow_info" | grep -E '"\s*name"\s*:\s*".*"' | cut -d \" -f 4 || echo "$CIRCLE_WORKFLOW_ID")
[[ "<< parameters.event >>" == "failure" ]] && message=" ❌ [${workflow_name}] Job **${CIRCLE_JOB}** failed on **${CIRCLE_BRANCH}**. Please see [build ${CIRCLE_BUILD_NUM}](${CIRCLE_BUILD_URL}) for details." [[ $CIRCLE_NODE_TOTAL == 1 ]] && job="**${CIRCLE_JOB}**"
[[ "<< parameters.event >>" == "success" ]] && message=" ✅ [${workflow_name}] Job **${CIRCLE_JOB}** succeeded on **${CIRCLE_BRANCH}**. Please see [build ${CIRCLE_BUILD_NUM}](${CIRCLE_BUILD_URL}) for details." [[ $CIRCLE_NODE_TOTAL != 1 ]] && job="**${CIRCLE_JOB}** (run $((CIRCLE_NODE_INDEX + 1))/${CIRCLE_NODE_TOTAL})"
[[ "<< parameters.event >>" == "failure" ]] && message=" ❌ [${workflow_name}] Job ${job} failed on **${CIRCLE_BRANCH}**. Please see [build ${CIRCLE_BUILD_NUM}](${CIRCLE_BUILD_URL}) for details."
[[ "<< parameters.event >>" == "success" ]] && message=" ✅ [${workflow_name}] Job ${job} succeeded on **${CIRCLE_BRANCH}**. Please see [build ${CIRCLE_BUILD_NUM}](${CIRCLE_BUILD_URL}) for details."
curl "https://api.gitter.im/v1/rooms/${GITTER_NOTIFY_ROOM_ID}/chatMessages" \ curl "https://api.gitter.im/v1/rooms/${GITTER_NOTIFY_ROOM_ID}/chatMessages" \
--request POST \ --request POST \
@ -355,7 +358,15 @@ defaults:
xcode: "13.2.0" xcode: "13.2.0"
environment: environment:
TERM: xterm TERM: xterm
MAKEFLAGS: -j 5 MAKEFLAGS: -j5
- base_osx_large: &base_osx_large
macos:
xcode: "13.2.0"
resource_class: large
environment:
TERM: xterm
MAKEFLAGS: -j10
- base_ems_large: &base_ems_large - base_ems_large: &base_ems_large
docker: docker:
@ -464,6 +475,98 @@ defaults:
requires: requires:
- b_win_release - b_win_release
# --------------------------------------------------------------------------
# Parameterized Job Templates
# Separate compile-only runs of those external tests where a full run takes much longer.
- job_ems_compile_ext_colony: &job_ems_compile_ext_colony
<<: *workflow_emscripten
name: t_ems_compile_ext_colony
project: colony
binary_type: solcjs
compile_only: 1
nodejs_version: '14'
- job_native_compile_ext_gnosis: &job_native_compile_ext_gnosis
<<: *workflow_ubuntu2004_static
name: t_native_compile_ext_gnosis
project: gnosis
binary_type: native
compile_only: 1
nodejs_version: '14'
- job_native_test_ext_gnosis: &job_native_test_ext_gnosis
<<: *workflow_emscripten
name: t_native_test_ext_gnosis
project: gnosis
binary_type: native
# NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1").
nodejs_version: '12'
- job_native_test_ext_gnosis_v2: &job_native_test_ext_gnosis_v2
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_gnosis_v2
project: gnosis-v2
binary_type: native
# NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1").
nodejs_version: '12'
- job_native_test_ext_zeppelin: &job_native_test_ext_zeppelin
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_zeppelin
project: zeppelin
binary_type: native
# NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '16'
resource_class: large
- job_native_test_ext_ens: &job_native_test_ext_ens
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_ens
project: ens
binary_type: native
# NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '16'
- job_native_test_ext_trident: &job_native_test_ext_trident
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_trident
project: trident
binary_type: native
# NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '16'
- job_native_test_ext_euler: &job_native_test_ext_euler
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_euler
project: euler
binary_type: native
# NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '16'
- job_native_test_ext_yield_liquidator: &job_native_test_ext_yield_liquidator
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_yield_liquidator
project: yield-liquidator
binary_type: native
# NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '16'
- job_native_test_ext_bleeps: &job_native_test_ext_bleeps
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_bleeps
project: bleeps
binary_type: native
# NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '16'
resource_class: medium
- job_native_test_ext_pool_together: &job_native_test_ext_pool_together
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_pool_together
project: pool-together
binary_type: native
# NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '16'
- job_ems_test_ext_colony: &job_ems_test_ext_colony
<<: *workflow_emscripten
name: t_ems_test_ext_colony
project: colony
binary_type: solcjs
nodejs_version: '14'
resource_class: medium
# ----------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------
jobs: jobs:
@ -791,11 +894,11 @@ jobs:
- gitter_notify_failure_unless_pr - gitter_notify_failure_unless_pr
b_osx: b_osx:
<<: *base_osx <<: *base_osx_large
environment: environment:
TERM: xterm TERM: xterm
CMAKE_BUILD_TYPE: Release CMAKE_BUILD_TYPE: Release
MAKEFLAGS: -j 5 MAKEFLAGS: -j10
steps: steps:
- checkout - checkout
- restore_cache: - restore_cache:
@ -897,7 +1000,7 @@ jobs:
t_ubu_soltest_all: &t_ubu_soltest_all t_ubu_soltest_all: &t_ubu_soltest_all
<<: *base_ubuntu2004 <<: *base_ubuntu2004
parallelism: 15 # 7 EVM versions, each with/without optimization + 1 ABIv1/@nooptions run parallelism: 50
<<: *steps_soltest_all <<: *steps_soltest_all
t_ubu_lsp: &t_ubu_lsp t_ubu_lsp: &t_ubu_lsp
@ -906,6 +1009,7 @@ jobs:
t_archlinux_soltest: &t_archlinux_soltest t_archlinux_soltest: &t_archlinux_soltest
<<: *base_archlinux <<: *base_archlinux
parallelism: 20
environment: environment:
EVM: << pipeline.parameters.evm-version >> EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 0 OPTIMIZE: 0
@ -924,6 +1028,7 @@ jobs:
t_ubu_soltest_enforce_yul: &t_ubu_soltest_enforce_yul t_ubu_soltest_enforce_yul: &t_ubu_soltest_enforce_yul
<<: *base_ubuntu2004 <<: *base_ubuntu2004
parallelism: 20
environment: environment:
EVM: << pipeline.parameters.evm-version >> EVM: << pipeline.parameters.evm-version >>
SOLTEST_FLAGS: --enforce-via-yul SOLTEST_FLAGS: --enforce-via-yul
@ -933,6 +1038,7 @@ jobs:
t_ubu_clang_soltest: &t_ubu_clang_soltest t_ubu_clang_soltest: &t_ubu_clang_soltest
<<: *base_ubuntu2004_clang <<: *base_ubuntu2004_clang
parallelism: 20
environment: environment:
EVM: << pipeline.parameters.evm-version >> EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 0 OPTIMIZE: 0
@ -960,6 +1066,7 @@ jobs:
t_ubu_asan_soltest: t_ubu_asan_soltest:
<<: *base_ubuntu2004 <<: *base_ubuntu2004
parallelism: 20
environment: environment:
EVM: << pipeline.parameters.evm-version >> EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 0 OPTIMIZE: 0
@ -969,6 +1076,7 @@ jobs:
t_ubu_asan_clang_soltest: t_ubu_asan_clang_soltest:
<<: *base_ubuntu2004_clang <<: *base_ubuntu2004_clang
parallelism: 20
environment: environment:
EVM: << pipeline.parameters.evm-version >> EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 0 OPTIMIZE: 0
@ -978,6 +1086,7 @@ jobs:
t_ubu_ubsan_clang_soltest: t_ubu_ubsan_clang_soltest:
<<: *base_ubuntu2004_clang <<: *base_ubuntu2004_clang
parallelism: 20
environment: environment:
EVM: << pipeline.parameters.evm-version >> EVM: << pipeline.parameters.evm-version >>
<<: *steps_soltest <<: *steps_soltest
@ -1318,53 +1427,20 @@ workflows:
- t_ems_solcjs: *workflow_emscripten - t_ems_solcjs: *workflow_emscripten
- t_ems_ext_hardhat: *workflow_emscripten - t_ems_ext_hardhat: *workflow_emscripten
# Separate compile-only runs of those external tests where a full run takes much longer. - t_ems_ext: *job_ems_compile_ext_colony
- t_ems_ext: - t_ems_ext: *job_native_compile_ext_gnosis
<<: *workflow_emscripten
name: t_ems_compile_ext_colony
project: colony
binary_type: solcjs
compile_only: 1
nodejs_version: '14'
- t_ems_ext:
<<: *workflow_ubuntu2004_static
name: t_native_compile_ext_gnosis
project: gnosis
binary_type: native
compile_only: 1
nodejs_version: '14'
# FIXME: Gnosis tests are pretty flaky right now. They often fail on CircleCI due to random ProviderError # FIXME: Gnosis tests are pretty flaky right now. They often fail on CircleCI due to random ProviderError
# and there are also other less frequent problems. See https://github.com/gnosis/safe-contracts/issues/216. # and there are also other less frequent problems. See https://github.com/gnosis/safe-contracts/issues/216.
#- t_ems_ext: #-t_ems_ext: *job_native_test_ext_gnosis
# <<: *workflow_emscripten - t_ems_ext: *job_native_test_ext_gnosis_v2
# name: t_native_test_ext_gnosis - t_ems_ext: *job_native_test_ext_zeppelin
# project: gnosis - t_ems_ext: *job_native_test_ext_ens
# binary_type: native - t_ems_ext: *job_native_test_ext_trident
# # NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1"). - t_ems_ext: *job_native_test_ext_euler
# nodejs_version: '12' - t_ems_ext: *job_native_test_ext_yield_liquidator
- t_ems_ext: - t_ems_ext: *job_native_test_ext_bleeps
<<: *workflow_ubuntu2004_static - t_ems_ext: *job_native_test_ext_pool_together
name: t_native_test_ext_gnosis_v2
project: gnosis-v2
binary_type: native
# NOTE: Tests do not start on node.js 14 ("ganache-cli exited early with code 1").
nodejs_version: '12'
- t_ems_ext:
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_zeppelin
project: zeppelin
binary_type: native
# NOTE: Tests crash on nodejs 17: "Error: error:0308010C:digital envelope routines::unsupported"
nodejs_version: '16'
resource_class: large
- t_ems_ext:
<<: *workflow_ubuntu2004_static
name: t_native_test_ext_ens
project: ens
binary_type: native
# NOTE: One of the dependencies (fsevents) fails to build its native extension on node.js 12+.
nodejs_version: '10'
# Windows build and tests # Windows build and tests
- b_win: *workflow_trigger_on_tags - b_win: *workflow_trigger_on_tags
@ -1374,18 +1450,23 @@ workflows:
# Bytecode comparison: # Bytecode comparison:
- b_bytecode_ubu: - b_bytecode_ubu:
<<: *workflow_trigger_on_tags
requires: requires:
- b_ubu - b_ubu
- b_bytecode_win: - b_bytecode_win:
<<: *workflow_trigger_on_tags
requires: requires:
- b_win - b_win
- b_bytecode_osx: - b_bytecode_osx:
<<: *workflow_trigger_on_tags
requires: requires:
- b_osx - b_osx
- b_bytecode_ems: - b_bytecode_ems:
<<: *workflow_trigger_on_tags
requires: requires:
- b_ems - b_ems
- t_bytecode_compare: - t_bytecode_compare:
<<: *workflow_trigger_on_tags
requires: requires:
- b_bytecode_ubu - b_bytecode_ubu
- b_bytecode_win - b_bytecode_win
@ -1425,10 +1506,4 @@ workflows:
# Emscripten build and tests that take more than 15 minutes to execute # Emscripten build and tests that take more than 15 minutes to execute
- b_ems: *workflow_trigger_on_tags - b_ems: *workflow_trigger_on_tags
- t_ems_ext: - t_ems_ext: *job_ems_test_ext_colony
<<: *workflow_emscripten
name: t_ems_test_ext_colony
project: colony
binary_type: solcjs
nodejs_version: '14'
resource_class: medium

View File

@ -61,11 +61,11 @@ then
./scripts/install_obsolete_jsoncpp_1_7_4.sh ./scripts/install_obsolete_jsoncpp_1_7_4.sh
# z3 # z3
z3_version="4.8.13" z3_version="4.8.14"
z3_dir="z3-${z3_version}-x64-osx-10.16" z3_dir="z3-${z3_version}-x64-osx-10.16"
z3_package="${z3_dir}.zip" z3_package="${z3_dir}.zip"
wget "https://github.com/Z3Prover/z3/releases/download/z3-${z3_version}/${z3_package}" wget "https://github.com/Z3Prover/z3/releases/download/z3-${z3_version}/${z3_package}"
validate_checksum "$z3_package" 191b26be2b617b2dffffce139d77abcd7e584859efbc10a58d01a1d7830697a4 validate_checksum "$z3_package" 1341671670e0c4e72da80815128a68975ee90816d50ceaf6bd820f06babe2cfd
unzip "$z3_package" unzip "$z3_package"
rm "$z3_package" rm "$z3_package"
cp "${z3_dir}/bin/libz3.a" /usr/local/lib cp "${z3_dir}/bin/libz3.a" /usr/local/lib

View File

@ -50,19 +50,48 @@ mkdir -p test_results
ulimit -s 16384 ulimit -s 16384
get_logfile_basename() { get_logfile_basename() {
local run="$1"
local filename="${EVM}" local filename="${EVM}"
test "${OPTIMIZE}" = "1" && filename="${filename}_opt" test "${OPTIMIZE}" = "1" && filename="${filename}_opt"
test "${ABI_ENCODER_V1}" = "1" && filename="${filename}_abiv1" test "${ABI_ENCODER_V1}" = "1" && filename="${filename}_abiv1"
filename="${filename}_${run}"
echo -ne "${filename}" echo -ne "${filename}"
} }
BOOST_TEST_ARGS=("--color_output=no" "--show_progress=yes" "--logger=JUNIT,error,test_results/$(get_logfile_basename).xml" "${BOOST_TEST_ARGS[@]}") [ -z "$CIRCLE_NODE_TOTAL" ] || [ "$CIRCLE_NODE_TOTAL" = 0 ] && CIRCLE_NODE_TOTAL=1
SOLTEST_ARGS=("--evm-version=$EVM" "${SOLTEST_FLAGS[@]}") [ -z "$CIRCLE_NODE_INDEX" ] && CIRCLE_NODE_INDEX=0
[ -z "$INDEX_SHIFT" ] && INDEX_SHIFT=0
test "${OPTIMIZE}" = "1" && SOLTEST_ARGS+=(--optimize) # Multiply by a prime number to get better spread, just in case
test "${ABI_ENCODER_V1}" = "1" && SOLTEST_ARGS+=(--abiencoderv1) # long-running test cases are next to each other.
CIRCLE_NODE_INDEX=$(((CIRCLE_NODE_INDEX + 23 * INDEX_SHIFT) % CIRCLE_NODE_TOTAL))
echo "Running ${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS[*]} -- ${SOLTEST_ARGS[*]}" CPUs=3
PIDs=()
for run in $(seq 0 $((CPUs - 1)))
do
BOOST_TEST_ARGS_RUN=(
"--color_output=no"
"--show_progress=yes"
"--logger=JUNIT,error,test_results/$(get_logfile_basename "$run").xml"
"${BOOST_TEST_ARGS[@]}"
)
SOLTEST_ARGS=("--evm-version=$EVM" "${SOLTEST_FLAGS[@]}")
"${REPODIR}/build/test/soltest" "${BOOST_TEST_ARGS[@]}" -- "${SOLTEST_ARGS[@]}" test "${OPTIMIZE}" = "1" && SOLTEST_ARGS+=(--optimize)
test "${ABI_ENCODER_V1}" = "1" && SOLTEST_ARGS+=(--abiencoderv1)
BATCH_ARGS=("--batches" "$((CPUs * CIRCLE_NODE_TOTAL))" "--selected-batch" "$((CPUs * CIRCLE_NODE_INDEX + run))")
echo "Running ${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS_RUN[*]} -- ${SOLTEST_ARGS[*]}"
"${REPODIR}/build/test/soltest" -l test_suite "${BOOST_TEST_ARGS_RUN[@]}" -- "${SOLTEST_ARGS[@]}" "${BATCH_ARGS[@]}" &
PIDs+=($!)
done
# wait for individual processes to get their exit status
for pid in ${PIDs[*]}
do
wait "$pid"
done

View File

@ -31,30 +31,21 @@ REPODIR="$(realpath "$(dirname "$0")"/..)"
# shellcheck source=scripts/common.sh # shellcheck source=scripts/common.sh
source "${REPODIR}/scripts/common.sh" source "${REPODIR}/scripts/common.sh"
# NOTE: If you add/remove values, remember to update `parallelism` setting in CircleCI config.
EVM_VALUES=(homestead byzantium constantinople petersburg istanbul berlin london) EVM_VALUES=(homestead byzantium constantinople petersburg istanbul berlin london)
DEFAULT_EVM=london DEFAULT_EVM=london
[[ " ${EVM_VALUES[*]} " =~ $DEFAULT_EVM ]] [[ " ${EVM_VALUES[*]} " =~ $DEFAULT_EVM ]]
OPTIMIZE_VALUES=(0 1) OPTIMIZE_VALUES=(0 1)
STEPS=$(( 1 + ${#EVM_VALUES[@]} * ${#OPTIMIZE_VALUES[@]} ))
RUN_STEPS=$(circleci_select_steps "$(seq "$STEPS")")
printTask "Running steps $RUN_STEPS..."
STEP=1
# Run for ABI encoder v1, without SMTChecker tests. # Run for ABI encoder v1, without SMTChecker tests.
if circleci_step_selected "$RUN_STEPS" "$STEP" EVM="${DEFAULT_EVM}" \
then OPTIMIZE=1 \
EVM="${DEFAULT_EVM}" \ ABI_ENCODER_V1=1 \
OPTIMIZE=1 \ BOOST_TEST_ARGS="-t !smtCheckerTests" \
ABI_ENCODER_V1=1 \ "${REPODIR}/.circleci/soltest.sh"
BOOST_TEST_ARGS="-t !smtCheckerTests" \
"${REPODIR}/.circleci/soltest.sh"
fi
((++STEP))
# We shift the batch index so that long-running tests
# do not always run in the same executor for all EVM versions
INDEX_SHIFT=0
for OPTIMIZE in "${OPTIMIZE_VALUES[@]}" for OPTIMIZE in "${OPTIMIZE_VALUES[@]}"
do do
for EVM in "${EVM_VALUES[@]}" for EVM in "${EVM_VALUES[@]}"
@ -68,16 +59,13 @@ do
DISABLE_SMTCHECKER="" DISABLE_SMTCHECKER=""
[ "${OPTIMIZE}" != "0" ] && DISABLE_SMTCHECKER="-t !smtCheckerTests" [ "${OPTIMIZE}" != "0" ] && DISABLE_SMTCHECKER="-t !smtCheckerTests"
if circleci_step_selected "$RUN_STEPS" "$STEP" EVM="$EVM" \
then OPTIMIZE="$OPTIMIZE" \
EVM="$EVM" \ SOLTEST_FLAGS="$SOLTEST_FLAGS $ENFORCE_GAS_ARGS $EWASM_ARGS" \
OPTIMIZE="$OPTIMIZE" \ BOOST_TEST_ARGS="-t !@nooptions $DISABLE_SMTCHECKER" \
SOLTEST_FLAGS="$SOLTEST_FLAGS $ENFORCE_GAS_ARGS $EWASM_ARGS" \ INDEX_SHIFT="$INDEX_SHIFT" \
BOOST_TEST_ARGS="-t !@nooptions $DISABLE_SMTCHECKER" \ "${REPODIR}/.circleci/soltest.sh"
"${REPODIR}/.circleci/soltest.sh"
fi INDEX_SHIFT=$((INDEX_SHIFT + 1))
((++STEP))
done done
done done
((STEP == STEPS + 1)) || assertFail "Step counter not properly adjusted!"

View File

@ -65,7 +65,7 @@ configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" include/licens
include(EthOptions) include(EthOptions)
configure_project(TESTS) configure_project(TESTS)
set(LATEST_Z3_VERSION "4.8.13") set(LATEST_Z3_VERSION "4.8.14")
set(MINIMUM_Z3_VERSION "4.8.0") set(MINIMUM_Z3_VERSION "4.8.0")
find_package(Z3) find_package(Z3)
if (${Z3_FOUND}) if (${Z3_FOUND})

View File

@ -10,10 +10,24 @@ Breaking changes:
### 0.8.12 (unreleased) ### 0.8.12 (unreleased)
Language Features:
* General: Support ``ContractName.functionName`` for ``abi.encodeCall``, in addition to external function pointers.
* General: Add equality-comparison operators for external function types.
Compiler Features: Compiler Features:
* Yul Optimizer: Remove ``mstore`` and ``sstore`` operations if the slot already contains the same value.
Bugfixes: Bugfixes:
* Antlr Grammar: Allow builtin names in ``yulPath`` to support ``.address`` in function pointers.
* Code Generator: Fix ICE when accessing the members of external functions occupying more than two stack slots.
* Code Generator: Fix ICE when doing an explicit conversion from ``string calldata`` to ``bytes``.
* Control Flow Graph: Perform proper virtual lookup for modifiers for uninitialized variable and unreachable code analysis.
* Immutables: Fix wrong error when the constructor of a base contract uses ``return`` and the parent contract contains immutable variables.
* IR Generator: Fix IR syntax error when copying storage arrays of structs containing functions.
* Natspec: Fix ICE when overriding a struct getter with a Natspec-documented return value and the name in the struct is different.
* TypeChecker: Fix ICE when a constant variable declaration forward references a struct.
Solc-Js: Solc-Js:
@ -2161,7 +2175,7 @@ Features:
* Internal: Inline assembly usable by the code generator. * Internal: Inline assembly usable by the code generator.
* Commandline interface: Using ``-`` as filename allows reading from stdin. * Commandline interface: Using ``-`` as filename allows reading from stdin.
* Interface JSON: Fallback function is now part of the ABI. * Interface JSON: Fallback function is now part of the ABI.
* Interface: Version string now *semver* compatible. * Interface: Version string now *SemVer* compatible.
* Code generator: Do not provide "new account gas" if we know the called account exists. * Code generator: Do not provide "new account gas" if we know the called account exists.
Bugfixes: Bugfixes:

View File

@ -54,7 +54,8 @@
- [ ] Run ``./scripts/docker_deploy_manual.sh v$VERSION``). - [ ] Run ``./scripts/docker_deploy_manual.sh v$VERSION``).
### PPA ### PPA
- [ ] Change ``scripts/release_ppa.sh`` to match your key's email and key id. - [ ] Make sure the ``ethereum/cpp-build-deps`` PPA repository contains libz3-static-dev builds for all current versions of ubuntu. If not run ``scripts/deps-ppa/static-z3.sh`` (after changing email address and key id and adding the missing ubuntu version) and wait for the builds to succeed before continuing.
- [ ] Change ``scripts/release_ppa.sh`` to match your key's email and key id; double-check that ``DISTRIBUTIONS`` contains the most recent versions.
- [ ] Run ``scripts/release_ppa.sh v$VERSION`` to create the PPA release (you need the relevant openssl key). - [ ] Run ``scripts/release_ppa.sh v$VERSION`` to create the PPA release (you need the relevant openssl key).
- [ ] Wait for the ``~ethereum/ubuntu/ethereum-static`` PPA build to be finished and published for *all platforms*. SERIOUSLY: DO NOT PROCEED EARLIER!!! *After* the static builds are *published*, copy the static package to the ``~ethereum/ubuntu/ethereum`` PPA for the destination series ``Trusty``, ``Xenial`` and ``Bionic`` while selecting ``Copy existing binaries``. - [ ] Wait for the ``~ethereum/ubuntu/ethereum-static`` PPA build to be finished and published for *all platforms*. SERIOUSLY: DO NOT PROCEED EARLIER!!! *After* the static builds are *published*, copy the static package to the ``~ethereum/ubuntu/ethereum`` PPA for the destination series ``Trusty``, ``Xenial`` and ``Bionic`` while selecting ``Copy existing binaries``.

View File

@ -63,7 +63,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = 'Solidity' project = 'Solidity'
copyright = '2016-2021, Ethereum' project_copyright = '2016-2021, Ethereum'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
@ -73,7 +73,7 @@ copyright = '2016-2021, Ethereum'
with open('../CMakeLists.txt', 'r', encoding='utf8') as f: with open('../CMakeLists.txt', 'r', encoding='utf8') as f:
version = re.search('PROJECT_VERSION "([^"]+)"', f.read()).group(1) version = re.search('PROJECT_VERSION "([^"]+)"', f.read()).group(1)
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
if os.path.isfile('../prerelease.txt') != True or os.path.getsize('../prerelease.txt') == 0: if not os.path.isfile('../prerelease.txt') or os.path.getsize('../prerelease.txt') == 0:
release = version release = version
else: else:
# This is a prerelease version # This is a prerelease version

View File

@ -34,7 +34,8 @@ contracts (using qualified access like ``L.f()``).
Of course, calls to internal functions Of course, calls to internal functions
use the internal calling convention, which means that all internal types use the internal calling convention, which means that all internal types
can be passed and types :ref:`stored in memory <data-location>` will be passed by reference and not copied. can be passed and types :ref:`stored in memory <data-location>` will be passed by reference and not copied.
To realize this in the EVM, code of internal library functions To realize this in the EVM, the code of internal library functions
that are called from a contract
and all functions called from therein will at compile time be included in the calling and all functions called from therein will at compile time be included in the calling
contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``. contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``.

View File

@ -33,7 +33,7 @@ the team and contributors are working on, you can join our public team calls:
- Mondays at 3pm CET/CEST. - Mondays at 3pm CET/CEST.
- Wednesdays at 2pm CET/CEST. - Wednesdays at 2pm CET/CEST.
Both calls take place on `Jitsi <https://meet.komputing.org/solidity>`_. Both calls take place on `Jitsi <https://meet.ethereum.org/solidity>`_.
How to Report Issues How to Report Issues
==================== ====================

View File

@ -699,7 +699,7 @@ safest action is to revert all changes and make the whole transaction
(or at least call) without effect. (or at least call) without effect.
In both cases, the caller can react on such failures using ``try``/``catch``, but In both cases, the caller can react on such failures using ``try``/``catch``, but
the changes in the caller will always be reverted. the changes in the callee will always be reverted.
.. note:: .. note::
@ -895,6 +895,6 @@ in scope in the block that follows.
The error might have happened deeper down in the call chain and the The error might have happened deeper down in the call chain and the
called contract just forwarded it. Also, it could be due to an called contract just forwarded it. Also, it could be due to an
out-of-gas situation and not a deliberate error condition: out-of-gas situation and not a deliberate error condition:
The caller always retains 63/64th of the gas in a call and thus The caller always retains at least 1/64th of the gas in a call and thus
even if the called contract goes out of gas, the caller still even if the called contract goes out of gas, the caller still
has some gas left. has some gas left.

View File

@ -121,6 +121,9 @@ to receive their money - contracts cannot activate themselves.
// before `send` returns. // before `send` returns.
pendingReturns[msg.sender] = 0; pendingReturns[msg.sender] = 0;
// msg.sender is not of type `address payable` and must be
// explicitly converted using `payable(msg.sender)` in order
// use the member function `send()`.
if (!payable(msg.sender).send(amount)) { if (!payable(msg.sender).send(amount)) {
// No need to call throw here, just reset the amount owing // No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount; pendingReturns[msg.sender] = amount;

View File

@ -37,7 +37,7 @@ Alice does not need to interact with the Ethereum network
to sign the transaction, the process is completely offline. to sign the transaction, the process is completely offline.
In this tutorial, we will sign messages in the browser In this tutorial, we will sign messages in the browser
using `web3.js <https://github.com/ethereum/web3.js>`_ and using `web3.js <https://github.com/ethereum/web3.js>`_ and
`MetaMask <https://metamask.io>`_, using the method described in `EIP-762 <https://github.com/ethereum/EIPs/pull/712>`_, `MetaMask <https://metamask.io>`_, using the method described in `EIP-712 <https://github.com/ethereum/EIPs/pull/712>`_,
as it provides a number of other security benefits. as it provides a number of other security benefits.
.. code-block:: javascript .. code-block:: javascript

View File

@ -564,7 +564,7 @@ yulFunctionDefinition:
* While only identifiers without dots can be declared within inline assembly, * While only identifiers without dots can be declared within inline assembly,
* paths containing dots can refer to declarations outside the inline assembly block. * paths containing dots can refer to declarations outside the inline assembly block.
*/ */
yulPath: YulIdentifier (YulPeriod YulIdentifier)*; yulPath: YulIdentifier (YulPeriod (YulIdentifier | YulEVMBuiltin))*;
/** /**
* A call to a function with return values can only occur as right-hand side of an assignment or * A call to a function with return values can only occur as right-hand side of an assignment or
* a variable declaration. * a variable declaration.

View File

@ -118,8 +118,17 @@ The nightly version can be installed using these commands:
sudo apt-get update sudo apt-get update
sudo apt-get install solc sudo apt-get install solc
We are also releasing a `snap package <https://snapcraft.io/>`_, which is Furthermore, some Linux distributions provide their own packages. These packages are not directly
installable in all the `supported Linux distros <https://snapcraft.io/docs/core/install>`_. To maintained by us, but usually kept up-to-date by the respective package maintainers.
For example, Arch Linux has packages for the latest development version:
.. code-block:: bash
pacman -S solidity
There is also a `snap package <https://snapcraft.io/solc>`_, however, it is **currently unmaintained**.
It is installable in all the `supported Linux distros <https://snapcraft.io/docs/core/install>`_. To
install the latest stable version of solc: install the latest stable version of solc:
.. code-block:: bash .. code-block:: bash
@ -139,18 +148,6 @@ with the most recent changes, please use the following:
but it comes with limitations, like accessing only the files in your ``/home`` and ``/media`` directories. but it comes with limitations, like accessing only the files in your ``/home`` and ``/media`` directories.
For more information, go to `Demystifying Snap Confinement <https://snapcraft.io/blog/demystifying-snap-confinement>`_. For more information, go to `Demystifying Snap Confinement <https://snapcraft.io/blog/demystifying-snap-confinement>`_.
Arch Linux also has packages, albeit limited to the latest development version:
.. code-block:: bash
pacman -S solidity
Gentoo Linux has an `Ethereum overlay <https://overlays.gentoo.org/#ethereum>`_ that contains a Solidity package.
After the overlay is setup, ``solc`` can be installed in x86_64 architectures by:
.. code-block:: bash
emerge dev-lang/solidity
macOS Packages macOS Packages
============== ==============
@ -537,8 +534,8 @@ The Solidity version string contains four parts:
If there are local modifications, the commit will be postfixed with ``.mod``. If there are local modifications, the commit will be postfixed with ``.mod``.
These parts are combined as required by Semver, where the Solidity pre-release tag equals to the Semver pre-release These parts are combined as required by SemVer, where the Solidity pre-release tag equals to the SemVer pre-release
and the Solidity commit and platform combined make up the Semver build metadata. and the Solidity commit and platform combined make up the SemVer build metadata.
A release example: ``0.4.8+commit.60cc1668.Emscripten.clang``. A release example: ``0.4.8+commit.60cc1668.Emscripten.clang``.
@ -549,7 +546,7 @@ Important Information About Versioning
After a release is made, the patch version level is bumped, because we assume that only After a release is made, the patch version level is bumped, because we assume that only
patch level changes follow. When changes are merged, the version should be bumped according patch level changes follow. When changes are merged, the version should be bumped according
to semver and the severity of the change. Finally, a release is always made with the version to SemVer and the severity of the change. Finally, a release is always made with the version
of the current nightly build, but without the ``prerelease`` specifier. of the current nightly build, but without the ``prerelease`` specifier.
Example: Example:

View File

@ -290,6 +290,7 @@ on the individual steps and their sequence below.
- :ref:`conditional-unsimplifier`. - :ref:`conditional-unsimplifier`.
- :ref:`control-flow-simplifier`. - :ref:`control-flow-simplifier`.
- :ref:`dead-code-eliminator`. - :ref:`dead-code-eliminator`.
- :ref:`equal-store-eliminator`.
- :ref:`equivalent-function-combiner`. - :ref:`equivalent-function-combiner`.
- :ref:`expression-joiner`. - :ref:`expression-joiner`.
- :ref:`expression-simplifier`. - :ref:`expression-simplifier`.
@ -938,6 +939,22 @@ we require ForLoopInitRewriter to run before this step.
Prerequisite: ForLoopInitRewriter, Function Hoister, Function Grouper Prerequisite: ForLoopInitRewriter, Function Hoister, Function Grouper
.. _equal-store-eliminator:
EqualStoreEliminator
^^^^^^^^^^^^^^^^^^^^
This steps removes ``mstore(k, v)`` and ``sstore(k, v)`` calls if
there was a previous call to ``mstore(k, v)`` / ``sstore(k, v)``,
no other store in between and the values of ``k`` and ``v`` did not change.
This simple step is effective if run after the SSA transform and the
Common Subexpression Eliminator, because SSA will make sure that the variables
will not change and the Common Subexpression Eliminator re-uses exactly the same
variable if the value is known to be the same.
Prerequisites: Disambiguator, ForLoopInitRewriter
.. _unused-pruner: .. _unused-pruner:
UnusedPruner UnusedPruner

View File

@ -13,7 +13,7 @@ and :ref:`constant variable<constants>` definitions.
SPDX License Identifier SPDX License Identifier
======================= =======================
Trust in smart contract can be better established if their source code Trust in smart contracts can be better established if their source code
is available. Since making source code available always touches on legal problems is available. Since making source code available always touches on legal problems
with regards to copyright, the Solidity compiler encourages the use with regards to copyright, the Solidity compiler encourages the use
of machine-readable `SPDX license identifiers <https://spdx.org>`_. of machine-readable `SPDX license identifiers <https://spdx.org>`_.

View File

@ -84,23 +84,24 @@ The example below uses ``_allowances`` to record the amount someone else is allo
} }
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) { function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
require(_allowances[sender][msg.sender] >= amount, "ERC20: Allowance not high enough.");
_allowances[sender][msg.sender] -= amount;
_transfer(sender, recipient, amount); _transfer(sender, recipient, amount);
approve(sender, msg.sender, amount);
return true; return true;
} }
function approve(address owner, address spender, uint256 amount) public returns (bool) { function approve(address spender, uint256 amount) public returns (bool) {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address"); require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount; _allowances[msg.sender][spender] = amount;
emit Approval(owner, spender, amount); emit Approval(msg.sender, spender, amount);
return true; return true;
} }
function _transfer(address sender, address recipient, uint256 amount) internal { function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address"); require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address");
require(_balances[sender] >= amount, "ERC20: Not enough funds.");
_balances[sender] -= amount; _balances[sender] -= amount;
_balances[recipient] += amount; _balances[recipient] += amount;

View File

@ -153,7 +153,8 @@ third-party string libraries. You can also compare two strings by their keccak25
concatenate two strings using ``bytes.concat(bytes(s1), bytes(s2))``. concatenate two strings using ``bytes.concat(bytes(s1), bytes(s2))``.
You should use ``bytes`` over ``bytes1[]`` because it is cheaper, You should use ``bytes`` over ``bytes1[]`` because it is cheaper,
since ``bytes1[]`` adds 31 padding bytes between the elements. As a general rule, since using ``bytes1[]`` in ``memory`` adds 31 padding bytes between the elements. Note that in ``storage``, the
padding is absent due to tight packing, see :ref:`bytes and string <bytes-and-string>`. As a general rule,
use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length
string (UTF-8) data. If you can limit the length to a certain number of bytes, string (UTF-8) data. If you can limit the length to a certain number of bytes,
always use one of the value types ``bytes1`` to ``bytes32`` because they are much cheaper. always use one of the value types ``bytes1`` to ``bytes32`` because they are much cheaper.

View File

@ -329,6 +329,10 @@ on ``call``.
regardless of whether state is read from or written to, as this can have many pitfalls. regardless of whether state is read from or written to, as this can have many pitfalls.
Also, access to gas might change in the future. Also, access to gas might change in the future.
* ``code`` and ``codehash``
You can query the deployed code for any smart contract. Use ``code`` to get the EVM bytecode as a string, which might be empty. Use ``codehash`` get the Keccak-256 hash of that code.
.. note:: .. note::
All contracts can be converted to ``address`` type, so it is possible to query the balance of the All contracts can be converted to ``address`` type, so it is possible to query the balance of the
current contract using ``address(this).balance``. current contract using ``address(this).balance``.

View File

@ -25,19 +25,21 @@ using namespace solidity::langutil;
using namespace solidity::frontend; using namespace solidity::frontend;
using namespace std; using namespace std;
ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow): ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow, ContractDefinition const* _contract):
m_nodeContainer(_nodeContainer), m_nodeContainer(_nodeContainer),
m_currentNode(_functionFlow.entry), m_currentNode(_functionFlow.entry),
m_returnNode(_functionFlow.exit), m_returnNode(_functionFlow.exit),
m_revertNode(_functionFlow.revert), m_revertNode(_functionFlow.revert),
m_transactionReturnNode(_functionFlow.transactionReturn) m_transactionReturnNode(_functionFlow.transactionReturn),
m_contract(_contract)
{ {
} }
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow( unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
CFG::NodeContainer& _nodeContainer, CFG::NodeContainer& _nodeContainer,
FunctionDefinition const& _function FunctionDefinition const& _function,
ContractDefinition const* _contract
) )
{ {
auto functionFlow = make_unique<FunctionFlow>(); auto functionFlow = make_unique<FunctionFlow>();
@ -45,7 +47,7 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
functionFlow->exit = _nodeContainer.newNode(); functionFlow->exit = _nodeContainer.newNode();
functionFlow->revert = _nodeContainer.newNode(); functionFlow->revert = _nodeContainer.newNode();
functionFlow->transactionReturn = _nodeContainer.newNode(); functionFlow->transactionReturn = _nodeContainer.newNode();
ControlFlowBuilder builder(_nodeContainer, *functionFlow); ControlFlowBuilder builder(_nodeContainer, *functionFlow, _contract);
builder.appendControlFlow(_function); builder.appendControlFlow(_function);
return functionFlow; return functionFlow;
@ -297,7 +299,8 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall)
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
ASTNode::listAccept(_functionCall.arguments(), *this); ASTNode::listAccept(_functionCall.arguments(), *this);
m_currentNode->functionCalls.emplace_back(&_functionCall); solAssert(!m_currentNode->functionCall);
m_currentNode->functionCall = &_functionCall;
auto nextNode = newLabel(); auto nextNode = newLabel();
@ -321,8 +324,20 @@ bool ControlFlowBuilder::visit(ModifierInvocation const& _modifierInvocation)
auto modifierDefinition = dynamic_cast<ModifierDefinition const*>( auto modifierDefinition = dynamic_cast<ModifierDefinition const*>(
_modifierInvocation.name().annotation().referencedDeclaration _modifierInvocation.name().annotation().referencedDeclaration
); );
if (!modifierDefinition) return false;
if (!modifierDefinition->isImplemented()) return false; if (!modifierDefinition)
return false;
VirtualLookup const& requiredLookup = *_modifierInvocation.name().annotation().requiredLookup;
if (requiredLookup == VirtualLookup::Virtual)
modifierDefinition = &modifierDefinition->resolveVirtual(*m_contract);
else
solAssert(requiredLookup == VirtualLookup::Static);
if (!modifierDefinition->isImplemented())
return false;
solAssert(!!m_returnNode, ""); solAssert(!!m_returnNode, "");
m_placeholderEntry = newLabel(); m_placeholderEntry = newLabel();
@ -355,8 +370,8 @@ bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition)
} }
for (auto const& modifier: _functionDefinition.modifiers()) for (auto const& modifierInvocation: _functionDefinition.modifiers())
appendControlFlow(*modifier); appendControlFlow(*modifierInvocation);
appendControlFlow(_functionDefinition.body()); appendControlFlow(_functionDefinition.body());

View File

@ -37,13 +37,15 @@ class ControlFlowBuilder: private ASTConstVisitor, private yul::ASTWalker
public: public:
static std::unique_ptr<FunctionFlow> createFunctionFlow( static std::unique_ptr<FunctionFlow> createFunctionFlow(
CFG::NodeContainer& _nodeContainer, CFG::NodeContainer& _nodeContainer,
FunctionDefinition const& _function FunctionDefinition const& _function,
ContractDefinition const* _contract = nullptr
); );
private: private:
explicit ControlFlowBuilder( explicit ControlFlowBuilder(
CFG::NodeContainer& _nodeContainer, CFG::NodeContainer& _nodeContainer,
FunctionFlow const& _functionFlow FunctionFlow const& _functionFlow,
ContractDefinition const* _contract = nullptr
); );
// Visits for constructing the control flow. // Visits for constructing the control flow.
@ -158,6 +160,8 @@ private:
CFGNode* m_revertNode = nullptr; CFGNode* m_revertNode = nullptr;
CFGNode* m_transactionReturnNode = nullptr; CFGNode* m_transactionReturnNode = nullptr;
ContractDefinition const* m_contract = nullptr;
/// The current jump destination of break Statements. /// The current jump destination of break Statements.
CFGNode* m_breakJump = nullptr; CFGNode* m_breakJump = nullptr;
/// The current jump destination of continue Statements. /// The current jump destination of continue Statements.

View File

@ -44,7 +44,7 @@ bool CFG::visit(ContractDefinition const& _contract)
for (FunctionDefinition const* function: contract->definedFunctions()) for (FunctionDefinition const* function: contract->definedFunctions())
if (function->isImplemented()) if (function->isImplemented())
m_functionControlFlow[{&_contract, function}] = m_functionControlFlow[{&_contract, function}] =
ControlFlowBuilder::createFunctionFlow(m_nodeContainer, *function); ControlFlowBuilder::createFunctionFlow(m_nodeContainer, *function, &_contract);
return true; return true;
} }

View File

@ -98,8 +98,8 @@ struct CFGNode
std::vector<CFGNode*> entries; std::vector<CFGNode*> entries;
/// Exit nodes. All CFG nodes to which control flow may continue after this node. /// Exit nodes. All CFG nodes to which control flow may continue after this node.
std::vector<CFGNode*> exits; std::vector<CFGNode*> exits;
/// Function calls done by this node /// Function call done by this node
std::vector<FunctionCall const*> functionCalls; FunctionCall const* functionCall = nullptr;
/// Variable occurrences in the node. /// Variable occurrences in the node.
std::vector<VariableOccurrence> variableOccurrences; std::vector<VariableOccurrence> variableOccurrences;

View File

@ -81,27 +81,27 @@ void ControlFlowRevertPruner::findRevertStates()
if (_node == functionFlow.exit) if (_node == functionFlow.exit)
foundExit = true; foundExit = true;
for (auto const* functionCall: _node->functionCalls) if (auto const* functionCall = _node->functionCall)
{ {
auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.contract); auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.contract);
if (resolvedFunction == nullptr || !resolvedFunction->isImplemented()) if (resolvedFunction && resolvedFunction->isImplemented())
continue;
CFG::FunctionContractTuple calledFunctionTuple{
findScopeContract(*resolvedFunction, item.contract),
resolvedFunction
};
switch (m_functions.at(calledFunctionTuple))
{ {
case RevertState::Unknown: CFG::FunctionContractTuple calledFunctionTuple{
wakeUp[calledFunctionTuple].insert(item); findScopeContract(*resolvedFunction, item.contract),
foundUnknown = true; resolvedFunction
return; };
case RevertState::AllPathsRevert: switch (m_functions.at(calledFunctionTuple))
return; {
case RevertState::HasNonRevertingPath: case RevertState::Unknown:
break; wakeUp[calledFunctionTuple].insert(item);
foundUnknown = true;
return;
case RevertState::AllPathsRevert:
return;
case RevertState::HasNonRevertingPath:
break;
}
} }
} }
@ -135,31 +135,29 @@ void ControlFlowRevertPruner::modifyFunctionFlows()
FunctionFlow const& functionFlow = m_cfg.functionFlow(*item.first.function, item.first.contract); FunctionFlow const& functionFlow = m_cfg.functionFlow(*item.first.function, item.first.contract);
solidity::util::BreadthFirstSearch<CFGNode*>{{functionFlow.entry}}.run( solidity::util::BreadthFirstSearch<CFGNode*>{{functionFlow.entry}}.run(
[&](CFGNode* _node, auto&& _addChild) { [&](CFGNode* _node, auto&& _addChild) {
for (auto const* functionCall: _node->functionCalls) if (auto const* functionCall = _node->functionCall)
{ {
auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.first.contract); auto const* resolvedFunction = ASTNode::resolveFunctionCall(*functionCall, item.first.contract);
if (resolvedFunction == nullptr || !resolvedFunction->isImplemented()) if (resolvedFunction && resolvedFunction->isImplemented())
continue; switch (m_functions.at({findScopeContract(*resolvedFunction, item.first.contract), resolvedFunction}))
{
case RevertState::Unknown:
[[fallthrough]];
case RevertState::AllPathsRevert:
// If the revert states of the functions do not
// change anymore, we treat all "unknown" states as
// "reverting", since they can only be caused by
// recursion.
for (CFGNode * node: _node->exits)
ranges::remove(node->entries, _node);
switch (m_functions.at({findScopeContract(*resolvedFunction, item.first.contract), resolvedFunction})) _node->exits = {functionFlow.revert};
{ functionFlow.revert->entries.push_back(_node);
case RevertState::Unknown: return;
[[fallthrough]]; default:
case RevertState::AllPathsRevert: break;
// If the revert states of the functions do not }
// change anymore, we treat all "unknown" states as
// "reverting", since they can only be caused by
// recursion.
for (CFGNode * node: _node->exits)
ranges::remove(node->entries, _node);
_node->exits = {functionFlow.revert};
functionFlow.revert->entries.push_back(_node);
return;
default:
break;
}
} }
for (CFGNode* exit: _node->exits) for (CFGNode* exit: _node->exits)

View File

@ -437,16 +437,14 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
type = TypeProvider::withLocation(ref, typeLoc, isPointer); type = TypeProvider::withLocation(ref, typeLoc, isPointer);
} }
if ( if (_variable.isConstant() && !type->isValueType())
_variable.isConstant() && {
!dynamic_cast<UserDefinedValueType const*>(type) && bool allowed = false;
type->containsNestedMapping() if (auto arrayType = dynamic_cast<ArrayType const*>(type))
) allowed = arrayType->isByteArray();
m_errorReporter.fatalDeclarationError( if (!allowed)
3530_error, m_errorReporter.fatalTypeError(9259_error, _variable.location(), "Only constants of value type and byte array type are implemented.");
_variable.location(), }
"The type contains a (nested) mapping and therefore cannot be a constant."
);
_variable.annotation().type = type; _variable.annotation().type = type;
} }

View File

@ -25,6 +25,7 @@
#include <libsolidity/analysis/DocStringAnalyser.h> #include <libsolidity/analysis/DocStringAnalyser.h>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@ -37,7 +38,7 @@ using namespace solidity::frontend;
namespace namespace
{ {
void copyMissingTags(set<CallableDeclaration const*> const& _baseFunctions, StructurallyDocumentedAnnotation& _target, CallableDeclaration const* _declaration = nullptr) void copyMissingTags(set<CallableDeclaration const*> const& _baseFunctions, StructurallyDocumentedAnnotation& _target, FunctionType const* _functionType = nullptr)
{ {
// Only copy if there is exactly one direct base function. // Only copy if there is exactly one direct base function.
if (_baseFunctions.size() != 1) if (_baseFunctions.size() != 1)
@ -45,12 +46,6 @@ void copyMissingTags(set<CallableDeclaration const*> const& _baseFunctions, Stru
CallableDeclaration const& baseFunction = **_baseFunctions.begin(); CallableDeclaration const& baseFunction = **_baseFunctions.begin();
auto hasReturnParameter = [](CallableDeclaration const& declaration, size_t _n)
{
return declaration.returnParameterList() &&
declaration.returnParameters().size() > _n;
};
auto& sourceDoc = dynamic_cast<StructurallyDocumentedAnnotation const&>(baseFunction.annotation()); auto& sourceDoc = dynamic_cast<StructurallyDocumentedAnnotation const&>(baseFunction.annotation());
for (auto it = sourceDoc.docTags.begin(); it != sourceDoc.docTags.end();) for (auto it = sourceDoc.docTags.begin(); it != sourceDoc.docTags.end();)
@ -70,21 +65,22 @@ void copyMissingTags(set<CallableDeclaration const*> const& _baseFunctions, Stru
DocTag content = it->second; DocTag content = it->second;
// Update the parameter name for @return tags // Update the parameter name for @return tags
if (_declaration && tag == "return") if (_functionType && tag == "return")
{ {
size_t docParaNameEndPos = content.content.find_first_of(" \t"); size_t docParaNameEndPos = content.content.find_first_of(" \t");
string const docParameterName = content.content.substr(0, docParaNameEndPos); string const docParameterName = content.content.substr(0, docParaNameEndPos);
if ( if (
hasReturnParameter(*_declaration, n) && _functionType->returnParameterNames().size() > n &&
docParameterName != _declaration->returnParameters().at(n)->name() docParameterName != _functionType->returnParameterNames().at(n)
) )
{ {
bool baseHasNoName = bool baseHasNoName =
hasReturnParameter(baseFunction, n) && baseFunction.returnParameterList() &&
baseFunction.returnParameters().size() > n &&
baseFunction.returnParameters().at(n)->name().empty(); baseFunction.returnParameters().at(n)->name().empty();
string paramName = _declaration->returnParameters().at(n)->name(); string paramName = _functionType->returnParameterNames().at(n);
content.content = content.content =
(paramName.empty() ? "" : std::move(paramName) + " ") + ( (paramName.empty() ? "" : std::move(paramName) + " ") + (
string::npos == docParaNameEndPos || baseHasNoName ? string::npos == docParaNameEndPos || baseHasNoName ?
@ -127,7 +123,7 @@ bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit)
bool DocStringAnalyser::visit(FunctionDefinition const& _function) bool DocStringAnalyser::visit(FunctionDefinition const& _function)
{ {
if (!_function.isConstructor()) if (!_function.isConstructor())
handleCallable(_function, _function, _function.annotation()); handleCallable(_function, _function, _function.annotation(), TypeProvider::function(_function));
return true; return true;
} }
@ -136,10 +132,12 @@ bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
if (!_variable.isStateVariable() && !_variable.isFileLevelVariable()) if (!_variable.isStateVariable() && !_variable.isFileLevelVariable())
return false; return false;
auto const* getterType = TypeProvider::function(_variable);
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation())) if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation()))
copyMissingTags({baseFunction}, _variable.annotation()); copyMissingTags({baseFunction}, _variable.annotation(), getterType);
else if (_variable.annotation().docTags.empty()) else if (_variable.annotation().docTags.empty())
copyMissingTags(_variable.annotation().baseFunctions, _variable.annotation()); copyMissingTags(_variable.annotation().baseFunctions, _variable.annotation(), getterType);
return false; return false;
} }
@ -168,17 +166,18 @@ bool DocStringAnalyser::visit(ErrorDefinition const& _error)
void DocStringAnalyser::handleCallable( void DocStringAnalyser::handleCallable(
CallableDeclaration const& _callable, CallableDeclaration const& _callable,
StructurallyDocumented const& _node, StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation StructurallyDocumentedAnnotation& _annotation,
FunctionType const* _functionType
) )
{ {
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_callable.annotation().baseFunctions, _node, _annotation)) if (CallableDeclaration const* baseFunction = resolveInheritDoc(_callable.annotation().baseFunctions, _node, _annotation))
copyMissingTags({baseFunction}, _annotation, &_callable); copyMissingTags({baseFunction}, _annotation, _functionType);
else if ( else if (
_annotation.docTags.empty() && _annotation.docTags.empty() &&
_callable.annotation().baseFunctions.size() == 1 && _callable.annotation().baseFunctions.size() == 1 &&
parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin()) parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin())
) )
copyMissingTags(_callable.annotation().baseFunctions, _annotation, &_callable); copyMissingTags(_callable.annotation().baseFunctions, _annotation, _functionType);
} }
CallableDeclaration const* DocStringAnalyser::resolveInheritDoc( CallableDeclaration const* DocStringAnalyser::resolveInheritDoc(

View File

@ -54,7 +54,8 @@ private:
void handleCallable( void handleCallable(
CallableDeclaration const& _callable, CallableDeclaration const& _callable,
StructurallyDocumented const& _node, StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation StructurallyDocumentedAnnotation& _annotation,
FunctionType const* _functionType = nullptr
); );
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;

View File

@ -73,24 +73,24 @@ inline vector<shared_ptr<MagicVariableDeclaration const>> constructMagicVariable
return { return {
magicVarDecl("abi", TypeProvider::magic(MagicType::Kind::ABI)), magicVarDecl("abi", TypeProvider::magic(MagicType::Kind::ABI)),
magicVarDecl("addmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)), magicVarDecl("addmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, StateMutability::Pure)),
magicVarDecl("assert", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), magicVarDecl("assert", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Assert, StateMutability::Pure)),
magicVarDecl("block", TypeProvider::magic(MagicType::Kind::Block)), magicVarDecl("block", TypeProvider::magic(MagicType::Kind::Block)),
magicVarDecl("blockhash", TypeProvider::function(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)), magicVarDecl("blockhash", TypeProvider::function(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, StateMutability::View)),
magicVarDecl("ecrecover", TypeProvider::function(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), magicVarDecl("ecrecover", TypeProvider::function(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, StateMutability::Pure)),
magicVarDecl("gasleft", TypeProvider::function(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)), magicVarDecl("gasleft", TypeProvider::function(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, StateMutability::View)),
magicVarDecl("keccak256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), magicVarDecl("keccak256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, StateMutability::Pure)),
magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)), magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)),
magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, StateMutability::Pure)),
magicVarDecl("now", TypeProvider::uint256()), magicVarDecl("now", TypeProvider::uint256()),
magicVarDecl("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), magicVarDecl("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, StateMutability::Pure)),
magicVarDecl("require", TypeProvider::function(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), magicVarDecl("require", TypeProvider::function(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, StateMutability::Pure)),
magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, StateMutability::Pure)),
magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, StateMutability::Pure)),
magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)), magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, StateMutability::Pure)),
magicVarDecl("selfdestruct", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), magicVarDecl("selfdestruct", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
magicVarDecl("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)), magicVarDecl("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, StateMutability::Pure)),
magicVarDecl("sha3", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), magicVarDecl("sha3", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, StateMutability::Pure)),
magicVarDecl("suicide", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), magicVarDecl("suicide", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
magicVarDecl("tx", TypeProvider::magic(MagicType::Kind::Transaction)), magicVarDecl("tx", TypeProvider::magic(MagicType::Kind::Transaction)),
// Accepts a MagicType that can be any contract type or an Integer type and returns a // Accepts a MagicType that can be any contract type or an Integer type and returns a
@ -99,8 +99,8 @@ inline vector<shared_ptr<MagicVariableDeclaration const>> constructMagicVariable
strings{}, strings{},
strings{}, strings{},
FunctionType::Kind::MetaType, FunctionType::Kind::MetaType,
true, StateMutability::Pure,
StateMutability::Pure FunctionType::Options::withArbitraryParameters()
)), )),
}; };
} }

View File

@ -29,7 +29,7 @@ void ImmutableValidator::analyze()
{ {
m_inCreationContext = true; m_inCreationContext = true;
auto linearizedContracts = m_currentContract.annotation().linearizedBaseContracts | ranges::views::reverse; auto linearizedContracts = m_mostDerivedContract.annotation().linearizedBaseContracts | ranges::views::reverse;
for (ContractDefinition const* contract: linearizedContracts) for (ContractDefinition const* contract: linearizedContracts)
for (VariableDeclaration const* stateVar: contract->stateVariables()) for (VariableDeclaration const* stateVar: contract->stateVariables())
@ -62,7 +62,7 @@ void ImmutableValidator::analyze()
visitCallableIfNew(*modDef); visitCallableIfNew(*modDef);
} }
checkAllVariablesInitialized(m_currentContract.location()); checkAllVariablesInitialized(m_mostDerivedContract.location());
} }
bool ImmutableValidator::visit(Assignment const& _assignment) bool ImmutableValidator::visit(Assignment const& _assignment)
@ -137,7 +137,7 @@ void ImmutableValidator::endVisit(IdentifierPath const& _identifierPath)
if (auto const callableDef = dynamic_cast<CallableDeclaration const*>(_identifierPath.annotation().referencedDeclaration)) if (auto const callableDef = dynamic_cast<CallableDeclaration const*>(_identifierPath.annotation().referencedDeclaration))
visitCallableIfNew( visitCallableIfNew(
*_identifierPath.annotation().requiredLookup == VirtualLookup::Virtual ? *_identifierPath.annotation().requiredLookup == VirtualLookup::Virtual ?
callableDef->resolveVirtual(m_currentContract) : callableDef->resolveVirtual(m_mostDerivedContract) :
*callableDef *callableDef
); );
@ -147,7 +147,7 @@ void ImmutableValidator::endVisit(IdentifierPath const& _identifierPath)
void ImmutableValidator::endVisit(Identifier const& _identifier) void ImmutableValidator::endVisit(Identifier const& _identifier)
{ {
if (auto const callableDef = dynamic_cast<CallableDeclaration const*>(_identifier.annotation().referencedDeclaration)) if (auto const callableDef = dynamic_cast<CallableDeclaration const*>(_identifier.annotation().referencedDeclaration))
visitCallableIfNew(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual ? callableDef->resolveVirtual(m_currentContract) : *callableDef); visitCallableIfNew(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual ? callableDef->resolveVirtual(m_mostDerivedContract) : *callableDef);
if (auto const varDecl = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration)) if (auto const varDecl = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
analyseVariableReference(*varDecl, _identifier); analyseVariableReference(*varDecl, _identifier);
} }
@ -160,15 +160,18 @@ void ImmutableValidator::endVisit(Return const& _return)
bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDeclaration) bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDeclaration)
{ {
FunctionDefinition const* prevConstructor = m_currentConstructor; ScopedSaveAndRestore constructorGuard{m_currentConstructor, {}};
m_currentConstructor = nullptr; ScopedSaveAndRestore constructorContractGuard{m_currentConstructorContract, {}};
if (FunctionDefinition const* funcDef = dynamic_cast<decltype(funcDef)>(&_callableDeclaration)) if (FunctionDefinition const* funcDef = dynamic_cast<decltype(funcDef)>(&_callableDeclaration))
{ {
ASTNode::listAccept(funcDef->modifiers(), *this); ASTNode::listAccept(funcDef->modifiers(), *this);
if (funcDef->isConstructor()) if (funcDef->isConstructor())
{
m_currentConstructorContract = funcDef->annotation().contract;
m_currentConstructor = funcDef; m_currentConstructor = funcDef;
}
if (funcDef->isImplemented()) if (funcDef->isImplemented())
funcDef->body().accept(*this); funcDef->body().accept(*this);
@ -177,8 +180,6 @@ bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDec
if (modDef->isImplemented()) if (modDef->isImplemented())
modDef->body().accept(*this); modDef->body().accept(*this);
m_currentConstructor = prevConstructor;
return false; return false;
} }
@ -253,7 +254,8 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::SourceLocation const& _location) void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::SourceLocation const& _location)
{ {
for (ContractDefinition const* contract: m_currentContract.annotation().linearizedBaseContracts) for (ContractDefinition const* contract: m_mostDerivedContract.annotation().linearizedBaseContracts | ranges::views::reverse)
{
for (VariableDeclaration const* varDecl: contract->stateVariables()) for (VariableDeclaration const* varDecl: contract->stateVariables())
if (varDecl->immutable()) if (varDecl->immutable())
if (!util::contains(m_initializedStateVariables, varDecl)) if (!util::contains(m_initializedStateVariables, varDecl))
@ -263,6 +265,11 @@ void ImmutableValidator::checkAllVariablesInitialized(solidity::langutil::Source
solidity::langutil::SecondarySourceLocation().append("Not initialized: ", varDecl->location()), solidity::langutil::SecondarySourceLocation().append("Not initialized: ", varDecl->location()),
"Construction control flow ends without initializing all immutable state variables." "Construction control flow ends without initializing all immutable state variables."
); );
// Don't check further than the current c'tors contract
if (contract == m_currentConstructorContract)
break;
}
} }
void ImmutableValidator::visitCallableIfNew(Declaration const& _declaration) void ImmutableValidator::visitCallableIfNew(Declaration const& _declaration)

View File

@ -42,7 +42,7 @@ class ImmutableValidator: private ASTConstVisitor
public: public:
ImmutableValidator(langutil::ErrorReporter& _errorReporter, ContractDefinition const& _contractDefinition): ImmutableValidator(langutil::ErrorReporter& _errorReporter, ContractDefinition const& _contractDefinition):
m_currentContract(_contractDefinition), m_mostDerivedContract(_contractDefinition),
m_errorReporter(_errorReporter) m_errorReporter(_errorReporter)
{ } { }
@ -66,7 +66,7 @@ private:
void visitCallableIfNew(Declaration const& _declaration); void visitCallableIfNew(Declaration const& _declaration);
ContractDefinition const& m_currentContract; ContractDefinition const& m_mostDerivedContract;
CallableDeclarationSet m_visitedCallables; CallableDeclarationSet m_visitedCallables;
@ -74,6 +74,7 @@ private:
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;
FunctionDefinition const* m_currentConstructor = nullptr; FunctionDefinition const* m_currentConstructor = nullptr;
ContractDefinition const* m_currentConstructorContract = nullptr;
bool m_inLoop = false; bool m_inLoop = false;
bool m_inBranch = false; bool m_inBranch = false;
bool m_inCreationContext = true; bool m_inCreationContext = true;

View File

@ -530,15 +530,6 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
} }
if (_variable.isConstant()) if (_variable.isConstant())
{ {
if (!varType->isValueType())
{
bool allowed = false;
if (auto arrayType = dynamic_cast<ArrayType const*>(varType))
allowed = arrayType->isByteArray();
if (!allowed)
m_errorReporter.fatalTypeError(9259_error, _variable.location(), "Constants of non-value type not yet implemented.");
}
if (!_variable.value()) if (!_variable.value())
m_errorReporter.typeError(4266_error, _variable.location(), "Uninitialized \"constant\" variable."); m_errorReporter.typeError(4266_error, _variable.location(), "Uninitialized \"constant\" variable.");
else if (!*_variable.value()->annotation().isPure) else if (!*_variable.value()->annotation().isPure)
@ -2122,16 +2113,35 @@ void TypeChecker::typeCheckABIEncodeCallFunction(FunctionCall const& _functionCa
return; return;
} }
if (functionPointerType->kind() != FunctionType::Kind::External) if (
functionPointerType->kind() != FunctionType::Kind::External &&
functionPointerType->kind() != FunctionType::Kind::Declaration
)
{ {
string msg = "Function must be \"public\" or \"external\"."; string msg = "Expected regular external function type, or external view on public function.";
if (functionPointerType->kind() == FunctionType::Kind::Internal)
msg += " Provided internal function.";
else if (functionPointerType->kind() == FunctionType::Kind::DelegateCall)
msg += " Cannot use library functions for abi.encodeCall.";
else if (functionPointerType->kind() == FunctionType::Kind::Creation)
msg += " Provided creation function.";
else
msg += " Cannot use special function.";
SecondarySourceLocation ssl{}; SecondarySourceLocation ssl{};
if (functionPointerType->hasDeclaration()) if (functionPointerType->hasDeclaration())
{ {
ssl.append("Function is declared here:", functionPointerType->declaration().location()); ssl.append("Function is declared here:", functionPointerType->declaration().location());
if (functionPointerType->declaration().scope() == m_currentContract) if (
functionPointerType->declaration().visibility() == Visibility::Public &&
functionPointerType->declaration().scope() == m_currentContract
)
msg += " Did you forget to prefix \"this.\"?"; msg += " Did you forget to prefix \"this.\"?";
else if (contains(
m_currentContract->annotation().linearizedBaseContracts,
functionPointerType->declaration().scope()
) && functionPointerType->declaration().scope() != m_currentContract)
msg += " Functions from base contracts have to be external.";
} }
m_errorReporter.typeError(3509_error, arguments[0]->location(), ssl, msg); m_errorReporter.typeError(3509_error, arguments[0]->location(), ssl, msg);
@ -2866,7 +2876,6 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
strings(1, ""), strings(1, ""),
strings(1, ""), strings(1, ""),
FunctionType::Kind::ObjectCreation, FunctionType::Kind::ObjectCreation,
false,
StateMutability::Pure StateMutability::Pure
); );
_newExpression.annotation().isPure = true; _newExpression.annotation().isPure = true;

View File

@ -511,23 +511,20 @@ ModifierDefinition const& ModifierDefinition::resolveVirtual(
ContractDefinition const* _searchStart ContractDefinition const* _searchStart
) const ) const
{ {
// Super is not possible with modifiers
solAssert(_searchStart == nullptr, "Used super in connection with modifiers."); solAssert(_searchStart == nullptr, "Used super in connection with modifiers.");
// If we are not doing super-lookup and the modifier is not virtual, we can stop here. // The modifier is not virtual, we can stop here.
if (_searchStart == nullptr && !virtualSemantics()) if (!virtualSemantics())
return *this; return *this;
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), ""); solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts) for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
{
if (_searchStart != nullptr && c != _searchStart)
continue;
_searchStart = nullptr;
for (ModifierDefinition const* modifier: c->functionModifiers()) for (ModifierDefinition const* modifier: c->functionModifiers())
if (modifier->name() == name()) if (modifier->name() == name())
return *modifier; return *modifier;
}
solAssert(false, "Virtual modifier " + name() + " not found."); solAssert(false, "Virtual modifier " + name() + " not found.");
return *this; // not reached return *this; // not reached
} }

View File

@ -445,13 +445,18 @@ FunctionType const* TypeProvider::function(
strings const& _parameterTypes, strings const& _parameterTypes,
strings const& _returnParameterTypes, strings const& _returnParameterTypes,
FunctionType::Kind _kind, FunctionType::Kind _kind,
bool _arbitraryParameters, StateMutability _stateMutability,
StateMutability _stateMutability FunctionType::Options _options
) )
{ {
// Can only use this constructor for "arbitraryParameters".
solAssert(!_options.valueSet && !_options.gasSet && !_options.saltSet && !_options.bound);
return createAndGet<FunctionType>( return createAndGet<FunctionType>(
_parameterTypes, _returnParameterTypes, _parameterTypes,
_kind, _arbitraryParameters, _stateMutability _returnParameterTypes,
_kind,
_stateMutability,
std::move(_options)
); );
} }
@ -461,13 +466,9 @@ FunctionType const* TypeProvider::function(
strings _parameterNames, strings _parameterNames,
strings _returnParameterNames, strings _returnParameterNames,
FunctionType::Kind _kind, FunctionType::Kind _kind,
bool _arbitraryParameters,
StateMutability _stateMutability, StateMutability _stateMutability,
Declaration const* _declaration, Declaration const* _declaration,
bool _gasSet, FunctionType::Options _options
bool _valueSet,
bool _bound,
bool _saltSet
) )
{ {
return createAndGet<FunctionType>( return createAndGet<FunctionType>(
@ -476,13 +477,9 @@ FunctionType const* TypeProvider::function(
_parameterNames, _parameterNames,
_returnParameterNames, _returnParameterNames,
_kind, _kind,
_arbitraryParameters,
_stateMutability, _stateMutability,
_declaration, _declaration,
_gasSet, std::move(_options)
_valueSet,
_bound,
_saltSet
); );
} }

View File

@ -149,8 +149,8 @@ public:
strings const& _parameterTypes, strings const& _parameterTypes,
strings const& _returnParameterTypes, strings const& _returnParameterTypes,
FunctionType::Kind _kind = FunctionType::Kind::Internal, FunctionType::Kind _kind = FunctionType::Kind::Internal,
bool _arbitraryParameters = false, StateMutability _stateMutability = StateMutability::NonPayable,
StateMutability _stateMutability = StateMutability::NonPayable FunctionType::Options _options = {}
); );
/// @returns a highly customized FunctionType, use with care. /// @returns a highly customized FunctionType, use with care.
@ -160,13 +160,9 @@ public:
strings _parameterNames = strings{}, strings _parameterNames = strings{},
strings _returnParameterNames = strings{}, strings _returnParameterNames = strings{},
FunctionType::Kind _kind = FunctionType::Kind::Internal, FunctionType::Kind _kind = FunctionType::Kind::Internal,
bool _arbitraryParameters = false,
StateMutability _stateMutability = StateMutability::NonPayable, StateMutability _stateMutability = StateMutability::NonPayable,
Declaration const* _declaration = nullptr, Declaration const* _declaration = nullptr,
bool _gasSet = false, FunctionType::Options _options = {}
bool _valueSet = false,
bool _bound = false,
bool _saltSet = false
); );
/// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does /// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does

View File

@ -470,15 +470,15 @@ MemberList::MemberMap AddressType::nativeMembers(ASTNode const*) const
{"balance", TypeProvider::uint256()}, {"balance", TypeProvider::uint256()},
{"code", TypeProvider::array(DataLocation::Memory)}, {"code", TypeProvider::array(DataLocation::Memory)},
{"codehash", TypeProvider::fixedBytes(32)}, {"codehash", TypeProvider::fixedBytes(32)},
{"call", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, false, StateMutability::Payable)}, {"call", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, StateMutability::Payable)},
{"callcode", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)}, {"callcode", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, StateMutability::Payable)},
{"delegatecall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, false, StateMutability::NonPayable)}, {"delegatecall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, StateMutability::NonPayable)},
{"staticcall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)} {"staticcall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, StateMutability::View)}
}; };
if (m_stateMutability == StateMutability::Payable) if (m_stateMutability == StateMutability::Payable)
{ {
members.emplace_back(MemberList::Member{"send", TypeProvider::function(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send, false, StateMutability::NonPayable)}); members.emplace_back(MemberList::Member{"send", TypeProvider::function(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send, StateMutability::NonPayable)});
members.emplace_back(MemberList::Member{"transfer", TypeProvider::function(strings{"uint"}, strings(), FunctionType::Kind::Transfer, false, StateMutability::NonPayable)}); members.emplace_back(MemberList::Member{"transfer", TypeProvider::function(strings{"uint"}, strings(), FunctionType::Kind::Transfer, StateMutability::NonPayable)});
} }
return members; return members;
} }
@ -2852,7 +2852,6 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c
parameterNames, parameterNames,
strings{""}, strings{""},
Kind::Creation, Kind::Creation,
false,
stateMutability stateMutability
); );
} }
@ -2946,11 +2945,11 @@ string FunctionType::richIdentifier() const
} }
id += "_" + stateMutabilityToString(m_stateMutability); id += "_" + stateMutabilityToString(m_stateMutability);
id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes);
if (m_gasSet) if (gasSet())
id += "gas"; id += "gas";
if (m_valueSet) if (valueSet())
id += "value"; id += "value";
if (m_saltSet) if (saltSet())
id += "salt"; id += "salt";
if (bound()) if (bound())
id += "bound_to" + identifierList(selfType()); id += "bound_to" + identifierList(selfType());
@ -3023,8 +3022,18 @@ TypeResult FunctionType::binaryOperatorResult(Token _operator, Type const* _othe
if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual)) if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual))
return nullptr; return nullptr;
FunctionType const& other = dynamic_cast<FunctionType const&>(*_other); FunctionType const& other = dynamic_cast<FunctionType const&>(*_other);
if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1) if (kind() == Kind::Internal && sizeOnStack() == 1 && other.kind() == Kind::Internal && other.sizeOnStack() == 1)
return commonType(this, _other); return commonType(this, _other);
else if (
kind() == Kind::External &&
sizeOnStack() == 2 &&
!bound() &&
other.kind() == Kind::External &&
other.sizeOnStack() == 2 &&
!other.bound()
)
return commonType(this, _other);
return nullptr; return nullptr;
} }
@ -3098,11 +3107,11 @@ bool FunctionType::nameable() const
{ {
return return
(m_kind == Kind::Internal || m_kind == Kind::External) && (m_kind == Kind::Internal || m_kind == Kind::External) &&
!m_bound && !bound() &&
!m_arbitraryParameters && !takesArbitraryParameters() &&
!m_gasSet && !gasSet() &&
!m_valueSet && !valueSet() &&
!m_saltSet; !saltSet();
} }
vector<tuple<string, Type const*>> FunctionType::makeStackItems() const vector<tuple<string, Type const*>> FunctionType::makeStackItems() const
@ -3144,11 +3153,11 @@ vector<tuple<string, Type const*>> FunctionType::makeStackItems() const
break; break;
} }
if (m_gasSet) if (gasSet())
slots.emplace_back("gas", TypeProvider::uint256()); slots.emplace_back("gas", TypeProvider::uint256());
if (m_valueSet) if (valueSet())
slots.emplace_back("value", TypeProvider::uint256()); slots.emplace_back("value", TypeProvider::uint256());
if (m_saltSet) if (saltSet())
slots.emplace_back("salt", TypeProvider::fixedBytes(32)); slots.emplace_back("salt", TypeProvider::fixedBytes(32));
if (bound()) if (bound())
slots.emplace_back("self", m_parameterTypes.front()); slots.emplace_back("self", m_parameterTypes.front());
@ -3180,13 +3189,13 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const
if (variable && retParamTypes.get().empty()) if (variable && retParamTypes.get().empty())
return FunctionTypePointer(); return FunctionTypePointer();
solAssert(!takesArbitraryParameters());
return TypeProvider::function( return TypeProvider::function(
paramTypes, paramTypes,
retParamTypes, retParamTypes,
m_parameterNames, m_parameterNames,
m_returnParameterNames, m_returnParameterNames,
m_kind, m_kind,
m_arbitraryParameters,
m_stateMutability, m_stateMutability,
m_declaration m_declaration
); );
@ -3241,12 +3250,9 @@ MemberList::MemberMap FunctionType::nativeMembers(ASTNode const* _scope) const
strings(1, ""), strings(1, ""),
strings(1, ""), strings(1, ""),
Kind::SetValue, Kind::SetValue,
false,
StateMutability::Pure, StateMutability::Pure,
nullptr, nullptr,
m_gasSet, Options::fromFunctionType(*this)
m_valueSet,
m_saltSet
) )
); );
} }
@ -3259,12 +3265,9 @@ MemberList::MemberMap FunctionType::nativeMembers(ASTNode const* _scope) const
strings(1, ""), strings(1, ""),
strings(1, ""), strings(1, ""),
Kind::SetGas, Kind::SetGas,
false,
StateMutability::Pure, StateMutability::Pure,
nullptr, nullptr,
m_gasSet, Options::fromFunctionType(*this)
m_valueSet,
m_saltSet
) )
); );
return members; return members;
@ -3292,7 +3295,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ASTNode const* _scope) const
Type const* FunctionType::encodingType() const Type const* FunctionType::encodingType() const
{ {
if (m_gasSet || m_valueSet) if (gasSet() || valueSet())
return nullptr; return nullptr;
// Only external functions can be encoded, internal functions cannot leave code boundaries. // Only external functions can be encoded, internal functions cannot leave code boundaries.
if (m_kind == Kind::External) if (m_kind == Kind::External)
@ -3311,7 +3314,7 @@ TypeResult FunctionType::interfaceType(bool /*_inLibrary*/) const
Type const* FunctionType::mobileType() const Type const* FunctionType::mobileType() const
{ {
if (m_valueSet || m_gasSet || m_saltSet || m_bound) if (valueSet() || gasSet() || saltSet() || bound())
return nullptr; return nullptr;
// return function without parameter names // return function without parameter names
@ -3321,13 +3324,9 @@ Type const* FunctionType::mobileType() const
strings(m_parameterTypes.size()), strings(m_parameterTypes.size()),
strings(m_returnParameterNames.size()), strings(m_returnParameterNames.size()),
m_kind, m_kind,
m_arbitraryParameters,
m_stateMutability, m_stateMutability,
m_declaration, m_declaration,
m_gasSet, Options::fromFunctionType(*this)
m_valueSet,
m_bound,
m_saltSet
); );
} }
@ -3413,7 +3412,7 @@ bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) con
return false; return false;
//@todo this is ugly, but cannot be prevented right now //@todo this is ugly, but cannot be prevented right now
if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet || m_saltSet != _other.m_saltSet) if (gasSet() != _other.gasSet() || valueSet() != _other.valueSet() || saltSet() != _other.saltSet())
return false; return false;
if (bound() != _other.bound()) if (bound() != _other.bound())
@ -3524,41 +3523,39 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
Type const* FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const Type const* FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const
{ {
solAssert(m_kind != Kind::Declaration, ""); solAssert(m_kind != Kind::Declaration, "");
Options options = Options::fromFunctionType(*this);
if (_setGas) options.gasSet = true;
if (_setValue) options.valueSet = true;
if (_setSalt) options.saltSet = true;
return TypeProvider::function( return TypeProvider::function(
m_parameterTypes, m_parameterTypes,
m_returnParameterTypes, m_returnParameterTypes,
m_parameterNames, m_parameterNames,
m_returnParameterNames, m_returnParameterNames,
m_kind, m_kind,
m_arbitraryParameters,
m_stateMutability, m_stateMutability,
m_declaration, m_declaration,
m_gasSet || _setGas, options
m_valueSet || _setValue,
m_saltSet || _setSalt,
m_bound
); );
} }
FunctionTypePointer FunctionType::asBoundFunction() const FunctionTypePointer FunctionType::asBoundFunction() const
{ {
solAssert(!m_parameterTypes.empty(), ""); solAssert(!m_parameterTypes.empty(), "");
solAssert(!m_gasSet, ""); solAssert(!gasSet(), "");
solAssert(!m_valueSet, ""); solAssert(!valueSet(), "");
solAssert(!m_saltSet, ""); solAssert(!saltSet(), "");
Options options = Options::fromFunctionType(*this);
options.bound = true;
return TypeProvider::function( return TypeProvider::function(
m_parameterTypes, m_parameterTypes,
m_returnParameterTypes, m_returnParameterTypes,
m_parameterNames, m_parameterNames,
m_returnParameterNames, m_returnParameterNames,
m_kind, m_kind,
m_arbitraryParameters,
m_stateMutability, m_stateMutability,
m_declaration, m_declaration,
m_gasSet, options
m_valueSet,
m_saltSet,
true
); );
} }
@ -3596,13 +3593,9 @@ FunctionTypePointer FunctionType::asExternallyCallableFunction(bool _inLibrary)
m_parameterNames, m_parameterNames,
m_returnParameterNames, m_returnParameterNames,
kind, kind,
m_arbitraryParameters,
m_stateMutability, m_stateMutability,
m_declaration, m_declaration,
m_gasSet, Options::fromFunctionType(*this)
m_valueSet,
m_saltSet,
m_bound
); );
} }
@ -3807,7 +3800,6 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
strings{string{}}, strings{string{}},
strings{string{}}, strings{string{}},
FunctionType::Kind::Wrap, FunctionType::Kind::Wrap,
false, /*_arbitraryParameters */
StateMutability::Pure StateMutability::Pure
) )
); );
@ -3819,7 +3811,6 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
strings{string{}}, strings{string{}},
strings{string{}}, strings{string{}},
FunctionType::Kind::Unwrap, FunctionType::Kind::Unwrap,
false, /* _arbitraryParameters */
StateMutability::Pure StateMutability::Pure
) )
); );
@ -3834,8 +3825,9 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
strings{}, strings{},
strings{string()}, strings{string()},
FunctionType::Kind::BytesConcat, FunctionType::Kind::BytesConcat,
/* _arbitraryParameters */ true, StateMutability::Pure,
StateMutability::Pure nullptr,
FunctionType::Options::withArbitraryParameters()
)); ));
return members; return members;
@ -3958,7 +3950,7 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
return MemberList::MemberMap({ return MemberList::MemberMap({
{"coinbase", TypeProvider::payableAddress()}, {"coinbase", TypeProvider::payableAddress()},
{"timestamp", TypeProvider::uint256()}, {"timestamp", TypeProvider::uint256()},
{"blockhash", TypeProvider::function(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)}, {"blockhash", TypeProvider::function(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, StateMutability::View)},
{"difficulty", TypeProvider::uint256()}, {"difficulty", TypeProvider::uint256()},
{"number", TypeProvider::uint256()}, {"number", TypeProvider::uint256()},
{"gaslimit", TypeProvider::uint256()}, {"gaslimit", TypeProvider::uint256()},
@ -3986,8 +3978,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
strings{}, strings{},
strings{1, ""}, strings{1, ""},
FunctionType::Kind::ABIEncode, FunctionType::Kind::ABIEncode,
true, StateMutability::Pure,
StateMutability::Pure nullptr,
FunctionType::Options::withArbitraryParameters()
)}, )},
{"encodePacked", TypeProvider::function( {"encodePacked", TypeProvider::function(
TypePointers{}, TypePointers{},
@ -3995,8 +3988,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
strings{}, strings{},
strings{1, ""}, strings{1, ""},
FunctionType::Kind::ABIEncodePacked, FunctionType::Kind::ABIEncodePacked,
true, StateMutability::Pure,
StateMutability::Pure nullptr,
FunctionType::Options::withArbitraryParameters()
)}, )},
{"encodeWithSelector", TypeProvider::function( {"encodeWithSelector", TypeProvider::function(
TypePointers{TypeProvider::fixedBytes(4)}, TypePointers{TypeProvider::fixedBytes(4)},
@ -4004,8 +3998,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
strings{1, ""}, strings{1, ""},
strings{1, ""}, strings{1, ""},
FunctionType::Kind::ABIEncodeWithSelector, FunctionType::Kind::ABIEncodeWithSelector,
true, StateMutability::Pure,
StateMutability::Pure nullptr,
FunctionType::Options::withArbitraryParameters()
)}, )},
{"encodeCall", TypeProvider::function( {"encodeCall", TypeProvider::function(
TypePointers{}, TypePointers{},
@ -4013,8 +4008,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
strings{}, strings{},
strings{1, ""}, strings{1, ""},
FunctionType::Kind::ABIEncodeCall, FunctionType::Kind::ABIEncodeCall,
true, StateMutability::Pure,
StateMutability::Pure nullptr,
FunctionType::Options::withArbitraryParameters()
)}, )},
{"encodeWithSignature", TypeProvider::function( {"encodeWithSignature", TypeProvider::function(
TypePointers{TypeProvider::array(DataLocation::Memory, true)}, TypePointers{TypeProvider::array(DataLocation::Memory, true)},
@ -4022,8 +4018,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
strings{1, ""}, strings{1, ""},
strings{1, ""}, strings{1, ""},
FunctionType::Kind::ABIEncodeWithSignature, FunctionType::Kind::ABIEncodeWithSignature,
true, StateMutability::Pure,
StateMutability::Pure nullptr,
FunctionType::Options::withArbitraryParameters()
)}, )},
{"decode", TypeProvider::function( {"decode", TypeProvider::function(
TypePointers(), TypePointers(),
@ -4031,8 +4028,9 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
strings{}, strings{},
strings{}, strings{},
FunctionType::Kind::ABIDecode, FunctionType::Kind::ABIDecode,
true, StateMutability::Pure,
StateMutability::Pure nullptr,
FunctionType::Options::withArbitraryParameters()
)} )}
}); });
case Kind::MetaType: case Kind::MetaType:

View File

@ -1110,11 +1110,7 @@ public:
u256 storageSize() const override { return underlyingType().storageSize(); } u256 storageSize() const override { return underlyingType().storageSize(); }
unsigned storageBytes() const override { return underlyingType().storageBytes(); } unsigned storageBytes() const override { return underlyingType().storageBytes(); }
bool isValueType() const override bool isValueType() const override { return true; }
{
solAssert(underlyingType().isValueType(), "");
return true;
}
bool nameable() const override bool nameable() const override
{ {
solAssert(underlyingType().nameable(), ""); solAssert(underlyingType().nameable(), "");
@ -1247,6 +1243,38 @@ public:
/// Cannot be called. /// Cannot be called.
Declaration, Declaration,
}; };
struct Options
{
/// true iff the function takes an arbitrary number of arguments of arbitrary types
bool arbitraryParameters = false;
/// true iff the gas value to be used is on the stack
bool gasSet = false;
/// true iff the value to be sent is on the stack
bool valueSet = false;
/// iff the salt value (for create2) to be used is on the stack
bool saltSet = false;
/// true iff the function is called as arg1.fun(arg2, ..., argn).
/// This is achieved through the "using for" directive.
bool bound = false;
static Options withArbitraryParameters()
{
Options result;
result.arbitraryParameters = true;
return result;
}
static Options fromFunctionType(FunctionType const& _type)
{
Options result;
result.arbitraryParameters = _type.takesArbitraryParameters();
result.gasSet = _type.gasSet();
result.valueSet = _type.valueSet();
result.saltSet = _type.saltSet();
result.bound = _type.bound();
return result;
}
};
/// Creates the type of a function. /// Creates the type of a function.
/// @arg _kind must be Kind::Internal, Kind::External or Kind::Declaration. /// @arg _kind must be Kind::Internal, Kind::External or Kind::Declaration.
@ -1263,18 +1291,21 @@ public:
strings const& _parameterTypes, strings const& _parameterTypes,
strings const& _returnParameterTypes, strings const& _returnParameterTypes,
Kind _kind, Kind _kind,
bool _arbitraryParameters = false, StateMutability _stateMutability = StateMutability::NonPayable,
StateMutability _stateMutability = StateMutability::NonPayable Options _options = Options{false, false, false, false, false}
): FunctionType( ): FunctionType(
parseElementaryTypeVector(_parameterTypes), parseElementaryTypeVector(_parameterTypes),
parseElementaryTypeVector(_returnParameterTypes), parseElementaryTypeVector(_returnParameterTypes),
strings(_parameterTypes.size(), ""), strings(_parameterTypes.size(), ""),
strings(_returnParameterTypes.size(), ""), strings(_returnParameterTypes.size(), ""),
_kind, _kind,
_arbitraryParameters, _stateMutability,
_stateMutability nullptr,
std::move(_options)
) )
{ {
// In this constructor, only the "arbitrary Parameters" option should be used.
solAssert(!bound() && !gasSet() && !valueSet() && !saltSet());
} }
/// Detailed constructor, use with care. /// Detailed constructor, use with care.
@ -1284,13 +1315,9 @@ public:
strings _parameterNames = strings(), strings _parameterNames = strings(),
strings _returnParameterNames = strings(), strings _returnParameterNames = strings(),
Kind _kind = Kind::Internal, Kind _kind = Kind::Internal,
bool _arbitraryParameters = false,
StateMutability _stateMutability = StateMutability::NonPayable, StateMutability _stateMutability = StateMutability::NonPayable,
Declaration const* _declaration = nullptr, Declaration const* _declaration = nullptr,
bool _gasSet = false, Options _options = Options{false, false, false, false, false}
bool _valueSet = false,
bool _saltSet = false,
bool _bound = false
): ):
m_parameterTypes(std::move(_parameterTypes)), m_parameterTypes(std::move(_parameterTypes)),
m_returnParameterTypes(std::move(_returnParameterTypes)), m_returnParameterTypes(std::move(_returnParameterTypes)),
@ -1298,12 +1325,8 @@ public:
m_returnParameterNames(std::move(_returnParameterNames)), m_returnParameterNames(std::move(_returnParameterNames)),
m_kind(_kind), m_kind(_kind),
m_stateMutability(_stateMutability), m_stateMutability(_stateMutability),
m_arbitraryParameters(_arbitraryParameters),
m_gasSet(_gasSet),
m_valueSet(_valueSet),
m_bound(_bound),
m_declaration(_declaration), m_declaration(_declaration),
m_saltSet(_saltSet) m_options(std::move(_options))
{ {
solAssert( solAssert(
m_parameterNames.size() == m_parameterTypes.size(), m_parameterNames.size() == m_parameterTypes.size(),
@ -1314,7 +1337,7 @@ public:
"Return parameter names list must match return parameter types list!" "Return parameter names list must match return parameter types list!"
); );
solAssert( solAssert(
!m_bound || !m_parameterTypes.empty(), !bound() || !m_parameterTypes.empty(),
"Attempted construction of bound function without self type" "Attempted construction of bound function without self type"
); );
} }
@ -1408,7 +1431,7 @@ public:
/// The only functions that do not pad are hash functions, the low-level call functions /// The only functions that do not pad are hash functions, the low-level call functions
/// and abi.encodePacked. /// and abi.encodePacked.
bool padArguments() const; bool padArguments() const;
bool takesArbitraryParameters() const { return m_arbitraryParameters; } bool takesArbitraryParameters() const { return m_options.arbitraryParameters; }
/// true iff the function takes a single bytes parameter and it is passed on without padding. /// true iff the function takes a single bytes parameter and it is passed on without padding.
bool takesSinglePackedBytesParameter() const bool takesSinglePackedBytesParameter() const
{ {
@ -1427,10 +1450,10 @@ public:
} }
} }
bool gasSet() const { return m_gasSet; } bool gasSet() const { return m_options.gasSet; }
bool valueSet() const { return m_valueSet; } bool valueSet() const { return m_options.valueSet; }
bool saltSet() const { return m_saltSet; } bool saltSet() const { return m_options.saltSet; }
bool bound() const { return m_bound; } bool bound() const { return m_options.bound; }
/// @returns a copy of this type, where gas or value are set manually. This will never set one /// @returns a copy of this type, where gas or value are set manually. This will never set one
/// of the parameters to false. /// of the parameters to false.
@ -1458,15 +1481,8 @@ private:
std::vector<std::string> m_returnParameterNames; std::vector<std::string> m_returnParameterNames;
Kind const m_kind; Kind const m_kind;
StateMutability m_stateMutability = StateMutability::NonPayable; StateMutability m_stateMutability = StateMutability::NonPayable;
/// true if the function takes an arbitrary number of arguments of arbitrary types
bool const m_arbitraryParameters = false;
bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack
bool const m_valueSet = false; ///< true iff the value to be sent is on the stack
/// true iff the function is called as arg1.fun(arg2, ..., argn).
/// This is achieved through the "using for" directive.
bool const m_bound = false;
Declaration const* m_declaration = nullptr; Declaration const* m_declaration = nullptr;
bool m_saltSet = false; ///< true iff the salt value to be used is on the stack Options const m_options;
}; };
/** /**

View File

@ -54,7 +54,7 @@ static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPoi
void CompilerUtils::initialiseFreeMemoryPointer() void CompilerUtils::initialiseFreeMemoryPointer()
{ {
size_t reservedMemory = m_context.reservedMemory(); size_t reservedMemory = m_context.reservedMemory();
solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63, ""); solAssert(bigint(generalPurposeMemoryStart) + bigint(reservedMemory) < bigint(1) << 63);
m_context << (u256(generalPurposeMemoryStart) + reservedMemory); m_context << (u256(generalPurposeMemoryStart) + reservedMemory);
storeFreeMemoryPointer(); storeFreeMemoryPointer();
} }
@ -92,7 +92,7 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer()
void CompilerUtils::revertWithStringData(Type const& _argumentType) void CompilerUtils::revertWithStringData(Type const& _argumentType)
{ {
solAssert(_argumentType.isImplicitlyConvertibleTo(*TypeProvider::fromElementaryTypeName("string memory")), ""); solAssert(_argumentType.isImplicitlyConvertibleTo(*TypeProvider::fromElementaryTypeName("string memory")));
fetchFreeMemoryPointer(); fetchFreeMemoryPointer();
m_context << util::selectorFromSignature("Error(string)"); m_context << util::selectorFromSignature("Error(string)");
m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << Instruction::DUP2 << Instruction::MSTORE;
@ -173,9 +173,9 @@ void CompilerUtils::loadFromMemoryDynamic(
if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
{ {
solAssert(!arrayType->isDynamicallySized(), ""); solAssert(!arrayType->isDynamicallySized());
solAssert(!_fromCalldata, ""); solAssert(!_fromCalldata);
solAssert(_padToWordBoundaries, ""); solAssert(_padToWordBoundaries);
if (_keepUpdatedMemoryOffset) if (_keepUpdatedMemoryOffset)
m_context << arrayType->memoryDataSize() << Instruction::ADD; m_context << arrayType->memoryDataSize() << Instruction::ADD;
} }
@ -251,7 +251,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
// Use the new Yul-based decoding function // Use the new Yul-based decoding function
auto stackHeightBefore = m_context.stackHeight(); auto stackHeightBefore = m_context.stackHeight();
abiDecodeV2(_typeParameters, _fromMemory); abiDecodeV2(_typeParameters, _fromMemory);
solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2, ""); solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2);
return; return;
} }
@ -290,7 +290,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
); );
// @todo If base type is an array or struct, it is still calldata-style encoded, so // @todo If base type is an array or struct, it is still calldata-style encoded, so
// we would have to convert it like below. // we would have to convert it like below.
solAssert(arrayType.location() == DataLocation::Memory, ""); solAssert(arrayType.location() == DataLocation::Memory);
if (arrayType.isDynamicallySized()) if (arrayType.isDynamicallySized())
{ {
// compute data pointer // compute data pointer
@ -430,7 +430,7 @@ void CompilerUtils::encodeToMemory(
// stack: <v1> <v2> ... <vn> <mem> // stack: <v1> <v2> ... <vn> <mem>
bool const encoderV2 = m_context.useABICoderV2(); bool const encoderV2 = m_context.useABICoderV2();
TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes;
solAssert(targetTypes.size() == _givenTypes.size(), ""); solAssert(targetTypes.size() == _givenTypes.size());
for (Type const*& t: targetTypes) for (Type const*& t: targetTypes)
{ {
Type const* tEncoding = t->fullEncodingType(_encodeAsLibraryTypes, encoderV2, !_padToWordBoundaries); Type const* tEncoding = t->fullEncodingType(_encodeAsLibraryTypes, encoderV2, !_padToWordBoundaries);
@ -449,7 +449,7 @@ void CompilerUtils::encodeToMemory(
); );
auto stackHeightBefore = m_context.stackHeight(); auto stackHeightBefore = m_context.stackHeight();
abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes, _padToWordBoundaries); abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes, _padToWordBoundaries);
solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), ""); solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes));
return; return;
} }
@ -489,8 +489,8 @@ void CompilerUtils::encodeToMemory(
{ {
// special case: convert storage reference type to value type - this is only // special case: convert storage reference type to value type - this is only
// possible for library calls where we just forward the storage reference // possible for library calls where we just forward the storage reference
solAssert(_encodeAsLibraryTypes, ""); solAssert(_encodeAsLibraryTypes);
solAssert(_givenTypes[i]->sizeOnStack() == 1, ""); solAssert(_givenTypes[i]->sizeOnStack() == 1);
} }
else if ( else if (
_givenTypes[i]->dataStoredIn(DataLocation::Storage) || _givenTypes[i]->dataStoredIn(DataLocation::Storage) ||
@ -638,7 +638,7 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
{ {
if (_type.baseType()->hasSimpleZeroValueInMemory()) if (_type.baseType()->hasSimpleZeroValueInMemory())
{ {
solAssert(_type.baseType()->isValueType(), ""); solAssert(_type.baseType()->isValueType());
Whiskers templ(R"({ Whiskers templ(R"({
let size := mul(length, <element_size>) let size := mul(length, <element_size>)
// cheap way of zero-initializing a memory range // cheap way of zero-initializing a memory range
@ -774,9 +774,9 @@ void CompilerUtils::convertType(
if (stackTypeCategory == Type::Category::UserDefinedValueType) if (stackTypeCategory == Type::Category::UserDefinedValueType)
{ {
solAssert(_cleanupNeeded, ""); solAssert(_cleanupNeeded);
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_typeOnStack); auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_typeOnStack);
solAssert(_typeOnStack == _targetType || _targetType == userDefined.underlyingType(), ""); solAssert(_typeOnStack == _targetType || _targetType == userDefined.underlyingType());
return convertType( return convertType(
userDefined.underlyingType(), userDefined.underlyingType(),
_targetType, _targetType,
@ -787,9 +787,9 @@ void CompilerUtils::convertType(
} }
if (targetTypeCategory == Type::Category::UserDefinedValueType) if (targetTypeCategory == Type::Category::UserDefinedValueType)
{ {
solAssert(_cleanupNeeded, ""); solAssert(_cleanupNeeded);
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_targetType); auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_targetType);
solAssert(_typeOnStack.isImplicitlyConvertibleTo(userDefined.underlyingType()), ""); solAssert(_typeOnStack.isImplicitlyConvertibleTo(userDefined.underlyingType()));
return convertType( return convertType(
_typeOnStack, _typeOnStack,
userDefined.underlyingType(), userDefined.underlyingType(),
@ -829,7 +829,7 @@ void CompilerUtils::convertType(
} }
else if (targetTypeCategory == Type::Category::Address) else if (targetTypeCategory == Type::Category::Address)
{ {
solAssert(typeOnStack.numBytes() * 8 == 160, ""); solAssert(typeOnStack.numBytes() * 8 == 160);
rightShiftNumberOnStack(256 - 160); rightShiftNumberOnStack(256 - 160);
} }
else else
@ -849,7 +849,7 @@ void CompilerUtils::convertType(
break; break;
} }
case Type::Category::Enum: case Type::Category::Enum:
solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, ""); solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer);
if (enumOverflowCheckPending) if (enumOverflowCheckPending)
{ {
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack); EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack);
@ -885,13 +885,13 @@ void CompilerUtils::convertType(
cleanHigherOrderBits(*typeOnStack); cleanHigherOrderBits(*typeOnStack);
} }
else if (stackTypeCategory == Type::Category::Address) else if (stackTypeCategory == Type::Category::Address)
solAssert(targetBytesType.numBytes() * 8 == 160, ""); solAssert(targetBytesType.numBytes() * 8 == 160);
leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8); leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8);
} }
else if (targetTypeCategory == Type::Category::Enum) else if (targetTypeCategory == Type::Category::Enum)
{ {
solAssert(stackTypeCategory != Type::Category::Address, "Invalid conversion to EnumType requested."); solAssert(stackTypeCategory != Type::Category::Address, "Invalid conversion to EnumType requested.");
solAssert(_typeOnStack.mobileType(), ""); solAssert(_typeOnStack.mobileType());
// just clean // just clean
convertType(_typeOnStack, *_typeOnStack.mobileType(), true); convertType(_typeOnStack, *_typeOnStack.mobileType(), true);
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType); EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType);
@ -964,13 +964,13 @@ void CompilerUtils::convertType(
if (targetTypeCategory == Type::Category::FixedBytes) if (targetTypeCategory == Type::Category::FixedBytes)
{ {
unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_targetType).numBytes(); unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_targetType).numBytes();
solAssert(data.size() <= 32, ""); solAssert(data.size() <= 32);
m_context << (u256(h256(data, h256::AlignLeft)) & (~(u256(-1) >> (8 * numBytes)))); m_context << (u256(h256(data, h256::AlignLeft)) & (~(u256(-1) >> (8 * numBytes))));
} }
else if (targetTypeCategory == Type::Category::Array) else if (targetTypeCategory == Type::Category::Array)
{ {
auto const& arrayType = dynamic_cast<ArrayType const&>(_targetType); auto const& arrayType = dynamic_cast<ArrayType const&>(_targetType);
solAssert(arrayType.isByteArray(), ""); solAssert(arrayType.isByteArray());
size_t storageSize = 32 + ((data.size() + 31) / 32) * 32; size_t storageSize = 32 + ((data.size() + 31) / 32) * 32;
allocateMemory(storageSize); allocateMemory(storageSize);
// stack: mempos // stack: mempos
@ -995,10 +995,10 @@ void CompilerUtils::convertType(
typeOnStack.isByteArray() && !typeOnStack.isString(), typeOnStack.isByteArray() && !typeOnStack.isString(),
"Array types other than bytes not convertible to bytesNN." "Array types other than bytes not convertible to bytesNN."
); );
solAssert(typeOnStack.isDynamicallySized(), ""); solAssert(typeOnStack.isDynamicallySized());
bool fromCalldata = typeOnStack.dataStoredIn(DataLocation::CallData); bool fromCalldata = typeOnStack.dataStoredIn(DataLocation::CallData);
solAssert(typeOnStack.sizeOnStack() == (fromCalldata ? 2 : 1), ""); solAssert(typeOnStack.sizeOnStack() == (fromCalldata ? 2 : 1));
if (fromCalldata) if (fromCalldata)
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
@ -1012,7 +1012,7 @@ void CompilerUtils::convertType(
); );
break; break;
} }
solAssert(targetTypeCategory == stackTypeCategory, ""); solAssert(targetTypeCategory == stackTypeCategory);
auto const& targetType = dynamic_cast<ArrayType const&>(_targetType); auto const& targetType = dynamic_cast<ArrayType const&>(_targetType);
switch (targetType.location()) switch (targetType.location())
{ {
@ -1034,9 +1034,9 @@ void CompilerUtils::convertType(
typeOnStack.baseType()->isDynamicallyEncoded() typeOnStack.baseType()->isDynamicallyEncoded()
) )
{ {
solAssert(m_context.useABICoderV2(), ""); solAssert(m_context.useABICoderV2());
// stack: offset length(optional in case of dynamically sized array) // stack: offset length(optional in case of dynamically sized array)
solAssert(typeOnStack.sizeOnStack() == (typeOnStack.isDynamicallySized() ? 2 : 1), ""); solAssert(typeOnStack.sizeOnStack() == (typeOnStack.isDynamicallySized() ? 2 : 1));
if (typeOnStack.isDynamicallySized()) if (typeOnStack.isDynamicallySized())
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
@ -1122,9 +1122,9 @@ void CompilerUtils::convertType(
typeOnStack.arrayType().isByteArray() && !typeOnStack.arrayType().isString(), typeOnStack.arrayType().isByteArray() && !typeOnStack.arrayType().isString(),
"Array types other than bytes not convertible to bytesNN." "Array types other than bytes not convertible to bytesNN."
); );
solAssert(typeOnStack.isDynamicallySized(), ""); solAssert(typeOnStack.isDynamicallySized());
solAssert(typeOnStack.dataStoredIn(DataLocation::CallData), ""); solAssert(typeOnStack.dataStoredIn(DataLocation::CallData));
solAssert(typeOnStack.sizeOnStack() == 2, ""); solAssert(typeOnStack.sizeOnStack() == 2);
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
m_context.callYulFunction( m_context.callYulFunction(
@ -1138,14 +1138,16 @@ void CompilerUtils::convertType(
break; break;
} }
solAssert(_targetType.category() == Type::Category::Array, ""); solAssert(_targetType.category() == Type::Category::Array);
auto const& targetArrayType = dynamic_cast<ArrayType const&>(_targetType); auto const& targetArrayType = dynamic_cast<ArrayType const&>(_targetType);
solAssert(typeOnStack.arrayType().isImplicitlyConvertibleTo(targetArrayType), ""); solAssert(
typeOnStack.arrayType().isImplicitlyConvertibleTo(targetArrayType) ||
(typeOnStack.arrayType().isByteArray() && targetArrayType.isByteArray())
);
solAssert( solAssert(
typeOnStack.arrayType().dataStoredIn(DataLocation::CallData) && typeOnStack.arrayType().dataStoredIn(DataLocation::CallData) &&
typeOnStack.arrayType().isDynamicallySized() && typeOnStack.arrayType().isDynamicallySized() &&
!typeOnStack.arrayType().baseType()->isDynamicallyEncoded(), !typeOnStack.arrayType().baseType()->isDynamicallyEncoded()
""
); );
if (!_targetType.dataStoredIn(DataLocation::CallData)) if (!_targetType.dataStoredIn(DataLocation::CallData))
return convertType(typeOnStack.arrayType(), _targetType); return convertType(typeOnStack.arrayType(), _targetType);
@ -1153,7 +1155,7 @@ void CompilerUtils::convertType(
} }
case Type::Category::Struct: case Type::Category::Struct:
{ {
solAssert(targetTypeCategory == stackTypeCategory, ""); solAssert(targetTypeCategory == stackTypeCategory);
auto& targetType = dynamic_cast<StructType const&>(_targetType); auto& targetType = dynamic_cast<StructType const&>(_targetType);
auto& typeOnStack = dynamic_cast<StructType const&>(_typeOnStack); auto& typeOnStack = dynamic_cast<StructType const&>(_typeOnStack);
switch (targetType.location()) switch (targetType.location())
@ -1182,7 +1184,7 @@ void CompilerUtils::convertType(
// stack: <memory ptr> <source ref> <memory ptr> // stack: <memory ptr> <source ref> <memory ptr>
for (auto const& member: typeOnStack->members(nullptr)) for (auto const& member: typeOnStack->members(nullptr))
{ {
solAssert(!member.type->containsNestedMapping(), ""); solAssert(!member.type->containsNestedMapping());
pair<u256, unsigned> const& offsets = typeOnStack->storageOffsetsOfMember(member.name); pair<u256, unsigned> const& offsets = typeOnStack->storageOffsetsOfMember(member.name);
_context << offsets.first << Instruction::DUP3 << Instruction::ADD; _context << offsets.first << Instruction::DUP3 << Instruction::ADD;
_context << u256(offsets.second); _context << u256(offsets.second);
@ -1209,7 +1211,7 @@ void CompilerUtils::convertType(
{ {
if (typeOnStack.isDynamicallyEncoded()) if (typeOnStack.isDynamicallyEncoded())
{ {
solAssert(m_context.useABICoderV2(), ""); solAssert(m_context.useABICoderV2());
m_context.callYulFunction( m_context.callYulFunction(
m_context.utilFunctions().conversionFunction(typeOnStack, targetType), m_context.utilFunctions().conversionFunction(typeOnStack, targetType),
1, 1,
@ -1231,7 +1233,7 @@ void CompilerUtils::convertType(
} }
break; break;
case DataLocation::CallData: case DataLocation::CallData:
solAssert(_typeOnStack == _targetType, ""); solAssert(_typeOnStack == _targetType);
// nothing to do // nothing to do
break; break;
} }
@ -1241,7 +1243,7 @@ void CompilerUtils::convertType(
{ {
TupleType const& sourceTuple = dynamic_cast<TupleType const&>(_typeOnStack); TupleType const& sourceTuple = dynamic_cast<TupleType const&>(_typeOnStack);
TupleType const& targetTuple = dynamic_cast<TupleType const&>(_targetType); TupleType const& targetTuple = dynamic_cast<TupleType const&>(_targetType);
solAssert(targetTuple.components().size() == sourceTuple.components().size(), ""); solAssert(targetTuple.components().size() == sourceTuple.components().size());
unsigned depth = sourceTuple.sizeOnStack(); unsigned depth = sourceTuple.sizeOnStack();
for (size_t i = 0; i < sourceTuple.components().size(); ++i) for (size_t i = 0; i < sourceTuple.components().size(); ++i)
{ {
@ -1249,7 +1251,7 @@ void CompilerUtils::convertType(
Type const* targetType = targetTuple.components()[i]; Type const* targetType = targetTuple.components()[i];
if (!sourceType) if (!sourceType)
{ {
solAssert(!targetType, ""); solAssert(!targetType);
continue; continue;
} }
unsigned sourceSize = sourceType->sizeOnStack(); unsigned sourceSize = sourceType->sizeOnStack();
@ -1291,7 +1293,7 @@ void CompilerUtils::convertType(
break; break;
default: default:
// we used to allow conversions from function to address // we used to allow conversions from function to address
solAssert(!(stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address), ""); solAssert(!(stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address));
if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Function) if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Function)
{ {
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack); FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
@ -1348,14 +1350,14 @@ void CompilerUtils::pushZeroValue(Type const& _type)
} }
if (referenceType->location() == DataLocation::CallData) if (referenceType->location() == DataLocation::CallData)
{ {
solAssert(referenceType->sizeOnStack() == 1 || referenceType->sizeOnStack() == 2, ""); solAssert(referenceType->sizeOnStack() == 1 || referenceType->sizeOnStack() == 2);
m_context << Instruction::CALLDATASIZE; m_context << Instruction::CALLDATASIZE;
if (referenceType->sizeOnStack() == 2) if (referenceType->sizeOnStack() == 2)
m_context << 0; m_context << 0;
return; return;
} }
solAssert(referenceType->location() == DataLocation::Memory, ""); solAssert(referenceType->location() == DataLocation::Memory);
if (auto arrayType = dynamic_cast<ArrayType const*>(&_type)) if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
if (arrayType->isDynamicallySized()) if (arrayType->isDynamicallySized())
{ {
@ -1383,7 +1385,7 @@ void CompilerUtils::pushZeroValue(Type const& _type)
} }
else if (auto arrayType = dynamic_cast<ArrayType const*>(type)) else if (auto arrayType = dynamic_cast<ArrayType const*>(type))
{ {
solAssert(!arrayType->isDynamicallySized(), ""); solAssert(!arrayType->isDynamicallySized());
if (arrayType->length() > 0) if (arrayType->length() > 0)
{ {
_context << arrayType->length() << Instruction::SWAP1; _context << arrayType->length() << Instruction::SWAP1;
@ -1483,7 +1485,7 @@ void CompilerUtils::popStackSlots(size_t _amount)
void CompilerUtils::popAndJump(unsigned _toHeight, evmasm::AssemblyItem const& _jumpTo) void CompilerUtils::popAndJump(unsigned _toHeight, evmasm::AssemblyItem const& _jumpTo)
{ {
solAssert(m_context.stackHeight() >= _toHeight, ""); solAssert(m_context.stackHeight() >= _toHeight);
unsigned amount = m_context.stackHeight() - _toHeight; unsigned amount = m_context.stackHeight() - _toHeight;
popStackSlots(amount); popStackSlots(amount);
m_context.appendJumpTo(_jumpTo); m_context.appendJumpTo(_jumpTo);
@ -1551,7 +1553,7 @@ void CompilerUtils::storeStringData(bytesConstRef _data)
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords)
{ {
solAssert(_type.isValueType(), ""); solAssert(_type.isValueType());
Type const* type = &_type; Type const* type = &_type;
if (auto const* userDefined = dynamic_cast<UserDefinedValueType const*>(type)) if (auto const* userDefined = dynamic_cast<UserDefinedValueType const*>(type))
type = &userDefined->underlyingType(); type = &userDefined->underlyingType();
@ -1603,7 +1605,7 @@ void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack)
void CompilerUtils::leftShiftNumberOnStack(unsigned _bits) void CompilerUtils::leftShiftNumberOnStack(unsigned _bits)
{ {
solAssert(_bits < 256, ""); solAssert(_bits < 256);
if (m_context.evmVersion().hasBitwiseShifting()) if (m_context.evmVersion().hasBitwiseShifting())
m_context << _bits << Instruction::SHL; m_context << _bits << Instruction::SHL;
else else
@ -1612,7 +1614,7 @@ void CompilerUtils::leftShiftNumberOnStack(unsigned _bits)
void CompilerUtils::rightShiftNumberOnStack(unsigned _bits) void CompilerUtils::rightShiftNumberOnStack(unsigned _bits)
{ {
solAssert(_bits < 256, ""); solAssert(_bits < 256);
// NOTE: If we add signed right shift, SAR rounds differently than SDIV // NOTE: If we add signed right shift, SAR rounds differently than SDIV
if (m_context.evmVersion().hasBitwiseShifting()) if (m_context.evmVersion().hasBitwiseShifting())
m_context << _bits << Instruction::SHR; m_context << _bits << Instruction::SHR;
@ -1627,7 +1629,7 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords,
"Memory store of types with stack size != 1 not allowed (Type: " + _type.toString(true) + ")." "Memory store of types with stack size != 1 not allowed (Type: " + _type.toString(true) + ")."
); );
solAssert(!_type.isDynamicallyEncoded(), ""); solAssert(!_type.isDynamicallyEncoded());
unsigned numBytes = _type.calldataEncodedSize(_padToWords); unsigned numBytes = _type.calldataEncodedSize(_padToWords);

View File

@ -780,6 +780,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
break; break;
case FunctionType::Kind::Send: case FunctionType::Kind::Send:
case FunctionType::Kind::Transfer: case FunctionType::Kind::Transfer:
{
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
// Provide the gas stipend manually at first because we may send zero ether. // Provide the gas stipend manually at first because we may send zero ether.
// Will be zeroed if we send more than zero ether. // Will be zeroed if we send more than zero ether.
@ -788,6 +789,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// gas <- gas * !value // gas <- gas * !value
m_context << Instruction::SWAP1 << Instruction::DUP2; m_context << Instruction::SWAP1 << Instruction::DUP2;
m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1;
FunctionType::Options callOptions;
callOptions.valueSet = true;
callOptions.gasSet = true;
appendExternalFunctionCall( appendExternalFunctionCall(
FunctionType( FunctionType(
TypePointers{}, TypePointers{},
@ -795,11 +799,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
strings(), strings(),
strings(), strings(),
FunctionType::Kind::BareCall, FunctionType::Kind::BareCall,
false,
StateMutability::NonPayable, StateMutability::NonPayable,
nullptr, nullptr,
true, callOptions
true
), ),
{}, {},
false false
@ -812,6 +814,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context.appendConditionalRevert(true); m_context.appendConditionalRevert(true);
} }
break; break;
}
case FunctionType::Kind::Selfdestruct: case FunctionType::Kind::Selfdestruct:
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true); acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true);
m_context << Instruction::SELFDESTRUCT; m_context << Instruction::SELFDESTRUCT;
@ -1255,7 +1258,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
auto const functionPtr = dynamic_cast<FunctionTypePointer>(arguments[0]->annotation().type); auto const functionPtr = dynamic_cast<FunctionTypePointer>(arguments[0]->annotation().type);
solAssert(functionPtr); solAssert(functionPtr);
solAssert(functionPtr->sizeOnStack() == 2);
// Account for tuples with one component which become that component // Account for tuples with one component which become that component
if (auto const tupleType = dynamic_cast<TupleType const*>(arguments[1]->annotation().type)) if (auto const tupleType = dynamic_cast<TupleType const*>(arguments[1]->annotation().type))
@ -1330,9 +1332,20 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
} }
else if (function.kind() == FunctionType::Kind::ABIEncodeCall) else if (function.kind() == FunctionType::Kind::ABIEncodeCall)
{ {
// stack: <memory pointer> <functionPointer> auto const& funType = dynamic_cast<FunctionType const&>(*selectorType);
// Extract selector from the stack if (funType.kind() == FunctionType::Kind::Declaration)
m_context << Instruction::SWAP1 << Instruction::POP; {
solAssert(funType.hasDeclaration());
solAssert(selectorType->sizeOnStack() == 0);
m_context << funType.externalIdentifier();
}
else
{
solAssert(selectorType->sizeOnStack() == 2);
// stack: <memory pointer> <functionPointer>
// Extract selector from the stack
m_context << Instruction::SWAP1 << Instruction::POP;
}
// Conversion will be done below // Conversion will be done below
dataOnStack = TypeProvider::uint(32); dataOnStack = TypeProvider::uint(32);
} }
@ -1747,6 +1760,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
case Type::Category::Function: case Type::Category::Function:
if (member == "selector") if (member == "selector")
{ {
auto const& functionType = dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type);
if (functionType.kind() == FunctionType::Kind::External)
CompilerUtils(m_context).popStackSlots(functionType.sizeOnStack() - 2);
m_context << Instruction::SWAP1 << Instruction::POP; m_context << Instruction::SWAP1 << Instruction::POP;
/// need to store it as bytes4 /// need to store it as bytes4
utils().leftShiftNumberOnStack(224); utils().leftShiftNumberOnStack(224);
@ -1755,8 +1771,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
{ {
auto const& functionType = dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type); auto const& functionType = dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type);
solAssert(functionType.kind() == FunctionType::Kind::External, ""); solAssert(functionType.kind() == FunctionType::Kind::External, "");
// stack: <address> <function_id> CompilerUtils(m_context).popStackSlots(functionType.sizeOnStack() - 1);
m_context << Instruction::POP;
} }
else else
solAssert( solAssert(
@ -2265,12 +2280,29 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO
void ExpressionCompiler::appendCompareOperatorCode(Token _operator, Type const& _type) void ExpressionCompiler::appendCompareOperatorCode(Token _operator, Type const& _type)
{ {
solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types.");
if (_operator == Token::Equal || _operator == Token::NotEqual) if (_operator == Token::Equal || _operator == Token::NotEqual)
{ {
if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type)) FunctionType const* functionType = dynamic_cast<decltype(functionType)>(&_type);
if (functionType && functionType->kind() == FunctionType::Kind::External)
{ {
if (funType->kind() == FunctionType::Kind::Internal) solUnimplementedAssert(functionType->sizeOnStack() == 2, "");
m_context << Instruction::SWAP3;
m_context << ((u256(1) << 160) - 1) << Instruction::AND;
m_context << Instruction::SWAP1;
m_context << ((u256(1) << 160) - 1) << Instruction::AND;
m_context << Instruction::EQ;
m_context << Instruction::SWAP2;
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
m_context << Instruction::SWAP1;
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
m_context << Instruction::EQ;
m_context << Instruction::AND;
}
else
{
solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types.");
if (functionType && functionType->kind() == FunctionType::Kind::Internal)
{ {
// We have to remove the upper bits (construction time value) because they might // We have to remove the upper bits (construction time value) because they might
// be "unknown" in one of the operands and not in the other. // be "unknown" in one of the operands and not in the other.
@ -2278,13 +2310,14 @@ void ExpressionCompiler::appendCompareOperatorCode(Token _operator, Type const&
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
m_context << ((u256(1) << 32) - 1) << Instruction::AND; m_context << ((u256(1) << 32) - 1) << Instruction::AND;
} }
m_context << Instruction::EQ;
} }
m_context << Instruction::EQ;
if (_operator == Token::NotEqual) if (_operator == Token::NotEqual)
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
} }
else else
{ {
solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types.");
bool isSigned = false; bool isSigned = false;
if (auto type = dynamic_cast<IntegerType const*>(&_type)) if (auto type = dynamic_cast<IntegerType const*>(&_type))
isSigned = type->isSigned(); isSigned = type->isSigned();

View File

@ -29,6 +29,7 @@
#include <libsolutil/FunctionSelector.h> #include <libsolutil/FunctionSelector.h>
#include <libsolutil/Whiskers.h> #include <libsolutil/Whiskers.h>
#include <libsolutil/StringUtils.h> #include <libsolutil/StringUtils.h>
#include <libsolidity/ast/TypeProvider.h>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
@ -3218,15 +3219,17 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
solAssert(fromType.arrayType().isByteArray(), "Array types other than bytes not convertible to bytesNN."); solAssert(fromType.arrayType().isByteArray(), "Array types other than bytes not convertible to bytesNN.");
return bytesToFixedBytesConversionFunction(fromType.arrayType(), dynamic_cast<FixedBytesType const &>(_to)); return bytesToFixedBytesConversionFunction(fromType.arrayType(), dynamic_cast<FixedBytesType const &>(_to));
} }
solAssert(_to.category() == Type::Category::Array, ""); solAssert(_to.category() == Type::Category::Array);
auto const& targetType = dynamic_cast<ArrayType const&>(_to); auto const& targetType = dynamic_cast<ArrayType const&>(_to);
solAssert(fromType.arrayType().isImplicitlyConvertibleTo(targetType), ""); solAssert(
fromType.arrayType().isImplicitlyConvertibleTo(targetType) ||
(fromType.arrayType().isByteArray() && targetType.isByteArray())
);
solAssert( solAssert(
fromType.arrayType().dataStoredIn(DataLocation::CallData) && fromType.arrayType().dataStoredIn(DataLocation::CallData) &&
fromType.arrayType().isDynamicallySized() && fromType.arrayType().isDynamicallySized() &&
!fromType.arrayType().baseType()->isDynamicallyEncoded(), !fromType.arrayType().baseType()->isDynamicallyEncoded()
""
); );
if (!targetType.dataStoredIn(DataLocation::CallData)) if (!targetType.dataStoredIn(DataLocation::CallData))
@ -3608,7 +3611,7 @@ string YulUtilFunctions::copyStructToStorageFunction(StructType const& _from, St
auto const& [srcSlotOffset, srcOffset] = _from.storageOffsetsOfMember(structMembers[i].name); auto const& [srcSlotOffset, srcOffset] = _from.storageOffsetsOfMember(structMembers[i].name);
t("memberOffset", formatNumber(srcSlotOffset)); t("memberOffset", formatNumber(srcSlotOffset));
if (memberType.isValueType()) if (memberType.isValueType())
t("read", readFromStorageValueType(memberType, srcOffset, false)); t("read", readFromStorageValueType(memberType, srcOffset, true));
else else
solAssert(srcOffset == 0, ""); solAssert(srcOffset == 0, "");
@ -4548,3 +4551,31 @@ string YulUtilFunctions::externalCodeFunction()
.render(); .render();
}); });
} }
std::string YulUtilFunctions::externalFunctionPointersEqualFunction()
{
std::string const functionName = "externalFunctionPointersEqualFunction";
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>(
leftAddress,
leftSelector,
rightAddress,
rightSelector
) -> result {
result := and(
eq(
<addressCleanUpFunction>(leftAddress), <addressCleanUpFunction>(rightAddress)
),
eq(
<selectorCleanUpFunction>(leftSelector), <selectorCleanUpFunction>(rightSelector)
)
)
}
)")
("functionName", functionName)
("addressCleanUpFunction", cleanupFunction(*TypeProvider::address()))
("selectorCleanUpFunction", cleanupFunction(*TypeProvider::uint(32)))
.render();
});
}

View File

@ -522,6 +522,9 @@ public:
/// Signature: (address) -> mpos /// Signature: (address) -> mpos
std::string externalCodeFunction(); std::string externalCodeFunction();
/// @return the name of a function that that checks if two external functions pointers are equal or not
std::string externalFunctionPointersEqualFunction();
private: private:
/// @returns the name of a function that copies a struct from calldata or memory to storage /// @returns the name of a function that copies a struct from calldata or memory to storage
/// signature: (slot, value) -> /// signature: (slot, value) ->

View File

@ -95,7 +95,7 @@ struct CopyTranslate: public yul::ASTCopier
return ASTCopier::translate(_identifier); return ASTCopier::translate(_identifier);
yul::Expression translated = translateReference(_identifier); yul::Expression translated = translateReference(_identifier);
solAssert(holds_alternative<yul::Identifier>(translated), ""); solAssert(holds_alternative<yul::Identifier>(translated));
return get<yul::Identifier>(std::move(translated)); return get<yul::Identifier>(std::move(translated));
} }
@ -115,14 +115,14 @@ private:
if (suffix.empty() && varDecl->isLocalVariable()) if (suffix.empty() && varDecl->isLocalVariable())
{ {
auto const& var = m_context.localVariable(*varDecl); auto const& var = m_context.localVariable(*varDecl);
solAssert(var.type().sizeOnStack() == 1, ""); solAssert(var.type().sizeOnStack() == 1);
value = var.commaSeparatedList(); value = var.commaSeparatedList();
} }
else if (varDecl->isConstant()) else if (varDecl->isConstant())
{ {
VariableDeclaration const* variable = rootConstVariableDeclaration(*varDecl); VariableDeclaration const* variable = rootConstVariableDeclaration(*varDecl);
solAssert(variable, ""); solAssert(variable);
if (variable->value()->annotation().type->category() == Type::Category::RationalNumber) if (variable->value()->annotation().type->category() == Type::Category::RationalNumber)
{ {
@ -130,7 +130,7 @@ private:
if (auto const* bytesType = dynamic_cast<FixedBytesType const*>(variable->type())) if (auto const* bytesType = dynamic_cast<FixedBytesType const*>(variable->type()))
intValue <<= 256 - 8 * bytesType->numBytes(); intValue <<= 256 - 8 * bytesType->numBytes();
else else
solAssert(variable->type()->category() == Type::Category::Integer, ""); solAssert(variable->type()->category() == Type::Category::Integer);
value = intValue.str(); value = intValue.str();
} }
else if (auto const* literal = dynamic_cast<Literal const*>(variable->value().get())) else if (auto const* literal = dynamic_cast<Literal const*>(variable->value().get()))
@ -141,20 +141,20 @@ private:
{ {
case Type::Category::Bool: case Type::Category::Bool:
case Type::Category::Address: case Type::Category::Address:
solAssert(type->category() == variable->annotation().type->category(), ""); solAssert(type->category() == variable->annotation().type->category());
value = toCompactHexWithPrefix(type->literalValue(literal)); value = toCompactHexWithPrefix(type->literalValue(literal));
break; break;
case Type::Category::StringLiteral: case Type::Category::StringLiteral:
{ {
auto const& stringLiteral = dynamic_cast<StringLiteralType const&>(*type); auto const& stringLiteral = dynamic_cast<StringLiteralType const&>(*type);
solAssert(variable->type()->category() == Type::Category::FixedBytes, ""); solAssert(variable->type()->category() == Type::Category::FixedBytes);
unsigned const numBytes = dynamic_cast<FixedBytesType const&>(*variable->type()).numBytes(); unsigned const numBytes = dynamic_cast<FixedBytesType const&>(*variable->type()).numBytes();
solAssert(stringLiteral.value().size() <= numBytes, ""); solAssert(stringLiteral.value().size() <= numBytes);
value = formatNumber(u256(h256(stringLiteral.value(), h256::AlignLeft))); value = formatNumber(u256(h256(stringLiteral.value(), h256::AlignLeft)));
break; break;
} }
default: default:
solAssert(false, ""); solAssert(false);
} }
} }
else else
@ -167,25 +167,25 @@ private:
else if (suffix == "offset") else if (suffix == "offset")
value = to_string(m_context.storageLocationOfStateVariable(*varDecl).second); value = to_string(m_context.storageLocationOfStateVariable(*varDecl).second);
else else
solAssert(false, ""); solAssert(false);
} }
else if (varDecl->type()->dataStoredIn(DataLocation::Storage)) else if (varDecl->type()->dataStoredIn(DataLocation::Storage))
{ {
solAssert(suffix == "slot" || suffix == "offset", ""); solAssert(suffix == "slot" || suffix == "offset");
solAssert(varDecl->isLocalVariable(), ""); solAssert(varDecl->isLocalVariable());
if (suffix == "slot") if (suffix == "slot")
value = IRVariable{*varDecl}.part("slot").name(); value = IRVariable{*varDecl}.part("slot").name();
else if (varDecl->type()->isValueType()) else if (varDecl->type()->isValueType())
value = IRVariable{*varDecl}.part("offset").name(); value = IRVariable{*varDecl}.part("offset").name();
else else
{ {
solAssert(!IRVariable{*varDecl}.hasPart("offset"), ""); solAssert(!IRVariable{*varDecl}.hasPart("offset"));
value = "0"; value = "0";
} }
} }
else if (varDecl->type()->dataStoredIn(DataLocation::CallData)) else if (varDecl->type()->dataStoredIn(DataLocation::CallData))
{ {
solAssert(suffix == "offset" || suffix == "length", ""); solAssert(suffix == "offset" || suffix == "length");
value = IRVariable{*varDecl}.part(suffix).name(); value = IRVariable{*varDecl}.part(suffix).name();
} }
else if ( else if (
@ -193,15 +193,15 @@ private:
functionType && functionType->kind() == FunctionType::Kind::External functionType && functionType->kind() == FunctionType::Kind::External
) )
{ {
solAssert(suffix == "selector" || suffix == "address", ""); solAssert(suffix == "selector" || suffix == "address");
solAssert(varDecl->type()->sizeOnStack() == 2, ""); solAssert(varDecl->type()->sizeOnStack() == 2);
if (suffix == "selector") if (suffix == "selector")
value = IRVariable{*varDecl}.part("functionSelector").name(); value = IRVariable{*varDecl}.part("functionSelector").name();
else else
value = IRVariable{*varDecl}.part("address").name(); value = IRVariable{*varDecl}.part("address").name();
} }
else else
solAssert(false, ""); solAssert(false);
if (isdigit(value.front())) if (isdigit(value.front()))
return yul::Literal{_identifier.debugData, yul::LiteralKind::Number, yul::YulString{value}, {}}; return yul::Literal{_identifier.debugData, yul::LiteralKind::Number, yul::YulString{value}, {}};
@ -268,7 +268,7 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
setLocation(_varDecl); setLocation(_varDecl);
solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable."); solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable.");
solAssert(!_varDecl.isConstant(), ""); solAssert(!_varDecl.isConstant());
if (!_varDecl.value()) if (!_varDecl.value())
return; return;
@ -355,7 +355,7 @@ string IRGeneratorForStatements::constantValueFunction(VariableDeclaration const
templ("sourceLocationComment", dispenseLocationComment(_constant, m_context)); templ("sourceLocationComment", dispenseLocationComment(_constant, m_context));
templ("functionName", functionName); templ("functionName", functionName);
IRGeneratorForStatements generator(m_context, m_utils); IRGeneratorForStatements generator(m_context, m_utils);
solAssert(_constant.value(), ""); solAssert(_constant.value());
Type const& constantType = *_constant.type(); Type const& constantType = *_constant.type();
templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList()); templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList());
templ("code", generator.code()); templ("code", generator.code());
@ -386,7 +386,7 @@ void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _var
for (size_t i = 0; i < _varDeclStatement.declarations().size(); ++i) for (size_t i = 0; i < _varDeclStatement.declarations().size(); ++i)
if (auto const& decl = _varDeclStatement.declarations()[i]) if (auto const& decl = _varDeclStatement.declarations()[i])
{ {
solAssert(tupleType->components()[i], ""); solAssert(tupleType->components()[i]);
define(m_context.addLocalVariable(*decl), IRVariable(*expression).tupleComponent(i)); define(m_context.addLocalVariable(*decl), IRVariable(*expression).tupleComponent(i));
} }
} }
@ -443,7 +443,7 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
TokenTraits::AssignmentToBinaryOp(assignmentOperator); TokenTraits::AssignmentToBinaryOp(assignmentOperator);
if (TokenTraits::isShiftOp(binaryOperator)) if (TokenTraits::isShiftOp(binaryOperator))
solAssert(type(_assignment.rightHandSide()).mobileType(), ""); solAssert(type(_assignment.rightHandSide()).mobileType());
IRVariable value = IRVariable value =
type(_assignment.leftHandSide()).isValueType() ? type(_assignment.leftHandSide()).isValueType() ?
convert( convert(
@ -460,11 +460,11 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
if (assignmentOperator != Token::Assign) if (assignmentOperator != Token::Assign)
{ {
solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types."); solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types.");
solAssert(binaryOperator != Token::Exp, ""); solAssert(binaryOperator != Token::Exp);
solAssert(type(_assignment) == type(_assignment.leftHandSide()), ""); solAssert(type(_assignment) == type(_assignment.leftHandSide()));
IRVariable leftIntermediate = readFromLValue(*m_currentLValue); IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
solAssert(type(_assignment) == leftIntermediate.type(), ""); solAssert(type(_assignment) == leftIntermediate.type());
define(_assignment) << ( define(_assignment) << (
TokenTraits::isShiftOp(binaryOperator) ? TokenTraits::isShiftOp(binaryOperator) ?
@ -523,14 +523,14 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
{ {
bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo; bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
if (willBeWrittenTo) if (willBeWrittenTo)
solAssert(!m_currentLValue, ""); solAssert(!m_currentLValue);
if (_tuple.components().size() == 1) if (_tuple.components().size() == 1)
{ {
solAssert(_tuple.components().front(), ""); solAssert(_tuple.components().front());
_tuple.components().front()->accept(*this); _tuple.components().front()->accept(*this);
setLocation(_tuple); setLocation(_tuple);
if (willBeWrittenTo) if (willBeWrittenTo)
solAssert(!!m_currentLValue, ""); solAssert(!!m_currentLValue);
else else
define(_tuple, *_tuple.components().front()); define(_tuple, *_tuple.components().front());
} }
@ -544,7 +544,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
setLocation(_tuple); setLocation(_tuple);
if (willBeWrittenTo) if (willBeWrittenTo)
{ {
solAssert(!!m_currentLValue, ""); solAssert(!!m_currentLValue);
lvalues.emplace_back(std::move(m_currentLValue)); lvalues.emplace_back(std::move(m_currentLValue));
m_currentLValue.reset(); m_currentLValue.reset();
} }
@ -568,7 +568,7 @@ bool IRGeneratorForStatements::visit(Block const& _block)
{ {
if (_block.unchecked()) if (_block.unchecked())
{ {
solAssert(m_context.arithmetic() == Arithmetic::Checked, ""); solAssert(m_context.arithmetic() == Arithmetic::Checked);
m_context.setArithmetic(Arithmetic::Wrapping); m_context.setArithmetic(Arithmetic::Wrapping);
} }
return true; return true;
@ -578,7 +578,7 @@ void IRGeneratorForStatements::endVisit(Block const& _block)
{ {
if (_block.unchecked()) if (_block.unchecked())
{ {
solAssert(m_context.arithmetic() == Arithmetic::Wrapping, ""); solAssert(m_context.arithmetic() == Arithmetic::Wrapping);
m_context.setArithmetic(Arithmetic::Checked); m_context.setArithmetic(Arithmetic::Checked);
} }
} }
@ -607,7 +607,7 @@ bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
void IRGeneratorForStatements::endVisit(PlaceholderStatement const& _placeholder) void IRGeneratorForStatements::endVisit(PlaceholderStatement const& _placeholder)
{ {
solAssert(m_placeholderCallback, ""); solAssert(m_placeholderCallback);
setLocation(_placeholder); setLocation(_placeholder);
appendCode() << m_placeholderCallback(); appendCode() << m_placeholderCallback();
} }
@ -776,7 +776,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
{ {
setLocation(_binOp); setLocation(_binOp);
solAssert(!!_binOp.annotation().commonType, ""); solAssert(!!_binOp.annotation().commonType);
Type const* commonType = _binOp.annotation().commonType; Type const* commonType = _binOp.annotation().commonType;
langutil::Token op = _binOp.getOperator(); langutil::Token op = _binOp.getOperator();
@ -799,13 +799,8 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
if (TokenTraits::isCompareOp(op)) if (TokenTraits::isCompareOp(op))
{ {
if (auto type = dynamic_cast<FunctionType const*>(commonType)) solAssert(commonType->isValueType());
{
solAssert(op == Token::Equal || op == Token::NotEqual, "Invalid function pointer comparison!");
solAssert(type->kind() != FunctionType::Kind::External, "External function comparison not allowed!");
}
solAssert(commonType->isValueType(), "");
bool isSigned = false; bool isSigned = false;
if (auto type = dynamic_cast<IntegerType const*>(commonType)) if (auto type = dynamic_cast<IntegerType const*>(commonType))
isSigned = type->isSigned(); isSigned = type->isSigned();
@ -813,8 +808,25 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
string args = expressionAsType(_binOp.leftExpression(), *commonType, true); string args = expressionAsType(_binOp.leftExpression(), *commonType, true);
args += ", " + expressionAsType(_binOp.rightExpression(), *commonType, true); args += ", " + expressionAsType(_binOp.rightExpression(), *commonType, true);
auto functionType = dynamic_cast<FunctionType const*>(commonType);
solAssert(functionType ? (op == Token::Equal || op == Token::NotEqual) : true, "Invalid function pointer comparison!");
string expr; string expr;
if (op == Token::Equal)
if (functionType && functionType->kind() == FunctionType::Kind::External)
{
solUnimplementedAssert(functionType->sizeOnStack() == 2, "");
expr = m_utils.externalFunctionPointersEqualFunction() +
"(" +
IRVariable{_binOp.leftExpression()}.part("address").name() + "," +
IRVariable{_binOp.leftExpression()}.part("functionSelector").name() + "," +
IRVariable{_binOp.rightExpression()}.part("address").name() + "," +
IRVariable{_binOp.rightExpression()}.part("functionSelector").name() +
")";
if (op == Token::NotEqual)
expr = "iszero(" + expr + ")";
}
else if (op == Token::Equal)
expr = "eq(" + move(args) + ")"; expr = "eq(" + move(args) + ")";
else if (op == Token::NotEqual) else if (op == Token::NotEqual)
expr = "iszero(eq(" + move(args) + "))"; expr = "iszero(eq(" + move(args) + "))";
@ -843,7 +855,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
else if (auto rationalNumberType = dynamic_cast<RationalNumberType const*>(_binOp.leftExpression().annotation().type)) else if (auto rationalNumberType = dynamic_cast<RationalNumberType const*>(_binOp.leftExpression().annotation().type))
{ {
solAssert(rationalNumberType->integerType(), "Invalid literal as the base for exponentiation."); solAssert(rationalNumberType->integerType(), "Invalid literal as the base for exponentiation.");
solAssert(dynamic_cast<IntegerType const*>(commonType), ""); solAssert(dynamic_cast<IntegerType const*>(commonType));
define(_binOp) << m_utils.overflowCheckedIntLiteralExpFunction( define(_binOp) << m_utils.overflowCheckedIntLiteralExpFunction(
*rationalNumberType, *rationalNumberType,
@ -939,7 +951,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{ {
FunctionDefinition const* functionDef = ASTNode::resolveFunctionCall(_functionCall, &m_context.mostDerivedContract()); FunctionDefinition const* functionDef = ASTNode::resolveFunctionCall(_functionCall, &m_context.mostDerivedContract());
solAssert(!functionType->takesArbitraryParameters(), ""); solAssert(!functionType->takesArbitraryParameters());
vector<string> args; vector<string> args;
if (functionType->bound()) if (functionType->bound())
@ -950,7 +962,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
if (functionDef) if (functionDef)
{ {
solAssert(functionDef->isImplemented(), ""); solAssert(functionDef->isImplemented());
define(_functionCall) << define(_functionCall) <<
m_context.enqueueFunctionForCodeGeneration(*functionDef) << m_context.enqueueFunctionForCodeGeneration(*functionDef) <<
@ -1053,7 +1065,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::Error: case FunctionType::Kind::Error:
{ {
ErrorDefinition const* error = dynamic_cast<ErrorDefinition const*>(ASTNode::referencedDeclaration(_functionCall.expression())); ErrorDefinition const* error = dynamic_cast<ErrorDefinition const*>(ASTNode::referencedDeclaration(_functionCall.expression()));
solAssert(error, ""); solAssert(error);
revertWithError( revertWithError(
error->functionType(true)->externalSignature(), error->functionType(true)->externalSignature(),
error->functionType(true)->parameterTypes(), error->functionType(true)->parameterTypes(),
@ -1064,7 +1076,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::Wrap: case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap: case FunctionType::Kind::Unwrap:
{ {
solAssert(arguments.size() == 1, ""); solAssert(arguments.size() == 1);
FunctionType::Kind kind = functionType->kind(); FunctionType::Kind kind = functionType->kind();
if (kind == FunctionType::Kind::Wrap) if (kind == FunctionType::Kind::Wrap)
solAssert( solAssert(
@ -1074,7 +1086,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
"" ""
); );
else else
solAssert(type(*arguments.at(0)).category() == Type::Category::UserDefinedValueType, ""); solAssert(type(*arguments.at(0)).category() == Type::Category::UserDefinedValueType);
define(_functionCall, *arguments.at(0)); define(_functionCall, *arguments.at(0));
break; break;
@ -1108,7 +1120,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::ABIEncodeWithSignature: case FunctionType::Kind::ABIEncodeWithSignature:
{ {
bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked; bool const isPacked = functionType->kind() == FunctionType::Kind::ABIEncodePacked;
solAssert(functionType->padArguments() != isPacked, ""); solAssert(functionType->padArguments() != isPacked);
bool const hasSelectorOrSignature = bool const hasSelectorOrSignature =
functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector || functionType->kind() == FunctionType::Kind::ABIEncodeWithSelector ||
functionType->kind() == FunctionType::Kind::ABIEncodeCall || functionType->kind() == FunctionType::Kind::ABIEncodeCall ||
@ -1122,7 +1134,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall) if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
{ {
solAssert(arguments.size() == 2, ""); solAssert(arguments.size() == 2);
// Account for tuples with one component which become that component // Account for tuples with one component which become that component
if (type(*arguments[1]).category() == Type::Category::Tuple) if (type(*arguments[1]).category() == Type::Category::Tuple)
{ {
@ -1150,10 +1162,21 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
} }
if (functionType->kind() == FunctionType::Kind::ABIEncodeCall) if (functionType->kind() == FunctionType::Kind::ABIEncodeCall)
selector = convert( {
IRVariable(*arguments[0]).part("functionSelector"), auto const& selectorType = dynamic_cast<FunctionType const&>(type(*arguments.front()));
*TypeProvider::fixedBytes(4) if (selectorType.kind() == FunctionType::Kind::Declaration)
).name(); {
solAssert(selectorType.hasDeclaration());
selector = formatNumber(selectorType.externalIdentifier() << (256 - 32));
}
else
{
selector = convert(
IRVariable(*arguments[0]).part("functionSelector"),
*TypeProvider::fixedBytes(4)
).name();
}
}
else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature) else if (functionType->kind() == FunctionType::Kind::ABIEncodeWithSignature)
{ {
// hash the signature // hash the signature
@ -1234,7 +1257,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
referenceType && referenceType->dataStoredIn(DataLocation::CallData) referenceType && referenceType->dataStoredIn(DataLocation::CallData)
) )
{ {
solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()), ""); solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()));
IRVariable var = convert(*arguments[0], *TypeProvider::bytesCalldata()); IRVariable var = convert(*arguments[0], *TypeProvider::bytesCalldata());
templ("abiDecode", m_context.abiFunctions().tupleDecoder(targetTypes, false)); templ("abiDecode", m_context.abiFunctions().tupleDecoder(targetTypes, false));
templ("offset", var.part("offset").name()); templ("offset", var.part("offset").name());
@ -1256,8 +1279,8 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
} }
case FunctionType::Kind::Revert: case FunctionType::Kind::Revert:
{ {
solAssert(arguments.size() == parameterTypes.size(), ""); solAssert(arguments.size() == parameterTypes.size());
solAssert(arguments.size() <= 1, ""); solAssert(arguments.size() <= 1);
solAssert( solAssert(
arguments.empty() || arguments.empty() ||
arguments.front()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()), arguments.front()->annotation().type->isImplicitlyConvertibleTo(*TypeProvider::stringMemory()),
@ -1276,7 +1299,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::ObjectCreation: case FunctionType::Kind::ObjectCreation:
{ {
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type); ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type);
solAssert(arguments.size() == 1, ""); solAssert(arguments.size() == 1);
IRVariable value = convert(*arguments[0], *TypeProvider::uint256()); IRVariable value = convert(*arguments[0], *TypeProvider::uint256());
define(_functionCall) << define(_functionCall) <<
@ -1288,7 +1311,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
} }
case FunctionType::Kind::KECCAK256: case FunctionType::Kind::KECCAK256:
{ {
solAssert(arguments.size() == 1, ""); solAssert(arguments.size() == 1);
ArrayType const* arrayType = TypeProvider::bytesMemory(); ArrayType const* arrayType = TypeProvider::bytesMemory();
@ -1316,10 +1339,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
} }
case FunctionType::Kind::ArrayPop: case FunctionType::Kind::ArrayPop:
{ {
solAssert(functionType->bound(), ""); solAssert(functionType->bound());
solAssert(functionType->parameterTypes().empty(), ""); solAssert(functionType->parameterTypes().empty());
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(functionType->selfType()); ArrayType const* arrayType = dynamic_cast<ArrayType const*>(functionType->selfType());
solAssert(arrayType, ""); solAssert(arrayType);
define(_functionCall) << define(_functionCall) <<
m_utils.storageArrayPopFunction(*arrayType) << m_utils.storageArrayPopFunction(*arrayType) <<
"(" << "(" <<
@ -1330,7 +1353,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::ArrayPush: case FunctionType::Kind::ArrayPush:
{ {
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(functionType->selfType()); ArrayType const* arrayType = dynamic_cast<ArrayType const*>(functionType->selfType());
solAssert(arrayType, ""); solAssert(arrayType);
if (arguments.empty()) if (arguments.empty())
{ {
@ -1391,8 +1414,8 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{FunctionType::Kind::AddMod, "addmod"}, {FunctionType::Kind::AddMod, "addmod"},
{FunctionType::Kind::MulMod, "mulmod"}, {FunctionType::Kind::MulMod, "mulmod"},
}; };
solAssert(functions.find(functionType->kind()) != functions.end(), ""); solAssert(functions.find(functionType->kind()) != functions.end());
solAssert(arguments.size() == 3 && parameterTypes.size() == 3, ""); solAssert(arguments.size() == 3 && parameterTypes.size() == 3);
IRVariable modulus(m_context.newYulVariable(), *(parameterTypes[2])); IRVariable modulus(m_context.newYulVariable(), *(parameterTypes[2]));
define(modulus, *arguments[2]); define(modulus, *arguments[2]);
@ -1417,7 +1440,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{FunctionType::Kind::Selfdestruct, "selfdestruct"}, {FunctionType::Kind::Selfdestruct, "selfdestruct"},
{FunctionType::Kind::BlockHash, "blockhash"}, {FunctionType::Kind::BlockHash, "blockhash"},
}; };
solAssert(functions.find(functionType->kind()) != functions.end(), ""); solAssert(functions.find(functionType->kind()) != functions.end());
string args; string args;
for (size_t i = 0; i < arguments.size(); ++i) for (size_t i = 0; i < arguments.size(); ++i)
@ -1474,7 +1497,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
t("saltSet", functionType->saltSet()); t("saltSet", functionType->saltSet());
if (functionType->saltSet()) if (functionType->saltSet())
t("salt", IRVariable(_functionCall.expression()).part("salt").name()); t("salt", IRVariable(_functionCall.expression()).part("salt").name());
solAssert(IRVariable(_functionCall).stackSlots().size() == 1, ""); solAssert(IRVariable(_functionCall).stackSlots().size() == 1);
t("address", IRVariable(_functionCall).commaSeparatedList()); t("address", IRVariable(_functionCall).commaSeparatedList());
t("isTryCall", _functionCall.annotation().tryCall); t("isTryCall", _functionCall.annotation().tryCall);
if (_functionCall.annotation().tryCall) if (_functionCall.annotation().tryCall)
@ -1488,7 +1511,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::Send: case FunctionType::Kind::Send:
case FunctionType::Kind::Transfer: case FunctionType::Kind::Transfer:
{ {
solAssert(arguments.size() == 1 && parameterTypes.size() == 1, ""); solAssert(arguments.size() == 1 && parameterTypes.size() == 1);
string address{IRVariable(_functionCall.expression()).part("address").name()}; string address{IRVariable(_functionCall.expression()).part("address").name()};
string value{expressionAsType(*arguments[0], *(parameterTypes[0]))}; string value{expressionAsType(*arguments[0], *(parameterTypes[0]))};
Whiskers templ(R"( Whiskers templ(R"(
@ -1517,10 +1540,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::RIPEMD160: case FunctionType::Kind::RIPEMD160:
case FunctionType::Kind::SHA256: case FunctionType::Kind::SHA256:
{ {
solAssert(!_functionCall.annotation().tryCall, ""); solAssert(!_functionCall.annotation().tryCall);
solAssert(!functionType->valueSet(), ""); solAssert(!functionType->valueSet());
solAssert(!functionType->gasSet(), ""); solAssert(!functionType->gasSet());
solAssert(!functionType->bound(), ""); solAssert(!functionType->bound());
static map<FunctionType::Kind, std::tuple<unsigned, size_t>> precompiles = { static map<FunctionType::Kind, std::tuple<unsigned, size_t>> precompiles = {
{FunctionType::Kind::ECRecover, std::make_tuple(1, 0)}, {FunctionType::Kind::ECRecover, std::make_tuple(1, 0)},
@ -1595,7 +1618,7 @@ void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options)
for (size_t i = 0; i < _options.names().size(); ++i) for (size_t i = 0; i < _options.names().size(); ++i)
{ {
string const& name = *_options.names()[i]; string const& name = *_options.names()[i];
solAssert(name == "salt" || name == "gas" || name == "value", ""); solAssert(name == "salt" || name == "gas" || name == "value");
define(IRVariable(_options).part(name), *_options.options()[i]); define(IRVariable(_options).part(name), *_options.options()[i]);
} }
@ -1613,7 +1636,7 @@ bool IRGeneratorForStatements::visit(MemberAccess const& _memberAccess)
innerExpression->expression().annotation().type->category() == Type::Category::Address innerExpression->expression().annotation().type->category() == Type::Category::Address
) )
{ {
solAssert(innerExpression->annotation().type->category() == Type::Category::Array, ""); solAssert(innerExpression->annotation().type->category() == Type::Category::Array);
// Skip visiting <address>.code // Skip visiting <address>.code
innerExpression->expression().accept(*this); innerExpression->expression().accept(*this);
@ -1634,7 +1657,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
if (memberFunctionType && memberFunctionType->bound()) if (memberFunctionType && memberFunctionType->bound())
{ {
define(IRVariable(_memberAccess).part("self"), _memberAccess.expression()); define(IRVariable(_memberAccess).part("self"), _memberAccess.expression());
solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static);
if (memberFunctionType->kind() == FunctionType::Kind::Internal) if (memberFunctionType->kind() == FunctionType::Kind::Internal)
assignInternalFunctionIDIfNotCalledDirectly( assignInternalFunctionIDIfNotCalledDirectly(
_memberAccess, _memberAccess,
@ -1650,9 +1673,9 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
else else
{ {
auto const& functionDefinition = dynamic_cast<FunctionDefinition const&>(memberFunctionType->declaration()); auto const& functionDefinition = dynamic_cast<FunctionDefinition const&>(memberFunctionType->declaration());
solAssert(memberFunctionType->kind() == FunctionType::Kind::DelegateCall, ""); solAssert(memberFunctionType->kind() == FunctionType::Kind::DelegateCall);
auto contract = dynamic_cast<ContractDefinition const*>(functionDefinition.scope()); auto contract = dynamic_cast<ContractDefinition const*>(functionDefinition.scope());
solAssert(contract && contract->isLibrary(), ""); solAssert(contract && contract->isLibrary());
define(IRVariable(_memberAccess).part("address")) << linkerSymbol(*contract) << "\n"; define(IRVariable(_memberAccess).part("address")) << linkerSymbol(*contract) << "\n";
define(IRVariable(_memberAccess).part("functionSelector")) << memberFunctionType->externalIdentifier() << "\n"; define(IRVariable(_memberAccess).part("functionSelector")) << memberFunctionType->externalIdentifier() << "\n";
} }
@ -1665,7 +1688,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{ {
ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type); ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.expression().annotation().type);
if (type.isSuper()) if (type.isSuper())
solAssert(false, ""); solAssert(false);
// ordinary contract type // ordinary contract type
else if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration) else if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration)
@ -1713,7 +1736,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
")\n"; ")\n";
else if (set<string>{"send", "transfer"}.count(member)) else if (set<string>{"send", "transfer"}.count(member))
{ {
solAssert(dynamic_cast<AddressType const&>(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable, ""); solAssert(dynamic_cast<AddressType const&>(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable);
define(IRVariable{_memberAccess}.part("address"), _memberAccess.expression()); define(IRVariable{_memberAccess}.part("address"), _memberAccess.expression());
} }
else if (set<string>{"call", "callcode", "delegatecall", "staticcall"}.count(member)) else if (set<string>{"call", "callcode", "delegatecall", "staticcall"}.count(member))
@ -1741,7 +1764,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
functionType.kind() == FunctionType::Kind::Internal functionType.kind() == FunctionType::Kind::Internal
) )
{ {
solAssert(functionType.hasDeclaration(), ""); solAssert(functionType.hasDeclaration());
solAssert( solAssert(
functionType.kind() == FunctionType::Kind::Error || functionType.kind() == FunctionType::Kind::Error ||
functionType.declaration().isPartOfExternalInterface(), functionType.declaration().isPartOfExternalInterface(),
@ -1809,7 +1832,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{ {
Type const* arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument(); Type const* arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
auto const& contractType = dynamic_cast<ContractType const&>(*arg); auto const& contractType = dynamic_cast<ContractType const&>(*arg);
solAssert(!contractType.isSuper(), ""); solAssert(!contractType.isSuper());
ContractDefinition const& contract = contractType.contractDefinition(); ContractDefinition const& contract = contractType.contractDefinition();
m_context.subObjectsCreated().insert(&contract); m_context.subObjectsCreated().insert(&contract);
appendCode() << Whiskers(R"( appendCode() << Whiskers(R"(
@ -1833,7 +1856,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{ {
Type const* arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument(); Type const* arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
auto const& contractType = dynamic_cast<ContractType const&>(*arg); auto const& contractType = dynamic_cast<ContractType const&>(*arg);
solAssert(!contractType.isSuper(), ""); solAssert(!contractType.isSuper());
ContractDefinition const& contract = contractType.contractDefinition(); ContractDefinition const& contract = contractType.contractDefinition();
define(_memberAccess) << formatNumber(u256{contract.interfaceId()} << (256 - 32)) << "\n"; define(_memberAccess) << formatNumber(u256{contract.interfaceId()} << (256 - 32)) << "\n";
} }
@ -1960,7 +1983,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
} }
else if (member == "pop" || member == "push") else if (member == "pop" || member == "push")
{ {
solAssert(type.location() == DataLocation::Storage, ""); solAssert(type.location() == DataLocation::Storage);
define(IRVariable{_memberAccess}.part("slot"), IRVariable{_memberAccess.expression()}.part("slot")); define(IRVariable{_memberAccess}.part("slot"), IRVariable{_memberAccess.expression()}.part("slot"));
} }
else else
@ -1996,8 +2019,8 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
*_memberAccess.annotation().referencedDeclaration *_memberAccess.annotation().referencedDeclaration
).resolveVirtual(m_context.mostDerivedContract(), super); ).resolveVirtual(m_context.mostDerivedContract(), super);
solAssert(resolvedFunctionDef.functionType(true), ""); solAssert(resolvedFunctionDef.functionType(true));
solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal, ""); solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal);
assignInternalFunctionIDIfNotCalledDirectly(_memberAccess, resolvedFunctionDef); assignInternalFunctionIDIfNotCalledDirectly(_memberAccess, resolvedFunctionDef);
} }
else if (auto const* variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration)) else if (auto const* variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
@ -2055,19 +2078,19 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
// The old code generator had a generic "else" case here // The old code generator had a generic "else" case here
// without any specific code being generated, // without any specific code being generated,
// but it would still be better to have an exhaustive list. // but it would still be better to have an exhaustive list.
solAssert(false, ""); solAssert(false);
} }
else if (EnumType const* enumType = dynamic_cast<EnumType const*>(&actualType)) else if (EnumType const* enumType = dynamic_cast<EnumType const*>(&actualType))
define(_memberAccess) << to_string(enumType->memberValue(_memberAccess.memberName())) << "\n"; define(_memberAccess) << to_string(enumType->memberValue(_memberAccess.memberName())) << "\n";
else if (dynamic_cast<UserDefinedValueType const*>(&actualType)) else if (dynamic_cast<UserDefinedValueType const*>(&actualType))
solAssert(member == "wrap" || member == "unwrap", ""); solAssert(member == "wrap" || member == "unwrap");
else if (auto const* arrayType = dynamic_cast<ArrayType const*>(&actualType)) else if (auto const* arrayType = dynamic_cast<ArrayType const*>(&actualType))
solAssert(arrayType->isByteArray() && member == "concat", ""); solAssert(arrayType->isByteArray() && member == "concat");
else else
// The old code generator had a generic "else" case here // The old code generator had a generic "else" case here
// without any specific code being generated, // without any specific code being generated,
// but it would still be better to have an exhaustive list. // but it would still be better to have an exhaustive list.
solAssert(false, ""); solAssert(false);
break; break;
} }
case Type::Category::Module: case Type::Category::Module:
@ -2083,17 +2106,17 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
); );
if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration)) if (auto variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
{ {
solAssert(variable->isConstant(), ""); solAssert(variable->isConstant());
handleVariableReference(*variable, static_cast<Expression const&>(_memberAccess)); handleVariableReference(*variable, static_cast<Expression const&>(_memberAccess));
} }
else if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration)) else if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
{ {
auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type); auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
solAssert(function && function->isFree(), ""); solAssert(function && function->isFree());
solAssert(function->functionType(true), ""); solAssert(function->functionType(true));
solAssert(function->functionType(true)->kind() == FunctionType::Kind::Internal, ""); solAssert(function->functionType(true)->kind() == FunctionType::Kind::Internal);
solAssert(funType->kind() == FunctionType::Kind::Internal, ""); solAssert(funType->kind() == FunctionType::Kind::Internal);
solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static);
assignInternalFunctionIDIfNotCalledDirectly(_memberAccess, *function); assignInternalFunctionIDIfNotCalledDirectly(_memberAccess, *function);
} }
@ -2117,7 +2140,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
yul::Statement modified = bodyCopier(_inlineAsm.operations()); yul::Statement modified = bodyCopier(_inlineAsm.operations());
solAssert(holds_alternative<yul::Block>(modified), ""); solAssert(holds_alternative<yul::Block>(modified));
// Do not provide dialect so that we get the full type information. // Do not provide dialect so that we get the full type information.
appendCode() << yul::AsmPrinter()(std::get<yul::Block>(modified)) << "\n"; appendCode() << yul::AsmPrinter()(std::get<yul::Block>(modified)) << "\n";
@ -2160,7 +2183,7 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
dynamic_cast<ArraySliceType const&>(baseType).arrayType(); dynamic_cast<ArraySliceType const&>(baseType).arrayType();
if (baseType.category() == Type::Category::ArraySlice) if (baseType.category() == Type::Category::ArraySlice)
solAssert(arrayType.dataStoredIn(DataLocation::CallData) && arrayType.isDynamicallySized(), ""); solAssert(arrayType.dataStoredIn(DataLocation::CallData) && arrayType.isDynamicallySized());
solAssert(_indexAccess.indexExpression(), "Index expression expected."); solAssert(_indexAccess.indexExpression(), "Index expression expected.");
@ -2253,8 +2276,8 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
} }
else if (baseType.category() == Type::Category::TypeType) else if (baseType.category() == Type::Category::TypeType)
{ {
solAssert(baseType.sizeOnStack() == 0, ""); solAssert(baseType.sizeOnStack() == 0);
solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, ""); solAssert(_indexAccess.annotation().type->sizeOnStack() == 0);
// no-op - this seems to be a lone array type (`structType[];`) // no-op - this seems to be a lone array type (`structType[];`)
} }
else else
@ -2279,7 +2302,7 @@ void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAcces
{ {
case DataLocation::CallData: case DataLocation::CallData:
{ {
solAssert(baseType.isDynamicallySized(), ""); solAssert(baseType.isDynamicallySized());
IRVariable sliceStart{m_context.newYulVariable(), *TypeProvider::uint256()}; IRVariable sliceStart{m_context.newYulVariable(), *TypeProvider::uint256()};
if (_indexRangeAccess.startExpression()) if (_indexRangeAccess.startExpression())
define(sliceStart, IRVariable{*_indexRangeAccess.startExpression()}); define(sliceStart, IRVariable{*_indexRangeAccess.startExpression()});
@ -2317,18 +2340,18 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
switch (magicVar->type()->category()) switch (magicVar->type()->category())
{ {
case Type::Category::Contract: case Type::Category::Contract:
solAssert(_identifier.name() == "this", ""); solAssert(_identifier.name() == "this");
define(_identifier) << "address()\n"; define(_identifier) << "address()\n";
break; break;
case Type::Category::Integer: case Type::Category::Integer:
solAssert(_identifier.name() == "now", ""); solAssert(_identifier.name() == "now");
define(_identifier) << "timestamp()\n"; define(_identifier) << "timestamp()\n";
break; break;
case Type::Category::TypeType: case Type::Category::TypeType:
{ {
auto typeType = dynamic_cast<TypeType const*>(magicVar->type()); auto typeType = dynamic_cast<TypeType const*>(magicVar->type());
if (auto contractType = dynamic_cast<ContractType const*>(typeType->actualType())) if (auto contractType = dynamic_cast<ContractType const*>(typeType->actualType()))
solAssert(!contractType->isSuper() || _identifier.name() == "super", ""); solAssert(!contractType->isSuper() || _identifier.name() == "super");
break; break;
} }
default: default:
@ -2338,11 +2361,11 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
} }
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
{ {
solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual, ""); solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual);
FunctionDefinition const& resolvedFunctionDef = functionDef->resolveVirtual(m_context.mostDerivedContract()); FunctionDefinition const& resolvedFunctionDef = functionDef->resolveVirtual(m_context.mostDerivedContract());
solAssert(resolvedFunctionDef.functionType(true), ""); solAssert(resolvedFunctionDef.functionType(true));
solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal, ""); solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal);
assignInternalFunctionIDIfNotCalledDirectly(_identifier, resolvedFunctionDef); assignInternalFunctionIDIfNotCalledDirectly(_identifier, resolvedFunctionDef);
} }
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
@ -2437,9 +2460,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
) )
{ {
FunctionType const& funType = dynamic_cast<FunctionType const&>(type(_functionCall.expression())); FunctionType const& funType = dynamic_cast<FunctionType const&>(type(_functionCall.expression()));
solAssert(!funType.takesArbitraryParameters(), ""); solAssert(!funType.takesArbitraryParameters());
solAssert(_arguments.size() == funType.parameterTypes().size(), ""); solAssert(_arguments.size() == funType.parameterTypes().size());
solAssert(!funType.isBareCall(), ""); solAssert(!funType.isBareCall());
FunctionType::Kind const funKind = funType.kind(); FunctionType::Kind const funKind = funType.kind();
solAssert( solAssert(
@ -2543,7 +2566,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
string const retVars = IRVariable(_functionCall).commaSeparatedList(); string const retVars = IRVariable(_functionCall).commaSeparatedList();
templ("retVars", retVars); templ("retVars", retVars);
solAssert(retVars.empty() == returnInfo.returnTypes.empty(), ""); solAssert(retVars.empty() == returnInfo.returnTypes.empty());
templ("abiDecode", m_context.abiFunctions().tupleDecoder(returnInfo.returnTypes, true)); templ("abiDecode", m_context.abiFunctions().tupleDecoder(returnInfo.returnTypes, true));
templ("dynamicReturnSize", returnInfo.dynamicReturnSize); templ("dynamicReturnSize", returnInfo.dynamicReturnSize);
@ -2552,7 +2575,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall; bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
solAssert(funType.padArguments(), ""); solAssert(funType.padArguments());
templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, parameterTypes, encodeForLibraryCall)); templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, parameterTypes, encodeForLibraryCall));
templ("argumentString", joinHumanReadablePrefixed(argumentStrings)); templ("argumentString", joinHumanReadablePrefixed(argumentStrings));
@ -2605,7 +2628,7 @@ void IRGeneratorForStatements::appendBareCall(
); );
FunctionType::Kind const funKind = funType.kind(); FunctionType::Kind const funKind = funType.kind();
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall());
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed."); solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
solAssert( solAssert(
funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCall ||
@ -2613,7 +2636,7 @@ void IRGeneratorForStatements::appendBareCall(
funKind == FunctionType::Kind::BareStaticCall, "" funKind == FunctionType::Kind::BareStaticCall, ""
); );
solAssert(!_functionCall.annotation().tryCall, ""); solAssert(!_functionCall.annotation().tryCall);
Whiskers templ(R"( Whiskers templ(R"(
<?needsEncoding> <?needsEncoding>
let <pos> := <allocateUnbounded>() let <pos> := <allocateUnbounded>()
@ -2825,7 +2848,7 @@ string IRGeneratorForStatements::binaryOperation(
"Not yet implemented - FixedPointType." "Not yet implemented - FixedPointType."
); );
IntegerType const* type = dynamic_cast<IntegerType const*>(&_type); IntegerType const* type = dynamic_cast<IntegerType const*>(&_type);
solAssert(type, ""); solAssert(type);
bool checked = m_context.arithmetic() == Arithmetic::Checked; bool checked = m_context.arithmetic() == Arithmetic::Checked;
switch (_operator) switch (_operator)
{ {
@ -2865,9 +2888,9 @@ std::string IRGeneratorForStatements::shiftOperation(
"Not yet implemented - FixedPointType." "Not yet implemented - FixedPointType."
); );
IntegerType const* amountType = dynamic_cast<IntegerType const*>(&_amountToShift.type()); IntegerType const* amountType = dynamic_cast<IntegerType const*>(&_amountToShift.type());
solAssert(amountType, ""); solAssert(amountType);
solAssert(_operator == Token::SHL || _operator == Token::SAR, ""); solAssert(_operator == Token::SHL || _operator == Token::SAR);
return return
Whiskers(R"( Whiskers(R"(
@ -2886,7 +2909,7 @@ std::string IRGeneratorForStatements::shiftOperation(
void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _binOp) void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _binOp)
{ {
langutil::Token const op = _binOp.getOperator(); langutil::Token const op = _binOp.getOperator();
solAssert(op == Token::Or || op == Token::And, ""); solAssert(op == Token::Or || op == Token::And);
_binOp.leftExpression().accept(*this); _binOp.leftExpression().accept(*this);
setLocation(_binOp); setLocation(_binOp);
@ -2933,7 +2956,7 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
if (_memory.byteArrayElement) if (_memory.byteArrayElement)
{ {
solAssert(_lvalue.type == *TypeProvider::byte(), ""); solAssert(_lvalue.type == *TypeProvider::byte());
appendCode() << "mstore8(" + _memory.address + ", byte(0, " + prepared.commaSeparatedList() + "))\n"; appendCode() << "mstore8(" + _memory.address + ", byte(0, " + prepared.commaSeparatedList() + "))\n";
} }
else else
@ -2957,9 +2980,9 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
} }
else else
{ {
solAssert(_lvalue.type.sizeOnStack() == 1, ""); solAssert(_lvalue.type.sizeOnStack() == 1);
auto const* valueReferenceType = dynamic_cast<ReferenceType const*>(&_value.type()); auto const* valueReferenceType = dynamic_cast<ReferenceType const*>(&_value.type());
solAssert(valueReferenceType && valueReferenceType->dataStoredIn(DataLocation::Memory), ""); solAssert(valueReferenceType && valueReferenceType->dataStoredIn(DataLocation::Memory));
appendCode() << "mstore(" + _memory.address + ", " + _value.part("mpos").name() + ")\n"; appendCode() << "mstore(" + _memory.address + ", " + _value.part("mpos").name() + ")\n";
} }
}, },
@ -2968,7 +2991,7 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
{ {
solUnimplementedAssert(_lvalue.type.isValueType()); solUnimplementedAssert(_lvalue.type.isValueType());
solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1); solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1);
solAssert(_lvalue.type == *_immutable.variable->type(), ""); solAssert(_lvalue.type == *_immutable.variable->type());
size_t memOffset = m_context.immutableMemoryOffset(*_immutable.variable); size_t memOffset = m_context.immutableMemoryOffset(*_immutable.variable);
IRVariable prepared(m_context.newYulVariable(), _lvalue.type); IRVariable prepared(m_context.newYulVariable(), _lvalue.type);
@ -3028,7 +3051,7 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue)
[&](IRLValue::Immutable const& _immutable) { [&](IRLValue::Immutable const& _immutable) {
solUnimplementedAssert(_lvalue.type.isValueType()); solUnimplementedAssert(_lvalue.type.isValueType());
solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1); solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1);
solAssert(_lvalue.type == *_immutable.variable->type(), ""); solAssert(_lvalue.type == *_immutable.variable->type());
if (m_context.executionContext() == IRGenerationContext::ExecutionContext::Creation) if (m_context.executionContext() == IRGenerationContext::ExecutionContext::Creation)
{ {
string readFunction = m_utils.readFromMemory(*_immutable.variable->type()); string readFunction = m_utils.readFromMemory(*_immutable.variable->type());
@ -3050,13 +3073,13 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue)
void IRGeneratorForStatements::setLValue(Expression const& _expression, IRLValue _lvalue) void IRGeneratorForStatements::setLValue(Expression const& _expression, IRLValue _lvalue)
{ {
solAssert(!m_currentLValue, ""); solAssert(!m_currentLValue);
if (_expression.annotation().willBeWrittenTo) if (_expression.annotation().willBeWrittenTo)
{ {
m_currentLValue.emplace(std::move(_lvalue)); m_currentLValue.emplace(std::move(_lvalue));
if (_lvalue.type.dataStoredIn(DataLocation::CallData)) if (_lvalue.type.dataStoredIn(DataLocation::CallData))
solAssert(holds_alternative<IRLValue::Stack>(_lvalue.kind), ""); solAssert(holds_alternative<IRLValue::Stack>(_lvalue.kind));
} }
else else
// Only define the expression, if it will not be written to. // Only define the expression, if it will not be written to.
@ -3130,7 +3153,7 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement)
size_t i = 0; size_t i = 0;
for (ASTPointer<VariableDeclaration> const& varDecl: successClause.parameters()->parameters()) for (ASTPointer<VariableDeclaration> const& varDecl: successClause.parameters()->parameters())
{ {
solAssert(varDecl, ""); solAssert(varDecl);
define(m_context.addLocalVariable(*varDecl), define(m_context.addLocalVariable(*varDecl),
successClause.parameters()->parameters().size() == 1 ? successClause.parameters()->parameters().size() == 1 ?
IRVariable(externalCall) : IRVariable(externalCall) :
@ -3171,7 +3194,7 @@ void IRGeneratorForStatements::handleCatch(TryStatement const& _tryStatement)
appendCode() << runFallback << " := 0\n"; appendCode() << runFallback << " := 0\n";
if (errorClause->parameters()) if (errorClause->parameters())
{ {
solAssert(errorClause->parameters()->parameters().size() == 1, ""); solAssert(errorClause->parameters()->parameters().size() == 1);
IRVariable const& var = m_context.addLocalVariable(*errorClause->parameters()->parameters().front()); IRVariable const& var = m_context.addLocalVariable(*errorClause->parameters()->parameters().front());
define(var) << dataVariable << "\n"; define(var) << dataVariable << "\n";
} }
@ -3192,7 +3215,7 @@ void IRGeneratorForStatements::handleCatch(TryStatement const& _tryStatement)
appendCode() << runFallback << " := 0\n"; appendCode() << runFallback << " := 0\n";
if (panicClause->parameters()) if (panicClause->parameters())
{ {
solAssert(panicClause->parameters()->parameters().size() == 1, ""); solAssert(panicClause->parameters()->parameters().size() == 1);
IRVariable const& var = m_context.addLocalVariable(*panicClause->parameters()->parameters().front()); IRVariable const& var = m_context.addLocalVariable(*panicClause->parameters()->parameters().front());
define(var) << code << "\n"; define(var) << code << "\n";
} }
@ -3218,7 +3241,7 @@ void IRGeneratorForStatements::handleCatchFallback(TryCatchClause const& _fallba
setLocation(_fallback); setLocation(_fallback);
if (_fallback.parameters()) if (_fallback.parameters())
{ {
solAssert(m_context.evmVersion().supportsReturndata(), ""); solAssert(m_context.evmVersion().supportsReturndata());
solAssert( solAssert(
_fallback.parameters()->parameters().size() == 1 && _fallback.parameters()->parameters().size() == 1 &&
_fallback.parameters()->parameters().front() && _fallback.parameters()->parameters().front() &&
@ -3254,7 +3277,7 @@ void IRGeneratorForStatements::revertWithError(
for (ASTPointer<Expression const> const& arg: _errorArguments) for (ASTPointer<Expression const> const& arg: _errorArguments)
{ {
errorArgumentVars += IRVariable(*arg).stackSlots(); errorArgumentVars += IRVariable(*arg).stackSlots();
solAssert(arg->annotation().type, ""); solAssert(arg->annotation().type);
errorArgumentTypes.push_back(arg->annotation().type); errorArgumentTypes.push_back(arg->annotation().type);
} }
templ("argumentVars", joinHumanReadablePrefixed(errorArgumentVars)); templ("argumentVars", joinHumanReadablePrefixed(errorArgumentVars));
@ -3272,6 +3295,6 @@ bool IRGeneratorForStatements::visit(TryCatchClause const& _clause)
string IRGeneratorForStatements::linkerSymbol(ContractDefinition const& _library) const string IRGeneratorForStatements::linkerSymbol(ContractDefinition const& _library) const
{ {
solAssert(_library.isLibrary(), ""); solAssert(_library.isLibrary());
return "linkersymbol(" + util::escapeAndQuoteString(_library.fullyQualifiedName()) + ")"; return "linkersymbol(" + util::escapeAndQuoteString(_library.fullyQualifiedName()) + ")";
} }

View File

@ -78,6 +78,9 @@ CHC::CHC(
void CHC::analyze(SourceUnit const& _source) void CHC::analyze(SourceUnit const& _source)
{ {
if (!shouldAnalyze(_source))
return;
if (!m_settings.solvers.z3 && !m_settings.solvers.smtlib2) if (!m_settings.solvers.z3 && !m_settings.solvers.smtlib2)
{ {
if (!m_noSolverWarning) if (!m_noSolverWarning)
@ -137,6 +140,9 @@ vector<string> CHC::unhandledQueries() const
bool CHC::visit(ContractDefinition const& _contract) bool CHC::visit(ContractDefinition const& _contract)
{ {
if (!shouldAnalyze(_contract))
return false;
resetContractAnalysis(); resetContractAnalysis();
initContract(_contract); initContract(_contract);
clearIndices(&_contract); clearIndices(&_contract);
@ -152,6 +158,9 @@ bool CHC::visit(ContractDefinition const& _contract)
void CHC::endVisit(ContractDefinition const& _contract) void CHC::endVisit(ContractDefinition const& _contract)
{ {
if (!shouldAnalyze(_contract))
return;
for (auto base: _contract.annotation().linearizedBaseContracts) for (auto base: _contract.annotation().linearizedBaseContracts)
{ {
if (auto constructor = base->constructor()) if (auto constructor = base->constructor())

View File

@ -1035,6 +1035,12 @@ void SMTEncoder::visitPublicGetter(FunctionCall const& _funCall)
} }
} }
bool SMTEncoder::shouldAnalyze(SourceUnit const& _source) const
{
return m_settings.contracts.isDefault() ||
m_settings.contracts.has(*_source.annotation().path);
}
bool SMTEncoder::shouldAnalyze(ContractDefinition const& _contract) const bool SMTEncoder::shouldAnalyze(ContractDefinition const& _contract) const
{ {
if (!_contract.canBeDeployed()) if (!_contract.canBeDeployed())

View File

@ -223,6 +223,8 @@ protected:
/// @returns true if @param _contract is set for analysis in the settings /// @returns true if @param _contract is set for analysis in the settings
/// and it is not abstract. /// and it is not abstract.
bool shouldAnalyze(ContractDefinition const& _contract) const; bool shouldAnalyze(ContractDefinition const& _contract) const;
/// @returns true if @param _source is set for analysis in the settings.
bool shouldAnalyze(SourceUnit const& _source) const;
bool isPublicGetter(Expression const& _expr); bool isPublicGetter(Expression const& _expr);

View File

@ -44,7 +44,7 @@ struct OptimiserSettings
static char constexpr DefaultYulOptimiserSteps[] = static char constexpr DefaultYulOptimiserSteps[] =
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse "dhfoDgvulfnTUtnIf" // None of these can make stack problems worse
"[" "["
"xa[r]scLM" // Turn into SSA and simplify "xa[r]EscLM" // Turn into SSA and simplify
"cCTUtTOntnfDIul" // Perform structural simplification "cCTUtTOntnfDIul" // Perform structural simplification
"Lcul" // Simplify again "Lcul" // Simplify again
"Vcul [j]" // Reverse SSA "Vcul [j]" // Reverse SSA

View File

@ -38,6 +38,7 @@
#include <string> #include <string>
using namespace std; using namespace std;
using namespace std::string_literals;
using namespace std::placeholders; using namespace std::placeholders;
using namespace solidity::lsp; using namespace solidity::lsp;
@ -96,10 +97,10 @@ LanguageServer::LanguageServer(Transport& _transport):
{"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)}, {"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)},
{"initialized", [](auto, auto) {}}, {"initialized", [](auto, auto) {}},
{"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }}, {"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }},
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _1, _2)}, {"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _1, _2)}, {"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)},
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _1, _2)}, {"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)},
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _1, _2)}, {"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
}, },
m_fileRepository("/" /* basePath */), m_fileRepository("/" /* basePath */),
m_compilerStack{m_fileRepository.reader()} m_compilerStack{m_fileRepository.reader()}
@ -260,6 +261,10 @@ bool LanguageServer::run()
else else
m_client.error({}, ErrorCode::ParseError, "\"method\" has to be a string."); m_client.error({}, ErrorCode::ParseError, "\"method\" has to be a string.");
} }
catch (RequestError const& error)
{
m_client.error(id, error.code(), error.comment() ? *error.comment() : ""s);
}
catch (...) catch (...)
{ {
m_client.error(id, ErrorCode::InternalError, "Unhandled exception: "s + boost::current_exception_diagnostic_information()); m_client.error(id, ErrorCode::InternalError, "Unhandled exception: "s + boost::current_exception_diagnostic_information());
@ -268,24 +273,23 @@ bool LanguageServer::run()
return m_state == State::ExitRequested; return m_state == State::ExitRequested;
} }
bool LanguageServer::checkServerInitialized(MessageID _id) void LanguageServer::requireServerInitialized()
{ {
if (m_state != State::Initialized) lspAssert(
{ m_state == State::Initialized,
m_client.error(_id, ErrorCode::ServerNotInitialized, "Server is not properly initialized."); ErrorCode::ServerNotInitialized,
return false; "Server is not properly initialized."
} );
else
return true;
} }
void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args) void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
{ {
if (m_state != State::Started) lspAssert(
{ m_state == State::Started,
m_client.error(_id, ErrorCode::RequestFailed, "Initialize called at the wrong time."); ErrorCode::RequestFailed,
return; "Initialize called at the wrong time."
} );
m_state = State::Initialized; m_state = State::Initialized;
// The default of FileReader is to use `.`, but the path from where the LSP was started // The default of FileReader is to use `.`, but the path from where the LSP was started
@ -294,11 +298,12 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
if (Json::Value uri = _args["rootUri"]) if (Json::Value uri = _args["rootUri"])
{ {
rootPath = uri.asString(); rootPath = uri.asString();
if (!boost::starts_with(rootPath, "file://")) lspAssert(
{ boost::starts_with(rootPath, "file://"),
m_client.error(_id, ErrorCode::InvalidParams, "rootUri only supports file URI scheme."); ErrorCode::InvalidParams,
return; "rootUri only supports file URI scheme."
} );
rootPath = rootPath.substr(7); rootPath = rootPath.substr(7);
} }
else if (Json::Value rootPath = _args["rootPath"]) else if (Json::Value rootPath = _args["rootPath"])
@ -317,23 +322,23 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
m_client.reply(_id, move(replyArgs)); m_client.reply(_id, move(replyArgs));
} }
void LanguageServer::handleWorkspaceDidChangeConfiguration(Json::Value const& _args)
void LanguageServer::handleWorkspaceDidChangeConfiguration(MessageID _id, Json::Value const& _args)
{ {
if (!checkServerInitialized(_id)) requireServerInitialized();
return;
if (_args["settings"].isObject()) if (_args["settings"].isObject())
changeConfiguration(_args["settings"]); changeConfiguration(_args["settings"]);
} }
void LanguageServer::handleTextDocumentDidOpen(MessageID _id, Json::Value const& _args) void LanguageServer::handleTextDocumentDidOpen(Json::Value const& _args)
{ {
if (!checkServerInitialized(_id)) requireServerInitialized();
return;
if (!_args["textDocument"]) lspAssert(
m_client.error(_id, ErrorCode::RequestFailed, "Text document parameter missing."); _args["textDocument"],
ErrorCode::RequestFailed,
"Text document parameter missing."
);
string text = _args["textDocument"]["text"].asString(); string text = _args["textDocument"]["text"].asString();
string uri = _args["textDocument"]["uri"].asString(); string uri = _args["textDocument"]["uri"].asString();
@ -342,41 +347,37 @@ void LanguageServer::handleTextDocumentDidOpen(MessageID _id, Json::Value const&
compileAndUpdateDiagnostics(); compileAndUpdateDiagnostics();
} }
void LanguageServer::handleTextDocumentDidChange(MessageID _id, Json::Value const& _args) void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
{ {
if (!checkServerInitialized(_id)) requireServerInitialized();
return;
string const uri = _args["textDocument"]["uri"].asString(); string const uri = _args["textDocument"]["uri"].asString();
for (Json::Value jsonContentChange: _args["contentChanges"]) for (Json::Value jsonContentChange: _args["contentChanges"])
{ {
if (!jsonContentChange.isObject()) lspAssert(
{ jsonContentChange.isObject(),
m_client.error(_id, ErrorCode::RequestFailed, "Invalid content reference."); ErrorCode::RequestFailed,
return; "Invalid content reference."
} );
string const sourceUnitName = m_fileRepository.clientPathToSourceUnitName(uri); string const sourceUnitName = m_fileRepository.clientPathToSourceUnitName(uri);
if (!m_fileRepository.sourceUnits().count(sourceUnitName)) lspAssert(
{ m_fileRepository.sourceUnits().count(sourceUnitName),
m_client.error(_id, ErrorCode::RequestFailed, "Unknown file: " + uri); ErrorCode::RequestFailed,
return; "Unknown file: " + uri
} );
string text = jsonContentChange["text"].asString(); string text = jsonContentChange["text"].asString();
if (jsonContentChange["range"].isObject()) // otherwise full content update if (jsonContentChange["range"].isObject()) // otherwise full content update
{ {
optional<SourceLocation> change = parseRange(sourceUnitName, jsonContentChange["range"]); optional<SourceLocation> change = parseRange(sourceUnitName, jsonContentChange["range"]);
if (!change || !change->hasText()) lspAssert(
{ change && change->hasText(),
m_client.error( ErrorCode::RequestFailed,
_id, "Invalid source range: " + jsonCompactPrint(jsonContentChange["range"])
ErrorCode::RequestFailed, );
"Invalid source range: " + jsonCompactPrint(jsonContentChange["range"])
);
return;
}
string buffer = m_fileRepository.sourceUnits().at(sourceUnitName); string buffer = m_fileRepository.sourceUnits().at(sourceUnitName);
buffer.replace(static_cast<size_t>(change->start), static_cast<size_t>(change->end - change->start), move(text)); buffer.replace(static_cast<size_t>(change->start), static_cast<size_t>(change->end - change->start), move(text));
text = move(buffer); text = move(buffer);
@ -387,13 +388,15 @@ void LanguageServer::handleTextDocumentDidChange(MessageID _id, Json::Value cons
compileAndUpdateDiagnostics(); compileAndUpdateDiagnostics();
} }
void LanguageServer::handleTextDocumentDidClose(MessageID _id, Json::Value const& _args) void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args)
{ {
if (!checkServerInitialized(_id)) requireServerInitialized();
return;
if (!_args["textDocument"]) lspAssert(
m_client.error(_id, ErrorCode::RequestFailed, "Text document parameter missing."); _args["textDocument"],
ErrorCode::RequestFailed,
"Text document parameter missing."
);
string uri = _args["textDocument"]["uri"].asString(); string uri = _args["textDocument"]["uri"].asString();
m_openFiles.erase(uri); m_openFiles.erase(uri);

View File

@ -60,12 +60,12 @@ public:
private: private:
/// Checks if the server is initialized (to be used by messages that need it to be initialized). /// Checks if the server is initialized (to be used by messages that need it to be initialized).
/// Reports an error and returns false if not. /// Reports an error and returns false if not.
bool checkServerInitialized(MessageID _id); void requireServerInitialized();
void handleInitialize(MessageID _id, Json::Value const& _args); void handleInitialize(MessageID _id, Json::Value const& _args);
void handleWorkspaceDidChangeConfiguration(MessageID _id, Json::Value const& _args); void handleWorkspaceDidChangeConfiguration(Json::Value const& _args);
void handleTextDocumentDidOpen(MessageID _id, Json::Value const& _args); void handleTextDocumentDidOpen(Json::Value const& _args);
void handleTextDocumentDidChange(MessageID _id, Json::Value const& _args); void handleTextDocumentDidChange(Json::Value const& _args);
void handleTextDocumentDidClose(MessageID _id, Json::Value const& _args); void handleTextDocumentDidClose(Json::Value const& _args);
/// Invoked when the server user-supplied configuration changes (initiated by the client). /// Invoked when the server user-supplied configuration changes (initiated by the client).
void changeConfiguration(Json::Value const&); void changeConfiguration(Json::Value const&);

View File

@ -17,6 +17,8 @@
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
#pragma once #pragma once
#include <libsolutil/Exceptions.h>
#include <json/value.h> #include <json/value.h>
#include <functional> #include <functional>
@ -45,6 +47,32 @@ enum class ErrorCode
RequestFailed = -32803 RequestFailed = -32803
}; };
/**
* Error exception used to bail out on errors in the LSP function-call handlers.
*/
class RequestError: public util::Exception
{
public:
explicit RequestError(ErrorCode _code):
m_code{_code}
{
}
ErrorCode code() const noexcept { return m_code; }
private:
ErrorCode m_code;
};
#define lspAssert(condition, errorCode, errorMessage) \
if (!(condition)) \
{ \
BOOST_THROW_EXCEPTION( \
RequestError(errorCode) << \
errinfo_comment(errorMessage) \
); \
}
/** /**
* Transport layer API * Transport layer API
* *

View File

@ -120,6 +120,8 @@ add_library(yul
optimiser/DeadCodeEliminator.h optimiser/DeadCodeEliminator.h
optimiser/Disambiguator.cpp optimiser/Disambiguator.cpp
optimiser/Disambiguator.h optimiser/Disambiguator.h
optimiser/EqualStoreEliminator.cpp
optimiser/EqualStoreEliminator.h
optimiser/EquivalentFunctionDetector.cpp optimiser/EquivalentFunctionDetector.cpp
optimiser/EquivalentFunctionDetector.h optimiser/EquivalentFunctionDetector.h
optimiser/EquivalentFunctionCombiner.cpp optimiser/EquivalentFunctionCombiner.cpp

View File

@ -0,0 +1,70 @@
/*
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/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Optimisation stage that removes mstore and sstore operations if they store the same
* value that is already known to be in that slot.
*/
#include <libyul/optimiser/EqualStoreEliminator.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/AST.h>
#include <libyul/Utilities.h>
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::evmasm;
using namespace solidity::yul;
void EqualStoreEliminator::run(OptimiserStepContext const& _context, Block& _ast)
{
EqualStoreEliminator eliminator{
_context.dialect,
SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast))
};
eliminator(_ast);
StatementRemover remover{eliminator.m_pendingRemovals};
remover(_ast);
}
void EqualStoreEliminator::visit(Statement& _statement)
{
// No need to consider potential changes through complex arguments since
// isSimpleStore only returns something if the arguments are identifiers.
if (ExpressionStatement const* expression = get_if<ExpressionStatement>(&_statement))
{
if (auto vars = isSimpleStore(StoreLoadLocation::Storage, *expression))
{
if (auto const* currentValue = valueOrNullptr(m_storage, vars->first))
if (*currentValue == vars->second)
m_pendingRemovals.insert(&_statement);
}
else if (auto vars = isSimpleStore(StoreLoadLocation::Memory, *expression))
{
if (auto const* currentValue = valueOrNullptr(m_memory, vars->first))
if (*currentValue == vars->second)
m_pendingRemovals.insert(&_statement);
}
}
DataFlowAnalyzer::visit(_statement);
}

View File

@ -0,0 +1,60 @@
/*
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/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Optimisation stage that removes mstore and sstore operations if they store the same
* value that is already known to be in that slot.
*/
#pragma once
#include <libyul/optimiser/DataFlowAnalyzer.h>
#include <libyul/optimiser/OptimiserStep.h>
namespace solidity::yul
{
/**
* Optimisation stage that removes mstore and sstore operations if they store the same
* value that is already known to be in that slot.
*
* Works best if the code is in SSA form - without literal arguments.
*
* Prerequisite: Disambiguator, ForLoopInitRewriter.
*/
class EqualStoreEliminator: public DataFlowAnalyzer
{
public:
static constexpr char const* name{"EqualStoreEliminator"};
static void run(OptimiserStepContext const&, Block& _ast);
private:
EqualStoreEliminator(
Dialect const& _dialect,
std::map<YulString, SideEffects> _functionSideEffects
):
DataFlowAnalyzer(_dialect, std::move(_functionSideEffects))
{}
protected:
using ASTModifier::visit;
void visit(Statement& _statement) override;
std::set<Statement const*> m_pendingRemovals;
};
}

View File

@ -57,3 +57,18 @@ optional<evmasm::Instruction> yul::toEVMInstruction(Dialect const& _dialect, Yul
return builtin->instruction; return builtin->instruction;
return nullopt; return nullopt;
} }
void StatementRemover::operator()(Block& _block)
{
util::iterateReplacing(
_block.statements,
[&](Statement& _statement) -> std::optional<vector<Statement>>
{
if (m_toRemove.count(&_statement))
return {vector<Statement>{}};
else
return nullopt;
}
);
ASTModifier::operator()(_block);
}

View File

@ -25,6 +25,7 @@
#include <libyul/ASTForward.h> #include <libyul/ASTForward.h>
#include <libyul/Dialect.h> #include <libyul/Dialect.h>
#include <libyul/YulString.h> #include <libyul/YulString.h>
#include <libyul/optimiser/ASTWalker.h>
#include <optional> #include <optional>
@ -48,4 +49,14 @@ bool isRestrictedIdentifier(Dialect const& _dialect, YulString const& _identifie
/// Helper function that returns the instruction, if the `_name` is a BuiltinFunction /// Helper function that returns the instruction, if the `_name` is a BuiltinFunction
std::optional<evmasm::Instruction> toEVMInstruction(Dialect const& _dialect, YulString const& _name); std::optional<evmasm::Instruction> toEVMInstruction(Dialect const& _dialect, YulString const& _name);
class StatementRemover: public ASTModifier
{
public:
explicit StatementRemover(std::set<Statement const*> const& _toRemove): m_toRemove(_toRemove) {}
void operator()(Block& _block) override;
private:
std::set<Statement const*> const& m_toRemove;
};
} }

View File

@ -32,6 +32,7 @@
#include <libyul/optimiser/DeadCodeEliminator.h> #include <libyul/optimiser/DeadCodeEliminator.h>
#include <libyul/optimiser/FunctionGrouper.h> #include <libyul/optimiser/FunctionGrouper.h>
#include <libyul/optimiser/FunctionHoister.h> #include <libyul/optimiser/FunctionHoister.h>
#include <libyul/optimiser/EqualStoreEliminator.h>
#include <libyul/optimiser/EquivalentFunctionCombiner.h> #include <libyul/optimiser/EquivalentFunctionCombiner.h>
#include <libyul/optimiser/ExpressionSplitter.h> #include <libyul/optimiser/ExpressionSplitter.h>
#include <libyul/optimiser/ExpressionJoiner.h> #include <libyul/optimiser/ExpressionJoiner.h>
@ -204,6 +205,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
ConditionalUnsimplifier, ConditionalUnsimplifier,
ControlFlowSimplifier, ControlFlowSimplifier,
DeadCodeEliminator, DeadCodeEliminator,
EqualStoreEliminator,
EquivalentFunctionCombiner, EquivalentFunctionCombiner,
ExpressionInliner, ExpressionInliner,
ExpressionJoiner, ExpressionJoiner,
@ -244,6 +246,7 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
{ConditionalUnsimplifier::name, 'U'}, {ConditionalUnsimplifier::name, 'U'},
{ControlFlowSimplifier::name, 'n'}, {ControlFlowSimplifier::name, 'n'},
{DeadCodeEliminator::name, 'D'}, {DeadCodeEliminator::name, 'D'},
{EqualStoreEliminator::name, 'E'},
{EquivalentFunctionCombiner::name, 'v'}, {EquivalentFunctionCombiner::name, 'v'},
{ExpressionInliner::name, 'e'}, {ExpressionInliner::name, 'e'},
{ExpressionJoiner::name, 'j'}, {ExpressionJoiner::name, 'j'},

View File

@ -23,6 +23,7 @@
#include <libyul/optimiser/UnusedAssignEliminator.h> #include <libyul/optimiser/UnusedAssignEliminator.h>
#include <libyul/optimiser/Semantics.h> #include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/AST.h> #include <libyul/AST.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>

View File

@ -156,18 +156,3 @@ void UnusedStoreBase::merge(TrackedStores& _target, vector<TrackedStores>&& _sou
merge(_target, move(ts)); merge(_target, move(ts));
_source.clear(); _source.clear();
} }
void StatementRemover::operator()(Block& _block)
{
util::iterateReplacing(
_block.statements,
[&](Statement& _statement) -> std::optional<vector<Statement>>
{
if (m_toRemove.count(&_statement))
return {vector<Statement>{}};
else
return nullopt;
}
);
ASTModifier::operator()(_block);
}

View File

@ -105,14 +105,4 @@ protected:
size_t m_forLoopNestingDepth = 0; size_t m_forLoopNestingDepth = 0;
}; };
class StatementRemover: public ASTModifier
{
public:
explicit StatementRemover(std::set<Statement const*> const& _toRemove): m_toRemove(_toRemove) {}
void operator()(Block& _block) override;
private:
std::set<Statement const*> const& m_toRemove;
};
} }

View File

@ -34,7 +34,7 @@ else
BUILD_DIR="$1" BUILD_DIR="$1"
fi fi
# solbuildpackpusher/solidity-buildpack-deps:emscripten-8 # solbuildpackpusher/solidity-buildpack-deps:emscripten-9
docker run -v "$(pwd):/root/project" -w /root/project \ docker run -v "$(pwd):/root/project" -w /root/project \
solbuildpackpusher/solidity-buildpack-deps@sha256:842d6074e0e7e5355c89122c1cafc1fdb59696596750e7d56e5f35c0d883ad59 \ solbuildpackpusher/solidity-buildpack-deps@sha256:d51534dfdd05ece86f69ed7beafd68c15b88606da00a4b7fe2873ccfbd0dce24\
./scripts/ci/build_emscripten.sh "$BUILD_DIR" ./scripts/ci/build_emscripten.sh "$BUILD_DIR"

View File

@ -187,7 +187,7 @@ def parse_cli_output(source_file_name: Path, cli_output: str) -> FileReport:
return file_report return file_report
def prepare_compiler_input( # pylint: disable=too-many-arguments def prepare_compiler_input(
compiler_path: Path, compiler_path: Path,
source_file_name: Path, source_file_name: Path,
optimize: bool, optimize: bool,
@ -256,7 +256,7 @@ def detect_metadata_cli_option_support(compiler_path: Path):
return process.returncode == 0 return process.returncode == 0
def run_compiler( # pylint: disable=too-many-arguments def run_compiler(
compiler_path: Path, compiler_path: Path,
source_file_name: Path, source_file_name: Path,
optimize: bool, optimize: bool,
@ -320,7 +320,7 @@ def run_compiler( # pylint: disable=too-many-arguments
return parse_cli_output(Path(source_file_name), process.stdout) return parse_cli_output(Path(source_file_name), process.stdout)
def generate_report( # pylint: disable=too-many-arguments,too-many-locals def generate_report(
source_file_names: List[str], source_file_names: List[str],
compiler_path: Path, compiler_path: Path,
interface: CompilerInterface, interface: CompilerInterface,

View File

@ -25,9 +25,9 @@ set -ev
keyid=70D110489D66E2F6 keyid=70D110489D66E2F6
email=builds@ethereum.org email=builds@ethereum.org
packagename=z3-static packagename=z3-static
version=4.8.13 version=4.8.14
DISTRIBUTIONS="focal groovy hirsute" DISTRIBUTIONS="focal hirsute impish jammy"
for distribution in $DISTRIBUTIONS for distribution in $DISTRIBUTIONS
do do

View File

@ -33,12 +33,12 @@
# Using $(em-config CACHE)/sysroot/usr seems to work, though, and still has cmake find the # Using $(em-config CACHE)/sysroot/usr seems to work, though, and still has cmake find the
# dependencies automatically. # dependencies automatically.
FROM emscripten/emsdk:2.0.33 AS base FROM emscripten/emsdk:2.0.33 AS base
LABEL version="8" LABEL version="9"
ADD emscripten.jam /usr/src ADD emscripten.jam /usr/src
RUN set -ex; \ RUN set -ex; \
cd /usr/src; \ cd /usr/src; \
git clone https://github.com/Z3Prover/z3.git -b z3-4.8.13 --depth 1 ; \ git clone https://github.com/Z3Prover/z3.git -b z3-4.8.14 --depth 1 ; \
cd z3; \ cd z3; \
mkdir build; \ mkdir build; \
cd build; \ cd build; \

View File

@ -22,7 +22,7 @@
# (c) 2016-2021 solidity contributors. # (c) 2016-2021 solidity contributors.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
FROM gcr.io/oss-fuzz-base/base-clang:latest as base FROM gcr.io/oss-fuzz-base/base-clang:latest as base
LABEL version="14" LABEL version="15"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
@ -61,7 +61,7 @@ RUN set -ex; \
# Z3 # Z3
RUN set -ex; \ RUN set -ex; \
git clone --depth 1 -b z3-4.8.13 https://github.com/Z3Prover/z3.git \ git clone --depth 1 -b z3-4.8.14 https://github.com/Z3Prover/z3.git \
/usr/src/z3; \ /usr/src/z3; \
cd /usr/src/z3; \ cd /usr/src/z3; \
mkdir build; \ mkdir build; \

View File

@ -22,7 +22,7 @@
# (c) 2016-2019 solidity contributors. # (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
FROM buildpack-deps:focal AS base FROM buildpack-deps:focal AS base
LABEL version="9" LABEL version="10"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive

View File

@ -22,7 +22,7 @@
# (c) 2016-2019 solidity contributors. # (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
FROM buildpack-deps:focal AS base FROM buildpack-deps:focal AS base
LABEL version="9" LABEL version="10"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive

View File

@ -33,12 +33,12 @@ def parse_call(call):
return function.strip(), arguments.strip(), results.strip() return function.strip(), arguments.strip(), results.strip()
def colorize(left, right, id): def colorize(left, right, index):
red = "\x1b[31m" red = "\x1b[31m"
yellow = "\x1b[33m" yellow = "\x1b[33m"
reset = "\x1b[0m" reset = "\x1b[0m"
colors = [red, yellow] colors = [red, yellow]
color = colors[id % len(colors)] color = colors[index % len(colors)]
function, _arguments, _results = parse_call(right) function, _arguments, _results = parse_call(right)
left = left.replace("compileAndRun", color + "compileAndRun" + reset) left = left.replace("compileAndRun", color + "compileAndRun" + reset)
right = right.replace("constructor", color + "constructor" + reset) right = right.replace("constructor", color + "constructor" + reset)

View File

@ -9,8 +9,6 @@
# #
# verify-testcases.py will compare both traces. If these traces are identical, the extracted tests were # verify-testcases.py will compare both traces. If these traces are identical, the extracted tests were
# identical with the tests specified in SolidityEndToEndTest.cpp. # identical with the tests specified in SolidityEndToEndTest.cpp.
#
# pylint: disable=too-many-instance-attributes
import re import re
import os import os
@ -32,11 +30,11 @@ class Trace:
def get_input(self): def get_input(self):
return self._input return self._input
def set_input(self, input): def set_input(self, bytecode):
if self.kind == "create": if self.kind == "create":
# remove cbor encoded metadata from bytecode # remove cbor encoded metadata from bytecode
length = int(input[-4:], 16) * 2 length = int(bytecode[-4:], 16) * 2
self._input = input[:len(input) - length - 4] self._input = bytecode[:len(bytecode) - length - 4]
def get_output(self): def get_output(self):
return self._output return self._output
@ -110,21 +108,21 @@ class TraceAnalyser:
@staticmethod @staticmethod
def parse_parameters(line, trace): def parse_parameters(line, trace):
input = re.search(r'\s*in:\s*([a-fA-F0-9]*)', line, re.M | re.I) input_match = re.search(r'\s*in:\s*([a-fA-F0-9]*)', line, re.M | re.I)
if input: if input_match:
trace.input = input.group(1) trace.input = input_match.group(1)
output = re.search(r'\s*out:\s*([a-fA-F0-9]*)', line, re.M | re.I) output_match = re.search(r'\s*out:\s*([a-fA-F0-9]*)', line, re.M | re.I)
if output: if output_match:
trace.output = output.group(1) trace.output = output_match.group(1)
result = re.search(r'\s*result:\s*([a-fA-F0-9]*)', line, re.M | re.I) result_match = re.search(r'\s*result:\s*([a-fA-F0-9]*)', line, re.M | re.I)
if result: if result_match:
trace.result = result.group(1) trace.result = result_match.group(1)
gas_used = re.search(r'\s*gas\sused:\s*([a-fA-F0-9]*)', line, re.M | re.I) gas_used_match = re.search(r'\s*gas\sused:\s*([a-fA-F0-9]*)', line, re.M | re.I)
if gas_used: if gas_used_match:
trace.gas = gas_used.group(1) trace.gas = gas_used_match.group(1)
value = re.search(r'\s*value:\s*([a-fA-F0-9]*)', line, re.M | re.I) value_match = re.search(r'\s*value:\s*([a-fA-F0-9]*)', line, re.M | re.I)
if value: if value_match:
trace.value = value.group(1) trace.value = value_match.group(1)
def diff(self, analyser): def diff(self, analyser):
if not self.ready: if not self.ready:
@ -154,7 +152,8 @@ class TraceAnalyser:
print(len(intersection), "test-cases - ", len(mismatches), " mismatche(s)") print(len(intersection), "test-cases - ", len(mismatches), " mismatche(s)")
def check_traces(self, test_name, left, right, mismatches): @classmethod
def check_traces(cls, test_name, left, right, mismatches):
for trace_id, trace in enumerate(left.traces): for trace_id, trace in enumerate(left.traces):
left_trace = trace left_trace = trace
right_trace = right.traces[trace_id] right_trace = right.traces[trace_id]

View File

@ -18,7 +18,7 @@ def read_file(file_name):
with open(file_name, "r", encoding="latin-1" if is_latin else ENCODING) as f: with open(file_name, "r", encoding="latin-1" if is_latin else ENCODING) as f:
content = f.read() content = f.read()
finally: finally:
if content == None: if content is None:
print(f"Error reading: {file_name}") print(f"Error reading: {file_name}")
return content return content
@ -44,11 +44,11 @@ def find_ids_in_source_file(file_name, id_to_file_names):
if in_comment(source, m.start()): if in_comment(source, m.start()):
continue continue
underscore_pos = m.group(0).index("_") underscore_pos = m.group(0).index("_")
id = m.group(0)[0:underscore_pos] error_id = m.group(0)[0:underscore_pos]
if id in id_to_file_names: if error_id in id_to_file_names:
id_to_file_names[id].append(file_name) id_to_file_names[error_id].append(file_name)
else: else:
id_to_file_names[id] = [file_name] id_to_file_names[error_id] = [file_name]
def find_ids_in_source_files(file_names): def find_ids_in_source_files(file_names):
@ -76,16 +76,16 @@ def fix_ids_in_source_file(file_name, id_to_count, available_ids):
destination.extend(source[k:m.start()]) destination.extend(source[k:m.start()])
underscore_pos = m.group(0).index("_") underscore_pos = m.group(0).index("_")
id = m.group(0)[0:underscore_pos] error_id = m.group(0)[0:underscore_pos]
# incorrect id or id has a duplicate somewhere # incorrect id or id has a duplicate somewhere
if not in_comment(source, m.start()) and (len(id) != 4 or id[0] == "0" or id_to_count[id] > 1): if not in_comment(source, m.start()) and (len(error_id) != 4 or error_id[0] == "0" or id_to_count[error_id] > 1):
assert id in id_to_count assert error_id in id_to_count
new_id = get_next_id(available_ids) new_id = get_next_id(available_ids)
assert new_id not in id_to_count assert new_id not in id_to_count
id_to_count[id] -= 1 id_to_count[error_id] -= 1
else: else:
new_id = id new_id = error_id
destination.extend(new_id + "_error") destination.extend(new_id + "_error")
k = m.end() k = m.end()
@ -104,7 +104,7 @@ def fix_ids_in_source_files(file_names, id_to_count):
id_to_count contains number of appearances of every id in sources id_to_count contains number of appearances of every id in sources
""" """
available_ids = {str(id) for id in range(1000, 10000)} - id_to_count.keys() available_ids = {str(error_id) for error_id in range(1000, 10000)} - id_to_count.keys()
for file_name in file_names: for file_name in file_names:
fix_ids_in_source_file(file_name, id_to_count, available_ids) fix_ids_in_source_file(file_name, id_to_count, available_ids)
@ -113,8 +113,8 @@ def find_files(top_dir, sub_dirs, extensions):
"""Builds a list of files with given extensions in specified subdirectories""" """Builds a list of files with given extensions in specified subdirectories"""
source_file_names = [] source_file_names = []
for dir in sub_dirs: for directory in sub_dirs:
for root, _, file_names in os.walk(os.path.join(top_dir, dir), onerror=lambda e: exit(f"Walk error: {e}")): for root, _, file_names in os.walk(os.path.join(top_dir, directory), onerror=lambda e: sys.exit(f"Walk error: {e}")):
for file_name in file_names: for file_name in file_names:
_, ext = path.splitext(file_name) _, ext = path.splitext(file_name)
if ext in extensions: if ext in extensions:
@ -145,27 +145,27 @@ def find_ids_in_cmdline_test_err(file_name):
def print_ids(ids): def print_ids(ids):
for k, id in enumerate(sorted(ids)): for k, error_id in enumerate(sorted(ids)):
if k % 10 > 0: if k % 10 > 0:
print(" ", end="") print(" ", end="")
elif k > 0: elif k > 0:
print() print()
print(id, end="") print(error_id, end="")
def print_ids_per_file(ids, id_to_file_names, top_dir): def print_ids_per_file(ids, id_to_file_names, top_dir):
file_name_to_ids = {} file_name_to_ids = {}
for id in ids: for error_id in ids:
for file_name in id_to_file_names[id]: for file_name in id_to_file_names[error_id]:
relpath = path.relpath(file_name, top_dir) relpath = path.relpath(file_name, top_dir)
if relpath not in file_name_to_ids: if relpath not in file_name_to_ids:
file_name_to_ids[relpath] = [] file_name_to_ids[relpath] = []
file_name_to_ids[relpath].append(id) file_name_to_ids[relpath].append(error_id)
for file_name in sorted(file_name_to_ids): for file_name in sorted(file_name_to_ids):
print(file_name) print(file_name)
for id in sorted(file_name_to_ids[file_name]): for error_id in sorted(file_name_to_ids[file_name]):
print(f" {id}", end="") print(f" {error_id}", end="")
print() print()
@ -254,8 +254,6 @@ def examine_id_coverage(top_dir, source_id_to_file_names, new_ids_only=False):
def main(argv): def main(argv):
# pylint: disable=too-many-branches, too-many-locals, too-many-statements
check = False check = False
fix = False fix = False
no_confirm = False no_confirm = False
@ -277,7 +275,7 @@ def main(argv):
if [check, fix, examine_coverage, next_id].count(True) != 1: if [check, fix, examine_coverage, next_id].count(True) != 1:
print("usage: python error_codes.py --check | --fix [--no-confirm] | --examine-coverage | --next") print("usage: python error_codes.py --check | --fix [--no-confirm] | --examine-coverage | --next")
exit(1) sys.exit(1)
cwd = os.getcwd() cwd = os.getcwd()
@ -289,23 +287,23 @@ def main(argv):
source_id_to_file_names = find_ids_in_source_files(source_file_names) source_id_to_file_names = find_ids_in_source_files(source_file_names)
ok = True ok = True
for id in sorted(source_id_to_file_names): for error_id in sorted(source_id_to_file_names):
if len(id) != 4: if len(error_id) != 4:
print(f"ID {id} length != 4") print(f"ID {error_id} length != 4")
ok = False ok = False
if id[0] == "0": if error_id[0] == "0":
print(f"ID {id} starts with zero") print(f"ID {error_id} starts with zero")
ok = False ok = False
if len(source_id_to_file_names[id]) > 1: if len(source_id_to_file_names[error_id]) > 1:
print(f"ID {id} appears {len(source_id_to_file_names[id])} times") print(f"ID {error_id} appears {len(source_id_to_file_names[error_id])} times")
ok = False ok = False
if examine_coverage: if examine_coverage:
if not ok: if not ok:
print("Incorrect IDs have to be fixed before applying --examine-coverage") print("Incorrect IDs have to be fixed before applying --examine-coverage")
exit(1) sys.exit(1)
res = 0 if examine_id_coverage(cwd, source_id_to_file_names) else 1 res = 0 if examine_id_coverage(cwd, source_id_to_file_names) else 1
exit(res) sys.exit(res)
ok &= examine_id_coverage(cwd, source_id_to_file_names, new_ids_only=True) ok &= examine_id_coverage(cwd, source_id_to_file_names, new_ids_only=True)
@ -314,18 +312,18 @@ def main(argv):
if next_id: if next_id:
if not ok: if not ok:
print("Incorrect IDs have to be fixed before applying --next") print("Incorrect IDs have to be fixed before applying --next")
exit(1) sys.exit(1)
available_ids = {str(id) for id in range(1000, 10000)} - source_id_to_file_names.keys() available_ids = {str(error_id) for error_id in range(1000, 10000)} - source_id_to_file_names.keys()
next_id = get_next_id(available_ids) next_id = get_next_id(available_ids)
print(f"Next ID: {next_id}") print(f"Next ID: {next_id}")
exit(0) sys.exit(0)
if ok: if ok:
print("No incorrect IDs found") print("No incorrect IDs found")
exit(0) sys.exit(0)
if check: if check:
exit(1) sys.exit(1)
assert fix, "Unexpected state, should not come here without --fix" assert fix, "Unexpected state, should not come here without --fix"
@ -338,14 +336,14 @@ def main(argv):
while len(answer) == 0 or answer not in "YNyn": while len(answer) == 0 or answer not in "YNyn":
answer = input("[Y/N]? ") answer = input("[Y/N]? ")
if answer not in "yY": if answer not in "yY":
exit(1) sys.exit(1)
# number of appearances for every id # number of appearances for every id
source_id_to_count = { id: len(file_names) for id, file_names in source_id_to_file_names.items() } source_id_to_count = { error_id: len(file_names) for error_id, file_names in source_id_to_file_names.items() }
fix_ids_in_source_files(source_file_names, source_id_to_count) fix_ids_in_source_files(source_file_names, source_id_to_count)
print("Fixing completed") print("Fixing completed")
exit(2) sys.exit(2)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -106,8 +106,8 @@ def write_cases(f, solidityTests, yulTests):
# When code examples are extracted they are indented by 8 spaces, which violates the style guide, # When code examples are extracted they are indented by 8 spaces, which violates the style guide,
# so before checking remove 4 spaces from each line. # so before checking remove 4 spaces from each line.
remainder = dedent(test) remainder = dedent(test)
hash = hashlib.sha256(test.encode("utf-8")).hexdigest() source_code_hash = hashlib.sha256(test.encode("utf-8")).hexdigest()
sol_filename = f'test_{hash}_{cleaned_filename}.{language}' sol_filename = f'test_{source_code_hash}_{cleaned_filename}.{language}'
with open(sol_filename, mode='w', encoding='utf8', newline='') as fi: with open(sol_filename, mode='w', encoding='utf8', newline='') as fi:
fi.write(remainder) fi.write(remainder)

View File

@ -6,9 +6,9 @@ Runs pylint on all Python files in project directories known to contain Python s
from argparse import ArgumentParser from argparse import ArgumentParser
from os import path, walk from os import path, walk
from sys import exit
from textwrap import dedent from textwrap import dedent
import subprocess import subprocess
import sys
PROJECT_ROOT = path.dirname(path.dirname(path.realpath(__file__))) PROJECT_ROOT = path.dirname(path.dirname(path.realpath(__file__)))
PYLINT_RCFILE = f"{PROJECT_ROOT}/scripts/pylintrc" PYLINT_RCFILE = f"{PROJECT_ROOT}/scripts/pylintrc"
@ -89,7 +89,7 @@ def main():
success = pylint_all_filenames(options.dev_mode, rootdirs) success = pylint_all_filenames(options.dev_mode, rootdirs)
if not success: if not success:
exit(1) sys.exit(1)
else: else:
print("No problems found.") print("No problems found.")
@ -98,4 +98,4 @@ if __name__ == "__main__":
try: try:
main() main()
except KeyboardInterrupt: except KeyboardInterrupt:
exit("Interrupted by user. Exiting.") sys.exit("Interrupted by user. Exiting.")

View File

@ -14,24 +14,23 @@
# ATTENTION: This list should be extended with care, consider using NOLINT comments inside your # ATTENTION: This list should be extended with care, consider using NOLINT comments inside your
# python files instead, as the goal is actually to reduce the list of globally disabled checks. # python files instead, as the goal is actually to reduce the list of globally disabled checks.
# #
# TODO: What could be eliminated in future PRs: bad-continuation, invalid-name, redefined-builtin, # TODO: What could be eliminated in future PRs: invalid-name, pointless-string-statement, redefined-outer-name.
# undefined-variable, unused-*, useless-object-inheritance.
disable= disable=
bad-continuation,
bad-indentation, bad-indentation,
bad-whitespace, bad-whitespace,
consider-using-sys-exit,
duplicate-code, duplicate-code,
invalid-name, invalid-name,
missing-docstring, missing-docstring,
no-else-return, no-else-return,
no-self-use,
pointless-string-statement, pointless-string-statement,
redefined-builtin,
redefined-outer-name, redefined-outer-name,
singleton-comparison,
too-few-public-methods, too-few-public-methods,
too-many-arguments,
too-many-branches,
too-many-instance-attributes,
too-many-locals,
too-many-public-methods, too-many-public-methods,
too-many-statements,
ungrouped-imports ungrouped-imports
[BASIC] [BASIC]

View File

@ -41,7 +41,8 @@ class regressor:
"build/test/tools/ossfuzz") "build/test/tools/ossfuzz")
self._logpath = os.path.join(self._repo_root, "test_results") self._logpath = os.path.join(self._repo_root, "test_results")
def parseCmdLine(self, description, args): @classmethod
def parseCmdLine(cls, description, args):
argParser = ArgumentParser(description) argParser = ArgumentParser(description)
argParser.add_argument('-o', '--out-dir', required=True, type=str, argParser.add_argument('-o', '--out-dir', required=True, type=str,
help="""Directory where test results will be written""") help="""Directory where test results will be written""")

View File

@ -57,7 +57,7 @@ packagename=solc
static_build_distribution=hirsute static_build_distribution=hirsute
DISTRIBUTIONS="focal hirsute impish" DISTRIBUTIONS="focal hirsute impish jammy"
if is_release if is_release
then then

View File

@ -40,7 +40,7 @@ def writeSourceToFile(lines):
filePath, srcName = extractSourceName(lines[0]) filePath, srcName = extractSourceName(lines[0])
# print("sourceName is ", srcName) # print("sourceName is ", srcName)
# print("filePath is", filePath) # print("filePath is", filePath)
if filePath != False: if filePath:
os.system("mkdir -p " + filePath) os.system("mkdir -p " + filePath)
with open(srcName, mode='a+', encoding='utf8', newline='') as f: with open(srcName, mode='a+', encoding='utf8', newline='') as f:
createdSources.append(srcName) createdSources.append(srcName)

View File

@ -47,8 +47,8 @@ def write_cases(f, tests):
cleaned_filename = f.replace(".","_").replace("-","_").replace(" ","_").lower() cleaned_filename = f.replace(".","_").replace("-","_").replace(" ","_").lower()
for test in tests: for test in tests:
remainder = re.sub(r'^ {4}', '', test, 0, re.MULTILINE) remainder = re.sub(r'^ {4}', '', test, 0, re.MULTILINE)
hash = hashlib.sha256(test).hexdigest() source_code_hash = hashlib.sha256(test).hexdigest()
with open(f'test_{hash}_{cleaned_filename}.sol', 'w', encoding='utf8') as _f: with open(f'test_{source_code_hash}_{cleaned_filename}.sol', 'w', encoding='utf8') as _f:
_f.write(remainder) _f.write(remainder)

View File

@ -102,6 +102,8 @@ void CommonOptions::addOptions()
("testpath", po::value<fs::path>(&this->testPath)->default_value(solidity::test::testPath()), "path to test files") ("testpath", po::value<fs::path>(&this->testPath)->default_value(solidity::test::testPath()), "path to test files")
("vm", po::value<std::vector<fs::path>>(&vmPaths), "path to evmc library, can be supplied multiple times.") ("vm", po::value<std::vector<fs::path>>(&vmPaths), "path to evmc library, can be supplied multiple times.")
("ewasm", po::bool_switch(&ewasm)->default_value(ewasm), "tries to automatically find an ewasm vm and enable ewasm test-execution.") ("ewasm", po::bool_switch(&ewasm)->default_value(ewasm), "tries to automatically find an ewasm vm and enable ewasm test-execution.")
("batches", po::value<size_t>(&this->batches)->default_value(1), "set number of batches to split the tests into")
("selected-batch", po::value<size_t>(&this->selectedBatch)->default_value(0), "zero-based number of batch to execute")
("no-semantic-tests", po::bool_switch(&disableSemanticTests)->default_value(disableSemanticTests), "disable semantic tests") ("no-semantic-tests", po::bool_switch(&disableSemanticTests)->default_value(disableSemanticTests), "disable semantic tests")
("no-smt", po::bool_switch(&disableSMT)->default_value(disableSMT), "disable SMT checker") ("no-smt", po::bool_switch(&disableSMT)->default_value(disableSMT), "disable SMT checker")
("optimize", po::bool_switch(&optimize)->default_value(optimize), "enables optimization") ("optimize", po::bool_switch(&optimize)->default_value(optimize), "enables optimization")
@ -126,6 +128,17 @@ void CommonOptions::validate() const
ConfigException, ConfigException,
"Invalid test path specified." "Invalid test path specified."
); );
assertThrow(
batches > 0,
ConfigException,
"Batches needs to be at least 1."
);
assertThrow(
selectedBatch < batches,
ConfigException,
"Selected batch has to be less than number of batches."
);
if (enforceGasTest) if (enforceGasTest)
{ {
assertThrow( assertThrow(

View File

@ -20,6 +20,7 @@
#include <libsolutil/Exceptions.h> #include <libsolutil/Exceptions.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <liblangutil/Exceptions.h>
#include <test/evmc/evmc.h> #include <test/evmc/evmc.h>
@ -67,6 +68,8 @@ struct CommonOptions
bool useABIEncoderV1 = false; bool useABIEncoderV1 = false;
bool showMessages = false; bool showMessages = false;
bool showMetadata = false; bool showMetadata = false;
size_t batches = 1;
size_t selectedBatch = 0;
langutil::EVMVersion evmVersion() const; langutil::EVMVersion evmVersion() const;
@ -96,4 +99,27 @@ bool isValidSemanticTestPath(boost::filesystem::path const& _testPath);
bool loadVMs(CommonOptions const& _options); bool loadVMs(CommonOptions const& _options);
/**
* Component to help with splitting up all tests into batches.
*/
class Batcher
{
public:
Batcher(size_t _offset, size_t _batches):
m_offset(_offset),
m_batches(_batches)
{
solAssert(m_batches > 0 && m_offset < m_batches);
}
Batcher(Batcher const&) = delete;
Batcher& operator=(Batcher const&) = delete;
bool checkAndAdvance() { return (m_counter++) % m_batches == m_offset; }
private:
size_t const m_offset;
size_t const m_batches;
size_t m_counter = 0;
};
} }

View File

@ -30,6 +30,7 @@
#pragma warning(disable:4535) // calling _set_se_translator requires /EHa #pragma warning(disable:4535) // calling _set_se_translator requires /EHa
#endif #endif
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <boost/test/tree/traverse.hpp>
#if defined(_MSC_VER) #if defined(_MSC_VER)
#pragma warning(pop) #pragma warning(pop)
#endif #endif
@ -60,6 +61,41 @@ void removeTestSuite(std::string const& _name)
master.remove(id); master.remove(id);
} }
/**
* Class that traverses the boost test tree and removes unit tests that are
* not in the current batch.
*/
class BoostBatcher: public test_tree_visitor
{
public:
BoostBatcher(solidity::test::Batcher& _batcher):
m_batcher(_batcher)
{}
void visit(test_case const& _testCase) override
{
if (!m_batcher.checkAndAdvance())
// disabling them would be nicer, but it does not work like this:
// const_cast<test_case&>(_testCase).p_run_status.value = test_unit::RS_DISABLED;
m_path.back()->remove(_testCase.p_id);
}
bool test_suite_start(test_suite const& _testSuite) override
{
m_path.push_back(&const_cast<test_suite&>(_testSuite));
return test_tree_visitor::test_suite_start(_testSuite);
}
void test_suite_finish(test_suite const& _testSuite) override
{
m_path.pop_back();
test_tree_visitor::test_suite_finish(_testSuite);
}
private:
solidity::test::Batcher& m_batcher;
std::vector<test_suite*> m_path;
};
void runTestCase(TestCase::Config const& _config, TestCase::TestCaseCreator const& _testCaseCreator) void runTestCase(TestCase::Config const& _config, TestCase::TestCaseCreator const& _testCaseCreator)
{ {
try try
@ -100,7 +136,8 @@ int registerTests(
bool _enforceViaYul, bool _enforceViaYul,
bool _enforceCompileToEwasm, bool _enforceCompileToEwasm,
vector<string> const& _labels, vector<string> const& _labels,
TestCase::TestCaseCreator _testCaseCreator TestCase::TestCaseCreator _testCaseCreator,
solidity::test::Batcher& _batcher
) )
{ {
int numTestsAdded = 0; int numTestsAdded = 0;
@ -131,33 +168,38 @@ int registerTests(
_enforceViaYul, _enforceViaYul,
_enforceCompileToEwasm, _enforceCompileToEwasm,
_labels, _labels,
_testCaseCreator _testCaseCreator,
_batcher
); );
_suite.add(sub_suite); _suite.add(sub_suite);
} }
else else
{ {
// This must be a vector of unique_ptrs because Boost.Test keeps the equivalent of a string_view to the filename // TODO would be better to set the test to disabled.
// that is passed in. If the strings were stored directly in the vector, pointers/references to them would be if (_batcher.checkAndAdvance())
// invalidated on reallocation. {
static vector<unique_ptr<string const>> filenames; // This must be a vector of unique_ptrs because Boost.Test keeps the equivalent of a string_view to the filename
// that is passed in. If the strings were stored directly in the vector, pointers/references to them would be
// invalidated on reallocation.
static vector<unique_ptr<string const>> filenames;
filenames.emplace_back(make_unique<string>(_path.string())); filenames.emplace_back(make_unique<string>(_path.string()));
auto test_case = make_test_case( auto test_case = make_test_case(
[config, _testCaseCreator] [config, _testCaseCreator]
{ {
BOOST_REQUIRE_NO_THROW({ BOOST_REQUIRE_NO_THROW({
runTestCase(config, _testCaseCreator); runTestCase(config, _testCaseCreator);
}); });
}, },
_path.stem().string(), _path.stem().string(),
*filenames.back(), *filenames.back(),
0 0
); );
for (auto const& _label: _labels) for (auto const& _label: _labels)
test_case->add_label(_label); test_case->add_label(_label);
_suite.add(test_case); _suite.add(test_case);
numTestsAdded = 1; numTestsAdded = 1;
}
} }
return numTestsAdded; return numTestsAdded;
} }
@ -172,6 +214,7 @@ void initializeOptions()
solidity::test::CommonOptions::setSingleton(std::move(options)); solidity::test::CommonOptions::setSingleton(std::move(options));
} }
} }
// TODO: Prototype -- why isn't this declared in the boost headers? // TODO: Prototype -- why isn't this declared in the boost headers?
@ -180,6 +223,8 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] );
test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] )
{ {
using namespace solidity::test;
master_test_suite_t& master = framework::master_test_suite(); master_test_suite_t& master = framework::master_test_suite();
master.p_name.value = "SolidityTests"; master.p_name.value = "SolidityTests";
@ -191,6 +236,17 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] )
if (solidity::test::CommonOptions::get().disableSemanticTests) if (solidity::test::CommonOptions::get().disableSemanticTests)
cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl; cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl;
if (!solidity::test::CommonOptions::get().enforceGasTest)
cout << endl << "WARNING :: Gas Cost Expectations are not being enforced" << endl << endl;
Batcher batcher(CommonOptions::get().selectedBatch, CommonOptions::get().batches);
if (CommonOptions::get().batches > 1)
cout << "Batch " << CommonOptions::get().selectedBatch << " out of " << CommonOptions::get().batches << endl;
// Batch the boost tests
BoostBatcher boostBatcher(batcher);
traverse_test_tree(master, boostBatcher, true);
// Include the interactive tests in the automatic tests as well // Include the interactive tests in the automatic tests as well
for (auto const& ts: g_interactiveTestsuites) for (auto const& ts: g_interactiveTestsuites)
{ {
@ -202,15 +258,19 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] )
if (ts.needsVM && solidity::test::CommonOptions::get().disableSemanticTests) if (ts.needsVM && solidity::test::CommonOptions::get().disableSemanticTests)
continue; continue;
solAssert(registerTests( //TODO
//solAssert(
registerTests(
master, master,
options.testPath / ts.path, options.testPath / ts.path,
ts.subpath, ts.subpath,
options.enforceViaYul, options.enforceViaYul,
options.enforceCompileToEwasm, options.enforceCompileToEwasm,
ts.labels, ts.labels,
ts.testCaseCreator ts.testCaseCreator,
) > 0, std::string("no ") + ts.title + " tests found"); batcher
);
// > 0, std::string("no ") + ts.title + " tests found");
} }
if (solidity::test::CommonOptions::get().disableSemanticTests) if (solidity::test::CommonOptions::get().disableSemanticTests)

View File

@ -42,3 +42,8 @@ printTask "Running external tests..."
"$REPO_ROOT/externalTests/gnosis-v2.sh" "$@" "$REPO_ROOT/externalTests/gnosis-v2.sh" "$@"
"$REPO_ROOT/externalTests/colony.sh" "$@" "$REPO_ROOT/externalTests/colony.sh" "$@"
"$REPO_ROOT/externalTests/ens.sh" "$@" "$REPO_ROOT/externalTests/ens.sh" "$@"
"$REPO_ROOT/externalTests/trident.sh" "$@"
"$REPO_ROOT/externalTests/euler.sh" "$@"
"$REPO_ROOT/externalTests/yield-liquidator.sh" "$@"
"$REPO_ROOT/externalTests/bleeps.sh" "$@"
"$REPO_ROOT/externalTests/pool-together.sh" "$@"

84
test/externalTests/bleeps.sh Executable file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# 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) 2022 solidity contributors.
#------------------------------------------------------------------------------
set -e
source scripts/common.sh
source test/externalTests/common.sh
verify_input "$@"
BINARY_TYPE="$1"
BINARY_PATH="$2"
function compile_fn { npm run compile; }
function test_fn { npm run test; }
function bleeps_test
{
local repo="https://github.com/wighawag/bleeps"
local ref_type=tag
local ref=bleeps_migrations # TODO: There's a 0.4.19 contract in 'main' that would need patching for the latest compiler.
local config_file="hardhat.config.ts"
local config_var=config
local compile_only_presets=()
local settings_presets=(
"${compile_only_presets[@]}"
#ir-no-optimize # Compilation fails with: "YulException: Variable param_0 is 2 slot(s) too deep inside the stack."
#ir-optimize-evm-only # Compilation fails with: "YulException: Variable param_0 is 2 slot(s) too deep inside the stack."
ir-optimize-evm+yul
#legacy-no-optimize # Compilation fails with: "CompilerError: Stack too deep, try removing local variables."
#legacy-optimize-evm-only # Compilation fails with: "CompilerError: Stack too deep, try removing local variables."
legacy-optimize-evm+yul
)
[[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
print_presets_or_exit "$SELECTED_PRESETS"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$ref_type" "$ref" "$DIR"
pushd "common-lib/"
neutralize_package_json_hooks
npm install
npm run build
popd
pushd "contracts/"
sed -i 's|"bleeps-common": "workspace:\*",|"bleeps-common": "file:../common-lib/",|g' package.json
neutralize_package_lock
neutralize_package_json_hooks
force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH"
force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" "$config_var"
npm install npm-run-all
npm install
replace_version_pragmas
for preset in $SELECTED_PRESETS; do
hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn "$config_var"
done
popd
}
external_test Bleeps bleeps_test

View File

@ -27,6 +27,7 @@ source test/externalTests/common.sh
verify_input "$@" verify_input "$@"
BINARY_TYPE="$1" BINARY_TYPE="$1"
BINARY_PATH="$2" BINARY_PATH="$2"
SELECTED_PRESETS="$3"
function compile_fn { yarn run provision:token:contracts; } function compile_fn { yarn run provision:token:contracts; }
function test_fn { yarn run test:contracts; } function test_fn { yarn run test:contracts; }
@ -34,7 +35,8 @@ function test_fn { yarn run test:contracts; }
function colony_test function colony_test
{ {
local repo="https://github.com/solidity-external-tests/colonyNetwork.git" local repo="https://github.com/solidity-external-tests/colonyNetwork.git"
local branch=develop_080 local ref_type=branch
local ref="develop_080"
local config_file="truffle.js" local config_file="truffle.js"
local compile_only_presets=( local compile_only_presets=(
@ -49,16 +51,15 @@ function colony_test
legacy-optimize-evm+yul legacy-optimize-evm+yul
) )
local selected_optimizer_presets [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") print_presets_or_exit "$SELECTED_PRESETS"
print_optimizer_presets_or_exit "$selected_optimizer_presets"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$branch" "$DIR" download_project "$repo" "$ref_type" "$ref" "$DIR"
[[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH" [[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH"
neutralize_package_json_hooks neutralize_package_json_hooks
force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$selected_optimizer_presets")" force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$SELECTED_PRESETS")"
yarn install yarn install
git submodule update --init git submodule update --init
@ -70,7 +71,7 @@ function colony_test
replace_version_pragmas replace_version_pragmas
[[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc" [[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc"
for preset in $selected_optimizer_presets; do for preset in $SELECTED_PRESETS; do
truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn
done done
} }

View File

@ -24,7 +24,16 @@ set -e
CURRENT_EVM_VERSION=london CURRENT_EVM_VERSION=london
function print_optimizer_presets_or_exit AVAILABLE_PRESETS=(
legacy-no-optimize
ir-no-optimize
legacy-optimize-evm-only
ir-optimize-evm-only
legacy-optimize-evm+yul
ir-optimize-evm+yul
)
function print_presets_or_exit
{ {
local selected_presets="$1" local selected_presets="$1"
@ -37,10 +46,22 @@ function verify_input
{ {
local binary_type="$1" local binary_type="$1"
local binary_path="$2" local binary_path="$2"
local selected_presets="$3"
(( $# == 2 )) || fail "Usage: $0 native|solcjs <path to solc or soljson.js>" (( $# >= 2 && $# <= 3 )) || fail "Usage: $0 native|solcjs <path to solc or soljson.js> [preset]"
[[ $binary_type == native || $binary_type == solcjs ]] || fail "Invalid binary type: '${binary_type}'. Must be either 'native' or 'solcjs'." [[ $binary_type == native || $binary_type == solcjs ]] || fail "Invalid binary type: '${binary_type}'. Must be either 'native' or 'solcjs'."
[[ -f "$binary_path" ]] || fail "The compiler binary does not exist at '${binary_path}'" [[ -f "$binary_path" ]] || fail "The compiler binary does not exist at '${binary_path}'"
if [[ $selected_presets != "" ]]
then
for preset in $selected_presets
do
if [[ " ${AVAILABLE_PRESETS[*]} " != *" $preset "* ]]
then
fail "Preset '${preset}' does not exist. Available presets: ${AVAILABLE_PRESETS[*]}."
fi
done
fi
} }
function setup_solc function setup_solc
@ -77,12 +98,24 @@ function setup_solc
function download_project function download_project
{ {
local repo="$1" local repo="$1"
local solcjs_branch="$2" local ref_type="$2"
local test_dir="$3" local solcjs_ref="$3"
local test_dir="$4"
printLog "Cloning $solcjs_branch of $repo..." [[ $ref_type == commit || $ref_type == branch || $ref_type == tag ]] || assertFail
git clone --depth 1 "$repo" -b "$solcjs_branch" "$test_dir/ext"
cd ext printLog "Cloning ${ref_type} ${solcjs_ref} of ${repo}..."
if [[ $ref_type == commit ]]; then
mkdir ext
cd ext
git init
git remote add origin "$repo"
git fetch --depth 1 origin "$solcjs_ref"
git reset --hard FETCH_HEAD
else
git clone --depth 1 "$repo" -b "$solcjs_ref" "$test_dir/ext"
cd ext
fi
echo "Current commit hash: $(git rev-parse HEAD)" echo "Current commit hash: $(git rev-parse HEAD)"
} }
@ -117,6 +150,19 @@ function neutralize_package_json_hooks
sed -i 's|"prepare": *".*"|"prepare": ""|g' package.json sed -i 's|"prepare": *".*"|"prepare": ""|g' package.json
} }
function neutralize_packaged_contracts
{
# Frameworks will build contracts from any package that contains a configuration file.
# This is both unnecessary (any files imported from these packages will get compiled again as a
# part of the main project anyway) and trips up our version check because it won't use our
# custom compiler binary.
printLog "Removing framework config and artifacts from npm packages..."
find node_modules/ -type f '(' -name 'hardhat.config.*' -o -name 'truffle-config.*' ')' -delete
# Some npm packages also come packaged with pre-built artifacts.
find node_modules/ -path '*artifacts/build-info/*.json' -delete
}
function force_solc_modules function force_solc_modules
{ {
local custom_solcjs_path="${1:-solc/}" local custom_solcjs_path="${1:-solc/}"
@ -176,14 +222,37 @@ function force_hardhat_compiler_binary
echo "Config file: ${config_file}" echo "Config file: ${config_file}"
echo "Binary type: ${binary_type}" echo "Binary type: ${binary_type}"
echo "Compiler path: ${solc_path}" echo "Compiler path: ${solc_path}"
hardhat_solc_build_subtask "$SOLCVERSION_SHORT" "$SOLCVERSION" "$binary_type" "$solc_path" >> "$config_file"
local language="${config_file##*.}"
hardhat_solc_build_subtask "$SOLCVERSION_SHORT" "$SOLCVERSION" "$binary_type" "$solc_path" "$language" >> "$config_file"
}
function force_hardhat_unlimited_contract_size
{
local config_file="$1"
local config_var_name="$2"
printLog "Configuring Hardhat..."
echo "-------------------------------------"
echo "Allow unlimited contract size: true"
echo "-------------------------------------"
if [[ $config_file == *\.js ]]; then
[[ $config_var_name == "" ]] || assertFail
echo "module.exports.networks.hardhat.allowUnlimitedContractSize = true" >> "$config_file"
else
[[ $config_file == *\.ts ]] || assertFail
[[ $config_var_name != "" ]] || assertFail
echo "${config_var_name}.networks!.hardhat!.allowUnlimitedContractSize = true" >> "$config_file"
fi
} }
function force_hardhat_compiler_settings function force_hardhat_compiler_settings
{ {
local config_file="$1" local config_file="$1"
local preset="$2" local preset="$2"
local evm_version="${3:-"$CURRENT_EVM_VERSION"}" local config_var_name="$3"
local evm_version="${4:-"$CURRENT_EVM_VERSION"}"
printLog "Configuring Hardhat..." printLog "Configuring Hardhat..."
echo "-------------------------------------" echo "-------------------------------------"
@ -195,10 +264,16 @@ function force_hardhat_compiler_settings
echo "Compiler version (full): ${SOLCVERSION}" echo "Compiler version (full): ${SOLCVERSION}"
echo "-------------------------------------" echo "-------------------------------------"
{ local settings
echo -n 'module.exports["solidity"] = ' settings=$(hardhat_compiler_settings "$SOLCVERSION_SHORT" "$preset" "$evm_version")
hardhat_compiler_settings "$SOLCVERSION_SHORT" "$preset" "$evm_version" if [[ $config_file == *\.js ]]; then
} >> "$config_file" [[ $config_var_name == "" ]] || assertFail
echo "module.exports['solidity'] = ${settings}" >> "$config_file"
else
[[ $config_file == *\.ts ]] || assertFail
[[ $config_var_name != "" ]] || assertFail
echo "${config_var_name}.solidity = {compilers: [${settings}]}" >> "$config_file"
fi
} }
function truffle_verify_compiler_version function truffle_verify_compiler_version
@ -216,8 +291,12 @@ function hardhat_verify_compiler_version
local full_solc_version="$2" local full_solc_version="$2"
printLog "Verify that the correct version (${solc_version}/${full_solc_version}) of the compiler was used to compile the contracts..." printLog "Verify that the correct version (${solc_version}/${full_solc_version}) of the compiler was used to compile the contracts..."
grep '"solcVersion": "'"${solc_version}"'"' --with-filename artifacts/build-info/*.json || fail "Wrong compiler version detected." local build_info_files
grep '"solcLongVersion": "'"${full_solc_version}"'"' --with-filename artifacts/build-info/*.json || fail "Wrong compiler version detected." build_info_files=$(find . -path '*artifacts/build-info/*.json')
for build_info_file in $build_info_files; do
grep '"solcVersion": "'"${solc_version}"'"' --with-filename "$build_info_file" || fail "Wrong compiler version detected in ${build_info_file}."
grep '"solcLongVersion": "'"${full_solc_version}"'"' --with-filename "$build_info_file" || fail "Wrong compiler version detected in ${build_info_file}."
done
} }
function truffle_clean function truffle_clean
@ -249,6 +328,8 @@ function settings_from_preset
local preset="$1" local preset="$1"
local evm_version="$2" local evm_version="$2"
[[ " ${AVAILABLE_PRESETS[*]} " == *" $preset "* ]] || assertFail
case "$preset" in case "$preset" in
# NOTE: Remember to update `parallelism` of `t_ems_ext` job in CI config if you add/remove presets # NOTE: Remember to update `parallelism` of `t_ems_ext` job in CI config if you add/remove presets
legacy-no-optimize) echo "{evmVersion: '${evm_version}', viaIR: false, optimizer: {enabled: false}}" ;; legacy-no-optimize) echo "{evmVersion: '${evm_version}', viaIR: false, optimizer: {enabled: false}}" ;;
@ -292,16 +373,27 @@ function hardhat_solc_build_subtask {
local full_solc_version="$2" local full_solc_version="$2"
local binary_type="$3" local binary_type="$3"
local solc_path="$4" local solc_path="$4"
local language="$5"
[[ $binary_type == native || $binary_type == solcjs ]] || assertFail [[ $binary_type == native || $binary_type == solcjs ]] || assertFail
[[ $binary_type == native ]] && local is_solcjs=false [[ $binary_type == native ]] && local is_solcjs=false
[[ $binary_type == solcjs ]] && local is_solcjs=true [[ $binary_type == solcjs ]] && local is_solcjs=true
echo "const {TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD} = require('hardhat/builtin-tasks/task-names');" if [[ $language == js ]]; then
echo "const assert = require('assert');" echo "const {TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD} = require('hardhat/builtin-tasks/task-names');"
echo echo "const assert = require('assert');"
echo "subtask(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, async (args, hre, runSuper) => {" echo
echo "subtask(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, async (args, hre, runSuper) => {"
else
[[ $language == ts ]] || assertFail
echo "import {TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD} from 'hardhat/builtin-tasks/task-names';"
echo "import assert = require('assert');"
echo "import {subtask} from 'hardhat/config';"
echo
echo "subtask(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, async (args: any, _hre: any, _runSuper: any) => {"
fi
echo " assert(args.solcVersion == '${solc_version}', 'Unexpected solc version: ' + args.solcVersion)" echo " assert(args.solcVersion == '${solc_version}', 'Unexpected solc version: ' + args.solcVersion)"
echo " return {" echo " return {"
echo " compilerPath: '$(realpath "$solc_path")'," echo " compilerPath: '$(realpath "$solc_path")',"
@ -367,9 +459,10 @@ function hardhat_run_test
local compile_only_presets="$3" local compile_only_presets="$3"
local compile_fn="$4" local compile_fn="$4"
local test_fn="$5" local test_fn="$5"
local config_var_name="$6"
hardhat_clean hardhat_clean
force_hardhat_compiler_settings "$config_file" "$preset" force_hardhat_compiler_settings "$config_file" "$preset" "$config_var_name"
compile_and_run_test compile_fn test_fn hardhat_verify_compiler_version "$preset" "$compile_only_presets" compile_and_run_test compile_fn test_fn hardhat_verify_compiler_version "$preset" "$compile_only_presets"
} }

View File

@ -27,49 +27,48 @@ source test/externalTests/common.sh
verify_input "$@" verify_input "$@"
BINARY_TYPE="$1" BINARY_TYPE="$1"
BINARY_PATH="$2" BINARY_PATH="$2"
SELECTED_PRESETS="$3"
function compile_fn { npx truffle compile; } function compile_fn { yarn build; }
function test_fn { npm run test; } function test_fn { yarn test; }
function ens_test function ens_test
{ {
local repo="https://github.com/ensdomains/ens.git" local repo="https://github.com/ensdomains/ens-contracts.git"
local branch=master local ref_type=tag
local config_file="truffle.js" local ref="v0.0.8" # The project is in flux right now and master might be too unstable for us
local config_file="hardhat.config.js"
local compile_only_presets=() local compile_only_presets=(
legacy-no-optimize # Compiles but tests fail to deploy GovernorCompatibilityBravo (code too large).
)
local settings_presets=( local settings_presets=(
"${compile_only_presets[@]}" "${compile_only_presets[@]}"
#ir-no-optimize # "YulException: Variable var_ttl_236 is 1 slot(s) too deep inside the stack." #ir-no-optimize # Compilation fails with "YulException: Variable var__945 is 1 slot(s) too deep inside the stack."
#ir-optimize-evm-only # "YulException: Variable var_ttl_236 is 1 slot(s) too deep inside the stack." #ir-optimize-evm-only # Compilation fails with "YulException: Variable var__945 is 1 slot(s) too deep inside the stack."
ir-optimize-evm+yul #ir-optimize-evm+yul # Compilation fails with "YulException: Variable _5 is 1 too deep in the stack [ _5 usr$i usr$h _7 usr$scratch usr$k usr$f _4 usr$len usr$j_2 RET _2 _1 var_data_mpos usr$totallen usr$x _12 ]"
legacy-no-optimize
legacy-optimize-evm-only legacy-optimize-evm-only
legacy-optimize-evm+yul legacy-optimize-evm+yul
) )
local selected_optimizer_presets [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") print_presets_or_exit "$SELECTED_PRESETS"
print_optimizer_presets_or_exit "$selected_optimizer_presets"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$branch" "$DIR" download_project "$repo" "$ref_type" "$ref" "$DIR"
[[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH"
# Use latest Truffle. Older versions crash on the output from 0.8.0.
force_truffle_version ^5.1.55
neutralize_package_lock neutralize_package_lock
neutralize_package_json_hooks neutralize_package_json_hooks
force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$selected_optimizer_presets")" force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH"
npm install force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")"
yarn install
replace_version_pragmas replace_version_pragmas
[[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc" neutralize_packaged_contracts
for preset in $selected_optimizer_presets; do for preset in $SELECTED_PRESETS; do
truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn
done done
} }
external_test Ens ens_test external_test ENS ens_test

74
test/externalTests/euler.sh Executable file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# 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) 2022 solidity contributors.
#------------------------------------------------------------------------------
set -e
source scripts/common.sh
source test/externalTests/common.sh
verify_input "$@"
BINARY_TYPE="$1"
BINARY_PATH="$2"
SELECTED_PRESETS="$3"
function compile_fn { npm run compile; }
function test_fn { npx --no hardhat --no-compile test; }
function euler_test
{
local repo="https://github.com/euler-xyz/euler-contracts"
local ref_type=branch
local ref="master"
local config_file="hardhat.config.js"
local compile_only_presets=()
local settings_presets=(
"${compile_only_presets[@]}"
#ir-no-optimize # Compilation fails with "YulException: Variable var_utilisation_307 is 6 slot(s) too deep inside the stack."
#ir-optimize-evm-only # Compilation fails with "YulException: Variable var_utilisation_307 is 6 slot(s) too deep inside the stack."
#ir-optimize-evm+yul # Compilation fails with "YulException: Variable var_status_mpos is 3 too deep in the stack"
legacy-optimize-evm-only
legacy-optimize-evm+yul
legacy-no-optimize
)
[[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
print_presets_or_exit "$SELECTED_PRESETS"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$ref_type" "$ref" "$DIR"
neutralize_package_lock
neutralize_package_json_hooks
force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH"
force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")"
force_hardhat_unlimited_contract_size "$config_file"
npm install
replace_version_pragmas
neutralize_packaged_contracts
for preset in $SELECTED_PRESETS; do
hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn
done
}
external_test Euler euler_test

View File

@ -27,6 +27,7 @@ source test/externalTests/common.sh
verify_input "$@" verify_input "$@"
BINARY_TYPE="$1" BINARY_TYPE="$1"
BINARY_PATH="$2" BINARY_PATH="$2"
SELECTED_PRESETS="$3"
function compile_fn { npx truffle compile; } function compile_fn { npx truffle compile; }
function test_fn { npm test; } function test_fn { npm test; }
@ -34,7 +35,8 @@ function test_fn { npm test; }
function gnosis_safe_test function gnosis_safe_test
{ {
local repo="https://github.com/solidity-external-tests/safe-contracts.git" local repo="https://github.com/solidity-external-tests/safe-contracts.git"
local branch=v2_080 local ref_type=branch
local ref="v2_080"
local config_file="truffle-config.js" local config_file="truffle-config.js"
local compile_only_presets=( local compile_only_presets=(
@ -49,12 +51,11 @@ function gnosis_safe_test
legacy-optimize-evm+yul legacy-optimize-evm+yul
) )
local selected_optimizer_presets [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") print_presets_or_exit "$SELECTED_PRESETS"
print_optimizer_presets_or_exit "$selected_optimizer_presets"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$branch" "$DIR" download_project "$repo" "$ref_type" "$ref" "$DIR"
[[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH" [[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH"
sed -i 's|github:gnosis/mock-contract#sol_0_5_0|github:solidity-external-tests/mock-contract#master_080|g' package.json sed -i 's|github:gnosis/mock-contract#sol_0_5_0|github:solidity-external-tests/mock-contract#master_080|g' package.json
@ -62,13 +63,13 @@ function gnosis_safe_test
neutralize_package_lock neutralize_package_lock
neutralize_package_json_hooks neutralize_package_json_hooks
force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$selected_optimizer_presets")" force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$SELECTED_PRESETS")"
npm install --package-lock npm install --package-lock
replace_version_pragmas replace_version_pragmas
[[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc" [[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc"
for preset in $selected_optimizer_presets; do for preset in $SELECTED_PRESETS; do
truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn
done done
} }

View File

@ -27,6 +27,7 @@ source test/externalTests/common.sh
verify_input "$@" verify_input "$@"
BINARY_TYPE="$1" BINARY_TYPE="$1"
BINARY_PATH="$2" BINARY_PATH="$2"
SELECTED_PRESETS="$3"
function compile_fn { npx truffle compile; } function compile_fn { npx truffle compile; }
function test_fn { npm test; } function test_fn { npm test; }
@ -34,7 +35,8 @@ function test_fn { npm test; }
function gnosis_safe_test function gnosis_safe_test
{ {
local repo="https://github.com/solidity-external-tests/safe-contracts.git" local repo="https://github.com/solidity-external-tests/safe-contracts.git"
local branch=development_080 local ref_type=branch
local ref="development_080"
local config_file="truffle-config.js" local config_file="truffle-config.js"
local compile_only_presets=() local compile_only_presets=()
@ -48,25 +50,24 @@ function gnosis_safe_test
legacy-optimize-evm+yul legacy-optimize-evm+yul
) )
local selected_optimizer_presets [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") print_presets_or_exit "$SELECTED_PRESETS"
print_optimizer_presets_or_exit "$selected_optimizer_presets"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$branch" "$DIR" download_project "$repo" "$ref_type" "$ref" "$DIR"
[[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH" [[ $BINARY_TYPE == native ]] && replace_global_solc "$BINARY_PATH"
sed -i 's|github:gnosis/mock-contract#sol_0_5_0|github:solidity-external-tests/mock-contract#master_080|g' package.json sed -i 's|github:gnosis/mock-contract#sol_0_5_0|github:solidity-external-tests/mock-contract#master_080|g' package.json
neutralize_package_lock neutralize_package_lock
neutralize_package_json_hooks neutralize_package_json_hooks
force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$selected_optimizer_presets")" force_truffle_compiler_settings "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$(first_word "$SELECTED_PRESETS")"
npm install --package-lock npm install --package-lock
replace_version_pragmas replace_version_pragmas
[[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc" [[ $BINARY_TYPE == solcjs ]] && force_solc_modules "${DIR}/solc"
for preset in $selected_optimizer_presets; do for preset in $SELECTED_PRESETS; do
truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn truffle_run_test "$config_file" "$BINARY_TYPE" "${DIR}/solc" "$preset" "${compile_only_presets[*]}" compile_fn test_fn
done done
} }

View File

@ -0,0 +1,78 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# 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) 2022 solidity contributors.
#------------------------------------------------------------------------------
set -e
source scripts/common.sh
source test/externalTests/common.sh
verify_input "$@"
BINARY_TYPE="$1"
BINARY_PATH="$2"
SELECTED_PRESETS="$3"
function compile_fn { yarn compile; }
function test_fn { yarn test; }
function pool_together_test
{
local repo="https://github.com/pooltogether/v4-core"
local ref_type=branch
local ref=master
local config_file="hardhat.config.ts"
local config_var="config"
local compile_only_presets=()
local settings_presets=(
"${compile_only_presets[@]}"
#ir-no-optimize # Compilation fails with "YulException: Variable var_amount_205 is 9 slot(s) too deep inside the stack."
#ir-optimize-evm-only # Compilation fails with "YulException: Variable var_amount_205 is 9 slot(s) too deep inside the stack."
#ir-optimize-evm+yul # FIXME: ICE due to https://github.com/ethereum/solidity/issues/12558
legacy-no-optimize
legacy-optimize-evm-only
legacy-optimize-evm+yul
)
[[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
print_presets_or_exit "$SELECTED_PRESETS"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$ref_type" "$ref" "$DIR"
neutralize_package_lock
neutralize_package_json_hooks
force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH"
force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" "$config_var"
yarn install
# These come with already compiled artifacts. We want them recompiled with latest compiler.
rm -r node_modules/@pooltogether/yield-source-interface/artifacts/
rm -r node_modules/@pooltogether/uniform-random-number/artifacts/
rm -r node_modules/@pooltogether/owner-manager-contracts/artifacts/
replace_version_pragmas
for preset in $SELECTED_PRESETS; do
hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn "$config_var"
done
}
external_test Pool-Together-V4 pool_together_test

97
test/externalTests/trident.sh Executable file
View File

@ -0,0 +1,97 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# 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) 2021 solidity contributors.
#------------------------------------------------------------------------------
set -e
source scripts/common.sh
source test/externalTests/common.sh
verify_input "$@"
BINARY_TYPE="$1"
BINARY_PATH="$2"
function compile_fn { yarn build; }
function test_fn {
# shellcheck disable=SC2046
TS_NODE_TRANSPILE_ONLY=1 npx hardhat test --no-compile $(
# TODO: We need to skip Migration.test.ts because it fails and makes other tests fail too.
# Replace this with `yarn test` once https://github.com/sushiswap/trident/issues/283 is fixed.
find test/ -name "*.test.ts" ! -path "test/Migration.test.ts" | LC_ALL=C sort
)
}
function trident_test
{
local repo="https://github.com/sushiswap/trident"
local ref_type=commit
# FIXME: Switch back to master branch when https://github.com/sushiswap/trident/issues/303 gets fixed.
local ref="0cab5ae884cc9a41223d52791be775c3a053cb26" # master as of 2021-12-16
local config_file="hardhat.config.ts"
local config_var=config
local compile_only_presets=()
local settings_presets=(
"${compile_only_presets[@]}"
#ir-no-optimize # Compilation fails with: "YulException: Variable var_amount_165 is 9 slot(s) too deep inside the stack."
#ir-optimize-evm-only # Compilation fails with: "YulException: Variable var_amount_165 is 9 slot(s) too deep inside the stack."
#ir-optimize-evm+yul # Compilation fails with: "YulException: Cannot swap Variable var_nearestTick with Variable _4: too deep in the stack by 4 slots"
legacy-no-optimize
legacy-optimize-evm-only
legacy-optimize-evm+yul
)
[[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
print_presets_or_exit "$SELECTED_PRESETS"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$ref_type" "$ref" "$DIR"
# TODO: Currently tests work only with the exact versions from yarn.lock.
# Re-enable this when https://github.com/sushiswap/trident/issues/284 is fixed.
#neutralize_package_lock
neutralize_package_json_hooks
force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH"
force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" "$config_var"
yarn install
replace_version_pragmas
force_solc_modules "${DIR}/solc"
# BentoBoxV1Flat.sol requires a few small tweaks to compile on 0.8.x.
# TODO: Remove once https://github.com/sushiswap/trident/pull/282 gets merged.
sed -i 's|uint128(-1)|type(uint128).max|g' contracts/flat/BentoBoxV1Flat.sol
sed -i 's|uint64(-1)|type(uint64).max|g' contracts/flat/BentoBoxV1Flat.sol
sed -i 's|uint32(-1)|type(uint32).max|g' contracts/flat/BentoBoxV1Flat.sol
sed -i 's|IERC20(0)|IERC20(address(0))|g' contracts/flat/BentoBoxV1Flat.sol
sed -i 's|IStrategy(0)|IStrategy(address(0))|g' contracts/flat/BentoBoxV1Flat.sol
# @sushiswap/core package contains contracts that get built with 0.6.12 and fail our compiler
# version check. It's not used by tests so we can remove it.
rm -r node_modules/@sushiswap/core/
for preset in $SELECTED_PRESETS; do
hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn "$config_var"
done
}
external_test Trident trident_test

View File

@ -0,0 +1,74 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# 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) 2022 solidity contributors.
#------------------------------------------------------------------------------
set -e
source scripts/common.sh
source test/externalTests/common.sh
verify_input "$@"
BINARY_TYPE="$1"
BINARY_PATH="$2"
SELECTED_PRESETS="$3"
function compile_fn { npm run build; }
function test_fn { npm run test; }
function yield_liquidator_test
{
local repo="https://github.com/yieldprotocol/yield-liquidator-v2"
local ref_type=branch
local ref="master"
local config_file="hardhat.config.ts"
local config_var="module.exports"
local compile_only_presets=()
local settings_presets=(
"${compile_only_presets[@]}"
#ir-no-optimize # Compilation fails with "YulException: Variable var_roles_168_mpos is 2 slot(s) too deep inside the stack."
#ir-optimize-evm-only # Compilation fails with "YulException: Variable var__33 is 6 slot(s) too deep inside the stack."
ir-optimize-evm+yul
legacy-optimize-evm-only
legacy-optimize-evm+yul
legacy-no-optimize
)
[[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
print_presets_or_exit "$SELECTED_PRESETS"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$ref_type" "$ref" "$DIR"
neutralize_package_lock
neutralize_package_json_hooks
force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH"
force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")" "$config_var"
force_hardhat_unlimited_contract_size "$config_file" "$config_var"
npm install
replace_version_pragmas
for preset in $SELECTED_PRESETS; do
hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn "$config_var"
done
}
external_test Yield-Liquidator-V2 yield_liquidator_test

View File

@ -27,6 +27,7 @@ source test/externalTests/common.sh
verify_input "$@" verify_input "$@"
BINARY_TYPE="$1" BINARY_TYPE="$1"
BINARY_PATH="$2" BINARY_PATH="$2"
SELECTED_PRESETS="$3"
function compile_fn { npm run compile; } function compile_fn { npm run compile; }
function test_fn { npm test; } function test_fn { npm test; }
@ -34,7 +35,8 @@ function test_fn { npm test; }
function zeppelin_test function zeppelin_test
{ {
local repo="https://github.com/OpenZeppelin/openzeppelin-contracts.git" local repo="https://github.com/OpenZeppelin/openzeppelin-contracts.git"
local branch=master local ref_type=branch
local ref="master"
local config_file="hardhat.config.js" local config_file="hardhat.config.js"
local compile_only_presets=( local compile_only_presets=(
@ -49,21 +51,20 @@ function zeppelin_test
legacy-optimize-evm+yul legacy-optimize-evm+yul
) )
local selected_optimizer_presets [[ $SELECTED_PRESETS != "" ]] || SELECTED_PRESETS=$(circleci_select_steps_multiarg "${settings_presets[@]}")
selected_optimizer_presets=$(circleci_select_steps_multiarg "${settings_presets[@]}") print_presets_or_exit "$SELECTED_PRESETS"
print_optimizer_presets_or_exit "$selected_optimizer_presets"
setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH" setup_solc "$DIR" "$BINARY_TYPE" "$BINARY_PATH"
download_project "$repo" "$branch" "$DIR" download_project "$repo" "$ref_type" "$ref" "$DIR"
neutralize_package_json_hooks neutralize_package_json_hooks
force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH" force_hardhat_compiler_binary "$config_file" "$BINARY_TYPE" "$BINARY_PATH"
force_hardhat_compiler_settings "$config_file" "$(first_word "$selected_optimizer_presets")" force_hardhat_compiler_settings "$config_file" "$(first_word "$SELECTED_PRESETS")"
npm install npm install
replace_version_pragmas replace_version_pragmas
for preset in $selected_optimizer_presets; do for preset in $SELECTED_PRESETS; do
hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn hardhat_run_test "$config_file" "$preset" "${compile_only_presets[*]}" compile_fn test_fn
done done
} }

View File

@ -39,6 +39,7 @@ class Rule:
self.error('Rule is incorrect.\nModel: ' + str(m)) self.error('Rule is incorrect.\nModel: ' + str(m))
self.solver.pop() self.solver.pop()
def error(self, msg): @classmethod
def error(cls, msg):
print(msg) print(msg)
sys.exit(1) sys.exit(1)

View File

@ -56,7 +56,8 @@ public:
else else
generatedDocumentation = m_compilerStack.natspecDev(_contractName); generatedDocumentation = m_compilerStack.natspecDev(_contractName);
Json::Value expectedDocumentation; Json::Value expectedDocumentation;
util::jsonParseStrict(_expectedDocumentationString, expectedDocumentation); std::string parseError;
BOOST_REQUIRE_MESSAGE(util::jsonParseStrict(_expectedDocumentationString, expectedDocumentation, &parseError), parseError);
expectedDocumentation["version"] = Json::Value(Natspec::c_natspecVersion); expectedDocumentation["version"] = Json::Value(Natspec::c_natspecVersion);
expectedDocumentation["kind"] = Json::Value(_userDocumentation ? "user" : "dev"); expectedDocumentation["kind"] = Json::Value(_userDocumentation ? "user" : "dev");
@ -118,7 +119,8 @@ BOOST_AUTO_TEST_CASE(user_newline_break)
char const* natspec = R"ABCDEF( char const* natspec = R"ABCDEF(
{ {
"methods": { "methods":
{
"f()": "f()":
{ {
"notice": "world" "notice": "world"
@ -146,8 +148,10 @@ BOOST_AUTO_TEST_CASE(user_multiline_empty_lines)
char const* natspec = R"ABCDEF( char const* natspec = R"ABCDEF(
{ {
"methods": { "methods":
"f()": { {
"f()":
{
"notice": "hello world" "notice": "hello world"
} }
} }
@ -185,18 +189,27 @@ BOOST_AUTO_TEST_CASE(dev_and_user_basic_test)
} }
)"; )";
char const* devNatspec = "{" char const* devNatspec = R"R(
"\"methods\":{" {
" \"mul(uint256)\":{ \n" "methods":
" \"details\": \"Multiplies a number by 7\"\n" {
" }\n" "mul(uint256)":
" }\n" {
"}}"; "details": "Multiplies a number by 7"
}
}
})R";
char const* userNatspec = "{" char const* userNatspec = R"R(
"\"methods\":{" {
" \"mul(uint256)\":{ \"notice\": \"Multiplies `a` by 7\"}" "methods":
"}}"; {
"mul(uint256)":
{
"notice": "Multiplies `a` by 7"
}
}
})R";
checkNatspec(sourceCode, "test", devNatspec, false); checkNatspec(sourceCode, "test", devNatspec, false);
checkNatspec(sourceCode, "test", userNatspec, true); checkNatspec(sourceCode, "test", userNatspec, true);
@ -297,16 +310,16 @@ BOOST_AUTO_TEST_CASE(public_state_variable)
char const* devDoc = R"R( char const* devDoc = R"R(
{ {
"methods" : {}, "methods": {},
"stateVariables" : "stateVariables":
{ {
"state" : "state":
{ {
"details" : "example of dev", "details": "example of dev",
"return" : "returns state", "return": "returns state",
"returns" : "returns":
{ {
"_0" : "returns state" "_0": "returns state"
} }
} }
} }
@ -316,9 +329,9 @@ BOOST_AUTO_TEST_CASE(public_state_variable)
char const* userDoc = R"R( char const* userDoc = R"R(
{ {
"methods" : "methods":
{ {
"state()" : "state()":
{ {
"notice": "example of notice" "notice": "example of notice"
} }
@ -346,15 +359,15 @@ BOOST_AUTO_TEST_CASE(public_state_variable_struct)
char const* devDoc = R"R( char const* devDoc = R"R(
{ {
"methods" : {}, "methods": {},
"stateVariables" : "stateVariables":
{ {
"coinStack" : "coinStack":
{ {
"returns" : "returns":
{ {
"observeGraphicURL" : "Front pic", "observeGraphicURL": "Front pic",
"reverseGraphicURL" : "Back pic" "reverseGraphicURL": "Back pic"
} }
} }
} }
@ -364,9 +377,9 @@ BOOST_AUTO_TEST_CASE(public_state_variable_struct)
char const* userDoc = R"R( char const* userDoc = R"R(
{ {
"methods" : "methods":
{ {
"coinStack(uint256)" : "coinStack(uint256)":
{ {
"notice": "Get the n-th coin I own" "notice": "Get the n-th coin I own"
} }
@ -406,12 +419,12 @@ BOOST_AUTO_TEST_CASE(private_state_variable)
char const* devDoc = R"( char const* devDoc = R"(
{ {
"methods" : {}, "methods": {},
"stateVariables" : "stateVariables":
{ {
"state" : "state":
{ {
"details" : "example of dev" "details": "example of dev"
} }
} }
} }
@ -634,9 +647,11 @@ BOOST_AUTO_TEST_CASE(dev_return_no_params)
char const* natspec = R"ABCDEF( char const* natspec = R"ABCDEF(
{ {
"methods": { "methods":
"mul(uint256,uint256)": { {
"returns": { "d": "The result of the multiplication" "mul(uint256,uint256)":
{
"returns": { "d": "The result of the multiplication" }
} }
} }
})ABCDEF"; })ABCDEF";
@ -866,19 +881,24 @@ BOOST_AUTO_TEST_CASE(dev_multiline_return)
} }
)"; )";
char const* natspec = "{" char const* natspec = R"R({
"\"methods\":{" "methods":
" \"mul(uint256,uint256)\":{ \n" {
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" "mul(uint256,uint256)":
" \"params\": {\n" {
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" "details": "Multiplies a number by 7 and adds second parameter",
" \"second\": \"Documentation for the second parameter\"\n" "params":
" },\n" {
" \"returns\": {\n" "a": "Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines",
" \"d\": \"The result of the multiplication and cookies with nutella\",\n" "second": "Documentation for the second parameter"
" }\n" },
" }\n" "returns":
"}}"; {
"d": "The result of the multiplication and cookies with nutella"
}
}
}
})R";
checkNatspec(sourceCode, "test", natspec, false); checkNatspec(sourceCode, "test", natspec, false);
} }
@ -901,19 +921,25 @@ BOOST_AUTO_TEST_CASE(dev_multiline_comment)
} }
)"; )";
char const* natspec = "{" char const* natspec = R"R(
"\"methods\":{" {
" \"mul(uint256,uint256)\":{ \n" "methods":
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n" {
" \"params\": {\n" "mul(uint256,uint256)":
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n" {
" \"second\": \"Documentation for the second parameter\"\n" "details": "Multiplies a number by 7 and adds second parameter",
" },\n" "params":
" \"returns\": {\n" {
" \"d\": \"The result of the multiplication and cookies with nutella\",\n" "a": "Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines",
" }\n" "second": "Documentation for the second parameter"
" }\n" },
"}}"; "returns":
{
"d": "The result of the multiplication and cookies with nutella"
}
}
}
})R";
checkNatspec(sourceCode, "test", natspec, false); checkNatspec(sourceCode, "test", natspec, false);
} }
@ -1004,13 +1030,14 @@ BOOST_AUTO_TEST_CASE(natspec_notice_without_tag)
char const* natspec = R"ABCDEF( char const* natspec = R"ABCDEF(
{ {
"methods" : { "methods":
"mul(uint256)" : { {
"notice" : "I do something awesome" "mul(uint256)":
} {
} "notice": "I do something awesome"
} }
)ABCDEF"; }
})ABCDEF";
checkNatspec(sourceCode, "test", natspec, true); checkNatspec(sourceCode, "test", natspec, true);
} }
@ -1027,11 +1054,13 @@ BOOST_AUTO_TEST_CASE(natspec_multiline_notice_without_tag)
char const* natspec = R"ABCDEF( char const* natspec = R"ABCDEF(
{ {
"methods" : { "methods":
"mul(uint256)" : { {
"notice" : "I do something awesome which requires two lines to explain" "mul(uint256)":
} {
} "notice": "I do something awesome which requires two lines to explain"
}
}
} }
)ABCDEF"; )ABCDEF";
@ -1047,7 +1076,7 @@ BOOST_AUTO_TEST_CASE(empty_comment)
)"; )";
char const* natspec = R"ABCDEF( char const* natspec = R"ABCDEF(
{ {
"methods" : {} "methods": {}
} }
)ABCDEF"; )ABCDEF";
@ -1135,8 +1164,10 @@ BOOST_AUTO_TEST_CASE(user_constructor)
)"; )";
char const* natspec = R"ABCDEF({ char const* natspec = R"ABCDEF({
"methods": { "methods":
"constructor" : { {
"constructor":
{
"notice": "this is a really nice constructor" "notice": "this is a really nice constructor"
} }
} }
@ -1157,12 +1188,15 @@ BOOST_AUTO_TEST_CASE(user_constructor_and_function)
)"; )";
char const* natspec = R"ABCDEF({ char const* natspec = R"ABCDEF({
"methods" : { "methods":
"mul(uint256,uint256)" : { {
"notice" : "another multiplier" "mul(uint256,uint256)":
{
"notice": "another multiplier"
}, },
"constructor" : { "constructor":
"notice" : "this is a really nice constructor" {
"notice": "this is a really nice constructor"
} }
} }
})ABCDEF"; })ABCDEF";
@ -1181,11 +1215,14 @@ BOOST_AUTO_TEST_CASE(dev_constructor)
)"; )";
char const *natspec = R"ABCDEF({ char const *natspec = R"ABCDEF({
"methods" : { "methods":
"constructor" : { {
"params" : { "constructor":
"a" : "the parameter a is really nice and very useful", {
"second" : "the second parameter is not very useful, it just provides additional confusion" "params":
{
"a": "the parameter a is really nice and very useful",
"second": "the second parameter is not very useful, it just provides additional confusion"
} }
} }
} }
@ -1228,21 +1265,27 @@ BOOST_AUTO_TEST_CASE(dev_constructor_and_function)
)"; )";
char const *natspec = R"ABCDEF({ char const *natspec = R"ABCDEF({
"methods" : { "methods":
"mul(uint256,uint256)" : { {
"details" : "Multiplies a number by 7 and adds second parameter", "mul(uint256,uint256)":
"params" : { {
"a" : "Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines", "details": "Multiplies a number by 7 and adds second parameter",
"second" : "Documentation for the second parameter" "params":
{
"a": "Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines",
"second": "Documentation for the second parameter"
}, },
"returns" : { "returns":
{
"d": "The result of the multiplication and cookies with nutella" "d": "The result of the multiplication and cookies with nutella"
} }
}, },
"constructor" : { "constructor":
"params" : { {
"a" : "the parameter a is really nice and very useful", "params":
"second" : "the second parameter is not very useful, it just provides additional confusion" {
"a": "the parameter a is really nice and very useful",
"second": "the second parameter is not very useful, it just provides additional confusion"
} }
} }
} }
@ -1292,7 +1335,8 @@ BOOST_AUTO_TEST_CASE(slash3_slash3)
)"; )";
char const* natspec = R"ABCDEF({ char const* natspec = R"ABCDEF({
"methods": { "methods":
{
"f()": { "notice": "lorem ipsum" } "f()": { "notice": "lorem ipsum" }
} }
})ABCDEF"; })ABCDEF";
@ -1311,7 +1355,8 @@ BOOST_AUTO_TEST_CASE(slash3_slash4)
)"; )";
char const* natspec = R"ABCDEF({ char const* natspec = R"ABCDEF({
"methods": { "methods":
{
"f()": { "notice": "lorem" } "f()": { "notice": "lorem" }
} }
})ABCDEF"; })ABCDEF";
@ -1340,12 +1385,12 @@ BOOST_AUTO_TEST_CASE(dev_default_inherit_variable)
})ABCDEF"; })ABCDEF";
char const *natspec1 = R"ABCDEF({ char const *natspec1 = R"ABCDEF({
"methods" : {}, "methods": {},
"stateVariables" : "stateVariables":
{ {
"x" : "x":
{ {
"details" : "test" "details": "test"
} }
} }
})ABCDEF"; })ABCDEF";
@ -1406,12 +1451,12 @@ BOOST_AUTO_TEST_CASE(dev_explicit_inherit_variable)
})ABCDEF"; })ABCDEF";
char const *natspec1 = R"ABCDEF({ char const *natspec1 = R"ABCDEF({
"methods" : {}, "methods": {},
"stateVariables" : "stateVariables":
{ {
"x" : "x":
{ {
"details" : "test" "details": "test"
} }
} }
})ABCDEF"; })ABCDEF";
@ -1467,7 +1512,7 @@ BOOST_AUTO_TEST_CASE(dev_default_inherit)
function transfer(address to, uint amount) virtual override external returns (bool) function transfer(address to, uint amount) virtual override external returns (bool)
{ {
return false; return false;
} }
} }
contract Token is Middle { contract Token is Middle {
@ -1513,8 +1558,8 @@ BOOST_AUTO_TEST_CASE(user_default_inherit)
contract Middle is ERC20 { contract Middle is ERC20 {
function transfer(address to, uint amount) virtual override external returns (bool) function transfer(address to, uint amount) virtual override external returns (bool)
{ {
return false; return false;
} }
} }
contract Token is Middle { contract Token is Middle {
@ -2295,7 +2340,7 @@ BOOST_AUTO_TEST_CASE(dev_return_name_no_description)
{ {
"returns": "returns":
{ {
"a": "a", "a": "a"
} }
} }
} }
@ -2308,7 +2353,7 @@ BOOST_AUTO_TEST_CASE(dev_return_name_no_description)
{ {
"returns": "returns":
{ {
"b": "a", "b": "a"
} }
} }
} }
@ -2333,11 +2378,11 @@ BOOST_AUTO_TEST_CASE(error)
char const* devdoc = R"X({ char const* devdoc = R"X({
"errors":{ "errors":{
"E(uint256,uint256)": [{ "E(uint256,uint256)": [{
"details" : "an error.", "details": "an error.",
"params" : "params":
{ {
"a" : "first parameter", "a": "first parameter",
"b" : "second parameter" "b": "second parameter"
} }
}] }]
}, },
@ -2349,7 +2394,7 @@ BOOST_AUTO_TEST_CASE(error)
char const* userdoc = R"X({ char const* userdoc = R"X({
"errors":{ "errors":{
"E(uint256,uint256)": [{ "E(uint256,uint256)": [{
"notice" : "Something failed." "notice": "Something failed."
}] }]
}, },
"methods": {} "methods": {}
@ -2384,22 +2429,23 @@ BOOST_AUTO_TEST_CASE(error_multiple)
char const* devdoc = R"X({ char const* devdoc = R"X({
"methods": {}, "methods": {},
"errors": { "errors":
{
"E(uint256,uint256)": [ "E(uint256,uint256)": [
{ {
"details" : "an error.", "details": "an error.",
"params" : "params":
{ {
"x" : "first parameter", "x": "first parameter",
"y" : "second parameter" "y": "second parameter"
} }
}, },
{ {
"details" : "X an error.", "details": "X an error.",
"params" : "params":
{ {
"a" : "X first parameter", "a": "X first parameter",
"b" : "X second parameter" "b": "X second parameter"
} }
} }
] ]
@ -2411,8 +2457,8 @@ BOOST_AUTO_TEST_CASE(error_multiple)
char const* userdoc = R"X({ char const* userdoc = R"X({
"errors":{ "errors":{
"E(uint256,uint256)": [ "E(uint256,uint256)": [
{ "notice" : "Something failed." }, { "notice": "Something failed." },
{ "notice" : "X Something failed." } { "notice": "X Something failed." }
] ]
}, },
"methods": {} "methods": {}
@ -2465,7 +2511,8 @@ BOOST_AUTO_TEST_CASE(custom_inheritance)
} }
)"; )";
char const* natspecA = R"ABCDEF({ char const* natspecA = R"ABCDEF(
{
"methods": "methods":
{ {
"g(uint256)": "g(uint256)":
@ -2473,8 +2520,9 @@ BOOST_AUTO_TEST_CASE(custom_inheritance)
"custom:since": "2014" "custom:since": "2014"
} }
} }
)ABCDEF"; })ABCDEF";
char const* natspecB = R"ABCDEF({ char const* natspecB = R"ABCDEF(
{
"methods": {} "methods": {}
})ABCDEF"; })ABCDEF";
@ -2482,7 +2530,7 @@ BOOST_AUTO_TEST_CASE(custom_inheritance)
checkNatspec(sourceCode, "B", natspecB, false); checkNatspec(sourceCode, "B", natspecB, false);
} }
BOOST_AUTO_TEST_CASE(dev_different_amount_return_parameters) BOOST_AUTO_TEST_CASE(dev_struct_getter_override)
{ {
char const *sourceCode = R"( char const *sourceCode = R"(
interface IThing { interface IThing {
@ -2506,11 +2554,11 @@ BOOST_AUTO_TEST_CASE(dev_different_amount_return_parameters)
{ {
"value()": "value()":
{ {
"returns": "returns":
{ {
"x": "a number", "x": "a number",
"y": "another number" "y": "another number"
} }
} }
} }
})ABCDEF"; })ABCDEF";
@ -2534,6 +2582,58 @@ BOOST_AUTO_TEST_CASE(dev_different_amount_return_parameters)
checkNatspec(sourceCode, "Thing", natspec2, false); checkNatspec(sourceCode, "Thing", natspec2, false);
} }
BOOST_AUTO_TEST_CASE(dev_struct_getter_override_different_return_parameter_names)
{
char const *sourceCode = R"(
interface IThing {
/// @return x a number
/// @return y another number
function value() external view returns (uint128 x, uint128 y);
}
contract Thing is IThing {
struct Value {
uint128 a;
uint128 b;
}
Value public override value;
}
)";
char const *natspec = R"ABCDEF({
"methods":
{
"value()":
{
"returns":
{
"x": "a number",
"y": "another number"
}
}
}
})ABCDEF";
char const *natspec2 = R"ABCDEF({
"methods": {},
"stateVariables":
{
"value":
{
"returns":
{
"a": "a number",
"b": "another number"
}
}
}
})ABCDEF";
checkNatspec(sourceCode, "IThing", natspec, false);
checkNatspec(sourceCode, "Thing", natspec2, false);
}
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,54 @@
pragma abicoder v2;
contract X {
// no "returns" on purpose
function a(uint) public pure {}
function b(uint) external pure {}
}
contract Base {
function a(uint x) external pure returns (uint) { return x + 1; }
}
contract C is Base {
function test() public view returns (uint r) {
bool success;
bytes memory result;
(success, result) = address(this).staticcall(abi.encodeCall(X.a, 1));
require(success && result.length == 32);
r += abi.decode(result, (uint));
require(r == 2);
(success, result) = address(this).staticcall(abi.encodeCall(X.b, 10));
require(success && result.length == 32);
r += abi.decode(result, (uint));
require(r == 13);
(success, result) = address(this).staticcall(abi.encodeCall(Base.a, 100));
require(success && result.length == 32);
r += abi.decode(result, (uint));
require(r == 114);
(success, result) = address(this).staticcall(abi.encodeCall(this.a, 1000));
require(success && result.length == 32);
r += abi.decode(result, (uint));
require(r == 1115);
(success, result) = address(this).staticcall(abi.encodeCall(C.b, 10000));
require(success && result.length == 32);
r += abi.decode(result, (uint));
require(r == 11116);
return r;
}
function b(uint x) external view returns (uint) {
return this.a(x);
}
}
// ====
// compileViaYul: also
// EVMVersion: >=byzantium
// ----
// test() -> 11116

View File

@ -178,7 +178,7 @@ contract DepositContract is IDepositContract, ERC165 {
// compileViaYul: also // compileViaYul: also
// ---- // ----
// constructor() // constructor()
// gas irOptimized: 1558001 // gas irOptimized: 1557137
// gas legacy: 2436584 // gas legacy: 2436584
// gas legacyOptimized: 1776483 // gas legacyOptimized: 1776483
// supportsInterface(bytes4): 0x0 -> 0 // supportsInterface(bytes4): 0x0 -> 0

View File

@ -20,7 +20,7 @@ contract test {
// compileViaYul: also // compileViaYul: also
// ---- // ----
// set(uint8,uint8,uint8,uint8,uint8): 1, 21, 22, 42, 43 -> 0, 0, 0, 0 // set(uint8,uint8,uint8,uint8,uint8): 1, 21, 22, 42, 43 -> 0, 0, 0, 0
// gas irOptimized: 111965 // gas irOptimized: 111896
// gas legacy: 113806 // gas legacy: 113806
// gas legacyOptimized: 111781 // gas legacyOptimized: 111781
// get(uint8): 1 -> 21, 22, 42, 43 // get(uint8): 1 -> 21, 22, 42, 43

View File

@ -0,0 +1,17 @@
contract C {
function g() external {}
function comparison_operators_for_external_function_pointers_with_dirty_bits() external returns (bool) {
function() external g_ptr_dirty = this.g;
assembly {
g_ptr_dirty.address := or(g_ptr_dirty.address, shl(160, sub(0,1)))
g_ptr_dirty.selector := or(g_ptr_dirty.selector, shl(32, sub(0,1)))
}
function() external g_ptr = this.g;
return g_ptr == g_ptr_dirty;
}
}
// ====
// compileViaYul: also
// EVMVersion: >=constantinople
// ----
// comparison_operators_for_external_function_pointers_with_dirty_bits() -> true

View File

@ -0,0 +1,81 @@
contract C {
function f() external {}
function g() external {}
function h() pure external {}
function i() view external {}
function comparison_operators_for_external_functions() public returns (bool) {
assert(
this.f != this.g &&
this.f != this.h &&
this.f != this.i &&
this.g != this.h &&
this.g != this.i &&
this.h != this.i &&
this.f == this.f &&
this.g == this.g &&
this.h == this.h &&
this.i == this.i
);
return true;
}
function comparison_operators_for_local_external_function_pointers() public returns (bool) {
function () external f_local = this.f;
function () external g_local = this.g;
function () external pure h_local = this.h;
function () external view i_local = this.i;
assert(
f_local == this.f &&
g_local == this.g &&
h_local == this.h &&
i_local == this.i &&
f_local != this.g &&
f_local != this.h &&
f_local != this.i &&
g_local != this.f &&
g_local != this.h &&
g_local != this.i &&
h_local != this.f &&
h_local != this.g &&
h_local != this.i &&
i_local != this.f &&
i_local != this.g &&
i_local != this.h
);
assert(
f_local == f_local &&
f_local != g_local &&
f_local != h_local &&
f_local != i_local
);
assert(
g_local == g_local &&
g_local != h_local &&
g_local != i_local
);
assert(
h_local == h_local &&
i_local == i_local &&
h_local != i_local
);
return true;
}
}
// ====
// compileViaYul: also
// ----
// comparison_operators_for_external_functions() -> true
// comparison_operators_for_local_external_function_pointers() -> true

View File

@ -0,0 +1,25 @@
contract C {
function g() external {}
function h() external payable {}
function test_function() external returns (bool){
assert (
this.g.address == this.g.address &&
this.g{gas: 42}.address == this.g.address &&
this.g{gas: 42}.selector == this.g.selector
);
assert (
this.h.address == this.h.address &&
this.h{gas: 42}.address == this.h.address &&
this.h{gas: 42}.selector == this.h.selector
);
assert (
this.h{gas: 42, value: 5}.address == this.h.address &&
this.h{gas: 42, value: 5}.selector == this.h.selector
);
return true;
}
}
// ====
// compileViaYul: also
// ----
// test_function() -> true

View File

@ -38,12 +38,12 @@ contract c {
// compileViaYul: also // compileViaYul: also
// ---- // ----
// set(uint256): 7 -> true // set(uint256): 7 -> true
// gas irOptimized: 110011 // gas irOptimized: 110119
// gas legacy: 110616 // gas legacy: 110616
// gas legacyOptimized: 110006 // gas legacyOptimized: 110006
// retrieve(uint256): 7 -> 1, 3, 4, 2 // retrieve(uint256): 7 -> 1, 3, 4, 2
// copy(uint256,uint256): 7, 8 -> true // copy(uint256,uint256): 7, 8 -> true
// gas irOptimized: 118707 // gas irOptimized: 118698
// gas legacy: 119166 // gas legacy: 119166
// gas legacyOptimized: 118622 // gas legacyOptimized: 118622
// retrieve(uint256): 7 -> 1, 3, 4, 2 // retrieve(uint256): 7 -> 1, 3, 4, 2

View File

@ -0,0 +1,11 @@
// Triggered ICE before
contract C {
function f(string calldata data) external pure returns(string memory) {
bytes calldata test = bytes(data[:3]);
return string(test);
}
}
// ====
// compileViaYul: also
// ----
// f(string): 0x20, 3, "123" -> 0x20, 3, "123"

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