diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ba66d79ffcdb..000000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "deps"] - path = deps - url = https://github.com/ethereum/cpp-dependencies diff --git a/.travis.yml b/.travis.yml index 315d29bf20a1..c30e3e0f1764 100644 --- a/.travis.yml +++ b/.travis.yml @@ -174,7 +174,6 @@ cache: ccache: true directories: - boost_1_57_0 - - build - $HOME/.local install: @@ -221,7 +220,7 @@ deploy: branch: - develop - release - - /^v[0-9]/ + - /^v\d/ # This is the deploy target for the native build (Linux and macOS) # which generates ZIPs per commit and the source tarball. # diff --git a/CMakeLists.txt b/CMakeLists.txt index 87a141a9ce07..139d4fd5dbb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,14 +8,17 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.4.16") +set(PROJECT_VERSION "0.4.17") project(solidity VERSION ${PROJECT_VERSION}) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) +# Setup cccache. +include(EthCcache) + # Let's find our dependencies include(EthDependencies) -include(deps/jsoncpp.cmake) +include(jsoncpp) find_package(Threads) diff --git a/Changelog.md b/Changelog.md index b78742063ceb..bdd6ac468db9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,28 @@ +### 0.4.17 (2017-09-21) + +Features: + * Assembly Parser: Support multiple assignment (``x, y := f()``). + * Code Generator: Keep a single copy of encoding functions when using the experimental "ABIEncoderV2". + * Code Generator: Partial support for passing ``structs`` as arguments and return parameters (requires ``pragma experimental ABIEncoderV2;`` for now). + * General: Support ``pragma experimental "v0.5.0";`` to activate upcoming breaking changes. + * General: Added ``.selector`` member on external function types to retrieve their signature. + * Optimizer: Add new optimization step to remove unused ``JUMPDEST``s. + * Static Analyzer: Warn when using deprecated builtins ``sha3`` and ``suicide`` + (replaced by ``keccak256`` and ``selfdestruct``, introduced in 0.4.2 and 0.2.0, respectively). + * Syntax Checker: Warn if no visibility is specified on contract functions. + * Type Checker: Display helpful warning for unused function arguments/return parameters. + * Type Checker: Do not show the same error multiple times for events. + * Type Checker: Greatly reduce the number of duplicate errors shown for duplicate constructors and functions. + * Type Checker: Warn on using literals as tight packing parameters in ``keccak256``, ``sha3``, ``sha256`` and ``ripemd160``. + * Type Checker: Enforce ``view`` and ``pure``. + * Type Checker: Enforce ``view`` / ``constant`` with error as experimental 0.5.0 feature. + * Type Checker: Enforce fallback functions to be ``external`` as experimental 0.5.0 feature. + +Bugfixes: + * ABI JSON: Include all overloaded events. + * Parser: Crash fix related to parseTypeName. + * Type Checker: Allow constant byte arrays. + ### 0.4.16 (2017-08-24) Features: diff --git a/README.md b/README.md index 1fed49fb493f..cb743729614b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # The Solidity Contract-Oriented Programming Language -[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/ethereum/solidity.svg?branch=develop)](https://travis-ci.org/ethereum/solidity) ## Useful links To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](https://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](https://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts. diff --git a/circle.yml b/circle.yml index fd506ba55cc0..db685da197f9 100644 --- a/circle.yml +++ b/circle.yml @@ -1,10 +1,50 @@ version: 2 jobs: build: - branches: - ignore: - - /.*/ docker: - - image: trzeci/emscripten:sdk-tag-1.37.18-64bit + - image: trzeci/emscripten:sdk-tag-1.37.21-64bit steps: - checkout + - run: + name: Install external tests deps + command: | + apt-get -qq update + apt-get -qy install netcat curl + curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.4/install.sh | NVM_DIR=/usr/local/nvm bash + - run: + name: Test external tests deps + command: | + export NVM_DIR="/usr/local/nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm + nvm --version + nvm install 6 + node --version + npm --version + - run: + name: Init submodules + command: | + git submodule update --init + - restore_cache: + name: Restore Boost build + key: &boost-cache-key emscripten-boost-{{ checksum "scripts/travis-emscripten/install_deps.sh" }}{{ checksum "scripts/travis-emscripten/build_emscripten.sh" }} + - run: + name: Bootstrap Boost + command: | + scripts/travis-emscripten/install_deps.sh + - run: + name: Build + command: | + scripts/travis-emscripten/build_emscripten.sh + - save_cache: + name: Save Boost build + key: *boost-cache-key + paths: + - boost_1_57_0 + - run: + name: Test + command: | + . /usr/local/nvm/nvm.sh + scripts/test_emscripten.sh + - store_artifacts: + path: build/solc/soljson.js + destination: soljson.js diff --git a/cmake/EthCcache.cmake b/cmake/EthCcache.cmake new file mode 100644 index 000000000000..9410cbcde7f1 --- /dev/null +++ b/cmake/EthCcache.cmake @@ -0,0 +1,15 @@ +# Setup ccache. +# +# The ccache is auto-enabled if the tool is found. +# To disable set -DCCACHE=OFF option. +if(NOT DEFINED CMAKE_CXX_COMPILER_LAUNCHER) + find_program(CCACHE ccache DOC "ccache tool path; set to OFF to disable") + if(CCACHE) + set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) + if(COMMAND cotire) + # Change ccache config to meet cotire requirements. + set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros) + endif() + message(STATUS "[ccache] Enabled: ${CCACHE}") + endif() +endif() diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 117dd319eb2f..1a00ae7064e2 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -14,14 +14,6 @@ # # These settings then end up spanning all POSIX platforms (Linux, OS X, BSD, etc) -# Use ccache if available -find_program(CCACHE_FOUND ccache) -if(CCACHE_FOUND) - set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) - set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) - message("Using ccache") -endif(CCACHE_FOUND) - include(CheckCXXCompilerFlag) check_cxx_compiler_flag(-fstack-protector-strong have_stack_protector_strong) diff --git a/cmake/jsoncpp.cmake b/cmake/jsoncpp.cmake new file mode 100644 index 000000000000..6ddf4c74c4b9 --- /dev/null +++ b/cmake/jsoncpp.cmake @@ -0,0 +1,51 @@ +include(ExternalProject) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten") + set(JSONCPP_CMAKE_COMMAND emcmake cmake) +else() + set(JSONCPP_CMAKE_COMMAND ${CMAKE_COMMAND}) +endif() + +# Disable implicit fallthrough warning in jsoncpp for gcc >= 7 until the upstream handles it properly +if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0) + set(JSONCCP_EXTRA_FLAGS -Wno-implicit-fallthrough) +else() + set(JSONCCP_EXTRA_FLAGS "") +endif() + +set(prefix "${CMAKE_BINARY_DIR}/deps") +set(JSONCPP_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}jsoncpp${CMAKE_STATIC_LIBRARY_SUFFIX}") +set(JSONCPP_INCLUDE_DIR "${prefix}/include") + +set(byproducts "") +if(CMAKE_VERSION VERSION_GREATER 3.1) + set(byproducts BUILD_BYPRODUCTS "${JSONCPP_LIBRARY}") +endif() + +ExternalProject_Add(jsoncpp-project + PREFIX "${prefix}" + DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads" + DOWNLOAD_NAME jsoncpp-1.7.7.tar.gz + URL https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz + URL_HASH SHA256=087640ebcf7fbcfe8e2717a0b9528fff89c52fcf69fa2a18cc2b538008098f97 + CMAKE_COMMAND ${JSONCPP_CMAKE_COMMAND} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + # Build static lib but suitable to be included in a shared lib. + -DCMAKE_POSITION_INDEPENDENT_CODE=${BUILD_SHARED_LIBS} + -DJSONCPP_WITH_TESTS=OFF + -DJSONCPP_WITH_PKGCONFIG_SUPPORT=OFF + -DCMAKE_CXX_FLAGS=${JSONCCP_EXTRA_FLAGS} + # Overwrite build and install commands to force Release build on MSVC. + BUILD_COMMAND cmake --build --config Release + INSTALL_COMMAND cmake --build --config Release --target install + ${byproducts} +) + +# Create jsoncpp imported library +add_library(jsoncpp STATIC IMPORTED) +file(MAKE_DIRECTORY ${JSONCPP_INCLUDE_DIR}) # Must exist. +set_property(TARGET jsoncpp PROPERTY IMPORTED_LOCATION ${JSONCPP_LIBRARY}) +set_property(TARGET jsoncpp PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${JSONCPP_INCLUDE_DIR}) +add_dependencies(jsoncpp jsoncpp-project) diff --git a/deps b/deps deleted file mode 160000 index e5c8316db8d3..000000000000 --- a/deps +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e5c8316db8d3daa0abc3b5af8545ce330057608c diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index c0969cae369d..29d98645ec06 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -17,6 +17,8 @@ We assume the interface functions of a contract are strongly typed, known at com This specification does not address contracts whose interface is dynamic or otherwise known only at run-time. Should these cases become important they can be adequately handled as facilities built within the Ethereum ecosystem. +.. _abi_function_selector: + Function Selector ================= @@ -34,45 +36,47 @@ Types The following elementary types exist: -- `uint`: unsigned integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`. e.g. `uint32`, `uint8`, `uint256`. +- ``uint``: unsigned integer type of ``M`` bits, ``0 < M <= 256``, ``M % 8 == 0``. e.g. ``uint32``, ``uint8``, ``uint256``. -- `int`: two's complement signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`. +- ``int``: two's complement signed integer type of ``M`` bits, ``0 < M <= 256``, ``M % 8 == 0``. -- `address`: equivalent to `uint160`, except for the assumed interpretation and language typing. +- ``address``: equivalent to ``uint160``, except for the assumed interpretation and language typing. -- `uint`, `int`: synonyms for `uint256`, `int256` respectively (not to be used for computing the function selector). +- ``uint``, ``int``: synonyms for ``uint256``, ``int256`` respectively (this shorthand not to be used for computing the function selector). -- `bool`: equivalent to `uint8` restricted to the values 0 and 1 +- ``bool``: equivalent to ``uint8`` restricted to the values 0 and 1 -- `fixedx`: signed fixed-point decimal number of `M` bits, `8 <= M <= 256`, `M % 8 ==0`, and `0 < N <= 80`, which denotes the value `v` as `v / (10 ** N)`. +- ``fixedx``: signed fixed-point decimal number of ``M`` bits, ``8 <= M <= 256``, ``M % 8 ==0``, and ``0 < N <= 80``, which denotes the value ``v`` as ``v / (10 ** N)``. -- `ufixedx`: unsigned variant of `fixedx`. +- ``ufixedx``: unsigned variant of ``fixedx``. -- `fixed`, `ufixed`: synonyms for `fixed128x19`, `ufixed128x19` respectively (not to be used for computing the function selector). +- ``fixed``, ``ufixed``: synonyms for ``fixed128x19``, ``ufixed128x19`` respectively (this shorthand not to be used for computing the function selector). -- `bytes`: binary type of `M` bytes, `0 < M <= 32`. +- ``bytes``: binary type of ``M`` bytes, ``0 < M <= 32``. -- `function`: equivalent to `bytes24`: an address, followed by a function selector +- ``function``: equivalent to ``bytes24``: an address, followed by a function selector The following (fixed-size) array type exists: -- `[M]`: a fixed-length array of the given fixed-length type. +- ``[M]``: a fixed-length array of ``M`` elements, ``M > 0``, of the given type. The following non-fixed-size types exist: -- `bytes`: dynamic sized byte sequence. +- ``bytes``: dynamic sized byte sequence. -- `string`: dynamic sized unicode string assumed to be UTF-8 encoded. +- ``string``: dynamic sized unicode string assumed to be UTF-8 encoded. -- `[]`: a variable-length array of the given fixed-length type. +- ``[]``: a variable-length array of elements of the given type. -Types can be combined to anonymous structs by enclosing a finite non-negative number +Types can be combined to a tuple by enclosing a finite non-negative number of them inside parentheses, separated by commas: -- `(T1,T2,...,Tn)`: anonymous struct (ordered tuple) consisting of the types `T1`, ..., `Tn`, `n >= 0` +- ``(T1,T2,...,Tn)``: tuple consisting of the types ``T1``, ..., ``Tn``, ``n >= 0`` -It is possible to form structs of structs, arrays of structs and so on. +It is possible to form tuples of tuples, arrays of tuples and so on. +.. note:: + Solidity supports all the types presented above with the same names with the exception of tuples. The ABI tuple type is utilised for encoding Solidity ``structs``. Formal Specification of the Encoding ==================================== @@ -82,98 +86,99 @@ properties, which are especially useful if some arguments are nested arrays: Properties: - 1. The number of reads necessary to access a value is at most the depth of the value inside the argument array structure, i.e. four reads are needed to retrieve `a_i[k][l][r]`. In a previous version of the ABI, the number of reads scaled linearly with the total number of dynamic parameters in the worst case. + 1. The number of reads necessary to access a value is at most the depth of the value inside the argument array structure, i.e. four reads are needed to retrieve ``a_i[k][l][r]``. In a previous version of the ABI, the number of reads scaled linearly with the total number of dynamic parameters in the worst case. 2. The data of a variable or array element is not interleaved with other data and it is relocatable, i.e. it only uses relative "addresses" We distinguish static and dynamic types. Static types are encoded in-place and dynamic types are encoded at a separately allocated location after the current block. **Definition:** The following types are called "dynamic": -* `bytes` -* `string` -* `T[]` for any `T` -* `T[k]` for any dynamic `T` and any `k > 0` -* `(T1,...,Tk)` if any `Ti` is dynamic for `1 <= i <= k` + +* ``bytes`` +* ``string`` +* ``T[]`` for any ``T`` +* ``T[k]`` for any dynamic ``T`` and any ``k > 0`` +* ``(T1,...,Tk)`` if any ``Ti`` is dynamic for ``1 <= i <= k`` All other types are called "static". -**Definition:** `len(a)` is the number of bytes in a binary string `a`. -The type of `len(a)` is assumed to be `uint256`. +**Definition:** ``len(a)`` is the number of bytes in a binary string ``a``. +The type of ``len(a)`` is assumed to be ``uint256``. -We define `enc`, the actual encoding, as a mapping of values of the ABI types to binary strings such -that `len(enc(X))` depends on the value of `X` if and only if the type of `X` is dynamic. +We define ``enc``, the actual encoding, as a mapping of values of the ABI types to binary strings such +that ``len(enc(X))`` depends on the value of ``X`` if and only if the type of ``X`` is dynamic. -**Definition:** For any ABI value `X`, we recursively define `enc(X)`, depending -on the type of `X` being +**Definition:** For any ABI value ``X``, we recursively define ``enc(X)``, depending +on the type of ``X`` being -- `(T1,...,Tk)` for `k >= 0` and any types `T1`, ..., `Tk` +- ``(T1,...,Tk)`` for ``k >= 0`` and any types ``T1``, ..., ``Tk`` - `enc(X) = head(X(1)) ... head(X(k-1)) tail(X(0)) ... tail(X(k-1))` + ``enc(X) = head(X(1)) ... head(X(k-1)) tail(X(0)) ... tail(X(k-1))`` - where `X(i)` is the `ith` component of the value, and - `head` and `tail` are defined for `Ti` being a static type as + where ``X(i)`` is the ``ith`` component of the value, and + ``head`` and ``tail`` are defined for ``Ti`` being a static type as - `head(X(i)) = enc(X(i))` and `tail(X(i)) = ""` (the empty string) + ``head(X(i)) = enc(X(i))`` and ``tail(X(i)) = ""`` (the empty string) and as - `head(X(i)) = enc(len(head(X(0)) ... head(X(k-1)) tail(X(0)) ... tail(X(i-1))))` - `tail(X(i)) = enc(X(i))` + ``head(X(i)) = enc(len(head(X(0)) ... head(X(k-1)) tail(X(0)) ... tail(X(i-1))))`` + ``tail(X(i)) = enc(X(i))`` - otherwise, i.e. if `Ti` is a dynamic type. + otherwise, i.e. if ``Ti`` is a dynamic type. - Note that in the dynamic case, `head(X(i))` is well-defined since the lengths of + Note that in the dynamic case, ``head(X(i))`` is well-defined since the lengths of the head parts only depend on the types and not the values. Its value is the offset - of the beginning of `tail(X(i))` relative to the start of `enc(X)`. + of the beginning of ``tail(X(i))`` relative to the start of ``enc(X)``. -- `T[k]` for any `T` and `k`: +- ``T[k]`` for any ``T`` and ``k``: - `enc(X) = enc((X[0], ..., X[k-1]))` + ``enc(X) = enc((X[0], ..., X[k-1]))`` - i.e. it is encoded as if it were an anonymous struct with `k` elements + i.e. it is encoded as if it were a tuple with ``k`` elements of the same type. -- `T[]` where `X` has `k` elements (`k` is assumed to be of type `uint256`): +- ``T[]`` where ``X`` has ``k`` elements (``k`` is assumed to be of type ``uint256``): - `enc(X) = enc(k) enc([X[1], ..., X[k]])` + ``enc(X) = enc(k) enc([X[1], ..., X[k]])`` - i.e. it is encoded as if it were an array of static size `k`, prefixed with + i.e. it is encoded as if it were an array of static size ``k``, prefixed with the number of elements. -- `bytes`, of length `k` (which is assumed to be of type `uint256`): +- ``bytes``, of length ``k`` (which is assumed to be of type ``uint256``): - `enc(X) = enc(k) pad_right(X)`, i.e. the number of bytes is encoded as a - `uint256` followed by the actual value of `X` as a byte sequence, followed by - the minimum number of zero-bytes such that `len(enc(X))` is a multiple of 32. + ``enc(X) = enc(k) pad_right(X)``, i.e. the number of bytes is encoded as a + ``uint256`` followed by the actual value of ``X`` as a byte sequence, followed by + the minimum number of zero-bytes such that ``len(enc(X))`` is a multiple of 32. -- `string`: +- ``string``: - `enc(X) = enc(enc_utf8(X))`, i.e. `X` is utf-8 encoded and this value is interpreted as of `bytes` type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters. + ``enc(X) = enc(enc_utf8(X))``, i.e. ``X`` is utf-8 encoded and this value is interpreted as of ``bytes`` type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters. -- `uint`: `enc(X)` is the big-endian encoding of `X`, padded on the higher-order (left) side with zero-bytes such that the length is a multiple of 32 bytes. -- `address`: as in the `uint160` case -- `int`: `enc(X)` is the big-endian two's complement encoding of `X`, padded on the higher-oder (left) side with `0xff` for negative `X` and with zero bytes for positive `X` such that the length is a multiple of 32 bytes. -- `bool`: as in the `uint8` case, where `1` is used for `true` and `0` for `false` -- `fixedx`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `int256`. -- `fixed`: as in the `fixed128x19` case -- `ufixedx`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `uint256`. -- `ufixed`: as in the `ufixed128x19` case -- `bytes`: `enc(X)` is the sequence of bytes in `X` padded with zero-bytes to a length of 32. +- ``uint``: ``enc(X)`` is the big-endian encoding of ``X``, padded on the higher-order (left) side with zero-bytes such that the length is a multiple of 32 bytes. +- ``address``: as in the ``uint160`` case +- ``int``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-oder (left) side with ``0xff`` for negative ``X`` and with zero bytes for positive ``X`` such that the length is a multiple of 32 bytes. +- ``bool``: as in the ``uint8`` case, where ``1`` is used for ``true`` and ``0`` for ``false`` +- ``fixedx``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``int256``. +- ``fixed``: as in the ``fixed128x19`` case +- ``ufixedx``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``uint256``. +- ``ufixed``: as in the ``ufixed128x19`` case +- ``bytes``: ``enc(X)`` is the sequence of bytes in ``X`` padded with zero-bytes to a length of 32. -Note that for any `X`, `len(enc(X))` is a multiple of 32. +Note that for any ``X``, ``len(enc(X))`` is a multiple of 32. Function Selector and Argument Encoding ======================================= -All in all, a call to the function `f` with parameters `a_1, ..., a_n` is encoded as +All in all, a call to the function ``f`` with parameters ``a_1, ..., a_n`` is encoded as - `function_selector(f) enc((a_1, ..., a_n))` + ``function_selector(f) enc((a_1, ..., a_n))`` -and the return values `v_1, ..., v_k` of `f` are encoded as +and the return values ``v_1, ..., v_k`` of ``f`` are encoded as - `enc((v_1, ..., v_k))` + ``enc((v_1, ..., v_k))`` -i.e. the values are combined into an anonymous struct and encoded. +i.e. the values are combined into a tuple and encoded. Examples ======== @@ -191,39 +196,40 @@ Given the contract: } -Thus for our `Foo` example if we wanted to call `baz` with the parameters `69` and `true`, we would pass 68 bytes total, which can be broken down into: +Thus for our ``Foo`` example if we wanted to call ``baz`` with the parameters ``69`` and ``true``, we would pass 68 bytes total, which can be broken down into: -- `0xcdcd77c0`: the Method ID. This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature `baz(uint32,bool)`. -- `0x0000000000000000000000000000000000000000000000000000000000000045`: the first parameter, a uint32 value `69` padded to 32 bytes -- `0x0000000000000000000000000000000000000000000000000000000000000001`: the second parameter - boolean `true`, padded to 32 bytes +- ``0xcdcd77c0``: the Method ID. This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature ``baz(uint32,bool)``. +- ``0x0000000000000000000000000000000000000000000000000000000000000045``: the first parameter, a uint32 value ``69`` padded to 32 bytes +- ``0x0000000000000000000000000000000000000000000000000000000000000001``: the second parameter - boolean ``true``, padded to 32 bytes In total:: 0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001 -It returns a single `bool`. If, for example, it were to return `false`, its output would be the single byte array `0x0000000000000000000000000000000000000000000000000000000000000000`, a single bool. +It returns a single ``bool``. If, for example, it were to return ``false``, its output would be the single byte array ``0x0000000000000000000000000000000000000000000000000000000000000000``, a single bool. -If we wanted to call `bar` with the argument `["abc", "def"]`, we would pass 68 bytes total, broken down into: +If we wanted to call ``bar`` with the argument ``["abc", "def"]``, we would pass 68 bytes total, broken down into: -- `0xfce353f6`: the Method ID. This is derived from the signature `bar(bytes3[2])`. -- `0x6162630000000000000000000000000000000000000000000000000000000000`: the first part of the first parameter, a `bytes3` value `"abc"` (left-aligned). -- `0x6465660000000000000000000000000000000000000000000000000000000000`: the second part of the first parameter, a `bytes3` value `"def"` (left-aligned). +- ``0xfce353f6``: the Method ID. This is derived from the signature ``bar(bytes3[2])``. +- ``0x6162630000000000000000000000000000000000000000000000000000000000``: the first part of the first parameter, a ``bytes3`` value ``"abc"`` (left-aligned). +- ``0x6465660000000000000000000000000000000000000000000000000000000000``: the second part of the first parameter, a ``bytes3`` value ``"def"`` (left-aligned). In total:: 0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000 -If we wanted to call `sam` with the arguments `"dave"`, `true` and `[1,2,3]`, we would pass 292 bytes total, broken down into: -- `0xa5643bf2`: the Method ID. This is derived from the signature `sam(bytes,bool,uint256[])`. Note that `uint` is replaced with its canonical representation `uint256`. -- `0x0000000000000000000000000000000000000000000000000000000000000060`: the location of the data part of the first parameter (dynamic type), measured in bytes from the start of the arguments block. In this case, `0x60`. -- `0x0000000000000000000000000000000000000000000000000000000000000001`: the second parameter: boolean true. -- `0x00000000000000000000000000000000000000000000000000000000000000a0`: the location of the data part of the third parameter (dynamic type), measured in bytes. In this case, `0xa0`. -- `0x0000000000000000000000000000000000000000000000000000000000000004`: the data part of the first argument, it starts with the length of the byte array in elements, in this case, 4. -- `0x6461766500000000000000000000000000000000000000000000000000000000`: the contents of the first argument: the UTF-8 (equal to ASCII in this case) encoding of `"dave"`, padded on the right to 32 bytes. -- `0x0000000000000000000000000000000000000000000000000000000000000003`: the data part of the third argument, it starts with the length of the array in elements, in this case, 3. -- `0x0000000000000000000000000000000000000000000000000000000000000001`: the first entry of the third parameter. -- `0x0000000000000000000000000000000000000000000000000000000000000002`: the second entry of the third parameter. -- `0x0000000000000000000000000000000000000000000000000000000000000003`: the third entry of the third parameter. +If we wanted to call ``sam`` with the arguments ``"dave"``, ``true`` and ``[1,2,3]``, we would pass 292 bytes total, broken down into: + +- ``0xa5643bf2``: the Method ID. This is derived from the signature ``sam(bytes,bool,uint256[])``. Note that ``uint`` is replaced with its canonical representation ``uint256``. +- ``0x0000000000000000000000000000000000000000000000000000000000000060``: the location of the data part of the first parameter (dynamic type), measured in bytes from the start of the arguments block. In this case, ``0x60``. +- ``0x0000000000000000000000000000000000000000000000000000000000000001``: the second parameter: boolean true. +- ``0x00000000000000000000000000000000000000000000000000000000000000a0``: the location of the data part of the third parameter (dynamic type), measured in bytes. In this case, ``0xa0``. +- ``0x0000000000000000000000000000000000000000000000000000000000000004``: the data part of the first argument, it starts with the length of the byte array in elements, in this case, 4. +- ``0x6461766500000000000000000000000000000000000000000000000000000000``: the contents of the first argument: the UTF-8 (equal to ASCII in this case) encoding of ``"dave"``, padded on the right to 32 bytes. +- ``0x0000000000000000000000000000000000000000000000000000000000000003``: the data part of the third argument, it starts with the length of the array in elements, in this case, 3. +- ``0x0000000000000000000000000000000000000000000000000000000000000001``: the first entry of the third parameter. +- ``0x0000000000000000000000000000000000000000000000000000000000000002``: the second entry of the third parameter. +- ``0x0000000000000000000000000000000000000000000000000000000000000003``: the third entry of the third parameter. In total:: @@ -232,26 +238,26 @@ In total:: Use of Dynamic Types ==================== -A call to a function with the signature `f(uint,uint32[],bytes10,bytes)` with values `(0x123, [0x456, 0x789], "1234567890", "Hello, world!")` is encoded in the following way: +A call to a function with the signature ``f(uint,uint32[],bytes10,bytes)`` with values ``(0x123, [0x456, 0x789], "1234567890", "Hello, world!")`` is encoded in the following way: -We take the first four bytes of `sha3("f(uint256,uint32[],bytes10,bytes)")`, i.e. `0x8be65246`. -Then we encode the head parts of all four arguments. For the static types `uint256` and `bytes10`, these are directly the values we want to pass, whereas for the dynamic types `uint32[]` and `bytes`, we use the offset in bytes to the start of their data area, measured from the start of the value encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are: +We take the first four bytes of ``sha3("f(uint256,uint32[],bytes10,bytes)")``, i.e. ``0x8be65246``. +Then we encode the head parts of all four arguments. For the static types ``uint256`` and ``bytes10``, these are directly the values we want to pass, whereas for the dynamic types ``uint32[]`` and ``bytes``, we use the offset in bytes to the start of their data area, measured from the start of the value encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are: - - `0x0000000000000000000000000000000000000000000000000000000000000123` (`0x123` padded to 32 bytes) - - `0x0000000000000000000000000000000000000000000000000000000000000080` (offset to start of data part of second parameter, 4*32 bytes, exactly the size of the head part) - - `0x3132333435363738393000000000000000000000000000000000000000000000` (`"1234567890"` padded to 32 bytes on the right) - - `0x00000000000000000000000000000000000000000000000000000000000000e0` (offset to start of data part of fourth parameter = offset to start of data part of first dynamic parameter + size of data part of first dynamic parameter = 4\*32 + 3\*32 (see below)) + - ``0x0000000000000000000000000000000000000000000000000000000000000123`` (``0x123`` padded to 32 bytes) + - ``0x0000000000000000000000000000000000000000000000000000000000000080`` (offset to start of data part of second parameter, 4*32 bytes, exactly the size of the head part) + - ``0x3132333435363738393000000000000000000000000000000000000000000000`` (``"1234567890"`` padded to 32 bytes on the right) + - ``0x00000000000000000000000000000000000000000000000000000000000000e0`` (offset to start of data part of fourth parameter = offset to start of data part of first dynamic parameter + size of data part of first dynamic parameter = 4\*32 + 3\*32 (see below)) -After this, the data part of the first dynamic argument, `[0x456, 0x789]` follows: +After this, the data part of the first dynamic argument, ``[0x456, 0x789]`` follows: - - `0x0000000000000000000000000000000000000000000000000000000000000002` (number of elements of the array, 2) - - `0x0000000000000000000000000000000000000000000000000000000000000456` (first element) - - `0x0000000000000000000000000000000000000000000000000000000000000789` (second element) + - ``0x0000000000000000000000000000000000000000000000000000000000000002`` (number of elements of the array, 2) + - ``0x0000000000000000000000000000000000000000000000000000000000000456`` (first element) + - ``0x0000000000000000000000000000000000000000000000000000000000000789`` (second element) -Finally, we encode the data part of the second dynamic argument, `"Hello, world!"`: +Finally, we encode the data part of the second dynamic argument, ``"Hello, world!"``: - - `0x000000000000000000000000000000000000000000000000000000000000000d` (number of elements (bytes in this case): 13) - - `0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000` (`"Hello, world!"` padded to 32 bytes on the right) + - ``0x000000000000000000000000000000000000000000000000000000000000000d`` (number of elements (bytes in this case): 13) + - ``0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000`` (``"Hello, world!"`` padded to 32 bytes on the right) All together, the encoding is (newline after function selector and each 32-bytes for clarity): @@ -277,41 +283,48 @@ Given an event name and series of event parameters, we split them into two sub-s In effect, a log entry using this ABI is described as: -- `address`: the address of the contract (intrinsically provided by Ethereum); -- `topics[0]`: `keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")` (`canonical_type_of` is a function that simply returns the canonical type of a given argument, e.g. for `uint indexed foo`, it would return `uint256`). If the event is declared as `anonymous` the `topics[0]` is not generated; -- `topics[n]`: `EVENT_INDEXED_ARGS[n - 1]` (`EVENT_INDEXED_ARGS` is the series of `EVENT_ARGS` that are indexed); -- `data`: `abi_serialise(EVENT_NON_INDEXED_ARGS)` (`EVENT_NON_INDEXED_ARGS` is the series of `EVENT_ARGS` that are not indexed, `abi_serialise` is the ABI serialisation function used for returning a series of typed values from a function, as described above). +- ``address``: the address of the contract (intrinsically provided by Ethereum); +- ``topics[0]``: ``keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")`` (``canonical_type_of`` is a function that simply returns the canonical type of a given argument, e.g. for ``uint indexed foo``, it would return ``uint256``). If the event is declared as ``anonymous`` the ``topics[0]`` is not generated; +- ``topics[n]``: ``EVENT_INDEXED_ARGS[n - 1]`` (``EVENT_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are indexed); +- ``data``: ``abi_serialise(EVENT_NON_INDEXED_ARGS)`` (``EVENT_NON_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are not indexed, ``abi_serialise`` is the ABI serialisation function used for returning a series of typed values from a function, as described above). JSON ==== -The JSON format for a contract's interface is given by an array of function and/or event descriptions. A function description is a JSON object with the fields: +The JSON format for a contract's interface is given by an array of function and/or event descriptions. +A function description is a JSON object with the fields: -- `type`: `"function"`, `"constructor"`, or `"fallback"` (the :ref:`unnamed "default" function `); -- `name`: the name of the function; -- `inputs`: an array of objects, each of which contains: - * `name`: the name of the parameter; - * `type`: the canonical type of the parameter. -- `outputs`: an array of objects similar to `inputs`, can be omitted if function doesn't return anything; -- `constant`: `true` if function is :ref:`specified to not modify blockchain state `); -- `payable`: `true` if function accepts ether, defaults to `false`; -- `stateMutability`: a string with one of the following values: `pure` (:ref:`specified to not read blockchain state `), `view` (same as `constant` above), `nonpayable` and `payable` (same as `payable` above). +- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function `); +- ``name``: the name of the function; +- ``inputs``: an array of objects, each of which contains: -`type` can be omitted, defaulting to `"function"`. + * ``name``: the name of the parameter; + * ``type``: the canonical type of the parameter (more below). + * ``components``: used for tuple types (more below). -Constructor and fallback function never have `name` or `outputs`. Fallback function doesn't have `inputs` either. +- ``outputs``: an array of objects similar to ``inputs``, can be omitted if function doesn't return anything; +- ``payable``: ``true`` if function accepts ether, defaults to ``false``; +- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state `), ``view`` (:ref:`specified to not modify the blockchain state `), ``nonpayable`` and ``payable`` (same as ``payable`` above). +- ``constant``: ``true`` if function is either ``pure`` or ``view`` + +``type`` can be omitted, defaulting to ``"function"``. + +Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either. Sending non-zero ether to non-payable function will throw. Don't do it. An event description is a JSON object with fairly similar fields: -- `type`: always `"event"` -- `name`: the name of the event; -- `inputs`: an array of objects, each of which contains: - * `name`: the name of the parameter; - * `type`: the canonical type of the parameter. - * `indexed`: `true` if the field is part of the log's topics, `false` if it one of the log's data segment. -- `anonymous`: `true` if the event was declared as `anonymous`. +- ``type``: always ``"event"`` +- ``name``: the name of the event; +- ``inputs``: an array of objects, each of which contains: + + * ``name``: the name of the parameter; + * ``type``: the canonical type of the parameter (more below). + * ``components``: used for tuple types (more below). + * ``indexed``: ``true`` if the field is part of the log's topics, ``false`` if it one of the log's data segment. + +- ``anonymous``: ``true`` if the event was declared as ``anonymous``. For example, @@ -345,3 +358,87 @@ would result in the JSON: "name":"foo", "outputs": [] }] + +Handling tuple types +-------------------- + +Despite that names are intentionally not part of the ABI encoding they do make a lot of sense to be included +in the JSON to enable displaying it to the end user. The structure is nested in the following way: + +An object with members ``name``, ``type`` and potentially ``components`` describes a typed variable. +The canonical type is determined until a tuple type is reached and the string description up +to that point is stored in ``type`` prefix with the word ``tuple``, i.e. it will be ``tuple`` followed by +a sequence of ``[]`` and ``[k]`` with +integers ``k``. The components of the tuple are then stored in the member ``components``, +which is of array type and has the same structure as the top-level object except that +``indexed`` is not allowed there. + +As an example, the code + +:: + + contract Test { + struct S { uint a; uint[] b; T[] c; } + struct T { uint x; uint y; } + function f(S s, T t, uint a) { } + } + +would result in the JSON: + +.. code:: json + + [ + { + "name": "f", + "type": "function", + "inputs": [ + { + "name": "s", + "type": "tuple", + "components": [ + { + "name": "a", + "type": "uint256" + }, + { + "name": "b", + "type": "uint256[]" + }, + { + "name": "c", + "type": "tuple[]", + "components": [ + { + "name": "x", + "type": "uint256" + }, + { + "name": "y", + "type": "uint256" + } + ] + } + ] + }, + { + "name": "t", + "type": "tuple", + "components": [ + { + "name": "x", + "type": "uint256" + }, + { + "name": "y", + "type": "uint256" + } + ] + }, + { + "name": "a", + "type": "uint256" + } + ], + "outputs": [] + } + ] diff --git a/docs/assembly.rst b/docs/assembly.rst index 6495699f77ce..f5abcdc83008 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -9,9 +9,10 @@ This assembly language can also be used as "inline assembly" inside Solidity source code. We start with describing how to use inline assembly and how it differs from standalone assembly and then specify assembly itself. -TODO: Write about how scoping rules of inline assembly are a bit different -and the complications that arise when for example using internal functions -of libraries. Furthermore, write about the symbols defined by the compiler. +.. note:: + TODO: Write about how scoping rules of inline assembly are a bit different + and the complications that arise when for example using internal functions + of libraries. Furthermore, write about the symbols defined by the compiler. .. _inline-assembly: @@ -76,7 +77,7 @@ you really know what you are doing. .. code:: - pragma solidity ^0.4.0; + pragma solidity ^0.4.12; library VectorSum { // This function is less efficient because the optimizer currently fails to @@ -1005,7 +1006,7 @@ that modifies the stack and with every label that is annotated with a stack adjustment. Every time a new local variable is introduced, it is registered together with the current stack height. If a variable is accessed (either for copying its value or for -assignment), the appropriate DUP or SWAP instruction is selected depending +assignment), the appropriate ``DUP`` or ``SWAP`` instruction is selected depending on the difference between the current stack height and the stack height at the point the variable was introduced. diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index ea242085550c..c3686ebfdf1b 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -358,6 +358,10 @@ "bugs": [], "released": "2017-08-24" }, + "0.4.17": { + "bugs": [], + "released": "2017-09-21" + }, "0.4.2": { "bugs": [ "DelegateCallReturnValue", diff --git a/docs/contracts.rst b/docs/contracts.rst index 50e7f3d1fb0c..69600fc165f6 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -16,51 +16,23 @@ inaccessible. Creating Contracts ****************** -Contracts can be created "from outside" or from Solidity contracts. +Contracts can be created "from outside" via Ethereum transactions or from within Solidity contracts. + +IDEs, such as `Remix `_, make the creation process seamless using UI elements. + +Creating contracts programatically on Ethereum is best done via using the JavaScript API `web3.js `_. +As of today it has a method called `web3.eth.Contract `_ +to facilitate contract creation. + When a contract is created, its constructor (a function with the same name as the contract) is executed once. - A constructor is optional. Only one constructor is allowed, and this means overloading is not supported. -From ``web3.js``, i.e. the JavaScript -API, this is done as follows:: - - // Need to specify some source including contract name for the data param below - var source = "contract CONTRACT_NAME { function CONTRACT_NAME(uint a, uint b) {} }"; - - // The json abi array generated by the compiler - var abiArray = [ - { - "inputs":[ - {"name":"x","type":"uint256"}, - {"name":"y","type":"uint256"} - ], - "type":"constructor" - }, - { - "constant":true, - "inputs":[], - "name":"x", - "outputs":[{"name":"","type":"bytes32"}], - "type":"function" - } - ]; - - var MyContract_ = web3.eth.contract(source); - MyContract = web3.eth.contract(MyContract_.CONTRACT_NAME.info.abiDefinition); - // deploy new contract - var contractInstance = MyContract.new( - 10, - 11, - {from: myAccount, gas: 1000000} - ); - .. index:: constructor;arguments -Internally, constructor arguments are passed after the code of -the contract itself, but you do not have to care about this -if you use ``web3.js``. +Internally, constructor arguments are passed :ref:`ABI encoded ` after the code of +the contract itself, but you do not have to care about this if you use ``web3.js``. If a contract wants to create another contract, the source code (and the binary) of the created contract has to be known to the creator. @@ -469,9 +441,20 @@ View Functions Functions can be declared ``view`` in which case they promise not to modify the state. +The following statements are considered modifying the state: + +#. Writing to state variables. +#. :ref:`Emitting events. `. +#. :ref:`Creating other contracts `. +#. Using ``selfdestruct``. +#. Sending Ether via calls. +#. Calling any function not marked ``view`` or ``pure``. +#. Using low-level calls. +#. Using inline assembly that contains certain opcodes. + :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { function f(uint a, uint b) view returns (uint) { @@ -496,9 +479,17 @@ Pure Functions Functions can be declared ``pure`` in which case they promise not to read from or modify the state. +In addition to the list of state modifying statements explained above, the following are considered reading from the state: + +#. Reading from state variables. +#. Accessing ``this.balance`` or ``
.balance``. +#. Accessing any of the members of ``block``, ``tx``, ``msg`` (with the exception of ``msg.sig`` and ``msg.data``). +#. Calling any function not marked ``pure``. +#. Using inline assembly that contains certain opcodes. + :: - pragma solidity ^0.4.0; + pragma solidity ^0.4.16; contract C { function f(uint a, uint b) pure returns (uint) { @@ -524,9 +515,11 @@ functions match the given function identifier (or if no data was supplied at all). Furthermore, this function is executed whenever the contract receives plain -Ether (without data). In such a context, there is usually very little gas available to -the function call (to be precise, 2300 gas), so it is important to make fallback functions as cheap as -possible. +Ether (without data). Additionally, in order to receive Ether, the fallback function +must be marked ``payable``. If no such function exists, the contract cannot receive +Ether through regular transactions. + +In such a context, there is usually very little gas available to the function call (to be precise, 2300 gas), so it is important to make fallback functions as cheap as possible. Note that the gas required by a transaction (as opposed to an internal call) that invokes the fallback function is much higher, because each transaction charges an additional amount of 21000 gas or more for things like signature checking. In particular, the following operations will consume more gas than the stipend provided to a fallback function: @@ -537,6 +530,10 @@ In particular, the following operations will consume more gas than the stipend p Please ensure you test your fallback function thoroughly to ensure the execution cost is less than 2300 gas before deploying a contract. +.. note:: + Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve + any payload supplied with the call. + .. warning:: Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``) but do not define a fallback function @@ -544,6 +541,14 @@ Please ensure you test your fallback function thoroughly to ensure the execution before Solidity v0.4.0). So if you want your contract to receive Ether, you have to implement a fallback function. +.. warning:: + A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`) + or as a destination of a ``selfdestruct``. + + A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it. + + It also means that ``this.balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function). + :: pragma solidity ^0.4.0; @@ -1100,7 +1105,7 @@ are all compiled as calls (``DELEGATECALL``) to an external contract/library. If you use libraries, take care that an actual external function call is performed. ``msg.sender``, ``msg.value`` and ``this`` will retain their values -in this call, though (prior to Homestead, because of the use of `CALLCODE`, ``msg.sender`` and +in this call, though (prior to Homestead, because of the use of ``CALLCODE``, ``msg.sender`` and ``msg.value`` changed, though). The following example shows how to use memory types and diff --git a/docs/contributing.rst b/docs/contributing.rst index 9d1b2ce327b8..01caa5b15129 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -66,14 +66,19 @@ Running the compiler tests Solidity includes different types of tests. They are included in the application called ``soltest``. Some of them require the ``cpp-ethereum`` client in testing mode. -To run ``cpp-ethereum`` in testing mode: ``eth --test -d /tmp/testeth``. +To run a subset of the tests that do not require ``cpp-ethereum``, use ``./build/test/soltest -- --no-ipc``. -To run the tests: ``soltest -- --ipcpath /tmp/testeth/geth.ipc``. +For all other tests, you need to install `cpp-ethereum `_ and run it in testing mode: ``eth --test -d /tmp/testeth``. + +Then you run the actual tests: ``./build/test/soltest -- --ipcpath /tmp/testeth/geth.ipc``. To run a subset of tests, filters can be used: ``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``. -Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests. +Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests and runs +``cpp-ethereum`` automatically if it is in the path (but does not download it). + +Travis CI even runs some additional tests (including ``solc-js`` and testing third party Solidity frameworks) that require compiling the Emscripten target. Whiskers ======== diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 796e92389fcd..0497365b1a25 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -206,7 +206,7 @@ Those names will still be present on the stack, but they are inaccessible. return k; } } - + .. index:: ! new, contracts;creating @@ -237,16 +237,17 @@ creation-dependencies are not possible. D newD = new D(arg); } - function createAndEndowD(uint arg, uint amount) { + function createAndEndowD(uint arg, uint amount) payable { // Send ether along with the creation D newD = (new D).value(amount)(arg); } } -As seen in the example, it is possible to forward Ether to the creation using the ``.value()`` option, -but it is not possible to limit the amount of gas. If the creation fails -(due to out-of-stack, not enough balance or other problems), an exception -is thrown. +As seen in the example, it is possible to forward Ether while creating +an instance of ``D`` using the ``.value()`` option, but it is not possible +to limit the amount of gas. +If the creation fails (due to out-of-stack, not enough balance or other problems), +an exception is thrown. Order of Evaluation of Expressions ================================== @@ -382,14 +383,17 @@ Solidity uses state-reverting exceptions to handle errors. Such an exception wil state in the current call (and all its sub-calls) and also flag an error to the caller. The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception if the condition is not met. The ``assert`` function should only be used to test for internal errors, and to check invariants. -The ``require`` function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts. +The ``require`` function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts. If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix. There are two other ways to trigger exceptions: The ``revert`` function can be used to flag an error and revert the current call. In the future it might be possible to also include details about the error in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``. -When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send`` +.. note:: + From version 0.4.13 the ``throw`` keyword is deprecated and will be phased out in the future. + +When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send`` and the low-level functions ``call``, ``delegatecall`` and ``callcode`` -- those return ``false`` in case of an exception instead of "bubbling up". diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index 5f1a981ea63a..f59d86e7ece0 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -103,11 +103,6 @@ This is a limitation of the EVM and will be solved with the next protocol update Returning variably-sized data as part of an external transaction or call is fine. -How do you represent ``double``/``float`` in Solidity? -====================================================== - -This is not yet possible. - Is it possible to in-line initialize an array like so: ``string[] myarray = ["a", "b"];`` ========================================================================================= @@ -125,24 +120,6 @@ Example:: } } -Are timestamps (``now,`` ``block.timestamp``) reliable? -======================================================= - -This depends on what you mean by "reliable". -In general, they are supplied by miners and are therefore vulnerable. - -Unless someone really messes up the blockchain or the clock on -your computer, you can make the following assumptions: - -You publish a transaction at a time X, this transaction contains same -code that calls ``now`` and is included in a block whose timestamp is Y -and this block is included into the canonical chain (published) at a time Z. - -The value of ``now`` will be identical to Y and X <= Y <= Z. - -Never use ``now`` or ``block.hash`` as a source of randomness, unless you know -what you are doing! - Can a contract function return a ``struct``? ============================================ @@ -155,37 +132,6 @@ Enums are not supported by the ABI, they are just supported by Solidity. You have to do the mapping yourself for now, we might provide some help later. -What is the deal with ``function () { ... }`` inside Solidity contracts? How can a function not have a name? -============================================================================================================ - -This function is called "fallback function" and it -is called when someone just sent Ether to the contract without -providing any data or if someone messed up the types so that they tried to -call a function that does not exist. - -The default behaviour (if no fallback function is explicitly given) in -these situations is to throw an exception. - -If the contract is meant to receive Ether with simple transfers, you -should implement the fallback function as - -``function() payable { }`` - -Another use of the fallback function is to e.g. register that your -contract received ether by using an event. - -*Attention*: If you implement the fallback function take care that it uses as -little gas as possible, because ``send()`` will only supply a limited amount. - -Is it possible to pass arguments to the fallback function? -========================================================== - -The fallback function cannot take parameters. - -Under special circumstances, you can send data. If you take care -that none of the other functions is invoked, you can access the data -by ``msg.data``. - Can state variables be initialized in-line? =========================================== @@ -230,13 +176,6 @@ Better use ``for (uint i = 0; i < a.length...`` See `struct_and_for_loop_tester.sol `_. -What character set does Solidity use? -===================================== - -Solidity is character set agnostic concerning strings in the source code, although -UTF-8 is recommended. Identifiers (variables, functions, ...) can only use -ASCII. - What are some examples of basic string manipulation (``substring``, ``indexOf``, ``charAt``, etc)? ================================================================================================== @@ -441,23 +380,6 @@ The correct way to do this is the following:: } } -What is the difference between ``bytes`` and ``byte[]``? -======================================================== - -``bytes`` is usually more efficient: When used as arguments to functions (i.e. in -CALLDATA) or in memory, every single element of a ``byte[]`` is padded to 32 -bytes which wastes 31 bytes per element. - -Is it possible to send a value while calling an overloaded function? -==================================================================== - -It's a known missing feature. https://www.pivotaltracker.com/story/show/92020468 -as part of https://www.pivotaltracker.com/n/projects/1189488 - -Best solution currently see is to introduce a special case for gas and value and -just re-check whether they are present at the point of overload resolution. - - ****************** Advanced Questions ****************** @@ -503,23 +425,6 @@ Note2: Optimizing storage access can pull the gas costs down considerably, becau currently do not work across loops and also have a problem with bounds checking. You might get much better results in the future, though. -What does ``p.recipient.call.value(p.amount)(p.data)`` do? -========================================================== - -Every external function call in Solidity can be modified in two ways: - -1. You can add Ether together with the call -2. You can limit the amount of gas available to the call - -This is done by "calling a function on the function": - -``f.gas(2).value(20)()`` calls the modified function ``f`` and thereby sending 20 -Wei and limiting the gas to 2 (so this function call will most likely go out of -gas and return your 20 Wei). - -In the above example, the low-level function ``call`` is used to invoke another -contract with ``p.data`` as payload and ``p.amount`` Wei is sent with that call. - What happens to a ``struct``'s mapping when copying over a ``struct``? ====================================================================== diff --git a/docs/index.rst b/docs/index.rst index 8c33fb9da9ab..cb093bd6bbe4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -144,6 +144,7 @@ Contents solidity-in-depth.rst security-considerations.rst using-the-compiler.rst + metadata.rst abi-spec.rst style-guide.rst common-patterns.rst diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 782bb606b391..7160774527f6 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -99,7 +99,7 @@ Arch Linux also has packages, albeit limited to the latest development version: .. code:: bash - pacman -S solidity-git + pacman -S solidity Homebrew is missing pre-built bottles at the time of writing, following a Jenkins to TravisCI migration, but Homebrew diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 1a3cf638707d..aedc0c09f668 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -57,6 +57,14 @@ and overwrite your number, but the number will still be stored in the history of the blockchain. Later, we will see how you can impose access restrictions so that only you can alter the number. +.. note:: + All identifiers (contract names, function names and variable names) are restricted to + the ASCII character set. It is possible to store UTF-8 encoded data in string variables. + +.. warning:: + Be careful with using Unicode text as similarly looking (or even identical) characters can + have different code points and as such will be encoded as a different byte array. + .. index:: ! subcurrency Subcurrency Example diff --git a/docs/metadata.rst b/docs/metadata.rst new file mode 100644 index 000000000000..dbde87e86f80 --- /dev/null +++ b/docs/metadata.rst @@ -0,0 +1,144 @@ +################# +Contract Metadata +################# + +.. index:: metadata, contract verification + +The Solidity compiler automatically generates a JSON file, the +contract metadata, that contains information about the current contract. +It can be used to query the compiler version, the sources used, the ABI +and NatSpec documentation in order to more safely interact with the contract +and to verify its source code. + +The compiler appends a Swarm hash of the metadata file to the end of the +bytecode (for details, see below) of each contract, so that you can retrieve +the file in an authenticated way without having to resort to a centralized +data provider. + +Of course, you have to publish the metadata file to Swarm (or some other service) +so that others can access it. The file can be output by using ``solc --metadata`` +and the file will be called ``ContractName_meta.json``. +It will contain Swarm references to the source code, so you have to upload +all source files and the metadata file. + +The metadata file has the following format. The example below is presented in a +human-readable way. Properly formatted metadata should use quotes correctly, +reduce whitespace to a minimum and sort the keys of all objects to arrive at a +unique formatting. +Comments are of course also not permitted and used here only for explanatory purposes. + +.. code-block:: none + + { + // Required: The version of the metadata format + version: "1", + // Required: Source code language, basically selects a "sub-version" + // of the specification + language: "Solidity", + // Required: Details about the compiler, contents are specific + // to the language. + compiler: { + // Required for Solidity: Version of the compiler + version: "0.4.6+commit.2dabbdf0.Emscripten.clang", + // Optional: Hash of the compiler binary which produced this output + keccak256: "0x123..." + }, + // Required: Compilation source files/source units, keys are file names + sources: + { + "myFile.sol": { + // Required: keccak256 hash of the source file + "keccak256": "0x123...", + // Required (unless "content" is used, see below): Sorted URL(s) + // to the source file, protocol is more or less arbitrary, but a + // Swarm URL is recommended + "urls": [ "bzzr://56ab..." ] + }, + "mortal": { + // Required: keccak256 hash of the source file + "keccak256": "0x234...", + // Required (unless "url" is used): literal contents of the source file + "content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }" + } + }, + // Required: Compiler settings + settings: + { + // Required for Solidity: Sorted list of remappings + remappings: [ ":g/dir" ], + // Optional: Optimizer settings (enabled defaults to false) + optimizer: { + enabled: true, + runs: 500 + }, + // Required for Solidity: File and name of the contract or library this + // metadata is created for. + compilationTarget: { + "myFile.sol": "MyContract" + }, + // Required for Solidity: Addresses for libraries used + libraries: { + "MyLib": "0x123123..." + } + }, + // Required: Generated information about the contract. + output: + { + // Required: ABI definition of the contract + abi: [ ... ], + // Required: NatSpec user documentation of the contract + userdoc: [ ... ], + // Required: NatSpec developer documentation of the contract + devdoc: [ ... ], + } + } + +.. note:: + Note the ABI definition above has no fixed order. It can change with compiler versions. + +.. note:: + Since the bytecode of the resulting contract contains the metadata hash, any change to + the metadata will result in a change of the bytecode. Furthermore, since the metadata + includes a hash of all the sources used, a single whitespace change in any of the source + codes will result in a different metadata, and subsequently a different bytecode. + +Encoding of the Metadata Hash in the Bytecode +============================================= + +Because we might support other ways to retrieve the metadata file in the future, +the mapping ``{"bzzr0": }`` is stored +`CBOR `_-encoded. Since the beginning of that +encoding is not easy to find, its length is added in a two-byte big-endian +encoding. The current version of the Solidity compiler thus adds the following +to the end of the deployed bytecode:: + + 0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29 + +So in order to retrieve the data, the end of the deployed bytecode can be checked +to match that pattern and use the Swarm hash to retrieve the file. + +Usage for Automatic Interface Generation and NatSpec +==================================================== + +The metadata is used in the following way: A component that wants to interact +with a contract (e.g. Mist) retrieves the code of the contract, from that +the Swarm hash of a file which is then retrieved. +That file is JSON-decoded into a structure like above. + +The component can then use the ABI to automatically generate a rudimentary +user interface for the contract. + +Furthermore, Mist can use the userdoc to display a confirmation message to the user +whenever they interact with the contract. + +Usage for Source Code Verification +================================== + +In order to verify the compilation, sources can be retrieved from Swarm +via the link in the metadata file. +The compiler of the correct version (which is checked to be part of the "official" compilers) +is invoked on that input with the specified settings. The resulting +bytecode is compared to the data of the creation transaction or ``CREATE`` opcode data. +This automatically verifies the metadata since its hash is part of the bytecode. +Excess data corresponds to the constructor input data, which should be decoded +according to the interface and presented to the user. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index e78c4807a1fb..6d6c25acb908 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -84,10 +84,8 @@ Layout of Call Data ******************* When a Solidity contract is deployed and when it is called from an -account, the input data is assumed to be in the format in `the ABI -specification -`_. The -ABI specification requires arguments to be padded to multiples of 32 +account, the input data is assumed to be in the format in :ref:`the ABI +specification `. The ABI specification requires arguments to be padded to multiples of 32 bytes. The internal function calls use a different convention. @@ -145,13 +143,13 @@ Different types have different rules for cleaning up invalid values: Internals - The Optimizer ************************* -The Solidity optimizer operates on assembly, so it can be and also is used by other languages. It splits the sequence of instructions into basic blocks at JUMPs and JUMPDESTs. Inside these blocks, the instructions are analysed and every modification to the stack, to memory or storage is recorded as an expression which consists of an instruction and a list of arguments which are essentially pointers to other expressions. The main idea is now to find expressions that are always equal (on every input) and combine them into an expression class. The optimizer first tries to find each new expression in a list of already known expressions. If this does not work, the expression is simplified according to rules like ``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is done recursively, we can also apply the latter rule if the second factor is a more complex expression where we know that it will always evaluate to one. Modifications to storage and memory locations have to erase knowledge about storage and memory locations which are not known to be different: If we first write to location x and then to location y and both are input variables, the second could overwrite the first, so we actually do not know what is stored at x after we wrote to y. On the other hand, if a simplification of the expression x - y evaluates to a non-zero constant, we know that we can keep our knowledge about what is stored at x. +The Solidity optimizer operates on assembly, so it can be and also is used by other languages. It splits the sequence of instructions into basic blocks at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the instructions are analysed and every modification to the stack, to memory or storage is recorded as an expression which consists of an instruction and a list of arguments which are essentially pointers to other expressions. The main idea is now to find expressions that are always equal (on every input) and combine them into an expression class. The optimizer first tries to find each new expression in a list of already known expressions. If this does not work, the expression is simplified according to rules like ``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is done recursively, we can also apply the latter rule if the second factor is a more complex expression where we know that it will always evaluate to one. Modifications to storage and memory locations have to erase knowledge about storage and memory locations which are not known to be different: If we first write to location x and then to location y and both are input variables, the second could overwrite the first, so we actually do not know what is stored at x after we wrote to y. On the other hand, if a simplification of the expression x - y evaluates to a non-zero constant, we know that we can keep our knowledge about what is stored at x. -At the end of this process, we know which expressions have to be on the stack in the end and have a list of modifications to memory and storage. This information is stored together with the basic blocks and is used to link them. Furthermore, knowledge about the stack, storage and memory configuration is forwarded to the next block(s). If we know the targets of all JUMP and JUMPI instructions, we can build a complete control flow graph of the program. If there is only one target we do not know (this can happen as in principle, jump targets can be computed from inputs), we have to erase all knowledge about the input state of a block as it can be the target of the unknown JUMP. If a JUMPI is found whose condition evaluates to a constant, it is transformed to an unconditional jump. +At the end of this process, we know which expressions have to be on the stack in the end and have a list of modifications to memory and storage. This information is stored together with the basic blocks and is used to link them. Furthermore, knowledge about the stack, storage and memory configuration is forwarded to the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions, we can build a complete control flow graph of the program. If there is only one target we do not know (this can happen as in principle, jump targets can be computed from inputs), we have to erase all knowledge about the input state of a block as it can be the target of the unknown ``JUMP``. If a ``JUMPI`` is found whose condition evaluates to a constant, it is transformed to an unconditional jump. As the last step, the code in each block is completely re-generated. A dependency graph is created from the expressions on the stack at the end of the block and every operation that is not part of this graph is essentially dropped. Now code is generated that applies the modifications to memory and storage in the order they were made in the original code (dropping modifications which were found not to be needed) and finally, generates all values that are required to be on the stack in the correct place. -These steps are applied to each basic block and the newly generated code is used as replacement if it is smaller. If a basic block is split at a JUMPI and during the analysis, the condition evaluates to a constant, the JUMPI is replaced depending on the value of the constant, and thus code like +These steps are applied to each basic block and the newly generated code is used as replacement if it is smaller. If a basic block is split at a ``JUMPI`` and during the analysis, the condition evaluates to a constant, the ``JUMPI`` is replaced depending on the value of the constant, and thus code like :: @@ -223,156 +221,12 @@ This means the following source mappings represent the same information: ``1:2:1;:9;2::2;;`` -***************** -Contract Metadata -***************** - -The Solidity compiler automatically generates a JSON file, the -contract metadata, that contains information about the current contract. -It can be used to query the compiler version, the sources used, the ABI -and NatSpec documentation in order to more safely interact with the contract -and to verify its source code. - -The compiler appends a Swarm hash of the metadata file to the end of the -bytecode (for details, see below) of each contract, so that you can retrieve -the file in an authenticated way without having to resort to a centralized -data provider. - -Of course, you have to publish the metadata file to Swarm (or some other service) -so that others can access it. The file can be output by using ``solc --metadata`` -and the file will be called ``ContractName_meta.json``. -It will contain Swarm references to the source code, so you have to upload -all source files and the metadata file. - -The metadata file has the following format. The example below is presented in a -human-readable way. Properly formatted metadata should use quotes correctly, -reduce whitespace to a minimum and sort the keys of all objects to arrive at a -unique formatting. -Comments are of course also not permitted and used here only for explanatory purposes. - -.. code-block:: none - - { - // Required: The version of the metadata format - version: "1", - // Required: Source code language, basically selects a "sub-version" - // of the specification - language: "Solidity", - // Required: Details about the compiler, contents are specific - // to the language. - compiler: { - // Required for Solidity: Version of the compiler - version: "0.4.6+commit.2dabbdf0.Emscripten.clang", - // Optional: Hash of the compiler binary which produced this output - keccak256: "0x123..." - }, - // Required: Compilation source files/source units, keys are file names - sources: - { - "myFile.sol": { - // Required: keccak256 hash of the source file - "keccak256": "0x123...", - // Required (unless "content" is used, see below): Sorted URL(s) - // to the source file, protocol is more or less arbitrary, but a - // Swarm URL is recommended - "urls": [ "bzzr://56ab..." ] - }, - "mortal": { - // Required: keccak256 hash of the source file - "keccak256": "0x234...", - // Required (unless "url" is used): literal contents of the source file - "content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }" - } - }, - // Required: Compiler settings - settings: - { - // Required for Solidity: Sorted list of remappings - remappings: [ ":g/dir" ], - // Optional: Optimizer settings (enabled defaults to false) - optimizer: { - enabled: true, - runs: 500 - }, - // Required for Solidity: File and name of the contract or library this - // metadata is created for. - compilationTarget: { - "myFile.sol": "MyContract" - }, - // Required for Solidity: Addresses for libraries used - libraries: { - "MyLib": "0x123123..." - } - }, - // Required: Generated information about the contract. - output: - { - // Required: ABI definition of the contract - abi: [ ... ], - // Required: NatSpec user documentation of the contract - userdoc: [ ... ], - // Required: NatSpec developer documentation of the contract - devdoc: [ ... ], - } - } - -.. note:: - Note the ABI definition above has no fixed order. It can change with compiler versions. - -.. note:: - Since the bytecode of the resulting contract contains the metadata hash, any change to - the metadata will result in a change of the bytecode. Furthermore, since the metadata - includes a hash of all the sources used, a single whitespace change in any of the source - codes will result in a different metadata, and subsequently a different bytecode. - -Encoding of the Metadata Hash in the Bytecode -============================================= - -Because we might support other ways to retrieve the metadata file in the future, -the mapping ``{"bzzr0": }`` is stored -[CBOR](https://tools.ietf.org/html/rfc7049)-encoded. Since the beginning of that -encoding is not easy to find, its length is added in a two-byte big-endian -encoding. The current version of the Solidity compiler thus adds the following -to the end of the deployed bytecode:: - - 0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29 - -So in order to retrieve the data, the end of the deployed bytecode can be checked -to match that pattern and use the Swarm hash to retrieve the file. - -Usage for Automatic Interface Generation and NatSpec -==================================================== - -The metadata is used in the following way: A component that wants to interact -with a contract (e.g. Mist) retrieves the code of the contract, from that -the Swarm hash of a file which is then retrieved. -That file is JSON-decoded into a structure like above. - -The component can then use the ABI to automatically generate a rudimentary -user interface for the contract. - -Furthermore, Mist can use the userdoc to display a confirmation message to the user -whenever they interact with the contract. - -Usage for Source Code Verification -================================== - -In order to verify the compilation, sources can be retrieved from Swarm -via the link in the metadata file. -The compiler of the correct version (which is checked to be part of the "official" compilers) -is invoked on that input with the specified settings. The resulting -bytecode is compared to the data of the creation transaction or CREATE opcode data. -This automatically verifies the metadata since its hash is part of the bytecode. -Excess data corresponds to the constructor input data, which should be decoded -according to the interface and presented to the user. - - *************** Tips and Tricks *************** * Use ``delete`` on arrays to delete all its elements. -* Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple SSTORE operations might be combined into a single (SSTORE costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check! +* Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple ``SSTORE`` operations might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check! * Make your state variables public - the compiler will create :ref:`getters ` for you automatically. * If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`. * If your contract has a function called ``send`` but you want to use the built-in send-function, use ``address(contractVariable).send(amount)``. @@ -469,7 +323,7 @@ Global Variables - ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component) - ``revert()``: abort execution and revert state changes - ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments -- ``sha3(...) returns (bytes32)``: an alias to `keccak256` +- ``sha3(...) returns (bytes32)``: an alias to ``keccak256`` - ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments - ``ripemd160(...) returns (bytes20)``: compute the RIPEMD-160 hash of the (tightly packed) arguments - ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error @@ -478,7 +332,7 @@ Global Variables - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address -- ``suicide(address recipieint)``: an alias to `selfdestruct`` +- ``suicide(address recipieint)``: an alias to ``selfdestruct`` - ``
.balance`` (``uint256``): balance of the :ref:`address` in Wei - ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure - ``
.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure @@ -519,7 +373,7 @@ Reserved Keywords These keywords are reserved in Solidity. They might become part of the syntax in the future: ``abstract``, ``after``, ``case``, ``catch``, ``default``, ``final``, ``in``, ``inline``, ``let``, ``match``, ``null``, -``of``, ``pure``, ``relocatable``, ``static``, ``switch``, ``try``, ``type``, ``typeof``, ``view``. +``of``, ``relocatable``, ``static``, ``switch``, ``try``, ``type``, ``typeof``. Language Grammar ================ diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index ca6b970c9896..139c8a423c7d 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -535,6 +535,9 @@ Safe Remote Purchase enum State { Created, Locked, Inactive } State public state; + // Ensure that `msg.value` is an even number. + // Division will truncate if it is an odd number. + // Check via multiplication that it wasn't an odd number. function Purchase() payable { seller = msg.sender; value = msg.value / 2; diff --git a/docs/types.rst b/docs/types.rst index fb88b0067741..5c291f35d8a9 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -54,7 +54,7 @@ Operators: * Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation) * Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder), ``**`` (exponentiation), ``<<`` (left shift), ``>>`` (right shift) -Division always truncates (it is just compiled to the DIV opcode of the EVM), but it does not truncate if both +Division always truncates (it is just compiled to the ``DIV`` opcode of the EVM), but it does not truncate if both operators are :ref:`literals` (or literal expressions). Division by zero and modulus with zero throws a runtime exception. @@ -70,6 +70,30 @@ sign extends. Shifting by a negative amount throws a runtime exception. are going to be rounded towards zero (truncated). In other programming languages the shift right of negative values works like division with rounding down (towards negative infinity). +.. index:: ! ufixed, ! fixed, ! fixed point number + +Fixed Point Numbers +------------------- + +.. warning:: + Fixed point numbers are not fully supported by Solidity yet. They can be declared, but + cannot be assigned to or from. + +``fixed`` / ``ufixed``: Signed and unsigned fixed point number of various sizes. Keywords ``ufixedMxN`` and ``fixedMxN``, where ``M`` represent the number of bits taken by +the type and ``N`` represent how many decimal points are available. ``M`` must be divisible by 8 and goes from 8 to 256 bits. ``N`` must be between 0 and 80, inclusive. +``ufixed`` and ``fixed`` are aliases for ``ufixed128x19`` and ``fixed128x19``, respectively. + +Operators: + +* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) +* Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder) + +.. note:: + The main difference between floating point (``float`` and ``double`` in many languages, more precisely IEEE 754 numbers) and fixed point numbers is + that the number of bits used for the integer and the fractional part (the part after the decimal dot) is flexible in the former, while it is strictly + defined in the latter. Generally, in floating point almost the entire space is used to represent the number, while only a small number of bits define + where the decimal point is. + .. index:: address, balance, send, call, callcode, delegatecall, transfer .. _address: @@ -127,6 +151,24 @@ the function ``call`` is provided which takes an arbitrary number of arguments o ``call`` returns a boolean indicating whether the invoked function terminated (``true``) or caused an EVM exception (``false``). It is not possible to access the actual data returned (for this we would need to know the encoding and size in advance). +It is possible to adjust the supplied gas with the ``.gas()`` modifier:: + + namReg.call.gas(1000000)("register", "MyName"); + +Similarly, the supplied Ether value can be controlled too:: + + nameReg.call.value(1 ether)("register", "MyName"); + +Lastly, these modifiers can be combined. Their order does not matter:: + + nameReg.call.gas(1000000).value(1 ether)("register", "MyName"); + +.. note:: + It is not yet possible to use the gas or value modifiers on overloaded functions. + + A workaround is to introduce a special case for gas and value and just re-check + whether they are present at the point of overload resolution. + In a similar way, the function ``delegatecall`` can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called ``callcode`` was available that did not provide access to the original ``msg.sender`` and ``msg.value`` values. All three functions ``call``, ``delegatecall`` and ``callcode`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity. @@ -169,6 +211,10 @@ Members: * ``.length`` yields the fixed length of the byte array (read-only). +.. note:: + It is possible to use an array of bytes as ``byte[]``, but it is wasting a lot of space, 31 bytes every element, + to be exact, when passing in calls. It is better to use ``bytes``. + Dynamically-sized byte array ---------------------------- @@ -181,15 +227,6 @@ As a rule of thumb, use ``bytes`` for arbitrary-length raw byte data and ``strin for arbitrary-length string (UTF-8) data. If you can limit the length to a certain number of bytes, always use one of ``bytes1`` to ``bytes32`` because they are much cheaper. -.. index:: ! ufixed, ! fixed, ! fixed point number - -Fixed Point Numbers -------------------- - -.. warning:: - Fixed point numbers are not fully supported by Solidity yet. They can be declared, but - cannot be assigned to or from. - .. index:: address, literal;address .. _address_literals: @@ -363,6 +400,17 @@ Note that public functions of the current contract can be used both as an internal and as an external function. To use ``f`` as an internal function, just use ``f``, if you want to use its external form, use ``this.f``. +Additionally, public (or external) functions also have a special member called ``selector``, +which returns the :ref:`ABI function selector `:: + + pragma solidity ^0.4.0; + + contract Selector { + function f() returns (bytes4) { + return this.f.selector; + } + } + Example that shows how to use internal function types:: pragma solidity ^0.4.5; @@ -467,10 +515,10 @@ context, there is always a default, but it can be overridden by appending either ``storage`` or ``memory`` to the type. The default for function parameters (including return parameters) is ``memory``, the default for local variables is ``storage`` and the location is forced to ``storage`` for state variables (obviously). -There is also a third data location, "calldata", which is a non-modifiable, +There is also a third data location, ``calldata``, which is a non-modifiable, non-persistent area where function arguments are stored. Function parameters -(not return parameters) of external functions are forced to "calldata" and -behave mostly like memory. +(not return parameters) of external functions are forced to ``calldata`` and +behave mostly like ``memory``. Data locations are important because they change how assignments behave: assignments between storage and memory and also to a state variable (even from other state variables) diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 647953060d7f..887535da72d9 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -72,6 +72,18 @@ Block and Transaction Properties ``msg.value`` can change for every **external** function call. This includes calls to library functions. +.. note:: + Do not rely on ``block.timestamp``, ``now`` and ``block.blockhash`` as a source of randomness, + unless you know what you are doing. + + Both the timestamp and the block hash can be influenced by miners to some degree. + Bad actors in the mining community can for example run a casino payout function on a chosen hash + and just retry a different hash if they did not receive any money. + + The current block timestamp must be strictly larger than the timestamp of the last block, + but the only guarantee is that it will be somewhere between the timestamps of two + consecutive blocks in the canonical chain. + .. note:: If you want to implement access restrictions in library functions using ``msg.sender``, you have to manually supply the value of diff --git a/libdevcore/ABI.h b/libdevcore/ABI.h deleted file mode 100644 index 8b9e5c98083b..000000000000 --- a/libdevcore/ABI.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - 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 . -*/ -/** @file ABI.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include -#include -#include - -namespace dev -{ -namespace eth -{ - -inline string32 toString32(std::string const& _s) -{ - string32 ret; - for (unsigned i = 0; i < 32; ++i) - ret[i] = i < _s.size() ? _s[i] : 0; - return ret; -} - -template struct ABISerialiser {}; -template struct ABISerialiser> { static bytes serialise(FixedHash const& _t) { static_assert(N <= 32, "Cannot serialise hash > 32 bytes."); static_assert(N > 0, "Cannot serialise zero-length hash."); return bytes(32 - N, 0) + _t.asBytes(); } }; -template <> struct ABISerialiser { static bytes serialise(u256 const& _t) { return h256(_t).asBytes(); } }; -template <> struct ABISerialiser { static bytes serialise(u160 const& _t) { return bytes(12, 0) + h160(_t).asBytes(); } }; -template <> struct ABISerialiser { static bytes serialise(string32 const& _t) { bytes ret; bytesConstRef((byte const*)_t.data(), 32).populate(bytesRef(&ret)); return ret; } }; -template <> struct ABISerialiser -{ - static bytes serialise(std::string const& _t) - { - bytes ret = h256(u256(32)).asBytes() + h256(u256(_t.size())).asBytes(); - ret.resize(ret.size() + (_t.size() + 31) / 32 * 32); - bytesConstRef(&_t).populate(bytesRef(&ret).cropped(64)); - return ret; - } -}; - -inline bytes abiInAux() { return {}; } -template bytes abiInAux(T const& _t, U const& ... _u) -{ - return ABISerialiser::serialise(_t) + abiInAux(_u ...); -} - -template bytes abiIn(std::string _id, T const& ... _t) -{ - return keccak256(_id).ref().cropped(0, 4).toBytes() + abiInAux(_t ...); -} - -template struct ABIDeserialiser {}; -template struct ABIDeserialiser> { static FixedHash deserialise(bytesConstRef& io_t) { static_assert(N <= 32, "Parameter sizes must be at most 32 bytes."); FixedHash ret; io_t.cropped(32 - N, N).populate(ret.ref()); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser { static u256 deserialise(bytesConstRef& io_t) { u256 ret = fromBigEndian(io_t.cropped(0, 32)); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser { static u160 deserialise(bytesConstRef& io_t) { u160 ret = fromBigEndian(io_t.cropped(12, 20)); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser { static string32 deserialise(bytesConstRef& io_t) { string32 ret; io_t.cropped(0, 32).populate(bytesRef((byte*)ret.data(), 32)); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser -{ - static std::string deserialise(bytesConstRef& io_t) - { - unsigned o = (uint16_t)u256(h256(io_t.cropped(0, 32))); - unsigned s = (uint16_t)u256(h256(io_t.cropped(o, 32))); - std::string ret; - ret.resize(s); - io_t.cropped(o + 32, s).populate(bytesRef((byte*)ret.data(), s)); - io_t = io_t.cropped(32); - return ret; - } -}; - -template T abiOut(bytes const& _data) -{ - bytesConstRef o(&_data); - return ABIDeserialiser::deserialise(o); -} - -template T abiOut(bytesConstRef& _data) -{ - return ABIDeserialiser::deserialise(_data); -} - -} -} diff --git a/libdevcore/Common.h b/libdevcore/Common.h index 9d6dd408b118..2543855d3fa5 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -37,13 +37,7 @@ #pragma warning(disable:3682) //call through incomplete class #endif -#include -#include -#include -#include -#include -#include -#include +#include #if defined(__GNUC__) #pragma warning(push) @@ -67,14 +61,13 @@ #pragma GCC diagnostic pop #endif // defined(__GNUC__) -#include "vector_ref.h" +#include +#include +#include +#include using byte = uint8_t; -// Quote a given token stream to turn it into a string. -#define DEV_QUOTED_HELPER(s) #s -#define DEV_QUOTED(s) DEV_QUOTED_HELPER(s) - namespace dev { @@ -85,32 +78,15 @@ using bytesConstRef = vector_ref; // Numeric types. using bigint = boost::multiprecision::number>; -using u64 = boost::multiprecision::number>; -using u128 = boost::multiprecision::number>; -using u256 = boost::multiprecision::number>; -using s256 = boost::multiprecision::number>; -using u160 = boost::multiprecision::number>; -using s160 = boost::multiprecision::number>; -using u512 = boost::multiprecision::number>; -using s512 = boost::multiprecision::number>; -using u256s = std::vector; -using u160s = std::vector; -using u256Set = std::set; -using u160Set = std::set; +using u256 = boost::multiprecision::number>; +using s256 = boost::multiprecision::number>; +using u160 = boost::multiprecision::number>; // Map types. using StringMap = std::map; -// Hash types. -using StringHashMap = std::unordered_map; - // String types. using strings = std::vector; -// Fixed-length string types. -using string32 = std::array; - -// Null/Invalid values for convenience. -static const bytes NullBytes; /// Interprets @a _u as a two's complement signed number and returns the resulting s256. inline s256 u2s(u256 _u) @@ -143,16 +119,6 @@ inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes) return os; } -template inline u256 exp10() -{ - return exp10() * u256(10); -} - -template <> inline u256 exp10<0>() -{ - return u256(1); -} - /// RAII utility class whose destructor calls a given function. class ScopeGuard { @@ -164,12 +130,4 @@ class ScopeGuard std::function m_f; }; -enum class WithExisting: int -{ - Trust = 0, - Verify, - Rescue, - Kill -}; - } diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 14caf4947f70..db11e61c643a 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -28,34 +28,6 @@ using namespace std; using namespace dev; -std::string dev::escaped(std::string const& _s, bool _all) -{ - static const map prettyEscapes{{'\r', 'r'}, {'\n', 'n'}, {'\t', 't'}, {'\v', 'v'}}; - std::string ret; - ret.reserve(_s.size() + 2); - ret.push_back('"'); - for (auto i: _s) - if (i == '"' && !_all) - ret += "\\\""; - else if (i == '\\' && !_all) - ret += "\\\\"; - else if (prettyEscapes.count(i) && !_all) - { - ret += '\\'; - ret += prettyEscapes.find(i)->second; - } - else if (i < ' ' || _all) - { - ret += "\\x"; - ret.push_back("0123456789abcdef"[(uint8_t)i / 16]); - ret.push_back("0123456789abcdef"[(uint8_t)i % 16]); - } - else - ret.push_back(i); - ret.push_back('"'); - return ret; -} - int dev::fromHex(char _i, WhenError _throw) { if (_i >= '0' && _i <= '9') diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 5df8986a87f6..765707f8095c 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -26,11 +26,10 @@ #include #include -#include -#include #include #include #include +#include namespace dev { diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index 52829455779c..5d47937b0b53 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -35,6 +35,9 @@ using namespace std; using namespace dev; +namespace +{ + template inline _T contentsGeneric(std::string const& _file) { @@ -56,6 +59,8 @@ inline _T contentsGeneric(std::string const& _file) return ret; } +} + string dev::contentsString(string const& _file) { return contentsGeneric(_file); diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h index 141e9ffd0755..cd6e1da1fb69 100644 --- a/libdevcore/FixedHash.h +++ b/libdevcore/FixedHash.h @@ -23,20 +23,18 @@ #pragma once +#include + +#include +#include + #include #include #include -#include -#include -#include "CommonData.h" namespace dev { -/// Compile-time calculation of Log2 of constant values. -template struct StaticLog2 { enum { result = 1 + StaticLog2::result }; }; -template <> struct StaticLog2<1> { enum { result = 0 }; }; - /// Fixed-size raw-byte array container type, with an API optimised for storing hashes. /// Transparently converts to/from the corresponding arithmetic type; this will /// assume the data contained in the hash is big-endian. @@ -50,9 +48,6 @@ class FixedHash /// The size of the container. enum { size = N }; - /// A dummy flag to avoid accidental construction from pointer. - enum ConstructFromPointerType { ConstructFromPointer }; - /// Method to convert from a string. enum ConstructFromStringType { FromHex, FromBinary }; @@ -77,9 +72,6 @@ class FixedHash /// Explicitly construct, copying from a byte array. explicit FixedHash(bytesConstRef _b, ConstructFromHashType _t = FailIfDifferent) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min(_b.size(), N)); else { m_data.fill(0); if (_t != FailIfDifferent) { auto c = std::min(_b.size(), N); for (unsigned i = 0; i < c; ++i) m_data[_t == AlignRight ? N - 1 - i : i] = _b[_t == AlignRight ? _b.size() - 1 - i : i]; } } } - /// Explicitly construct, copying from a bytes in memory with given pointer. - explicit FixedHash(byte const* _bs, ConstructFromPointerType) { memcpy(m_data.data(), _bs, N); } - /// Explicitly construct, copying from a string. explicit FixedHash(std::string const& _s, ConstructFromStringType _t = FromHex, ConstructFromHashType _ht = FailIfDifferent): FixedHash(_t == FromHex ? fromHex(_s, WhenError::Throw) : dev::asBytes(_s), _ht) {} @@ -92,37 +84,16 @@ class FixedHash // The obvious comparison operators. bool operator==(FixedHash const& _c) const { return m_data == _c.m_data; } bool operator!=(FixedHash const& _c) const { return m_data != _c.m_data; } + /// Required to sort objects of this type or use them as map keys. bool operator<(FixedHash const& _c) const { for (unsigned i = 0; i < N; ++i) if (m_data[i] < _c.m_data[i]) return true; else if (m_data[i] > _c.m_data[i]) return false; return false; } - bool operator>=(FixedHash const& _c) const { return !operator<(_c); } - bool operator<=(FixedHash const& _c) const { return operator==(_c) || operator<(_c); } - bool operator>(FixedHash const& _c) const { return !operator<=(_c); } - - // The obvious binary operators. - FixedHash& operator^=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] ^= _c.m_data[i]; return *this; } - FixedHash operator^(FixedHash const& _c) const { return FixedHash(*this) ^= _c; } - FixedHash& operator|=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] |= _c.m_data[i]; return *this; } - FixedHash operator|(FixedHash const& _c) const { return FixedHash(*this) |= _c; } - FixedHash& operator&=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] &= _c.m_data[i]; return *this; } - FixedHash operator&(FixedHash const& _c) const { return FixedHash(*this) &= _c; } - FixedHash operator~() const { FixedHash ret; for (unsigned i = 0; i < N; ++i) ret[i] = ~m_data[i]; return ret; } - - // Big-endian increment. - FixedHash& operator++() { for (unsigned i = size; i > 0 && !++m_data[--i]; ) {} return *this; } - /// @returns true if all one-bits in @a _c are set in this object. - bool contains(FixedHash const& _c) const { return (*this & _c) == _c; } + FixedHash operator~() const { FixedHash ret; for (unsigned i = 0; i < N; ++i) ret[i] = ~m_data[i]; return ret; } /// @returns a particular byte from the hash. byte& operator[](unsigned _i) { return m_data[_i]; } /// @returns a particular byte from the hash. byte operator[](unsigned _i) const { return m_data[_i]; } - /// @returns an abridged version of the hash as a user-readable hex string. - std::string abridged() const { return toHex(ref().cropped(0, 4)) + "\342\200\246"; } - - /// @returns a version of the hash as a user-readable hex string that leaves out the middle part. - std::string abridgedMiddle() const { return toHex(ref().cropped(0, 4)) + "\342\200\246" + toHex(ref().cropped(N - 4)); } - /// @returns the hash as a user-readable hex string. std::string hex() const { return toHex(ref()); } @@ -147,54 +118,17 @@ class FixedHash /// @returns a constant reference to the object's data as an STL array. std::array const& asArray() const { return m_data; } - struct hash - { - /// Make a hash of the object's data. - size_t operator()(FixedHash const& _value) const { return boost::hash_range(_value.m_data.cbegin(), _value.m_data.cend()); } - }; - - template inline FixedHash& shiftBloom(FixedHash const& _h) - { - return (*this |= _h.template bloomPart()); - } - - template inline bool containsBloom(FixedHash const& _h) - { - return contains(_h.template bloomPart()); - } - - template inline FixedHash bloomPart() const - { - unsigned const c_bloomBits = M * 8; - unsigned const c_mask = c_bloomBits - 1; - unsigned const c_bloomBytes = (StaticLog2::result + 7) / 8; - - static_assert((M & (M - 1)) == 0, "M must be power-of-two"); - static_assert(P * c_bloomBytes <= N, "out of range"); - - FixedHash ret; - byte const* p = data(); - for (unsigned i = 0; i < P; ++i) - { - unsigned index = 0; - for (unsigned j = 0; j < c_bloomBytes; ++j, ++p) - index = (index << 8) | *p; - index &= c_mask; - ret[M - 1 - index / 8] |= (1 << (index % 8)); - } - return ret; - } - /// Returns the index of the first bit set to one, or size() * 8 if no bits are set. inline unsigned firstBitSet() const { unsigned ret = 0; for (auto d: m_data) if (d) + { for (;; ++ret, d <<= 1) if (d & 0x80) return ret; - else {} + } else ret += 8; return ret; @@ -206,21 +140,6 @@ class FixedHash std::array m_data; ///< The binary data. }; -/// Fast equality operator for h256. -template<> inline bool FixedHash<32>::operator==(FixedHash<32> const& _other) const -{ - const uint64_t* hash1 = (const uint64_t*)data(); - const uint64_t* hash2 = (const uint64_t*)_other.data(); - return (hash1[0] == hash2[0]) && (hash1[1] == hash2[1]) && (hash1[2] == hash2[2]) && (hash1[3] == hash2[3]); -} - -/// Fast std::hash compatible hash function object for h256. -template<> inline size_t FixedHash<32>::hash::operator()(FixedHash<32> const& value) const -{ - uint64_t const* data = reinterpret_cast(value.data()); - return boost::hash_range(data, data + 4); -} - /// Stream I/O for the FixedHash class. template inline std::ostream& operator<<(std::ostream& _out, FixedHash const& _h) @@ -234,56 +153,7 @@ inline std::ostream& operator<<(std::ostream& _out, FixedHash const& _h) } // Common types of FixedHash. -using h2048 = FixedHash<256>; -using h1024 = FixedHash<128>; -using h520 = FixedHash<65>; -using h512 = FixedHash<64>; using h256 = FixedHash<32>; using h160 = FixedHash<20>; -using h128 = FixedHash<16>; -using h64 = FixedHash<8>; -using h512s = std::vector; -using h256s = std::vector; -using h160s = std::vector; -using h256Set = std::set; -using h160Set = std::set; -using h256Hash = std::unordered_set; -using h160Hash = std::unordered_set; - -/// Convert the given value into h160 (160-bit unsigned integer) using the right 20 bytes. -inline h160 right160(h256 const& _t) -{ - h160 ret; - memcpy(ret.data(), _t.data() + 12, 20); - return ret; -} - -/// Convert the given value into h160 (160-bit unsigned integer) using the left 20 bytes. -inline h160 left160(h256 const& _t) -{ - h160 ret; - memcpy(&ret[0], _t.data(), 20); - return ret; -} - -inline std::string toString(h256s const& _bs) -{ - std::ostringstream out; - out << "[ "; - for (auto i: _bs) - out << i.abridged() << ", "; - out << "]"; - return out.str(); -} } - -namespace std -{ - /// Forward std::hash to dev::FixedHash::hash. - template<> struct hash: dev::h64::hash {}; - template<> struct hash: dev::h128::hash {}; - template<> struct hash: dev::h160::hash {}; - template<> struct hash: dev::h256::hash {}; - template<> struct hash: dev::h512::hash {}; -} diff --git a/libdevcore/SHA3.cpp b/libdevcore/SHA3.cpp index 4d82ec85b311..b0e40ccb8f53 100644 --- a/libdevcore/SHA3.cpp +++ b/libdevcore/SHA3.cpp @@ -97,10 +97,9 @@ static const uint64_t RC[24] = \ static inline void keccakf(void* state) { uint64_t* a = (uint64_t*)state; uint64_t b[5] = {0}; - uint64_t t = 0; - uint8_t x, y; for (int i = 0; i < 24; i++) { + uint8_t x, y; // Theta FOR5(x, 1, b[x] = 0; @@ -110,7 +109,7 @@ static inline void keccakf(void* state) { FOR5(y, 5, a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); )) // Rho and pi - t = a[1]; + uint64_t t = a[1]; x = 0; REPEAT24(b[0] = a[pi[x]]; a[pi[x]] = rol(t, rho[x]); diff --git a/libdevcore/SHA3.h b/libdevcore/SHA3.h index 1a56106676c9..d1e2cc983b01 100644 --- a/libdevcore/SHA3.h +++ b/libdevcore/SHA3.h @@ -23,8 +23,9 @@ #pragma once +#include + #include -#include "FixedHash.h" namespace dev { @@ -47,10 +48,4 @@ inline h256 keccak256(std::string const& _input) { return keccak256(bytesConstRe /// Calculate Keccak-256 hash of the given input (presented as a FixedHash), returns a 256-bit hash. template inline h256 keccak256(FixedHash const& _input) { return keccak256(_input.ref()); } -/// Calculate Keccak-256 hash of the given input, possibly interpreting it as nibbles, and return the hash as a string filled with binary data. -inline std::string keccak256(std::string const& _input, bool _isNibbles) { return asString((_isNibbles ? keccak256(fromHex(_input)) : keccak256(bytesConstRef(&_input))).asBytes()); } - -/// Calculate Keccak-256 MAC -inline void keccak256mac(bytesConstRef _secret, bytesConstRef _plain, bytesRef _output) { keccak256(_secret.toBytes() + _plain.toBytes()).ref().populate(_output); } - } diff --git a/libdevcore/SwarmHash.cpp b/libdevcore/SwarmHash.cpp index 781886681871..1c718200efee 100644 --- a/libdevcore/SwarmHash.cpp +++ b/libdevcore/SwarmHash.cpp @@ -24,6 +24,8 @@ using namespace std; using namespace dev; +namespace +{ bytes toLittleEndian(size_t _size) { @@ -59,6 +61,8 @@ h256 swarmHashIntermediate(string const& _input, size_t _offset, size_t _length) return swarmHashSimple(ref, _length); } +} + h256 dev::swarmHash(string const& _input) { return swarmHashIntermediate(_input, 0, _input.size()); diff --git a/libdevcore/SwarmHash.h b/libdevcore/SwarmHash.h index a5da96f5330a..a06f7bda6b9c 100644 --- a/libdevcore/SwarmHash.h +++ b/libdevcore/SwarmHash.h @@ -26,7 +26,7 @@ namespace dev { -/// Compute the "swarm hash" of @a _data -h256 swarmHash(std::string const& _data); +/// Compute the "swarm hash" of @a _input +h256 swarmHash(std::string const& _input); } diff --git a/libdevcore/vector_ref.h b/libdevcore/vector_ref.h index 0f5431811633..b4dcff6515cd 100644 --- a/libdevcore/vector_ref.h +++ b/libdevcore/vector_ref.h @@ -23,6 +23,8 @@ class vector_ref using value_type = _T; using element_type = _T; using mutable_value_type = typename std::conditional::value, typename std::remove_const<_T>::type, _T>::type; + using string_type = typename std::conditional::value, std::string const, std::string>::type; + using vector_type = typename std::conditional::value, std::vector::type> const, std::vector<_T>>::type; static_assert(std::is_pod::value, "vector_ref can only be used with PODs due to its low-level treatment of data."); @@ -30,18 +32,13 @@ class vector_ref /// Creates a new vector_ref to point to @a _count elements starting at @a _data. vector_ref(_T* _data, size_t _count): m_data(_data), m_count(_count) {} /// Creates a new vector_ref pointing to the data part of a string (given as pointer). - vector_ref(typename std::conditional::value, std::string const*, std::string*>::type _data): m_data(reinterpret_cast<_T*>(_data->data())), m_count(_data->size() / sizeof(_T)) {} - /// Creates a new vector_ref pointing to the data part of a vector (given as pointer). - vector_ref(typename std::conditional::value, std::vector::type> const*, std::vector<_T>*>::type _data): m_data(_data->data()), m_count(_data->size()) {} + vector_ref(string_type* _data): m_data(reinterpret_cast<_T*>(_data->data())), m_count(_data->size() / sizeof(_T)) {} /// Creates a new vector_ref pointing to the data part of a string (given as reference). - vector_ref(typename std::conditional::value, std::string const&, std::string&>::type _data): m_data(reinterpret_cast<_T*>(_data.data())), m_count(_data.size() / sizeof(_T)) {} -#if DEV_LDB - vector_ref(ldb::Slice const& _s): m_data(reinterpret_cast<_T*>(_s.data())), m_count(_s.size() / sizeof(_T)) {} -#endif + vector_ref(string_type& _data): vector_ref(&_data) {} + /// Creates a new vector_ref pointing to the data part of a vector (given as pointer). + vector_ref(vector_type* _data): m_data(_data->data()), m_count(_data->size()) {} explicit operator bool() const { return m_data && m_count; } - bool contentsEqual(std::vector const& _c) const { if (!m_data || m_count == 0) return _c.empty(); else return _c.size() == m_count && !memcmp(_c.data(), m_data, m_count * sizeof(_T)); } - std::vector toVector() const { return std::vector(m_data, m_data + m_count); } std::vector toBytes() const { return std::vector(reinterpret_cast(m_data), reinterpret_cast(m_data) + m_count * sizeof(_T)); } std::string toString() const { return std::string((char const*)m_data, ((char const*)m_data) + m_count * sizeof(_T)); } @@ -50,25 +47,14 @@ class vector_ref _T* data() const { return m_data; } /// @returns the number of elements referenced (not necessarily number of bytes). - size_t count() const { return m_count; } - /// @returns the number of elements referenced (not necessarily number of bytes). size_t size() const { return m_count; } bool empty() const { return !m_count; } - /// @returns a new vector_ref pointing at the next chunk of @a size() elements. - vector_ref<_T> next() const { if (!m_data) return *this; else return vector_ref<_T>(m_data + m_count, m_count); } /// @returns a new vector_ref which is a shifted and shortened view of the original data. /// If this goes out of bounds in any way, returns an empty vector_ref. /// If @a _count is ~size_t(0), extends the view to the end of the data. vector_ref<_T> cropped(size_t _begin, size_t _count) const { if (m_data && _begin <= m_count && _count <= m_count && _begin + _count <= m_count) return vector_ref<_T>(m_data + _begin, _count == ~size_t(0) ? m_count - _begin : _count); else return vector_ref<_T>(); } /// @returns a new vector_ref which is a shifted view of the original data (not going beyond it). vector_ref<_T> cropped(size_t _begin) const { if (m_data && _begin <= m_count) return vector_ref<_T>(m_data + _begin, m_count - _begin); else return vector_ref<_T>(); } - void retarget(_T* _d, size_t _s) { m_data = _d; m_count = _s; } - void retarget(std::vector<_T> const& _t) { m_data = _t.data(); m_count = _t.size(); } - template bool overlapsWith(vector_ref _t) const { void const* f1 = data(); void const* t1 = data() + size(); void const* f2 = _t.data(); void const* t2 = _t.data() + _t.size(); return f1 < t2 && t1 > f2; } - /// Copies the contents of this vector_ref to the contents of @a _t, up to the max size of @a _t. - void copyTo(vector_ref::type> _t) const { if (overlapsWith(_t)) memmove(_t.data(), m_data, std::min(_t.size(), m_count) * sizeof(_T)); else memcpy(_t.data(), m_data, std::min(_t.size(), m_count) * sizeof(_T)); } - /// Copies the contents of this vector_ref to the contents of @a _t, and zeros further trailing elements in @a _t. - void populate(vector_ref::type> _t) const { copyTo(_t); memset(_t.data() + m_count, 0, std::max(_t.size(), m_count) - m_count); } _T* begin() { return m_data; } _T* end() { return m_data + m_count; } @@ -81,20 +67,11 @@ class vector_ref bool operator==(vector_ref<_T> const& _cmp) const { return m_data == _cmp.m_data && m_count == _cmp.m_count; } bool operator!=(vector_ref<_T> const& _cmp) const { return !operator==(_cmp); } -#if DEV_LDB - operator ldb::Slice() const { return ldb::Slice((char const*)m_data, m_count * sizeof(_T)); } -#endif - void reset() { m_data = nullptr; m_count = 0; } private: - _T* m_data; - size_t m_count; + _T* m_data = nullptr; + size_t m_count = 0; }; -template vector_ref<_T const> ref(_T const& _t) { return vector_ref<_T const>(&_t, 1); } -template vector_ref<_T> ref(_T& _t) { return vector_ref<_T>(&_t, 1); } -template vector_ref<_T const> ref(std::vector<_T> const& _t) { return vector_ref<_T const>(&_t); } -template vector_ref<_T> ref(std::vector<_T>& _t) { return vector_ref<_T>(&_t); } - } diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 0a3bf6b8b546..df691e7dd163 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,8 @@ void Assembly::append(Assembly const& _a) } m_deposit = newDeposit; m_usedTags += _a.m_usedTags; + // This does not transfer the names of named tags on purpose. The tags themselves are + // transferred, but their names are only available inside the assembly. for (auto const& i: _a.m_data) m_data.insert(i); for (auto const& i: _a.m_strings) @@ -180,7 +183,7 @@ class Functionalizer } -ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const +void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const { Functionalizer f(_out, _prefix, _sourceCodes); @@ -198,18 +201,23 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con for (size_t i = 0; i < m_subs.size(); ++i) { _out << endl << _prefix << "sub_" << i << ": assembly {\n"; - m_subs[i]->streamAsm(_out, _prefix + " ", _sourceCodes); + m_subs[i]->assemblyStream(_out, _prefix + " ", _sourceCodes); _out << _prefix << "}" << endl; } } if (m_auxiliaryData.size() > 0) _out << endl << _prefix << "auxdata: 0x" << toHex(m_auxiliaryData) << endl; +} - return _out; +string Assembly::assemblyString(StringMap const& _sourceCodes) const +{ + ostringstream tmp; + assemblyStream(tmp, "", _sourceCodes); + return tmp.str(); } -Json::Value Assembly::createJsonValue(string _name, int _begin, int _end, string _value, string _jumpType) const +Json::Value Assembly::createJsonValue(string _name, int _begin, int _end, string _value, string _jumpType) { Json::Value value; value["name"] = _name; @@ -222,14 +230,14 @@ Json::Value Assembly::createJsonValue(string _name, int _begin, int _end, string return value; } -string toStringInHex(u256 _value) +string Assembly::toStringInHex(u256 _value) { std::stringstream hexStr; hexStr << hex << _value; return hexStr.str(); } -Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes) const +Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const { Json::Value root; @@ -300,32 +308,19 @@ Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes { std::stringstream hexStr; hexStr << hex << i; - data[hexStr.str()] = m_subs[i]->stream(_out, "", _sourceCodes, true); + data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceCodes); } } if (m_auxiliaryData.size() > 0) root[".auxdata"] = toHex(m_auxiliaryData); - _out << root; - return root; } -Json::Value Assembly::stream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes, bool _inJsonFormat) const -{ - if (_inJsonFormat) - return streamAsmJson(_out, _sourceCodes); - else - { - streamAsm(_out, _prefix, _sourceCodes); - return Json::Value(); - } -} - AssemblyItem const& Assembly::append(AssemblyItem const& _i) { - assertThrow(m_deposit >= 0, AssemblyException, ""); + assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); m_deposit += _i.deposit(); m_items.push_back(_i); if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty()) @@ -333,6 +328,14 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i) return back(); } +AssemblyItem Assembly::namedTag(string const& _name) +{ + assertThrow(!_name.empty(), AssemblyException, "Empty named tag."); + if (!m_namedTags.count(_name)) + m_namedTags[_name] = size_t(newTag().data()); + return AssemblyItem(Tag, m_namedTags.at(_name)); +} + AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier) { h256 h(dev::keccak256(_identifier)); @@ -349,6 +352,7 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) { OptimiserSettings settings; settings.isCreation = _isCreation; + settings.runJumpdestRemover = true; settings.runPeephole = true; if (_enable) { @@ -357,18 +361,21 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) settings.runConstantOptimiser = true; } settings.expectedExecutionsPerDeployment = _runs; - optimiseInternal(settings); + optimise(settings); return *this; } -Assembly& Assembly::optimise(OptimiserSettings _settings) +Assembly& Assembly::optimise(OptimiserSettings const& _settings) { - optimiseInternal(_settings); + optimiseInternal(_settings, {}); return *this; } -map Assembly::optimiseInternal(OptimiserSettings _settings) +map Assembly::optimiseInternal( + OptimiserSettings const& _settings, + std::set const& _tagsReferencedFromOutside +) { // Run optimisation for sub-assemblies. for (size_t subId = 0; subId < m_subs.size(); ++subId) @@ -376,7 +383,10 @@ map Assembly::optimiseInternal(OptimiserSettings _settings) OptimiserSettings settings = _settings; // Disable creation mode for sub-assemblies. settings.isCreation = false; - map subTagReplacements = m_subs[subId]->optimiseInternal(settings); + map subTagReplacements = m_subs[subId]->optimiseInternal( + settings, + JumpdestRemover::referencedTags(m_items, subId) + ); // Apply the replacements (can be empty). BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); } @@ -387,6 +397,13 @@ map Assembly::optimiseInternal(OptimiserSettings _settings) { count = 0; + if (_settings.runJumpdestRemover) + { + JumpdestRemover jumpdestOpt(m_items); + if (jumpdestOpt.optimise(_tagsReferencedFromOutside)) + count++; + } + if (_settings.runPeephole) { PeepholeOptimiser peepOpt(m_items); @@ -473,8 +490,9 @@ LinkerObject const& Assembly::assemble() const for (auto const& sub: m_subs) { sub->assemble(); - if (!sub->m_tagPositionsInBytecode.empty()) - subTagSize = max(subTagSize, *max_element(sub->m_tagPositionsInBytecode.begin(), sub->m_tagPositionsInBytecode.end())); + for (size_t tagPos: sub->m_tagPositionsInBytecode) + if (tagPos != size_t(-1) && tagPos > subTagSize) + subTagSize = tagPos; } LinkerObject& ret = m_assembledObject; @@ -570,9 +588,10 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.resize(ret.bytecode.size() + 20); break; case Tag: - assertThrow(i.data() != 0, AssemblyException, ""); + assertThrow(i.data() != 0, AssemblyException, "Invalid tag position."); assertThrow(i.splitForeignPushTag().first == size_t(-1), AssemblyException, "Foreign tag."); assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); + assertThrow(m_tagPositionsInBytecode[size_t(i.data())] == size_t(-1), AssemblyException, "Duplicate tag position."); m_tagPositionsInBytecode[size_t(i.data())] = ret.bytecode.size(); ret.bytecode.push_back((byte)Instruction::JUMPDEST); break; diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 451b4ea0decc..885192e49808 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -47,6 +47,8 @@ class Assembly AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); } + /// Returns a tag identified by the given name. Creates it if it does not yet exist. + AssemblyItem namedTag(std::string const& _name); AssemblyItem newData(bytes const& _data) { h256 h(dev::keccak256(asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); } @@ -100,6 +102,7 @@ class Assembly struct OptimiserSettings { bool isCreation = false; + bool runJumpdestRemover = false; bool runPeephole = false; bool runDeduplicate = false; bool runCSE = false; @@ -110,7 +113,7 @@ class Assembly }; /// Execute optimisation passes as defined by @a _settings and return the optimised assembly. - Assembly& optimise(OptimiserSettings _settings); + Assembly& optimise(OptimiserSettings const& _settings); /// Modify (if @a _enable is set) and return the current assembly such that creation and /// execution gas usage is optimised. @a _isCreation should be true for the top-level assembly. @@ -119,28 +122,37 @@ class Assembly /// If @a _enable is not set, will perform some simple peephole optimizations. Assembly& optimise(bool _enable, bool _isCreation = true, size_t _runs = 200); - Json::Value stream( + /// Create a text representation of the assembly. + std::string assemblyString( + StringMap const& _sourceCodes = StringMap() + ) const; + void assemblyStream( std::ostream& _out, std::string const& _prefix = "", - const StringMap &_sourceCodes = StringMap(), - bool _inJsonFormat = false + StringMap const& _sourceCodes = StringMap() + ) const; + + /// Create a JSON representation of the assembly. + Json::Value assemblyJSON( + StringMap const& _sourceCodes = StringMap() ) const; protected: /// Does the same operations as @a optimise, but should only be applied to a sub and - /// returns the replaced tags. - std::map optimiseInternal(OptimiserSettings _settings); + /// returns the replaced tags. Also takes an argument containing the tags of this assembly + /// that are referenced in a super-assembly. + std::map optimiseInternal(OptimiserSettings const& _settings, std::set const& _tagsReferencedFromOutside); unsigned bytesRequired(unsigned subTagSize) const; private: - Json::Value streamAsmJson(std::ostream& _out, StringMap const& _sourceCodes) const; - std::ostream& streamAsm(std::ostream& _out, std::string const& _prefix, StringMap const& _sourceCodes) const; - Json::Value createJsonValue(std::string _name, int _begin, int _end, std::string _value = std::string(), std::string _jumpType = std::string()) const; + static Json::Value createJsonValue(std::string _name, int _begin, int _end, std::string _value = std::string(), std::string _jumpType = std::string()); + static std::string toStringInHex(u256 _value); protected: /// 0 is reserved for exception unsigned m_usedTags = 1; + std::map m_namedTags; AssemblyItems m_items; std::map m_data; /// Data that is appended to the very end of the contract. @@ -159,7 +171,7 @@ class Assembly inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a) { - _a.stream(_out); + _a.assemblyStream(_out); return _out; } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 419a8c0b1bb8..cfe91be09bbc 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -59,18 +59,18 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const case Tag: // 1 byte for the JUMPDEST return 1; case PushString: - return 33; + return 1 + 32; case Push: return 1 + max(1, dev::bytesRequired(data())); case PushSubSize: case PushProgramSize: - return 4; // worst case: a 16MB program + return 1 + 4; // worst case: a 16MB program case PushTag: case PushData: case PushSub: return 1 + _addressLength; case PushLibraryAddress: - return 21; + return 1 + 20; default: break; } @@ -249,8 +249,11 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " PushProgramSize"; break; case PushLibraryAddress: - _out << " PushLibraryAddress " << hex << h256(_item.data()).abridgedMiddle() << dec; + { + string hash(h256((_item.data())).hex()); + _out << " PushLibraryAddress " << hash.substr(0, 8) + "..." + hash.substr(hash.length() - 8); break; + } case UndefinedItem: _out << " ???"; break; diff --git a/libevmasm/BlockDeduplicator.cpp b/libevmasm/BlockDeduplicator.cpp index d21be07e7858..b7c695311aab 100644 --- a/libevmasm/BlockDeduplicator.cpp +++ b/libevmasm/BlockDeduplicator.cpp @@ -22,10 +22,13 @@ */ #include -#include + #include #include +#include +#include + using namespace std; using namespace dev; using namespace dev::eth; diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index 82982e25849b..c450b0b4c126 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -91,7 +91,7 @@ class ConstantOptimisationMethod } /// Replaces all constants i by the code given in @a _replacement[i]. - static void replaceConstants(AssemblyItems& _items, std::map const& _replacement); + static void replaceConstants(AssemblyItems& _items, std::map const& _replacements); Params m_params; u256 const& m_value; diff --git a/libevmasm/ExpressionClasses.h b/libevmasm/ExpressionClasses.h index 5d53b2921958..6b426e971b17 100644 --- a/libevmasm/ExpressionClasses.h +++ b/libevmasm/ExpressionClasses.h @@ -23,11 +23,13 @@ #pragma once +#include +#include + #include #include #include -#include -#include +#include namespace dev { diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 6a7c80e005eb..dad952bc3920 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -189,9 +189,9 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ return gas; } -GasMeter::GasConsumption GasMeter::wordGas(u256 const& _multiplier, ExpressionClasses::Id _position) +GasMeter::GasConsumption GasMeter::wordGas(u256 const& _multiplier, ExpressionClasses::Id _value) { - u256 const* value = m_state->expressionClasses().knownConstant(_position); + u256 const* value = m_state->expressionClasses().knownConstant(_value); if (!value) return GasConsumption::infinite(); return GasConsumption(_multiplier * ((*value + 31) / 32)); diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 89a25fb7041b..afbef71d0a32 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -87,13 +87,6 @@ enum class Instruction: uint8_t DIFFICULTY, ///< get the block's difficulty GASLIMIT, ///< get the block's gas limit - JUMPTO = 0x4a, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp - JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp - JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp - JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp - JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp - RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp - POP = 0x50, ///< remove item from stack MLOAD, ///< load word from memory MSTORE, ///< save word to memory @@ -106,8 +99,6 @@ enum class Instruction: uint8_t MSIZE, ///< get the size of active memory GAS, ///< get the amount of available gas JUMPDEST, ///< set a potential jump destination - BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp - BEGINDATA, ///< begine the data section -- not part of Instructions.cpp PUSH1 = 0x60, ///< place 1 byte item on stack PUSH2, ///< place 2 byte item on stack @@ -182,6 +173,17 @@ enum class Instruction: uint8_t LOG3, ///< Makes a log entry; 3 topics. LOG4, ///< Makes a log entry; 4 topics. + JUMPTO = 0xb0, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp + JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp + JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp + JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp + JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp + BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp + BEGINDATA, ///< begin the data section -- not part of Instructions.cpp + RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp + PUTLOCAL, ///< pop top of stack to local variable -- not part of Instructions.cpp + GETLOCAL, ///< push local variable to top of stack -- not part of Instructions.cpp + CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only diff --git a/libevmasm/JumpdestRemover.cpp b/libevmasm/JumpdestRemover.cpp new file mode 100644 index 000000000000..b60167982f1e --- /dev/null +++ b/libevmasm/JumpdestRemover.cpp @@ -0,0 +1,68 @@ +/* + 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 . +*/ +/** + * @author Alex Beregszaszi + * Removes unused JUMPDESTs. + */ + +#include "JumpdestRemover.h" + +#include + +#include + +using namespace std; +using namespace dev::eth; +using namespace dev; + + +bool JumpdestRemover::optimise(set const& _tagsReferencedFromOutside) +{ + set references{referencedTags(m_items, -1)}; + references.insert(_tagsReferencedFromOutside.begin(), _tagsReferencedFromOutside.end()); + + size_t initialSize = m_items.size(); + /// Remove tags which are never referenced. + auto pend = remove_if( + m_items.begin(), + m_items.end(), + [&](AssemblyItem const& _item) + { + if (_item.type() != Tag) + return false; + auto asmIdAndTag = _item.splitForeignPushTag(); + solAssert(asmIdAndTag.first == size_t(-1), "Sub-assembly tag used as label."); + size_t tag = asmIdAndTag.second; + return !references.count(tag); + } + ); + m_items.erase(pend, m_items.end()); + return m_items.size() != initialSize; +} + +set JumpdestRemover::referencedTags(AssemblyItems const& _items, size_t _subId) +{ + set ret; + for (auto const& item: _items) + if (item.type() == PushTag) + { + auto subAndTag = item.splitForeignPushTag(); + if (subAndTag.first == _subId) + ret.insert(subAndTag.second); + } + return ret; +} diff --git a/libevmasm/JumpdestRemover.h b/libevmasm/JumpdestRemover.h new file mode 100644 index 000000000000..2dad09272753 --- /dev/null +++ b/libevmasm/JumpdestRemover.h @@ -0,0 +1,50 @@ +/* + 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 . +*/ +/** + * @author Alex Beregszaszi + * Removes unused JUMPDESTs. + */ +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace eth +{ +class AssemblyItem; +using AssemblyItems = std::vector; + +class JumpdestRemover +{ +public: + explicit JumpdestRemover(AssemblyItems& _items): m_items(_items) {} + + bool optimise(std::set const& _tagsReferencedFromOutside); + + /// @returns a set of all tags from the given sub-assembly that are referenced + /// from the given list of items. + static std::set referencedTags(AssemblyItems const& _items, size_t _subId); + +private: + AssemblyItems& m_items; +}; + +} +} diff --git a/libevmasm/LinkerObject.cpp b/libevmasm/LinkerObject.cpp index 06607089e4fc..8b7d9e06a192 100644 --- a/libevmasm/LinkerObject.cpp +++ b/libevmasm/LinkerObject.cpp @@ -38,7 +38,7 @@ void LinkerObject::link(map const& _libraryAddresses) std::map remainingRefs; for (auto const& linkRef: linkReferences) if (h160 const* address = matchLibrary(linkRef.second, _libraryAddresses)) - address->ref().copyTo(ref(bytecode).cropped(linkRef.first, 20)); + copy(address->data(), address->data() + 20, bytecode.begin() + linkRef.first); else remainingRefs.insert(linkRef); linkReferences.swap(remainingRefs); diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index e94a8ba4c5c5..31fdd3176dcf 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -30,6 +30,9 @@ using namespace dev; // TODO: Extend this to use the tools from ExpressionClasses.cpp +namespace +{ + struct OptimiserState { AssemblyItems const& items; @@ -246,6 +249,8 @@ void applyMethods(OptimiserState& _state, Method, OtherMethods... _other) applyMethods(_state, _other...); } +} + bool PeepholeOptimiser::optimise() { OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)}; diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index f63f0c616d44..ceb3fbdd0cce 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -188,3 +188,56 @@ bool SemanticInformation::invalidatesStorage(Instruction _instruction) return false; } } + +bool SemanticInformation::invalidInPureFunctions(Instruction _instruction) +{ + switch (_instruction) + { + case Instruction::ADDRESS: + case Instruction::BALANCE: + case Instruction::ORIGIN: + case Instruction::CALLER: + case Instruction::CALLVALUE: + case Instruction::GASPRICE: + case Instruction::EXTCODESIZE: + case Instruction::EXTCODECOPY: + case Instruction::BLOCKHASH: + case Instruction::COINBASE: + case Instruction::TIMESTAMP: + case Instruction::NUMBER: + case Instruction::DIFFICULTY: + case Instruction::GASLIMIT: + case Instruction::STATICCALL: + case Instruction::SLOAD: + return true; + default: + break; + } + return invalidInViewFunctions(_instruction); +} + +bool SemanticInformation::invalidInViewFunctions(Instruction _instruction) +{ + switch (_instruction) + { + case Instruction::SSTORE: + case Instruction::JUMP: + case Instruction::JUMPI: + case Instruction::GAS: + case Instruction::LOG0: + case Instruction::LOG1: + case Instruction::LOG2: + case Instruction::LOG3: + case Instruction::LOG4: + case Instruction::CREATE: + case Instruction::CALL: + case Instruction::CALLCODE: + case Instruction::DELEGATECALL: + case Instruction::CREATE2: + case Instruction::SELFDESTRUCT: + return true; + default: + break; + } + return false; +} diff --git a/libevmasm/SemanticInformation.h b/libevmasm/SemanticInformation.h index 5b02061fc7e0..e5ea7c180459 100644 --- a/libevmasm/SemanticInformation.h +++ b/libevmasm/SemanticInformation.h @@ -53,6 +53,8 @@ struct SemanticInformation static bool invalidatesMemory(solidity::Instruction _instruction); /// @returns true if the given instruction modifies storage (even indirectly). static bool invalidatesStorage(solidity::Instruction _instruction); + static bool invalidInPureFunctions(solidity::Instruction _instruction); + static bool invalidInViewFunctions(solidity::Instruction _instruction); }; } diff --git a/libjulia/backends/evm/AbstractAssembly.h b/libjulia/backends/evm/AbstractAssembly.h index cfc9b8a55154..8e90a912bd32 100644 --- a/libjulia/backends/evm/AbstractAssembly.h +++ b/libjulia/backends/evm/AbstractAssembly.h @@ -66,6 +66,8 @@ class AbstractAssembly virtual void appendLabelReference(LabelID _labelId) = 0; /// Generate a new unique label. virtual LabelID newLabelId() = 0; + /// Returns a label identified by the given name. Creates it if it does not yet exist. + virtual LabelID namedLabel(std::string const& _name) = 0; /// Append a reference to a to-be-linked symobl. /// Currently, we assume that the value is always a 20 byte number. virtual void appendLinkerSymbol(std::string const& _name) = 0; diff --git a/libjulia/backends/evm/EVMAssembly.cpp b/libjulia/backends/evm/EVMAssembly.cpp index 173d5e934b14..1d499b200be7 100644 --- a/libjulia/backends/evm/EVMAssembly.cpp +++ b/libjulia/backends/evm/EVMAssembly.cpp @@ -77,6 +77,14 @@ EVMAssembly::LabelID EVMAssembly::newLabelId() return m_nextLabelId++; } +AbstractAssembly::LabelID EVMAssembly::namedLabel(string const& _name) +{ + solAssert(!_name.empty(), ""); + if (!m_namedLabels.count(_name)) + m_namedLabels[_name] = newLabelId(); + return m_namedLabels[_name]; +} + void EVMAssembly::appendLinkerSymbol(string const&) { solAssert(false, "Linker symbols not yet implemented."); diff --git a/libjulia/backends/evm/EVMAssembly.h b/libjulia/backends/evm/EVMAssembly.h index 695858226c07..593cee6a2495 100644 --- a/libjulia/backends/evm/EVMAssembly.h +++ b/libjulia/backends/evm/EVMAssembly.h @@ -52,6 +52,8 @@ class EVMAssembly: public AbstractAssembly virtual void appendLabelReference(LabelID _labelId) override; /// Generate a new unique label. virtual LabelID newLabelId() override; + /// Returns a label identified by the given name. Creates it if it does not yet exist. + virtual LabelID namedLabel(std::string const& _name) override; /// Append a reference to a to-be-linked symobl. /// Currently, we assume that the value is always a 20 byte number. virtual void appendLinkerSymbol(std::string const& _name) override; @@ -85,6 +87,7 @@ class EVMAssembly: public AbstractAssembly LabelID m_nextLabelId = 0; int m_stackHeight = 0; bytes m_bytecode; + std::map m_namedLabels; std::map m_labelPositions; std::map m_labelReferences; std::vector m_assemblySizePositions; diff --git a/libjulia/backends/evm/EVMCodeTransform.cpp b/libjulia/backends/evm/EVMCodeTransform.cpp index 704aa3c100c2..66f593e80501 100644 --- a/libjulia/backends/evm/EVMCodeTransform.cpp +++ b/libjulia/backends/evm/EVMCodeTransform.cpp @@ -60,9 +60,12 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl) void CodeTransform::operator()(Assignment const& _assignment) { - visitExpression(*_assignment.value); + int height = m_assembly.stackHeight(); + boost::apply_visitor(*this, *_assignment.value); + expectDeposit(_assignment.variableNames.size(), height); + m_assembly.setSourceLocation(_assignment.location); - generateAssignment(_assignment.variableName); + generateMultiAssignment(_assignment.variableNames); checkStackHeight(&_assignment); } @@ -108,10 +111,10 @@ void CodeTransform::operator()(FunctionCall const& _call) visitExpression(arg); m_assembly.setSourceLocation(_call.location); if (m_evm15) - m_assembly.appendJumpsub(functionEntryID(*function), function->arguments.size(), function->returns.size()); + m_assembly.appendJumpsub(functionEntryID(_call.functionName.name, *function), function->arguments.size(), function->returns.size()); else { - m_assembly.appendJumpTo(functionEntryID(*function), function->returns.size() - function->arguments.size() - 1); + m_assembly.appendJumpTo(functionEntryID(_call.functionName.name, *function), function->returns.size() - function->arguments.size() - 1); m_assembly.appendLabel(returnLabel); m_stackAdjustment--; } @@ -286,12 +289,12 @@ void CodeTransform::operator()(FunctionDefinition const& _function) if (m_evm15) { m_assembly.appendJumpTo(afterFunction, -stackHeightBefore); - m_assembly.appendBeginsub(functionEntryID(function), _function.arguments.size()); + m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.arguments.size()); } else { m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height); - m_assembly.appendLabel(functionEntryID(function)); + m_assembly.appendLabel(functionEntryID(_function.name, function)); } m_stackAdjustment += localStackAdjustment; @@ -303,8 +306,16 @@ void CodeTransform::operator()(FunctionDefinition const& _function) m_assembly.appendConstant(u256(0)); } - CodeTransform(m_assembly, m_info, m_julia, m_evm15, m_identifierAccess, localStackAdjustment, m_context) - (_function.body); + CodeTransform( + m_assembly, + m_info, + m_julia, + m_evm15, + m_identifierAccess, + m_useNamedLabelsForFunctions, + localStackAdjustment, + m_context + )(_function.body); { // The stack layout here is: @@ -421,10 +432,16 @@ AbstractAssembly::LabelID CodeTransform::labelID(Scope::Label const& _label) return m_context->labelIDs[&_label]; } -AbstractAssembly::LabelID CodeTransform::functionEntryID(Scope::Function const& _function) +AbstractAssembly::LabelID CodeTransform::functionEntryID(string const& _name, Scope::Function const& _function) { if (!m_context->functionEntryIDs.count(&_function)) - m_context->functionEntryIDs[&_function] = m_assembly.newLabelId(); + { + AbstractAssembly::LabelID id = + m_useNamedLabelsForFunctions ? + m_assembly.namedLabel(_name) : + m_assembly.newLabelId(); + m_context->functionEntryIDs[&_function] = id; + } return m_context->functionEntryIDs[&_function]; } @@ -455,6 +472,13 @@ void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight checkStackHeight(&_block); } +void CodeTransform::generateMultiAssignment(vector const& _variableNames) +{ + solAssert(m_scope, ""); + for (auto const& variableName: _variableNames | boost::adaptors::reversed) + generateAssignment(variableName); +} + void CodeTransform::generateAssignment(Identifier const& _variableName) { solAssert(m_scope, ""); diff --git a/libjulia/backends/evm/EVMCodeTransform.h b/libjulia/backends/evm/EVMCodeTransform.h index cd452c5b4f4e..951c8a50eb10 100644 --- a/libjulia/backends/evm/EVMCodeTransform.h +++ b/libjulia/backends/evm/EVMCodeTransform.h @@ -50,13 +50,15 @@ class CodeTransform: public boost::static_visitor<> solidity::assembly::AsmAnalysisInfo& _analysisInfo, bool _julia = false, bool _evm15 = false, - ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess() + ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess(), + bool _useNamedLabelsForFunctions = false ): CodeTransform( _assembly, _analysisInfo, _julia, _evm15, _identifierAccess, + _useNamedLabelsForFunctions, _assembly.stackHeight(), std::make_shared() ) @@ -78,6 +80,7 @@ class CodeTransform: public boost::static_visitor<> bool _julia, bool _evm15, ExternalIdentifierAccess const& _identifierAccess, + bool _useNamedLabelsForFunctions, int _stackAdjustment, std::shared_ptr _context ): @@ -85,6 +88,7 @@ class CodeTransform: public boost::static_visitor<> m_info(_analysisInfo), m_julia(_julia), m_evm15(_evm15), + m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions), m_identifierAccess(_identifierAccess), m_stackAdjustment(_stackAdjustment), m_context(_context) @@ -110,7 +114,7 @@ class CodeTransform: public boost::static_visitor<> /// @returns the label ID corresponding to the given label, allocating a new one if /// necessary. AbstractAssembly::LabelID labelID(solidity::assembly::Scope::Label const& _label); - AbstractAssembly::LabelID functionEntryID(solidity::assembly::Scope::Function const& _function); + AbstractAssembly::LabelID functionEntryID(std::string const& _name, solidity::assembly::Scope::Function const& _function); /// Generates code for an expression that is supposed to return a single value. void visitExpression(solidity::assembly::Statement const& _expression); @@ -120,6 +124,7 @@ class CodeTransform: public boost::static_visitor<> /// to @a _blackStartStackHeight. void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight); + void generateMultiAssignment(std::vector const& _variableNames); void generateAssignment(solidity::assembly::Identifier const& _variableName); /// Determines the stack height difference to the given variables. Throws @@ -136,6 +141,7 @@ class CodeTransform: public boost::static_visitor<> solidity::assembly::Scope* m_scope = nullptr; bool m_julia = false; bool m_evm15 = false; + bool m_useNamedLabelsForFunctions = false; ExternalIdentifierAccess m_identifierAccess; /// Adjustment between the stack height as determined during the analysis phase /// and the stack height in the assembly. This is caused by an initial stack being present diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp index 4ec11ca93174..b69675aabe60 100644 --- a/liblll/Compiler.cpp +++ b/liblll/Compiler.cpp @@ -72,14 +72,13 @@ std::string dev::eth::compileLLLToAsm(std::string const& _src, bool _opt, std::v { CompilerState cs; cs.populateStandard(); - stringstream ret; auto assembly = CodeFragment::compile(_src, cs).assembly(cs); if (_opt) assembly = assembly.optimise(true); - assembly.stream(ret); + string ret = assembly.assemblyString(); for (auto i: cs.treesToKill) killBigints(i); - return ret.str(); + return ret; } catch (Exception const& _e) { diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index 9a846b3188ca..b3fb52583ccb 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -38,30 +38,30 @@ bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit) return !m_errorOccured; } -bool DocStringAnalyser::visit(ContractDefinition const& _node) +bool DocStringAnalyser::visit(ContractDefinition const& _contract) { static const set validTags = set{"author", "title", "dev", "notice"}; - parseDocStrings(_node, _node.annotation(), validTags, "contracts"); + parseDocStrings(_contract, _contract.annotation(), validTags, "contracts"); return true; } -bool DocStringAnalyser::visit(FunctionDefinition const& _node) +bool DocStringAnalyser::visit(FunctionDefinition const& _function) { - handleCallable(_node, _node, _node.annotation()); + handleCallable(_function, _function, _function.annotation()); return true; } -bool DocStringAnalyser::visit(ModifierDefinition const& _node) +bool DocStringAnalyser::visit(ModifierDefinition const& _modifier) { - handleCallable(_node, _node, _node.annotation()); + handleCallable(_modifier, _modifier, _modifier.annotation()); return true; } -bool DocStringAnalyser::visit(EventDefinition const& _node) +bool DocStringAnalyser::visit(EventDefinition const& _event) { - handleCallable(_node, _node, _node.annotation()); + handleCallable(_event, _event, _event.annotation()); return true; } @@ -72,7 +72,7 @@ void DocStringAnalyser::handleCallable( DocumentedAnnotation& _annotation ) { - static const set validTags = set{"author", "dev", "notice", "return", "param", "why3"}; + static const set validTags = set{"author", "dev", "notice", "return", "param"}; parseDocStrings(_node, _annotation, validTags, "functions"); set validParams; diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index a54b8c8d40be..62dbd394373f 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -43,13 +43,13 @@ m_magicVariables(vector>{make_shared< make_shared("selfdestruct", make_shared(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)), make_shared("addmod", - make_shared(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod)), + make_shared(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)), make_shared("mulmod", - make_shared(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod)), + make_shared(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), make_shared("sha3", - make_shared(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)), + make_shared(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)), make_shared("keccak256", - make_shared(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)), + make_shared(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)), make_shared("log0", make_shared(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), make_shared("log1", @@ -61,17 +61,17 @@ m_magicVariables(vector>{make_shared< make_shared("log4", make_shared(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)), make_shared("sha256", - make_shared(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true)), + make_shared(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true, StateMutability::Pure)), make_shared("ecrecover", - make_shared(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover)), + make_shared(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), make_shared("ripemd160", - make_shared(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true)), + make_shared(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true, StateMutability::Pure)), make_shared("assert", - make_shared(strings{"bool"}, strings{}, FunctionType::Kind::Assert)), + make_shared(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), make_shared("require", - make_shared(strings{"bool"}, strings{}, FunctionType::Kind::Require)), + make_shared(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), make_shared("revert", - make_shared(strings(), strings(), FunctionType::Kind::Revert))}) + make_shared(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure))}) { } diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 59bd3b1f4c27..d83697cdab6f 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -148,7 +148,7 @@ class DeclarationRegistrationHelper: private ASTVisitor private: bool visit(SourceUnit& _sourceUnit) override; void endVisit(SourceUnit& _sourceUnit) override; - bool visit(ImportDirective& _declaration) override; + bool visit(ImportDirective& _import) override; bool visit(ContractDefinition& _contract) override; void endVisit(ContractDefinition& _contract) override; bool visit(StructDefinition& _struct) override; diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h index dbdf50e0690b..91d2b0b934c1 100644 --- a/libsolidity/analysis/PostTypeChecker.h +++ b/libsolidity/analysis/PostTypeChecker.h @@ -50,8 +50,8 @@ class PostTypeChecker: private ASTConstVisitor virtual bool visit(ContractDefinition const& _contract) override; virtual void endVisit(ContractDefinition const& _contract) override; - virtual bool visit(VariableDeclaration const& _declaration) override; - virtual void endVisit(VariableDeclaration const& _declaration) override; + virtual bool visit(VariableDeclaration const& _variable) override; + virtual void endVisit(VariableDeclaration const& _variable) override; virtual bool visit(Identifier const& _identifier) override; diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 2f1304146a65..ffa538b69e1f 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -57,8 +57,6 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function) solAssert(m_localVarUseCount.empty(), ""); m_nonPayablePublic = _function.isPublic() && !_function.isPayable(); m_constructor = _function.isConstructor(); - if (_function.stateMutability() == StateMutability::Pure) - m_errorReporter.warning(_function.location(), "Function is marked pure. Be careful, pureness is not enforced yet."); return true; } @@ -69,7 +67,16 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&) m_constructor = false; for (auto const& var: m_localVarUseCount) if (var.second == 0) - m_errorReporter.warning(var.first->location(), "Unused local variable"); + { + if (var.first->isCallableParameter()) + m_errorReporter.warning( + var.first->location(), + "Unused function parameter. Remove or comment out the variable name to silence this warning." + ); + else + m_errorReporter.warning(var.first->location(), "Unused local variable."); + } + m_localVarUseCount.clear(); } diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index a3080b428a89..24ed119fa75f 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -37,8 +37,8 @@ namespace solidity /** * The module that performs static analysis on the AST. * In this context, static analysis is anything that can produce warnings which can help - * programmers write cleaner code. For every warning generated eher, it has to be possible to write - * equivalent code that does generate the warning. + * programmers write cleaner code. For every warning generated here, it has to be possible to write + * equivalent code that does not generate the warning. */ class StaticAnalyzer: private ASTConstVisitor { diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index d2571cd35600..187eb26fabb3 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -138,7 +138,7 @@ bool SyntaxChecker::visit(WhileStatement const&) return true; } -void SyntaxChecker::endVisit(WhileStatement const& ) +void SyntaxChecker::endVisit(WhileStatement const&) { m_inLoopDepth--; } @@ -193,6 +193,18 @@ bool SyntaxChecker::visit(PlaceholderStatement const&) return true; } +bool SyntaxChecker::visit(FunctionDefinition const& _function) +{ + if (_function.noVisibilitySpecified()) + m_errorReporter.warning( + _function.location(), + "No visibility specified. Defaulting to \"" + + Declaration::visibilityToString(_function.visibility()) + + "\"." + ); + return true; +} + bool SyntaxChecker::visit(FunctionTypeName const& _node) { for (auto const& decl: _node.parameterTypeList()->parameters()) diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index fa34bab312eb..7fffbec00c7f 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -66,6 +66,7 @@ class SyntaxChecker: private ASTConstVisitor virtual bool visit(PlaceholderStatement const& _placeholderStatement) override; + virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(FunctionTypeName const& _node) override; ErrorReporter& m_errorReporter; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 99f3c64cbe75..4b2ec8d64233 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -120,6 +120,11 @@ bool TypeChecker::visit(ContractDefinition const& _contract) m_errorReporter.typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters."); if (!fallbackFunction->returnParameters().empty()) m_errorReporter.typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values."); + if ( + _contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050) && + fallbackFunction->visibility() != FunctionDefinition::Visibility::External + ) + m_errorReporter.typeError(fallbackFunction->location(), "Fallback function must be defined as \"external\"."); } } } @@ -164,28 +169,52 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con for (; it != functions[_contract.name()].end(); ++it) ssl.append("Another declaration is here:", (*it)->location()); + string msg = "More than one constructor defined."; + size_t occurrences = ssl.infos.size(); + if (occurrences > 32) + { + ssl.infos.resize(32); + msg += " Truncated from " + boost::lexical_cast(occurrences) + " to the first 32 occurrences."; + } + m_errorReporter.declarationError( functions[_contract.name()].front()->location(), ssl, - "More than one constructor defined." + msg ); } for (auto const& it: functions) { vector const& overloads = it.second; - for (size_t i = 0; i < overloads.size(); ++i) + set reported; + for (size_t i = 0; i < overloads.size() && !reported.count(i); ++i) + { + SecondarySourceLocation ssl; + for (size_t j = i + 1; j < overloads.size(); ++j) if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j]))) { - m_errorReporter.declarationError( - overloads[j]->location(), - SecondarySourceLocation().append( - "Other declaration is here:", - overloads[i]->location() - ), - "Function with same name and arguments defined twice." - ); + ssl.append("Other declaration is here:", overloads[j]->location()); + reported.insert(j); + } + + if (ssl.infos.size() > 0) + { + string msg = "Function with same name and arguments defined twice."; + size_t occurrences = ssl.infos.size(); + if (occurrences > 32) + { + ssl.infos.resize(32); + msg += " Truncated from " + boost::lexical_cast(occurrences) + " to the first 32 occurrences."; } + + m_errorReporter.declarationError( + overloads[i]->location(), + ssl, + msg + ); + } + } } } @@ -315,6 +344,9 @@ void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, Func if (!functionType.hasEqualArgumentTypes(superType)) return; + if (!function.annotation().superFunction) + function.annotation().superFunction = &super; + if (function.visibility() != super.visibility()) overrideError(function, super, "Overriding function visibility differs."); @@ -458,7 +490,7 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) " to " + parameterTypes[i]->toString() + " requested." - ); + ); } void TypeChecker::endVisit(UsingForDirective const& _usingFor) @@ -519,7 +551,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) if (!type(*var)->canLiveOutsideStorage()) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction))) - m_errorReporter.fatalTypeError(var->location(), "Internal type is not allowed for public or external functions."); + m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions."); var->accept(*this); } @@ -591,7 +623,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) { bool allowed = false; if (auto arrayType = dynamic_cast(_variable.type().get())) - allowed = arrayType->isString(); + allowed = arrayType->isByteArray(); if (!allowed) m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented."); } @@ -614,7 +646,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) _variable.visibility() >= VariableDeclaration::Visibility::Public && !FunctionType(_variable).interfaceFunctionType() ) - m_errorReporter.typeError(_variable.location(), "Internal type is not allowed for public state variables."); + m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables."); if (varType->category() == Type::Category::Array) if (auto arrayType = dynamic_cast(varType.get())) @@ -623,7 +655,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) (arrayType->location() == DataLocation::CallData)) && !arrayType->validForCalldata() ) - m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded as calldata."); + m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded."); return false; } @@ -698,15 +730,15 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) { if (var->isIndexed()) numIndexed++; - if (_eventDef.isAnonymous() && numIndexed > 4) - m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event."); - else if (!_eventDef.isAnonymous() && numIndexed > 3) - m_errorReporter.typeError(_eventDef.location(), "More than 3 indexed arguments for event."); if (!type(*var)->canLiveOutsideStorage()) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (!type(*var)->interfaceType(false)) - m_errorReporter.typeError(var->location(), "Internal type is not allowed as event parameter type."); + m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type."); } + if (_eventDef.isAnonymous() && numIndexed > 4) + m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event."); + else if (!_eventDef.isAnonymous() && numIndexed > 3) + m_errorReporter.typeError(_eventDef.location(), "More than 3 indexed arguments for event."); return false; } @@ -1445,7 +1477,37 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) else _functionCall.annotation().type = make_shared(functionType->returnParameterTypes()); + if (auto functionName = dynamic_cast(&_functionCall.expression())) + { + if (functionName->name() == "sha3" && functionType->kind() == FunctionType::Kind::SHA3) + m_errorReporter.warning(_functionCall.location(), "\"sha3\" has been deprecated in favour of \"keccak256\""); + else if (functionName->name() == "suicide" && functionType->kind() == FunctionType::Kind::Selfdestruct) + m_errorReporter.warning(_functionCall.location(), "\"suicide\" has been deprecated in favour of \"selfdestruct\""); + } + TypePointers parameterTypes = functionType->parameterTypes(); + + if (!functionType->padArguments()) + { + for (size_t i = 0; i < arguments.size(); ++i) + { + auto const& argType = type(*arguments[i]); + if (auto literal = dynamic_cast(argType.get())) + { + /* If no mobile type is available an error will be raised elsewhere. */ + if (literal->mobileType()) + m_errorReporter.warning( + _functionCall.location(), + "The type of \"" + + argType->toString() + + "\" was inferred as " + + literal->mobileType()->toString() + + ". This is probably not desired. Use an explicit type to silence this warning." + ); + } + } + } + if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size()) { string msg = @@ -1561,14 +1623,16 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) if (contract->contractKind() == ContractDefinition::ContractKind::Interface) m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface."); if (!contract->annotation().unimplementedFunctions.empty()) + { + SecondarySourceLocation ssl; + for (auto function: contract->annotation().unimplementedFunctions) + ssl.append("Missing implementation:", function->location()); m_errorReporter.typeError( _newExpression.location(), - SecondarySourceLocation().append( - "Missing implementation:", - contract->annotation().unimplementedFunctions.front()->location() - ), + ssl, "Trying to create an instance of an abstract contract." ); + } if (!contract->constructorIsPublic()) m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly."); @@ -1604,7 +1668,9 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) TypePointers{type}, strings(), strings(), - FunctionType::Kind::ObjectCreation + FunctionType::Kind::ObjectCreation, + false, + StateMutability::Pure ); _newExpression.annotation().isPure = true; } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index f2e13765ddbb..0c6f54d3f1e0 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -63,6 +63,7 @@ class TypeChecker: private ASTConstVisitor void checkContractDuplicateFunctions(ContractDefinition const& _contract); void checkContractIllegalOverrides(ContractDefinition const& _contract); /// Reports a type error with an appropiate message if overriden function signature differs. + /// Also stores the direct super function in the AST annotations. void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super); void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message); void checkContractAbstractFunctions(ContractDefinition const& _contract); diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp new file mode 100644 index 000000000000..7f28c7d23e50 --- /dev/null +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -0,0 +1,325 @@ +/* + 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 . +*/ + +#include + +#include + +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +namespace +{ + +class AssemblyViewPureChecker: public boost::static_visitor +{ +public: + explicit AssemblyViewPureChecker(std::function _reportMutability): + m_reportMutability(_reportMutability) {} + + void operator()(assembly::Label const&) { } + void operator()(assembly::Instruction const& _instruction) + { + if (eth::SemanticInformation::invalidInViewFunctions(_instruction.instruction)) + m_reportMutability(StateMutability::NonPayable, _instruction.location); + else if (eth::SemanticInformation::invalidInPureFunctions(_instruction.instruction)) + m_reportMutability(StateMutability::View, _instruction.location); + } + void operator()(assembly::Literal const&) {} + void operator()(assembly::Identifier const&) {} + void operator()(assembly::FunctionalInstruction const& _instr) + { + (*this)(_instr.instruction); + for (auto const& arg: _instr.arguments) + boost::apply_visitor(*this, arg); + } + void operator()(assembly::StackAssignment const&) {} + void operator()(assembly::Assignment const& _assignment) + { + boost::apply_visitor(*this, *_assignment.value); + } + void operator()(assembly::VariableDeclaration const& _varDecl) + { + if (_varDecl.value) + boost::apply_visitor(*this, *_varDecl.value); + } + void operator()(assembly::FunctionDefinition const& _funDef) + { + (*this)(_funDef.body); + } + void operator()(assembly::FunctionCall const& _funCall) + { + for (auto const& arg: _funCall.arguments) + boost::apply_visitor(*this, arg); + } + void operator()(assembly::Switch const& _switch) + { + boost::apply_visitor(*this, *_switch.expression); + for (auto const& _case: _switch.cases) + { + if (_case.value) + (*this)(*_case.value); + (*this)(_case.body); + } + } + void operator()(assembly::ForLoop const& _for) + { + (*this)(_for.pre); + boost::apply_visitor(*this, *_for.condition); + (*this)(_for.body); + (*this)(_for.post); + } + void operator()(assembly::Block const& _block) + { + for (auto const& s: _block.statements) + boost::apply_visitor(*this, s); + } + +private: + std::function m_reportMutability; +}; + +} + +bool ViewPureChecker::check() +{ + // The bool means "enforce view with errors". + map contracts; + + for (auto const& node: m_ast) + { + SourceUnit const* source = dynamic_cast(node.get()); + solAssert(source, ""); + bool enforceView = source->annotation().experimentalFeatures.count(ExperimentalFeature::V050); + for (ContractDefinition const* c: source->filteredNodes(source->nodes())) + contracts[c] = enforceView; + } + + // Check modifiers first to infer their state mutability. + for (auto const& contract: contracts) + { + m_enforceViewWithError = contract.second; + for (ModifierDefinition const* mod: contract.first->functionModifiers()) + mod->accept(*this); + } + + for (auto const& contract: contracts) + { + m_enforceViewWithError = contract.second; + contract.first->accept(*this); + } + + return !m_errors; +} + + + +bool ViewPureChecker::visit(FunctionDefinition const& _funDef) +{ + solAssert(!m_currentFunction, ""); + m_currentFunction = &_funDef; + m_currentBestMutability = StateMutability::Pure; + return true; +} + +void ViewPureChecker::endVisit(FunctionDefinition const& _funDef) +{ + solAssert(m_currentFunction == &_funDef, ""); + if ( + m_currentBestMutability < _funDef.stateMutability() && + _funDef.stateMutability() != StateMutability::Payable && + _funDef.isImplemented() && + !_funDef.isConstructor() && + !_funDef.isFallback() && + !_funDef.annotation().superFunction + ) + m_errorReporter.warning( + _funDef.location(), + "Function state mutability can be restricted to " + stateMutabilityToString(m_currentBestMutability) + ); + m_currentFunction = nullptr; +} + +bool ViewPureChecker::visit(ModifierDefinition const&) +{ + solAssert(m_currentFunction == nullptr, ""); + m_currentBestMutability = StateMutability::Pure; + return true; +} + +void ViewPureChecker::endVisit(ModifierDefinition const& _modifierDef) +{ + solAssert(m_currentFunction == nullptr, ""); + m_inferredMutability[&_modifierDef] = m_currentBestMutability; +} + +void ViewPureChecker::endVisit(Identifier const& _identifier) +{ + Declaration const* declaration = _identifier.annotation().referencedDeclaration; + solAssert(declaration, ""); + + StateMutability mutability = StateMutability::Pure; + + bool writes = _identifier.annotation().lValueRequested; + if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) + { + if (varDecl->isStateVariable() && !varDecl->isConstant()) + mutability = writes ? StateMutability::NonPayable : StateMutability::View; + } + else if (MagicVariableDeclaration const* magicVar = dynamic_cast(declaration)) + { + switch (magicVar->type()->category()) + { + case Type::Category::Contract: + solAssert(_identifier.name() == "this" || _identifier.name() == "super", ""); + if (!dynamic_cast(*magicVar->type()).isSuper()) + // reads the address + mutability = StateMutability::View; + break; + case Type::Category::Integer: + solAssert(_identifier.name() == "now", ""); + mutability = StateMutability::View; + break; + default: + break; + } + } + + reportMutability(mutability, _identifier.location()); +} + +void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly) +{ + AssemblyViewPureChecker{ + [=](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); } + }(_inlineAssembly.operations()); +} + +void ViewPureChecker::reportMutability(StateMutability _mutability, SourceLocation const& _location) +{ + if (m_currentFunction && m_currentFunction->stateMutability() < _mutability) + { + string text; + if (_mutability == StateMutability::View) + text = + "Function declared as pure, but this expression (potentially) reads from the " + "environment or state and thus requires \"view\"."; + else if (_mutability == StateMutability::NonPayable) + text = + "Function declared as " + + stateMutabilityToString(m_currentFunction->stateMutability()) + + ", but this expression (potentially) modifies the state and thus " + "requires non-payable (the default) or payable."; + else + solAssert(false, ""); + + solAssert( + m_currentFunction->stateMutability() == StateMutability::View || + m_currentFunction->stateMutability() == StateMutability::Pure, + "" + ); + if (!m_enforceViewWithError && m_currentFunction->stateMutability() == StateMutability::View) + m_errorReporter.warning(_location, text); + else + { + m_errors = true; + m_errorReporter.typeError(_location, text); + } + } + if (_mutability > m_currentBestMutability) + m_currentBestMutability = _mutability; +} + +void ViewPureChecker::endVisit(FunctionCall const& _functionCall) +{ + if (_functionCall.annotation().kind != FunctionCallKind::FunctionCall) + return; + + StateMutability mut = dynamic_cast(*_functionCall.expression().annotation().type).stateMutability(); + // We only require "nonpayable" to call a payble function. + if (mut == StateMutability::Payable) + mut = StateMutability::NonPayable; + reportMutability(mut, _functionCall.location()); +} + +void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) +{ + StateMutability mutability = StateMutability::Pure; + bool writes = _memberAccess.annotation().lValueRequested; + + ASTString const& member = _memberAccess.memberName(); + switch (_memberAccess.expression().annotation().type->category()) + { + case Type::Category::Contract: + case Type::Category::Integer: + if (member == "balance" && !_memberAccess.annotation().referencedDeclaration) + mutability = StateMutability::View; + break; + case Type::Category::Magic: + // we can ignore the kind of magic and only look at the name of the member + if (member != "data" && member != "sig" && member != "blockhash") + mutability = StateMutability::View; + break; + case Type::Category::Struct: + { + if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage)) + mutability = writes ? StateMutability::NonPayable : StateMutability::View; + break; + } + case Type::Category::Array: + { + auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type); + if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage)) + mutability = writes ? StateMutability::NonPayable : StateMutability::View; + break; + } + default: + break; + } + reportMutability(mutability, _memberAccess.location()); +} + +void ViewPureChecker::endVisit(IndexAccess const& _indexAccess) +{ + if (!_indexAccess.indexExpression()) + solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, ""); + else + { + bool writes = _indexAccess.annotation().lValueRequested; + if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage)) + reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess.location()); + } +} + +void ViewPureChecker::endVisit(ModifierInvocation const& _modifier) +{ + solAssert(_modifier.name(), ""); + if (ModifierDefinition const* mod = dynamic_cast(_modifier.name()->annotation().referencedDeclaration)) + { + solAssert(m_inferredMutability.count(mod), ""); + reportMutability(m_inferredMutability.at(mod), _modifier.location()); + } + else + solAssert(dynamic_cast(_modifier.name()->annotation().referencedDeclaration), ""); +} + diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h new file mode 100644 index 000000000000..fec060b6a31a --- /dev/null +++ b/libsolidity/analysis/ViewPureChecker.h @@ -0,0 +1,80 @@ +/* + 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 . +*/ + +#pragma once + +#include +#include +#include + +#include + +#include +#include + +namespace dev +{ +namespace solidity +{ + +class ASTNode; +class FunctionDefinition; +class ModifierDefinition; +class Identifier; +class MemberAccess; +class IndexAccess; +class ModifierInvocation; +class FunctionCall; +class InlineAssembly; + +class ViewPureChecker: private ASTConstVisitor +{ +public: + ViewPureChecker(std::vector> const& _ast, ErrorReporter& _errorReporter): + m_ast(_ast), m_errorReporter(_errorReporter) {} + + bool check(); + +private: + + virtual bool visit(FunctionDefinition const& _funDef) override; + virtual void endVisit(FunctionDefinition const& _funDef) override; + virtual bool visit(ModifierDefinition const& _modifierDef) override; + virtual void endVisit(ModifierDefinition const& _modifierDef) override; + virtual void endVisit(Identifier const& _identifier) override; + virtual void endVisit(MemberAccess const& _memberAccess) override; + virtual void endVisit(IndexAccess const& _indexAccess) override; + virtual void endVisit(ModifierInvocation const& _modifier) override; + virtual void endVisit(FunctionCall const& _functionCall) override; + virtual void endVisit(InlineAssembly const& _inlineAssembly) override; + + /// Called when an element of mutability @a _mutability is encountered. + /// Creates appropriate warnings and errors and sets @a m_currentBestMutability. + void reportMutability(StateMutability _mutability, SourceLocation const& _location); + + std::vector> const& m_ast; + ErrorReporter& m_errorReporter; + + bool m_errors = false; + bool m_enforceViewWithError = false; + StateMutability m_currentBestMutability = StateMutability::Payable; + FunctionDefinition const* m_currentFunction = nullptr; + std::map m_inferredMutability; +}; + +} +} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 7f4dea0ef48b..a805322b831f 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -176,11 +176,19 @@ vector const& ContractDefinition::interfaceEvents() cons m_interfaceEvents.reset(new vector()); for (ContractDefinition const* contract: annotation().linearizedBaseContracts) for (EventDefinition const* e: contract->events()) - if (eventsSeen.count(e->name()) == 0) + { + /// NOTE: this requires the "internal" version of an Event, + /// though here internal strictly refers to visibility, + /// and not to function encoding (jump vs. call) + auto const& function = e->functionType(true); + solAssert(function, ""); + string eventSignature = function->externalSignature(); + if (eventsSeen.count(eventSignature) == 0) { - eventsSeen.insert(e->name()); + eventsSeen.insert(eventSignature); m_interfaceEvents->push_back(e); } + } } return *m_interfaceEvents; } @@ -218,26 +226,6 @@ vector, FunctionTypePointer>> const& ContractDefinition::inter return *m_interfaceFunctionList; } -Json::Value const& ContractDefinition::devDocumentation() const -{ - return m_devDocumentation; -} - -Json::Value const& ContractDefinition::userDocumentation() const -{ - return m_userDocumentation; -} - -void ContractDefinition::setDevDocumentation(Json::Value const& _devDocumentation) -{ - m_devDocumentation = _devDocumentation; -} - -void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentation) -{ - m_userDocumentation = _userDocumentation; -} - vector const& ContractDefinition::inheritableMembers() const { if (!m_inheritableMembers) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 4592a190796b..75b8e94616e8 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -180,6 +180,7 @@ class Declaration: public ASTNode /// @returns the declared name. ASTString const& name() const { return *m_name; } + bool noVisibilitySpecified() const { return m_visibility == Visibility::Default; } Visibility visibility() const { return m_visibility == Visibility::Default ? defaultVisibility() : m_visibility; } bool isPublic() const { return visibility() >= Visibility::Public; } virtual bool isVisibleInContract() const { return visibility() != Visibility::External; } @@ -392,12 +393,6 @@ class ContractDefinition: public Declaration, public Documented /// Returns the fallback function or nullptr if no fallback function was specified. FunctionDefinition const* fallbackFunction() const; - Json::Value const& userDocumentation() const; - void setUserDocumentation(Json::Value const& _userDocumentation); - - Json::Value const& devDocumentation() const; - void setDevDocumentation(Json::Value const& _devDocumentation); - virtual TypePointer type() const override; virtual ContractDefinitionAnnotation& annotation() const override; @@ -409,10 +404,6 @@ class ContractDefinition: public Declaration, public Documented std::vector> m_subNodes; ContractKind m_contractKind; - // parsed Natspec documentation of the contract. - Json::Value m_userDocumentation; - Json::Value m_devDocumentation; - std::vector m_linearizedBaseContracts; mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; mutable std::unique_ptr> m_interfaceEvents; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index fd9efb4d9f56..3d4236cced36 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -94,6 +94,9 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation { + /// The function this function overrides, if any. This is always the closest + /// in the linearized inheritance hierarchy. + FunctionDefinition const* superFunction = nullptr; }; struct EventDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index afc53bfe76f3..51249f20d514 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -129,7 +129,7 @@ string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location) return std::to_string(_location.start) + ":" + std::to_string(length) + ":" + std::to_string(sourceIndex); } -string ASTJsonConverter::namePathToString(std::vector const& _namePath) const +string ASTJsonConverter::namePathToString(std::vector const& _namePath) { return boost::algorithm::join(_namePath, "."); } @@ -171,7 +171,7 @@ void ASTJsonConverter::appendExpressionAttributes( _attributes += exprAttributes; } -Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair _info) +Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair _info) const { Json::Value tuple(Json::objectValue); tuple["src"] = sourceLocationToString(_info.first->location); @@ -328,6 +328,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node) make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View), make_pair("payable", _node.isPayable()), make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), + make_pair("superFunction", idOrNull(_node.annotation().superFunction)), make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("parameters", toJson(_node.parameterList())), make_pair("isConstructor", _node.isConstructor()), diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 60c660c1f793..9a886220f9f3 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -120,7 +120,7 @@ class ASTJsonConverter: public ASTConstVisitor std::vector>&& _attributes ); std::string sourceLocationToString(SourceLocation const& _location) const; - std::string namePathToString(std::vector const& _namePath) const; + static std::string namePathToString(std::vector const& _namePath); static Json::Value idOrNull(ASTNode const* _pt) { return _pt ? Json::Value(nodeId(*_pt)) : Json::nullValue; @@ -129,13 +129,13 @@ class ASTJsonConverter: public ASTConstVisitor { return _node ? toJson(*_node) : Json::nullValue; } - Json::Value inlineAssemblyIdentifierToJson(std::pair _info); - std::string location(VariableDeclaration::Location _location); - std::string contractKind(ContractDefinition::ContractKind _kind); - std::string functionCallKind(FunctionCallKind _kind); - std::string literalTokenKind(Token::Value _token); - std::string type(Expression const& _expression); - std::string type(VariableDeclaration const& _varDecl); + Json::Value inlineAssemblyIdentifierToJson(std::pair _info) const; + static std::string location(VariableDeclaration::Location _location); + static std::string contractKind(ContractDefinition::ContractKind _kind); + static std::string functionCallKind(FunctionCallKind _kind); + static std::string literalTokenKind(Token::Value _token); + static std::string type(Expression const& _expression); + static std::string type(VariableDeclaration const& _varDecl); static int nodeId(ASTNode const& _node) { return _node.id(); @@ -151,8 +151,8 @@ class ASTJsonConverter: public ASTConstVisitor } return tmp; } - Json::Value typePointerToJson(TypePointer _tp); - Json::Value typePointerToJson(std::shared_ptr> _tps); + static Json::Value typePointerToJson(TypePointer _tp); + static Json::Value typePointerToJson(std::shared_ptr> _tps); void appendExpressionAttributes( std::vector> &_attributes, ExpressionAnnotation const& _annotation diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index 392179ef5ae8..81e6cc44fb69 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -21,9 +21,12 @@ */ #include -#include #include +#include + +#include + using namespace std; namespace dev @@ -579,8 +582,11 @@ void ASTPrinter::printSourcePart(ASTNode const& _node) if (!m_source.empty()) { SourceLocation const& location(_node.location()); - *m_ostream << indentation() << " Source: " - << escaped(m_source.substr(location.start, location.end - location.start), false) << endl; + *m_ostream << + indentation() << + " Source: " << + Json::valueToQuotedString(m_source.substr(location.start, location.end - location.start).c_str()) << + endl; } } diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h index 2c089671222f..3ecfac7bfce3 100644 --- a/libsolidity/ast/ExperimentalFeatures.h +++ b/libsolidity/ast/ExperimentalFeatures.h @@ -31,6 +31,7 @@ enum class ExperimentalFeature { SMTChecker, ABIEncoderV2, // new ABI encoder that makes use of JULIA + V050, // v0.5.0 breaking changes Test, TestOnlyAnalysis }; @@ -45,6 +46,7 @@ static const std::map ExperimentalFeatureNames { { "SMTChecker", ExperimentalFeature::SMTChecker }, { "ABIEncoderV2", ExperimentalFeature::ABIEncoderV2 }, + { "v0.5.0", ExperimentalFeature::V050 }, { "__test", ExperimentalFeature::Test }, { "__testOnlyAnalysis", ExperimentalFeature::TestOnlyAnalysis }, }; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 5e61cdee7fe6..83a5b465afc2 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -304,6 +305,9 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition return members; } +namespace +{ + bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountType) { // Disable >>> here. @@ -317,6 +321,8 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT return false; } +} + IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): m_bits(_bits), m_modifier(_modifier) { @@ -1465,7 +1471,7 @@ string ArrayType::toString(bool _short) const return ret; } -string ArrayType::canonicalName(bool _addDataLocation) const +string ArrayType::canonicalName() const { string ret; if (isString()) @@ -1474,16 +1480,29 @@ string ArrayType::canonicalName(bool _addDataLocation) const ret = "bytes"; else { - ret = baseType()->canonicalName(false) + "["; + ret = baseType()->canonicalName() + "["; if (!isDynamicallySized()) ret += length().str(); ret += "]"; } - if (_addDataLocation && location() == DataLocation::Storage) - ret += " storage"; return ret; } +string ArrayType::signatureInExternalFunction(bool _structsByName) const +{ + if (isByteArray()) + return canonicalName(); + else + { + solAssert(baseType(), ""); + return + baseType()->signatureInExternalFunction(_structsByName) + + "[" + + (isDynamicallySized() ? "" : length().str()) + + "]"; + } +} + MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const { MemberList::MemberMap members; @@ -1592,7 +1611,7 @@ string ContractType::toString(bool) const m_contract.name(); } -string ContractType::canonicalName(bool) const +string ContractType::canonicalName() const { return m_contract.annotation().canonicalName; } @@ -1716,15 +1735,22 @@ unsigned StructType::calldataEncodedSize(bool _padded) const bool StructType::isDynamicallyEncoded() const { - solAssert(false, "Structs are not yet supported in the ABI."); + solAssert(!recursive(), ""); + for (auto t: memoryMemberTypes()) + { + solAssert(t, "Parameter should have external type."); + t = t->interfaceType(false); + if (t->isDynamicallyEncoded()) + return true; + } + return false; } u256 StructType::memorySize() const { u256 size; - for (auto const& member: members(nullptr)) - if (member.type->canLiveOutsideStorage()) - size += member.type->memoryHeadSize(); + for (auto const& t: memoryMemberTypes()) + size += t->memoryHeadSize(); return size; } @@ -1762,10 +1788,33 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const TypePointer StructType::interfaceType(bool _inLibrary) const { + if (!canBeUsedExternally(_inLibrary)) + return TypePointer(); + + // Has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) if (_inLibrary && location() == DataLocation::Storage) return shared_from_this(); else - return TypePointer(); + return copyForLocation(DataLocation::Memory, true); +} + +bool StructType::canBeUsedExternally(bool _inLibrary) const +{ + if (_inLibrary && location() == DataLocation::Storage) + return true; + else if (recursive()) + return false; + else + { + // Check that all members have interface types. + // We pass "false" to canBeUsedExternally (_inLibrary), because this struct will be + // passed by value and thus the encoding does not differ, but it will disallow + // mappings. + for (auto const& var: m_struct.members()) + if (!var->annotation().type->canBeUsedExternally(false)) + return false; + } + return true; } TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const @@ -1775,12 +1824,27 @@ TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) return copy; } -string StructType::canonicalName(bool _addDataLocation) const +string StructType::signatureInExternalFunction(bool _structsByName) const { - string ret = m_struct.annotation().canonicalName; - if (_addDataLocation && location() == DataLocation::Storage) - ret += " storage"; - return ret; + if (_structsByName) + return canonicalName(); + else + { + TypePointers memberTypes = memoryMemberTypes(); + auto memberTypeStrings = memberTypes | boost::adaptors::transformed([&](TypePointer _t) -> string + { + solAssert(_t, "Parameter should have external type."); + auto t = _t->interfaceType(_structsByName); + solAssert(t, ""); + return t->signatureInExternalFunction(_structsByName); + }); + return "(" + boost::algorithm::join(memberTypeStrings, ",") + ")"; + } +} + +string StructType::canonicalName() const +{ + return m_struct.annotation().canonicalName; } FunctionTypePointer StructType::constructorType() const @@ -1822,6 +1886,15 @@ u256 StructType::memoryOffsetOfMember(string const& _name) const return 0; } +TypePointers StructType::memoryMemberTypes() const +{ + TypePointers types; + for (ASTPointer const& variable: m_struct.members()) + if (variable->annotation().type->canLiveOutsideStorage()) + types.push_back(variable->annotation().type); + return types; +} + set StructType::membersMissingInMemory() const { set missing; @@ -1831,6 +1904,33 @@ set StructType::membersMissingInMemory() const return missing; } +bool StructType::recursive() const +{ + if (!m_recursive.is_initialized()) + { + set structsSeen; + function check = [&](StructType const* t) -> bool + { + StructDefinition const* str = &t->structDefinition(); + if (structsSeen.count(str)) + return true; + structsSeen.insert(str); + for (ASTPointer const& variable: str->members()) + { + Type const* memberType = variable->annotation().type.get(); + while (dynamic_cast(memberType)) + memberType = dynamic_cast(memberType)->baseType().get(); + if (StructType const* innerStruct = dynamic_cast(memberType)) + if (check(innerStruct)) + return true; + } + return false; + }; + m_recursive = check(this); + } + return *m_recursive; +} + TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const { return _operator == Token::Delete ? make_shared() : TypePointer(); @@ -1863,7 +1963,7 @@ string EnumType::toString(bool) const return string("enum ") + m_enum.annotation().canonicalName; } -string EnumType::canonicalName(bool) const +string EnumType::canonicalName() const { return m_enum.annotation().canonicalName; } @@ -2030,7 +2130,9 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal } FunctionType::FunctionType(VariableDeclaration const& _varDecl): - m_kind(Kind::External), m_stateMutability(StateMutability::View), m_declaration(&_varDecl) + m_kind(Kind::External), + m_stateMutability(StateMutability::View), + m_declaration(&_varDecl) { TypePointers paramTypes; vector paramNames; @@ -2090,7 +2192,9 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): } FunctionType::FunctionType(EventDefinition const& _event): - m_kind(Kind::Event), m_stateMutability(StateMutability::View), m_declaration(&_event) + m_kind(Kind::Event), + m_stateMutability(StateMutability::NonPayable), + m_declaration(&_event) { TypePointers params; vector paramNames; @@ -2160,7 +2264,6 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c strings{""}, Kind::Creation, false, - nullptr, stateMutability ); } @@ -2287,7 +2390,7 @@ TypePointer FunctionType::binaryOperatorResult(Token::Value _operator, TypePoint return TypePointer(); } -string FunctionType::canonicalName(bool) const +string FunctionType::canonicalName() const { solAssert(m_kind == Kind::External, ""); return "function"; @@ -2412,8 +2515,8 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const m_returnParameterNames, m_kind, m_arbitraryParameters, - m_declaration, - m_stateMutability + m_stateMutability, + m_declaration ); } @@ -2428,6 +2531,11 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con case Kind::BareDelegateCall: { MemberList::MemberMap members; + if (m_kind == Kind::External) + members.push_back(MemberList::Member( + "selector", + make_shared(4) + )); if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall) { if (isPayable()) @@ -2440,8 +2548,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con strings(), Kind::SetValue, false, - nullptr, StateMutability::NonPayable, + nullptr, m_gasSet, m_valueSet ) @@ -2457,8 +2565,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con strings(), Kind::SetGas, false, - nullptr, StateMutability::NonPayable, + nullptr, m_gasSet, m_valueSet ) @@ -2542,20 +2650,19 @@ string FunctionType::externalSignature() const solAssert(m_declaration != nullptr, "External signature of function needs declaration"); solAssert(!m_declaration->name().empty(), "Fallback function has no signature."); - bool _inLibrary = dynamic_cast(*m_declaration->scope()).isLibrary(); - - string ret = m_declaration->name() + "("; - + bool const inLibrary = dynamic_cast(*m_declaration->scope()).isLibrary(); FunctionTypePointer external = interfaceFunctionType(); solAssert(!!external, "External function type requested."); - TypePointers externalParameterTypes = external->parameterTypes(); - for (auto it = externalParameterTypes.cbegin(); it != externalParameterTypes.cend(); ++it) + auto parameterTypes = external->parameterTypes(); + auto typeStrings = parameterTypes | boost::adaptors::transformed([&](TypePointer _t) -> string { - solAssert(!!(*it), "Parameter should have external type"); - ret += (*it)->canonicalName(_inLibrary) + (it + 1 == externalParameterTypes.cend() ? "" : ","); - } - - return ret + ")"; + solAssert(_t, "Parameter should have external type."); + string typeName = _t->signatureInExternalFunction(inLibrary); + if (inLibrary && _t->dataStoredIn(DataLocation::Storage)) + typeName += " storage"; + return typeName; + }); + return m_declaration->name() + "(" + boost::algorithm::join(typeStrings, ",") + ")"; } u256 FunctionType::externalIdentifier() const @@ -2565,6 +2672,8 @@ u256 FunctionType::externalIdentifier() const bool FunctionType::isPure() const { + // FIXME: replace this with m_stateMutability == StateMutability::Pure once + // the callgraph analyzer is in place return m_kind == Kind::SHA3 || m_kind == Kind::ECRecover || @@ -2593,8 +2702,8 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con m_returnParameterNames, m_kind, m_arbitraryParameters, - m_declaration, m_stateMutability, + m_declaration, m_gasSet || _setGas, m_valueSet || _setValue, m_bound @@ -2642,8 +2751,8 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) m_returnParameterNames, kind, m_arbitraryParameters, - m_declaration, m_stateMutability, + m_declaration, m_gasSet, m_valueSet, _bound @@ -2684,9 +2793,9 @@ string MappingType::toString(bool _short) const return "mapping(" + keyType()->toString(_short) + " => " + valueType()->toString(_short) + ")"; } -string MappingType::canonicalName(bool) const +string MappingType::canonicalName() const { - return "mapping(" + keyType()->canonicalName(false) + " => " + valueType()->canonicalName(false) + ")"; + return "mapping(" + keyType()->canonicalName() + " => " + valueType()->canonicalName() + ")"; } string TypeType::identifier() const @@ -2861,7 +2970,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const return MemberList::MemberMap({ {"coinbase", make_shared(0, IntegerType::Modifier::Address)}, {"timestamp", make_shared(256)}, - {"blockhash", make_shared(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash)}, + {"blockhash", make_shared(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)}, {"difficulty", make_shared(256)}, {"number", make_shared(256)}, {"gaslimit", make_shared(256)} diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index ce2d3bf8cee4..8ba555215d49 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -32,10 +32,12 @@ #include #include +#include #include #include #include +#include namespace dev { @@ -244,9 +246,15 @@ class Type: private boost::noncopyable, public std::enable_shared_from_thiscanLiveOutsideStorage(); } virtual unsigned sizeOnStack() const override; virtual std::string toString(bool _short) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; + virtual std::string signatureInExternalFunction(bool _structsByName) const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override; virtual TypePointer decodingType() const override; @@ -676,7 +685,7 @@ class ContractType: public Type virtual unsigned sizeOnStack() const override { return m_super ? 0 : 1; } virtual bool isValueType() const override { return true; } virtual std::string toString(bool _short) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override @@ -737,13 +746,15 @@ class StructType: public ReferenceType virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override { - return location() == DataLocation::Storage ? std::make_shared(256) : TypePointer(); + return location() == DataLocation::Storage ? std::make_shared(256) : shared_from_this(); } virtual TypePointer interfaceType(bool _inLibrary) const override; + virtual bool canBeUsedExternally(bool _inLibrary) const override; TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; + virtual std::string signatureInExternalFunction(bool _structsByName) const override; /// @returns a function that peforms the type conversion between a list of struct members /// and a memory struct of this type. @@ -754,11 +765,19 @@ class StructType: public ReferenceType StructDefinition const& structDefinition() const { return m_struct; } + /// @returns the vector of types of members available in memory. + TypePointers memoryMemberTypes() const; /// @returns the set of all members that are removed in the memory version (typically mappings). std::set membersMissingInMemory() const; + /// @returns true if the same struct is used recursively in one of its members. Only + /// analyses the "memory" representation, i.e. mappings are ignored in all structs. + bool recursive() const; + private: StructDefinition const& m_struct; + /// Cache for the recursive() function. + mutable boost::optional m_recursive; }; /** @@ -779,7 +798,7 @@ class EnumType: public Type virtual unsigned storageBytes() const override; virtual bool canLiveOutsideStorage() const override { return true; } virtual std::string toString(bool _short) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; virtual bool isValueType() const override { return true; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; @@ -898,7 +917,6 @@ class FunctionType: public Type strings(), _kind, _arbitraryParameters, - nullptr, _stateMutability ) { @@ -915,8 +933,8 @@ class FunctionType: public Type strings _returnParameterNames = strings(), Kind _kind = Kind::Internal, bool _arbitraryParameters = false, - Declaration const* _declaration = nullptr, StateMutability _stateMutability = StateMutability::NonPayable, + Declaration const* _declaration = nullptr, bool _gasSet = false, bool _valueSet = false, bool _bound = false @@ -951,7 +969,7 @@ class FunctionType: public Type virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override; - virtual std::string canonicalName(bool /*_addDataLocation*/) const override; + virtual std::string canonicalName() const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } @@ -1053,7 +1071,7 @@ class MappingType: public Type virtual std::string identifier() const override; virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; - virtual std::string canonicalName(bool _addDataLocation) const override; + virtual std::string canonicalName() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual TypePointer encodingType() const override @@ -1064,6 +1082,7 @@ class MappingType: public Type { return _inLibrary ? shared_from_this() : TypePointer(); } + virtual bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; } TypePointer const& keyType() const { return m_keyType; } TypePointer const& valueType() const { return m_valueType; } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index a2938ed796e9..9f6c55ba6702 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -30,65 +30,73 @@ using namespace std; using namespace dev; using namespace dev::solidity; -ABIFunctions::~ABIFunctions() -{ - // This throws an exception and thus might cause immediate termination, but hey, - // it's a failed assertion anyway :-) - solAssert(m_requestedFunctions.empty(), "Forgot to call ``requestedFunctions()``."); -} - string ABIFunctions::tupleEncoder( TypePointers const& _givenTypes, TypePointers const& _targetTypes, bool _encodeAsLibraryTypes ) { - // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart> + string functionName = string("abi_encode_tuple_"); + for (auto const& t: _givenTypes) + functionName += t->identifier() + "_"; + functionName += "_to_"; + for (auto const& t: _targetTypes) + functionName += t->identifier() + "_"; + if (_encodeAsLibraryTypes) + functionName += "_library"; - solAssert(!_givenTypes.empty(), ""); - size_t const headSize_ = headSize(_targetTypes); + return createFunction(functionName, [&]() { + solAssert(!_givenTypes.empty(), ""); - Whiskers encoder(R"( + // Note that the values are in reverse due to the difference in calling semantics. + Whiskers templ(R"( + function (headStart ) -> tail { + tail := add(headStart, ) + + } + )"); + templ("functionName", functionName); + size_t const headSize_ = headSize(_targetTypes); + templ("headSize", to_string(headSize_)); + string valueParams; + string encodeElements; + size_t headPos = 0; + size_t stackPos = 0; + for (size_t i = 0; i < _givenTypes.size(); ++i) { - let tail := add($headStart, ) - - := tail + solAssert(_givenTypes[i], ""); + solAssert(_targetTypes[i], ""); + size_t sizeOnStack = _givenTypes[i]->sizeOnStack(); + string valueNames = ""; + for (size_t j = 0; j < sizeOnStack; j++) + { + valueNames += "value" + to_string(stackPos) + ", "; + valueParams = ", value" + to_string(stackPos) + valueParams; + stackPos++; + } + bool dynamic = _targetTypes[i]->isDynamicallyEncoded(); + Whiskers elementTempl( + dynamic ? + string(R"( + mstore(add(headStart, ), sub(tail, headStart)) + tail := ( tail) + )") : + string(R"( + ( add(headStart, )) + )") + ); + elementTempl("values", valueNames); + elementTempl("pos", to_string(headPos)); + elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], _encodeAsLibraryTypes, false)); + encodeElements += elementTempl.render(); + headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize(); } - )"); - encoder("headSize", to_string(headSize_)); - string encodeElements; - size_t headPos = 0; - size_t stackPos = 0; - for (size_t i = 0; i < _givenTypes.size(); ++i) - { - solAssert(_givenTypes[i], ""); - solAssert(_targetTypes[i], ""); - size_t sizeOnStack = _givenTypes[i]->sizeOnStack(); - string valueNames = ""; - for (size_t j = 0; j < sizeOnStack; j++) - valueNames += "$value" + to_string(stackPos++) + ", "; - bool dynamic = _targetTypes[i]->isDynamicallyEncoded(); - Whiskers elementTempl( - dynamic ? - string(R"( - mstore(add($headStart, ), sub(tail, $headStart)) - tail := ( tail) - )") : - string(R"( - ( add($headStart, )) - )") - ); - elementTempl("values", valueNames); - elementTempl("pos", to_string(headPos)); - elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], _encodeAsLibraryTypes, false)); - encodeElements += elementTempl.render(); - headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize(); - } - solAssert(headPos == headSize_, ""); - encoder("encodeElements", encodeElements); - encoder("deepestStackElement", stackPos > 0 ? "$value0" : "$headStart"); + solAssert(headPos == headSize_, ""); + templ("valueParams", valueParams); + templ("encodeElements", encodeElements); - return encoder.render(); + return templ.render(); + }); } string ABIFunctions::requestedFunctions() @@ -396,9 +404,11 @@ string ABIFunctions::abiEncodingFunction( else solAssert(false, ""); } - else if (dynamic_cast(&to)) + else if (auto const* toStruct = dynamic_cast(&to)) { - solUnimplementedAssert(false, "Structs not yet implemented."); + StructType const* fromStruct = dynamic_cast(&_from); + solAssert(fromStruct, ""); + return abiEncodingFunctionStruct(*fromStruct, *toStruct, _encodeAsLibraryTypes); } else if (_from.category() == Type::Category::Function) return abiEncodingFunctionFunctionType( @@ -526,7 +536,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( for { let i := 0 } lt(i, length) { i := add(i, 1) } { mstore(pos, sub(tail, headStart)) - tail := ((srcPtr), tail) + tail := (, tail) srcPtr := (srcPtr) pos := add(pos, ) } @@ -541,7 +551,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( let srcPtr := (value) for { let i := 0 } lt(i, length) { i := add(i, 1) } { - ((srcPtr), pos) + (, pos) srcPtr := (srcPtr) pos := add(pos, ) } @@ -565,7 +575,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( _encodeAsLibraryTypes, true )); - templ("arrayElementAccess", inMemory ? "mload" : "sload"); + templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" ); templ("nextArrayElement", nextArrayElementFunction(_from)); return templ.render(); }); @@ -718,6 +728,122 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( }); } +string ABIFunctions::abiEncodingFunctionStruct( + StructType const& _from, + StructType const& _to, + bool _encodeAsLibraryTypes +) +{ + string functionName = + "abi_encode_" + + _from.identifier() + + "_to_" + + _to.identifier() + + (_encodeAsLibraryTypes ? "_library" : ""); + + solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), ""); + solAssert(&_from.structDefinition() == &_to.structDefinition(), ""); + + return createFunction(functionName, [&]() { + bool fromStorage = _from.location() == DataLocation::Storage; + bool dynamic = _to.isDynamicallyEncoded(); + Whiskers templ(R"( + function (value, pos) { + let tail := add(pos, ) + + <#members> + { + // + + } + + + } + )"); + templ("functionName", functionName); + templ("return", dynamic ? " -> end " : ""); + templ("assignEnd", dynamic ? "end := tail" : ""); + // to avoid multiple loads from the same slot for subsequent members + templ("init", fromStorage ? "let slotValue := 0" : ""); + u256 previousSlotOffset(-1); + u256 encodingOffset = 0; + vector> members; + for (auto const& member: _to.members(nullptr)) + { + solAssert(member.type, ""); + if (!member.type->canLiveOutsideStorage()) + continue; + solUnimplementedAssert( + member.type->mobileType() && + member.type->mobileType()->interfaceType(_encodeAsLibraryTypes) && + member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(), + "Encoding type \"" + member.type->toString() + "\" not yet implemented." + ); + auto memberTypeTo = member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(); + auto memberTypeFrom = _from.memberType(member.name); + solAssert(memberTypeFrom, ""); + bool dynamicMember = memberTypeTo->isDynamicallyEncoded(); + if (dynamicMember) + solAssert(dynamic, ""); + Whiskers memberTempl(R"( + + let memberValue := + )" + ( + dynamicMember ? + string(R"( + mstore(add(pos, ), sub(tail, pos)) + tail := (memberValue, tail) + )") : + string(R"( + (memberValue, add(pos, )) + )") + ) + ); + if (fromStorage) + { + solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); + u256 storageSlotOffset; + size_t intraSlotOffset; + tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name); + if (memberTypeFrom->isValueType()) + { + if (storageSlotOffset != previousSlotOffset) + { + memberTempl("preprocess", "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"); + previousSlotOffset = storageSlotOffset; + } + else + memberTempl("preprocess", ""); + memberTempl("retrieveValue", shiftRightFunction(intraSlotOffset * 8, false) + "(slotValue)"); + } + else + { + solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); + solAssert(intraSlotOffset == 0, ""); + memberTempl("preprocess", ""); + memberTempl("retrieveValue", "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"); + } + } + else + { + memberTempl("preprocess", ""); + string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); + memberTempl("retrieveValue", "mload(add(value, " + sourceOffset + "))"); + } + memberTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); + encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize(); + memberTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, _encodeAsLibraryTypes, false)); + + members.push_back({}); + members.back()["encode"] = memberTempl.render(); + members.back()["memberName"] = member.name; + } + templ("members", members); + templ("headSize", toCompactHexWithPrefix(encodingOffset)); + return templ.render(); + }); +} + string ABIFunctions::abiEncodingFunctionStringLiteral( Type const& _from, Type const& _to, diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index e43e2323ae85..de2a140ae2c8 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -44,15 +44,18 @@ using TypePointers = std::vector; /// multiple times. /// /// Make sure to include the result of ``requestedFunctions()`` to a block that -/// is visible from the code that was generated here. +/// is visible from the code that was generated here, or use named labels. class ABIFunctions { public: - ~ABIFunctions(); - - /// @returns assembly code block to ABI-encode values of @a _givenTypes residing on the stack + /// @returns name of an assembly function to ABI-encode values of @a _givenTypes /// into memory, converting the types to @a _targetTypes on the fly. - /// Assumed variables to be present: <$value0> <$value1> ... <$value(n-1)> <$headStart> + /// Parameters are: ... , i.e. + /// the layout on the stack is ... with + /// the top of the stack on the right. + /// The values represent stack slots. If a type occupies more or less than one + /// stack slot, it takes exactly that number of values. + /// Returns a pointer to the end of the area written in memory. /// Does not allocate memory (does not change the memory head pointer), but writes /// to memory starting at $headStart and an unrestricted amount after that. /// Assigns the end of encoded memory either to $value0 or (if that is not present) @@ -63,7 +66,7 @@ class ABIFunctions bool _encodeAsLibraryTypes = false ); - /// @returns auxiliary functions referenced from the block generated in @a tupleEncoder + /// @returns concatenation of all generated functions. std::string requestedFunctions(); private: @@ -120,6 +123,13 @@ class ABIFunctions bool _encodeAsLibraryTypes ); + /// Part of @a abiEncodingFunction for struct types. + std::string abiEncodingFunctionStruct( + StructType const& _givenType, + StructType const& _targetType, + bool _encodeAsLibraryTypes + ); + // @returns the name of the ABI encoding function with the given type // and queues the generation of the function to the requested functions. // Case for _givenType being a string literal diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 67ca22f14544..e17188c26e9f 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -913,10 +913,10 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c switch (location) { case DataLocation::Memory: - if (_arrayType.isDynamicallySized()) - m_context << u256(32) << Instruction::ADD; - // fall-through case DataLocation::CallData: + if (location == DataLocation::Memory && _arrayType.isDynamicallySized()) + m_context << u256(32) << Instruction::ADD; + if (!_arrayType.isByteArray()) { m_context << Instruction::SWAP1; diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 8c63ea9c54d9..06654486a64d 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -40,6 +40,8 @@ class Compiler m_context(&m_runtimeContext) { } + /// Compiles a contract. + /// @arg _metadata contains the to be injected metadata CBOR void compileContract( ContractDefinition const& _contract, std::map const& _contracts, @@ -51,14 +53,21 @@ class Compiler ContractDefinition const& _contract, std::map const& _contracts ); + /// @returns Entire assembly. eth::Assembly const& assembly() const { return m_context.assembly(); } + /// @returns The entire assembled object (with constructor). eth::LinkerObject assembledObject() const { return m_context.assembledObject(); } + /// @returns Only the runtime object (without constructor). eth::LinkerObject runtimeObject() const { return m_context.assembledRuntimeObject(m_runtimeSub); } /// @arg _sourceCodes is the map of input files to source code strings - /// @arg _inJsonFromat shows whether the out should be in Json format - Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const + std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const { - return m_context.streamAssembly(_stream, _sourceCodes, _inJsonFormat); + return m_context.assemblyString(_sourceCodes); + } + /// @arg _sourceCodes is the map of input files to source code strings + Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const + { + return m_context.assemblyJSON(_sourceCodes); } /// @returns Assembly items of the normal compiler context eth::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); } diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index ed780d0bd51c..5a77162eeeee 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -266,19 +266,9 @@ void CompilerContext::resetVisitedNodes(ASTNode const* _node) void CompilerContext::appendInlineAssembly( string const& _assembly, vector const& _localVariables, - map const& _replacements + bool _system ) { - string replacedAssembly; - string const* assembly = &_assembly; - if (!_replacements.empty()) - { - replacedAssembly = _assembly; - for (auto const& replacement: _replacements) - replacedAssembly = boost::algorithm::replace_all_copy(replacedAssembly, replacement.first, replacement.second); - assembly = &replacedAssembly; - } - int startStackHeight = stackHeight(); julia::ExternalIdentifierAccess identifierAccess; @@ -320,7 +310,7 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); - auto scanner = make_shared(CharStream(*assembly), "--CODEGEN--"); + auto scanner = make_shared(CharStream(_assembly), "--CODEGEN--"); auto parserResult = assembly::Parser(errorReporter).parse(scanner); solAssert(parserResult, "Failed to parse inline assembly block."); solAssert(errorReporter.errors().empty(), "Failed to parse inline assembly block."); @@ -329,7 +319,7 @@ void CompilerContext::appendInlineAssembly( assembly::AsmAnalyzer analyzer(analysisInfo, errorReporter, false, identifierAccess.resolve); solAssert(analyzer.analyze(*parserResult), "Failed to analyze inline assembly block."); solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block."); - assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess); + assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess, _system); } FunctionDefinition const& CompilerContext::resolveVirtualFunction( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 96cbf6c1115d..7743fd3f017b 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -22,6 +22,8 @@ #pragma once +#include + #include #include #include @@ -56,7 +58,9 @@ class CompilerContext m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); } + /// Update currently enabled set of experimental features. void setExperimentalFeatures(std::set const& _features) { m_experimentalFeatures = _features; } + /// @returns true if the given feature is enabled. bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); } void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); @@ -78,13 +82,15 @@ class CompilerContext /// @returns the entry label of the given function. Might return an AssemblyItem of type /// UndefinedItem if it does not exist yet. eth::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const; - void setInheritanceHierarchy(std::vector const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } /// @returns the entry label of the given function and takes overrides into account. FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function); /// @returns the function that overrides the given declaration from the most derived class just /// above _base in the current inheritance hierarchy. FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base); + /// @returns the next constructor in the inheritance hierarchy. FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const; + /// Sets the current inheritance hierarchy from derived to base. + void setInheritanceHierarchy(std::vector const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } /// @returns the next function in the queue of functions that are still to be compiled /// (i.e. that were referenced during compilation but where we did not yet generate code for). @@ -117,6 +123,7 @@ class CompilerContext ); /// Generates the code for missing low-level functions, i.e. calls the generators passed above. void appendMissingLowLevelFunctions(); + ABIFunctions& abiFunctions() { return m_abiFunctions; } ModifierDefinition const& functionModifier(std::string const& _name) const; /// Returns the distance of the given local variable from the bottom of the stack (of the current function). @@ -152,9 +159,12 @@ class CompilerContext eth::AssemblyItem pushNewTag() { return m_asm->append(m_asm->newPushTag()).tag(); } /// @returns a new tag without pushing any opcodes or data eth::AssemblyItem newTag() { return m_asm->newTag(); } + /// @returns a new tag identified by name. + eth::AssemblyItem namedTag(std::string const& _name) { return m_asm->namedTag(_name); } /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) /// on the stack. @returns the pushsub assembly item. eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { return m_asm->appendSubroutine(_assembly); } + /// Pushes the size of the subroutine. void pushSubroutineSize(size_t _subRoutine) { m_asm->pushSubroutineSize(_subRoutine); } /// Pushes the offset of the subroutine. void pushSubroutineOffset(size_t _subRoutine) { m_asm->pushSubroutineOffset(_subRoutine); } @@ -180,15 +190,17 @@ class CompilerContext /// Appends inline assembly. @a _replacements are string-matching replacements that are performed /// prior to parsing the inline assembly. /// @param _localVariables assigns stack positions to variables with the last one being the stack top + /// @param _system if true, this is a "system-level" assembly where all functions use named labels. void appendInlineAssembly( std::string const& _assembly, std::vector const& _localVariables = std::vector(), - std::map const& _replacements = std::map{} + bool _system = false ); /// Appends arbitrary data to the end of the bytecode. void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); } + /// Run optimisation step. void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, true, _runs); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. @@ -196,16 +208,22 @@ class CompilerContext /// @returns the identifier of the runtime subroutine. size_t runtimeSub() const { return m_runtimeSub; } + /// @returns a const reference to the underlying assembly. eth::Assembly const& assembly() const { return *m_asm; } /// @returns non-const reference to the underlying assembly. Should be avoided in favour of /// wrappers in this class. eth::Assembly& nonConstAssembly() { return *m_asm; } /// @arg _sourceCodes is the map of input files to source code strings - /// @arg _inJsonFormat shows whether the out should be in Json format - Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const + std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const + { + return m_asm->assemblyString(_sourceCodes); + } + + /// @arg _sourceCodes is the map of input files to source code strings + Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const { - return m_asm->stream(_stream, "", _sourceCodes, _inJsonFormat); + return m_asm->assemblyJSON(_sourceCodes); } eth::LinkerObject const& assembledObject() const { return m_asm->assemble(); } @@ -287,6 +305,8 @@ class CompilerContext size_t m_runtimeSub = -1; /// An index of low-level function labels by name. std::map m_lowLevelFunctions; + /// Container for ABI functions to be generated. + ABIFunctions m_abiFunctions; /// The queue of low-level functions to generate. std::queue>> m_lowLevelFunctionGenerationQueue; }; diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index a0fc5d55e640..37aa1aea685e 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -121,7 +121,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound { if (auto ref = dynamic_cast(&_type)) { - solAssert(ref->location() == DataLocation::Memory, ""); + solUnimplementedAssert(ref->location() == DataLocation::Memory, ""); storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries); } else if (auto str = dynamic_cast(&_type)) @@ -310,18 +310,13 @@ void CompilerUtils::abiEncode( { // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart> - vector variables; - size_t numValues = sizeOnStack(_givenTypes); - for (size_t i = 0; i < numValues; ++i) - variables.push_back("$value" + to_string(i)); - variables.push_back("$headStart"); - - ABIFunctions funs; - string routine = funs.tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes); - routine += funs.requestedFunctions(); - m_context.appendInlineAssembly("{" + routine + "}", variables); - // Remove everyhing except for "value0" / the final memory pointer. - popStackSlots(numValues); + auto ret = m_context.pushNewTag(); + moveIntoStack(sizeOnStack(_givenTypes) + 1); + + string encoderName = m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes); + m_context.appendJumpTo(m_context.namedTag(encoderName)); + m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1); + m_context << ret.tag(); } void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) @@ -829,6 +824,7 @@ void CompilerUtils::convertType( break; } } + // fall-through default: // All other types should not be convertible to non-equal types. solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 18b70250084d..5e45699be9f6 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -38,14 +38,20 @@ class CompilerUtils /// Stores the initial value of the free-memory-pointer at its position; void initialiseFreeMemoryPointer(); /// Copies the free memory pointer to the stack. + /// Stack pre: + /// Stack post: void fetchFreeMemoryPointer(); /// Stores the free memory pointer from the stack. + /// Stack pre: + /// Stack post: void storeFreeMemoryPointer(); /// Allocates a number of bytes in memory as given on the stack. /// Stack pre: /// Stack post: void allocateMemory(); /// Appends code that transforms memptr to (memptr - free_memptr) memptr + /// Stack pre: + /// Stack post: void toSizeAfterFreeMemoryPointer(); /// Loads data from memory to the stack. @@ -105,6 +111,8 @@ class CompilerUtils /// Special case of @a encodeToMemory which assumes that everything is padded to words /// and dynamic data is not copied in place (i.e. a proper ABI encoding). + /// Stack pre: ... + /// Stack post: void abiEncode( TypePointers const& _givenTypes, TypePointers const& _targetTypes, @@ -185,9 +193,13 @@ class CompilerUtils static unsigned sizeOnStack(std::vector> const& _variableTypes); /// Helper function to shift top value on the stack to the left. + /// Stack pre: + /// Stack post: void leftShiftNumberOnStack(unsigned _bits); /// Helper function to shift top value on the stack to the right. + /// Stack pre: + /// Stack post: void rightShiftNumberOnStack(unsigned _bits, bool _isSigned = false); /// Appends code that computes tha Keccak-256 hash of the topmost stack element of 32 byte type. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index e53f1b94ed9e..92782b8d7dcc 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -39,6 +39,9 @@ using namespace std; using namespace dev; using namespace dev::solidity; +namespace +{ + /** * Simple helper class to ensure that the stack height is the same at certain places in the code. */ @@ -53,6 +56,8 @@ class StackHeightChecker unsigned stackHeight; }; +} + void ContractCompiler::compileContract( ContractDefinition const& _contract, std::map const& _contracts @@ -117,6 +122,7 @@ void ContractCompiler::appendCallValueCheck() void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) { + CompilerContext::LocationSetter locationSetter(m_context, _contract); // Determine the arguments that are used for the base constructors. std::vector const& bases = _contract.annotation().linearizedBaseContracts; for (ContractDefinition const* contract: bases) @@ -169,6 +175,7 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont appendMissingFunctions(); m_runtimeCompiler->appendMissingFunctions(); + CompilerContext::LocationSetter locationSetter(m_context, _contract); m_context << deployRoutine; solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); @@ -326,7 +333,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter { // stack: v1 v2 ... v(k-1) base_offset current_offset TypePointer type = parameterType->decodingType(); - solAssert(type, "No decoding type found."); + solUnimplementedAssert(type, "No decoding type found."); if (type->category() == Type::Category::Array) { auto const& arrayType = dynamic_cast(*type); @@ -887,6 +894,9 @@ void ContractCompiler::appendMissingFunctions() solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?"); } m_context.appendMissingLowLevelFunctions(); + string abiFunctions = m_context.abiFunctions().requestedFunctions(); + if (!abiFunctions.empty()) + m_context.appendInlineAssembly("{" + move(abiFunctions) + "}", {}, true); } void ContractCompiler::appendModifierOrFunctionCode() diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 38c1e0454360..7c5ee59f7a0c 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -96,8 +96,8 @@ class ContractCompiler: private ASTConstVisitor virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; virtual bool visit(ForStatement const& _forStatement) override; - virtual bool visit(Continue const& _continue) override; - virtual bool visit(Break const& _break) override; + virtual bool visit(Continue const& _continueStatement) override; + virtual bool visit(Break const& _breakStatement) override; virtual bool visit(Return const& _return) override; virtual bool visit(Throw const& _throw) override; virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 639bfc324f97..c94baa1078fc 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -644,8 +644,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) strings(), FunctionType::Kind::BareCall, false, - nullptr, StateMutability::NonPayable, + nullptr, true, true ), @@ -1047,6 +1047,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) if (!alsoSearchInteger) break; } + // fall-through case Type::Category::Integer: if (member == "balance") { @@ -1067,7 +1068,14 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(false, "Invalid member access to integer"); break; case Type::Category::Function: - solAssert(!!_memberAccess.expression().annotation().type->memberType(member), + if (member == "selector") + { + m_context << Instruction::SWAP1 << Instruction::POP; + /// need to store store it as bytes4 + utils().leftShiftNumberOnStack(224); + } + else + solAssert(!!_memberAccess.expression().annotation().type->memberType(member), "Invalid member access to function."); break; case Type::Category::Magic: @@ -1811,7 +1819,7 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression) setLValue(_expression, *_expression.annotation().type); } -bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op) const +bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op) { if (Token::isCompareOp(_op) || Token::isShiftOp(_op)) return true; diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 5f6c3d64dd8d..cdfa096ea28b 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -119,7 +119,7 @@ class ExpressionCompiler: private ASTConstVisitor /// @returns true if the operator applied to the given type requires a cleanup prior to the /// operation. - bool cleanupNeededForOp(Type::Category _type, Token::Value _op) const; + static bool cleanupNeededForOp(Type::Category _type, Token::Value _op); /// @returns the CompilerUtils object containing the current context. CompilerUtils utils(); diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h index b8dac36628bc..63188acd51cd 100644 --- a/libsolidity/formal/SMTLib2Interface.h +++ b/libsolidity/formal/SMTLib2Interface.h @@ -41,7 +41,7 @@ namespace smt class SMTLib2Interface: public SolverInterface, public boost::noncopyable { public: - SMTLib2Interface(ReadCallback::Callback const& _queryCallback); + explicit SMTLib2Interface(ReadCallback::Callback const& _queryCallback); void reset() override; diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 32d92a2a6b2c..70dc1585321f 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -56,10 +56,10 @@ class Expression Expression(u256 const& _number): name(_number.str()) {} Expression(bigint const& _number): name(_number.str()) {} - Expression(Expression const& _other) = default; - Expression(Expression&& _other) = default; - Expression& operator=(Expression const& _other) = default; - Expression& operator=(Expression&& _other) = default; + Expression(Expression const&) = default; + Expression(Expression&&) = default; + Expression& operator=(Expression const&) = default; + Expression& operator=(Expression&&) = default; static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue) { diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index 76b0bbd543b1..e5bdc90f928c 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -163,11 +163,25 @@ bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) { + int const expectedItems = _assignment.variableNames.size(); + solAssert(expectedItems >= 1, ""); int const stackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, *_assignment.value); - solAssert(m_stackHeight >= stackHeight, "Negative value size."); - if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight)) - success = false; + if ((m_stackHeight - stackHeight) != expectedItems) + { + m_errorReporter.declarationError( + _assignment.location, + "Variable count does not match number of values (" + + to_string(expectedItems) + + " vs. " + + to_string(m_stackHeight - stackHeight) + + ")" + ); + return false; + } + for (auto const& variableName: _assignment.variableNames) + if (!checkAssignment(variableName, 1)) + success = false; m_info.stackHeightInfo[&_assignment] = m_stackHeight; return success; } diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 6d0c02558475..dded9f768102 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -83,6 +83,10 @@ class EthAssemblyAdapter: public julia::AbstractAssembly { return assemblyTagToIdentifier(m_assembly.newTag()); } + virtual size_t namedLabel(std::string const& _name) override + { + return assemblyTagToIdentifier(m_assembly.namedTag(_name)); + } virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override { m_assembly.appendLibraryAddress(_linkerSymbol); @@ -141,9 +145,17 @@ void assembly::CodeGenerator::assemble( Block const& _parsedData, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly, - julia::ExternalIdentifierAccess const& _identifierAccess + julia::ExternalIdentifierAccess const& _identifierAccess, + bool _useNamedLabelsForFunctions ) { EthAssemblyAdapter assemblyAdapter(_assembly); - julia::CodeTransform(assemblyAdapter, _analysisInfo, false, false, _identifierAccess)(_parsedData); + julia::CodeTransform( + assemblyAdapter, + _analysisInfo, + false, + false, + _identifierAccess, + _useNamedLabelsForFunctions + )(_parsedData); } diff --git a/libsolidity/inlineasm/AsmCodeGen.h b/libsolidity/inlineasm/AsmCodeGen.h index 2a36a590e0b6..a7d7ead1ae69 100644 --- a/libsolidity/inlineasm/AsmCodeGen.h +++ b/libsolidity/inlineasm/AsmCodeGen.h @@ -46,7 +46,8 @@ class CodeGenerator Block const& _parsedData, AsmAnalysisInfo& _analysisInfo, eth::Assembly& _assembly, - julia::ExternalIdentifierAccess const& _identifierAccess = julia::ExternalIdentifierAccess() + julia::ExternalIdentifierAccess const& _identifierAccess = julia::ExternalIdentifierAccess(), + bool _useNamedLabelsForFunctions = false ); }; diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index db5840bc9073..b0dd85ca8747 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -54,7 +54,11 @@ struct Label { SourceLocation location; std::string name; }; struct StackAssignment { SourceLocation location; Identifier variableName; }; /// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand /// side and requires x to occupy exactly one stack slot. -struct Assignment { SourceLocation location; Identifier variableName; std::shared_ptr value; }; +/// +/// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy +/// a single stack slot and expects a single expression on the right hand returning +/// the same amount of items as the number of variables. +struct Assignment { SourceLocation location; std::vector variableNames; std::shared_ptr value; }; /// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector arguments; }; struct FunctionCall { SourceLocation location; Identifier functionName; std::vector arguments; }; diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index d84fe999c929..3087ad864813 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -122,6 +122,34 @@ assembly::Statement Parser::parseStatement() { case Token::LParen: return parseCall(std::move(statement)); + case Token::Comma: + { + // if a comma follows, a multiple assignment is assumed + + if (statement.type() != typeid(assembly::Identifier)) + fatalParserError("Label name / variable name must precede \",\" (multiple assignment)."); + assembly::Identifier const& identifier = boost::get(statement); + + Assignment assignment = createWithLocation(identifier.location); + assignment.variableNames.emplace_back(identifier); + + do + { + expectToken(Token::Comma); + statement = parseElementaryOperation(false); + if (statement.type() != typeid(assembly::Identifier)) + fatalParserError("Variable name expected in multiple assignemnt."); + assignment.variableNames.emplace_back(boost::get(statement)); + } + while (currentToken() == Token::Comma); + + expectToken(Token::Colon); + expectToken(Token::Assign); + + assignment.value.reset(new Statement(parseExpression())); + assignment.location.end = locationOf(*assignment.value).end; + return assignment; + } case Token::Colon: { if (statement.type() != typeid(assembly::Identifier)) @@ -136,7 +164,7 @@ assembly::Statement Parser::parseStatement() if (!m_julia && instructions().count(identifier.name)) fatalParserError("Cannot use instruction names for identifier names."); advance(); - assignment.variableName = identifier; + assignment.variableNames.emplace_back(identifier); assignment.value.reset(new Statement(parseExpression())); assignment.location.end = locationOf(*assignment.value).end; return assignment; diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index 47ede91d4174..a52728084984 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -116,7 +116,11 @@ string AsmPrinter::operator()(assembly::StackAssignment const& _assignment) string AsmPrinter::operator()(assembly::Assignment const& _assignment) { - return (*this)(_assignment.variableName) + " := " + boost::apply_visitor(*this, *_assignment.value); + solAssert(_assignment.variableNames.size() >= 1, ""); + string variables = (*this)(_assignment.variableNames.front()); + for (size_t i = 1; i < _assignment.variableNames.size(); ++i) + variables += ", " + (*this)(_assignment.variableNames[i]); + return variables + " := " + boost::apply_visitor(*this, *_assignment.value); } string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration) diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index 3df9d1f8fde3..aefb34afa848 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -32,13 +32,14 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) for (auto it: _contractDef.interfaceFunctions()) { auto externalFunctionType = it.second->interfaceFunctionType(); + solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = "function"; method["name"] = it.second->declaration().name(); // TODO: deprecate constant in a future release - method["constant"] = it.second->stateMutability() == StateMutability::Pure || it.second->stateMutability() == StateMutability::View; - method["payable"] = it.second->isPayable(); - method["stateMutability"] = stateMutabilityToString(it.second->stateMutability()); + method["constant"] = externalFunctionType->stateMutability() == StateMutability::Pure || it.second->stateMutability() == StateMutability::View; + method["payable"] = externalFunctionType->isPayable(); + method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability()); method["inputs"] = formatTypeList( externalFunctionType->parameterNames(), externalFunctionType->parameterTypes(), @@ -53,15 +54,15 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) } if (_contractDef.constructor()) { + auto externalFunctionType = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); + solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = "constructor"; - auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); - solAssert(!!externalFunction, ""); - method["payable"] = externalFunction->isPayable(); - method["stateMutability"] = stateMutabilityToString(externalFunction->stateMutability()); + method["payable"] = externalFunctionType->isPayable(); + method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability()); method["inputs"] = formatTypeList( - externalFunction->parameterNames(), - externalFunction->parameterTypes(), + externalFunctionType->parameterNames(), + externalFunctionType->parameterTypes(), _contractDef.isLibrary() ); abi.append(method); @@ -85,12 +86,12 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) Json::Value params(Json::arrayValue); for (auto const& p: it->parameters()) { - solAssert(!!p->annotation().type->interfaceType(false), ""); + auto type = p->annotation().type->interfaceType(false); + solAssert(type, ""); Json::Value input; - input["name"] = p->name(); - input["type"] = p->annotation().type->interfaceType(false)->canonicalName(false); - input["indexed"] = p->isIndexed(); - params.append(input); + auto param = formatType(p->name(), *type, false); + param["indexed"] = p->isIndexed(); + params.append(param); } event["inputs"] = params; abi.append(event); @@ -110,10 +111,53 @@ Json::Value ABI::formatTypeList( for (unsigned i = 0; i < _names.size(); ++i) { solAssert(_types[i], ""); - Json::Value param; - param["name"] = _names[i]; - param["type"] = _types[i]->canonicalName(_forLibrary); - params.append(param); + params.append(formatType(_names[i], *_types[i], _forLibrary)); } return params; } + +Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLibrary) +{ + Json::Value ret; + ret["name"] = _name; + string suffix = (_forLibrary && _type.dataStoredIn(DataLocation::Storage)) ? " storage" : ""; + if (_type.isValueType() || (_forLibrary && _type.dataStoredIn(DataLocation::Storage))) + ret["type"] = _type.canonicalName() + suffix; + else if (ArrayType const* arrayType = dynamic_cast(&_type)) + { + if (arrayType->isByteArray()) + ret["type"] = _type.canonicalName() + suffix; + else + { + string suffix; + if (arrayType->isDynamicallySized()) + suffix = "[]"; + else + suffix = string("[") + arrayType->length().str() + "]"; + solAssert(arrayType->baseType(), ""); + Json::Value subtype = formatType("", *arrayType->baseType(), _forLibrary); + if (subtype.isMember("components")) + { + ret["type"] = subtype["type"].asString() + suffix; + ret["components"] = subtype["components"]; + } + else + ret["type"] = subtype["type"].asString() + suffix; + } + } + else if (StructType const* structType = dynamic_cast(&_type)) + { + ret["type"] = "tuple"; + ret["components"] = Json::arrayValue; + for (auto const& member: structType->members(nullptr)) + { + solAssert(member.type, ""); + auto t = member.type->interfaceType(_forLibrary); + solAssert(t, ""); + ret["components"].append(formatType(member.name, *t, _forLibrary)); + } + } + else + solAssert(false, "Invalid type."); + return ret; +} diff --git a/libsolidity/interface/ABI.h b/libsolidity/interface/ABI.h index 95b162a9572a..db70729db5e7 100644 --- a/libsolidity/interface/ABI.h +++ b/libsolidity/interface/ABI.h @@ -50,6 +50,10 @@ class ABI std::vector const& _types, bool _forLibrary ); + /// @returns a Json object with "name", "type" and potentially "components" keys, according + /// to the ABI specification. + /// If it is possible to express the type as a single string, it is allowed to return a single string. + static Json::Value formatType(std::string const& _name, Type const& _type, bool _forLibrary); }; } diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index 23524bb3fcd3..504ad92c46fe 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -91,9 +91,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const eth::Assembly assembly; assembly::CodeGenerator::assemble(*m_parserResult, *m_analysisInfo, assembly); object.bytecode = make_shared(assembly.assemble()); - ostringstream tmp; - assembly.stream(tmp); - object.assembly = tmp.str(); + object.assembly = assembly.assemblyString(); return object; } case Machine::EVM15: diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 363f45ddde73..51544f8a5b9c 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -197,30 +198,13 @@ bool CompilerStack::analyze() m_contracts[contract->fullyQualifiedName()].contract = contract; } + TypeChecker typeChecker(m_errorReporter); for (Source const* source: m_sourceOrder) for (ASTPointer const& node: source->ast->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) - { - m_globalContext->setCurrentContract(*contract); - resolver.updateDeclaration(*m_globalContext->currentThis()); - TypeChecker typeChecker(m_errorReporter); - if (typeChecker.checkTypeRequirements(*contract)) - { - contract->setDevDocumentation(Natspec::devDocumentation(*contract)); - contract->setUserDocumentation(Natspec::userDocumentation(*contract)); - } - else + if (!typeChecker.checkTypeRequirements(*contract)) noErrors = false; - // Note that we now reference contracts by their fully qualified names, and - // thus contracts can only conflict if declared in the same source file. This - // already causes a double-declaration error elsewhere, so we do not report - // an error here and instead silently drop any additional contracts we find. - - if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end()) - m_contracts[contract->fullyQualifiedName()].contract = contract; - } - if (noErrors) { PostTypeChecker postTypeChecker(m_errorReporter); @@ -237,6 +221,16 @@ bool CompilerStack::analyze() noErrors = false; } + if (noErrors) + { + vector> ast; + for (Source const* source: m_sourceOrder) + ast.push_back(source->ast); + + if (!ViewPureChecker(ast, m_errorReporter).check()) + noErrors = false; + } + if (noErrors) { SMTChecker smtChecker(m_errorReporter, m_smtQuery); @@ -364,16 +358,24 @@ eth::LinkerObject const& CompilerStack::cloneObject(string const& _contractName) return contract(_contractName).cloneObject; } -Json::Value CompilerStack::streamAssembly(ostream& _outStream, string const& _contractName, StringMap _sourceCodes, bool _inJsonFormat) const +/// FIXME: cache this string +string CompilerStack::assemblyString(string const& _contractName, StringMap _sourceCodes) const { Contract const& currentContract = contract(_contractName); if (currentContract.compiler) - return currentContract.compiler->streamAssembly(_outStream, _sourceCodes, _inJsonFormat); + return currentContract.compiler->assemblyString(_sourceCodes); + else + return string(); +} + +/// FIXME: cache the JSON +Json::Value CompilerStack::assemblyJSON(string const& _contractName, StringMap _sourceCodes) const +{ + Contract const& currentContract = contract(_contractName); + if (currentContract.compiler) + return currentContract.compiler->assemblyJSON(_sourceCodes); else - { - _outStream << "Contract not fully implemented" << endl; return Json::Value(); - } } vector CompilerStack::sourceNames() const diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 361b8a45ea9a..f1bbae47e668 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -116,6 +116,9 @@ class CompilerStack: boost::noncopyable m_optimizeRuns = _runs; } + /// @arg _metadataLiteralSources When true, store sources as literals in the contract metadata. + void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } + /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. /// @returns true if a source object by the name already existed and was replaced. bool addSource(std::string const& _name, std::string const& _content, bool _isLibrary = false); @@ -125,7 +128,7 @@ class CompilerStack: boost::noncopyable bool parse(); /// Performs the analysis steps (imports, scopesetting, syntaxCheck, referenceResolving, - /// typechecking, staticAnalysis) on previously set sources + /// typechecking, staticAnalysis) on previously parsed sources. /// @returns false on error. bool analyze(); @@ -133,9 +136,6 @@ class CompilerStack: boost::noncopyable /// @returns false on error. bool parseAndAnalyze(); - /// @returns a list of the contract names in the sources. - std::vector contractNames() const; - /// Compiles the source units that were previously added and parsed. /// @returns false on error. bool compile(); @@ -158,6 +158,9 @@ class CompilerStack: boost::noncopyable /// start line, start column, end line, end column std::tuple positionFromSourceLocation(SourceLocation const& _sourceLocation) const; + /// @returns a list of the contract names in the sources. + std::vector contractNames() const; + /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use std::string const filesystemFriendlyName(std::string const& _contractName) const; @@ -187,11 +190,15 @@ class CompilerStack: boost::noncopyable /// if the contract does not (yet) have bytecode. std::string const* runtimeSourceMapping(std::string const& _contractName = "") const; - /// Streams a verbose version of the assembly to @a _outStream. + /// @return a verbose text representation of the assembly. + /// @arg _sourceCodes is the map of input files to source code strings + /// Prerequisite: Successful compilation. + std::string assemblyString(std::string const& _contractName = "", StringMap _sourceCodes = StringMap()) const; + + /// @returns a JSON representation of the assembly. /// @arg _sourceCodes is the map of input files to source code strings - /// @arg _inJsonFromat shows whether the out should be in Json format /// Prerequisite: Successful compilation. - Json::Value streamAssembly(std::ostream& _outStream, std::string const& _contractName = "", StringMap _sourceCodes = StringMap(), bool _inJsonFormat = false) const; + Json::Value assemblyJSON(std::string const& _contractName = "", StringMap _sourceCodes = StringMap()) const; /// @returns a JSON representing the contract ABI. /// Prerequisite: Successful call to parse or compile. @@ -210,7 +217,6 @@ class CompilerStack: boost::noncopyable /// @returns the Contract Metadata std::string const& metadata(std::string const& _contractName) const; - void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions Json::Value gasEstimates(std::string const& _contractName) const; diff --git a/libsolidity/interface/ErrorReporter.h b/libsolidity/interface/ErrorReporter.h index 241d6b438bfd..a87db21d3f8f 100644 --- a/libsolidity/interface/ErrorReporter.h +++ b/libsolidity/interface/ErrorReporter.h @@ -39,6 +39,9 @@ class ErrorReporter explicit ErrorReporter(ErrorList& _errors): m_errorList(_errors) { } + ErrorReporter(ErrorReporter const& _errorReporter) noexcept: + m_errorList(_errorReporter.m_errorList) { } + ErrorReporter& operator=(ErrorReporter const& _errorReporter); void warning(std::string const& _description); @@ -83,7 +86,7 @@ class ErrorReporter void fatalTypeError(SourceLocation const& _location, std::string const& _description); - void docstringParsingError(std::string const& _location); + void docstringParsingError(std::string const& _description); ErrorList const& errors() const; diff --git a/libsolidity/interface/Natspec.h b/libsolidity/interface/Natspec.h index 9ac3efea5063..0701f821966b 100644 --- a/libsolidity/interface/Natspec.h +++ b/libsolidity/interface/Natspec.h @@ -36,27 +36,8 @@ namespace solidity // Forward declarations class ContractDefinition; -class Type; -using TypePointer = std::shared_ptr; struct DocTag; -enum class DocTagType: uint8_t -{ - None = 0, - Dev, - Notice, - Param, - Return, - Author, - Title -}; - -enum class CommentOwner -{ - Contract, - Function -}; - class Natspec { public: @@ -71,14 +52,6 @@ class Natspec static Json::Value devDocumentation(ContractDefinition const& _contractDef); private: - /// @returns a json value suitable for a list of types in function input or output - /// parameters or other places. If @a _forLibrary is true, complex types are referenced - /// by name, otherwise they are anonymously expanded. - static Json::Value formatTypeList( - std::vector const& _names, - std::vector const& _types, - bool _forLibrary - ); /// @returns concatenation of all content under the given tag name. static std::string extractDoc(std::multimap const& _tags, std::string const& _name); }; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index be823743fead..b4fbbef9b12b 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -400,10 +400,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) // EVM Json::Value evmData(Json::objectValue); // @TODO: add ir - ostringstream tmp; - m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), false); - evmData["assembly"] = tmp.str(); - evmData["legacyAssembly"] = m_compilerStack.streamAssembly(tmp, contractName, createSourceList(_input), true); + evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); + evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index ddfdb6672e3b..ce8a9f0113f4 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -903,11 +903,13 @@ ASTPointer Parser::parseStatement() { statement = ASTNodeFactory(*this).createNode(docString); m_scanner->next(); - break; } - // fall-through + else + statement = parseSimpleStatement(docString); + break; default: statement = parseSimpleStatement(docString); + break; } expectToken(Token::Semicolon); return statement; @@ -1242,7 +1244,10 @@ ASTPointer Parser::parseLeftHandSideExpression( { expectToken(Token::New); ASTPointer typeName(parseTypeName(false)); - nodeFactory.setEndPositionFromNode(typeName); + if (typeName) + nodeFactory.setEndPositionFromNode(typeName); + else + nodeFactory.markEndPosition(); expression = nodeFactory.createNode(typeName); } else @@ -1261,15 +1266,15 @@ ASTPointer Parser::parseLeftHandSideExpression( nodeFactory.markEndPosition(); expectToken(Token::RBrack); expression = nodeFactory.createNode(expression, index); + break; } - break; case Token::Period: { m_scanner->next(); nodeFactory.markEndPosition(); expression = nodeFactory.createNode(expression, expectIdentifierToken()); + break; } - break; case Token::LParen: { m_scanner->next(); @@ -1279,8 +1284,8 @@ ASTPointer Parser::parseLeftHandSideExpression( nodeFactory.markEndPosition(); expectToken(Token::RParen); expression = nodeFactory.createNode(expression, arguments, names); + break; } - break; default: return expression; } @@ -1309,18 +1314,21 @@ ASTPointer Parser::parsePrimaryExpression() Literal::SubDenomination subdenomination = static_cast(m_scanner->currentToken()); m_scanner->next(); expression = nodeFactory.createNode(token, literal, subdenomination); - break; } - if (Token::isTimeSubdenomination(m_scanner->peekNextToken())) + else if (Token::isTimeSubdenomination(m_scanner->peekNextToken())) { ASTPointer literal = getLiteralAndAdvance(); nodeFactory.markEndPosition(); Literal::SubDenomination subdenomination = static_cast(m_scanner->currentToken()); m_scanner->next(); expression = nodeFactory.createNode(token, literal, subdenomination); - break; } - // fall-through + else + { + nodeFactory.markEndPosition(); + expression = nodeFactory.createNode(token, getLiteralAndAdvance()); + } + break; case Token::StringLiteral: nodeFactory.markEndPosition(); expression = nodeFactory.createNode(token, getLiteralAndAdvance()); @@ -1357,9 +1365,9 @@ ASTPointer Parser::parsePrimaryExpression() } nodeFactory.markEndPosition(); expectToken(oppositeToken); - return nodeFactory.createNode(components, isArray); + expression = nodeFactory.createNode(components, isArray); + break; } - default: if (Token::isElementaryTypeName(token)) { diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp index fe95b0feea98..5b83c5bdc171 100644 --- a/libsolidity/parsing/ParserBase.cpp +++ b/libsolidity/parsing/ParserBase.cpp @@ -104,7 +104,7 @@ void ParserBase::expectToken(Token::Value _value) void ParserBase::increaseRecursionDepth() { m_recursionDepth++; - if (m_recursionDepth >= 3000) + if (m_recursionDepth >= 2560) fatalParserError("Maximum recursion depth reached during parsing."); } diff --git a/libsolidity/parsing/Scanner.cpp b/libsolidity/parsing/Scanner.cpp index fdca23ea165e..6541f6c2ff5b 100644 --- a/libsolidity/parsing/Scanner.cpp +++ b/libsolidity/parsing/Scanner.cpp @@ -435,7 +435,7 @@ void Scanner::scanToken() m_nextToken.location.start = sourcePos(); switch (m_char) { - case '\n': // fall-through + case '\n': case ' ': case '\t': token = selectToken(Token::Whitespace); diff --git a/lllc/main.cpp b/lllc/main.cpp index adf181c7d2e6..06a0fc81b784 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -39,7 +39,7 @@ static string const VersionString = (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + (string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO)); -void help() +static void help() { cout << "Usage lllc [OPTIONS] " << endl @@ -54,7 +54,7 @@ void help() exit(0); } -void version() +static void version() { cout << "LLLC, the Lovely Little Language Compiler " << endl; cout << "Version: " << VersionString << endl; @@ -74,7 +74,7 @@ specified default locale if it is valid, and if not then it will modify the environment the process is running in to use a sensible default. This also means that users do not need to install language packs for their OS. */ -void setDefaultOrCLocale() +static void setDefaultOrCLocale() { #if __unix__ if (!std::setlocale(LC_ALL, "")) diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index 6046978e54bb..cddcd4f8636c 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -30,5 +30,5 @@ set -e if [[ "$OSTYPE" != "darwin"* ]]; then ./scripts/travis-emscripten/install_deps.sh - docker run -v $(pwd):/src trzeci/emscripten:sdk-tag-1.35.4-64bit ./scripts/travis-emscripten/build_emscripten.sh + docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.35.4-64bit ./scripts/travis-emscripten/build_emscripten.sh fi diff --git a/scripts/install_deps.bat b/scripts/install_deps.bat index 512a28dfa9ad..d02005ccd0f8 100644 --- a/scripts/install_deps.bat +++ b/scripts/install_deps.bat @@ -58,4 +58,4 @@ REM REM Copyright (c) 2016 solidity contributors. REM --------------------------------------------------------------------------- -cmake -P deps\install_deps.cmake +cmake -P scripts\install_deps.cmake diff --git a/scripts/install_deps.cmake b/scripts/install_deps.cmake new file mode 100644 index 000000000000..d1284b9e5672 --- /dev/null +++ b/scripts/install_deps.cmake @@ -0,0 +1,99 @@ +get_filename_component(ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../deps" ABSOLUTE) + +set(CACHE_DIR "${ROOT_DIR}/cache") +set(PACKAGES_DIR "${ROOT_DIR}/packages") + +function(download URL DST_FILE STATUS) + set(TMP_FILE "${DST_FILE}.part") + + get_filename_component(FILE_NAME ${DST_FILE} NAME) + if (NOT EXISTS ${DST_FILE}) + message("Downloading ${FILE_NAME}") + file(DOWNLOAD ${URL} ${TMP_FILE} SHOW_PROGRESS STATUS DOWNLOAD_STATUS) + list(GET DOWNLOAD_STATUS 0 STATUS_CODE) + if (STATUS_CODE EQUAL 0) + file(RENAME ${TMP_FILE} ${DST_FILE}) + else() + file(REMOVE ${TMP_FILE}) + list(GET DOWNLOAD_STATUS 1 ERROR_MSG) + + message("ERROR! Downloading '${FILE_NAME}' failed.") + message(STATUS "URL: ${URL}") + message(STATUS "Error: ${STATUS_CODE} ${ERROR_MSG}") + set(STATUS FALSE PARENT_SCOPE) + return() + endif() + else() + message("Using cached ${FILE_NAME}") + endif() + set(STATUS TRUE PARENT_SCOPE) +endfunction(download) + +function(download_and_unpack PACKAGE_URL DST_DIR) + get_filename_component(FILE_NAME ${PACKAGE_URL} NAME) + + set(DST_FILE "${CACHE_DIR}/${FILE_NAME}") + set(TMP_FILE "${DST_FILE}.part") + + file(MAKE_DIRECTORY ${CACHE_DIR}) + file(MAKE_DIRECTORY ${DST_DIR}) + + download(${PACKAGE_URL} ${DST_FILE} STATUS) + + if (STATUS) + message("Unpacking ${FILE_NAME} to ${DST_DIR}") + execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf ${DST_FILE} + WORKING_DIRECTORY ${DST_DIR}) + endif() +endfunction(download_and_unpack) + +# Packs installed package binaries and headers into an archive. +function(create_package NAME DIR) + message("Creating package ${NAME}") + file(MAKE_DIRECTORY ${PACKAGES_DIR}) + + # To create an archive without addicional top level directory + # (like package-X.Y.Z) we need to know all top level files/dirs. + # Usually it is just "win64" dir. + file(GLOB TOP_FILES RELATIVE ${DIR} "${DIR}/*") + + set(PACKAGE_FILE "${PACKAGES_DIR}/${NAME}.tar.gz") + execute_process(COMMAND ${CMAKE_COMMAND} -E + tar -czf ${PACKAGE_FILE} ${TOP_FILES} + WORKING_DIRECTORY ${DIR}) +endfunction(create_package) + +# Downloads the source code of the package and unpacks it to dedicated 'src' +# dir. Also creates 'build' and 'install' dir to be used by a build script. +function(prepare_package_source NAME VERSION URL) + set(PACKAGE_NAME "${NAME}-${VERSION}") + + set(PACKAGE_DIR "${CACHE_DIR}/${PACKAGE_NAME}") + set(SOURCE_DIR "${PACKAGE_DIR}/src") + set(BUILD_DIR "${PACKAGE_DIR}/build") + set(INSTALL_DIR "${PACKAGE_DIR}/install") + + if (NOT EXISTS ${SOURCE_DIR}) + download_and_unpack(${URL} ${PACKAGE_DIR} STATUS) + file(GLOB ORIG_SOURCE_DIR_NAME "${PACKAGE_DIR}/*") + file(RENAME ${ORIG_SOURCE_DIR_NAME} ${SOURCE_DIR}) + endif() + + file(MAKE_DIRECTORY ${BUILD_DIR}) + file(MAKE_DIRECTORY ${INSTALL_DIR}) + + # Export names and dirs to be used by a package-specific build script. + set(PACKAGE_NAME ${PACKAGE_NAME} PARENT_SCOPE) + set(SOURCE_DIR ${SOURCE_DIR} PARENT_SCOPE) + set(BUILD_DIR ${BUILD_DIR} PARENT_SCOPE) + set(INSTALL_DIR ${INSTALL_DIR} PARENT_SCOPE) +endfunction() + +set(INSTALL_DIR "${ROOT_DIR}/install") +set(SERVER "https://github.com/ethereum/cpp-dependencies/releases/download/vc140/") + +function(download_and_install PACKAGE_NAME) + download_and_unpack("${SERVER}${PACKAGE_NAME}.tar.gz" ${INSTALL_DIR}) +endfunction(download_and_install) + +download_and_install("boost-1.61") diff --git a/scripts/release.sh b/scripts/release.sh index a2f4d98af06e..ebc7759f59ce 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -35,47 +35,12 @@ ZIP_TEMP_DIR=$(pwd)/build/zip/ # There is an implicit assumption here that we HAVE to run from root directory. REPO_ROOT=$(pwd) -if [[ "$OSTYPE" == "darwin"* ]]; then - DLL_EXT=dylib -else - DLL_EXT=so -fi - mkdir -p $ZIP_TEMP_DIR # Copy all the solidity executables into a temporary directory prior to ZIP creation cp $REPO_ROOT/build/lllc/lllc $ZIP_TEMP_DIR cp $REPO_ROOT/build/solc/solc $ZIP_TEMP_DIR -cp $REPO_ROOT/build/soltest/soltest $ZIP_TEMP_DIR - -# Copy all the dynamic libraries into a temporary directory prior to ZIP creation. -# There are a lot of these, and it would be great if we didn't have to worry about them. -# There is work-in-progress to support static-linkage on the UNIX platforms, which -# is most promising on Alpine Linux using musl. macOS doesn't support statically -# linked binaries (ie. executables which make direct system calls to the kernel. -# -# See https://developer.apple.com/library/mac/qa/qa1118/_index.html. -# See https://github.com/ethereum/webthree-umbrella/issues/495. - -cp $REPO_ROOT/build/libdevcore/*.$DLL_EXT $ZIP_TEMP_DIR -cp $REPO_ROOT/build/libevmasm/*.$DLL_EXT $ZIP_TEMP_DIR -cp $REPO_ROOT/build/libsolidity/*.$DLL_EXT $ZIP_TEMP_DIR - -# For macOS, we also copy the dynamic libraries for our external dependencies. -# When building from source on your own machine, these libraries will be installed -# globally, using Homebrew, but we don't want to rely on that for these ZIPs, so -# we copy these into the ZIP temporary directory too. -# -# TODO - So what happens for Linux and other UNIX distros in this case? -# There will be runtime dependencies on equivalent SO files being present, likely in -# a completely analogous way. Does that mean that ZIPs are actually useless on such -# distros, because there will be symbol links to global install locations (distro-specific) -# and those files will just be missing on the target machines? - -if [[ "$OSTYPE" == "darwin"* ]]; then - cp /usr/local/opt/jsoncpp/lib/libjsoncpp.1.dylib $ZIP_TEMP_DIR -fi # For macOS, we run a fix-up script which alters all of the symbolic links within # the executables and dynamic libraries such that the ZIP becomes self-contained, by diff --git a/scripts/test_emscripten.sh b/scripts/test_emscripten.sh index f1d44a1fa521..b01b33bb778a 100755 --- a/scripts/test_emscripten.sh +++ b/scripts/test_emscripten.sh @@ -29,28 +29,29 @@ set -e REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) - -cd $REPO_ROOT/build - -echo "Preparing solc-js..." -rm -rf solc-js -git clone https://github.com/ethereum/solc-js -cd solc-js -npm install - -# Replace soljson with current build -echo "Replacing soljson.js" -rm -f soljson.js -# Make a copy because paths might not be absolute -cp ../solc/soljson.js soljson.js - -# Update version (needed for some tests) -VERSION=$(../../scripts/get_version.sh) -echo "Updating package.json to version $VERSION" -npm version $VERSION - -echo "Running solc-js tests..." -npm run test +SOLJSON="$REPO_ROOT/build/solc/soljson.js" + +DIR=$(mktemp -d) +( + echo "Preparing solc-js..." + git clone --depth 1 https://github.com/ethereum/solc-js "$DIR" + cd "$DIR" + npm install + + # Replace soljson with current build + echo "Replacing soljson.js" + rm -f soljson.js + cp "$SOLJSON" soljson.js + + # Update version (needed for some tests) + VERSION=$("$REPO_ROOT/scripts/get_version.sh") + echo "Updating package.json to version $VERSION" + npm version --no-git-tag-version $VERSION + + echo "Running solc-js tests..." + npm run test +) +rm -rf "$DIR" echo "Running external tests...." -"$REPO_ROOT"/test/externalTests.sh "$REPO_ROOT"/build/solc/soljson.js +"$REPO_ROOT/test/externalTests.sh" "$SOLJSON" diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index f92b3c44530a..bf460e8e9504 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -34,11 +34,13 @@ set -ev -# We need git for extracting the commit hash -apt-get update -apt-get -y install git-core +if ! type git &>/dev/null; then + # We need git for extracting the commit hash + apt-get update + apt-get -y install git-core +fi -export WORKSPACE=/src +WORKSPACE=/root/project # Boost echo -en 'travis_fold:start:compiling_boost\\r' @@ -46,11 +48,11 @@ cd "$WORKSPACE"/boost_1_57_0 # if b2 exists, it is a fresh checkout, otherwise it comes from the cache # and is already compiled test -e b2 && ( -sed -i 's|using gcc ;|using gcc : : /usr/local/bin/em++ ;|g' ./project-config.jam -sed -i 's|$(archiver\[1\])|/usr/local/bin/emar|g' ./tools/build/src/tools/gcc.jam -sed -i 's|$(ranlib\[1\])|/usr/local/bin/emranlib|g' ./tools/build/src/tools/gcc.jam +sed -i 's|using gcc ;|using gcc : : em++ ;|g' ./project-config.jam +sed -i 's|$(archiver\[1\])|emar|g' ./tools/build/src/tools/gcc.jam +sed -i 's|$(ranlib\[1\])|emranlib|g' ./tools/build/src/tools/gcc.jam ./b2 link=static variant=release threading=single runtime-link=static \ - thread system regex date_time chrono filesystem unit_test_framework program_options random + system regex filesystem unit_test_framework program_options find . -name 'libboost*.a' -exec cp {} . \; rm -rf b2 libs doc tools more bin.v2 status ) @@ -61,43 +63,34 @@ echo -en 'travis_fold:start:compiling_solidity\\r' cd $WORKSPACE mkdir -p build cd build -emcmake cmake \ +cmake \ + -DCMAKE_TOOLCHAIN_FILE=$EMSCRIPTEN/cmake/Modules/Platform/Emscripten.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DEMSCRIPTEN=1 \ -DBoost_FOUND=1 \ -DBoost_USE_STATIC_LIBS=1 \ -DBoost_USE_STATIC_RUNTIME=1 \ -DBoost_INCLUDE_DIR="$WORKSPACE"/boost_1_57_0/ \ - -DBoost_CHRONO_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_chrono.a \ - -DBoost_CHRONO_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_chrono.a \ - -DBoost_DATE_TIME_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_date_time.a \ - -DBoost_DATE_TIME_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_date_time.a \ -DBoost_FILESYSTEM_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_filesystem.a \ -DBoost_FILESYSTEM_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_filesystem.a \ -DBoost_PROGRAM_OPTIONS_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_program_options.a \ -DBoost_PROGRAM_OPTIONS_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_program_options.a \ - -DBoost_RANDOM_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_random.a \ - -DBoost_RANDOM_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_random.a \ -DBoost_REGEX_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_regex.a \ -DBoost_REGEX_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_regex.a \ -DBoost_SYSTEM_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_system.a \ -DBoost_SYSTEM_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_system.a \ - -DBoost_THREAD_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_thread.a \ - -DBoost_THREAD_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_thread.a \ -DBoost_UNIT_TEST_FRAMEWORK_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_unit_test_framework.a \ -DBoost_UNIT_TEST_FRAMEWORK_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_unit_test_framework.a \ - -DDev_DEVCORE_LIBRARY="$WORKSPACE"/solidity/build/libdevcore/libsoldevcore.a \ - -DEth_EVMASM_LIBRARY="$WORKSPACE"/solidity/build/libevmasm/libsolevmasm.a \ - -DETH_STATIC=1 -DTESTS=0 \ + -DTESTS=0 \ .. -emmake make -j 4 +make -j 4 cd .. -cp build/solc/soljson.js ./ mkdir -p upload -cp soljson.js upload/ +cp build/solc/soljson.js upload/ +cp build/solc/soljson.js ./ -OUTPUT_SIZE=`ls -la build/solc/soljson.js` +OUTPUT_SIZE=`ls -la soljson.js` echo "Emscripten output size: $OUTPUT_SIZE" diff --git a/scripts/travis-emscripten/install_deps.sh b/scripts/travis-emscripten/install_deps.sh index 252c74b07447..45c16a9f8434 100755 --- a/scripts/travis-emscripten/install_deps.sh +++ b/scripts/travis-emscripten/install_deps.sh @@ -31,10 +31,8 @@ set -ev echo -en 'travis_fold:start:installing_dependencies\\r' test -e boost_1_57_0 -a -e boost_1_57_0/boost || ( -wget 'http://downloads.sourceforge.net/project/boost/boost/'\ -'1.57.0/boost_1_57_0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2F'\ -'projects%2Fboost%2Ffiles%2Fboost%2F1.57.0%2F&ts=1421887207'\ - -O - | tar xj +wget 'https://sourceforge.net/projects/boost/files/boost/1.57.0/boost_1_57_0.tar.gz/download'\ + -O - | tar xz cd boost_1_57_0 ./bootstrap.sh --with-toolset=gcc --with-libraries=thread,system,regex,date_time,chrono,filesystem,program_options,random ) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 315f951e7deb..271511d4f611 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -112,10 +112,12 @@ static string const g_strSourceList = "sourceList"; static string const g_strSrcMap = "srcmap"; static string const g_strSrcMapRuntime = "srcmap-runtime"; static string const g_strStandardJSON = "standard-json"; +static string const g_strPrettyJson = "pretty-json"; static string const g_strVersion = "version"; static string const g_argAbi = g_strAbi; static string const g_argAddStandard = g_strAddStandard; +static string const g_argPrettyJson = g_strPrettyJson; static string const g_argAllowPaths = g_strAllowPaths; static string const g_argAsm = g_strAsm; static string const g_argAsmJson = g_strAsmJson; @@ -508,6 +510,11 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da BOOST_THROW_EXCEPTION(FileError() << errinfo_comment("Could not write to file: " + pathName)); } +void CommandLineInterface::createJson(string const& _fileName, string const& _json) +{ + createFile(boost::filesystem::basename(_fileName) + string(".json"), _json); +} + bool CommandLineInterface::parseArguments(int _argc, char** _argv) { // Declare the supported options. @@ -541,6 +548,7 @@ Allowed options)", "Estimated number of contract runs for optimizer tuning." ) (g_argAddStandard.c_str(), "Add standard contracts.") + (g_argPrettyJson.c_str(), "Output JSON in pretty format. Currently it only works with the combined JSON output.") ( g_argLibraries.c_str(), po::value>()->value_name("libs"), @@ -865,10 +873,7 @@ void CommandLineInterface::handleCombinedJSON() if (requests.count(g_strOpcodes)) contractData[g_strOpcodes] = solidity::disassemble(m_compiler->object(contractName).bytecode); if (requests.count(g_strAsm)) - { - ostringstream unused; - contractData[g_strAsm] = m_compiler->streamAssembly(unused, contractName, m_sourceCodes, true); - } + contractData[g_strAsm] = m_compiler->assemblyJSON(contractName, m_sourceCodes); if (requests.count(g_strSrcMap)) { auto map = m_compiler->sourceMapping(contractName); @@ -908,7 +913,13 @@ void CommandLineInterface::handleCombinedJSON() output[g_strSources][sourceCode.first]["AST"] = converter.toJson(m_compiler->ast(sourceCode.first)); } } - cout << dev::jsonCompactPrint(output) << endl; + + string json = m_args.count(g_argPrettyJson) ? dev::jsonPrettyPrint(output) : dev::jsonCompactPrint(output); + + if (m_args.count(g_argOutputDir)) + createJson("combined", json); + else + cout << json << endl; } void CommandLineInterface::handleAst(string const& _argStr) @@ -1150,16 +1161,19 @@ void CommandLineInterface::outputCompilationResults() // do we need EVM assembly? if (m_args.count(g_argAsm) || m_args.count(g_argAsmJson)) { + string ret; + if (m_args.count(g_argAsmJson)) + ret = dev::jsonPrettyPrint(m_compiler->assemblyJSON(contract, m_sourceCodes)); + else + ret = m_compiler->assemblyString(contract, m_sourceCodes); + if (m_args.count(g_argOutputDir)) { - stringstream data; - m_compiler->streamAssembly(data, contract, m_sourceCodes, m_args.count(g_argAsmJson)); - createFile(m_compiler->filesystemFriendlyName(contract) + (m_args.count(g_argAsmJson) ? "_evm.json" : ".evm"), data.str()); + createFile(m_compiler->filesystemFriendlyName(contract) + (m_args.count(g_argAsmJson) ? "_evm.json" : ".evm"), ret); } else { - cout << "EVM assembly:" << endl; - m_compiler->streamAssembly(cout, contract, m_sourceCodes, m_args.count(g_argAsmJson)); + cout << "EVM assembly:" << endl << ret << endl; } } diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index bf9400e4ce23..4768c9d880ba 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -81,6 +81,11 @@ class CommandLineInterface /// @arg _data to be written void createFile(std::string const& _fileName, std::string const& _data); + /// Create a json file in the given directory + /// @arg _fileName the name of the file (the extension will be replaced with .json) + /// @arg _json json string to be written + void createJson(std::string const& _fileName, std::string const& _json); + bool m_error = false; ///< If true, some error occurred. bool m_onlyAssemble = false; diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index 684d49e4b141..7e797a62df3b 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -20,24 +20,20 @@ * JSON interface for the solidity compiler to be used from Javascript. */ -#include +#include #include #include #include #include +#include + #include "license.h" using namespace std; using namespace dev; using namespace solidity; -extern "C" { -/// Callback used to retrieve additional source files. "Returns" two pointers that should be -/// heap-allocated and are free'd by the caller. -typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); -} - namespace { diff --git a/solc/jsonCompiler.h b/solc/jsonCompiler.h new file mode 100644 index 000000000000..c392ce93a829 --- /dev/null +++ b/solc/jsonCompiler.h @@ -0,0 +1,42 @@ +/* + 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 . +*/ +/** + * @author Christian + * @date 2014 + * JSON interface for the solidity compiler to be used from Javascript. + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// Callback used to retrieve additional source files. "Returns" two pointers that should be +/// heap-allocated and are free'd by the caller. +typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); + +char const* license(); +char const* version(); +char const* compileJSON(char const* _input, bool _optimize); +char const* compileJSONMulti(char const* _input, bool _optimize); +char const* compileJSONCallback(char const* _input, bool _optimize, CStyleReadFileCallback _readCallback); +char const* compileStandard(char const* _input, CStyleReadFileCallback _readCallback); + +#ifdef __cplusplus +} +#endif diff --git a/std/StandardToken.sol b/std/StandardToken.sol index 51f925e095b8..2986cb5632c7 100644 --- a/std/StandardToken.sol +++ b/std/StandardToken.sol @@ -8,24 +8,24 @@ contract StandardToken is Token { mapping (address => mapping (address => uint256)) m_allowance; - function StandardToken(address _initialOwner, uint256 _supply) { + function StandardToken(address _initialOwner, uint256 _supply) public { supply = _supply; balance[_initialOwner] = _supply; } - function balanceOf(address _account) constant returns (uint) { + function balanceOf(address _account) constant public returns (uint) { return balance[_account]; } - function totalSupply() constant returns (uint) { + function totalSupply() constant public returns (uint) { return supply; } - function transfer(address _to, uint256 _value) returns (bool success) { + function transfer(address _to, uint256 _value) public returns (bool success) { return doTransfer(msg.sender, _to, _value); } - function transferFrom(address _from, address _to, uint256 _value) returns (bool) { + function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { if (m_allowance[_from][msg.sender] >= _value) { if (doTransfer(_from, _to, _value)) { m_allowance[_from][msg.sender] -= _value; @@ -47,13 +47,13 @@ contract StandardToken is Token { } } - function approve(address _spender, uint256 _value) returns (bool success) { + function approve(address _spender, uint256 _value) public returns (bool success) { m_allowance[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; } - function allowance(address _owner, address _spender) constant returns (uint256) { + function allowance(address _owner, address _spender) constant public returns (uint256) { return m_allowance[_owner][_spender]; } } diff --git a/std/Token.sol b/std/Token.sol index 59566f26f8f3..4b4eb71e2253 100644 --- a/std/Token.sol +++ b/std/Token.sol @@ -4,10 +4,10 @@ contract Token { event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); - function totalSupply() constant returns (uint256 supply); - function balanceOf(address _owner) constant returns (uint256 balance); - function transfer(address _to, uint256 _value) returns (bool success); - function transferFrom(address _from, address _to, uint256 _value) returns (bool success); - function approve(address _spender, uint256 _value) returns (bool success); - function allowance(address _owner, address _spender) constant returns (uint256 remaining); + function totalSupply() constant public returns (uint256 supply); + function balanceOf(address _owner) constant public returns (uint256 balance); + function transfer(address _to, uint256 _value) public returns (bool success); + function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); + function approve(address _spender, uint256 _value) public returns (bool success); + function allowance(address _owner, address _spender) constant public returns (uint256 remaining); } diff --git a/std/mortal.sol b/std/mortal.sol index f0a6f4ce031d..c43f1e4f7951 100644 --- a/std/mortal.sol +++ b/std/mortal.sol @@ -3,7 +3,7 @@ pragma solidity ^0.4.0; import "./owned.sol"; contract mortal is owned { - function kill() { + function kill() public { if (msg.sender == owner) selfdestruct(owner); } diff --git a/std/owned.sol b/std/owned.sol index bbb8d957da44..ee9860d343af 100644 --- a/std/owned.sol +++ b/std/owned.sol @@ -9,7 +9,7 @@ contract owned { } } - function owned() { + function owned() public { owner = msg.sender; } } diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index f4e5fcef5442..b2de814a9838 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -31,8 +31,8 @@ using namespace dev::test; namespace // anonymous { - h256 const EmptyTrie("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); -} + +h256 const EmptyTrie("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); string getIPCSocketPath() { @@ -43,6 +43,8 @@ string getIPCSocketPath() return ipcPath; } +} + ExecutionFramework::ExecutionFramework() : m_rpc(RPCSession::instance(getIPCSocketPath())), m_optimize(dev::test::Options::get().optimize), diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 76d0fd8cf270..e8d8d111c2c8 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -22,13 +22,13 @@ #pragma once -#include - -#include "TestHelper.h" -#include "RPCSession.h" +#include +#include -#include #include +#include + +#include namespace dev { @@ -40,11 +40,11 @@ namespace test using Address = h160; // The various denominations; here for ease of use where needed within code. - static const u256 ether = exp10<18>(); - static const u256 finney = exp10<15>(); - static const u256 szabo = exp10<12>(); - static const u256 shannon = exp10<9>(); - static const u256 wei = exp10<0>(); + static const u256 wei = 1; + static const u256 shannon = u256("1000000000"); + static const u256 szabo = shannon * 1000; + static const u256 finney = szabo * 1000; + static const u256 ether = finney * 1000; class ExecutionFramework { @@ -217,25 +217,25 @@ class ExecutionFramework bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); BOOST_REQUIRE(ret.size() == 0x20); BOOST_CHECK(std::count(ret.begin(), ret.begin() + 12, 0) == 12); - return eth::abiOut(ret); + return u160(u256(h256(ret))); } std::string callAddressReturnsString(std::string const& _name, u160 const& _arg) { - bytesConstRef ret = ref(call(_name + "(address)", _arg)); - BOOST_REQUIRE(ret.size() >= 0x20); - u256 offset = eth::abiOut(ret); + bytesConstRef const ret(&call(_name + "(address)", _arg)); + BOOST_REQUIRE(ret.size() >= 0x40); + u256 offset(h256(ret.cropped(0, 0x20))); BOOST_REQUIRE_EQUAL(offset, 0x20); - u256 len = eth::abiOut(ret); - BOOST_REQUIRE_EQUAL(ret.size(), ((len + 0x1f) / 0x20) * 0x20); - return ret.cropped(0, size_t(len)).toString(); + u256 len(h256(ret.cropped(0x20, 0x20))); + BOOST_REQUIRE_EQUAL(ret.size(), 0x40 + ((len + 0x1f) / 0x20) * 0x20); + return ret.cropped(0x40, size_t(len)).toString(); } h256 callStringReturnsBytes32(std::string const& _name, std::string const& _arg) { bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); BOOST_REQUIRE(ret.size() == 0x20); - return eth::abiOut(ret); + return h256(ret); } private: @@ -262,7 +262,7 @@ class ExecutionFramework void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0); void sendEther(Address const& _to, u256 const& _value); size_t currentTimestamp(); - size_t blockTimestamp(u256 number); + size_t blockTimestamp(u256 _number); /// @returns the (potentially newly created) _ith address. Address account(size_t _i); diff --git a/test/RPCSession.h b/test/RPCSession.h index 558cb99f25be..eae6a09c37cf 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -40,7 +40,7 @@ class IPCSocket : public boost::noncopyable { public: - IPCSocket(std::string const& _path); + explicit IPCSocket(std::string const& _path); std::string sendRequest(std::string const& _req); ~IPCSocket() { CloseHandle(m_socket); } @@ -55,7 +55,7 @@ class IPCSocket : public boost::noncopyable class IPCSocket: public boost::noncopyable { public: - IPCSocket(std::string const& _path); + explicit IPCSocket(std::string const& _path); std::string sendRequest(std::string const& _req); ~IPCSocket() { close(m_socket); } @@ -107,7 +107,7 @@ class RPCSession: public boost::noncopyable Json::Value eth_getBlockByNumber(std::string const& _blockNumber, bool _fullObjects); std::string eth_call(TransactionData const& _td, std::string const& _blockNumber); TransactionReceipt eth_getTransactionReceipt(std::string const& _transactionHash); - std::string eth_sendTransaction(TransactionData const& _transactionData); + std::string eth_sendTransaction(TransactionData const& _td); std::string eth_sendTransaction(std::string const& _transaction); std::string eth_getBalance(std::string const& _address, std::string const& _blockNumber); std::string eth_getStorageRoot(std::string const& _address, std::string const& _blockNumber); diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index eb5c714da2bd..f12a66865e42 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -147,6 +147,13 @@ TMPDIR=$(mktemp -d) cat "$f" exit 1 fi + + "$REPO_ROOT"/build/test/solfuzzer --without-optimizer --quiet < "$f" + if [ $? -ne 0 ]; then + echo "Fuzzer (without optimizer) failed on:" + cat "$f" + exit 1 + fi set -e done ) diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index 773b14b94b15..d56edc5622ad 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include using namespace std; diff --git a/test/externalTests.sh b/test/externalTests.sh index 1b74561b740f..6ff2ebc5b5bf 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -38,10 +38,9 @@ SOLJSON="$1" DIR=$(mktemp -d) ( - cd "$DIR" echo "Running Zeppelin tests..." - git clone https://github.com/OpenZeppelin/zeppelin-solidity.git - cd zeppelin-solidity + git clone --depth 1 https://github.com/OpenZeppelin/zeppelin-solidity.git "$DIR" + cd "$DIR" npm install cp "$SOLJSON" ./node_modules/solc/soljson.js npm run test diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp index cf99755f1117..53ba72017518 100644 --- a/test/fuzzer.cpp +++ b/test/fuzzer.cpp @@ -20,6 +20,7 @@ #include #include +#include #include @@ -33,12 +34,8 @@ using namespace dev; using namespace dev::eth; namespace po = boost::program_options; -extern "C" +namespace { -extern char const* compileJSON(char const* _input, bool _optimize); -typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); -extern char const* compileStandard(char const* _input, CStyleReadFileCallback _readCallback); -} bool quiet = false; @@ -124,13 +121,12 @@ void testStandardCompiler() } } -void testCompiler() +void testCompiler(bool optimize) { if (!quiet) - cout << "Testing compiler." << endl; + cout << "Testing compiler " << (optimize ? "with" : "without") << " optimizer." << endl; string input = readInput(); - bool optimize = true; string outputString(compileJSON(input.c_str(), optimize)); Json::Value outputJson; if (!Json::Reader().parse(outputString, outputJson)) @@ -169,6 +165,8 @@ void testCompiler() } } +} + int main(int argc, char** argv) { po::options_description options( @@ -192,6 +190,10 @@ Allowed options)", "const-opt", "Run the constant optimizer instead of compiling. " "Expects a binary string of up to 32 bytes on stdin." + ) + ( + "without-optimizer", + "Run without optimizations. Cannot be used together with standard-json." ); po::variables_map arguments; @@ -217,7 +219,7 @@ Allowed options)", else if (arguments.count("standard-json")) testStandardCompiler(); else - testCompiler(); + testCompiler(!arguments.count("without-optimizer")); return 0; } diff --git a/test/libdevcore/MiniMoustache.cpp b/test/libdevcore/Whiskers.cpp similarity index 100% rename from test/libdevcore/MiniMoustache.cpp rename to test/libdevcore/Whiskers.cpp diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 6656f15bea98..9dc49581067f 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -840,6 +841,89 @@ BOOST_AUTO_TEST_CASE(peephole_double_push) ); } +BOOST_AUTO_TEST_CASE(jumpdest_removal) +{ + AssemblyItems items{ + AssemblyItem(Tag, 2), + AssemblyItem(PushTag, 1), + u256(5), + AssemblyItem(Tag, 10), + AssemblyItem(Tag, 3), + u256(6), + AssemblyItem(Tag, 1), + Instruction::JUMP, + }; + AssemblyItems expectation{ + AssemblyItem(PushTag, 1), + u256(5), + u256(6), + AssemblyItem(Tag, 1), + Instruction::JUMP + }; + JumpdestRemover jdr(items); + BOOST_REQUIRE(jdr.optimise({})); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); +} + +BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies) +{ + // This tests that tags from subassemblies are not removed + // if they are referenced by a super-assembly. Furthermore, + // tag unifications (due to block deduplication) is also + // visible at the super-assembly. + + Assembly main; + AssemblyPointer sub = make_shared(); + + sub->append(u256(1)); + auto t1 = sub->newTag(); + sub->append(t1); + sub->append(u256(2)); + sub->append(Instruction::JUMP); + auto t2 = sub->newTag(); + sub->append(t2); // Identical to T1, will be unified + sub->append(u256(2)); + sub->append(Instruction::JUMP); + auto t3 = sub->newTag(); + sub->append(t3); + auto t4 = sub->newTag(); + sub->append(t4); + auto t5 = sub->newTag(); + sub->append(t5); // This will be removed + sub->append(u256(7)); + sub->append(t4.pushTag()); + sub->append(Instruction::JUMP); + + size_t subId = size_t(main.appendSubroutine(sub).data()); + main.append(t1.toSubAssemblyTag(subId)); + main.append(t1.toSubAssemblyTag(subId)); + main.append(u256(8)); + + main.optimise(true); + + AssemblyItems expectationMain{ + AssemblyItem(PushSubSize, 0), + t1.toSubAssemblyTag(subId).pushTag(), + t1.toSubAssemblyTag(subId).pushTag(), + u256(8) + }; + BOOST_CHECK_EQUAL_COLLECTIONS( + main.items().begin(), main.items().end(), + expectationMain.begin(), expectationMain.end() + ); + + AssemblyItems expectationSub{ + u256(1), t1.tag(), u256(2), Instruction::JUMP, t4.tag(), u256(7), t4.pushTag(), Instruction::JUMP + }; + BOOST_CHECK_EQUAL_COLLECTIONS( + sub->items().begin(), sub->items().end(), + expectationSub.begin(), expectationSub.end() + ); +} + BOOST_AUTO_TEST_CASE(cse_sub_zero) { checkCSE({ diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index 51070370817e..f8c1aa4dee3b 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -249,6 +249,26 @@ BOOST_AUTO_TEST_CASE(recursion_depth) CHECK_ERROR(input, ParserError, "recursion"); } +BOOST_AUTO_TEST_CASE(multiple_assignment) +{ + CHECK_ERROR("{ let x:u256 function f() -> a:u256, b:u256 {} 123:u256, x := f() }", ParserError, "Label name / variable name must precede \",\" (multiple assignment)."); + CHECK_ERROR("{ let x:u256 function f() -> a:u256, b:u256 {} x, 123:u256 := f() }", ParserError, "Variable name expected in multiple assignemnt."); + + /// NOTE: Travis hiccups if not having a variable + char const* text = R"( + { + function f(a:u256) -> r1:u256, r2:u256 { + r1 := a + r2 := 7:u256 + } + let x:u256 := 9:u256 + let y:u256 := 2:u256 + x, y := f(x) + } + )"; + BOOST_CHECK(successParse(text)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp index 297c4ef08015..05158601f4ba 100644 --- a/test/libsolidity/ABIEncoderTests.cpp +++ b/test/libsolidity/ABIEncoderTests.cpp @@ -398,6 +398,70 @@ BOOST_AUTO_TEST_CASE(calldata) ) } +BOOST_AUTO_TEST_CASE(function_name_collision) +{ + // This tests a collision between a function name used by inline assembly + // and by the ABI encoder + string sourceCode = R"( + contract C { + function f(uint x) returns (uint) { + assembly { + function abi_encode_t_uint256_to_t_uint256() { + mstore(0, 7) + return(0, 0x20) + } + switch x + case 0 { abi_encode_t_uint256_to_t_uint256() } + } + return 1; + } + } + )"; + BOTH_ENCODERS( + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(uint256)", encodeArgs(0)) == encodeArgs(7)); + BOOST_CHECK(callContractFunction("f(uint256)", encodeArgs(1)) == encodeArgs(1)); + ) +} + +BOOST_AUTO_TEST_CASE(structs) +{ + string sourceCode = R"( + contract C { + struct S { uint16 a; uint16 b; T[] sub; uint16 c; } + struct T { uint64[2] x; } + S s; + event e(uint16, S); + function f() returns (uint, S) { + uint16 x = 7; + s.a = 8; + s.b = 9; + s.c = 10; + s.sub.length = 3; + s.sub[0].x[0] = 11; + s.sub[1].x[0] = 12; + s.sub[2].x[1] = 13; + e(x, s); + return (x, s); + } + } + )"; + + NEW_ENCODER( + compileAndRun(sourceCode, 0, "C"); + bytes encoded = encodeArgs( + u256(7), 0x40, + 8, 9, 0x80, 10, + 3, + 11, 0, + 12, 0, + 0, 13 + ); + BOOST_CHECK(callContractFunction("f()") == encoded); + REQUIRE_LOG_DATA(encoded); + ) +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/AnalysisFramework.cpp b/test/libsolidity/AnalysisFramework.cpp new file mode 100644 index 000000000000..5f5f6411c584 --- /dev/null +++ b/test/libsolidity/AnalysisFramework.cpp @@ -0,0 +1,127 @@ +/* + 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 . +*/ +/** + * Framework for testing features from the analysis phase of compiler. + */ + +#include + +#include +#include + +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::test; + +pair> +AnalysisFramework::parseAnalyseAndReturnError( + string const& _source, + bool _reportWarnings, + bool _insertVersionPragma, + bool _allowMultipleErrors +) +{ + m_compiler.reset(); + m_compiler.addSource("", _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source); + if (!m_compiler.parse()) + { + printErrors(); + BOOST_ERROR("Parsing contract failed in analysis test suite."); + } + + m_compiler.analyze(); + + std::shared_ptr firstError; + for (auto const& currentError: m_compiler.errors()) + { + solAssert(currentError->comment(), ""); + if (currentError->comment()->find("This is a pre-release compiler version") == 0) + continue; + + if (_reportWarnings || (currentError->type() != Error::Type::Warning)) + { + if (firstError && !_allowMultipleErrors) + { + printErrors(); + BOOST_FAIL("Multiple errors found."); + } + if (!firstError) + firstError = currentError; + } + } + + return make_pair(&m_compiler.ast(), firstError); +} + +SourceUnit const* AnalysisFramework::parseAndAnalyse(string const& _source) +{ + auto sourceAndError = parseAnalyseAndReturnError(_source); + BOOST_REQUIRE(!!sourceAndError.first); + BOOST_REQUIRE(!sourceAndError.second); + return sourceAndError.first; +} + +bool AnalysisFramework::success(string const& _source) +{ + return !parseAnalyseAndReturnError(_source).second; +} + +Error AnalysisFramework::expectError(std::string const& _source, bool _warning, bool _allowMultiple) +{ + auto sourceAndError = parseAnalyseAndReturnError(_source, _warning, true, _allowMultiple); + BOOST_REQUIRE(!!sourceAndError.second); + BOOST_REQUIRE(!!sourceAndError.first); + return *sourceAndError.second; +} + +void AnalysisFramework::printErrors() +{ + for (auto const& error: m_compiler.errors()) + SourceReferenceFormatter::printExceptionInformation( + std::cerr, + *error, + (error->type() == Error::Type::Warning) ? "Warning" : "Error", + [&](std::string const& _sourceName) -> solidity::Scanner const& { return m_compiler.scanner(_sourceName); } + ); +} + +ContractDefinition const* AnalysisFramework::retrieveContractByName(SourceUnit const& _source, string const& _name) +{ + ContractDefinition* contract = nullptr; + + for (shared_ptr const& node: _source.nodes()) + if ((contract = dynamic_cast(node.get())) && contract->name() == _name) + return contract; + + return nullptr; +} + +FunctionTypePointer AnalysisFramework::retrieveFunctionBySignature( + ContractDefinition const& _contract, + std::string const& _signature +) +{ + FixedHash<4> hash(dev::keccak256(_signature)); + return _contract.interfaceFunctions()[hash]; +} diff --git a/test/libsolidity/AnalysisFramework.h b/test/libsolidity/AnalysisFramework.h new file mode 100644 index 000000000000..172ae01b918a --- /dev/null +++ b/test/libsolidity/AnalysisFramework.h @@ -0,0 +1,113 @@ +/* + 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 . +*/ +/** + * Framework for testing features from the analysis phase of compiler. + */ + +#pragma once + +#include + +#include + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class Type; +class FunctionType; +using TypePointer = std::shared_ptr; +using FunctionTypePointer = std::shared_ptr; + +namespace test +{ + +class AnalysisFramework +{ + +protected: + std::pair> + parseAnalyseAndReturnError( + std::string const& _source, + bool _reportWarnings = false, + bool _insertVersionPragma = true, + bool _allowMultipleErrors = false + ); + + SourceUnit const* parseAndAnalyse(std::string const& _source); + bool success(std::string const& _source); + Error expectError(std::string const& _source, bool _warning = false, bool _allowMultiple = false); + + void printErrors(); + + static ContractDefinition const* retrieveContractByName(SourceUnit const& _source, std::string const& _name); + static FunctionTypePointer retrieveFunctionBySignature( + ContractDefinition const& _contract, + std::string const& _signature + ); + + dev::solidity::CompilerStack m_compiler; +}; + + +#define CHECK_ERROR_OR_WARNING(text, typ, substring, warning, allowMulti) \ +do \ +{ \ + Error err = expectError((text), (warning), (allowMulti)); \ + BOOST_CHECK(err.type() == (Error::Type::typ)); \ + BOOST_CHECK(searchErrorMessage(err, (substring))); \ +} while(0) + +// [checkError(text, type, substring)] asserts that the compilation down to typechecking +// emits an error of type [type] and with a message containing [substring]. +#define CHECK_ERROR(text, type, substring) \ +CHECK_ERROR_OR_WARNING(text, type, substring, false, false) + +// [checkError(text, type, substring)] asserts that the compilation down to typechecking +// emits an error of type [type] and with a message containing [substring]. +#define CHECK_ERROR_ALLOW_MULTI(text, type, substring) \ +CHECK_ERROR_OR_WARNING(text, type, substring, false, true) + +// [checkWarning(text, substring)] asserts that the compilation down to typechecking +// emits a warning and with a message containing [substring]. +#define CHECK_WARNING(text, substring) \ +CHECK_ERROR_OR_WARNING(text, Warning, substring, true, false) + +// [checkWarningAllowMulti(text, substring)] aserts that the compilation down to typechecking +// emits a warning and with a message containing [substring]. +#define CHECK_WARNING_ALLOW_MULTI(text, substring) \ +CHECK_ERROR_OR_WARNING(text, Warning, substring, true, true) + +// [checkSuccess(text)] asserts that the compilation down to typechecking succeeds. +#define CHECK_SUCCESS(text) do { BOOST_CHECK(success((text))); } while(0) + +#define CHECK_SUCCESS_NO_WARNINGS(text) \ +do \ +{ \ + auto sourceAndError = parseAnalyseAndReturnError((text), true); \ + BOOST_CHECK(sourceAndError.second == nullptr); \ +} \ +while(0) + +} +} +} diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 99a2996ee2ef..56ac8cf5cb78 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -119,11 +119,11 @@ BOOST_AUTO_TEST_CASE(location_test) shared_ptr n = make_shared(""); AssemblyItems items = compileContract(sourceCode); vector locations = - vector(19, SourceLocation(2, 75, n)) + + vector(18, SourceLocation(2, 75, n)) + vector(32, SourceLocation(20, 72, n)) + vector{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + vector(2, SourceLocation(58, 67, n)) + - vector(3, SourceLocation(20, 72, n)); + vector(2, SourceLocation(20, 72, n)); checkAssemblyLocations(items, locations); } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 0debc66d8828..da3522b41f0e 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -412,7 +412,25 @@ BOOST_AUTO_TEST_CASE(recursion_depth) CHECK_PARSE_ERROR(input, ParserError, "recursion"); } +BOOST_AUTO_TEST_CASE(multiple_assignment) +{ + CHECK_PARSE_ERROR("{ let x function f() -> a, b {} 123, x := f() }", ParserError, "Label name / variable name must precede \",\" (multiple assignment)."); + CHECK_PARSE_ERROR("{ let x function f() -> a, b {} x, 123 := f() }", ParserError, "Variable name expected in multiple assignemnt."); + /// NOTE: Travis hiccups if not having a variable + char const* text = R"( + { + function f(a) -> r1, r2 { + r1 := a + r2 := 7 + } + let x := 9 + let y := 2 + x, y := f(x) + } + )"; + BOOST_CHECK(successParse(text)); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/JSONCompiler.cpp b/test/libsolidity/JSONCompiler.cpp index 0fe7636c94ae..7dc4808bd90c 100644 --- a/test/libsolidity/JSONCompiler.cpp +++ b/test/libsolidity/JSONCompiler.cpp @@ -23,22 +23,13 @@ #include #include #include +#include #include "../Metadata.h" #include "../TestHelper.h" using namespace std; -extern "C" -{ -extern char const* version(); -extern char const* license(); -extern char const* compileJSON(char const* _input, bool _optimize); -extern char const* compileJSONMulti(char const* _input, bool _optimize); -extern char const* compileJSONCallback(char const* _input, bool _optimize, void* _readCallback); -extern char const* compileStandard(char const* _input, void* _readCallback); -} - namespace dev { namespace solidity @@ -120,18 +111,18 @@ BOOST_AUTO_TEST_CASE(basic_compilation) BOOST_CHECK(contract["bytecode"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["bytecode"].asString()), - "60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00" + "60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00" ); BOOST_CHECK(contract["runtimeBytecode"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["runtimeBytecode"].asString()), - "60606040525b600080fd00" + "6060604052600080fd00" ); BOOST_CHECK(contract["functionHashes"].isObject()); BOOST_CHECK(contract["gasEstimates"].isObject()); BOOST_CHECK_EQUAL( dev::jsonCompactPrint(contract["gasEstimates"]), - "{\"creation\":[62,10800],\"external\":{},\"internal\":{}}" + "{\"creation\":[61,10600],\"external\":{},\"internal\":{}}" ); BOOST_CHECK(contract["metadata"].isString()); BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString())); @@ -162,18 +153,18 @@ BOOST_AUTO_TEST_CASE(single_compilation) BOOST_CHECK(contract["bytecode"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["bytecode"].asString()), - "60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00" + "60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00" ); BOOST_CHECK(contract["runtimeBytecode"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["runtimeBytecode"].asString()), - "60606040525b600080fd00" + "6060604052600080fd00" ); BOOST_CHECK(contract["functionHashes"].isObject()); BOOST_CHECK(contract["gasEstimates"].isObject()); BOOST_CHECK_EQUAL( dev::jsonCompactPrint(contract["gasEstimates"]), - "{\"creation\":[62,10800],\"external\":{},\"internal\":{}}" + "{\"creation\":[61,10600],\"external\":{},\"internal\":{}}" ); BOOST_CHECK(contract["metadata"].isString()); BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString())); diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 0512ba1fd6b6..e5d9e99c13cb 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -48,7 +48,7 @@ class JSONInterfaceChecker Json::Value generatedInterface = m_compilerStack.contractABI(""); Json::Value expectedInterface; - m_reader.parse(_expectedInterfaceString, expectedInterface); + BOOST_REQUIRE(m_reader.parse(_expectedInterfaceString, expectedInterface)); BOOST_CHECK_MESSAGE( expectedInterface == generatedInterface, "Expected:\n" << expectedInterface.toStyledString() << @@ -423,6 +423,8 @@ BOOST_AUTO_TEST_CASE(events) function f(uint a) returns(uint d) { return a * 7; } event e1(uint b, address indexed c); event e2(); + event e2(uint a); + event e3() anonymous; } )"; char const* interface = R"([ @@ -467,6 +469,24 @@ BOOST_AUTO_TEST_CASE(events) "type": "event", "anonymous": false, "inputs": [] + }, + { + "name": "e2", + "type": "event", + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "a", + "type": "uint256" + } + ] + }, + { + "name": "e3", + "type": "event", + "anonymous": true, + "inputs": [] } ])"; @@ -919,6 +939,217 @@ BOOST_AUTO_TEST_CASE(function_type) checkInterface(sourceCode, interface); } +BOOST_AUTO_TEST_CASE(return_structs) +{ + char const* text = R"( + contract C { + struct S { uint a; T[] sub; } + struct T { uint[2] x; } + function f() returns (uint x, S s) { + } + } + )"; + char const* interface = R"( + [{ + "constant" : false, + "inputs" : [], + "name" : "f", + "outputs" : [ + { + "name" : "x", + "type" : "uint256" + }, + { + "components" : [ + { + "name" : "a", + "type" : "uint256" + }, + { + "components" : [ + { + "name" : "x", + "type" : "uint256[2]" + } + ], + "name" : "sub", + "type" : "tuple[]" + } + ], + "name" : "s", + "type" : "tuple" + } + ], + "payable" : false, + "stateMutability" : "nonpayable", + "type" : "function" + }] + )"; + checkInterface(text, interface); +} + +BOOST_AUTO_TEST_CASE(return_structs_with_contracts) +{ + char const* text = R"( + contract C { + struct S { C[] x; C y; } + function f() returns (S s, C c) { + } + } + )"; + char const* interface = R"( + [{ + "constant": false, + "inputs": [], + "name": "f", + "outputs": [ + { + "components": [ + { + "name": "x", + "type": "address[]" + }, + { + "name": "y", + "type": "address" + } + ], + "name": "s", + "type": "tuple" + }, + { + "name": "c", + "type": "address" + } + ], + "payable": false, + "stateMutability" : "nonpayable", + "type": "function" + }] + )"; + checkInterface(text, interface); +} + +BOOST_AUTO_TEST_CASE(event_structs) +{ + char const* text = R"( + contract C { + struct S { uint a; T[] sub; bytes b; } + struct T { uint[2] x; } + event E(T t, S s); + } + )"; + char const *interface = R"( + [{ + "anonymous": false, + "inputs": [ + { + "components": [ + { + "name": "x", + "type": "uint256[2]" + } + ], + "indexed": false, + "name": "t", + "type": "tuple" + }, + { + "components": [ + { + "name": "a", + "type": "uint256" + }, + { + "components": [ + { + "name": "x", + "type": "uint256[2]" + } + ], + "name": "sub", + "type": "tuple[]" + }, + { + "name": "b", + "type": "bytes" + } + ], + "indexed": false, + "name": "s", + "type": "tuple" + } + ], + "name": "E", + "type": "event" + }] + )"; + checkInterface(text, interface); +} + +BOOST_AUTO_TEST_CASE(structs_in_libraries) +{ + char const* text = R"( + library L { + struct S { uint a; T[] sub; bytes b; } + struct T { uint[2] x; } + function f(L.S storage s) {} + function g(L.S s) {} + } + )"; + char const* interface = R"( + [{ + "constant": false, + "inputs": [ + { + "components": [ + { + "name": "a", + "type": "uint256" + }, + { + "components": [ + { + "name": "x", + "type": "uint256[2]" + } + ], + "name": "sub", + "type": "tuple[]" + }, + { + "name": "b", + "type": "bytes" + } + ], + "name": "s", + "type": "tuple" + } + ], + "name": "g", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "s", + "type": "L.S storage" + } + ], + "name": "f", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }] + )"; + checkInterface(text, interface); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 73dd7d22ca83..458b64f40ab1 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -6525,7 +6525,7 @@ BOOST_AUTO_TEST_CASE(state_variable_under_contract_name) contract Scope { uint stateVar = 42; - function getStateVar() constant returns (uint stateVar) { + function getStateVar() view returns (uint stateVar) { stateVar = Scope.stateVar; } } @@ -6791,7 +6791,7 @@ BOOST_AUTO_TEST_CASE(fixed_arrays_as_return_type) { char const* sourceCode = R"( contract A { - function f(uint16 input) constant returns (uint16[5] arr) + function f(uint16 input) pure returns (uint16[5] arr) { arr[0] = input; arr[1] = ++input; @@ -6820,7 +6820,7 @@ BOOST_AUTO_TEST_CASE(internal_types_in_library) { char const* sourceCode = R"( library Lib { - function find(uint16[] storage _haystack, uint16 _needle) constant returns (uint) + function find(uint16[] storage _haystack, uint16 _needle) view returns (uint) { for (uint i = 0; i < _haystack.length; ++i) if (_haystack[i] == _needle) @@ -7867,6 +7867,31 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_call) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7))); } +BOOST_AUTO_TEST_CASE(inline_assembly_function_call_assignment) +{ + char const* sourceCode = R"( + contract C { + function f() { + assembly { + let a1, b1, c1 + function asmfun(a, b, c) -> x, y, z { + x := a + y := b + z := 7 + } + a1, b1, c1 := asmfun(1, 2, 3) + mstore(0x00, a1) + mstore(0x20, b1) + mstore(0x40, c1) + return(0, 0x60) + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1), u256(2), u256(7))); +} + BOOST_AUTO_TEST_CASE(inline_assembly_function_call2) { char const* sourceCode = R"( @@ -9682,6 +9707,7 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment) compileAndRun(sourceCode, 0, "C2"); } + BOOST_AUTO_TEST_CASE(include_creation_bytecode_only_once) { char const* sourceCode = R"( @@ -9913,12 +9939,12 @@ BOOST_AUTO_TEST_CASE(keccak256_assembly) { char const* sourceCode = R"( contract C { - function f() returns (bytes32 ret) { + function f() pure returns (bytes32 ret) { assembly { ret := keccak256(0, 0) } } - function g() returns (bytes32 ret) { + function g() pure returns (bytes32 ret) { assembly { 0 0 @@ -9926,12 +9952,12 @@ BOOST_AUTO_TEST_CASE(keccak256_assembly) =: ret } } - function h() returns (bytes32 ret) { + function h() pure returns (bytes32 ret) { assembly { ret := sha3(0, 0) } } - function i() returns (bytes32 ret) { + function i() pure returns (bytes32 ret) { assembly { 0 0 @@ -9979,7 +10005,7 @@ BOOST_AUTO_TEST_CASE(inlineasm_empty_let) { char const* sourceCode = R"( contract C { - function f() returns (uint a, uint b) { + function f() pure returns (uint a, uint b) { assembly { let x let y, z @@ -9998,13 +10024,13 @@ BOOST_AUTO_TEST_CASE(bare_call_invalid_address) char const* sourceCode = R"( contract C { /// Calling into non-existant account is successful (creates the account) - function f() external constant returns (bool) { + function f() external view returns (bool) { return address(0x4242).call(); } - function g() external constant returns (bool) { + function g() external view returns (bool) { return address(0x4242).callcode(); } - function h() external constant returns (bool) { + function h() external view returns (bool) { return address(0x4242).delegatecall(); } } @@ -10023,16 +10049,16 @@ BOOST_AUTO_TEST_CASE(delegatecall_return_value) function set(uint _value) external { value = _value; } - function get() external constant returns (uint) { + function get() external view returns (uint) { return value; } - function get_delegated() external constant returns (bool) { + function get_delegated() external view returns (bool) { return this.delegatecall(bytes4(sha3("get()"))); } - function assert0() external constant { + function assert0() external view { assert(value == 0); } - function assert0_delegated() external constant returns (bool) { + function assert0_delegated() external view returns (bool) { return this.delegatecall(bytes4(sha3("assert0()"))); } } @@ -10051,6 +10077,54 @@ BOOST_AUTO_TEST_CASE(delegatecall_return_value) BOOST_CHECK(callContractFunction("get_delegated()") == encodeArgs(u256(1))); } +BOOST_AUTO_TEST_CASE(function_types_sig) +{ + char const* sourceCode = R"( + contract C { + function f() returns (bytes4) { + return this.f.selector; + } + function g() returns (bytes4) { + function () external returns (bytes4) fun = this.f; + return fun.selector; + } + function h() returns (bytes4) { + function () external returns (bytes4) fun = this.f; + var funvar = fun; + return funvar.selector; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(asString(FixedHash<4>(dev::keccak256("f()")).asBytes()))); + BOOST_CHECK(callContractFunction("g()") == encodeArgs(asString(FixedHash<4>(dev::keccak256("f()")).asBytes()))); + BOOST_CHECK(callContractFunction("h()") == encodeArgs(asString(FixedHash<4>(dev::keccak256("f()")).asBytes()))); +} + +BOOST_AUTO_TEST_CASE(constant_string) +{ + char const* sourceCode = R"( + contract C { + bytes constant a = "\x03\x01\x02"; + bytes constant b = hex"030102"; + string constant c = "hello"; + function f() returns (bytes) { + return a; + } + function g() returns (bytes) { + return b; + } + function h() returns (bytes) { + return bytes(c); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeDyn(string("\x03\x01\x02"))); + BOOST_CHECK(callContractFunction("g()") == encodeDyn(string("\x03\x01\x02"))); + BOOST_CHECK(callContractFunction("h()") == encodeDyn(string("hello"))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 380978e812be..39c47f9c54bb 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -20,22 +20,14 @@ * Unit tests for the name and type resolution of the solidity parser. */ -#include +#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include +#include + #include using namespace std; @@ -47,195 +39,14 @@ namespace solidity namespace test { -namespace -{ - -pair, std::shared_ptr> -parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false, bool _insertVersionPragma = true, bool _allowMultipleErrors = false) -{ - // Silence compiler version warning - string source = _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source; - ErrorList errors; - ErrorReporter errorReporter(errors); - Parser parser(errorReporter); - ASTPointer sourceUnit; - // catch exceptions for a transition period - try - { - sourceUnit = parser.parse(std::make_shared(CharStream(source))); - if(!sourceUnit) - BOOST_FAIL("Parsing failed in type checker test."); - - SyntaxChecker syntaxChecker(errorReporter); - if (!syntaxChecker.checkSyntax(*sourceUnit)) - return make_pair(sourceUnit, errorReporter.errors().at(0)); - - std::shared_ptr globalContext = make_shared(); - map> scopes; - NameAndTypeResolver resolver(globalContext->declarations(), scopes, errorReporter); - solAssert(Error::containsOnlyWarnings(errorReporter.errors()), ""); - resolver.registerDeclarations(*sourceUnit); - - bool success = true; - for (ASTPointer const& node: sourceUnit->nodes()) - if (ContractDefinition* contract = dynamic_cast(node.get())) - { - globalContext->setCurrentContract(*contract); - resolver.updateDeclaration(*globalContext->currentThis()); - resolver.updateDeclaration(*globalContext->currentSuper()); - if (!resolver.resolveNamesAndTypes(*contract)) - success = false; - } - if (success) - for (ASTPointer const& node: sourceUnit->nodes()) - if (ContractDefinition* contract = dynamic_cast(node.get())) - { - globalContext->setCurrentContract(*contract); - resolver.updateDeclaration(*globalContext->currentThis()); - - TypeChecker typeChecker(errorReporter); - bool success = typeChecker.checkTypeRequirements(*contract); - BOOST_CHECK(success || !errorReporter.errors().empty()); - } - if (success) - if (!PostTypeChecker(errorReporter).check(*sourceUnit)) - success = false; - if (success) - if (!StaticAnalyzer(errorReporter).analyze(*sourceUnit)) - success = false; - std::shared_ptr error; - for (auto const& currentError: errorReporter.errors()) - { - if ( - (_reportWarnings && currentError->type() == Error::Type::Warning) || - (!_reportWarnings && currentError->type() != Error::Type::Warning) - ) - { - if (error && !_allowMultipleErrors) - { - string message("Multiple errors found: "); - for (auto const& e: errorReporter.errors()) - if (string const* description = boost::get_error_info(*e)) - message += *description + ", "; - - BOOST_FAIL(message); - } - if (!error) - error = currentError; - } - } - if (error) - return make_pair(sourceUnit, error); - } - catch (InternalCompilerError const& _e) - { - string message("Internal compiler error"); - if (string const* description = boost::get_error_info(_e)) - message += ": " + *description; - BOOST_FAIL(message); - } - catch (Error const& _e) - { - return make_pair(sourceUnit, std::make_shared(_e)); - } - catch (...) - { - BOOST_FAIL("Unexpected exception."); - } - return make_pair(sourceUnit, nullptr); -} - -ASTPointer parseAndAnalyse(string const& _source) -{ - auto sourceAndError = parseAnalyseAndReturnError(_source); - BOOST_REQUIRE(!!sourceAndError.first); - BOOST_REQUIRE(!sourceAndError.second); - return sourceAndError.first; -} - -bool success(string const& _source) -{ - return !parseAnalyseAndReturnError(_source).second; -} - -Error expectError(std::string const& _source, bool _warning = false, bool _allowMultiple = false) -{ - auto sourceAndError = parseAnalyseAndReturnError(_source, _warning, true, _allowMultiple); - BOOST_REQUIRE(!!sourceAndError.second); - BOOST_REQUIRE(!!sourceAndError.first); - return *sourceAndError.second; -} - -static ContractDefinition const* retrieveContract(ASTPointer _source, unsigned index) -{ - ContractDefinition* contract; - unsigned counter = 0; - for (ASTPointer const& node: _source->nodes()) - if ((contract = dynamic_cast(node.get())) && counter == index) - return contract; - - return nullptr; -} - -static FunctionTypePointer retrieveFunctionBySignature( - ContractDefinition const& _contract, - std::string const& _signature -) -{ - FixedHash<4> hash(dev::keccak256(_signature)); - return _contract.interfaceFunctions()[hash]; -} - -} - -#define CHECK_ERROR_OR_WARNING(text, typ, substring, warning, allowMulti) \ -do \ -{ \ - Error err = expectError((text), (warning), (allowMulti)); \ - BOOST_CHECK(err.type() == (Error::Type::typ)); \ - BOOST_CHECK(searchErrorMessage(err, (substring))); \ -} while(0) - -// [checkError(text, type, substring)] asserts that the compilation down to typechecking -// emits an error of type [type] and with a message containing [substring]. -#define CHECK_ERROR(text, type, substring) \ -CHECK_ERROR_OR_WARNING(text, type, substring, false, false) - -// [checkError(text, type, substring)] asserts that the compilation down to typechecking -// emits an error of type [type] and with a message containing [substring]. -#define CHECK_ERROR_ALLOW_MULTI(text, type, substring) \ -CHECK_ERROR_OR_WARNING(text, type, substring, false, true) - -// [checkWarning(text, substring)] asserts that the compilation down to typechecking -// emits a warning and with a message containing [substring]. -#define CHECK_WARNING(text, substring) \ -CHECK_ERROR_OR_WARNING(text, Warning, substring, true, false) - -// [checkWarningAllowMulti(text, substring)] aserts that the compilation down to typechecking -// emits a warning and with a message containing [substring]. -#define CHECK_WARNING_ALLOW_MULTI(text, substring) \ -CHECK_ERROR_OR_WARNING(text, Warning, substring, true, true) - -// [checkSuccess(text)] asserts that the compilation down to typechecking succeeds. -#define CHECK_SUCCESS(text) do { BOOST_CHECK(success((text))); } while(0) - -#define CHECK_SUCCESS_NO_WARNINGS(text) \ -do \ -{ \ - auto sourceAndError = parseAnalyseAndReturnError((text), true); \ - BOOST_CHECK(sourceAndError.second == nullptr); \ -} \ -while(0) - - -BOOST_AUTO_TEST_SUITE(SolidityNameAndTypeResolution) +BOOST_FIXTURE_TEST_SUITE(SolidityNameAndTypeResolution, AnalysisFramework) BOOST_AUTO_TEST_CASE(smoke_test) { char const* text = R"( contract test { uint256 stateVariable1; - function fun(uint256 arg1) { uint256 y; y = arg1; } + function fun(uint256 arg1) public { uint256 y; y = arg1; } } )"; CHECK_SUCCESS(text); @@ -256,8 +67,8 @@ BOOST_AUTO_TEST_CASE(double_function_declaration) { char const* text = R"( contract test { - function fun() { } - function fun() { } + function fun() public { } + function fun() public { } } )"; CHECK_ERROR(text, DeclarationError, "Function with same name and arguments defined twice."); @@ -267,9 +78,9 @@ BOOST_AUTO_TEST_CASE(double_variable_declaration) { char const* text = R"( contract test { - function f() { + function f() public { uint256 x; - if (true) { uint256 x; } + if (true) { uint256 x; } } } )"; @@ -281,7 +92,7 @@ BOOST_AUTO_TEST_CASE(name_shadowing) char const* text = R"( contract test { uint256 variable; - function f() { uint32 variable; variable = 2; } + function f() public { uint32 variable; variable = 2; } } )"; CHECK_SUCCESS(text); @@ -292,7 +103,7 @@ BOOST_AUTO_TEST_CASE(name_references) char const* text = R"( contract test { uint256 variable; - function f(uint256) returns (uint out) { f(variable); test; out; } + function f(uint256) public returns (uint out) { f(variable); test; out; } } )"; CHECK_SUCCESS(text); @@ -303,7 +114,7 @@ BOOST_AUTO_TEST_CASE(undeclared_name) char const* text = R"( contract test { uint256 variable; - function f(uint256 arg) { + function f(uint256 arg) public { f(notfound); } } @@ -315,8 +126,8 @@ BOOST_AUTO_TEST_CASE(reference_to_later_declaration) { char const* text = R"( contract test { - function g() { f(); } - function f() {} + function g() public { f(); } + function f() public {} } )"; CHECK_SUCCESS(text); @@ -381,7 +192,7 @@ BOOST_AUTO_TEST_CASE(type_inference_smoke_test) { char const* text = R"( contract test { - function f(uint256 arg1, uint32 arg2) returns (bool ret) { + function f(uint256 arg1, uint32 arg2) public returns (bool ret) { var x = arg1 + arg2 == 8; ret = x; } } @@ -393,7 +204,7 @@ BOOST_AUTO_TEST_CASE(type_checking_return) { char const* text = R"( contract test { - function f() returns (bool r) { return 1 >= 2; } + function f() public returns (bool r) { return 1 >= 2; } } )"; CHECK_SUCCESS(text); @@ -403,7 +214,7 @@ BOOST_AUTO_TEST_CASE(type_checking_return_wrong_number) { char const* text = R"( contract test { - function f() returns (bool r1, bool r2) { return 1 >= 2; } + function f() public returns (bool r1, bool r2) { return 1 >= 2; } } )"; CHECK_ERROR(text, TypeError, "Different number of arguments in return statement than in returns declaration."); @@ -413,7 +224,7 @@ BOOST_AUTO_TEST_CASE(type_checking_return_wrong_type) { char const* text = R"( contract test { - function f() returns (uint256 r) { return 1 >= 2; } + function f() public returns (uint256 r) { return 1 >= 2; } } )"; CHECK_ERROR(text, TypeError, "Return argument type bool is not implicitly convertible to expected type (type of first return variable) uint256."); @@ -423,8 +234,8 @@ BOOST_AUTO_TEST_CASE(type_checking_function_call) { char const* text = R"( contract test { - function f() returns (bool) { return g(12, true) == 3; } - function g(uint256, bool) returns (uint256) { } + function f() public returns (bool) { return g(12, true) == 3; } + function g(uint256, bool) public returns (uint256) { } } )"; CHECK_SUCCESS(text); @@ -434,7 +245,7 @@ BOOST_AUTO_TEST_CASE(type_conversion_for_comparison) { char const* text = R"( contract test { - function f() { uint32(2) == int64(2); } + function f() public { uint32(2) == int64(2); } } )"; CHECK_SUCCESS(text); @@ -444,7 +255,7 @@ BOOST_AUTO_TEST_CASE(type_conversion_for_comparison_invalid) { char const* text = R"( contract test { - function f() { int32(2) == uint64(2); } + function f() public { int32(2) == uint64(2); } } )"; CHECK_ERROR(text, TypeError, "Operator == not compatible with types int32 and uint64"); @@ -454,7 +265,7 @@ BOOST_AUTO_TEST_CASE(type_inference_explicit_conversion) { char const* text = R"( contract test { - function f() returns (int256 r) { var x = int256(uint32(2)); return x; } + function f() public returns (int256 r) { var x = int256(uint32(2)); return x; } } )"; CHECK_SUCCESS(text); @@ -464,7 +275,7 @@ BOOST_AUTO_TEST_CASE(large_string_literal) { char const* text = R"( contract test { - function f() { var x = "123456789012345678901234567890123"; } + function f() public { var x = "123456789012345678901234567890123"; } } )"; CHECK_SUCCESS(text); @@ -474,7 +285,7 @@ BOOST_AUTO_TEST_CASE(balance) { char const* text = R"( contract test { - function fun() { + function fun() public { uint256 x = address(0).balance; } } @@ -486,7 +297,7 @@ BOOST_AUTO_TEST_CASE(balance_invalid) { char const* text = R"( contract test { - function fun() { + function fun() public { address(0).balance = 7; } } @@ -502,7 +313,7 @@ BOOST_AUTO_TEST_CASE(assignment_to_mapping) mapping(uint=>uint) map; } str data; - function fun() { + function fun() public { var a = data.map; data.map = a; } @@ -519,7 +330,7 @@ BOOST_AUTO_TEST_CASE(assignment_to_struct) mapping(uint=>uint) map; } str data; - function fun() { + function fun() public { var a = data; data = a; } @@ -532,7 +343,7 @@ BOOST_AUTO_TEST_CASE(returns_in_constructor) { char const* text = R"( contract test { - function test() returns (uint a) { } + function test() public returns (uint a) { } } )"; CHECK_ERROR(text, TypeError, "Non-empty \"returns\" directive for constructor."); @@ -542,12 +353,12 @@ BOOST_AUTO_TEST_CASE(forward_function_reference) { char const* text = R"( contract First { - function fun() returns (bool) { + function fun() public returns (bool) { return Second(1).fun(1, true, 3) > 0; } } contract Second { - function fun(uint, bool, uint) returns (uint) { + function fun(uint, bool, uint) public returns (uint) { if (First(2).fun() == true) return 1; } } @@ -559,7 +370,7 @@ BOOST_AUTO_TEST_CASE(comparison_bitop_precedence) { char const* text = R"( contract First { - function fun() returns (bool ret) { + function fun() public returns (bool ret) { return 1 & 2 == 8 & 9 && 1 ^ 2 < 4 | 6; } } @@ -571,7 +382,7 @@ BOOST_AUTO_TEST_CASE(comparison_of_function_types) { char const* text = R"( contract C { - function f() returns (bool ret) { + function f() public returns (bool ret) { return this.f < this.f; } } @@ -579,7 +390,7 @@ BOOST_AUTO_TEST_CASE(comparison_of_function_types) CHECK_ERROR(text, TypeError, "Operator < not compatible"); text = R"( contract C { - function f() returns (bool ret) { + function f() public returns (bool ret) { return f < f; } } @@ -587,10 +398,10 @@ BOOST_AUTO_TEST_CASE(comparison_of_function_types) CHECK_ERROR(text, TypeError, "Operator < not compatible"); text = R"( contract C { - function f() returns (bool ret) { + function f() public returns (bool ret) { return f == f; } - function g() returns (bool ret) { + function g() public returns (bool ret) { return f != f; } } @@ -603,7 +414,7 @@ BOOST_AUTO_TEST_CASE(comparison_of_mapping_types) char const* text = R"( contract C { mapping(uint => uint) x; - function f() returns (bool ret) { + function f() public returns (bool ret) { var y = x; return x == y; } @@ -614,13 +425,13 @@ BOOST_AUTO_TEST_CASE(comparison_of_mapping_types) BOOST_AUTO_TEST_CASE(function_no_implementation) { - ASTPointer sourceUnit; + SourceUnit const* sourceUnit = nullptr; char const* text = R"( contract test { - function functionName(bytes32 input) returns (bytes32 out); + function functionName(bytes32 input) public returns (bytes32 out); } )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); + sourceUnit = parseAndAnalyse(text); std::vector> nodes = sourceUnit->nodes(); ContractDefinition* contract = dynamic_cast(nodes[1].get()); BOOST_REQUIRE(contract); @@ -630,12 +441,12 @@ BOOST_AUTO_TEST_CASE(function_no_implementation) BOOST_AUTO_TEST_CASE(abstract_contract) { - ASTPointer sourceUnit; + SourceUnit const* sourceUnit = nullptr; char const* text = R"( contract base { function foo(); } - contract derived is base { function foo() {} } + contract derived is base { function foo() public {} } )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); + sourceUnit = parseAndAnalyse(text); std::vector> nodes = sourceUnit->nodes(); ContractDefinition* base = dynamic_cast(nodes[1].get()); ContractDefinition* derived = dynamic_cast(nodes[2].get()); @@ -649,12 +460,12 @@ BOOST_AUTO_TEST_CASE(abstract_contract) BOOST_AUTO_TEST_CASE(abstract_contract_with_overload) { - ASTPointer sourceUnit; + SourceUnit const* sourceUnit = nullptr; char const* text = R"( contract base { function foo(bool); } - contract derived is base { function foo(uint) {} } + contract derived is base { function foo(uint) public {} } )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); + sourceUnit = parseAndAnalyse(text); std::vector> nodes = sourceUnit->nodes(); ContractDefinition* base = dynamic_cast(nodes[1].get()); ContractDefinition* derived = dynamic_cast(nodes[2].get()); @@ -666,12 +477,11 @@ BOOST_AUTO_TEST_CASE(abstract_contract_with_overload) BOOST_AUTO_TEST_CASE(create_abstract_contract) { - ASTPointer sourceUnit; char const* text = R"( contract base { function foo(); } contract derived { base b; - function foo() { b = new base(); } + function foo() public { b = new base(); } } )"; CHECK_ERROR(text, TypeError, "Trying to create an instance of an abstract contract."); @@ -679,10 +489,9 @@ BOOST_AUTO_TEST_CASE(create_abstract_contract) BOOST_AUTO_TEST_CASE(redeclare_implemented_abstract_function_as_abstract) { - ASTPointer sourceUnit; char const* text = R"( contract base { function foo(); } - contract derived is base { function foo() {} } + contract derived is base { function foo() public {} } contract wrong is derived { function foo(); } )"; CHECK_ERROR(text, TypeError, "Redeclaring an already implemented function as abstract"); @@ -690,12 +499,12 @@ BOOST_AUTO_TEST_CASE(redeclare_implemented_abstract_function_as_abstract) BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor) { - ASTPointer sourceUnit; + SourceUnit const* sourceUnit = nullptr; char const* text = R"( contract base { function foo(); } - contract foo is base { function foo() {} } + contract foo is base { function foo() public {} } )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name resolving failed"); + sourceUnit = parseAndAnalyse(text); std::vector> nodes = sourceUnit->nodes(); BOOST_CHECK_EQUAL(nodes.size(), 3); ContractDefinition* derived = dynamic_cast(nodes[2].get()); @@ -705,15 +514,15 @@ BOOST_AUTO_TEST_CASE(implement_abstract_via_constructor) BOOST_AUTO_TEST_CASE(function_canonical_signature) { - ASTPointer sourceUnit; + SourceUnit const* sourceUnit = nullptr; char const* text = R"( contract Test { - function foo(uint256 arg1, uint64 arg2, bool arg3) returns (uint256 ret) { + function foo(uint256 arg1, uint64 arg2, bool arg3) public returns (uint256 ret) { ret = arg1 + arg2; } } )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); + sourceUnit = parseAndAnalyse(text); for (ASTPointer const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { @@ -724,15 +533,15 @@ BOOST_AUTO_TEST_CASE(function_canonical_signature) BOOST_AUTO_TEST_CASE(function_canonical_signature_type_aliases) { - ASTPointer sourceUnit; + SourceUnit const* sourceUnit = nullptr; char const* text = R"( contract Test { - function boo(uint, bytes32, address) returns (uint ret) { + function boo(uint, bytes32, address) public returns (uint ret) { ret = 5; } } )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); + sourceUnit = parseAndAnalyse(text); for (ASTPointer const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { @@ -745,7 +554,7 @@ BOOST_AUTO_TEST_CASE(function_canonical_signature_type_aliases) BOOST_AUTO_TEST_CASE(function_external_types) { - ASTPointer sourceUnit; + SourceUnit const* sourceUnit = nullptr; char const* text = R"( contract C { uint a; @@ -756,7 +565,7 @@ BOOST_AUTO_TEST_CASE(function_external_types) } } )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); + sourceUnit = parseAndAnalyse(text); for (ASTPointer const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { @@ -770,7 +579,7 @@ BOOST_AUTO_TEST_CASE(function_external_types) BOOST_AUTO_TEST_CASE(enum_external_type) { // bug #1801 - ASTPointer sourceUnit; + SourceUnit const* sourceUnit = nullptr; char const* text = R"( contract Test { enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } @@ -779,7 +588,7 @@ BOOST_AUTO_TEST_CASE(enum_external_type) } } )"; - ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseAndAnalyse(text), "Parsing and name Resolving failed"); + sourceUnit = parseAndAnalyse(text); for (ASTPointer const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { @@ -790,12 +599,155 @@ BOOST_AUTO_TEST_CASE(enum_external_type) } } +BOOST_AUTO_TEST_CASE(external_structs) +{ + char const* text = R"( + contract Test { + enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } + struct Empty {} + struct Nested { X[2][] a; uint y; } + struct X { bytes32 x; Test t; Empty[] e; } + function f(ActionChoices, uint, Empty) external {} + function g(Test, Nested) external {} + function h(function(Nested) external returns (uint)[]) external {} + function i(Nested[]) external {} + } + )"; + SourceUnit const* sourceUnit = parseAndAnalyse(text); + for (ASTPointer const& node: sourceUnit->nodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + { + auto functions = contract->definedFunctions(); + BOOST_REQUIRE(!functions.empty()); + BOOST_CHECK_EQUAL("f(uint8,uint256,())", functions[0]->externalSignature()); + BOOST_CHECK_EQUAL("g(address,((bytes32,address,()[])[2][],uint256))", functions[1]->externalSignature()); + BOOST_CHECK_EQUAL("h(function[])", functions[2]->externalSignature()); + BOOST_CHECK_EQUAL("i(((bytes32,address,()[])[2][],uint256)[])", functions[3]->externalSignature()); + } +} + +BOOST_AUTO_TEST_CASE(external_structs_in_libraries) +{ + char const* text = R"( + library Test { + enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } + struct Empty {} + struct Nested { X[2][] a; uint y; } + struct X { bytes32 x; Test t; Empty[] e; } + function f(ActionChoices, uint, Empty) external {} + function g(Test, Nested) external {} + function h(function(Nested) external returns (uint)[]) external {} + function i(Nested[]) external {} + } + )"; + SourceUnit const* sourceUnit = parseAndAnalyse(text); + for (ASTPointer const& node: sourceUnit->nodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + { + auto functions = contract->definedFunctions(); + BOOST_REQUIRE(!functions.empty()); + BOOST_CHECK_EQUAL("f(Test.ActionChoices,uint256,Test.Empty)", functions[0]->externalSignature()); + BOOST_CHECK_EQUAL("g(Test,Test.Nested)", functions[1]->externalSignature()); + BOOST_CHECK_EQUAL("h(function[])", functions[2]->externalSignature()); + BOOST_CHECK_EQUAL("i(Test.Nested[])", functions[3]->externalSignature()); + } +} + +BOOST_AUTO_TEST_CASE(struct_with_mapping_in_library) +{ + char const* text = R"( + library Test { + struct Nested { mapping(uint => uint)[2][] a; uint y; } + struct X { Nested n; } + function f(X storage x) external {} + } + )"; + SourceUnit const* sourceUnit = parseAndAnalyse(text); + for (ASTPointer const& node: sourceUnit->nodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + { + auto functions = contract->definedFunctions(); + BOOST_REQUIRE(!functions.empty()); + BOOST_CHECK_EQUAL("f(Test.X storage)", functions[0]->externalSignature()); + } +} + +BOOST_AUTO_TEST_CASE(functions_with_identical_structs_in_interface) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct S1 { } + struct S2 { } + function f(S1) pure {} + function f(S2) pure {} + } + )"; + CHECK_ERROR(text, TypeError, "Function overload clash during conversion to external types for arguments"); +} + +BOOST_AUTO_TEST_CASE(functions_with_different_structs_in_interface) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct S1 { function() external a; } + struct S2 { bytes24 a; } + function f(S1) pure {} + function f(S2) pure {} + } + )"; + CHECK_SUCCESS(text); +} + +BOOST_AUTO_TEST_CASE(functions_with_stucts_of_non_external_types_in_interface) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct S { function() internal a; } + function f(S) {} + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + +BOOST_AUTO_TEST_CASE(functions_with_stucts_of_non_external_types_in_interface_2) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct S { mapping(uint => uint) a; } + function f(S) {} + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + +BOOST_AUTO_TEST_CASE(functions_with_stucts_of_non_external_types_in_interface_nested) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct T { mapping(uint => uint) a; } + struct S { T[][2] b; } + function f(S) {} + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + BOOST_AUTO_TEST_CASE(function_external_call_allowed_conversion) { char const* text = R"( contract C {} contract Test { - function externalCall() { + function externalCall() public { C arg; this.g(arg); } @@ -810,7 +762,7 @@ BOOST_AUTO_TEST_CASE(function_external_call_not_allowed_conversion) char const* text = R"( contract C {} contract Test { - function externalCall() { + function externalCall() public { address arg; this.g(arg); } @@ -828,8 +780,8 @@ BOOST_AUTO_TEST_CASE(function_internal_allowed_conversion) } contract Test { C a; - function g (C c) {} - function internalCall() { + function g (C c) public {} + function internalCall() public { g(a); } } @@ -845,8 +797,8 @@ BOOST_AUTO_TEST_CASE(function_internal_not_allowed_conversion) } contract Test { address a; - function g (C c) {} - function internalCall() { + function g (C c) public {} + function internalCall() public { g(a); } } @@ -858,8 +810,8 @@ BOOST_AUTO_TEST_CASE(hash_collision_in_interface) { char const* text = R"( contract test { - function gsf() { } - function tgeo() { } + function gsf() public { } + function tgeo() public { } } )"; CHECK_ERROR(text, TypeError, "Function signature hash collision for tgeo()"); @@ -871,7 +823,7 @@ BOOST_AUTO_TEST_CASE(inheritance_basic) contract base { uint baseMember; struct BaseType { uint element; } } contract derived is base { BaseType data; - function f() { baseMember = 7; } + function f() public { baseMember = 7; } } )"; CHECK_SUCCESS(text); @@ -880,11 +832,11 @@ BOOST_AUTO_TEST_CASE(inheritance_basic) BOOST_AUTO_TEST_CASE(inheritance_diamond_basic) { char const* text = R"( - contract root { function rootFunction() {} } - contract inter1 is root { function f() {} } - contract inter2 is root { function f() {} } + contract root { function rootFunction() public {} } + contract inter1 is root { function f() public {} } + contract inter2 is root { function f() public {} } contract derived is root, inter2, inter1 { - function g() { f(); rootFunction(); } + function g() public { f(); rootFunction(); } } )"; CHECK_SUCCESS(text); @@ -902,8 +854,8 @@ BOOST_AUTO_TEST_CASE(cyclic_inheritance) BOOST_AUTO_TEST_CASE(legal_override_direct) { char const* text = R"( - contract B { function f() {} } - contract C is B { function f(uint i) {} } + contract B { function f() public {} } + contract C is B { function f(uint i) public {} } )"; CHECK_SUCCESS(text); } @@ -911,8 +863,8 @@ BOOST_AUTO_TEST_CASE(legal_override_direct) BOOST_AUTO_TEST_CASE(legal_override_indirect) { char const* text = R"( - contract A { function f(uint a) {} } - contract B { function f() {} } + contract A { function f(uint a) public {} } + contract B { function f() public {} } contract C is A, B { } )"; CHECK_SUCCESS(text); @@ -931,7 +883,7 @@ BOOST_AUTO_TEST_CASE(illegal_override_remove_constness) { char const* text = R"( contract B { function f() constant {} } - contract C is B { function f() {} } + contract C is B { function f() public {} } )"; CHECK_ERROR(text, TypeError, "Overriding function changes state mutability from \"view\" to \"nonpayable\"."); } @@ -939,7 +891,7 @@ BOOST_AUTO_TEST_CASE(illegal_override_remove_constness) BOOST_AUTO_TEST_CASE(illegal_override_add_constness) { char const* text = R"( - contract B { function f() {} } + contract B { function f() public {} } contract C is B { function f() constant {} } )"; CHECK_ERROR(text, TypeError, "Overriding function changes state mutability from \"nonpayable\" to \"view\"."); @@ -948,8 +900,8 @@ BOOST_AUTO_TEST_CASE(illegal_override_add_constness) BOOST_AUTO_TEST_CASE(complex_inheritance) { char const* text = R"( - contract A { function f() { uint8 x = C(0).g(); } } - contract B { function f() {} function g() returns (uint8) {} } + contract A { function f() public { uint8 x = C(0).g(); } } + contract B { function f() public {} function g() public returns (uint8) {} } contract C is A, B { } )"; CHECK_SUCCESS(text); @@ -959,8 +911,8 @@ BOOST_AUTO_TEST_CASE(constructor_visibility) { // The constructor of a base class should not be visible in the derived class char const* text = R"( - contract A { function A() { } } - contract B is A { function f() { A x = A(0); } } + contract A { function A() public { } } + contract B is A { function f() public { A x = A(0); } } )"; CHECK_SUCCESS(text); } @@ -969,8 +921,8 @@ BOOST_AUTO_TEST_CASE(overriding_constructor) { // It is fine to "override" constructor of a base class since it is invisible char const* text = R"( - contract A { function A() { } } - contract B is A { function A() returns (uint8 r) {} } + contract A { function A() public { } } + contract B is A { function A() public returns (uint8 r) {} } )"; CHECK_SUCCESS(text); } @@ -978,7 +930,7 @@ BOOST_AUTO_TEST_CASE(overriding_constructor) BOOST_AUTO_TEST_CASE(missing_base_constructor_arguments) { char const* text = R"( - contract A { function A(uint a) { } } + contract A { function A(uint a) public { } } contract B is A { } )"; CHECK_SUCCESS(text); @@ -987,7 +939,7 @@ BOOST_AUTO_TEST_CASE(missing_base_constructor_arguments) BOOST_AUTO_TEST_CASE(base_constructor_arguments_override) { char const* text = R"( - contract A { function A(uint a) { } } + contract A { function A(uint a) public { } } contract B is A { } )"; CHECK_SUCCESS(text); @@ -998,7 +950,7 @@ BOOST_AUTO_TEST_CASE(implicit_derived_to_base_conversion) char const* text = R"( contract A { } contract B is A { - function f() { A a = B(1); } + function f() public { A a = B(1); } } )"; CHECK_SUCCESS(text); @@ -1009,7 +961,7 @@ BOOST_AUTO_TEST_CASE(implicit_base_to_derived_conversion) char const* text = R"( contract A { } contract B is A { - function f() { B b = A(1); } + function f() public { B b = A(1); } } )"; CHECK_ERROR(text, TypeError, "Type contract A is not implicitly convertible to expected type contract B."); @@ -1019,11 +971,11 @@ BOOST_AUTO_TEST_CASE(super_excludes_current_contract) { char const* text = R"( contract A { - function b() {} + function b() public {} } contract B is A { - function f() { + function f() public { super.f(); } } @@ -1036,7 +988,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation) { char const* text = R"( contract B { - function f() mod1(2, true) mod2("0123456") { } + function f() mod1(2, true) mod2("0123456") public { } modifier mod1(uint a, bool b) { if (b) _; } modifier mod2(bytes7 a) { while (a == "1234567") _; } } @@ -1048,7 +1000,7 @@ BOOST_AUTO_TEST_CASE(invalid_function_modifier_type) { char const* text = R"( contract B { - function f() mod1(true) { } + function f() mod1(true) public { } modifier mod1(uint a) { if (a > 0) _; } } )"; @@ -1059,7 +1011,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation_parameters) { char const* text = R"( contract B { - function f(uint8 a) mod1(a, true) mod2(r) returns (bytes7 r) { } + function f(uint8 a) mod1(a, true) mod2(r) public returns (bytes7 r) { } modifier mod1(uint a, bool b) { if (b) _; } modifier mod2(bytes7 a) { while (a == "1234567") _; } } @@ -1071,7 +1023,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation_local_variables) { char const* text = R"( contract B { - function f() mod(x) { uint x = 7; } + function f() mod(x) public { uint x = 7; } modifier mod(uint a) { if (a > 0) _; } } )"; @@ -1082,7 +1034,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_double_invocation) { char const* text = R"( contract B { - function f(uint x) mod(x) mod(2) { } + function f(uint x) mod(x) mod(2) public { } modifier mod(uint a) { if (a > 0) _; } } )"; @@ -1092,9 +1044,9 @@ BOOST_AUTO_TEST_CASE(function_modifier_double_invocation) BOOST_AUTO_TEST_CASE(base_constructor_double_invocation) { char const* text = R"( - contract C { function C(uint a) {} } + contract C { function C(uint a) public {} } contract B is C { - function B() C(2) C(2) {} + function B() C(2) C(2) public {} } )"; CHECK_ERROR(text, DeclarationError, "Base constructor already provided"); @@ -1122,7 +1074,7 @@ BOOST_AUTO_TEST_CASE(modifier_overrides_function) { char const* text = R"( contract A { modifier mod(uint a) { _; } } - contract B is A { function mod(uint a) { } } + contract B is A { function mod(uint a) public { } } )"; // Error: Identifier already declared. // Error: Override changes modifier to function. @@ -1132,7 +1084,7 @@ BOOST_AUTO_TEST_CASE(modifier_overrides_function) BOOST_AUTO_TEST_CASE(function_overrides_modifier) { char const* text = R"( - contract A { function mod(uint a) { } } + contract A { function mod(uint a) public { } } contract B is A { modifier mod(uint a) { _; } } )"; // Error: Identifier already declared. @@ -1144,7 +1096,7 @@ BOOST_AUTO_TEST_CASE(modifier_returns_value) { char const* text = R"( contract A { - function f(uint a) mod(2) returns (uint r) { } + function f(uint a) mod(2) public returns (uint r) { } modifier mod(uint a) { _; return 7; } } )"; @@ -1155,7 +1107,7 @@ BOOST_AUTO_TEST_CASE(state_variable_accessors) { char const* text = R"( contract test { - function fun() { + function fun() public { uint64(2); } uint256 public foo; @@ -1164,31 +1116,31 @@ BOOST_AUTO_TEST_CASE(state_variable_accessors) } )"; - ASTPointer source; + SourceUnit const* source; ContractDefinition const* contract; - ETH_TEST_CHECK_NO_THROW(source = parseAndAnalyse(text), "Parsing and Resolving names failed"); - BOOST_REQUIRE((contract = retrieveContract(source, 0)) != nullptr); + source = parseAndAnalyse(text); + BOOST_REQUIRE((contract = retrieveContractByName(*source, "test")) != nullptr); FunctionTypePointer function = retrieveFunctionBySignature(*contract, "foo()"); BOOST_REQUIRE(function && function->hasDeclaration()); auto returnParams = function->returnParameterTypes(); - BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "uint256"); + BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(), "uint256"); BOOST_CHECK(function->stateMutability() == StateMutability::View); function = retrieveFunctionBySignature(*contract, "map(uint256)"); BOOST_REQUIRE(function && function->hasDeclaration()); auto params = function->parameterTypes(); - BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256"); + BOOST_CHECK_EQUAL(params.at(0)->canonicalName(), "uint256"); returnParams = function->returnParameterTypes(); - BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4"); + BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(), "bytes4"); BOOST_CHECK(function->stateMutability() == StateMutability::View); function = retrieveFunctionBySignature(*contract, "multiple_map(uint256,uint256)"); BOOST_REQUIRE(function && function->hasDeclaration()); params = function->parameterTypes(); - BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256"); - BOOST_CHECK_EQUAL(params.at(1)->canonicalName(false), "uint256"); + BOOST_CHECK_EQUAL(params.at(0)->canonicalName(), "uint256"); + BOOST_CHECK_EQUAL(params.at(1)->canonicalName(), "uint256"); returnParams = function->returnParameterTypes(); - BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4"); + BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(), "bytes4"); BOOST_CHECK(function->stateMutability() == StateMutability::View); } @@ -1196,11 +1148,11 @@ BOOST_AUTO_TEST_CASE(function_clash_with_state_variable_accessor) { char const* text = R"( contract test { - function fun() { + function fun() public { uint64(2); } uint256 foo; - function foo() {} + function foo() public {} } )"; CHECK_ERROR(text, DeclarationError, "Identifier already declared."); @@ -1210,7 +1162,7 @@ BOOST_AUTO_TEST_CASE(private_state_variable) { char const* text = R"( contract test { - function fun() { + function fun() public { uint64(2); } uint256 private foo; @@ -1218,10 +1170,9 @@ BOOST_AUTO_TEST_CASE(private_state_variable) } )"; - ASTPointer source; ContractDefinition const* contract; - ETH_TEST_CHECK_NO_THROW(source = parseAndAnalyse(text), "Parsing and Resolving names failed"); - BOOST_CHECK((contract = retrieveContract(source, 0)) != nullptr); + SourceUnit const* source = parseAndAnalyse(text); + BOOST_CHECK((contract = retrieveContractByName(*source, "test")) != nullptr); FunctionTypePointer function; function = retrieveFunctionBySignature(*contract, "foo()"); BOOST_CHECK_MESSAGE(function == nullptr, "Accessor function of a private variable should not exist"); @@ -1233,7 +1184,7 @@ BOOST_AUTO_TEST_CASE(missing_state_variable) { char const* text = R"( contract Scope { - function getStateVar() constant returns (uint stateVar) { + function getStateVar() constant public returns (uint stateVar) { stateVar = Scope.stateVar; // should fail. } } @@ -1250,7 +1201,7 @@ BOOST_AUTO_TEST_CASE(base_class_state_variable_accessor) uint256 public m_aMember; } contract Child is Parent { - function foo() returns (uint256) { return Parent.m_aMember; } + function foo() public returns (uint256) { return Parent.m_aMember; } } )"; CHECK_SUCCESS(text); @@ -1264,7 +1215,7 @@ BOOST_AUTO_TEST_CASE(struct_accessor_one_array_only) Data public data; } )"; - CHECK_ERROR(sourceCode, TypeError, "Internal type is not allowed for public state variables."); + CHECK_ERROR(sourceCode, TypeError, "Internal or recursive type is not allowed for public state variables."); } BOOST_AUTO_TEST_CASE(base_class_state_variable_internal_member) @@ -1273,8 +1224,8 @@ BOOST_AUTO_TEST_CASE(base_class_state_variable_internal_member) contract Parent { uint256 internal m_aMember; } - contract Child is Parent{ - function foo() returns (uint256) { return Parent.m_aMember; } + contract Child is Parent { + function foo() public returns (uint256) { return Parent.m_aMember; } } )"; CHECK_SUCCESS(text); @@ -1286,11 +1237,11 @@ BOOST_AUTO_TEST_CASE(state_variable_member_of_wrong_class1) contract Parent1 { uint256 internal m_aMember1; } - contract Parent2 is Parent1{ + contract Parent2 is Parent1 { uint256 internal m_aMember2; } - contract Child is Parent2{ - function foo() returns (uint256) { return Parent2.m_aMember1; } + contract Child is Parent2 { + function foo() public returns (uint256) { return Parent2.m_aMember1; } } )"; CHECK_ERROR(text, TypeError, "Member \"m_aMember1\" not found or not visible after argument-dependent lookup in type(contract Parent2)"); @@ -1306,7 +1257,7 @@ BOOST_AUTO_TEST_CASE(state_variable_member_of_wrong_class2) uint256 internal m_aMember2; } contract Child is Parent2 { - function foo() returns (uint256) { return Child.m_aMember2; } + function foo() public returns (uint256) { return Child.m_aMember2; } uint256 public m_aMember3; } )"; @@ -1318,7 +1269,7 @@ BOOST_AUTO_TEST_CASE(fallback_function) char const* text = R"( contract C { uint x; - function() { x = 2; } + function() public { x = 2; } } )"; CHECK_SUCCESS(text); @@ -1329,7 +1280,7 @@ BOOST_AUTO_TEST_CASE(fallback_function_with_arguments) char const* text = R"( contract C { uint x; - function(uint a) { x = 2; } + function(uint a) public { x = 2; } } )"; CHECK_ERROR(text, TypeError, "Fallback function cannot take parameters."); @@ -1339,7 +1290,7 @@ BOOST_AUTO_TEST_CASE(fallback_function_in_library) { char const* text = R"( library C { - function() {} + function() public {} } )"; CHECK_ERROR(text, TypeError, "Libraries cannot have fallback functions."); @@ -1349,7 +1300,7 @@ BOOST_AUTO_TEST_CASE(fallback_function_with_return_parameters) { char const* text = R"( contract C { - function() returns (uint) { } + function() public returns (uint) { } } )"; CHECK_ERROR(text, TypeError, "Fallback function cannot return values."); @@ -1371,8 +1322,8 @@ BOOST_AUTO_TEST_CASE(fallback_function_twice) char const* text = R"( contract C { uint x; - function() { x = 2; } - function() { x = 3; } + function() public { x = 2; } + function() public { x = 3; } } )"; CHECK_ERROR_ALLOW_MULTI(text, DeclarationError, "Function with same name and arguments defined twice."); @@ -1383,10 +1334,10 @@ BOOST_AUTO_TEST_CASE(fallback_function_inheritance) char const* text = R"( contract A { uint x; - function() { x = 1; } + function() public { x = 1; } } contract C is A { - function() { x = 2; } + function() public { x = 2; } } )"; CHECK_SUCCESS(text); @@ -1397,7 +1348,7 @@ BOOST_AUTO_TEST_CASE(event) char const* text = R"( contract c { event e(uint indexed a, bytes3 indexed s, bool indexed b); - function f() { e(2, "abc", true); } + function f() public { e(2, "abc", true); } } )"; CHECK_SUCCESS(text); @@ -1449,7 +1400,7 @@ BOOST_AUTO_TEST_CASE(event_call) char const* text = R"( contract c { event e(uint a, bytes3 indexed s, bool indexed b); - function f() { e(2, "abc", true); } + function f() public { e(2, "abc", true); } } )"; CHECK_SUCCESS(text); @@ -1459,7 +1410,7 @@ BOOST_AUTO_TEST_CASE(event_function_inheritance_clash) { char const* text = R"( contract A { - function dup() returns (uint) { + function dup() public returns (uint) { return 1; } } @@ -1479,7 +1430,7 @@ BOOST_AUTO_TEST_CASE(function_event_inheritance_clash) event dup(); } contract A { - function dup() returns (uint) { + function dup() public returns (uint) { return 1; } } @@ -1494,7 +1445,7 @@ BOOST_AUTO_TEST_CASE(function_event_in_contract_clash) char const* text = R"( contract A { event dup(); - function dup() returns (uint) { + function dup() public returns (uint) { return 1; } } @@ -1509,7 +1460,7 @@ BOOST_AUTO_TEST_CASE(event_inheritance) event e(uint a, bytes3 indexed s, bool indexed b); } contract c is base { - function f() { e(2, "abc", true); } + function f() public { e(2, "abc", true); } } )"; CHECK_SUCCESS(text); @@ -1530,10 +1481,10 @@ BOOST_AUTO_TEST_CASE(access_to_default_function_visibility) { char const* text = R"( contract c { - function f() {} + function f() public {} } contract d { - function g() { c(0).f(); } + function g() public { c(0).f(); } } )"; CHECK_SUCCESS(text); @@ -1546,7 +1497,7 @@ BOOST_AUTO_TEST_CASE(access_to_internal_function) function f() internal {} } contract d { - function g() { c(0).f(); } + function g() public { c(0).f(); } } )"; CHECK_ERROR(text, TypeError, "Member \"f\" not found or not visible after argument-dependent lookup in contract c"); @@ -1559,7 +1510,7 @@ BOOST_AUTO_TEST_CASE(access_to_default_state_variable_visibility) uint a; } contract d { - function g() { c(0).a(); } + function g() public { c(0).a(); } } )"; CHECK_ERROR(text, TypeError, "Member \"a\" not found or not visible after argument-dependent lookup in contract c"); @@ -1572,7 +1523,7 @@ BOOST_AUTO_TEST_CASE(access_to_internal_state_variable) uint public a; } contract d { - function g() { c(0).a(); } + function g() public { c(0).a(); } } )"; CHECK_SUCCESS(text); @@ -1582,10 +1533,10 @@ BOOST_AUTO_TEST_CASE(error_count_in_named_args) { char const* sourceCode = R"( contract test { - function a(uint a, uint b) returns (uint r) { + function a(uint a, uint b) public returns (uint r) { r = a + b; } - function b() returns (uint r) { + function b() public returns (uint r) { r = a({a: 1}); } } @@ -1597,10 +1548,10 @@ BOOST_AUTO_TEST_CASE(empty_in_named_args) { char const* sourceCode = R"( contract test { - function a(uint a, uint b) returns (uint r) { + function a(uint a, uint b) public returns (uint r) { r = a + b; } - function b() returns (uint r) { + function b() public returns (uint r) { r = a({}); } } @@ -1612,10 +1563,10 @@ BOOST_AUTO_TEST_CASE(duplicate_parameter_names_in_named_args) { char const* sourceCode = R"( contract test { - function a(uint a, uint b) returns (uint r) { + function a(uint a, uint b) public returns (uint r) { r = a + b; } - function b() returns (uint r) { + function b() public returns (uint r) { r = a({a: 1, a: 2}); } } @@ -1627,10 +1578,10 @@ BOOST_AUTO_TEST_CASE(invalid_parameter_names_in_named_args) { char const* sourceCode = R"( contract test { - function a(uint a, uint b) returns (uint r) { + function a(uint a, uint b) public returns (uint r) { r = a + b; } - function b() returns (uint r) { + function b() public returns (uint r) { r = a({a: 1, c: 2}); } } @@ -1642,7 +1593,7 @@ BOOST_AUTO_TEST_CASE(empty_name_input_parameter) { char const* text = R"( contract test { - function f(uint) { } + function f(uint) public { } } )"; CHECK_SUCCESS(text); @@ -1652,7 +1603,7 @@ BOOST_AUTO_TEST_CASE(constant_input_parameter) { char const* text = R"( contract test { - function f(uint[] constant a) { } + function f(uint[] constant a) public { } } )"; CHECK_ERROR_ALLOW_MULTI(text, TypeError, "Illegal use of \"constant\" specifier."); @@ -1662,7 +1613,7 @@ BOOST_AUTO_TEST_CASE(empty_name_return_parameter) { char const* text = R"( contract test { - function f() returns(bool) { } + function f() public returns (bool) { } } )"; CHECK_SUCCESS(text); @@ -1672,7 +1623,7 @@ BOOST_AUTO_TEST_CASE(empty_name_input_parameter_with_named_one) { char const* text = R"( contract test { - function f(uint, uint k) returns(uint ret_k) { + function f(uint, uint k) public returns (uint ret_k) { return k; } } @@ -1684,7 +1635,7 @@ BOOST_AUTO_TEST_CASE(empty_name_return_parameter_with_named_one) { char const* text = R"( contract test { - function f() returns(uint ret_k, uint) { + function f() public returns (uint ret_k, uint) { return 5; } } @@ -1696,7 +1647,7 @@ BOOST_AUTO_TEST_CASE(disallow_declaration_of_void_type) { char const* sourceCode = R"( contract c { - function f() { var (x) = f(); } + function f() public { var (x) = f(); } } )"; CHECK_ERROR(sourceCode, TypeError, "Not enough components (0) in value to assign all variables (1)."); @@ -1706,17 +1657,16 @@ BOOST_AUTO_TEST_CASE(overflow_caused_by_ether_units) { char const* sourceCodeFine = R"( contract c { - function c () { + function c () public { a = 115792089237316195423570985008687907853269984665640564039458; } uint256 a; } )"; - ETH_TEST_CHECK_NO_THROW(parseAndAnalyse(sourceCodeFine), - "Parsing and Resolving names failed"); + CHECK_SUCCESS(sourceCodeFine); char const* sourceCode = R"( contract c { - function c () { + function c () public { a = 115792089237316195423570985008687907853269984665640564039458 ether; } uint256 a; @@ -1729,7 +1679,7 @@ BOOST_AUTO_TEST_CASE(exp_operator_exponent_too_big) { char const* sourceCode = R"( contract test { - function f() returns(uint d) { return 2 ** 10000000000; } + function f() public returns (uint d) { return 2 ** 10000000000; } } )"; CHECK_ERROR(sourceCode, TypeError, "Operator ** not compatible with types int_const 2 and int_const 10000000000"); @@ -1739,7 +1689,7 @@ BOOST_AUTO_TEST_CASE(exp_warn_literal_base) { char const* sourceCode = R"( contract test { - function f() returns(uint) { + function f() pure public returns(uint) { uint8 x = 100; return 10**x; } @@ -1748,7 +1698,7 @@ BOOST_AUTO_TEST_CASE(exp_warn_literal_base) CHECK_WARNING(sourceCode, "might overflow"); sourceCode = R"( contract test { - function f() returns(uint) { + function f() pure public returns(uint) { uint8 x = 100; return uint8(10)**x; } @@ -1757,7 +1707,7 @@ BOOST_AUTO_TEST_CASE(exp_warn_literal_base) CHECK_SUCCESS(sourceCode); sourceCode = R"( contract test { - function f() returns(uint) { + function f() pure public returns(uint) { return 2**80; } } @@ -1769,7 +1719,7 @@ BOOST_AUTO_TEST_CASE(shift_warn_literal_base) { char const* sourceCode = R"( contract test { - function f() returns(uint) { + function f() pure public returns(uint) { uint8 x = 100; return 10 << x; } @@ -1778,7 +1728,7 @@ BOOST_AUTO_TEST_CASE(shift_warn_literal_base) CHECK_WARNING(sourceCode, "might overflow"); sourceCode = R"( contract test { - function f() returns(uint) { + function f() pure public returns(uint) { uint8 x = 100; return uint8(10) << x; } @@ -1787,7 +1737,7 @@ BOOST_AUTO_TEST_CASE(shift_warn_literal_base) CHECK_SUCCESS(sourceCode); sourceCode = R"( contract test { - function f() returns(uint) { + function f() pure public returns(uint) { return 2 << 80; } } @@ -1795,7 +1745,7 @@ BOOST_AUTO_TEST_CASE(shift_warn_literal_base) CHECK_SUCCESS(sourceCode); sourceCode = R"( contract test { - function f() returns(uint) { + function f() pure public returns(uint) { uint8 x = 100; return 10 >> x; } @@ -1808,7 +1758,7 @@ BOOST_AUTO_TEST_CASE(warn_var_from_zero) { char const* sourceCode = R"( contract test { - function f() returns (uint) { + function f() pure public returns (uint) { var i = 1; return i; } @@ -1817,7 +1767,7 @@ BOOST_AUTO_TEST_CASE(warn_var_from_zero) CHECK_WARNING(sourceCode, "uint8, which can hold values between 0 and 255"); sourceCode = R"( contract test { - function f() { + function f() pure public { var i = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; i; } @@ -1826,7 +1776,7 @@ BOOST_AUTO_TEST_CASE(warn_var_from_zero) CHECK_WARNING(sourceCode, "uint256, which can hold values between 0 and 115792089237316195423570985008687907853269984665640564039457584007913129639935"); sourceCode = R"( contract test { - function f() { + function f() pure public { var i = -2; i; } @@ -1835,7 +1785,7 @@ BOOST_AUTO_TEST_CASE(warn_var_from_zero) CHECK_WARNING(sourceCode, "int8, which can hold values between -128 and 127"); sourceCode = R"( contract test { - function f() { + function f() pure public { for (var i = 0; i < msg.data.length; i++) { } } } @@ -1865,7 +1815,7 @@ BOOST_AUTO_TEST_CASE(enum_member_access_accross_contracts) enum MyEnum { One, Two } } contract Impl { - function test() returns (Interface.MyEnum) { + function test() public returns (Interface.MyEnum) { return Interface.MyEnum.One; } } @@ -1878,7 +1828,7 @@ BOOST_AUTO_TEST_CASE(enum_invalid_member_access) char const* text = R"( contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } - function test() { + function test() public { choices = ActionChoices.RunAroundWavingYourHands; } ActionChoices choices; @@ -1892,7 +1842,7 @@ BOOST_AUTO_TEST_CASE(enum_invalid_direct_member_access) char const* text = R"( contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } - function test() { + function test() public { choices = Sit; } ActionChoices choices; @@ -1906,7 +1856,7 @@ BOOST_AUTO_TEST_CASE(enum_explicit_conversion_is_okay) char const* text = R"( contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } - function test() { + function test() public { a = uint256(ActionChoices.GoStraight); b = uint64(ActionChoices.Sit); } @@ -1922,7 +1872,7 @@ BOOST_AUTO_TEST_CASE(int_to_enum_explicit_conversion_is_okay) char const* text = R"( contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } - function test() { + function test() public { a = 2; b = ActionChoices(a); } @@ -1938,7 +1888,7 @@ BOOST_AUTO_TEST_CASE(enum_implicit_conversion_is_not_okay_256) char const* text = R"( contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } - function test() { + function test() public { a = ActionChoices.GoStraight; } uint256 a; @@ -1952,7 +1902,7 @@ BOOST_AUTO_TEST_CASE(enum_implicit_conversion_is_not_okay_64) char const* text = R"( contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, Sit } - function test() { + function test() public { b = ActionChoices.Sit; } uint64 b; @@ -1967,7 +1917,7 @@ BOOST_AUTO_TEST_CASE(enum_to_enum_conversion_is_not_okay) contract test { enum Paper { Up, Down, Left, Right } enum Ground { North, South, West, East } - function test() { + function test() public { Ground(Paper.Up); } } @@ -1994,7 +1944,7 @@ BOOST_AUTO_TEST_CASE(enum_name_resolution_under_current_contract_name) Second } - function a() { + function a() public { A.Foo; } } @@ -2009,7 +1959,7 @@ BOOST_AUTO_TEST_CASE(private_visibility) function f() private {} } contract derived is base { - function g() { f(); } + function g() public { f(); } } )"; CHECK_ERROR(sourceCode, DeclarationError, "Undeclared identifier."); @@ -2022,7 +1972,7 @@ BOOST_AUTO_TEST_CASE(private_visibility_via_explicit_base_access) function f() private {} } contract derived is base { - function g() { base.f(); } + function g() public { base.f(); } } )"; CHECK_ERROR(sourceCode, TypeError, "Member \"f\" not found or not visible after argument-dependent lookup in type(contract base)"); @@ -2033,7 +1983,7 @@ BOOST_AUTO_TEST_CASE(external_visibility) char const* sourceCode = R"( contract c { function f() external {} - function g() { f(); } + function g() public { f(); } } )"; CHECK_ERROR(sourceCode, DeclarationError, "Undeclared identifier."); @@ -2046,7 +1996,7 @@ BOOST_AUTO_TEST_CASE(external_base_visibility) function f() external {} } contract derived is base { - function g() { base.f(); } + function g() public { base.f(); } } )"; CHECK_ERROR(sourceCode, TypeError, "Member \"f\" not found or not visible after argument-dependent lookup in type(contract base)"); @@ -2092,14 +2042,14 @@ BOOST_AUTO_TEST_CASE(test_for_bug_override_function_with_bytearray_type) function f(bytes) external returns (uint256 r) {r = 42;} } )"; - ETH_TEST_CHECK_NO_THROW(parseAndAnalyse(sourceCode), "Parsing and Name Resolving failed"); + CHECK_SUCCESS(sourceCode); } BOOST_AUTO_TEST_CASE(array_with_nonconstant_length) { char const* text = R"( contract c { - function f(uint a) { uint8[a] x; } + function f(uint a) public { uint8[a] x; } } )"; CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal."); @@ -2109,7 +2059,7 @@ BOOST_AUTO_TEST_CASE(array_with_negative_length) { char const* text = R"( contract c { - function f(uint a) { uint8[-1] x; } + function f(uint a) public { uint8[-1] x; } } )"; CHECK_ERROR(text, TypeError, "Array with negative length specified"); @@ -2121,7 +2071,7 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types1) contract c { bytes a; uint[] b; - function f() { b = a; } + function f() public { b = a; } } )"; CHECK_ERROR(text, TypeError, "Type bytes storage ref is not implicitly convertible to expected type uint256[] storage ref."); @@ -2133,7 +2083,7 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types2) contract c { uint32[] a; uint8[] b; - function f() { b = a; } + function f() public { b = a; } } )"; CHECK_ERROR(text, TypeError, "Type uint32[] storage ref is not implicitly convertible to expected type uint8[] storage ref."); @@ -2145,7 +2095,7 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types_conversion_possible) contract c { uint32[] a; uint8[] b; - function f() { a = b; } + function f() public { a = b; } } )"; CHECK_SUCCESS(text); @@ -2157,7 +2107,7 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types_static_dynamic) contract c { uint32[] a; uint8[80] b; - function f() { a = b; } + function f() public { a = b; } } )"; CHECK_SUCCESS(text); @@ -2169,7 +2119,7 @@ BOOST_AUTO_TEST_CASE(array_copy_with_different_types_dynamic_static) contract c { uint[] a; uint[80] b; - function f() { b = a; } + function f() public { b = a; } } )"; CHECK_ERROR(text, TypeError, "Type uint256[] storage ref is not implicitly convertible to expected type uint256[80] storage ref."); @@ -2309,10 +2259,10 @@ BOOST_AUTO_TEST_CASE(test_byte_is_alias_of_byte1) char const* text = R"( contract c { bytes arr; - function f() { byte a = arr[0];} + function f() public { byte a = arr[0];} } )"; - ETH_TEST_REQUIRE_NO_THROW(parseAndAnalyse(text), "Type resolving failed"); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(warns_assigning_decimal_to_bytesxx) @@ -2349,7 +2299,7 @@ BOOST_AUTO_TEST_CASE(assigning_value_to_const_variable) { char const* text = R"( contract Foo { - function changeIt() { x = 9; } + function changeIt() public { x = 9; } uint constant x = 56; } )"; @@ -2372,7 +2322,7 @@ BOOST_AUTO_TEST_CASE(constant_string_literal_disallows_assignment) char const* text = R"( contract Test { string constant x = "abefghijklmnopqabcdefghijklmnopqabcdefghijklmnopqabca"; - function f() { + function f() public { x[0] = "f"; } } @@ -2435,6 +2385,18 @@ BOOST_AUTO_TEST_CASE(assignment_to_const_array_vars) CHECK_ERROR(text, TypeError, "implemented"); } +BOOST_AUTO_TEST_CASE(assignment_to_const_string_bytes) +{ + char const* text = R"( + contract C { + bytes constant a = "\x00\x01\x02"; + bytes constant b = hex"000102"; + string constant c = "hello"; + } + )"; + CHECK_SUCCESS(text); +} + BOOST_AUTO_TEST_CASE(constant_struct) { char const* text = R"( @@ -2470,9 +2432,9 @@ BOOST_AUTO_TEST_CASE(overloaded_function_cannot_resolve) { char const* sourceCode = R"( contract test { - function f() returns(uint) { return 1; } - function f(uint a) returns(uint) { return a; } - function g() returns(uint) { return f(3, 5); } + function f() public returns (uint) { return 1; } + function f(uint a) public returns (uint) { return a; } + function g() public returns (uint) { return f(3, 5); } } )"; CHECK_ERROR(sourceCode, TypeError, "No matching declaration found after argument-dependent lookup."); @@ -2483,9 +2445,9 @@ BOOST_AUTO_TEST_CASE(ambiguous_overloaded_function) // literal 1 can be both converted to uint and uint8, so the call is ambiguous. char const* sourceCode = R"( contract test { - function f(uint8 a) returns(uint) { return a; } - function f(uint a) returns(uint) { return 2*a; } - function g() returns(uint) { return f(1); } + function f(uint8 a) public returns (uint) { return a; } + function f(uint a) public returns (uint) { return 2*a; } + function g() public returns (uint) { return f(1); } } )"; CHECK_ERROR(sourceCode, TypeError, "No unique declaration found after argument-dependent lookup."); @@ -2495,20 +2457,20 @@ BOOST_AUTO_TEST_CASE(assignment_of_nonoverloaded_function) { char const* sourceCode = R"( contract test { - function f(uint a) returns(uint) { return 2 * a; } - function g() returns(uint) { var x = f; return x(7); } + function f(uint a) public returns (uint) { return 2 * a; } + function g() public returns (uint) { var x = f; return x(7); } } )"; - ETH_TEST_REQUIRE_NO_THROW(parseAndAnalyse(sourceCode), "Type resolving failed"); + CHECK_SUCCESS(sourceCode); } BOOST_AUTO_TEST_CASE(assignment_of_overloaded_function) { char const* sourceCode = R"( contract test { - function f() returns(uint) { return 1; } - function f(uint a) returns(uint) { return 2 * a; } - function g() returns(uint) { var x = f; return x(7); } + function f() public returns (uint) { return 1; } + function f(uint a) public returns (uint) { return 2 * a; } + function g() public returns (uint) { var x = f; return x(7); } } )"; CHECK_ERROR(sourceCode, TypeError, "No matching declaration found after variable lookup."); @@ -2519,10 +2481,10 @@ BOOST_AUTO_TEST_CASE(external_types_clash) char const* sourceCode = R"( contract base { enum a { X } - function f(a) { } + function f(a) public { } } contract test is base { - function f(uint8 a) { } + function f(uint8 a) public { } } )"; CHECK_ERROR(sourceCode, TypeError, "Function overload clash during conversion to external types for arguments."); @@ -2532,10 +2494,10 @@ BOOST_AUTO_TEST_CASE(override_changes_return_types) { char const* sourceCode = R"( contract base { - function f(uint a) returns (uint) { } + function f(uint a) public returns (uint) { } } contract test is base { - function f(uint a) returns (uint8) { } + function f(uint a) public returns (uint8) { } } )"; CHECK_ERROR(sourceCode, TypeError, "Overriding function return types differ"); @@ -2545,8 +2507,8 @@ BOOST_AUTO_TEST_CASE(multiple_constructors) { char const* sourceCode = R"( contract test { - function test(uint a) { } - function test() {} + function test(uint a) public { } + function test() public {} } )"; CHECK_ERROR(sourceCode, DeclarationError, "More than one constructor defined"); @@ -2556,7 +2518,7 @@ BOOST_AUTO_TEST_CASE(equal_overload) { char const* sourceCode = R"( contract C { - function test(uint a) returns (uint b) { } + function test(uint a) public returns (uint b) { } function test(uint a) external {} } )"; @@ -2567,7 +2529,7 @@ BOOST_AUTO_TEST_CASE(uninitialized_var) { char const* sourceCode = R"( contract C { - function f() returns (uint) { var x; return 2; } + function f() public returns (uint) { var x; return 2; } } )"; CHECK_ERROR(sourceCode, TypeError, "Assignment necessary for type detection."); @@ -2619,7 +2581,7 @@ BOOST_AUTO_TEST_CASE(string_index) char const* sourceCode = R"( contract C { string s; - function f() { var a = s[2]; } + function f() public { var a = s[2]; } } )"; CHECK_ERROR(sourceCode, TypeError, "Index access for string is not possible."); @@ -2630,7 +2592,7 @@ BOOST_AUTO_TEST_CASE(string_length) char const* sourceCode = R"( contract C { string s; - function f() { var a = s.length; } + function f() public { var a = s.length; } } )"; CHECK_ERROR(sourceCode, TypeError, "Member \"length\" not found or not visible after argument-dependent lookup in string storage ref"); @@ -2699,15 +2661,15 @@ BOOST_AUTO_TEST_CASE(positive_integers_to_unsigned_out_of_bound) BOOST_AUTO_TEST_CASE(integer_boolean_operators) { char const* sourceCode1 = R"( - contract test { function() { uint x = 1; uint y = 2; x || y; } } + contract test { function() public { uint x = 1; uint y = 2; x || y; } } )"; CHECK_ERROR(sourceCode1, TypeError, "Operator || not compatible with types uint256 and uint256"); char const* sourceCode2 = R"( - contract test { function() { uint x = 1; uint y = 2; x && y; } } + contract test { function() public { uint x = 1; uint y = 2; x && y; } } )"; CHECK_ERROR(sourceCode2, TypeError, "Operator && not compatible with types uint256 and uint256"); char const* sourceCode3 = R"( - contract test { function() { uint x = 1; !x; } } + contract test { function() public { uint x = 1; !x; } } )"; CHECK_ERROR(sourceCode3, TypeError, "Unary operator ! cannot be applied to type uint256"); } @@ -2715,15 +2677,15 @@ BOOST_AUTO_TEST_CASE(integer_boolean_operators) BOOST_AUTO_TEST_CASE(exp_signed_variable) { char const* sourceCode1 = R"( - contract test { function() { uint x = 3; int y = -4; x ** y; } } + contract test { function() public { uint x = 3; int y = -4; x ** y; } } )"; CHECK_ERROR(sourceCode1, TypeError, "Operator ** not compatible with types uint256 and int256"); char const* sourceCode2 = R"( - contract test { function() { uint x = 3; int y = -4; y ** x; } } + contract test { function() public { uint x = 3; int y = -4; y ** x; } } )"; CHECK_ERROR(sourceCode2, TypeError, "Operator ** not compatible with types int256 and uint256"); char const* sourceCode3 = R"( - contract test { function() { int x = -3; int y = -4; x ** y; } } + contract test { function() public { int x = -3; int y = -4; x ** y; } } )"; CHECK_ERROR(sourceCode3, TypeError, "Operator ** not compatible with types int256 and int256"); } @@ -2731,11 +2693,11 @@ BOOST_AUTO_TEST_CASE(exp_signed_variable) BOOST_AUTO_TEST_CASE(reference_compare_operators) { char const* sourceCode1 = R"( - contract test { bytes a; bytes b; function() { a == b; } } + contract test { bytes a; bytes b; function() public { a == b; } } )"; CHECK_ERROR(sourceCode1, TypeError, "Operator == not compatible with types bytes storage ref and bytes storage ref"); char const* sourceCode2 = R"( - contract test { struct s {uint a;} s x; s y; function() { x == y; } } + contract test { struct s {uint a;} s x; s y; function() public { x == y; } } )"; CHECK_ERROR(sourceCode2, TypeError, "Operator == not compatible with types struct test.s storage ref and struct test.s storage ref"); } @@ -2764,7 +2726,7 @@ BOOST_AUTO_TEST_CASE(storage_location_local_variables) { char const* sourceCode = R"( contract C { - function f() { + function f() public { uint[] storage x; uint[] memory y; uint[] memory z; @@ -2779,7 +2741,7 @@ BOOST_AUTO_TEST_CASE(no_mappings_in_memory_array) { char const* sourceCode = R"( contract C { - function f() { + function f() public { mapping(uint=>uint)[] memory x; } } @@ -2792,7 +2754,7 @@ BOOST_AUTO_TEST_CASE(assignment_mem_to_local_storage_variable) char const* sourceCode = R"( contract C { uint[] data; - function f(uint[] x) { + function f(uint[] x) public { var dataRef = data; dataRef = x; } @@ -2807,7 +2769,7 @@ BOOST_AUTO_TEST_CASE(storage_assign_to_different_local_variable) contract C { uint[] data; uint8[] otherData; - function f() { + function f() public { uint8[] storage x = otherData; uint[] storage y = data; y = x; @@ -2822,7 +2784,7 @@ BOOST_AUTO_TEST_CASE(uninitialized_mapping_variable) { char const* sourceCode = R"( contract C { - function f() { + function f() public { mapping(uint => uint) x; x; } @@ -2835,7 +2797,7 @@ BOOST_AUTO_TEST_CASE(uninitialized_mapping_array_variable) { char const* sourceCode = R"( contract C { - function f() { + function f() pure public { mapping(uint => uint)[] storage x; x; } @@ -2849,7 +2811,7 @@ BOOST_AUTO_TEST_CASE(no_delete_on_storage_pointers) char const* sourceCode = R"( contract C { uint[] data; - function f() { + function f() public { var x = data; delete x; } @@ -2863,7 +2825,7 @@ BOOST_AUTO_TEST_CASE(assignment_mem_storage_variable_directly) char const* sourceCode = R"( contract C { uint[] data; - function f(uint[] x) { + function f(uint[] x) public { data = x; } } @@ -2877,7 +2839,7 @@ BOOST_AUTO_TEST_CASE(function_argument_mem_to_storage) contract C { function f(uint[] storage x) private { } - function g(uint[] x) { + function g(uint[] x) public { f(x); } } @@ -2892,7 +2854,7 @@ BOOST_AUTO_TEST_CASE(function_argument_storage_to_mem) function f(uint[] storage x) private { g(x); } - function g(uint[] x) { + function g(uint[] x) public { } } )"; @@ -2918,8 +2880,8 @@ BOOST_AUTO_TEST_CASE(dynamic_return_types_not_possible) { char const* sourceCode = R"( contract C { - function f(uint) returns (string); - function g() { + function f(uint) public returns (string); + function g() public { var (x,) = this.f(2); // we can assign to x but it is not usable. bytes(x).length; @@ -2933,7 +2895,7 @@ BOOST_AUTO_TEST_CASE(memory_arrays_not_resizeable) { char const* sourceCode = R"( contract C { - function f() { + function f() public { uint[] memory x; x.length = 2; } @@ -2947,7 +2909,7 @@ BOOST_AUTO_TEST_CASE(struct_constructor) char const* sourceCode = R"( contract C { struct S { uint a; bool x; } - function f() { + function f() public { S memory s = S(1, true); } } @@ -2961,7 +2923,7 @@ BOOST_AUTO_TEST_CASE(struct_constructor_nested) contract C { struct X { uint x1; uint x2; } struct S { uint s1; uint[3] s2; X s3; } - function f() { + function f() public { uint[3] memory s2; S memory s = S(1, s2, X(4, 5)); } @@ -2975,7 +2937,7 @@ BOOST_AUTO_TEST_CASE(struct_named_constructor) char const* sourceCode = R"( contract C { struct S { uint a; bool x; } - function f() { + function f() public { S memory s = S({a: 1, x: true}); } } @@ -2987,7 +2949,7 @@ BOOST_AUTO_TEST_CASE(literal_strings) { char const* text = R"( contract Foo { - function f() { + function f() public { string memory long = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; string memory short = "123"; long; short; @@ -3003,7 +2965,7 @@ BOOST_AUTO_TEST_CASE(memory_structs_with_mappings) contract Test { struct S { uint8 a; mapping(uint => uint) b; uint8 c; } S s; - function f() { + function f() public { S memory x; x.b[1]; } @@ -3077,10 +3039,10 @@ BOOST_AUTO_TEST_CASE(call_to_library_function) { char const* text = R"( library Lib { - function min(uint, uint) returns (uint); + function min(uint, uint) public returns (uint); } contract Test { - function f() { + function f() public { uint t = Lib.min(12, 7); } } @@ -3092,7 +3054,7 @@ BOOST_AUTO_TEST_CASE(creating_contract_within_the_contract) { char const* sourceCode = R"( contract Test { - function f() { var x = new Test(); } + function f() public { var x = new Test(); } } )"; CHECK_ERROR(sourceCode, TypeError, "Circular reference for contract creation (cannot create instance of derived or same contract)."); @@ -3103,7 +3065,7 @@ BOOST_AUTO_TEST_CASE(array_out_of_bound_access) char const* text = R"( contract c { uint[2] dataArray; - function set5th() returns (bool) { + function set5th() public returns (bool) { dataArray[5] = 2; return true; } @@ -3116,7 +3078,7 @@ BOOST_AUTO_TEST_CASE(literal_string_to_storage_pointer) { char const* text = R"( contract C { - function f() { string x = "abc"; } + function f() public { string x = "abc"; } } )"; CHECK_ERROR(text, TypeError, "Type literal_string \"abc\" is not implicitly convertible to expected type string storage pointer."); @@ -3127,11 +3089,10 @@ BOOST_AUTO_TEST_CASE(non_initialized_references) char const* text = R"( contract c { - struct s{ + struct s { uint a; } - function f() - { + function f() public { s storage x; x.a = 2; } @@ -3146,7 +3107,7 @@ BOOST_AUTO_TEST_CASE(keccak256_with_large_integer_constant) char const* text = R"( contract c { - function f() { keccak256(2**500); } + function f() public { keccak256(2**500); } } )"; CHECK_ERROR(text, TypeError, "Invalid rational number (too large or division by zero)."); @@ -3155,9 +3116,9 @@ BOOST_AUTO_TEST_CASE(keccak256_with_large_integer_constant) BOOST_AUTO_TEST_CASE(cyclic_binary_dependency) { char const* text = R"( - contract A { function f() { new B(); } } - contract B { function f() { new C(); } } - contract C { function f() { new A(); } } + contract A { function f() public { new B(); } } + contract B { function f() public { new C(); } } + contract C { function f() public { new A(); } } )"; CHECK_ERROR(text, TypeError, "Circular reference for contract creation (cannot create instance of derived or same contract)."); } @@ -3166,8 +3127,8 @@ BOOST_AUTO_TEST_CASE(cyclic_binary_dependency_via_inheritance) { char const* text = R"( contract A is B { } - contract B { function f() { new C(); } } - contract C { function f() { new A(); } } + contract B { function f() public { new C(); } } + contract C { function f() public { new A(); } } )"; CHECK_ERROR(text, TypeError, "Definition of base has to precede definition of derived contract"); } @@ -3175,7 +3136,7 @@ BOOST_AUTO_TEST_CASE(cyclic_binary_dependency_via_inheritance) BOOST_AUTO_TEST_CASE(multi_variable_declaration_fail) { char const* text = R"( - contract C { function f() { var (x,y); x = 1; y = 1;} } + contract C { function f() public { var (x,y); x = 1; y = 1;} } )"; CHECK_ERROR(text, TypeError, "Assignment necessary for type detection."); } @@ -3184,10 +3145,10 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fine) { char const* text = R"( contract C { - function three() returns (uint, uint, uint); - function two() returns (uint, uint); + function three() public returns (uint, uint, uint); + function two() public returns (uint, uint); function none(); - function f() { + function f() public { var (a,) = three(); var (b,c,) = two(); var (,d) = three(); @@ -3205,8 +3166,8 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_1) { char const* text = R"( contract C { - function one() returns (uint); - function f() { var (a, b, ) = one(); } + function one() public returns (uint); + function f() public { var (a, b, ) = one(); } } )"; CHECK_ERROR(text, TypeError, "Not enough components (1) in value to assign all variables (2)."); @@ -3215,8 +3176,8 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_2) { char const* text = R"( contract C { - function one() returns (uint); - function f() { var (a, , ) = one(); } + function one() public returns (uint); + function f() public { var (a, , ) = one(); } } )"; CHECK_ERROR(text, TypeError, "Not enough components (1) in value to assign all variables (2)."); @@ -3226,8 +3187,8 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_3) { char const* text = R"( contract C { - function one() returns (uint); - function f() { var (, , a) = one(); } + function one() public returns (uint); + function f() public { var (, , a) = one(); } } )"; CHECK_ERROR(text, TypeError, "Not enough components (1) in value to assign all variables (2)."); @@ -3237,8 +3198,8 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_4) { char const* text = R"( contract C { - function one() returns (uint); - function f() { var (, a, b) = one(); } + function one() public returns (uint); + function f() public { var (, a, b) = one(); } } )"; CHECK_ERROR(text, TypeError, "Not enough components (1) in value to assign all variables (2)."); @@ -3248,7 +3209,7 @@ BOOST_AUTO_TEST_CASE(tuples) { char const* text = R"( contract C { - function f() { + function f() public { uint a = (1); var (b,) = (uint8(1),); var (c,d) = (uint32(1), 2 + a); @@ -3264,7 +3225,7 @@ BOOST_AUTO_TEST_CASE(tuples_empty_components) { char const* text = R"( contract C { - function f() { + function f() public { (1,,2); } } @@ -3276,8 +3237,8 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_5) { char const* text = R"( contract C { - function one() returns (uint); - function f() { var (,) = one(); } + function one() public returns (uint); + function f() public { var (,) = one(); } } )"; CHECK_ERROR(text, TypeError, "Wildcard both at beginning and end of variable declaration list is only allowed if the number of components is equal."); @@ -3287,8 +3248,8 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_6) { char const* text = R"( contract C { - function two() returns (uint, uint); - function f() { var (a, b, c) = two(); } + function two() public returns (uint, uint); + function f() public { var (a, b, c) = two(); } } )"; CHECK_ERROR(text, TypeError, "Not enough components (2) in value to assign all variables (3)"); @@ -3298,8 +3259,8 @@ BOOST_AUTO_TEST_CASE(tuple_assignment_from_void_function) { char const* text = R"( contract C { - function f() { } - function g() { + function f() public { } + function g() public { var (x,) = (f(), f()); } } @@ -3311,7 +3272,7 @@ BOOST_AUTO_TEST_CASE(tuple_compound_assignment) { char const* text = R"( contract C { - function f() returns (uint a, uint b) { + function f() public returns (uint a, uint b) { (a, b) += (1, 1); } } @@ -3326,7 +3287,7 @@ BOOST_AUTO_TEST_CASE(member_access_parser_ambiguity) struct R { uint[10][10] y; } struct S { uint a; uint b; uint[20][20][20] c; R d; } S data; - function f() { + function f() public { C.S x = data; C.S memory y; C.S[10] memory z; @@ -3365,10 +3326,10 @@ BOOST_AUTO_TEST_CASE(using_for_not_library) BOOST_AUTO_TEST_CASE(using_for_function_exists) { char const* text = R"( - library D { function double(uint self) returns (uint) { return 2*self; } } + library D { function double(uint self) public returns (uint) { return 2*self; } } contract C { using D for uint; - function f(uint a) { + function f(uint a) public { a.double; } } @@ -3379,10 +3340,10 @@ BOOST_AUTO_TEST_CASE(using_for_function_exists) BOOST_AUTO_TEST_CASE(using_for_function_on_int) { char const* text = R"( - library D { function double(uint self) returns (uint) { return 2*self; } } + library D { function double(uint self) public returns (uint) { return 2*self; } } contract C { using D for uint; - function f(uint a) returns (uint) { + function f(uint a) public returns (uint) { return a.double(); } } @@ -3393,11 +3354,11 @@ BOOST_AUTO_TEST_CASE(using_for_function_on_int) BOOST_AUTO_TEST_CASE(using_for_function_on_struct) { char const* text = R"( - library D { struct s { uint a; } function mul(s storage self, uint x) returns (uint) { return self.a *= x; } } + library D { struct s { uint a; } function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } } contract C { using D for D.s; D.s x; - function f(uint a) returns (uint) { + function f(uint a) public returns (uint) { return x.mul(a); } } @@ -3410,13 +3371,13 @@ BOOST_AUTO_TEST_CASE(using_for_overload) char const* text = R"( library D { struct s { uint a; } - function mul(s storage self, uint x) returns (uint) { return self.a *= x; } - function mul(s storage, bytes32) returns (bytes32) { } + function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } + function mul(s storage, bytes32) public returns (bytes32) { } } contract C { using D for D.s; D.s x; - function f(uint a) returns (uint) { + function f(uint a) public returns (uint) { return x.mul(a); } } @@ -3427,11 +3388,11 @@ BOOST_AUTO_TEST_CASE(using_for_overload) BOOST_AUTO_TEST_CASE(using_for_by_name) { char const* text = R"( - library D { struct s { uint a; } function mul(s storage self, uint x) returns (uint) { return self.a *= x; } } + library D { struct s { uint a; } function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } } contract C { using D for D.s; D.s x; - function f(uint a) returns (uint) { + function f(uint a) public returns (uint) { return x.mul({x: a}); } } @@ -3442,10 +3403,10 @@ BOOST_AUTO_TEST_CASE(using_for_by_name) BOOST_AUTO_TEST_CASE(using_for_mismatch) { char const* text = R"( - library D { function double(bytes32 self) returns (uint) { return 2; } } + library D { function double(bytes32 self) public returns (uint) { return 2; } } contract C { using D for uint; - function f(uint a) returns (uint) { + function f(uint a) public returns (uint) { return a.double(); } } @@ -3458,10 +3419,10 @@ BOOST_AUTO_TEST_CASE(using_for_not_used) // This is an error because the function is only bound to uint. // Had it been bound to *, it would have worked. char const* text = R"( - library D { function double(uint self) returns (uint) { return 2; } } + library D { function double(uint self) public returns (uint) { return 2; } } contract C { using D for uint; - function f(uint16 a) returns (uint) { + function f(uint16 a) public returns (uint) { return a.double(); } } @@ -3474,20 +3435,20 @@ BOOST_AUTO_TEST_CASE(library_memory_struct) char const* text = R"( library c { struct S { uint x; } - function f() returns (S ) {} + function f() public returns (S ) {} } )"; - CHECK_ERROR(text, TypeError, "Internal type is not allowed for public or external functions."); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(using_for_arbitrary_mismatch) { // Bound to a, but self type does not match. char const* text = R"( - library D { function double(bytes32 self) returns (uint) { return 2; } } + library D { function double(bytes32 self) public returns (uint) { return 2; } } contract C { using D for *; - function f(uint a) returns (uint) { + function f(uint a) public returns (uint) { return a.double(); } } @@ -3498,11 +3459,11 @@ BOOST_AUTO_TEST_CASE(using_for_arbitrary_mismatch) BOOST_AUTO_TEST_CASE(bound_function_in_var) { char const* text = R"( - library D { struct s { uint a; } function mul(s storage self, uint x) returns (uint) { return self.a *= x; } } + library D { struct s { uint a; } function mul(s storage self, uint x) public returns (uint) { return self.a *= x; } } contract C { using D for D.s; D.s x; - function f(uint a) returns (uint) { + function f(uint a) public returns (uint) { var g = x.mul; return g({x: a}); } @@ -3519,7 +3480,7 @@ BOOST_AUTO_TEST_CASE(create_memory_arrays) struct S { uint a; uint b; uint[20][20][20] c; R d; } } contract C { - function f(uint size) { + function f(uint size) public { L.S[][] memory x = new L.S[][](10); var y = new uint[](20); var z = new bytes(size); @@ -3534,7 +3495,7 @@ BOOST_AUTO_TEST_CASE(mapping_in_memory_array) { char const* text = R"( contract C { - function f(uint size) { + function f(uint size) public { var x = new mapping(uint => uint)[](4); } } @@ -3546,7 +3507,7 @@ BOOST_AUTO_TEST_CASE(new_for_non_array) { char const* text = R"( contract C { - function f(uint size) { + function f(uint size) public { var x = new uint(7); } } @@ -3558,7 +3519,7 @@ BOOST_AUTO_TEST_CASE(invalid_args_creating_memory_array) { char const* text = R"( contract C { - function f(uint size) { + function f(uint size) public { var x = new uint[](); } } @@ -3581,7 +3542,7 @@ BOOST_AUTO_TEST_CASE(inline_array_declaration_and_passing_implicit_conversion) { char const* text = R"( contract C { - function f() returns (uint) { + function f() public returns (uint) { uint8 x = 7; uint16 y = 8; uint32 z = 9; @@ -3597,7 +3558,7 @@ BOOST_AUTO_TEST_CASE(inline_array_declaration_and_passing_implicit_conversion_st { char const* text = R"( contract C { - function f() returns (string) { + function f() public returns (string) { string memory x = "Hello"; string memory y = "World"; string[2] memory z = [x, y]; @@ -3612,7 +3573,7 @@ BOOST_AUTO_TEST_CASE(inline_array_declaration_const_int_conversion) { char const* text = R"( contract C { - function f() returns (uint) { + function f() public returns (uint) { uint8[4] memory z = [1,2,3,5]; return (z[0]); } @@ -3625,7 +3586,7 @@ BOOST_AUTO_TEST_CASE(inline_array_declaration_const_string_conversion) { char const* text = R"( contract C { - function f() returns (string) { + function f() public returns (string) { string[2] memory z = ["Hello", "World"]; return (z[0]); } @@ -3638,7 +3599,7 @@ BOOST_AUTO_TEST_CASE(inline_array_declaration_no_type) { char const* text = R"( contract C { - function f() returns (uint) { + function f() public returns (uint) { return ([4,5,6][1]); } } @@ -3650,7 +3611,7 @@ BOOST_AUTO_TEST_CASE(inline_array_declaration_no_type_strings) { char const* text = R"( contract C { - function f() returns (string) { + function f() public returns (string) { return (["foo", "man", "choo"][1]); } } @@ -3678,7 +3639,7 @@ BOOST_AUTO_TEST_CASE(invalid_types_in_inline_array) { char const* text = R"( contract C { - function f() { + function f() public { uint[3] x = [45, 'foo', true]; } } @@ -3690,7 +3651,7 @@ BOOST_AUTO_TEST_CASE(dynamic_inline_array) { char const* text = R"( contract C { - function f() { + function f() public { uint8[4][4] memory dyn = [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7]]; } } @@ -3702,7 +3663,7 @@ BOOST_AUTO_TEST_CASE(lvalues_as_inline_array) { char const* text = R"( contract C { - function f() { + function f() public { [1, 2, 3]++; [1, 2, 3] = [4, 5, 6]; } @@ -3715,7 +3676,7 @@ BOOST_AUTO_TEST_CASE(break_not_in_loop) { char const* text = R"( contract C { - function f() { + function f() public { if (true) break; } @@ -3728,7 +3689,7 @@ BOOST_AUTO_TEST_CASE(continue_not_in_loop) { char const* text = R"( contract C { - function f() { + function f() public { if (true) continue; } @@ -3741,7 +3702,7 @@ BOOST_AUTO_TEST_CASE(continue_not_in_loop_2) { char const* text = R"( contract C { - function f() { + function f() public { while (true) { } @@ -3756,7 +3717,7 @@ BOOST_AUTO_TEST_CASE(invalid_different_types_for_conditional_expression) { char const* text = R"( contract C { - function f() { + function f() public { true ? true : 2; } } @@ -3768,7 +3729,7 @@ BOOST_AUTO_TEST_CASE(left_value_in_conditional_expression_not_supported_yet) { char const* text = R"( contract C { - function f() { + function f() public { uint x; uint y; (true ? x : y) = 1; @@ -3788,7 +3749,7 @@ BOOST_AUTO_TEST_CASE(conditional_expression_with_different_struct) struct s2 { uint x; } - function f() { + function f() public { s1 memory x; s2 memory y; true ? x : y; @@ -3802,10 +3763,10 @@ BOOST_AUTO_TEST_CASE(conditional_expression_with_different_function_type) { char const* text = R"( contract C { - function x(bool) {} - function y() {} + function x(bool) public {} + function y() public {} - function f() { + function f() public { true ? x : y; } } @@ -3820,7 +3781,7 @@ BOOST_AUTO_TEST_CASE(conditional_expression_with_different_enum) enum small { A, B, C, D } enum big { A, B, C, D } - function f() { + function f() public { small x; big y; @@ -3838,7 +3799,7 @@ BOOST_AUTO_TEST_CASE(conditional_expression_with_different_mapping) mapping(uint8 => uint8) table1; mapping(uint32 => uint8) table2; - function f() { + function f() public { true ? table1 : table2; } } @@ -3856,15 +3817,15 @@ BOOST_AUTO_TEST_CASE(conditional_with_all_types) s1 struct_x; s1 struct_y; - function fun_x() {} - function fun_y() {} + function fun_x() public {} + function fun_y() public {} enum small { A, B, C, D } mapping(uint8 => uint8) table1; mapping(uint8 => uint8) table2; - function f() { + function f() public { // integers uint x; uint y; @@ -3879,7 +3840,7 @@ BOOST_AUTO_TEST_CASE(conditional_with_all_types) var i = true ? "hello" : "world"; i = "used"; //Avoid unused var warning } - function f2() { + function f2() public { // bool bool j = true ? true : false; j = j && true; // Avoid unused var warning @@ -3904,7 +3865,7 @@ BOOST_AUTO_TEST_CASE(conditional_with_all_types) m &= m; } - function f3() { + function f3() public { // contract doesn't fit in here // struct @@ -3958,7 +3919,7 @@ BOOST_AUTO_TEST_CASE(index_access_for_bytes) char const* text = R"( contract C { bytes20 x; - function f(bytes16 b) { + function f(bytes16 b) public { b[uint(x[2])]; } } @@ -3971,7 +3932,7 @@ BOOST_AUTO_TEST_CASE(uint7_and_uintM_as_identifier) char const* text = R"( contract test { string uintM = "Hello 4 you"; - function f() { + function f() public { uint8 uint7 = 3; uint7 = 5; string memory intM; @@ -3987,7 +3948,7 @@ BOOST_AUTO_TEST_CASE(varM_disqualified_as_keyword) { char const* text = R"( contract test { - function f() { + function f() public { uintM something = 3; intM should = 4; bytesM fail = "now"; @@ -4001,7 +3962,7 @@ BOOST_AUTO_TEST_CASE(long_uint_variable_fails) { char const* text = R"( contract test { - function f() { + function f() public { uint99999999999999999999999999 something = 3; } } @@ -4013,7 +3974,7 @@ BOOST_AUTO_TEST_CASE(bytes10abc_is_identifier) { char const* text = R"( contract test { - function f() { + function f() public { bytes32 bytes10abc = "abc"; } } @@ -4025,7 +3986,7 @@ BOOST_AUTO_TEST_CASE(int10abc_is_identifier) { char const* text = R"( contract test { - function f() { + function f() public { uint uint10abc = 3; int int10abc = 4; uint10abc; int10abc; @@ -4038,9 +3999,9 @@ BOOST_AUTO_TEST_CASE(int10abc_is_identifier) BOOST_AUTO_TEST_CASE(library_functions_do_not_have_value) { char const* text = R"( - library L { function l() {} } + library L { function l() public {} } contract test { - function f() { + function f() public { L.l.value; } } @@ -4081,9 +4042,9 @@ BOOST_AUTO_TEST_CASE(invalid_fixed_types_7x8_mxn) BOOST_AUTO_TEST_CASE(library_instances_cannot_be_used) { char const* text = R"( - library L { function l() {} } + library L { function l() public {} } contract test { - function f() { + function f() public { L x; x.l(); } @@ -4096,7 +4057,7 @@ BOOST_AUTO_TEST_CASE(invalid_fixed_type_long) { char const* text = R"( contract test { - function f() { + function f() public { fixed8x888888888888888888888888888888888888888888888888888 b; } } @@ -4108,7 +4069,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_int_conversion) { char const* text = R"( contract test { - function f() { + function f() public { uint64 a = 3; int64 b = 4; fixed c = b; @@ -4124,7 +4085,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_rational_int_conversion) { char const* text = R"( contract test { - function f() { + function f() public { fixed c = 3; ufixed d = 4; c; d; @@ -4138,7 +4099,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_rational_fraction_conversion) { char const* text = R"( contract test { - function f() { + function f() public { fixed a = 4.5; ufixed d = 2.5; a; d; @@ -4152,7 +4113,7 @@ BOOST_AUTO_TEST_CASE(invalid_int_implicit_conversion_from_fixed) { char const* text = R"( contract test { - function f() { + function f() public { fixed a = 4.5; int b = a; a; b; @@ -4166,7 +4127,7 @@ BOOST_AUTO_TEST_CASE(rational_unary_operation) { char const* text = R"( contract test { - function f() { + function f() pure public { ufixed16x2 a = 3.25; fixed16x2 b = -3.25; a; b; @@ -4176,7 +4137,7 @@ BOOST_AUTO_TEST_CASE(rational_unary_operation) CHECK_SUCCESS_NO_WARNINGS(text); text = R"( contract test { - function f() { + function f() pure public { ufixed16x2 a = +3.25; fixed16x2 b = -3.25; a; b; @@ -4186,7 +4147,7 @@ BOOST_AUTO_TEST_CASE(rational_unary_operation) CHECK_WARNING(text, "Use of unary + is deprecated"); text = R"( contract test { - function f(uint x) { + function f(uint x) pure public { uint y = +x; y; } @@ -4199,7 +4160,7 @@ BOOST_AUTO_TEST_CASE(leading_zero_rationals_convert) { char const* text = R"( contract A { - function f() { + function f() pure public { ufixed16x2 a = 0.5; ufixed256x52 b = 0.0000000000000006661338147750939242541790008544921875; fixed16x2 c = -0.5; @@ -4215,7 +4176,7 @@ BOOST_AUTO_TEST_CASE(size_capabilities_of_fixed_point_types) { char const* text = R"( contract test { - function f() { + function f() public { ufixed256x1 a = 123456781234567979695948382928485849359686494864095409282048094275023098123.5; ufixed256x77 b = 0.920890746623327805482905058466021565416131529487595827354393978494366605267637; ufixed224x78 c = 0.000000000001519884736399797998492268541131529487595827354393978494366605267646; @@ -4233,7 +4194,7 @@ BOOST_AUTO_TEST_CASE(zero_handling) { char const* text = R"( contract test { - function f() { + function f() public { fixed16x2 a = 0; a; ufixed32x1 b = 0; b; } @@ -4246,7 +4207,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_invalid_implicit_conversion_size) { char const* text = R"( contract test { - function f() { + function f() public { ufixed a = 11/4; ufixed248x8 b = a; b; } @@ -4259,7 +4220,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_invalid_implicit_conversion_lost_data) { char const* text = R"( contract test { - function f() { + function f() public { ufixed256x1 a = 1/3; a; } } @@ -4271,7 +4232,7 @@ BOOST_AUTO_TEST_CASE(fixed_type_valid_explicit_conversions) { char const* text = R"( contract test { - function f() { + function f() public { ufixed256x80 a = ufixed256x80(1/3); a; ufixed248x80 b = ufixed248x80(1/3); b; ufixed8x1 c = ufixed8x1(1/3); c; @@ -4285,7 +4246,7 @@ BOOST_AUTO_TEST_CASE(invalid_array_declaration_with_rational) { char const* text = R"( contract test { - function f() { + function f() public { uint[3.5] a; a; } } @@ -4297,7 +4258,7 @@ BOOST_AUTO_TEST_CASE(invalid_array_declaration_with_signed_fixed_type) { char const* text = R"( contract test { - function f() { + function f() public { uint[fixed(3.5)] a; a; } } @@ -4309,7 +4270,7 @@ BOOST_AUTO_TEST_CASE(invalid_array_declaration_with_unsigned_fixed_type) { char const* text = R"( contract test { - function f() { + function f() public { uint[ufixed(3.5)] a; a; } } @@ -4321,7 +4282,7 @@ BOOST_AUTO_TEST_CASE(rational_to_bytes_implicit_conversion) { char const* text = R"( contract test { - function f() { + function f() public { bytes32 c = 3.2; c; } } @@ -4333,7 +4294,7 @@ BOOST_AUTO_TEST_CASE(fixed_to_bytes_implicit_conversion) { char const* text = R"( contract test { - function f() { + function f() public { fixed a = 3.25; bytes32 c = a; c; } @@ -4347,7 +4308,7 @@ BOOST_AUTO_TEST_CASE(mapping_with_fixed_literal) char const* text = R"( contract test { mapping(ufixed8x1 => string) fixedString; - function f() { + function f() public { fixedString[0.5] = "Half"; } } @@ -4373,7 +4334,7 @@ BOOST_AUTO_TEST_CASE(inline_array_fixed_types) { char const* text = R"( contract test { - function f() { + function f() public { fixed[3] memory a = [fixed(3.5), fixed(-4.25), fixed(967.125)]; } } @@ -4385,7 +4346,7 @@ BOOST_AUTO_TEST_CASE(inline_array_rationals) { char const* text = R"( contract test { - function f() { + function f() public { ufixed128x3[4] memory a = [ufixed128x3(3.5), 4.125, 2.5, 4.0]; } } @@ -4397,7 +4358,7 @@ BOOST_AUTO_TEST_CASE(rational_index_access) { char const* text = R"( contract test { - function f() { + function f() public { uint[] memory a; a[.5]; } @@ -4410,7 +4371,7 @@ BOOST_AUTO_TEST_CASE(rational_to_fixed_literal_expression) { char const* text = R"( contract test { - function f() { + function f() public { ufixed64x8 a = 3.5 * 3; ufixed64x8 b = 4 - 2.5; ufixed64x8 c = 11 / 4; @@ -4429,7 +4390,7 @@ BOOST_AUTO_TEST_CASE(rational_as_exponent_value_signed) { char const* text = R"( contract test { - function f() { + function f() public { fixed g = 2 ** -2.2; } } @@ -4441,7 +4402,7 @@ BOOST_AUTO_TEST_CASE(rational_as_exponent_value_unsigned) { char const* text = R"( contract test { - function f() { + function f() public { ufixed b = 3 ** 2.5; } } @@ -4453,7 +4414,7 @@ BOOST_AUTO_TEST_CASE(rational_as_exponent_half) { char const* text = R"( contract test { - function f() { + function f() public { 2 ** (1/2); } } @@ -4465,7 +4426,7 @@ BOOST_AUTO_TEST_CASE(rational_as_exponent_value_neg_quarter) { char const* text = R"( contract test { - function f() { + function f() public { 42 ** (-1/4); } } @@ -4477,7 +4438,7 @@ BOOST_AUTO_TEST_CASE(fixed_point_casting_exponents_15) { char const* text = R"( contract test { - function f() { + function f() public { var a = 3 ** ufixed(1.5); } } @@ -4489,7 +4450,7 @@ BOOST_AUTO_TEST_CASE(fixed_point_casting_exponents_neg) { char const* text = R"( contract test { - function f() { + function f() public { var c = 42 ** fixed(-1/4); } } @@ -4501,7 +4462,7 @@ BOOST_AUTO_TEST_CASE(var_capable_of_holding_constant_rationals) { char const* text = R"( contract test { - function f() { + function f() public { var a = 0.12345678; var b = 12345678.352; var c = 0.00000009; @@ -4516,7 +4477,7 @@ BOOST_AUTO_TEST_CASE(var_and_rational_with_tuple) { char const* text = R"( contract test { - function f() { + function f() public { var (a, b) = (.5, 1/3); a; b; } @@ -4529,7 +4490,7 @@ BOOST_AUTO_TEST_CASE(var_handle_divided_integers) { char const* text = R"( contract test { - function f() { + function f() public { var x = 1/3; } } @@ -4541,7 +4502,7 @@ BOOST_AUTO_TEST_CASE(rational_bitnot_unary_operation) { char const* text = R"( contract test { - function f() { + function f() public { ~fixed(3.5); } } @@ -4553,7 +4514,7 @@ BOOST_AUTO_TEST_CASE(rational_bitor_binary_operation) { char const* text = R"( contract test { - function f() { + function f() public { fixed(1.5) | 3; } } @@ -4565,7 +4526,7 @@ BOOST_AUTO_TEST_CASE(rational_bitxor_binary_operation) { char const* text = R"( contract test { - function f() { + function f() public { fixed(1.75) ^ 3; } } @@ -4577,7 +4538,7 @@ BOOST_AUTO_TEST_CASE(rational_bitand_binary_operation) { char const* text = R"( contract test { - function f() { + function f() public { fixed(1.75) & 3; } } @@ -4589,7 +4550,7 @@ BOOST_AUTO_TEST_CASE(missing_bool_conversion) { char const* text = R"( contract test { - function b(uint a) { + function b(uint a) public { bool(a == 1); } } @@ -4601,7 +4562,7 @@ BOOST_AUTO_TEST_CASE(integer_and_fixed_interaction) { char const* text = R"( contract test { - function f() { + function f() public { ufixed a = uint64(1) + ufixed(2); } } @@ -4613,7 +4574,7 @@ BOOST_AUTO_TEST_CASE(signed_rational_modulus) { char const* text = R"( contract test { - function f() { + function f() public { fixed a = 0.42578125 % -0.4271087646484375; fixed b = .5 % a; fixed c = a % b; @@ -4627,7 +4588,7 @@ BOOST_AUTO_TEST_CASE(one_divided_by_three_integer_conversion) { char const* text = R"( contract test { - function f() { + function f() public { uint a = 1/3; } } @@ -4639,8 +4600,8 @@ BOOST_AUTO_TEST_CASE(unused_return_value) { char const* text = R"( contract test { - function g() returns (uint) {} - function f() { + function g() public returns (uint) {} + function f() public { g(); } } @@ -4652,7 +4613,7 @@ BOOST_AUTO_TEST_CASE(unused_return_value_send) { char const* text = R"( contract test { - function f() { + function f() public { address(0x12).send(1); } } @@ -4664,7 +4625,7 @@ BOOST_AUTO_TEST_CASE(unused_return_value_call) { char const* text = R"( contract test { - function f() { + function f() public { address(0x12).call("abc"); } } @@ -4676,7 +4637,7 @@ BOOST_AUTO_TEST_CASE(unused_return_value_call_value) { char const* text = R"( contract test { - function f() { + function f() public { address(0x12).call.value(2)("abc"); } } @@ -4688,7 +4649,7 @@ BOOST_AUTO_TEST_CASE(unused_return_value_callcode) { char const* text = R"( contract test { - function f() { + function f() public { address(0x12).callcode("abc"); } } @@ -4700,7 +4661,7 @@ BOOST_AUTO_TEST_CASE(unused_return_value_delegatecall) { char const* text = R"( contract test { - function f() { + function f() public { address(0x12).delegatecall("abc"); } } @@ -4712,21 +4673,21 @@ BOOST_AUTO_TEST_CASE(warn_about_callcode) { char const* text = R"( contract test { - function f() { + function f() pure public { var x = address(0x12).callcode; x; } } )"; - CHECK_WARNING(text, "\"callcode\" has been deprecated in favour"); + CHECK_WARNING(text, "\"callcode\" has been deprecated in favour of \"delegatecall\""); } -BOOST_AUTO_TEST_CASE(no_warn_about_callcode_as_local) +BOOST_AUTO_TEST_CASE(no_warn_about_callcode_as_function) { char const* text = R"( contract test { - function callcode() { - var x = this.callcode; + function callcode() pure public { + test.callcode(); } } )"; @@ -4747,7 +4708,7 @@ BOOST_AUTO_TEST_CASE(payable_in_library) { char const* text = R"( library test { - function f() payable {} + function f() payable public {} } )"; CHECK_ERROR(text, TypeError, "Library functions cannot be payable."); @@ -4786,8 +4747,8 @@ BOOST_AUTO_TEST_CASE(payable_private) BOOST_AUTO_TEST_CASE(illegal_override_payable) { char const* text = R"( - contract B { function f() payable {} } - contract C is B { function f() {} } + contract B { function f() payable public {} } + contract C is B { function f() public {} } )"; CHECK_ERROR(text, TypeError, "Overriding function changes state mutability from \"payable\" to \"nonpayable\"."); } @@ -4795,8 +4756,8 @@ BOOST_AUTO_TEST_CASE(illegal_override_payable) BOOST_AUTO_TEST_CASE(illegal_override_payable_nonpayable) { char const* text = R"( - contract B { function f() {} } - contract C is B { function f() payable {} } + contract B { function f() public {} } + contract C is B { function f() payable public {} } )"; CHECK_ERROR(text, TypeError, "Overriding function changes state mutability from \"nonpayable\" to \"payable\"."); } @@ -4809,11 +4770,11 @@ BOOST_AUTO_TEST_CASE(function_variable_mixin) bool ok = false; } contract func { - function ok() returns (bool) { return true; } + function ok() public returns (bool) { return true; } } contract attr_func is attribute, func { - function checkOk() returns (bool) { return ok(); } + function checkOk() public returns (bool) { return ok(); } } )"; CHECK_ERROR(text, DeclarationError, "Identifier already declared."); @@ -4822,11 +4783,11 @@ BOOST_AUTO_TEST_CASE(function_variable_mixin) BOOST_AUTO_TEST_CASE(calling_payable) { char const* text = R"( - contract receiver { function pay() payable {} } + contract receiver { function pay() payable public {} } contract test { - function f() { (new receiver()).pay.value(10)(); } + function f() public { (new receiver()).pay.value(10)(); } receiver r = new receiver(); - function g() { r.pay.value(10)(); } + function g() public { r.pay.value(10)(); } } )"; CHECK_SUCCESS(text); @@ -4835,9 +4796,9 @@ BOOST_AUTO_TEST_CASE(calling_payable) BOOST_AUTO_TEST_CASE(calling_nonpayable) { char const* text = R"( - contract receiver { function nopay() {} } + contract receiver { function nopay() public {} } contract test { - function f() { (new receiver()).nopay.value(10)(); } + function f() public { (new receiver()).nopay.value(10)(); } } )"; CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup in function () external - did you forget the \"payable\" modifier?"); @@ -4850,7 +4811,7 @@ BOOST_AUTO_TEST_CASE(non_payable_constructor) function C() { } } contract D { - function f() returns (uint) { + function f() public returns (uint) { (new C).value(2)(); return 2; } @@ -4873,10 +4834,14 @@ BOOST_AUTO_TEST_CASE(unsatisfied_version) char const* text = R"( pragma solidity ^99.99.0; )"; - BOOST_CHECK(expectError(text, true).type() == Error::Type::SyntaxError); + auto sourceAndError = parseAnalyseAndReturnError(text, false, false, false); + BOOST_REQUIRE(!!sourceAndError.second); + BOOST_REQUIRE(!!sourceAndError.first); + BOOST_CHECK(sourceAndError.second->type() == Error::Type::SyntaxError); + BOOST_CHECK(searchErrorMessage(*sourceAndError.second, "Source file requires different compiler version")); } -BOOST_AUTO_TEST_CASE(constant_constructor) +BOOST_AUTO_TEST_CASE(invalid_constructor_statemutability) { char const* text = R"( contract test { @@ -4884,6 +4849,18 @@ BOOST_AUTO_TEST_CASE(constant_constructor) } )"; CHECK_ERROR(text, TypeError, "Constructor must be payable or non-payable"); + text = R"( + contract test { + function test() view {} + } + )"; + CHECK_ERROR(text, TypeError, "Constructor must be payable or non-payable"); + text = R"( + contract test { + function test() pure {} + } + )"; + CHECK_ERROR(text, TypeError, "Constructor must be payable or non-payable"); } BOOST_AUTO_TEST_CASE(external_constructor) @@ -4901,7 +4878,7 @@ BOOST_AUTO_TEST_CASE(invalid_array_as_statement) char const* text = R"( contract test { struct S { uint x; } - function test(uint k) { S[k]; } + function test(uint k) public { S[k]; } } )"; CHECK_ERROR(text, TypeError, "Integer constant expected."); @@ -4911,13 +4888,13 @@ BOOST_AUTO_TEST_CASE(using_directive_for_missing_selftype) { char const* text = R"( library B { - function b() {} + function b() public {} } contract A { using B for bytes; - function a() { + function a() public { bytes memory x; x.b(); } @@ -4930,7 +4907,7 @@ BOOST_AUTO_TEST_CASE(function_type) { char const* text = R"( contract C { - function f() { + function f() public { function(uint) returns (uint) x; } } @@ -4942,7 +4919,7 @@ BOOST_AUTO_TEST_CASE(function_type_parameter) { char const* text = R"( contract C { - function f(function(uint) external returns (uint) g) returns (function(uint) external returns (uint)) { + function f(function(uint) external returns (uint) g) public returns (function(uint) external returns (uint)) { return g; } } @@ -4954,7 +4931,7 @@ BOOST_AUTO_TEST_CASE(function_type_returned) { char const* text = R"( contract C { - function f() returns (function(uint) external returns (uint) g) { + function f() public returns (function(uint) external returns (uint) g) { return g; } } @@ -4966,7 +4943,7 @@ BOOST_AUTO_TEST_CASE(private_function_type) { char const* text = R"( contract C { - function f() { + function f() public { function(uint) private returns (uint) x; } } @@ -4978,7 +4955,7 @@ BOOST_AUTO_TEST_CASE(public_function_type) { char const* text = R"( contract C { - function f() { + function f() public { function(uint) public returns (uint) x; } } @@ -5001,7 +4978,7 @@ BOOST_AUTO_TEST_CASE(call_value_on_non_payable_function_type) char const* text = R"( contract C { function (uint) external returns (uint) x; - function f() { + function f() public { x.value(2)(); } } @@ -5034,7 +5011,7 @@ BOOST_AUTO_TEST_CASE(call_value_on_payable_function_type) char const* text = R"( contract C { function (uint) external payable returns (uint) x; - function f() { + function f() public { x.value(2)(1); } } @@ -5048,11 +5025,11 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter) // as parameters to external functions. char const* text = R"( contract C { - function f(function(uint) internal returns (uint) x) { + function f(function(uint) internal returns (uint) x) public { } } )"; - CHECK_ERROR(text, TypeError, "Internal type is not allowed for public or external functions."); + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); } BOOST_AUTO_TEST_CASE(internal_function_returned_from_public_function) @@ -5060,11 +5037,11 @@ BOOST_AUTO_TEST_CASE(internal_function_returned_from_public_function) // It should not be possible to return internal functions from external functions. char const* text = R"( contract C { - function f() returns (function(uint) internal returns (uint) x) { + function f() public returns (function(uint) internal returns (uint) x) { } } )"; - CHECK_ERROR(text, TypeError, "Internal type is not allowed for public or external functions."); + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); } BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_internal) @@ -5082,11 +5059,11 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external { char const* text = R"( library L { - function f(function(uint) internal returns (uint) x) { + function f(function(uint) internal returns (uint) x) public { } } )"; - CHECK_ERROR(text, TypeError, "Internal type is not allowed for public or external functions."); + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); } BOOST_AUTO_TEST_CASE(function_type_arrays) @@ -5095,7 +5072,7 @@ BOOST_AUTO_TEST_CASE(function_type_arrays) contract C { function(uint) external returns (uint)[] public x; function(uint) internal returns (uint)[10] y; - function f() { + function f() public { function(uint) returns (uint)[10] memory a; function(uint) returns (uint)[10] storage b = y; function(uint) external returns (uint)[] memory c; @@ -5113,7 +5090,7 @@ BOOST_AUTO_TEST_CASE(delete_function_type) contract C { function(uint) external returns (uint) x; function(uint) internal returns (uint) y; - function f() { + function f() public { delete x; var a = y; delete a; @@ -5132,7 +5109,7 @@ BOOST_AUTO_TEST_CASE(delete_function_type_invalid) { char const* text = R"( contract C { - function f() { + function f() public { delete f; } } @@ -5144,7 +5121,7 @@ BOOST_AUTO_TEST_CASE(delete_external_function_type_invalid) { char const* text = R"( contract C { - function f() { + function f() public { delete this.f; } } @@ -5159,9 +5136,9 @@ BOOST_AUTO_TEST_CASE(external_function_to_function_type_calldata_parameter) // when converting to a function type. char const* text = R"( contract C { - function f(function(bytes memory) external g) { } + function f(function(bytes memory) external g) public { } function callback(bytes) external {} - function g() { + function g() public { f(this.callback); } } @@ -5173,7 +5150,7 @@ BOOST_AUTO_TEST_CASE(external_function_type_to_address) { char const* text = R"( contract C { - function f() returns (address) { + function f() public returns (address) { return address(this.f); } } @@ -5185,7 +5162,7 @@ BOOST_AUTO_TEST_CASE(internal_function_type_to_address) { char const* text = R"( contract C { - function f() returns (address) { + function f() public returns (address) { return address(f); } } @@ -5197,7 +5174,7 @@ BOOST_AUTO_TEST_CASE(external_function_type_to_uint) { char const* text = R"( contract C { - function f() returns (uint) { + function f() public returns (uint) { return uint(this.f); } } @@ -5219,7 +5196,7 @@ BOOST_AUTO_TEST_CASE(warn_function_type_return_parameters_with_names) { char const* text = R"( contract C { - function(uint) returns(bool ret) f; + function(uint) returns (bool ret) f; } )"; CHECK_WARNING(text, "Naming function type return parameters is deprecated."); @@ -5269,7 +5246,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_positive_stack) { char const* text = R"( contract test { - function f() { + function f() public { assembly { 1 } @@ -5283,7 +5260,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) { char const* text = R"( contract test { - function f() { + function f() public { assembly { pop } @@ -5298,7 +5275,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) char const* text = R"( contract c { uint8 x; - function f() { + function f() public { assembly { x pop } } } @@ -5329,7 +5306,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage) char const* text = R"( contract test { uint x = 1; - function f() { + function f() public { assembly { x := 2 } @@ -5362,7 +5339,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_constant_assign) char const* text = R"( contract test { uint constant x = 1; - function f() { + function f() public { assembly { x := 2 } @@ -5377,7 +5354,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_constant_access) char const* text = R"( contract test { uint constant x = 1; - function f() { + function f() public { assembly { let y := x } @@ -5391,7 +5368,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions) { char const* text = R"( contract test { - function f() { + function f() public { uint a; assembly { function g() -> x { x := a } @@ -5407,7 +5384,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions_stor char const* text = R"( contract test { uint[] r; - function f() { + function f() public { uint[] storage a = r; assembly { function g() -> x { x := a_offset } @@ -5423,7 +5400,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_variable_access_out_of_functions) char const* text = R"( contract test { uint a; - function f() { + function f() pure public { assembly { function g() -> x { x := a_slot } } @@ -5451,7 +5428,7 @@ BOOST_AUTO_TEST_CASE(invalid_mobile_type) { char const* text = R"( contract C { - function f() { + function f() public { // Invalid number [1, 78901234567890123456789012345678901234567890123456789345678901234567890012345678012345678901234567]; } @@ -5464,7 +5441,7 @@ BOOST_AUTO_TEST_CASE(warns_msg_value_in_non_payable_public_function) { char const* text = R"( contract C { - function f() { + function f() view public { msg.value; } } @@ -5476,7 +5453,7 @@ BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_payable_function) { char const* text = R"( contract C { - function f() payable { + function f() payable public { msg.value; } } @@ -5488,7 +5465,7 @@ BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_internal_function) { char const* text = R"( contract C { - function f() internal { + function f() view internal { msg.value; } } @@ -5500,7 +5477,7 @@ BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_library) { char const* text = R"( library C { - function f() { + function f() view public { msg.value; } } @@ -5512,7 +5489,7 @@ BOOST_AUTO_TEST_CASE(does_not_warn_msg_value_in_modifier_following_non_payable_p { char const* text = R"( contract c { - function f() { } + function f() pure public { } modifier m() { msg.value; _; } } )"; @@ -5524,7 +5501,7 @@ BOOST_AUTO_TEST_CASE(assignment_to_constant) char const* text = R"( contract c { uint constant a = 1; - function f() { a = 2; } + function f() public { a = 2; } } )"; CHECK_ERROR(text, TypeError, "Cannot assign to a constant variable."); @@ -5537,7 +5514,7 @@ BOOST_AUTO_TEST_CASE(inconstructible_internal_constructor) function C() internal {} } contract D { - function f() { var x = new C(); } + function f() public { var x = new C(); } } )"; CHECK_ERROR(text, TypeError, "Contract with internal constructor cannot be created directly."); @@ -5550,7 +5527,7 @@ BOOST_AUTO_TEST_CASE(inconstructible_internal_constructor_inverted) char const* text = R"( contract B { A a; - function B() { + function B() public { a = new A(this); } } @@ -5568,17 +5545,67 @@ BOOST_AUTO_TEST_CASE(constructible_internal_constructor) function C() internal {} } contract D is C { - function D() { } + function D() public { } + } + )"; + success(text); +} + +BOOST_AUTO_TEST_CASE(return_structs) +{ + char const* text = R"( + contract C { + struct S { uint a; T[] sub; } + struct T { uint[] x; } + function f() returns (uint, S) { + } } )"; success(text); } +BOOST_AUTO_TEST_CASE(return_recursive_structs) +{ + char const* text = R"( + contract C { + struct S { uint a; S[] sub; } + function f() returns (uint, S) { + } + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + +BOOST_AUTO_TEST_CASE(return_recursive_structs2) +{ + char const* text = R"( + contract C { + struct S { uint a; S[2][] sub; } + function f() returns (uint, S) { + } + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + +BOOST_AUTO_TEST_CASE(return_recursive_structs3) +{ + char const* text = R"( + contract C { + struct S { uint a; S[][][] sub; } + struct T { S s; } + function f() returns (uint x, T t) { + } + } + )"; + CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); +} + BOOST_AUTO_TEST_CASE(address_checksum_type_deduction) { char const* text = R"( contract C { - function f() { + function f() public { var x = 0xfA0bFc97E48458494Ccd857e1A85DC91F7F0046E; x.send(2); } @@ -5591,7 +5618,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_checksum) { char const* text = R"( contract C { - function f() { + function f() pure public { address x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E; x; } @@ -5604,7 +5631,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_no_checksum) { char const* text = R"( contract C { - function f() { + function f() pure public { address x = 0xfa0bfc97e48458494ccd857e1a85dc91f7f0046e; x; } @@ -5617,7 +5644,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_length) { char const* text = R"( contract C { - function f() { + function f() pure public { address x = 0xA0bFc97E48458494Ccd857e1A85DC91F7F0046E; x; } @@ -5637,7 +5664,7 @@ BOOST_AUTO_TEST_CASE(address_test_for_bug_in_implementation) CHECK_ERROR(text, TypeError, "is not implicitly convertible to expected type address"); text = R"( contract AddrString { - function f() returns (address) { + function f() public returns (address) { return "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"; } } @@ -5665,7 +5692,7 @@ BOOST_AUTO_TEST_CASE(address_methods) { char const* text = R"( contract C { - function f() { + function f() public { address addr; uint balance = addr.balance; bool callRet = addr.call(); @@ -5742,7 +5769,7 @@ BOOST_AUTO_TEST_CASE(interface_function_bodies) { char const* text = R"( interface I { - function f() { + function f() public { } } )"; @@ -5816,7 +5843,7 @@ BOOST_AUTO_TEST_CASE(interface_function_parameters) { char const* text = R"( interface I { - function f(uint a) returns(bool); + function f(uint a) public returns (bool); } )"; success(text); @@ -5839,7 +5866,7 @@ BOOST_AUTO_TEST_CASE(using_interface) function f(); } contract C is I { - function f() { + function f() public { } } )"; @@ -5856,7 +5883,7 @@ BOOST_AUTO_TEST_CASE(using_interface_complex) function(); } contract C is I { - function f() { + function f() public { } } )"; @@ -5867,7 +5894,7 @@ BOOST_AUTO_TEST_CASE(warn_about_throw) { char const* text = R"( contract C { - function f() { + function f() pure public { throw; } } @@ -5879,7 +5906,7 @@ BOOST_AUTO_TEST_CASE(bare_revert) { char const* text = R"( contract C { - function f(uint x) { + function f(uint x) pure public { if (x > 7) revert; } @@ -5890,17 +5917,17 @@ BOOST_AUTO_TEST_CASE(bare_revert) BOOST_AUTO_TEST_CASE(bare_others) { - CHECK_WARNING("contract C { function f() { selfdestruct; } }", "Statement has no effect."); - CHECK_WARNING("contract C { function f() { assert; } }", "Statement has no effect."); - CHECK_WARNING("contract C { function f() { require; } }", "Statement has no effect."); - CHECK_WARNING("contract C { function f() { suicide; } }", "Statement has no effect."); + CHECK_WARNING("contract C { function f() pure public { selfdestruct; } }", "Statement has no effect."); + CHECK_WARNING("contract C { function f() pure public { assert; } }", "Statement has no effect."); + CHECK_WARNING("contract C { function f() pure public { require; } }", "Statement has no effect."); + CHECK_WARNING("contract C { function f() pure public { suicide; } }", "Statement has no effect."); } BOOST_AUTO_TEST_CASE(pure_statement_in_for_loop) { char const* text = R"( contract C { - function f() { + function f() pure public { for (uint x = 0; x < 10; true) x++; } @@ -5913,7 +5940,7 @@ BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop) { char const* text = R"( contract C { - function f() { + function f() pure public { for (uint x = 0; true; x++) {} } @@ -5928,7 +5955,7 @@ BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies) contract C { struct S { uint a; uint b; } S x; S y; - function f() { + function f() public { (x, y) = (y, x); } } @@ -5942,7 +5969,7 @@ BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_right) contract C { struct S { uint a; uint b; } S x; S y; - function f() { + function f() public { (x, y, ) = (y, x, 1, 2); } } @@ -5956,7 +5983,7 @@ BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies_fill_left) contract C { struct S { uint a; uint b; } S x; S y; - function f() { + function f() public { (,x, y) = (1, 2, y, x); } } @@ -5969,7 +5996,7 @@ BOOST_AUTO_TEST_CASE(nowarn_swap_memory) char const* text = R"( contract C { struct S { uint a; uint b; } - function f() { + function f() pure public { S memory x; S memory y; (x, y) = (y, x); @@ -5985,7 +6012,7 @@ BOOST_AUTO_TEST_CASE(nowarn_swap_storage_pointers) contract C { struct S { uint a; uint b; } S x; S y; - function f() { + function f() public { S storage x_local = x; S storage y_local = y; S storage z_local = x; @@ -6000,71 +6027,71 @@ BOOST_AUTO_TEST_CASE(warn_unused_local) { char const* text = R"( contract C { - function f() { + function f() pure public { uint a; } } )"; - CHECK_WARNING(text, "Unused"); + CHECK_WARNING(text, "Unused local variable."); } BOOST_AUTO_TEST_CASE(warn_unused_local_assigned) { char const* text = R"( contract C { - function f() { + function f() pure public { uint a = 1; } } )"; - CHECK_WARNING(text, "Unused"); + CHECK_WARNING(text, "Unused local variable."); } -BOOST_AUTO_TEST_CASE(warn_unused_param) +BOOST_AUTO_TEST_CASE(warn_unused_function_parameter) { char const* text = R"( contract C { - function f(uint a) { + function f(uint a) pure public { } } )"; - CHECK_WARNING(text, "Unused"); + CHECK_WARNING(text, "Unused function parameter. Remove or comment out the variable name to silence this warning."); text = R"( contract C { - function f(uint a) { + function f(uint a) pure public { } } )"; success(text); } -BOOST_AUTO_TEST_CASE(warn_unused_return_param) +BOOST_AUTO_TEST_CASE(warn_unused_return_parameter) { char const* text = R"( contract C { - function f() returns (uint a) { + function f() pure public returns (uint a) { } } )"; - CHECK_WARNING(text, "Unused"); + CHECK_WARNING(text, "Unused function parameter. Remove or comment out the variable name to silence this warning."); text = R"( contract C { - function f() returns (uint a) { + function f() pure public returns (uint a) { return; } } )"; - CHECK_WARNING(text, "Unused"); + CHECK_WARNING(text, "Unused function parameter. Remove or comment out the variable name to silence this warning."); text = R"( contract C { - function f() returns (uint) { + function f() pure public returns (uint) { } } )"; CHECK_SUCCESS_NO_WARNINGS(text); text = R"( contract C { - function f() returns (uint a) { + function f() pure public returns (uint a) { a = 1; } } @@ -6072,7 +6099,7 @@ BOOST_AUTO_TEST_CASE(warn_unused_return_param) CHECK_SUCCESS_NO_WARNINGS(text); text = R"( contract C { - function f() returns (uint a) { + function f() pure public returns (uint a) { return 1; } } @@ -6084,7 +6111,7 @@ BOOST_AUTO_TEST_CASE(no_unused_warnings) { char const* text = R"( contract C { - function f(uint a) returns (uint b) { + function f(uint a) pure public returns (uint b) { uint c = 1; b = a + c; } @@ -6097,7 +6124,7 @@ BOOST_AUTO_TEST_CASE(no_unused_dec_after_use) { char const* text = R"( contract C { - function f() { + function f() pure public { a = 7; uint a; } @@ -6110,7 +6137,7 @@ BOOST_AUTO_TEST_CASE(no_unused_inline_asm) { char const* text = R"( contract C { - function f() { + function f() pure public { uint a; assembly { a := 1 @@ -6125,7 +6152,7 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_functions) { char const* text = R"( contract C { - function keccak256() {} + function keccak256() pure public {} } )"; CHECK_WARNING(text, "shadows a builtin symbol"); @@ -6135,7 +6162,7 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_variables) { char const* text = R"( contract C { - function f() { + function f() pure public { uint msg; msg; } @@ -6167,7 +6194,7 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_parameters) { char const* text = R"( contract C { - function f(uint require) { + function f(uint require) pure public { require = 2; } } @@ -6179,7 +6206,7 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_return_parameters) { char const* text = R"( contract C { - function f() returns (uint require) { + function f() pure public returns (uint require) { require = 2; } } @@ -6213,7 +6240,7 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_ignores_constructor) { char const* text = R"( contract C { - function C() {} + function C() public {} } )"; CHECK_SUCCESS_NO_WARNINGS(text); @@ -6223,8 +6250,8 @@ BOOST_AUTO_TEST_CASE(function_overload_is_not_shadowing) { char const* text = R"( contract C { - function f() {} - function f(uint) {} + function f() pure public {} + function f(uint) pure public {} } )"; CHECK_SUCCESS_NO_WARNINGS(text); @@ -6233,9 +6260,9 @@ BOOST_AUTO_TEST_CASE(function_overload_is_not_shadowing) BOOST_AUTO_TEST_CASE(function_override_is_not_shadowing) { char const* text = R"( - contract D { function f() {} } + contract D { function f() pure public {} } contract C is D { - function f(uint) {} + function f(uint) pure public {} } )"; CHECK_SUCCESS_NO_WARNINGS(text); @@ -6247,7 +6274,7 @@ BOOST_AUTO_TEST_CASE(callable_crash) contract C { struct S { uint a; bool x; } S public s; - function C() { + function C() public { 3({a: 1, x: true}); } } @@ -6259,13 +6286,13 @@ BOOST_AUTO_TEST_CASE(error_transfer_non_payable_fallback) { char const* text = R"( contract A { - function() {} + function() public {} } contract B { A a; - function() { + function() public { a.transfer(100); } } @@ -6281,7 +6308,7 @@ BOOST_AUTO_TEST_CASE(error_transfer_no_fallback) contract B { A a; - function() { + function() public { a.transfer(100); } } @@ -6293,13 +6320,13 @@ BOOST_AUTO_TEST_CASE(error_send_non_payable_fallback) { char const* text = R"( contract A { - function() {} + function() public {} } contract B { A a; - function() { + function() public { require(a.send(100)); } } @@ -6311,13 +6338,13 @@ BOOST_AUTO_TEST_CASE(does_not_error_transfer_payable_fallback) { char const* text = R"( contract A { - function() payable {} + function() payable public {} } contract B { A a; - function() { + function() public { a.transfer(100); } } @@ -6329,14 +6356,14 @@ BOOST_AUTO_TEST_CASE(does_not_error_transfer_regular_function) { char const* text = R"( contract A { - function transfer(uint) {} + function transfer() pure public {} } contract B { A a; - function() { - a.transfer(100); + function() public { + a.transfer(); } } )"; @@ -6346,7 +6373,7 @@ BOOST_AUTO_TEST_CASE(does_not_error_transfer_regular_function) BOOST_AUTO_TEST_CASE(returndatacopy_as_variable) { char const* text = R"( - contract c { function f() { uint returndatasize; assembly { returndatasize }}} + contract c { function f() public { uint returndatasize; assembly { returndatasize }}} )"; CHECK_WARNING_ALLOW_MULTI(text, "Variable is shadowed in inline assembly by an instruction of the same name"); } @@ -6354,7 +6381,7 @@ BOOST_AUTO_TEST_CASE(returndatacopy_as_variable) BOOST_AUTO_TEST_CASE(create2_as_variable) { char const* text = R"( - contract c { function f() { uint create2; assembly { create2(0, 0, 0, 0) }}} + contract c { function f() public { uint create2; assembly { create2(0, 0, 0, 0) }}} )"; CHECK_WARNING_ALLOW_MULTI(text, "Variable is shadowed in inline assembly by an instruction of the same name"); } @@ -6365,7 +6392,7 @@ BOOST_AUTO_TEST_CASE(warn_unspecified_storage) contract C { struct S { uint a; string b; } S x; - function f() { + function f() view public { S storage y = x; y; } @@ -6376,7 +6403,7 @@ BOOST_AUTO_TEST_CASE(warn_unspecified_storage) contract C { struct S { uint a; } S x; - function f() { + function f() view public { S y = x; y; } @@ -6389,7 +6416,7 @@ BOOST_AUTO_TEST_CASE(implicit_conversion_disallowed) { char const* text = R"( contract C { - function f() returns (bytes4) { + function f() public returns (bytes4) { uint32 tmp = 1; return tmp; } @@ -6402,32 +6429,32 @@ BOOST_AUTO_TEST_CASE(too_large_arrays_for_calldata) { char const* text = R"( contract C { - function f(uint[85678901234] a) external { + function f(uint[85678901234] a) pure external { } } )"; - CHECK_ERROR(text, TypeError, "Array is too large to be encoded as calldata."); + CHECK_ERROR(text, TypeError, "Array is too large to be encoded."); text = R"( contract C { - function f(uint[85678901234] a) internal { + function f(uint[85678901234] a) pure internal { } } )"; - CHECK_SUCCESS_NO_WARNINGS(text); + CHECK_ERROR(text, TypeError, "Array is too large to be encoded."); text = R"( contract C { - function f(uint[85678901234] a) { + function f(uint[85678901234] a) pure public { } } )"; - CHECK_ERROR(text, TypeError, "Array is too large to be encoded as calldata."); + CHECK_ERROR(text, TypeError, "Array is too large to be encoded."); } BOOST_AUTO_TEST_CASE(explicit_literal_to_storage_string) { char const* text = R"( contract C { - function f() { + function f() pure public { string memory x = "abc"; x; } @@ -6436,7 +6463,7 @@ BOOST_AUTO_TEST_CASE(explicit_literal_to_storage_string) CHECK_SUCCESS_NO_WARNINGS(text); text = R"( contract C { - function f() { + function f() pure public { string storage x = "abc"; } } @@ -6444,7 +6471,7 @@ BOOST_AUTO_TEST_CASE(explicit_literal_to_storage_string) CHECK_ERROR(text, TypeError, "Type literal_string \"abc\" is not implicitly convertible to expected type string storage pointer."); text = R"( contract C { - function f() { + function f() pure public { string x = "abc"; } } @@ -6452,7 +6479,7 @@ BOOST_AUTO_TEST_CASE(explicit_literal_to_storage_string) CHECK_ERROR(text, TypeError, "Type literal_string \"abc\" is not implicitly convertible to expected type string storage pointer."); text = R"( contract C { - function f() { + function f() pure public { string("abc"); } } @@ -6474,14 +6501,95 @@ BOOST_AUTO_TEST_CASE(modifiers_access_storage_pointer) CHECK_SUCCESS_NO_WARNINGS(text); } +BOOST_AUTO_TEST_CASE(function_types_sig) +{ + char const* text = R"( + contract C { + function f() view returns (bytes4) { + return f.selector; + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"selector\" not found"); + text = R"( + contract C { + function g() pure internal { + } + function f() view returns (bytes4) { + return g.selector; + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"selector\" not found"); + text = R"( + contract C { + function f() view returns (bytes4) { + function () g; + return g.selector; + } + } + )"; + CHECK_ERROR(text, TypeError, "Member \"selector\" not found"); + text = R"( + contract C { + function f() view external returns (bytes4) { + return this.f.selector; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f() view external returns (bytes4) { + return this.f.selector; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function h() pure external { + } + function f() view external returns (bytes4) { + var g = this.h; + return g.selector; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function h() pure external { + } + function f() view external returns (bytes4) { + function () pure external g = this.h; + return g.selector; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function h() pure external { + } + function f() view external returns (bytes4) { + function () pure external g = this.h; + var i = g; + return i.selector; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_CASE(using_this_in_constructor) { char const* text = R"( contract C { - function C() { + function C() public { this.f(); } - function f() { + function f() pure public { } } )"; @@ -6494,7 +6602,7 @@ BOOST_AUTO_TEST_CASE(do_not_crash_on_not_lvalue) char const* text = R"( contract C { mapping (uint => uint) m; - function f() { + function f() public { m(1) = 2; } } @@ -6506,7 +6614,7 @@ BOOST_AUTO_TEST_CASE(builtin_reject_gas) { char const* text = R"( contract C { - function f() { + function f() public { keccak256.gas(); } } @@ -6514,7 +6622,7 @@ BOOST_AUTO_TEST_CASE(builtin_reject_gas) CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); text = R"( contract C { - function f() { + function f() public { sha256.gas(); } } @@ -6522,7 +6630,7 @@ BOOST_AUTO_TEST_CASE(builtin_reject_gas) CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); text = R"( contract C { - function f() { + function f() public { ripemd160.gas(); } } @@ -6530,7 +6638,7 @@ BOOST_AUTO_TEST_CASE(builtin_reject_gas) CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); text = R"( contract C { - function f() { + function f() public { ecrecover.gas(); } } @@ -6542,7 +6650,7 @@ BOOST_AUTO_TEST_CASE(builtin_reject_value) { char const* text = R"( contract C { - function f() { + function f() public { keccak256.value(); } } @@ -6550,7 +6658,7 @@ BOOST_AUTO_TEST_CASE(builtin_reject_value) CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); text = R"( contract C { - function f() { + function f() public { sha256.value(); } } @@ -6558,7 +6666,7 @@ BOOST_AUTO_TEST_CASE(builtin_reject_value) CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); text = R"( contract C { - function f() { + function f() public { ripemd160.value(); } } @@ -6566,7 +6674,7 @@ BOOST_AUTO_TEST_CASE(builtin_reject_value) CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup"); text = R"( contract C { - function f() { + function f() public { ecrecover.value(); } } @@ -6639,7 +6747,7 @@ BOOST_AUTO_TEST_CASE(library_function_without_implementation) { char const* text = R"( library L { - function f(); + function f() public; } )"; CHECK_SUCCESS_NO_WARNINGS(text); @@ -6714,7 +6822,7 @@ BOOST_AUTO_TEST_CASE(reject_interface_creation) char const* text = R"( interface I {} contract C { - function f() { + function f() public { new I(); } } @@ -6727,7 +6835,7 @@ BOOST_AUTO_TEST_CASE(accept_library_creation) char const* text = R"( library L {} contract C { - function f() { + function f() public { new L(); } } @@ -6744,6 +6852,107 @@ BOOST_AUTO_TEST_CASE(reject_interface_constructors) CHECK_ERROR(text, TypeError, "Wrong argument count for constructor call: 1 arguments given but expected 0."); } +BOOST_AUTO_TEST_CASE(tight_packing_literals) +{ + char const* text = R"( + contract C { + function f() pure public returns (bytes32) { + return keccak256(1); + } + } + )"; + CHECK_WARNING(text, "The type of \"int_const 1\" was inferred as uint8."); + text = R"( + contract C { + function f() pure public returns (bytes32) { + return keccak256(uint8(1)); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f() pure public returns (bytes32) { + return sha3(1); + } + } + )"; +// CHECK_WARNING(text, "The type of \"int_const 1\" was inferred as uint8."); + text = R"( + contract C { + function f() pure public returns (bytes32) { + return sha256(1); + } + } + )"; + CHECK_WARNING(text, "The type of \"int_const 1\" was inferred as uint8."); + text = R"( + contract C { + function f() pure public returns (bytes32) { + return ripemd160(1); + } + } + )"; + CHECK_WARNING(text, "The type of \"int_const 1\" was inferred as uint8."); +} + +BOOST_AUTO_TEST_CASE(non_external_fallback) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract C { + function () external { } + } + )"; + CHECK_WARNING(text, "Experimental features are turned on."); + text = R"( + pragma experimental "v0.5.0"; + contract C { + function () internal { } + } + )"; + CHECK_ERROR(text, TypeError, "Fallback function must be defined as \"external\"."); + text = R"( + pragma experimental "v0.5.0"; + contract C { + function () private { } + } + )"; + CHECK_ERROR(text, TypeError, "Fallback function must be defined as \"external\"."); + text = R"( + pragma experimental "v0.5.0"; + contract C { + function () public { } + } + )"; + CHECK_ERROR(text, TypeError, "Fallback function must be defined as \"external\"."); +} + +BOOST_AUTO_TEST_CASE(warn_about_sha3) +{ + char const* text = R"( + contract test { + function f() pure public { + var x = sha3(uint8(1)); + x; + } + } + )"; + CHECK_WARNING(text, "\"sha3\" has been deprecated in favour of \"keccak256\""); +} + +BOOST_AUTO_TEST_CASE(warn_about_suicide) +{ + char const* text = R"( + contract test { + function f() public { + suicide(1); + } + } + )"; + CHECK_WARNING(text, "\"suicide\" has been deprecated in favour of \"selfdestruct\""); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index a39e0958ac7d..60ca03c9789f 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1602,6 +1602,18 @@ BOOST_AUTO_TEST_CASE(interface) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(newInvalidTypeName) +{ + char const* text = R"( + contract C { + function f() { + new var; + } + } + )"; + CHECK_PARSE_ERROR(text, "Expected explicit type name"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 79848c36dda2..24f915c07aab 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -198,19 +198,19 @@ BOOST_AUTO_TEST_CASE(basic_compilation) BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString()); BOOST_CHECK_EQUAL( dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()), - "60606040523415600e57600080fd5b5b603680601c6000396000f30060606040525b600080fd00" + "60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00" ); BOOST_CHECK(contract["evm"]["assembly"].isString()); BOOST_CHECK(contract["evm"]["assembly"].asString().find( " /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x60)\n jumpi(tag_1, iszero(callvalue))\n" - " 0x0\n dup1\n revert\ntag_1:\ntag_2:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n" + " 0x0\n dup1\n revert\ntag_1:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n" " return\nstop\n\nsub_0: assembly {\n /* \"fileA\":0:14 contract A { } */\n" - " mstore(0x40, 0x60)\n tag_1:\n 0x0\n dup1\n revert\n\n" + " mstore(0x40, 0x60)\n 0x0\n dup1\n revert\n\n" " auxdata: 0xa165627a7a7230582") == 0); BOOST_CHECK(contract["evm"]["gasEstimates"].isObject()); BOOST_CHECK_EQUAL( dev::jsonCompactPrint(contract["evm"]["gasEstimates"]), - "{\"creation\":{\"codeDepositCost\":\"10800\",\"executionCost\":\"62\",\"totalCost\":\"10862\"}}" + "{\"creation\":{\"codeDepositCost\":\"10600\",\"executionCost\":\"61\",\"totalCost\":\"10661\"}}" ); BOOST_CHECK(contract["metadata"].isString()); BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString())); diff --git a/test/libsolidity/ViewPureChecker.cpp b/test/libsolidity/ViewPureChecker.cpp new file mode 100644 index 000000000000..8024151967d2 --- /dev/null +++ b/test/libsolidity/ViewPureChecker.cpp @@ -0,0 +1,406 @@ +/* + 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 . +*/ +/** + * Unit tests for the view and pure checker. + */ + +#include + +#include + +#include + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +BOOST_FIXTURE_TEST_SUITE(ViewPureChecker, AnalysisFramework) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + char const* text = R"( + contract C { + uint x; + function g() pure public {} + function f() view public returns (uint) { return now; } + function h() public { x = 2; } + function i() payable public { x = 2; } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(call_internal_functions_success) +{ + char const* text = R"( + contract C { + function g() pure public { g(); } + function f() view public returns (uint) { f(); g(); } + function h() public { h(); g(); f(); } + function i() payable public { i(); h(); g(); f(); } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(suggest_pure) +{ + char const* text = R"( + contract C { + function g() view public { } + } + )"; + CHECK_WARNING(text, "can be restricted to pure"); +} + +BOOST_AUTO_TEST_CASE(suggest_view) +{ + char const* text = R"( + contract C { + uint x; + function g() public returns (uint) { return x; } + } + )"; + CHECK_WARNING(text, "can be restricted to view"); +} + +BOOST_AUTO_TEST_CASE(call_internal_functions_fail) +{ + CHECK_ERROR( + "contract C{ function f() pure public { g(); } function g() view public {} }", + TypeError, + "Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires \"view\"" + ); +} + +BOOST_AUTO_TEST_CASE(write_storage_fail) +{ + CHECK_WARNING( + "contract C{ uint x; function f() view public { x = 2; } }", + "Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable." + ); +} + +BOOST_AUTO_TEST_CASE(environment_access) +{ + vector view{ + "block.coinbase", + "block.timestamp", + "block.blockhash(7)", + "block.difficulty", + "block.number", + "block.gaslimit", + "msg.gas", + "msg.value", + "msg.sender", + "tx.origin", + "tx.gasprice", + "this", + "address(1).balance" + }; + vector pure{ + "msg.data", + "msg.data[0]", + "msg.sig", + "block.blockhash", // Not evaluating the function + "msg", + "block", + "tx" + }; + for (string const& x: view) + { + CHECK_ERROR( + "contract C { function f() pure public { var x = " + x + "; x; } }", + TypeError, + "Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires \"view\"" + ); + } + for (string const& x: pure) + { + CHECK_WARNING( + "contract C { function f() view public { var x = " + x + "; x; } }", + "restricted to pure" + ); + } +} + +BOOST_AUTO_TEST_CASE(view_error_for_050) +{ + CHECK_ERROR( + "pragma experimental \"v0.5.0\"; contract C { uint x; function f() view { x = 2; } }", + TypeError, + "Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable." + ); + +} + +BOOST_AUTO_TEST_CASE(modifiers) +{ + string text = R"( + contract D { + uint x; + modifier purem(uint) { _; } + modifier viewm(uint) { uint a = x; _; a; } + modifier nonpayablem(uint) { x = 2; _; } + } + contract C is D { + function f() purem(0) pure public {} + function g() viewm(0) view public {} + function h() nonpayablem(0) public {} + function i() purem(x) view public {} + function j() viewm(x) view public {} + function k() nonpayablem(x) public {} + function l() purem(x = 2) public {} + function m() viewm(x = 2) public {} + function n() nonpayablem(x = 2) public {} + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(interface) +{ + string text = R"( + interface D { + function f() view public; + } + contract C is D { + function f() view public {} + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(overriding) +{ + string text = R"( + contract D { + uint x; + function f() public { x = 2; } + } + contract C is D { + function f() public {} + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(returning_structs) +{ + string text = R"( + contract C { + struct S { uint x; } + S s; + function f() view internal returns (S storage) { + return s; + } + function g() public { + f().x = 2; + } + function h() view public { + f(); + f().x; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(mappings) +{ + string text = R"( + contract C { + mapping(uint => uint) a; + function f() view public { + a; + } + function g() view public { + a[2]; + } + function h() public { + a[2] = 3; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(local_storage_variables) +{ + string text = R"( + contract C { + struct S { uint a; } + S s; + function f() view public { + S storage x = s; + x; + } + function g() view public { + S storage x = s; + x = s; + } + function i() public { + s.a = 2; + } + function h() public { + S storage x = s; + x.a = 2; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(builtin_functions) +{ + string text = R"( + contract C { + function f() public { + this.transfer(1); + require(this.send(2)); + selfdestruct(this); + require(this.delegatecall()); + require(this.call()); + } + function g() pure public { + var x = keccak256("abc"); + var y = sha256("abc"); + var z = ecrecover(1, 2, 3, 4); + require(true); + assert(true); + x; y; z; + } + function() payable public {} + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(function_types) +{ + string text = R"( + contract C { + function f() pure public { + function () external nonpayFun; + function () external view viewFun; + function () external pure pureFun; + + nonpayFun; + viewFun; + pureFun; + pureFun(); + } + function g() view public { + function () external view viewFun; + + viewFun(); + } + function h() public { + function () external nonpayFun; + + nonpayFun(); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(creation) +{ + string text = R"( + contract D {} + contract C { + function f() public { new D(); } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(assembly) +{ + string text = R"( + contract C { + struct S { uint x; } + S s; + function e() pure public { + assembly { mstore(keccak256(0, 20), mul(s_slot, 2)) } + } + function f() pure public { + uint x; + assembly { x := 7 } + } + function g() view public { + assembly { for {} 1 { pop(sload(0)) } { } } + } + function h() view public { + assembly { function g() { pop(blockhash(20)) } } + } + function j() public { + assembly { pop(call(0, 1, 2, 3, 4, 5, 6)) } + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(assembly_staticcall) +{ + string text = R"( + contract C { + function i() view public { + assembly { pop(staticcall(0, 1, 2, 3, 4, 5)) } + } + } + )"; + CHECK_WARNING(text, "only available after the Metropolis"); +} + +BOOST_AUTO_TEST_CASE(assembly_jump) +{ + string text = R"( + contract C { + function k() public { + assembly { jump(2) } + } + } + )"; + CHECK_WARNING(text, "low-level EVM features"); +} + +BOOST_AUTO_TEST_CASE(constant) +{ + string text = R"( + contract C { + uint constant x = 2; + function k() pure public returns (uint) { + return x; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +}