diff options
author | chriseth <chris@ethereum.org> | 2017-09-21 22:56:16 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-21 22:56:16 +0800 |
commit | bdeb9e52a2211510644fb53df93fb98258b40a65 (patch) | |
tree | d8fb917e7dc27b937cb4505029bbc3c8c1bc1a67 | |
parent | d7661dd97460250b4e1127b9e7ea91e116143780 (diff) | |
parent | a14fc5ffa1f03d5aa312396a39633d720b04c90a (diff) | |
download | dexon-solidity-bdeb9e52a2211510644fb53df93fb98258b40a65.tar.gz dexon-solidity-bdeb9e52a2211510644fb53df93fb98258b40a65.tar.zst dexon-solidity-bdeb9e52a2211510644fb53df93fb98258b40a65.zip |
Merge pull request #2947 from ethereum/develop
Merge develop into release for 0.4.17.
146 files changed, 4725 insertions, 2210 deletions
diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ba66d79f..00000000 --- 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 315d29bf..c30e3e0f 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 87a141a9..139d4fd5 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 b7874206..bdd6ac46 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: @@ -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. @@ -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 00000000..9410cbcd --- /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 117dd319..1a00ae70 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 00000000..6ddf4c74 --- /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=<INSTALL_DIR> + -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 <BINARY_DIR> --config Release + INSTALL_COMMAND cmake --build <BINARY_DIR> --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 -Subproject e5c8316db8d3daa0abc3b5af8545ce330057608 diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index c0969cae..29d98645 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<M>`: unsigned integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`. e.g. `uint32`, `uint8`, `uint256`. +- ``uint<M>``: unsigned integer type of ``M`` bits, ``0 < M <= 256``, ``M % 8 == 0``. e.g. ``uint32``, ``uint8``, ``uint256``. -- `int<M>`: two's complement signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`. +- ``int<M>``: 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 -- `fixed<M>x<N>`: 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)`. +- ``fixed<M>x<N>``: 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)``. -- `ufixed<M>x<N>`: unsigned variant of `fixed<M>x<N>`. +- ``ufixed<M>x<N>``: unsigned variant of ``fixed<M>x<N>``. -- `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<M>`: binary type of `M` bytes, `0 < M <= 32`. +- ``bytes<M>``: 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: -- `<type>[M]`: a fixed-length array of the given fixed-length type. +- ``<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. -- `<type>[]`: a variable-length array of the given fixed-length type. +- ``<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<M>`: `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<M>`: `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` -- `fixed<M>x<N>`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `int256`. -- `fixed`: as in the `fixed128x19` case -- `ufixed<M>x<N>`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `uint256`. -- `ufixed`: as in the `ufixed128x19` case -- `bytes<M>`: `enc(X)` is the sequence of bytes in `X` padded with zero-bytes to a length of 32. +- ``uint<M>``: ``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<M>``: ``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`` +- ``fixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``int256``. +- ``fixed``: as in the ``fixed128x19`` case +- ``ufixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``uint256``. +- ``ufixed``: as in the ``ufixed128x19`` case +- ``bytes<M>``: ``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 <fallback-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 <view-functions>`); -- `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 <pure-functions>`), `view` (same as `constant` above), `nonpayable` and `payable` (same as `payable` above). +- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function <fallback-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 <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain state <view-functions>`), ``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 6495699f..f5abcdc8 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 ea242085..c3686ebf 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 50e7f3d1..69600fc1 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 <https://remix.ethereum.org/>`_, make the creation process seamless using UI elements. + +Creating contracts programatically on Ethereum is best done via using the JavaScript API `web3.js <https://github.com/etherem/web3.js>`_. +As of today it has a method called `web3.eth.Contract <https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#new-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 <ABI>` 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. <events>`. +#. :ref:`Creating other contracts <creating-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 ``<address>.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 9d1b2ce3..01caa5b1 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 <https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/eth>`_ 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 796e9238..0497365b 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 5f1a981e..f59d86e7 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 <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/65_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 8c33fb9d..cb093bd6 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 782bb606..71607745 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 1a3cf638..aedc0c09 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 00000000..dbde87e8 --- /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": <Swarm hash>}`` 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. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index e78c4807..6d6c25ac 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 -<https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI>`_. 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 <ABI>`. 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": <Swarm hash>}`` 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 <visibility-and-getters>` for you automatically. * If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`. * If 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`` - ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei - ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure - ``<address>.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 ca6b970c..139c8a42 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 fb88b006..5c291f35 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<rational_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 <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 64795306..887535da 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -73,6 +73,18 @@ Block and Transaction Properties 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 ``msg.sender`` as an argument. diff --git a/libdevcore/ABI.h b/libdevcore/ABI.h deleted file mode 100644 index 8b9e5c98..00000000 --- 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 <http://www.gnu.org/licenses/>. -*/ -/** @file ABI.h - * @author Gav Wood <i@gavwood.com> - * @date 2014 - */ - -#pragma once - -#include <libdevcore/Common.h> -#include <libdevcore/FixedHash.h> -#include <libdevcore/CommonData.h> -#include <libdevcore/SHA3.h> - -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 <class T> struct ABISerialiser {}; -template <unsigned N> struct ABISerialiser<FixedHash<N>> { static bytes serialise(FixedHash<N> 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<u256> { static bytes serialise(u256 const& _t) { return h256(_t).asBytes(); } }; -template <> struct ABISerialiser<u160> { static bytes serialise(u160 const& _t) { return bytes(12, 0) + h160(_t).asBytes(); } }; -template <> struct ABISerialiser<string32> { static bytes serialise(string32 const& _t) { bytes ret; bytesConstRef((byte const*)_t.data(), 32).populate(bytesRef(&ret)); return ret; } }; -template <> struct ABISerialiser<std::string> -{ - 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 <class T, class ... U> bytes abiInAux(T const& _t, U const& ... _u) -{ - return ABISerialiser<T>::serialise(_t) + abiInAux(_u ...); -} - -template <class ... T> bytes abiIn(std::string _id, T const& ... _t) -{ - return keccak256(_id).ref().cropped(0, 4).toBytes() + abiInAux(_t ...); -} - -template <class T> struct ABIDeserialiser {}; -template <unsigned N> struct ABIDeserialiser<FixedHash<N>> { static FixedHash<N> deserialise(bytesConstRef& io_t) { static_assert(N <= 32, "Parameter sizes must be at most 32 bytes."); FixedHash<N> ret; io_t.cropped(32 - N, N).populate(ret.ref()); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser<u256> { static u256 deserialise(bytesConstRef& io_t) { u256 ret = fromBigEndian<u256>(io_t.cropped(0, 32)); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser<u160> { static u160 deserialise(bytesConstRef& io_t) { u160 ret = fromBigEndian<u160>(io_t.cropped(12, 20)); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser<string32> { 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<std::string> -{ - 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 <class T> T abiOut(bytes const& _data) -{ - bytesConstRef o(&_data); - return ABIDeserialiser<T>::deserialise(o); -} - -template <class T> T abiOut(bytesConstRef& _data) -{ - return ABIDeserialiser<T>::deserialise(_data); -} - -} -} diff --git a/libdevcore/Common.h b/libdevcore/Common.h index 9d6dd408..2543855d 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -37,13 +37,7 @@ #pragma warning(disable:3682) //call through incomplete class #endif -#include <map> -#include <unordered_map> -#include <vector> -#include <set> -#include <unordered_set> -#include <functional> -#include <string> +#include <libdevcore/vector_ref.h> #if defined(__GNUC__) #pragma warning(push) @@ -67,14 +61,13 @@ #pragma GCC diagnostic pop #endif // defined(__GNUC__) -#include "vector_ref.h" +#include <map> +#include <vector> +#include <functional> +#include <string> 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<byte const>; // Numeric types. using bigint = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<>>; -using u64 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<64, 64, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>; -using u128 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<128, 128, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>; -using u256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>; -using s256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>>; -using u160 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<160, 160, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>; -using s160 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<160, 160, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>>; -using u512 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<512, 512, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>; -using s512 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<512, 512, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>>; -using u256s = std::vector<u256>; -using u160s = std::vector<u160>; -using u256Set = std::set<u256>; -using u160Set = std::set<u160>; +using u256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>; +using s256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>>; +using u160 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<160, 160, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>; // Map types. using StringMap = std::map<std::string, std::string>; -// Hash types. -using StringHashMap = std::unordered_map<std::string, std::string>; - // String types. using strings = std::vector<std::string>; -// Fixed-length string types. -using string32 = std::array<char, 32>; - -// 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 <size_t n> inline u256 exp10() -{ - return exp10<n - 1>() * 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 @@ private: std::function<void(void)> m_f; }; -enum class WithExisting: int -{ - Trust = 0, - Verify, - Rescue, - Kill -}; - } diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 14caf494..db11e61c 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<char, char> 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 5df8986a..765707f8 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -26,11 +26,10 @@ #include <libdevcore/Common.h> #include <vector> -#include <algorithm> -#include <unordered_set> #include <type_traits> #include <cstring> #include <string> +#include <set> namespace dev { diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index 52829455..5d47937b 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -35,6 +35,9 @@ using namespace std; using namespace dev; +namespace +{ + template <typename _T> 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<string>(_file); diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h index 141e9ffd..cd6e1da1 100644 --- a/libdevcore/FixedHash.h +++ b/libdevcore/FixedHash.h @@ -23,20 +23,18 @@ #pragma once +#include <libdevcore/CommonData.h> + +#include <boost/functional/hash.hpp> +#include <boost/io/ios_state.hpp> + #include <array> #include <cstdint> #include <algorithm> -#include <boost/functional/hash.hpp> -#include <boost/io/ios_state.hpp> -#include "CommonData.h" namespace dev { -/// Compile-time calculation of Log2 of constant values. -template <unsigned N> struct StaticLog2 { enum { result = 1 + StaticLog2<N/2>::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 @@ public: /// 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 @@ public: /// 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<unsigned>(_b.size(), N)); else { m_data.fill(0); if (_t != FailIfDifferent) { auto c = std::min<unsigned>(_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 @@ public: // 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 @@ public: /// @returns a constant reference to the object's data as an STL array. std::array<byte, N> 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 <unsigned P, unsigned M> inline FixedHash& shiftBloom(FixedHash<M> const& _h) - { - return (*this |= _h.template bloomPart<P, N>()); - } - - template <unsigned P, unsigned M> inline bool containsBloom(FixedHash<M> const& _h) - { - return contains(_h.template bloomPart<P, N>()); - } - - template <unsigned P, unsigned M> inline FixedHash<M> bloomPart() const - { - unsigned const c_bloomBits = M * 8; - unsigned const c_mask = c_bloomBits - 1; - unsigned const c_bloomBytes = (StaticLog2<c_bloomBits>::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<M> 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 @@ private: std::array<byte, N> 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<uint64_t const*>(value.data()); - return boost::hash_range(data, data + 4); -} - /// Stream I/O for the FixedHash class. template <unsigned N> inline std::ostream& operator<<(std::ostream& _out, FixedHash<N> const& _h) @@ -234,56 +153,7 @@ inline std::ostream& operator<<(std::ostream& _out, FixedHash<N> 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<h512>; -using h256s = std::vector<h256>; -using h160s = std::vector<h160>; -using h256Set = std::set<h256>; -using h160Set = std::set<h160>; -using h256Hash = std::unordered_set<h256>; -using h160Hash = std::unordered_set<h160>; - -/// 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<dev::FixedHash> to dev::FixedHash::hash. - template<> struct hash<dev::h64>: dev::h64::hash {}; - template<> struct hash<dev::h128>: dev::h128::hash {}; - template<> struct hash<dev::h160>: dev::h160::hash {}; - template<> struct hash<dev::h256>: dev::h256::hash {}; - template<> struct hash<dev::h512>: dev::h512::hash {}; -} diff --git a/libdevcore/SHA3.cpp b/libdevcore/SHA3.cpp index 4d82ec85..b0e40ccb 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 1a561066..d1e2cc98 100644 --- a/libdevcore/SHA3.h +++ b/libdevcore/SHA3.h @@ -23,8 +23,9 @@ #pragma once +#include <libdevcore/FixedHash.h> + #include <string> -#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<unsigned N> inline h256 keccak256(FixedHash<N> 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 78188668..1c718200 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 a5da96f5..a06f7bda 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 0f543181..b4dcff65 100644 --- a/libdevcore/vector_ref.h +++ b/libdevcore/vector_ref.h @@ -23,6 +23,8 @@ public: using value_type = _T; using element_type = _T; using mutable_value_type = typename std::conditional<std::is_const<_T>::value, typename std::remove_const<_T>::type, _T>::type; + using string_type = typename std::conditional<std::is_const<_T>::value, std::string const, std::string>::type; + using vector_type = typename std::conditional<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::type> const, std::vector<_T>>::type; static_assert(std::is_pod<value_type>::value, "vector_ref can only be used with PODs due to its low-level treatment of data."); @@ -30,18 +32,13 @@ public: /// 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<std::is_const<_T>::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<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::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<std::is_const<_T>::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<mutable_value_type> 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<mutable_value_type> toVector() const { return std::vector<mutable_value_type>(m_data, m_data + m_count); } std::vector<unsigned char> toBytes() const { return std::vector<unsigned char>(reinterpret_cast<unsigned char const*>(m_data), reinterpret_cast<unsigned char const*>(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 @@ public: _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 <class T> bool overlapsWith(vector_ref<T> _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<typename std::remove_const<_T>::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<typename std::remove_const<_T>::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 @@ public: 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<class _T> vector_ref<_T const> ref(_T const& _t) { return vector_ref<_T const>(&_t, 1); } -template<class _T> vector_ref<_T> ref(_T& _t) { return vector_ref<_T>(&_t, 1); } -template<class _T> vector_ref<_T const> ref(std::vector<_T> const& _t) { return vector_ref<_T const>(&_t); } -template<class _T> vector_ref<_T> ref(std::vector<_T>& _t) { return vector_ref<_T>(&_t); } - } diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 0a3bf6b8..df691e7d 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -24,6 +24,7 @@ #include <libevmasm/CommonSubexpressionEliminator.h> #include <libevmasm/ControlFlowGraph.h> #include <libevmasm/PeepholeOptimiser.h> +#include <libevmasm/JumpdestRemover.h> #include <libevmasm/BlockDeduplicator.h> #include <libevmasm/ConstantOptimiser.h> #include <libevmasm/GasMeter.h> @@ -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 @@ private: } -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<u256, u256> Assembly::optimiseInternal(OptimiserSettings _settings) +map<u256, u256> Assembly::optimiseInternal( + OptimiserSettings const& _settings, + std::set<size_t> const& _tagsReferencedFromOutside +) { // Run optimisation for sub-assemblies. for (size_t subId = 0; subId < m_subs.size(); ++subId) @@ -376,7 +383,10 @@ map<u256, u256> Assembly::optimiseInternal(OptimiserSettings _settings) OptimiserSettings settings = _settings; // Disable creation mode for sub-assemblies. settings.isCreation = false; - map<u256, u256> subTagReplacements = m_subs[subId]->optimiseInternal(settings); + map<u256, u256> 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<u256, u256> 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 451b4ea0..885192e4 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -47,6 +47,8 @@ public: 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 @@ public: struct OptimiserSettings { bool isCreation = false; + bool runJumpdestRemover = false; bool runPeephole = false; bool runDeduplicate = false; bool runCSE = false; @@ -110,7 +113,7 @@ public: }; /// 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 @@ public: /// 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<u256, u256> 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<u256, u256> optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> 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<std::string, size_t> m_namedTags; AssemblyItems m_items; std::map<h256, bytes> m_data; /// Data that is appended to the very end of the contract. @@ -159,7 +171,7 @@ protected: 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 419a8c0b..cfe91be0 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<unsigned>(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 d21be07e..b7c69531 100644 --- a/libevmasm/BlockDeduplicator.cpp +++ b/libevmasm/BlockDeduplicator.cpp @@ -22,10 +22,13 @@ */ #include <libevmasm/BlockDeduplicator.h> -#include <functional> + #include <libevmasm/AssemblyItem.h> #include <libevmasm/SemanticInformation.h> +#include <functional> +#include <set> + using namespace std; using namespace dev; using namespace dev::eth; diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index 82982e25..c450b0b4 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -91,7 +91,7 @@ protected: } /// Replaces all constants i by the code given in @a _replacement[i]. - static void replaceConstants(AssemblyItems& _items, std::map<u256, AssemblyItems> const& _replacement); + static void replaceConstants(AssemblyItems& _items, std::map<u256, AssemblyItems> const& _replacements); Params m_params; u256 const& m_value; diff --git a/libevmasm/ExpressionClasses.h b/libevmasm/ExpressionClasses.h index 5d53b292..6b426e97 100644 --- a/libevmasm/ExpressionClasses.h +++ b/libevmasm/ExpressionClasses.h @@ -23,11 +23,13 @@ #pragma once +#include <libdevcore/Common.h> +#include <libevmasm/AssemblyItem.h> + #include <vector> #include <map> #include <memory> -#include <libdevcore/Common.h> -#include <libevmasm/AssemblyItem.h> +#include <set> namespace dev { diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 6a7c80e0..dad952bc 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 89a25fb7..afbef71d 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 00000000..b6016798 --- /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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Alex Beregszaszi + * Removes unused JUMPDESTs. + */ + +#include "JumpdestRemover.h" + +#include <libsolidity/interface/Exceptions.h> + +#include <libevmasm/AssemblyItem.h> + +using namespace std; +using namespace dev::eth; +using namespace dev; + + +bool JumpdestRemover::optimise(set<size_t> const& _tagsReferencedFromOutside) +{ + set<size_t> 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<size_t> JumpdestRemover::referencedTags(AssemblyItems const& _items, size_t _subId) +{ + set<size_t> 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 00000000..2dad0927 --- /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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Alex Beregszaszi + * Removes unused JUMPDESTs. + */ +#pragma once + +#include <vector> +#include <cstddef> +#include <set> + +namespace dev +{ +namespace eth +{ +class AssemblyItem; +using AssemblyItems = std::vector<AssemblyItem>; + +class JumpdestRemover +{ +public: + explicit JumpdestRemover(AssemblyItems& _items): m_items(_items) {} + + bool optimise(std::set<size_t> 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<size_t> referencedTags(AssemblyItems const& _items, size_t _subId); + +private: + AssemblyItems& m_items; +}; + +} +} diff --git a/libevmasm/LinkerObject.cpp b/libevmasm/LinkerObject.cpp index 06607089..8b7d9e06 100644 --- a/libevmasm/LinkerObject.cpp +++ b/libevmasm/LinkerObject.cpp @@ -38,7 +38,7 @@ void LinkerObject::link(map<string, h160> const& _libraryAddresses) std::map<size_t, std::string> 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 e94a8ba4..31fdd317 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 f63f0c61..ceb3fbdd 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 5b02061f..e5ea7c18 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 cfc9b8a5..8e90a912 100644 --- a/libjulia/backends/evm/AbstractAssembly.h +++ b/libjulia/backends/evm/AbstractAssembly.h @@ -66,6 +66,8 @@ public: 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 173d5e93..1d499b20 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 69585822..593cee6a 100644 --- a/libjulia/backends/evm/EVMAssembly.h +++ b/libjulia/backends/evm/EVMAssembly.h @@ -52,6 +52,8 @@ public: 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 @@ private: LabelID m_nextLabelId = 0; int m_stackHeight = 0; bytes m_bytecode; + std::map<std::string, LabelID> m_namedLabels; std::map<LabelID, size_t> m_labelPositions; std::map<size_t, LabelID> m_labelReferences; std::vector<size_t> m_assemblySizePositions; diff --git a/libjulia/backends/evm/EVMCodeTransform.cpp b/libjulia/backends/evm/EVMCodeTransform.cpp index 704aa3c1..66f593e8 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<Identifier> 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 cd452c5b..951c8a50 100644 --- a/libjulia/backends/evm/EVMCodeTransform.h +++ b/libjulia/backends/evm/EVMCodeTransform.h @@ -50,13 +50,15 @@ public: 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<Context>() ) @@ -78,6 +80,7 @@ protected: bool _julia, bool _evm15, ExternalIdentifierAccess const& _identifierAccess, + bool _useNamedLabelsForFunctions, int _stackAdjustment, std::shared_ptr<Context> _context ): @@ -85,6 +88,7 @@ protected: 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 @@ private: /// @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 @@ private: /// to @a _blackStartStackHeight. void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight); + void generateMultiAssignment(std::vector<solidity::assembly::Identifier> const& _variableNames); void generateAssignment(solidity::assembly::Identifier const& _variableName); /// Determines the stack height difference to the given variables. Throws @@ -136,6 +141,7 @@ private: 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 4ec11ca9..b69675aa 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 9a846b31..b3fb5258 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<string> validTags = set<string>{"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<string> validTags = set<string>{"author", "dev", "notice", "return", "param", "why3"}; + static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param"}; parseDocStrings(_node, _annotation, validTags, "functions"); set<string> validParams; diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index a54b8c8d..62dbd394 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -43,13 +43,13 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared< make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)), make_shared<MagicVariableDeclaration>("addmod", - make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod)), + make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("mulmod", - make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod)), + make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("sha3", - make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("keccak256", - make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("log0", make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), make_shared<MagicVariableDeclaration>("log1", @@ -61,17 +61,17 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared< make_shared<MagicVariableDeclaration>("log4", make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)), make_shared<MagicVariableDeclaration>("sha256", - make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true)), + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("ecrecover", - make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover)), + make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("ripemd160", - make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true)), + make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("assert", - make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert)), + make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("require", - make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require)), + make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("revert", - make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert))}) + make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure))}) { } diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 59bd3b1f..d83697cd 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -148,7 +148,7 @@ public: 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 dbdf50e0..91d2b0b9 100644 --- a/libsolidity/analysis/PostTypeChecker.h +++ b/libsolidity/analysis/PostTypeChecker.h @@ -50,8 +50,8 @@ private: 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 2f130414..ffa538b6 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 a3080b42..24ed119f 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 d2571cd3..187eb26f 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 fa34bab3..7fffbec0 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -66,6 +66,7 @@ private: 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 99f3c64c..4b2ec8d6 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<string>(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<FunctionDefinition const*> const& overloads = it.second; - for (size_t i = 0; i < overloads.size(); ++i) + set<size_t> 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<string>(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<ArrayType const*>(_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<ArrayType const*>(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<TupleType>(functionType->returnParameterTypes()); + if (auto functionName = dynamic_cast<Identifier const*>(&_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<RationalNumberType const*>(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 f2e13765..0c6f54d3 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -63,6 +63,7 @@ private: 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 00000000..7f28c7d2 --- /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 <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/analysis/ViewPureChecker.h> + +#include <libevmasm/SemanticInformation.h> + +#include <libsolidity/inlineasm/AsmData.h> +#include <libsolidity/ast/ExperimentalFeatures.h> + +#include <functional> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +namespace +{ + +class AssemblyViewPureChecker: public boost::static_visitor<void> +{ +public: + explicit AssemblyViewPureChecker(std::function<void(StateMutability, SourceLocation const&)> _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<void(StateMutability, SourceLocation const&)> m_reportMutability; +}; + +} + +bool ViewPureChecker::check() +{ + // The bool means "enforce view with errors". + map<ContractDefinition const*, bool> contracts; + + for (auto const& node: m_ast) + { + SourceUnit const* source = dynamic_cast<SourceUnit const*>(node.get()); + solAssert(source, ""); + bool enforceView = source->annotation().experimentalFeatures.count(ExperimentalFeature::V050); + for (ContractDefinition const* c: source->filteredNodes<ContractDefinition>(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<VariableDeclaration const*>(declaration)) + { + if (varDecl->isStateVariable() && !varDecl->isConstant()) + mutability = writes ? StateMutability::NonPayable : StateMutability::View; + } + else if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration)) + { + switch (magicVar->type()->category()) + { + case Type::Category::Contract: + solAssert(_identifier.name() == "this" || _identifier.name() == "super", ""); + if (!dynamic_cast<ContractType const&>(*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<FunctionType const&>(*_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<ArrayType const&>(*_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<decltype(mod)>(_modifier.name()->annotation().referencedDeclaration)) + { + solAssert(m_inferredMutability.count(mod), ""); + reportMutability(m_inferredMutability.at(mod), _modifier.location()); + } + else + solAssert(dynamic_cast<ContractDefinition const*>(_modifier.name()->annotation().referencedDeclaration), ""); +} + diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h new file mode 100644 index 00000000..fec060b6 --- /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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/ast/ASTEnums.h> +#include <libsolidity/ast/ASTForward.h> +#include <libsolidity/ast/ASTVisitor.h> + +#include <libsolidity/interface/ErrorReporter.h> + +#include <map> +#include <memory> + +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<std::shared_ptr<ASTNode>> 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<std::shared_ptr<ASTNode>> 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<ModifierDefinition const*, StateMutability> m_inferredMutability; +}; + +} +} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 7f4dea0e..a805322b 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -176,11 +176,19 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons m_interfaceEvents.reset(new vector<EventDefinition const*>()); 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<pair<FixedHash<4>, 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<Declaration const*> const& ContractDefinition::inheritableMembers() const { if (!m_inheritableMembers) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 4592a190..75b8e946 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -180,6 +180,7 @@ public: /// @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 @@ public: /// 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 @@ private: std::vector<ASTPointer<ASTNode>> m_subNodes; ContractKind m_contractKind; - // parsed Natspec documentation of the contract. - Json::Value m_userDocumentation; - Json::Value m_devDocumentation; - std::vector<ContractDefinition const*> m_linearizedBaseContracts; mutable std::unique_ptr<std::vector<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList; mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index fd9efb4d..3d4236cc 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 afc53bfe..51249f20 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<ASTString> const& _namePath) const +string ASTJsonConverter::namePathToString(std::vector<ASTString> const& _namePath) { return boost::algorithm::join(_namePath, "."); } @@ -171,7 +171,7 @@ void ASTJsonConverter::appendExpressionAttributes( _attributes += exprAttributes; } -Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<assembly::Identifier const* ,InlineAssemblyAnnotation::ExternalIdentifierInfo> _info) +Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<assembly::Identifier const* ,InlineAssemblyAnnotation::ExternalIdentifierInfo> _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 60c660c1..9a886220 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -120,7 +120,7 @@ private: std::vector<std::pair<std::string, Json::Value>>&& _attributes ); std::string sourceLocationToString(SourceLocation const& _location) const; - std::string namePathToString(std::vector<ASTString> const& _namePath) const; + static std::string namePathToString(std::vector<ASTString> const& _namePath); static Json::Value idOrNull(ASTNode const* _pt) { return _pt ? Json::Value(nodeId(*_pt)) : Json::nullValue; @@ -129,13 +129,13 @@ private: { return _node ? toJson(*_node) : Json::nullValue; } - Json::Value inlineAssemblyIdentifierToJson(std::pair<assembly::Identifier const* , InlineAssemblyAnnotation::ExternalIdentifierInfo> _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<assembly::Identifier const* , InlineAssemblyAnnotation::ExternalIdentifierInfo> _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 @@ private: } return tmp; } - Json::Value typePointerToJson(TypePointer _tp); - Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps); + static Json::Value typePointerToJson(TypePointer _tp); + static Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps); void appendExpressionAttributes( std::vector<std::pair<std::string, Json::Value>> &_attributes, ExpressionAnnotation const& _annotation diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index 392179ef..81e6cc44 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -21,9 +21,12 @@ */ #include <libsolidity/ast/ASTPrinter.h> -#include <boost/algorithm/string/join.hpp> #include <libsolidity/ast/AST.h> +#include <json/json.h> + +#include <boost/algorithm/string/join.hpp> + 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 2c089671..3ecfac7b 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<std::string, ExperimentalFeature> 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 5e61cdee..83a5b465 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -33,6 +33,7 @@ #include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/range/adaptor/reversed.hpp> +#include <boost/range/algorithm/copy.hpp> #include <boost/range/adaptor/sliced.hpp> #include <boost/range/adaptor/transformed.hpp> @@ -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<VariableDeclaration> const& variable: m_struct.members()) + if (variable->annotation().type->canLiveOutsideStorage()) + types.push_back(variable->annotation().type); + return types; +} + set<string> StructType::membersMissingInMemory() const { set<string> missing; @@ -1831,6 +1904,33 @@ set<string> StructType::membersMissingInMemory() const return missing; } +bool StructType::recursive() const +{ + if (!m_recursive.is_initialized()) + { + set<StructDefinition const*> structsSeen; + function<bool(StructType const*)> check = [&](StructType const* t) -> bool + { + StructDefinition const* str = &t->structDefinition(); + if (structsSeen.count(str)) + return true; + structsSeen.insert(str); + for (ASTPointer<VariableDeclaration> const& variable: str->members()) + { + Type const* memberType = variable->annotation().type.get(); + while (dynamic_cast<ArrayType const*>(memberType)) + memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get(); + if (StructType const* innerStruct = dynamic_cast<StructType const*>(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<TupleType>() : 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<string> 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<string> 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<FixedBytesType>(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<ContractDefinition const&>(*m_declaration->scope()).isLibrary(); - - string ret = m_declaration->name() + "("; - + bool const inLibrary = dynamic_cast<ContractDefinition const&>(*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<IntegerType>(0, IntegerType::Modifier::Address)}, {"timestamp", make_shared<IntegerType>(256)}, - {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash)}, + {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)}, {"difficulty", make_shared<IntegerType>(256)}, {"number", make_shared<IntegerType>(256)}, {"gaslimit", make_shared<IntegerType>(256)} diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index ce2d3bf8..8ba55521 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -32,10 +32,12 @@ #include <boost/noncopyable.hpp> #include <boost/rational.hpp> +#include <boost/optional.hpp> #include <memory> #include <string> #include <map> +#include <set> namespace dev { @@ -244,9 +246,15 @@ public: virtual std::string toString(bool _short) const = 0; std::string toString() const { return toString(false); } - /// @returns the canonical name of this type for use in function signatures. - /// @param _addDataLocation if true, includes data location for reference types if it is "storage". - virtual std::string canonicalName(bool /*_addDataLocation*/) const { return toString(true); } + /// @returns the canonical name of this type for use in library function signatures. + virtual std::string canonicalName() const { return toString(true); } + /// @returns the signature of this type in external functions, i.e. `uint256` for integers + /// or `(uint256,bytes8)[2]` for an array of structs. If @a _structsByName, + /// structs are given by canonical name like `ContractName.StructName[2]`. + virtual std::string signatureInExternalFunction(bool /*_structsByName*/) const + { + return canonicalName(); + } virtual u256 literalValue(Literal const*) const { solAssert(false, "Literal value requested for type without literals."); @@ -618,7 +626,8 @@ public: virtual bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } 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 @@ public: 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 @@ public: virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual TypePointer encodingType() const override { - return location() == DataLocation::Storage ? std::make_shared<IntegerType>(256) : TypePointer(); + return location() == DataLocation::Storage ? std::make_shared<IntegerType>(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 @@ public: 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<std::string> 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<bool> m_recursive; }; /** @@ -779,7 +798,7 @@ public: 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 @@ public: strings(), _kind, _arbitraryParameters, - nullptr, _stateMutability ) { @@ -915,8 +933,8 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: { 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 a2938ed7..9f6c55ba 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 <functionName>(headStart <valueParams>) -> tail { + tail := add(headStart, <headSize>) + <encodeElements> + } + )"); + 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, <headSize>) - <encodeElements> - <deepestStackElement> := 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, <pos>), sub(tail, headStart)) + tail := <abiEncode>(<values> tail) + )") : + string(R"( + <abiEncode>(<values> add(headStart, <pos>)) + )") + ); + 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, <pos>), sub(tail, $headStart)) - tail := <abiEncode>(<values> tail) - )") : - string(R"( - <abiEncode>(<values> add($headStart, <pos>)) - )") - ); - 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<StructType const*>(&to)) + else if (auto const* toStruct = dynamic_cast<StructType const*>(&to)) { - solUnimplementedAssert(false, "Structs not yet implemented."); + StructType const* fromStruct = dynamic_cast<StructType const*>(&_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 := <encodeToMemoryFun>(<arrayElementAccess>(srcPtr), tail) + tail := <encodeToMemoryFun>(<arrayElementAccess>, tail) srcPtr := <nextArrayElement>(srcPtr) pos := add(pos, <elementEncodedSize>) } @@ -541,7 +551,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( let srcPtr := <dataAreaFun>(value) for { let i := 0 } lt(i, length) { i := add(i, 1) } { - <encodeToMemoryFun>(<arrayElementAccess>(srcPtr), pos) + <encodeToMemoryFun>(<arrayElementAccess>, pos) srcPtr := <nextArrayElement>(srcPtr) pos := add(pos, <elementEncodedSize>) } @@ -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 <functionName>(value, pos) <return> { + let tail := add(pos, <headSize>) + <init> + <#members> + { + // <memberName> + <encode> + } + </members> + <assignEnd> + } + )"); + 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<map<string, string>> 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"( + <preprocess> + let memberValue := <retrieveValue> + )" + ( + dynamicMember ? + string(R"( + mstore(add(pos, <encodingOffset>), sub(tail, pos)) + tail := <abiEncode>(memberValue, tail) + )") : + string(R"( + <abiEncode>(memberValue, add(pos, <encodingOffset>)) + )") + ) + ); + 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 e43e2323..de2a140a 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -44,15 +44,18 @@ using TypePointers = std::vector<TypePointer>; /// 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: <headStart> <value_n> ... <value_1>, i.e. + /// the layout on the stack is <value_1> ... <value_n> <headStart> 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 @@ public: 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 @@ private: 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 67ca22f1..e17188c2 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 8c63ea9c..06654486 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -40,6 +40,8 @@ public: m_context(&m_runtimeContext) { } + /// Compiles a contract. + /// @arg _metadata contains the to be injected metadata CBOR void compileContract( ContractDefinition const& _contract, std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts, @@ -51,14 +53,21 @@ public: ContractDefinition const& _contract, std::map<ContractDefinition const*, eth::Assembly const*> 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 ed780d0b..5a77162e 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<string> const& _localVariables, - map<string, string> 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<Scanner>(CharStream(*assembly), "--CODEGEN--"); + auto scanner = make_shared<Scanner>(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 96cbf6c1..7743fd3f 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -22,6 +22,8 @@ #pragma once +#include <libsolidity/codegen/ABIFunctions.h> + #include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> @@ -56,7 +58,9 @@ public: m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); } + /// Update currently enabled set of experimental features. void setExperimentalFeatures(std::set<ExperimentalFeature> 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 @@ public: /// @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<ContractDefinition const*> 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<ContractDefinition const*> 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 @@ public: ); /// 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 @@ public: 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 @@ public: /// 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<std::string> const& _localVariables = std::vector<std::string>(), - std::map<std::string, std::string> const& _replacements = std::map<std::string, std::string>{} + 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 @@ public: /// @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 @@ private: size_t m_runtimeSub = -1; /// An index of low-level function labels by name. std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions; + /// Container for ABI functions to be generated. + ABIFunctions m_abiFunctions; /// The queue of low-level functions to generate. std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue; }; diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index a0fc5d55..37aa1aea 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<ReferenceType const*>(&_type)) { - solAssert(ref->location() == DataLocation::Memory, ""); + solUnimplementedAssert(ref->location() == DataLocation::Memory, ""); storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries); } else if (auto str = dynamic_cast<StringLiteralType const*>(&_type)) @@ -310,18 +310,13 @@ void CompilerUtils::abiEncode( { // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart> - vector<string> 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 18b70250..5e45699b 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -38,14 +38,20 @@ public: /// 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: <mem_start> void fetchFreeMemoryPointer(); /// Stores the free memory pointer from the stack. + /// Stack pre: <mem_end> + /// Stack post: void storeFreeMemoryPointer(); /// Allocates a number of bytes in memory as given on the stack. /// Stack pre: <size> /// Stack post: <mem_start> void allocateMemory(); /// Appends code that transforms memptr to (memptr - free_memptr) memptr + /// Stack pre: <mem_end> + /// Stack post: <size> <mem_start> void toSizeAfterFreeMemoryPointer(); /// Loads data from memory to the stack. @@ -105,6 +111,8 @@ public: /// 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: <value0> <value1> ... <valueN-1> <head_start> + /// Stack post: <mem_ptr> void abiEncode( TypePointers const& _givenTypes, TypePointers const& _targetTypes, @@ -185,9 +193,13 @@ public: static unsigned sizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes); /// Helper function to shift top value on the stack to the left. + /// Stack pre: <value> <shift_by_bits> + /// Stack post: <shifted_value> void leftShiftNumberOnStack(unsigned _bits); /// Helper function to shift top value on the stack to the right. + /// Stack pre: <value> <shift_by_bits> + /// Stack post: <shifted_value> 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 e53f1b94..92782b8d 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 @@ private: unsigned stackHeight; }; +} + void ContractCompiler::compileContract( ContractDefinition const& _contract, std::map<const ContractDefinition*, eth::Assembly const*> 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<ContractDefinition const*> 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<ArrayType const&>(*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 38c1e045..7c5ee59f 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -96,8 +96,8 @@ private: 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 639bfc32..c94baa10 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<StorageItem>(_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 5f6c3d64..cdfa096e 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -119,7 +119,7 @@ private: /// @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 b8dac366..63188acd 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 32d92a2a..70dc1585 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -56,10 +56,10 @@ public: 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 76b0bbd5..e5bdc90f 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 6d0c0255..dded9f76 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -83,6 +83,10 @@ public: { 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 2a36a590..a7d7ead1 100644 --- a/libsolidity/inlineasm/AsmCodeGen.h +++ b/libsolidity/inlineasm/AsmCodeGen.h @@ -46,7 +46,8 @@ public: 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 db5840bc..b0dd85ca 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<Statement> 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<Identifier> variableNames; std::shared_ptr<Statement> value; }; /// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; }; struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; }; diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index d84fe999..3087ad86 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<assembly::Identifier>(statement); + + Assignment assignment = createWithLocation<Assignment>(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<assembly::Identifier>(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 47ede91d..a5272808 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 3df9d1f8..aefb34af 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<ArrayType const*>(&_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<StructType const*>(&_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 95b162a9..db70729d 100644 --- a/libsolidity/interface/ABI.h +++ b/libsolidity/interface/ABI.h @@ -50,6 +50,10 @@ private: std::vector<TypePointer> 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 23524bb3..504ad92c 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<eth::LinkerObject>(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 363f45dd..51544f8a 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -36,6 +36,7 @@ #include <libsolidity/analysis/StaticAnalyzer.h> #include <libsolidity/analysis/PostTypeChecker.h> #include <libsolidity/analysis/SyntaxChecker.h> +#include <libsolidity/analysis/ViewPureChecker.h> #include <libsolidity/codegen/Compiler.h> #include <libsolidity/formal/SMTChecker.h> #include <libsolidity/interface/ABI.h> @@ -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<ASTNode> const& node: source->ast->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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); @@ -239,6 +223,16 @@ bool CompilerStack::analyze() if (noErrors) { + vector<ASTPointer<ASTNode>> 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); for (Source const* source: m_sourceOrder) smtChecker.analyze(*source->ast); @@ -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<string> CompilerStack::sourceNames() const diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 361b8a45..f1bbae47 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -116,6 +116,9 @@ public: 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 @@ public: 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 @@ public: /// @returns false on error. bool parseAndAnalyze(); - /// @returns a list of the contract names in the sources. - std::vector<std::string> contractNames() const; - /// Compiles the source units that were previously added and parsed. /// @returns false on error. bool compile(); @@ -158,6 +158,9 @@ public: /// start line, start column, end line, end column std::tuple<int, int, int, int> positionFromSourceLocation(SourceLocation const& _sourceLocation) const; + /// @returns a list of the contract names in the sources. + std::vector<std::string> 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 @@ public: /// 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 @@ public: /// @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 241d6b43..a87db21d 100644 --- a/libsolidity/interface/ErrorReporter.h +++ b/libsolidity/interface/ErrorReporter.h @@ -39,6 +39,9 @@ public: 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 @@ public: 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 9ac3efea..0701f821 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<Type const>; 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 @@ public: 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<std::string> const& _names, - std::vector<TypePointer> const& _types, - bool _forLibrary - ); /// @returns concatenation of all content under the given tag name. static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name); }; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index be823743..b4fbbef9 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 ddfdb667..ce8a9f01 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -903,11 +903,13 @@ ASTPointer<Statement> Parser::parseStatement() { statement = ASTNodeFactory(*this).createNode<PlaceholderStatement>(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<Expression> Parser::parseLeftHandSideExpression( { expectToken(Token::New); ASTPointer<TypeName> typeName(parseTypeName(false)); - nodeFactory.setEndPositionFromNode(typeName); + if (typeName) + nodeFactory.setEndPositionFromNode(typeName); + else + nodeFactory.markEndPosition(); expression = nodeFactory.createNode<NewExpression>(typeName); } else @@ -1261,15 +1266,15 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression( nodeFactory.markEndPosition(); expectToken(Token::RBrack); expression = nodeFactory.createNode<IndexAccess>(expression, index); + break; } - break; case Token::Period: { m_scanner->next(); nodeFactory.markEndPosition(); expression = nodeFactory.createNode<MemberAccess>(expression, expectIdentifierToken()); + break; } - break; case Token::LParen: { m_scanner->next(); @@ -1279,8 +1284,8 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression( nodeFactory.markEndPosition(); expectToken(Token::RParen); expression = nodeFactory.createNode<FunctionCall>(expression, arguments, names); + break; } - break; default: return expression; } @@ -1309,18 +1314,21 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(m_scanner->currentToken()); m_scanner->next(); expression = nodeFactory.createNode<Literal>(token, literal, subdenomination); - break; } - if (Token::isTimeSubdenomination(m_scanner->peekNextToken())) + else if (Token::isTimeSubdenomination(m_scanner->peekNextToken())) { ASTPointer<ASTString> literal = getLiteralAndAdvance(); nodeFactory.markEndPosition(); Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(m_scanner->currentToken()); m_scanner->next(); expression = nodeFactory.createNode<Literal>(token, literal, subdenomination); - break; } - // fall-through + else + { + nodeFactory.markEndPosition(); + expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance()); + } + break; case Token::StringLiteral: nodeFactory.markEndPosition(); expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance()); @@ -1357,9 +1365,9 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() } nodeFactory.markEndPosition(); expectToken(oppositeToken); - return nodeFactory.createNode<TupleExpression>(components, isArray); + expression = nodeFactory.createNode<TupleExpression>(components, isArray); + break; } - default: if (Token::isElementaryTypeName(token)) { diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp index fe95b0fe..5b83c5bd 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 fdca23ea..6541f6c2 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 adf181c7..06a0fc81 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] <file>" << 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 6046978e..cddcd4f8 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 512a28df..d02005cc 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 00000000..d1284b9e --- /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 a2f4d98a..ebc7759f 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 f1d44a1f..b01b33bb 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 f92b3c44..bf460e8e 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 252c74b0..45c16a9f 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 315f951e..271511d4 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<vector<string>>()->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 bf9400e4..4768c9d8 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -81,6 +81,11 @@ private: /// @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 684d49e4..7e797a62 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 <string> +#include <solc/jsonCompiler.h> #include <libdevcore/Common.h> #include <libdevcore/JSON.h> #include <libsolidity/interface/StandardCompiler.h> #include <libsolidity/interface/Version.h> +#include <string> + #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 00000000..c392ce93 --- /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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * JSON interface for the solidity compiler to be used from Javascript. + */ + +#include <stdbool.h> + +#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 51f925e0..2986cb56 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 59566f26..4b4eb71e 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 f0a6f4ce..c43f1e4f 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 bbb8d957..ee9860d3 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 f4e5fcef..b2de814a 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 76d0fd8c..e8d8d111 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -22,13 +22,13 @@ #pragma once -#include <functional> - -#include "TestHelper.h" -#include "RPCSession.h" +#include <test/TestHelper.h> +#include <test/RPCSession.h> -#include <libdevcore/ABI.h> #include <libdevcore/FixedHash.h> +#include <libdevcore/SHA3.h> + +#include <functional> 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 @@ public: 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<u160>(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<u256>(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<u256>(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<h256>(ret); + return h256(ret); } private: @@ -262,7 +262,7 @@ protected: 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 558cb99f..eae6a09c 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 @@ private: 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 @@ public: 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 eb5c714d..f12a6686 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 773b14b9..d56edc56 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -23,7 +23,6 @@ #include <string> #include <tuple> #include <boost/test/unit_test.hpp> -#include <libdevcore/ABI.h> #include <test/libsolidity/SolidityExecutionFramework.h> using namespace std; diff --git a/test/externalTests.sh b/test/externalTests.sh index 1b74561b..6ff2ebc5 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 cf99755f..53ba7201 100644 --- a/test/fuzzer.cpp +++ b/test/fuzzer.cpp @@ -20,6 +20,7 @@ #include <libevmasm/Assembly.h> #include <libevmasm/ConstantOptimiser.h> +#include <solc/jsonCompiler.h> #include <json/json.h> @@ -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 index 84149173..84149173 100644 --- a/test/libdevcore/MiniMoustache.cpp +++ b/test/libdevcore/Whiskers.cpp diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 6656f15b..9dc49581 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -22,6 +22,7 @@ #include <libevmasm/CommonSubexpressionEliminator.h> #include <libevmasm/PeepholeOptimiser.h> +#include <libevmasm/JumpdestRemover.h> #include <libevmasm/ControlFlowGraph.h> #include <libevmasm/BlockDeduplicator.h> #include <libevmasm/Assembly.h> @@ -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<Assembly>(); + + 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 51070370..f8c1aa4d 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 297c4ef0..05158601 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 00000000..5f5f6411 --- /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 <http://www.gnu.org/licenses/>. +*/ +/** + * Framework for testing features from the analysis phase of compiler. + */ + +#include <test/libsolidity/AnalysisFramework.h> + +#include <libsolidity/interface/CompilerStack.h> +#include <libsolidity/interface/SourceReferenceFormatter.h> + +#include <libsolidity/ast/AST.h> + +#include <libdevcore/SHA3.h> + +#include <boost/test/unit_test.hpp> + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::test; + +pair<SourceUnit const*, shared_ptr<Error const>> +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<Error const> 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<ASTNode> const& node: _source.nodes()) + if ((contract = dynamic_cast<ContractDefinition*>(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 00000000..172ae01b --- /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 <http://www.gnu.org/licenses/>. +*/ +/** + * Framework for testing features from the analysis phase of compiler. + */ + +#pragma once + +#include <test/libsolidity/ErrorCheck.h> + +#include <libsolidity/interface/CompilerStack.h> + +#include <functional> +#include <string> +#include <memory> + +namespace dev +{ +namespace solidity +{ + +class Type; +class FunctionType; +using TypePointer = std::shared_ptr<Type const>; +using FunctionTypePointer = std::shared_ptr<FunctionType const>; + +namespace test +{ + +class AnalysisFramework +{ + +protected: + std::pair<SourceUnit const*, std::shared_ptr<Error const>> + 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 99a2996e..56ac8cf5 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -119,11 +119,11 @@ BOOST_AUTO_TEST_CASE(location_test) shared_ptr<string const> n = make_shared<string>(""); AssemblyItems items = compileContract(sourceCode); vector<SourceLocation> locations = - vector<SourceLocation>(19, SourceLocation(2, 75, n)) + + vector<SourceLocation>(18, SourceLocation(2, 75, n)) + vector<SourceLocation>(32, SourceLocation(20, 72, n)) + vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + vector<SourceLocation>(2, SourceLocation(58, 67, n)) + - vector<SourceLocation>(3, SourceLocation(20, 72, n)); + vector<SourceLocation>(2, SourceLocation(20, 72, n)); checkAssemblyLocations(items, locations); } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 0debc66d..da3522b4 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 0fe7636c..7dc4808b 100644 --- a/test/libsolidity/JSONCompiler.cpp +++ b/test/libsolidity/JSONCompiler.cpp @@ -23,22 +23,13 @@ #include <boost/test/unit_test.hpp> #include <libdevcore/JSON.h> #include <libsolidity/interface/Version.h> +#include <solc/jsonCompiler.h> #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 0512ba1f..e5d9e99c 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -48,7 +48,7 @@ public: 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 73dd7d22..458b64f4 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 380978e8..39c47f9c 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 <test/libsolidity/ErrorCheck.h> +#include <test/libsolidity/AnalysisFramework.h> -#include <test/TestHelper.h> - -#include <libsolidity/parsing/Scanner.h> -#include <libsolidity/parsing/Parser.h> -#include <libsolidity/analysis/NameAndTypeResolver.h> -#include <libsolidity/analysis/StaticAnalyzer.h> -#include <libsolidity/analysis/PostTypeChecker.h> -#include <libsolidity/analysis/SyntaxChecker.h> -#include <libsolidity/interface/ErrorReporter.h> -#include <libsolidity/analysis/GlobalContext.h> -#include <libsolidity/analysis/TypeChecker.h> +#include <libsolidity/ast/AST.h> #include <libdevcore/SHA3.h> +#include <boost/test/unit_test.hpp> + #include <string> using namespace std; @@ -47,195 +39,14 @@ namespace solidity namespace test { -namespace -{ - -pair<ASTPointer<SourceUnit>, std::shared_ptr<Error const>> -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> sourceUnit; - // catch exceptions for a transition period - try - { - sourceUnit = parser.parse(std::make_shared<Scanner>(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> globalContext = make_shared<GlobalContext>(); - map<ASTNode const*, shared_ptr<DeclarationContainer>> scopes; - NameAndTypeResolver resolver(globalContext->declarations(), scopes, errorReporter); - solAssert(Error::containsOnlyWarnings(errorReporter.errors()), ""); - resolver.registerDeclarations(*sourceUnit); - - bool success = true; - for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) - if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) - { - globalContext->setCurrentContract(*contract); - resolver.updateDeclaration(*globalContext->currentThis()); - resolver.updateDeclaration(*globalContext->currentSuper()); - if (!resolver.resolveNamesAndTypes(*contract)) - success = false; - } - if (success) - for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) - if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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 const> 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<errinfo_comment>(*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<errinfo_comment>(_e)) - message += ": " + *description; - BOOST_FAIL(message); - } - catch (Error const& _e) - { - return make_pair(sourceUnit, std::make_shared<Error const>(_e)); - } - catch (...) - { - BOOST_FAIL("Unexpected exception."); - } - return make_pair(sourceUnit, nullptr); -} - -ASTPointer<SourceUnit> 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<SourceUnit> _source, unsigned index) -{ - ContractDefinition* contract; - unsigned counter = 0; - for (ASTPointer<ASTNode> const& node: _source->nodes()) - if ((contract = dynamic_cast<ContractDefinition*>(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; + 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<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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; + 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<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get()); ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get()); @@ -649,12 +460,12 @@ BOOST_AUTO_TEST_CASE(abstract_contract) BOOST_AUTO_TEST_CASE(abstract_contract_with_overload) { - ASTPointer<SourceUnit> 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<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); ContractDefinition* base = dynamic_cast<ContractDefinition*>(nodes[1].get()); ContractDefinition* derived = dynamic_cast<ContractDefinition*>(nodes[2].get()); @@ -666,12 +477,11 @@ BOOST_AUTO_TEST_CASE(abstract_contract_with_overload) BOOST_AUTO_TEST_CASE(create_abstract_contract) { - ASTPointer<SourceUnit> 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> 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; + 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<ASTPointer<ASTNode>> nodes = sourceUnit->nodes(); BOOST_CHECK_EQUAL(nodes.size(), 3); ContractDefinition* derived = dynamic_cast<ContractDefinition*>(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; + 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<ASTNode> const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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; + 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<ASTNode> const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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; + 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<ASTNode> const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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; + 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<ASTNode> const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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<ASTNode> const& node: sourceUnit->nodes()) + if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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<ASTNode> const& node: sourceUnit->nodes()) + if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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<ASTNode> const& node: sourceUnit->nodes()) + if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(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<SourceUnit> 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<SourceUnit> 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 a39e0958..60ca03c9 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 79848c36..24f915c0 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 00000000..80241519 --- /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 <http://www.gnu.org/licenses/>. +*/ +/** + * Unit tests for the view and pure checker. + */ + +#include <test/libsolidity/AnalysisFramework.h> + +#include <boost/test/unit_test.hpp> + +#include <string> + +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<string> 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<string> 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() + +} +} +} |