diff --git a/.circleci/config.yml b/.circleci/config.yml index 2139fa94d580..c2e64a76413b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ version: 2.1 parameters: docker-image-rev: type: string - default: "3" + default: "4" defaults: @@ -593,19 +593,24 @@ jobs: t_ems_solcjs: docker: - - image: circleci/node:10 + - image: ethereum/solidity-buildpack-deps:ubuntu1904 environment: TERM: xterm steps: - checkout - attach_workspace: at: /tmp/workspace + - run: + name: Install test dependencies + command: | + apt-get update + apt-get install -qqy --no-install-recommends nodejs npm cvc4 - run: name: Test solcjs command: | node --version npm --version - test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) + test/externalTests/solc-js/solc-js.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) t_ems_compile_ext_gnosis: docker: @@ -716,7 +721,7 @@ workflows: # basic checks - chk_spelling: *workflow_trigger_on_tags - chk_coding_style: *workflow_trigger_on_tags - - chk_docs_examples: *workflow_trigger_on_tags + # DISABLED FOR 0.6.0 - chk_docs_examples: *workflow_trigger_on_tags - chk_buglist: *workflow_trigger_on_tags - chk_proofs: *workflow_trigger_on_tags diff --git a/.circleci/docker/Dockerfile.clang.ubuntu1904 b/.circleci/docker/Dockerfile.clang.ubuntu1904 index 0306dcf6844c..14c9b1339ec0 100644 --- a/.circleci/docker/Dockerfile.clang.ubuntu1904 +++ b/.circleci/docker/Dockerfile.clang.ubuntu1904 @@ -92,7 +92,7 @@ RUN set -ex; \ # EVMONE RUN set -ex; \ cd /usr/src; \ - git clone --branch="v0.3.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + git clone --branch="v0.4.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ cd evmone; \ mkdir build; \ cd build; \ diff --git a/.circleci/docker/Dockerfile.ubuntu1804 b/.circleci/docker/Dockerfile.ubuntu1804 index fbf664a49ba5..fa651900b873 100644 --- a/.circleci/docker/Dockerfile.ubuntu1804 +++ b/.circleci/docker/Dockerfile.ubuntu1804 @@ -75,9 +75,9 @@ RUN set -ex; \ rm -rf /var/lib/libfuzzer # EVMONE -ARG EVMONE_HASH="fa4f40daf7cf9ccbcca6c78345977e084ea2136a8eae661e4d19471be852b15b" +ARG EVMONE_HASH="e9f8df89c52d9c60c9a38dd00687b1ec9e9ae9650b400a87c4c0cf7468e35307" ARG EVMONE_MAJOR="0" -ARG EVMONE_MINOR="3" +ARG EVMONE_MINOR="4" ARG EVMONE_MICRO="0" RUN set -ex; \ EVMONE_VERSION="$EVMONE_MAJOR.$EVMONE_MINOR.$EVMONE_MICRO"; \ diff --git a/.circleci/docker/Dockerfile.ubuntu1904 b/.circleci/docker/Dockerfile.ubuntu1904 index 2c211d956b81..903b6945beb5 100644 --- a/.circleci/docker/Dockerfile.ubuntu1904 +++ b/.circleci/docker/Dockerfile.ubuntu1904 @@ -77,7 +77,7 @@ RUN set -ex; \ # EVMONE RUN set -ex; \ cd /usr/src; \ - git clone --branch="v0.3.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + git clone --branch="v0.4.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ cd evmone; \ mkdir build; \ cd build; \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 10b830a6ca5b..30073fd08ef6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.5.15") +set(PROJECT_VERSION "0.6.0") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX) include(TestBigEndian) diff --git a/Changelog.md b/Changelog.md index f09fb0355c19..e8731b5fa5bf 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,9 +1,55 @@ +### 0.6.0 (2019-12-17) + +Breaking changes: + * ABI: Remove the deprecated ``constant`` and ``payable`` fields. + * ABI: The ``type`` field is now required and no longer specified to default to ``function``. + * AST: Inline assembly is exported as structured JSON instead of plain string. + * C API (``libsolc``): Introduce context parameter to both ``solidity_compile`` and the callback. + * C API (``libsolc``): The provided callback now takes two parameters, kind and data. The callback can then be used for multiple purposes, such has file imports and SMT queries. + * C API (``libsolc``): ``solidity_free`` was renamed to ``solidity_reset``. Functions ``solidity_alloc`` and ``solidity_free`` were added. + * C API (``libsolc``): ``solidity_compile`` now returns a string that must be explicitly freed via ``solidity_free()`` + * Commandline Interface: Remove the text-based AST printer (``--ast``). + * Commandline Interface: Switch to the new error reporter by default. ``--old-reporter`` falls back to the deprecated old error reporter. + * Commandline Interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata. + * General: Disallow explicit conversions from external function types to ``address`` and add a member called ``address`` to them as replacement. + * General: Enable Yul optimizer as part of standard optimization. + * General: New reserved keywords: ``override``, ``receive``, and ``virtual``. + * General: ``private`` cannot be used together with ``virtual``. + * General: Split unnamed fallback functions into two cases defined using ``fallback()`` and ``receive()``. + * Inheritance: State variable shadowing is now disallowed. + * Inline Assembly: Only strict inline assembly is allowed. + * Inline Assembly: Variable declarations cannot shadow declarations outside the assembly block. + * JSON AST: Replace ``superFunction`` attribute by ``baseFunctions``. + * Natspec JSON Interface: Properly support multiple ``@return`` statements in ``@dev`` documentation and enforce named return parameters to be mentioned documentation. + * Source mappings: Add "modifier depth" as a fifth field in the source mappings. + * Standard JSON Interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata. + * Syntax: ``push(element)`` for dynamic storage arrays do not return the new length anymore. + * Syntax: Abstract contracts need to be marked explicitly as abstract by using the ``abstract`` keyword. + * Syntax: ``length`` member of arrays is now always read-only, even for storage arrays. + * Type Checker: Resulting type of exponentiation is equal to the type of the base. Also allow signed types for the base. + +Language Features: + * Allow explicit conversions from ``address`` to ``address payable`` via ``payable(...)``. + * Allow global enums and structs. + * Allow public variables to override external functions. + * Allow underscores as delimiters in hex strings. + * Introduce syntax for array slices and implement them for dynamic calldata arrays. + * Introduce ``push()`` for dynamic storage arrays. It returns a reference to the newly allocated element, if applicable. + * Introduce ``virtual`` and ``override`` keywords. + * Modify ``push(element)`` for dynamic storage arrays such that it does not return the new length anymore. + * Yul: Introduce ``leave`` statement that exits the current function. + * JSON AST: Add the function selector of each externally-visible FunctonDefinition to the AST JSON export. + +Compiler Features: + * Allow revert strings to be stripped from the binary using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting. + * ABIEncoderV2: Do not warn about enabled ABIEncoderV2 anymore (the pragma is still needed, though). + + ### 0.5.15 (2019-12-17) Bugfixes: * Yul Optimizer: Fix incorrect redundant load optimization crossing user-defined functions that contain for-loops with memory / storage writes. - ### 0.5.14 (2019-12-09) Language Features: @@ -13,7 +59,7 @@ Language Features: Compiler Features: - * Commandline Interface: Allow translation from yul / strict assembly to EWasm using ``solc --yul --yul-dialect evm --machine eWasm`` + * Commandline Interface: Allow translation from yul / strict assembly to EWasm using ``solc --yul --yul-dialect evm --machine ewasm`` * Set the default EVM version to "Istanbul". * SMTChecker: Add support to constructors including constructor inheritance. * Yul: When compiling via Yul, string literals from the Solidity code are kept as string literals if every character is safely printable. @@ -48,7 +94,6 @@ Compiler Features: * TypeChecker: List possible candidates when overload resolution fails. * TypeChecker: Disallow variables of library types. - Bugfixes: * Code Generator: Fixed a faulty assert that would wrongly trigger for array sizes exceeding unsigned integer. * SMTChecker: Fix internal error when accessing indices of fixed bytes. @@ -59,7 +104,6 @@ Bugfixes: * Code Generator: Fix internal error when trying to convert ``super`` to a different type - ### 0.5.12 (2019-10-01) Language Features: diff --git a/README.md b/README.md index e7e5bdb7c451..4729c95c067b 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,15 @@ that run on the Ethereum Virtual Machine. Smart contracts are programs that are network where nobody has special authority over the execution, and thus they allow to implement tokens of value, ownership, voting and other kinds of logics. -When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number [to indicate this fast pace of change](https://semver.org/#spec-item-4). +When deploying contracts, you should use the latest released version of +Solidity. This is because breaking changes as well as new features and bug fixes are +introduced regularly. We currently use a 0.x version +number [to indicate this fast pace of change](https://semver.org/#spec-item-4). ## Build and Install -Instructions about how to build and install the Solidity compiler can be found in the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source). +Instructions about how to build and install the Solidity compiler can be +found in the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source). ## Example @@ -32,7 +36,7 @@ Instructions about how to build and install the Solidity compiler can be found i A "Hello World" program in Solidity is of even less use than in other languages, but still: ```solidity -pragma solidity ^0.5.0; +pragma solidity ^0.6.0; contract HelloWorld { function helloWorld() external pure returns (string memory) { @@ -60,7 +64,8 @@ Please follow the [Developers Guide](https://solidity.readthedocs.io/en/latest/contributing.html) if you want to help. -You can find our current feature and bug priorities for forthcoming releases [in the projects section](https://github.com/ethereum/solidity/projects). +You can find our current feature and bug priorities for forthcoming +releases [in the projects section](https://github.com/ethereum/solidity/projects). ## Maintainers * [@axic](https://github.com/axic) diff --git a/cmake/EthCheckCXXCompilerFlag.cmake b/cmake/EthCheckCXXCompilerFlag.cmake index c6ed35b4e230..3e62885fe8ef 100644 --- a/cmake/EthCheckCXXCompilerFlag.cmake +++ b/cmake/EthCheckCXXCompilerFlag.cmake @@ -10,7 +10,9 @@ include(CheckCXXCompilerFlag) # function(eth_add_cxx_compiler_flag_if_supported FLAG) # Remove leading - or / from the flag name. - string(REGEX REPLACE "^-|/" "" name ${FLAG}) + string(REGEX REPLACE "^[-/]" "" name ${FLAG}) + # Deletes any ':' because it's invalid variable names. + string(REGEX REPLACE ":" "" name ${name}) check_cxx_compiler_flag(${FLAG} ${name}) if(${name}) add_compile_options(${FLAG}) diff --git a/docs/050-breaking-changes.rst b/docs/050-breaking-changes.rst index fd201d77c234..497936f31078 100644 --- a/docs/050-breaking-changes.rst +++ b/docs/050-breaking-changes.rst @@ -345,7 +345,8 @@ commandline compiler for linking): :: - pragma solidity >=0.5.0 <0.7.0; + // This will not compile after 0.6.0 + pragma solidity >=0.5.0 <0.5.99; library OldLibrary { function someFunction(uint8 a) public returns(bool); @@ -437,7 +438,7 @@ New version: function f(uint y) external { x = y; } - function() payable external {} + receive() payable external {} } contract New { diff --git a/docs/060-breaking-changes.rst b/docs/060-breaking-changes.rst new file mode 100644 index 000000000000..99e969e07ac4 --- /dev/null +++ b/docs/060-breaking-changes.rst @@ -0,0 +1,175 @@ +******************************** +Solidity v0.6.0 Breaking Changes +******************************** + +This section highlights the main breaking changes introduced in Solidity +version 0.6.0, along with the reasoning behind the changes and how to update +affected code. +For the full list check +`the release changelog `_. + + +Changes the Compiler Might not Warn About +========================================= + +This section lists changes where the behaviour of your code might +change without the compiler telling you about it. + +* The resulting type of an exponentiation is the type of the base. It used to be the smallest type + that can hold both the type of the base and the type of the exponent, as with symmentric + operations. Additionally, signed types are allowed for the base of the exponetation. + + +Explicitness Requirements +========================= + +This section lists changes where the code now needs to be more explicit, +but the semantics do not change. +For most of the topics the compiler will provide suggestions. + +* Functions can now only be overridden when they are either marked with the + ``virtual`` keyword or defined in an interface. Functions without + implementation outside an interface have to be marked ``virtual``. + When overriding a function or modifier, the new keyword ``override`` + must be used. When overriding a function or modifier defined in multiple + parallel bases, all bases must be listed in parentheses after the keyword + like so: ``override(Base1, Base2)``. + +* Member-access to ``length`` of arrays is now always read-only, even for storage arrays. It is no + longer possible to resize storage arrays assigning a new value to their length. Use ``push()``, + ``push(value)`` or ``pop()`` instead, or assign a full array, which will of course overwrite existing content. + The reason behind this is to prevent storage collisions by gigantic + storage arrays. + +* The new keyword ``abstract`` can be used to mark contracts as abstract. It has to be used + if a contract does not implement all its functions. + +* Libraries have to implement all their functions, not only the internal ones. + +* The names of variables declared in inline assembly may no longer end in ``_slot`` or ``_offset``. + +* Variable declarations in inline assembly may no longer shadow any declaration outside the inline assembly block. + If the name contains a dot, its prefix up to the dot may not conflict with any declaration outside the inline + assembly block. + +* State variable shadowing is now disallowed. A derived contract can only + declare a state variable ``x``, if there is no visible state variable with + the same name in any of its bases. + + +Semantic and Syntactic Changes +============================== + +This section lists changes where you have to modify your code +and it does something else afterwards. + +* Conversions from external function types to ``address`` are now disallowed. Instead external + function types have a member called ``address``, similar to the existing ``selector`` member. + +* The function ``push(value)`` for dynamic storage arrays does not return the new length anymore (it returns nothing). + +* The unnamed function commonly referred to as "fallback function" was split up into a new + fallback function that is defined using the ``fallback`` keyword and a receive ether function + defined using the ``receive`` keyword. + + * If present, the receive ether function is called whenever the call data is empty (whether + or not ether is received). This function is implicitly ``payable``. + + * The new fallback function is called when no other function matches (if the receive ether + function does not exist then this includes calls with empty call data). + You can make this function ``payable`` or not. If it is not ``payable`` then transactions + not matching any other function which send value will revert. You should only need to + implement the new fallback function if you are following an upgrade or proxy pattern. + + +New Features +============ + +This section lists things that were not possible prior to Solidity 0.6.0 +or at least were more difficult to achieve prior to Solidity 0.6.0. + + * The :ref:`try/catch statement ` allows you to react on failed external calls. + * ``struct`` and ``enum`` types can be declared at file level. + * Array slices can be used for calldata arrays, for example ``abi.decode(msg.data[4:], (uint, uint))`` + is a low-level way to decode the function call payload. + * Natspec supports multiple return parameters in developer documentation, enforcing the same naming check as ``@param``. + * Yul and Inline Assembly have a new statement called ``leave`` that exits the current function. + * Conversions from ``address`` to ``address payable`` are now possible via ``payable(x)``, where + ``x`` must be of type ``address``. + + +Interface Changes +================= + +This section lists changes that are unrelated to the language itself, but that have an effect on the interfaces of +the compiler. These may change the way how you use the compiler on the command line, how you use its programmable +interface or how you analyze the output produced by it. + +New Error Reporter +~~~~~~~~~~~~~~~~~~ + +A new error reporter was introduced, which aims at producing more accessible error messages on the command line. +It is enabled by default, but passing ``--old-reporter`` falls back to the the deprecated old error reporter. + +Metadata Hash Options +~~~~~~~~~~~~~~~~~~~~~ + +The compiler now appends the `IPFS `_ hash of the metadata file to the end of the bytecode by default +(for details, see documentation on :doc:`contract metadata `). Before 0.6.0, the compiler appended the +`Swarm `_ hash by default, and in order to still support this behaviour, +the new command line option ``--metadata-hash`` was introduced. It allows you to select the hash to be produced and +appended, by passing either ``ipfs`` or ``swarm`` as value to the ``--metadata-hash`` command line option. +Passing the value ``none`` completely removes the hash. + +These changes can also be used via the :ref:`Standard JSON Interface` and effect the metadata JSON generated by the compiler. + +The recommended way to read the metadata is to read the last two bytes to determine the length of the CBOR encoding +and perform a proper decoding on that data block as explained in the :ref:`metadata section`. + +Yul Optimizer +~~~~~~~~~~~~~ + +Together with the legacy bytecode optimizer, the :doc:`Yul ` optimizer is now enabled by default when you call the compiler +with ``--optimize``. It can be disabled by calling the compiler with ``--no-optimize-yul``. +This mostly affects code that uses ABIEncoderV2. + +C API Changes +~~~~~~~~~~~~~ + +The client code that uses the C API of ``libsolc`` is now in control of the memory used by the compiler. To make +this change consistent, ``solidity_free`` was renamed to ``solidity_reset``, the functions ``solidity_alloc`` and +``solidity_free`` were added and ``solidity_compile`` now returns a string that must be explicitly freed via +``solidity_free()``. + + +How to update your code +======================= + +This section gives detailed instructions on how to update prior code for every breaking change. + +* Change ``address(f)`` to ``f.address`` for ``f`` being of external function type. + +* Replace ``function () external [payable] { ... }`` by either ``receive() external payable { ... }``, + ``fallback() external [payable] { ... }`` or both. Prefer + using a ``receive`` function only, whenever possible. + +* Change ``uint length = array.push(value)`` to ``array.push(value);``. The new length can be + accessed via ``array.length``. + +* Change ``array.length++`` to ``array.push()`` to increase, and use ``pop()`` to decrease + the length of a storage array. + +* For every named return parameter in a function's ``@dev`` documentation define a ``@return`` + entry which contains the parameter's name as the first word. E.g. if you have function ``f()`` defined + like ``function f() public returns (uint value)`` and a ``@dev`` annotating it, document its return + parameters like so: ``@return value The return value.``. You can mix named and un-named return parameters + documentation so long as the notices are in the order they appear in the tuple return type. + +* Choose unique identifiers for variable declarations in inline assembly that do not conflict + with declartions outside the inline assembly block. + +* Add ``virtual`` to every non-interface function you intend to override. Add ``virtual`` + to all functions without implementation outside interfaces. For single inheritance, add + ``override`` to every overriding function. For multiple inheritance, add ``override(A, B, ..)``, + where you list all contracts that define the overridden function in the parentheses. When + multiple bases define the same function, the inheriting contract must override all conflicting functions. diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 3d3e846c8353..a3bd78319a5d 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -11,9 +11,10 @@ Basic Design The Contract Application Binary Interface (ABI) is the standard way to interact with contracts in the Ethereum ecosystem, both from outside the blockchain and for contract-to-contract interaction. Data is encoded according to its type, -as described in this specification. The encoding is not self describing and thus requires a schema in order to decode. +as described in this specification. The encoding is not self describing and thus requires a schema in order to decode. -We assume the interface functions of a contract are strongly typed, known at compilation time and static. We assume that all contracts will have the interface definitions of any contracts they call available at compile-time. +We assume the interface functions of a contract are strongly typed, known at compilation time and static. +We assume that all contracts will have the interface definitions of any contracts they call available at compile-time. This specification does not address contracts whose interface is dynamic or otherwise known only at run-time. @@ -23,17 +24,24 @@ Function Selector ================= The first four bytes of the call data for a function call specifies the function to be called. It is the -first (left, high-order in big-endian) four bytes of the Keccak-256 (SHA-3) hash of the signature of the function. The signature is defined as the canonical expression of the basic prototype without data location specifier, i.e. -the function name with the parenthesised list of parameter types. Parameter types are split by a single comma - no spaces are used. +first (left, high-order in big-endian) four bytes of the Keccak-256 (SHA-3) hash of the signature of +the function. The signature is defined as the canonical expression of the basic prototype without data +location specifier, i.e. +the function name with the parenthesised list of parameter types. Parameter types are split by a single +comma - no spaces are used. .. note:: - The return type of a function is not part of this signature. In :ref:`Solidity's function overloading ` return types are not considered. The reason is to keep function call resolution context-independent. + The return type of a function is not part of this signature. In + :ref:`Solidity's function overloading ` return types are not considered. + The reason is to keep function call resolution context-independent. The :ref:`JSON description of the ABI` however contains both inputs and outputs. Argument Encoding ================= -Starting from the fifth byte, the encoded arguments follow. This encoding is also used in other places, e.g. the return values and also event arguments are encoded in the same way, without the four bytes specifying the function. +Starting from the fifth byte, the encoded arguments follow. This encoding is also used in +other places, e.g. the return values and also event arguments are encoded in the same way, +without the four bytes specifying the function. Types ===== @@ -44,17 +52,21 @@ The following elementary types exist: - ``int``: two's complement signed integer type of ``M`` bits, ``0 < M <= 256``, ``M % 8 == 0``. -- ``address``: equivalent to ``uint160``, except for the assumed interpretation and language typing. For computing the function selector, ``address`` is used. +- ``address``: equivalent to ``uint160``, except for the assumed interpretation and language typing. + For computing the function selector, ``address`` is used. -- ``uint``, ``int``: synonyms for ``uint256``, ``int256`` respectively. For computing the function selector, ``uint256`` and ``int256`` have to be used. +- ``uint``, ``int``: synonyms for ``uint256``, ``int256`` respectively. For computing the function + selector, ``uint256`` and ``int256`` have to be used. - ``bool``: equivalent to ``uint8`` restricted to the values 0 and 1. For computing the function selector, ``bool`` is used. -- ``fixedx``: signed fixed-point decimal number of ``M`` bits, ``8 <= M <= 256``, ``M % 8 ==0``, and ``0 < N <= 80``, which denotes the value ``v`` as ``v / (10 ** N)``. +- ``fixedx``: signed fixed-point decimal number of ``M`` bits, ``8 <= M <= 256``, + ``M % 8 ==0``, and ``0 < N <= 80``, which denotes the value ``v`` as ``v / (10 ** N)``. - ``ufixedx``: unsigned variant of ``fixedx``. -- ``fixed``, ``ufixed``: synonyms for ``fixed128x18``, ``ufixed128x18`` respectively. For computing the function selector, ``fixed128x18`` and ``ufixed128x18`` have to be used. +- ``fixed``, ``ufixed``: synonyms for ``fixed128x18``, ``ufixed128x18`` respectively. For + computing the function selector, ``fixed128x18`` and ``ufixed128x18`` have to be used. - ``bytes``: binary type of ``M`` bytes, ``0 < M <= 32``. @@ -76,14 +88,14 @@ Types can be combined to a tuple by enclosing them inside parentheses, separated - ``(T1,T2,...,Tn)``: tuple consisting of the types ``T1``, ..., ``Tn``, ``n >= 0`` -It is possible to form tuples of tuples, arrays of tuples and so on. It is also possible to form zero-tuples (where ``n == 0``). +It is possible to form tuples of tuples, arrays of tuples and so on. It is also possible to form zero-tuples (where ``n == 0``). Mapping Solidity to ABI types ----------------------------- Solidity supports all the types presented above with the same names with the exception of tuples. On the other hand, some Solidity types are not supported -by the ABI. The following table shows on the left column Solidity types that +by the ABI. The following table shows on the left column Solidity types that are not part of the ABI, and on the right column the ABI types that represent them. @@ -107,15 +119,20 @@ Design Criteria for the Encoding The encoding is designed to have the following properties, which are especially useful if some arguments are nested arrays: - 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". + 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". Formal Specification of the Encoding ==================================== -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. +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": @@ -178,9 +195,12 @@ on the type of ``X`` being - ``string``: - ``enc(X) = enc(enc_utf8(X))``, i.e. ``X`` is utf-8 encoded and this value is interpreted as of ``bytes`` type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters. + ``enc(X) = enc(enc_utf8(X))``, i.e. ``X`` is utf-8 encoded and this value is interpreted + as of ``bytes`` type and encoded further. Note that the length used in this subsequent + encoding is the number of bytes of the utf-8 encoded string, not its number of characters. -- ``uint``: ``enc(X)`` is the big-endian encoding of ``X``, padded on the higher-order (left) side with zero-bytes such that the length is 32 bytes. +- ``uint``: ``enc(X)`` is the big-endian encoding of ``X``, padded on the higher-order + (left) side with zero-bytes such that the length is 32 bytes. - ``address``: as in the ``uint160`` case - ``int``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-order (left) side with ``0xff`` for negative ``X`` and with zero bytes for positive ``X`` such that the length is 32 bytes. - ``bool``: as in the ``uint8`` case, where ``1`` is used for ``true`` and ``0`` for ``false`` @@ -222,11 +242,15 @@ 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: @@ -234,13 +258,16 @@ 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: - ``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). +- ``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: @@ -248,7 +275,8 @@ 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: +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``. @@ -270,10 +298,14 @@ 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: +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) @@ -306,11 +338,12 @@ All together, the encoding is (newline after function selector and each 32-bytes 000000000000000000000000000000000000000000000000000000000000000d 48656c6c6f2c20776f726c642100000000000000000000000000000000000000 -Let us apply the same principle to encode the data for a function with a signature ``g(uint[][],string[])`` with values ``([[1, 2], [3]], ["one", "two", "three"])`` but start from the most atomic parts of the encoding: +Let us apply the same principle to encode the data for a function with a signature ``g(uint[][],string[])`` +with values ``([[1, 2], [3]], ["one", "two", "three"])`` but start from the most atomic parts of the encoding: First we encode the length and data of the first embedded dynamic array ``[1, 2]`` of the first root array ``[[1, 2], [3]]``: - - ``0x0000000000000000000000000000000000000000000000000000000000000002`` (number of elements in the first array, 2; the elements themselves are ``1`` and ``2``) + - ``0x0000000000000000000000000000000000000000000000000000000000000002`` (number of elements in the first array, 2; the elements themselves are ``1`` and ``2``) - ``0x0000000000000000000000000000000000000000000000000000000000000001`` (first element) - ``0x0000000000000000000000000000000000000000000000000000000000000002`` (second element) @@ -319,7 +352,9 @@ Then we encode the length and data of the second embedded dynamic array ``[3]`` - ``0x0000000000000000000000000000000000000000000000000000000000000001`` (number of elements in the second array, 1; the element is ``3``) - ``0x0000000000000000000000000000000000000000000000000000000000000003`` (first element) -Then we need to find the offsets ``a`` and ``b`` for their respective dynamic arrays ``[1, 2]`` and ``[3]``. To calculate the offsets we can take a look at the encoded data of the first root array ``[[1, 2], [3]]`` enumerating each line in the encoding: +Then we need to find the offsets ``a`` and ``b`` for their respective dynamic arrays ``[1, 2]`` and ``[3]``. +To calculate the offsets we can take a look at the encoded data of the first root array ``[[1, 2], [3]]`` +enumerating each line in the encoding: .. code-block:: none @@ -331,9 +366,11 @@ Then we need to find the offsets ``a`` and ``b`` for their respective dynamic ar 5 - 0000000000000000000000000000000000000000000000000000000000000001 - count for [3] 6 - 0000000000000000000000000000000000000000000000000000000000000003 - encoding of 3 -Offset ``a`` points to the start of the content of the array ``[1, 2]`` which is line 2 (64 bytes); thus ``a = 0x0000000000000000000000000000000000000000000000000000000000000040``. +Offset ``a`` points to the start of the content of the array ``[1, 2]`` which is line +2 (64 bytes); thus ``a = 0x0000000000000000000000000000000000000000000000000000000000000040``. -Offset ``b`` points to the start of the content of the array ``[3]`` which is line 5 (160 bytes); thus ``b = 0x00000000000000000000000000000000000000000000000000000000000000a0``. +Offset ``b`` points to the start of the content of the array ``[3]`` which is line 5 (160 bytes); +thus ``b = 0x00000000000000000000000000000000000000000000000000000000000000a0``. Then we encode the embedded strings of the second root array: @@ -359,14 +396,18 @@ In parallel to the first root array, since strings are dynamic elements we need 7 - 0000000000000000000000000000000000000000000000000000000000000005 - count for "three" 8 - 7468726565000000000000000000000000000000000000000000000000000000 - encoding of "three" -Offset ``c`` points to the start of the content of the string ``"one"`` which is line 3 (96 bytes); thus ``c = 0x0000000000000000000000000000000000000000000000000000000000000060``. +Offset ``c`` points to the start of the content of the string ``"one"`` which is line 3 (96 bytes); +thus ``c = 0x0000000000000000000000000000000000000000000000000000000000000060``. -Offset ``d`` points to the start of the content of the string ``"two"`` which is line 5 (160 bytes); thus ``d = 0x00000000000000000000000000000000000000000000000000000000000000a0``. +Offset ``d`` points to the start of the content of the string ``"two"`` which is line 5 (160 bytes); +thus ``d = 0x00000000000000000000000000000000000000000000000000000000000000a0``. -Offset ``e`` points to the start of the content of the string ``"three"`` which is line 7 (224 bytes); thus ``e = 0x00000000000000000000000000000000000000000000000000000000000000e0``. +Offset ``e`` points to the start of the content of the string ``"three"`` which is line 7 (224 bytes); +thus ``e = 0x00000000000000000000000000000000000000000000000000000000000000e0``. -Note that the encodings of the embedded elements of the root arrays are not dependent on each other and have the same encodings for a function with a signature ``g(string[],uint[][])``. +Note that the encodings of the embedded elements of the root arrays are not dependent on each other +and have the same encodings for a function with a signature ``g(string[],uint[][])``. Then we encode the length of the first root array: @@ -376,7 +417,8 @@ Then we encode the length of the second root array: - ``0x0000000000000000000000000000000000000000000000000000000000000003`` (number of strings in the second root array, 3; the strings themselves are ``"one"``, ``"two"`` and ``"three"``) -Finally we find the offsets ``f`` and ``g`` for their respective root dynamic arrays ``[[1, 2], [3]]`` and ``["one", "two", "three"]``, and assemble parts in the correct order: +Finally we find the offsets ``f`` and ``g`` for their respective root dynamic arrays ``[[1, 2], [3]]`` and +``["one", "two", "three"]``, and assemble parts in the correct order: .. code-block:: none @@ -402,25 +444,36 @@ Finally we find the offsets ``f`` and ``g`` for their respective root dynamic ar 18 - 0000000000000000000000000000000000000000000000000000000000000005 - count for "three" 19 - 7468726565000000000000000000000000000000000000000000000000000000 - encoding of "three" -Offset ``f`` points to the start of the content of the array ``[[1, 2], [3]]`` which is line 2 (64 bytes); thus ``f = 0x0000000000000000000000000000000000000000000000000000000000000040``. +Offset ``f`` points to the start of the content of the array ``[[1, 2], [3]]`` which is line 2 (64 bytes); +thus ``f = 0x0000000000000000000000000000000000000000000000000000000000000040``. -Offset ``g`` points to the start of the content of the array ``["one", "two", "three"]`` which is line 10 (320 bytes); thus ``g = 0x0000000000000000000000000000000000000000000000000000000000000140``. +Offset ``g`` points to the start of the content of the array ``["one", "two", "three"]`` which is line 10 (320 bytes); +thus ``g = 0x0000000000000000000000000000000000000000000000000000000000000140``. .. _abi_events: Events ====== -Events are an abstraction of the Ethereum logging/event-watching protocol. Log entries provide the contract's address, a series of up to four topics and some arbitrary length binary data. Events leverage the existing function ABI in order to interpret this (together with an interface spec) as a properly typed structure. +Events are an abstraction of the Ethereum logging/event-watching protocol. Log entries provide the contract's +address, a series of up to four topics and some arbitrary length binary data. Events leverage the existing function +ABI in order to interpret this (together with an interface spec) as a properly typed structure. -Given an event name and series of event parameters, we split them into two sub-series: those which are indexed and those which are not. Those which are indexed, which may number up to 3, are used alongside the Keccak hash of the event signature to form the topics of the log entry. Those which are not indexed form the byte array of the event. +Given an event name and series of event parameters, we split them into two sub-series: those which are indexed and +those which are not. Those which are indexed, which may number up to 3, are used alongside the Keccak hash of the +event signature to form the topics of the log entry. Those which are not indexed form the byte array of the event. 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]``: ``abi_encode(EVENT_INDEXED_ARGS[n - 1])`` (``EVENT_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are indexed); -- ``data``: ABI encoding of ``EVENT_NON_INDEXED_ARGS`` (``EVENT_NON_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are not indexed, ``abi_encode`` is the ABI encoding function used for returning a series of typed values from a function, as described above). +- ``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]``: ``abi_encode(EVENT_INDEXED_ARGS[n - 1])`` (``EVENT_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` + that are indexed); +- ``data``: ABI encoding of ``EVENT_NON_INDEXED_ARGS`` (``EVENT_NON_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` + that are not indexed, ``abi_encode`` is the ABI encoding function used for returning a series of typed values + from a function, as described above). For all types of length at most 32 bytes, the ``EVENT_INDEXED_ARGS`` array contains the value directly, padded or sign-extended (for signed integers) to 32 bytes, just as for regular ABI encoding. @@ -444,8 +497,8 @@ 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: -- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function `). -- ``name``: the name of the function. +- ``type``: ``"function"``, ``"constructor"``, ``"receive"`` (the :ref:`"receive Ether" function `) or ``"fallback"`` (the :ref:`"default" function `); +- ``name``: the name of the function; - ``inputs``: an array of objects, each of which contains: * ``name``: the name of the parameter. @@ -453,17 +506,12 @@ A function description is a JSON object with the fields: * ``components``: used for tuple types (more below). - ``outputs``: an array of objects similar to ``inputs``. -- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state `), ``view`` (:ref:`specified to not modify the blockchain state `), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether). -- ``payable``: ``true`` if function accepts Ether, ``false`` otherwise. -- ``constant``: ``true`` if function is either ``pure`` or ``view``, ``false`` otherwise. - -``type`` can be omitted, defaulting to ``"function"``, likewise ``payable`` and ``constant`` can be omitted, both defaulting to ``false``. +- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read + blockchain state `), ``view`` (:ref:`specified to not modify the blockchain + state `), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether). Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either. -.. warning:: - The fields ``constant`` and ``payable`` are deprecated and will be removed in the future. Instead, the ``stateMutability`` field can be used to determine the same properties. - .. note:: Sending non-zero Ether to non-payable function will revert the transaction. @@ -539,8 +587,8 @@ As an example, the code contract Test { struct S { uint a; uint[] b; T[] c; } struct T { uint x; uint y; } - function f(S memory s, T memory t, uint a) public; - function g() public returns (S memory s, T memory t, uint a); + function f(S memory s, T memory t, uint a) public {} + function g() public returns (S memory s, T memory t, uint a) {} } would result in the JSON: diff --git a/docs/assembly.rst b/docs/assembly.rst index d735b54cd619..2bc89ad57aa2 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -6,7 +6,8 @@ Solidity Assembly Solidity defines an assembly language that you can use without Solidity and also as "inline assembly" inside Solidity source code. This guide starts with describing -how to use inline assembly, how it differs from standalone assembly, and +how to use inline assembly, how it differs from standalone assembly +(sometimes also referred to by its proper name "Yul"), and specifies assembly itself. .. _inline-assembly: @@ -22,6 +23,10 @@ As the EVM is a stack machine, it is often hard to address the correct stack slo and provide arguments to opcodes at the correct point on the stack. Solidity's inline assembly helps you do this, and with other issues that arise when writing manual assembly. +For inline assembly, the stack is actually not visible at all, but if you look +closer, there is always a very direct translation from inline assembly to +the stack based EVM opcode stream. + Inline assembly has the following features: * functional-style opcodes: ``mul(1, add(2, 3))`` @@ -48,32 +53,22 @@ these curly braces, you can use the following (see the later sections for more d - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) - opcodes in functional style, e.g. ``add(1, mload(0))`` - - variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of empty (0) is assigned) + - variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of 0 is assigned) - identifiers (assembly-local variables and externals if used as inline assembly), e.g. ``add(3, x)``, ``sstore(x_slot, 2)`` - assignments, e.g. ``x := add(y, 3)`` - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }`` -The following features are only available for standalone assembly: - - - direct stack control via ``dup1``, ``swap1``, ... - - direct stack assignments (in "instruction style"), e.g. ``3 =: x`` - - labels, e.g. ``name:`` - - jump opcodes - -.. note:: - Standalone assembly is supported for backwards compatibility but is not documented - here anymore. - -At the end of the ``assembly { ... }`` block, the stack must be balanced, -unless you require it otherwise. If it is not balanced, the compiler generates -a warning. +Inline assembly manages local variables and control-flow. Because of that, +opcodes that interfere with these features are not available. This includes +the ``dup`` and ``swap`` instructions as well as ``jump`` instructions and labels. Example ------- The following example provides library code to access the code of another contract and load it into a ``bytes`` variable. This is not possible with "plain Solidity" and the -idea is that assembly libraries will be used to enhance the Solidity language. +idea is that reusable assembly libraries can enhance the Solidity language +without a compiler change. .. code:: @@ -157,26 +152,23 @@ Opcodes ------- This document does not want to be a full description of the Ethereum virtual machine, but the -following list can be used as a reference of its opcodes. +following list can be used as a quick reference of its opcodes. -If an opcode takes arguments (always from the top of the stack), they are given in parentheses. -Note that the order of arguments can be seen to be reversed in non-functional style (explained below). -Opcodes marked with ``-`` do not push an item onto the stack (do not return a result), -those marked with ``*`` are special and all others push exactly one item onto the stack (their "return value"). +If an opcode takes arguments, they are given in parentheses. +Opcodes marked with ``-`` do not return a result, +those marked with ``*`` are special in a certain way and all others return exactly one value. Opcodes marked with ``F``, ``H``, ``B``, ``C`` or ``I`` are present since Frontier, Homestead, Byzantium, Constantinople or Istanbul, respectively. In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to -but not including position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``. +but not including position ``b`` and ``storage[p]`` signifies the storage contents at slot ``p``. -The opcodes ``pushi`` and ``jumpdest`` cannot be used directly. - -In the grammar, opcodes are represented as pre-defined identifiers. +In the grammar, opcodes are represented as pre-defined identifiers ("built-in functions"). +-------------------------+-----+---+-----------------------------------------------------------------+ | Instruction | | | Explanation | +=========================+=====+===+=================================================================+ -| stop + `-` | F | stop execution, identical to return(0,0) | +| stop() + `-` | F | stop execution, identical to return(0, 0) | +-------------------------+-----+---+-----------------------------------------------------------------+ | add(x, y) | | F | x + y | +-------------------------+-----+---+-----------------------------------------------------------------+ @@ -208,11 +200,11 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+-----+---+-----------------------------------------------------------------+ | iszero(x) | | F | 1 if x == 0, 0 otherwise | +-------------------------+-----+---+-----------------------------------------------------------------+ -| and(x, y) | | F | bitwise and of x and y | +| and(x, y) | | F | bitwise "and" of x and y | +-------------------------+-----+---+-----------------------------------------------------------------+ -| or(x, y) | | F | bitwise or of x and y | +| or(x, y) | | F | bitwise "or" of x and y | +-------------------------+-----+---+-----------------------------------------------------------------+ -| xor(x, y) | | F | bitwise xor of x and y | +| xor(x, y) | | F | bitwise "xor" of x and y | +-------------------------+-----+---+-----------------------------------------------------------------+ | byte(n, x) | | F | nth byte of x, where the most significant byte is the 0th byte | +-------------------------+-----+---+-----------------------------------------------------------------+ @@ -220,7 +212,7 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+-----+---+-----------------------------------------------------------------+ | shr(x, y) | | C | logical shift right y by x bits | +-------------------------+-----+---+-----------------------------------------------------------------+ -| sar(x, y) | | C | arithmetic shift right y by x bits | +| sar(x, y) | | C | signed arithmetic shift right y by x bits | +-------------------------+-----+---+-----------------------------------------------------------------+ | addmod(x, y, m) | | F | (x + y) % m with arbitrary precision arithmetic | +-------------------------+-----+---+-----------------------------------------------------------------+ @@ -230,17 +222,9 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+-----+---+-----------------------------------------------------------------+ | keccak256(p, n) | | F | keccak(mem[p...(p+n))) | +-------------------------+-----+---+-----------------------------------------------------------------+ -| jump(label) | `-` | F | jump to label / code position | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| jumpi(label, cond) | `-` | F | jump to label if cond is nonzero | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| pc | | F | current position in code | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| pop(x) | `-` | F | remove the element pushed by x | +| pc() | | F | current position in code | +-------------------------+-----+---+-----------------------------------------------------------------+ -| dup1 ... dup16 | | F | copy nth stack slot to the top (counting from top) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| swap1 ... swap16 | `*` | F | swap topmost and nth stack slot below it | +| pop(x) | `-` | F | discard value x | +-------------------------+-----+---+-----------------------------------------------------------------+ | mload(p) | | F | mem[p...(p+32)) | +-------------------------+-----+---+-----------------------------------------------------------------+ @@ -252,27 +236,27 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+-----+---+-----------------------------------------------------------------+ | sstore(p, v) | `-` | F | storage[p] := v | +-------------------------+-----+---+-----------------------------------------------------------------+ -| msize | | F | size of memory, i.e. largest accessed memory index | +| msize() | | F | size of memory, i.e. largest accessed memory index | +-------------------------+-----+---+-----------------------------------------------------------------+ -| gas | | F | gas still available to execution | +| gas() | | F | gas still available to execution | +-------------------------+-----+---+-----------------------------------------------------------------+ -| address | | F | address of the current contract / execution context | +| address() | | F | address of the current contract / execution context | +-------------------------+-----+---+-----------------------------------------------------------------+ | balance(a) | | F | wei balance at address a | +-------------------------+-----+---+-----------------------------------------------------------------+ | selfbalance() | | I | equivalent to balance(address()), but cheaper | +-------------------------+-----+---+-----------------------------------------------------------------+ -| caller | | F | call sender (excluding ``delegatecall``) | +| caller() | | F | call sender (excluding ``delegatecall``) | +-------------------------+-----+---+-----------------------------------------------------------------+ -| callvalue | | F | wei sent together with the current call | +| callvalue() | | F | wei sent together with the current call | +-------------------------+-----+---+-----------------------------------------------------------------+ | calldataload(p) | | F | call data starting from position p (32 bytes) | +-------------------------+-----+---+-----------------------------------------------------------------+ -| calldatasize | | F | size of call data in bytes | +| calldatasize() | | F | size of call data in bytes | +-------------------------+-----+---+-----------------------------------------------------------------+ | calldatacopy(t, f, s) | `-` | F | copy s bytes from calldata at position f to mem at position t | +-------------------------+-----+---+-----------------------------------------------------------------+ -| codesize | | F | size of the code of the current contract / execution context | +| codesize() | | F | size of the code of the current contract / execution context | +-------------------------+-----+---+-----------------------------------------------------------------+ | codecopy(t, f, s) | `-` | F | copy s bytes from code at position f to mem at position t | +-------------------------+-----+---+-----------------------------------------------------------------+ @@ -280,7 +264,7 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+-----+---+-----------------------------------------------------------------+ | extcodecopy(a, t, f, s) | `-` | F | like codecopy(t, f, s) but take code at address a | +-------------------------+-----+---+-----------------------------------------------------------------+ -| returndatasize | | B | size of the last returndata | +| returndatasize() | | B | size of the last returndata | +-------------------------+-----+---+-----------------------------------------------------------------+ | returndatacopy(t, f, s) | `-` | B | copy s bytes from returndata at position f to mem at position t | +-------------------------+-----+---+-----------------------------------------------------------------+ @@ -315,7 +299,7 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+-----+---+-----------------------------------------------------------------+ | selfdestruct(a) | `-` | F | end execution, destroy current contract and send funds to a | +-------------------------+-----+---+-----------------------------------------------------------------+ -| invalid | `-` | F | end execution with invalid instruction | +| invalid() | `-` | F | end execution with invalid instruction | +-------------------------+-----+---+-----------------------------------------------------------------+ | log0(p, s) | `-` | F | log without topics and data mem[p...(p+s)) | +-------------------------+-----+---+-----------------------------------------------------------------+ @@ -328,23 +312,23 @@ In the grammar, opcodes are represented as pre-defined identifiers. | log4(p, s, t1, t2, t3, | `-` | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) | | t4) | | | | +-------------------------+-----+---+-----------------------------------------------------------------+ -| chainid | | I | ID of the executing chain (EIP 1344) | +| chainid() | | I | ID of the executing chain (EIP 1344) | +-------------------------+-----+---+-----------------------------------------------------------------+ -| origin | | F | transaction sender | +| origin() | | F | transaction sender | +-------------------------+-----+---+-----------------------------------------------------------------+ -| gasprice | | F | gas price of the transaction | +| gasprice() | | F | gas price of the transaction | +-------------------------+-----+---+-----------------------------------------------------------------+ | blockhash(b) | | F | hash of block nr b - only for last 256 blocks excluding current | +-------------------------+-----+---+-----------------------------------------------------------------+ -| coinbase | | F | current mining beneficiary | +| coinbase() | | F | current mining beneficiary | +-------------------------+-----+---+-----------------------------------------------------------------+ -| timestamp | | F | timestamp of the current block in seconds since the epoch | +| timestamp() | | F | timestamp of the current block in seconds since the epoch | +-------------------------+-----+---+-----------------------------------------------------------------+ -| number | | F | current block number | +| number() | | F | current block number | +-------------------------+-----+---+-----------------------------------------------------------------+ -| difficulty | | F | difficulty of the current block | +| difficulty() | | F | difficulty of the current block | +-------------------------+-----+---+-----------------------------------------------------------------+ -| gaslimit | | F | block gas limit of the current block | +| gaslimit() | | F | block gas limit of the current block | +-------------------------+-----+---+-----------------------------------------------------------------+ Literals @@ -423,12 +407,6 @@ Local Solidity variables are available for assignments, for example: To clean signed types, you can use the ``signextend`` opcode: ``assembly { signextend(, x) }`` -Labels ------- - -Support for labels has been removed in version 0.5.0 of Solidity. -Please use functions, loops, if or switch statements instead. - Declaring Assembly-Local Variables ---------------------------------- @@ -439,6 +417,12 @@ for the variable and automatically removed again when the end of the block is reached. You need to provide an initial value for the variable which can be just ``0``, but it can also be a complex functional-style expression. +Since 0.6.0 the name of a declared variable may not end in ``_offset`` or ``_slot`` +and it may not shadow any declaration visible in the scope of the inline assembly block +(including variable, contract and function declarations). Similarly, if the name of a declared +variable contains a dot ``.``, the prefix up to the ``.`` may not conflict with any +declaration visible in the scope of the inline assembly block. + .. code:: pragma solidity >=0.4.16 <0.7.0; @@ -530,6 +514,9 @@ the other two are blocks. If the initializing part declares any variables, the scope of these variables is extended into the body (including the condition and the post-iteration part). +The ``break`` and ``continue`` statements can be used to exit the loop +or skip to the post-part, respectively. + The following example computes the sum of an area in memory. .. code:: @@ -565,12 +552,16 @@ opcode. Functions can be defined anywhere and are visible in the block they are declared in. Inside a function, you cannot access local variables -defined outside of that function. There is no explicit ``return`` -statement. +defined outside of that function. If you call a function that returns multiple values, you have to assign them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``. +The ``leave`` statement can be used to exit the current function. It +works like the ``return`` statement in other languages just that it does +not take a value to return, it just exits the functions and the function +will return whatever values are currently assigned to the return variable(s). + The following example implements the power function by square-and-multiply. .. code:: @@ -763,14 +754,13 @@ Grammar:: AssemblyExpression | AssemblyLocalDefinition | AssemblyAssignment | - AssemblyStackAssignment | - LabelDefinition | AssemblyIf | AssemblySwitch | AssemblyFunctionDefinition | AssemblyFor | 'break' | 'continue' | + 'leave' | SubAssembly AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral @@ -780,8 +770,6 @@ Grammar:: AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression IdentifierOrList = Identifier | '(' IdentifierList ')' IdentifierList = Identifier ( ',' Identifier)* - AssemblyStackAssignment = '=:' Identifier - LabelDefinition = Identifier ':' AssemblyIf = 'if' AssemblyExpression AssemblyBlock AssemblySwitch = 'switch' AssemblyExpression AssemblyCase* ( 'default' AssemblyBlock )? diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 1a0e99749db3..0fa1e1076e71 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -854,5 +854,9 @@ "ABIEncoderV2StorageArrayWithMultiSlotElement" ], "released": "2019-05-28" + }, + "0.6.0": { + "bugs": [], + "released": "2019-12-17" } } \ No newline at end of file diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst index ed87666d9bbf..ab25cb4b9ec9 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -82,7 +82,7 @@ This is as opposed to the more intuitive sending pattern: Notice that, in this example, an attacker could trap the contract into an unusable state by causing ``richest`` to be -the address of a contract that has a fallback function +the address of a contract that has a receive or fallback function which fails (e.g. by using ``revert()`` or by just consuming more than the 2300 gas stipend transferred to them). That way, whenever ``transfer`` is called to deliver funds to the diff --git a/docs/contracts.rst b/docs/contracts.rst index 5bab6e78a81e..1426bfd16310 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -7,9 +7,10 @@ Contracts ########## Contracts in Solidity are similar to classes in object-oriented languages. They -contain persistent data in state variables and functions that can modify these +contain persistent data in state variables, and functions that can modify these variables. Calling a function on a different contract (instance) will perform -an EVM function call and thus switch the context such that state variables are +an EVM function call and thus switch the context such that state variables +in the calling contract are inaccessible. A contract and its functions need to be called for anything to happen. There is no "cron" concept in Ethereum to call a function at a particular event automatically. diff --git a/docs/contracts/abstract-contracts.rst b/docs/contracts/abstract-contracts.rst index 924805467ccf..51db0dfbedeb 100644 --- a/docs/contracts/abstract-contracts.rst +++ b/docs/contracts/abstract-contracts.rst @@ -6,38 +6,49 @@ Abstract Contracts ****************** -Contracts are marked as abstract when at least one of their functions lacks an implementation as in the following example (note that the function declaration header is terminated by ``;``):: +Contracts need to be marked as abstract when at least one of their functions is not implemented. +Contracts may be marked as abstract even though all functions are implemented. + +This can be done by using the ``abstract`` keyword as shown in the following example. Note that this contract needs to be +defined as abstract, because the function ``utterance()`` was defined, but no implementation was +provided (no implementation body ``{ }`` was given).:: pragma solidity >=0.4.0 <0.7.0; - contract Feline { - function utterance() public returns (bytes32); + abstract contract Feline { + function utterance() public virtual returns (bytes32); } -Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts:: +Such abstract contracts can not be instantiated directly. This is also true, if an abstract contract itself does implement +all defined functions. The usage of an abstract contract as a base class is shown in the following example:: - pragma solidity >=0.4.0 <0.7.0; + pragma solidity ^0.6.0; - contract Feline { - function utterance() public returns (bytes32); + abstract contract Feline { + function utterance() public virtual returns (bytes32); } contract Cat is Feline { - function utterance() public returns (bytes32) { return "miaow"; } + function utterance() public override returns (bytes32) { return "miaow"; } } -If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract. +If a contract inherits from an abstract contract and does not implement all non-implemented +functions by overriding, it needs to be marked as abstract as well. -Note that a function without implementation is different from a :ref:`Function Type ` even though their syntax looks very similar. +Note that a function without implementation is different from +a :ref:`Function Type ` even though their syntax looks very similar. Example of function without implementation (a function declaration):: function foo(address) external returns (address); -Example of a Function Type (a variable declaration, where the variable is of type ``function``):: +Example of a declaration of a variable whose type is a function type:: function(address) external returns (address) foo; -Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and +Abstract contracts decouple the definition of a contract from its +implementation providing better extensibility and self-documentation and facilitating patterns like the `Template method `_ and removing code duplication. -Abstract contracts are useful in the same way that defining methods in an interface is useful. It is a way for the designer of the abstract contract to say "any child of mine must implement this method". +Abstract contracts are useful in the same way that defining methods +in an interface is useful. It is a way for the designer of the +abstract contract to say "any child of mine must implement this method". diff --git a/docs/contracts/creating-contracts.rst b/docs/contracts/creating-contracts.rst index b6f5aee501f8..90578f784768 100644 --- a/docs/contracts/creating-contracts.rst +++ b/docs/contracts/creating-contracts.rst @@ -8,16 +8,17 @@ Contracts can be created "from outside" via Ethereum transactions or from within IDEs, such as `Remix `_, make the creation process seamless using UI elements. -Creating contracts programmatically on Ethereum is best done via using the JavaScript API `web3.js `_. +One way to create contracts programmatically on Ethereum is via the JavaScript API `web3.js `_. It has a function called `web3.eth.Contract `_ to facilitate contract creation. -When a contract is created, its :ref:`constructor ` (a function declared with the ``constructor`` keyword) is executed once. +When a contract is created, its :ref:`constructor ` (a function declared with +the ``constructor`` keyword) is executed once. A constructor is optional. Only one constructor is allowed, which means overloading is not supported. -After the constructor has executed, the final code of the contract is deployed to the +After the constructor has executed, the final code of the contract is stored on the blockchain. This code includes all public and external functions and all functions that are reachable from there through function calls. The deployed code does not include the constructor code or internal functions only called from the constructor. @@ -57,18 +58,20 @@ This means that cyclic creation dependencies are impossible. // See the next section for details. owner = msg.sender; - // We do an explicit type conversion from `address` + // We perform an explicit type conversion from `address` // to `TokenCreator` and assume that the type of // the calling contract is `TokenCreator`, there is - // no real way to check that. + // no real way to verify that. + // This does not create a new contract. creator = TokenCreator(msg.sender); name = _name; } function changeName(bytes32 newName) public { - // Only the creator can alter the name -- - // the comparison is possible since contracts - // are explicitly convertible to addresses. + // Only the creator can alter the name. + // We compare the contract based on its + // address which can be retrieved by + // explicit conversion to address. if (msg.sender == address(creator)) name = newName; } @@ -94,9 +97,9 @@ This means that cyclic creation dependencies are impossible. returns (OwnedToken tokenAddress) { // Create a new `Token` contract and return its address. - // From the JavaScript side, the return type is - // `address`, as this is the closest type available in - // the ABI. + // From the JavaScript side, the return type + // of this function is `address`, as this is + // the closest type available in the ABI. return new OwnedToken(name); } diff --git a/docs/contracts/events.rst b/docs/contracts/events.rst index c780da8a902f..46b426d8e66b 100644 --- a/docs/contracts/events.rst +++ b/docs/contracts/events.rst @@ -13,12 +13,12 @@ Events are inheritable members of contracts. When you call them, they cause the arguments to be stored in the transaction's log - a special data structure in the blockchain. These logs are associated with the address of the contract, are incorporated into the blockchain, and stay there as long as a block is -accessible (forever as of the Frontier and Homestead releases, but this might +accessible (forever as of now, but this might change with Serenity). The Log and its event data is not accessible from within contracts (not even from the contract that created them). -It is possible to request a simple payment verification (SPV) for logs, so if -an external entity supplies a contract with such a verification, it can check +It is possible to request a Merkle proof for logs, so if +an external entity supplies a contract with such a proof, it can check that the log actually exists inside the blockchain. You have to supply block headers because the contract can only see the last 256 block hashes. diff --git a/docs/contracts/function-modifiers.rst b/docs/contracts/function-modifiers.rst index fd24a8cb5b00..ad2804302feb 100644 --- a/docs/contracts/function-modifiers.rst +++ b/docs/contracts/function-modifiers.rst @@ -6,9 +6,14 @@ Function Modifiers ****************** -Modifiers can be used to easily change the behaviour of functions. For example, -they can automatically check a condition prior to executing the function. Modifiers are -inheritable properties of contracts and may be overridden by derived contracts. +Modifiers can be used to change the behaviour of functions in a declarative way. +For example, +you can use a modifier to automatically check a condition prior to executing the function. + +Modifiers are +inheritable properties of contracts and may be overridden by derived contracts, but only +if they are marked ``virtual``. For details, please see +:ref:`Modifier Overriding `. :: diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst index e8d9961e7aec..717961b7c09d 100644 --- a/docs/contracts/functions.rst +++ b/docs/contracts/functions.rst @@ -11,8 +11,8 @@ Functions Function Parameters and Return Variables ======================================== -As in JavaScript, functions may take parameters as input. Unlike in JavaScript -and C, functions may also return an arbitrary number of values as output. +Functions take typed parameters as input and may, unlike in many other +languages, also return an arbitrary number of values as output. Function Parameters ------------------- @@ -21,7 +21,7 @@ Function parameters are declared the same way as variables, and the name of unused parameters can be omitted. For example, if you want your contract to accept one kind of external call -with two integers, you would use something like:: +with two integers, you would use something like the following:: pragma solidity >=0.4.16 <0.7.0; @@ -39,7 +39,7 @@ Function parameters can be used as any other local variable and they can also be An :ref:`external function` cannot accept a multi-dimensional array as an input parameter. This functionality is possible if you enable the new - experimental ``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file. + ``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file. An :ref:`internal function` can accept a multi-dimensional array without enabling the feature. @@ -70,7 +70,8 @@ two integers passed as function parameters, then you use something like:: The names of return variables can be omitted. Return variables can be used as any other local variable and they -are initialized with their :ref:`default value ` and have that value unless explicitly set. +are initialized with their :ref:`default value ` and have that +value until they are (re-)assigned. You can either explicitly assign to return variables and then leave the function using ``return;``, @@ -96,7 +97,7 @@ return variables and then using ``return;`` to leave the function. .. note:: You cannot return some types from non-internal functions, notably multi-dimensional dynamic arrays and structs. If you enable the - new experimental ``ABIEncoderV2`` feature by adding ``pragma experimental + new ``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file then more types are available, but ``mapping`` types are still limited to inside a single contract and you cannot transfer them. @@ -107,7 +108,8 @@ Returning Multiple Values ------------------------- When a function has multiple return types, the statement ``return (v0, v1, ..., vn)`` can be used to return multiple values. -The number of components must be the same as the number of return types. +The number of components must be the same as the number of return variables +and their types have to match, potentially after an :ref:`implicit conversion `. .. index:: ! view function, function;view @@ -120,7 +122,7 @@ Functions can be declared ``view`` in which case they promise not to modify the .. note:: If the compiler's EVM target is Byzantium or newer (default) the opcode - ``STATICCALL`` is used for ``view`` functions which enforces the state + ``STATICCALL`` is used when ``view`` functions are called, which enforces the state to stay unmodified as part of the EVM execution. For library ``view`` functions ``DELEGATECALL`` is used, because there is no combined ``DELEGATECALL`` and ``STATICCALL``. This means library ``view`` functions do not have run-time checks that prevent state @@ -222,23 +224,25 @@ This behaviour is also in line with the ``STATICCALL`` opcode. not do state-changing operations, but it cannot check that the contract that will be called at runtime is actually of that type. -.. index:: ! fallback function, function;fallback +.. index:: ! receive ether function, function;receive ! receive -.. _fallback-function: +.. _receive-ether-function: -Fallback Function -================= +Receive Ether Function +====================== -A contract can have exactly one unnamed function. This function cannot have -arguments, cannot return anything and has to have ``external`` visibility. -It is executed on a call to the contract if none of the other -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). To receive Ether and add it to the total balance of the contract, the fallback function -must be marked ``payable``. If no such function exists, the contract cannot receive -Ether through regular transactions and throws an exception. +A contract can have at most one ``receive`` function, declared using +``receive() external payable { ... }`` +(without the ``function`` keyword). +This function cannot have arguments, cannot return anything and must have +``external`` visibility and ``payable`` state mutability. It is executed on a +call to the contract with empty calldata. This is the function that is executed +on plain Ether transfers (e.g. via `.send()` or `.transfer()`). If no such +function exists, but a payable :ref:`fallback function ` +exists, the fallback function will be called on a plain Ether transfer. If +neither a receive Ether nor a payable fallback function is present, the +contract cannot receive Ether through regular transactions and throws an +exception. In the worst case, the fallback function can only rely on 2300 gas being available (for example when `send` or `transfer` is used), leaving little @@ -250,36 +254,88 @@ will consume more gas than the 2300 gas stipend: - Calling an external function which consumes a large amount of gas - Sending Ether -Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it. - -.. warning:: - The fallback function is also executed if the caller meant to call - a function that is not available. If you want to implement the fallback - function only to receive ether, you should add a check - like ``require(msg.data.length == 0)`` to prevent invalid calls. - .. warning:: Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``) - but do not define a fallback function + but do not define a receive Ether function or a payable fallback function throw an exception, sending back the Ether (this was different before Solidity v0.4.0). So if you want your contract to receive Ether, - you have to implement a payable fallback function. + you have to implement a receive Ether function (using payable fallback functions for receiving Ether is + not recommended, since it would not fail on interface confusions). + .. warning:: - A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`) + A contract without a receive Ether 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 ``address(this).balance`` can be higher + than the sum of some manual accounting implemented in a + contract (i.e. having a counter updated in the receive Ether function). + +Below you can see an example of a Sink contract that uses function ``receive``. + +:: + + pragma solidity ^0.6.0; + + // This contract keeps all Ether sent to it with no way + // to get it back. + contract Sink { + event Received(address, uint); + receive() external payable { + emit Received(msg.sender, msg.value); + } + } + +.. index:: ! fallback function, function;fallback + +.. _fallback-function: + +Fallback Function +================= + +A contract can have at most one ``fallback`` function, declared using ``fallback () external [payable]`` +(without the ``function`` keyword). +This function cannot have arguments, cannot return anything and must have ``external`` visibility. +It is executed on a call to the contract if none of the other +functions match the given function signature, or if no data was supplied at +all and there is no :ref:`receive Ether function `. +The fallback function always receives data, but in order to also receive Ether +it must be marked ``payable``. + +In the worst case, if a payable fallback function is also used in +place of a receive function, it can only rely on 2300 gas being +available (see :ref:`receive Ether function ` +for a brief description of the implications of this). + +Like any function, the fallback function can execute complex +operations as long as there is enough gas passed on to it. + +.. warning:: + A ``payable`` fallback function is also executed for + plain Ether transfers, if no :ref:`receive Ether function ` + is present. It is recommended to always define a receive Ether + function as well, if you define a payable fallback function + to distinguish Ether transfers from interface confusions. + .. note:: Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve any payload supplied with the call. + After having checked the first four bytes of ``msg.data``, + you can use ``abi.decode`` together with the array slice syntax to + decode ABI-encoded data: + ``(c, d) = abi.decode(msg.data[4:], (uint256, uint256));`` + Note that this should only be used as a last resort and + proper functions should be used instead. - A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it. - - It also means that ``address(this).balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function). :: - pragma solidity >=0.5.0 <0.7.0; + pragma solidity ^0.6.0; contract Test { // This function is called for all messages sent to @@ -287,15 +343,23 @@ Like any function, the fallback function can execute complex operations as long // Sending Ether to this contract will cause an exception, // because the fallback function does not have the `payable` // modifier. - function() external { x = 1; } + fallback() external { x = 1; } uint x; } - - // This contract keeps all Ether sent to it with no way - // to get it back. - contract Sink { - function() external payable { } + contract TestPayable { + // This function is called for all messages sent to + // this contract, except plain Ether transfers + // (there is no other function except the receive function). + // Any call with non-empty calldata to this contract will execute + // the fallback function (even if Ether is sent along with the call). + fallback() external payable { x = 1; y = msg.value; } + + // This function is called for plain Ether transfers, i.e. + // for every call with empty calldata. + receive() external payable { x = 2; y = msg.value; } + uint x; + uint y; } contract Caller { @@ -305,14 +369,27 @@ Like any function, the fallback function can execute complex operations as long // results in test.x becoming == 1. // address(test) will not allow to call ``send`` directly, since ``test`` has no payable - // fallback function. It has to be converted to the ``address payable`` type via an - // intermediate conversion to ``uint160`` to even allow calling ``send`` on it. - address payable testPayable = address(uint160(address(test))); + // fallback function. + // It has to be converted to the ``address payable`` type to even allow calling ``send`` on it. + address payable testPayable = payable(address(test)); - // If someone sends ether to that contract, + // If someone sends Ether to that contract, // the transfer will fail, i.e. this returns false here. return testPayable.send(2 ether); } + + function callTestPayable(TestPayable test) public returns (bool) { + (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()")); + require(success); + // results in test.x becoming == 1 and test.y becoming 0. + (success,) = address(test).call.value(1)(abi.encodeWithSignature("nonExistingFunction()")); + require(success); + // results in test.x becoming == 1 and test.y becoming 1. + + // If someone sends Ether to that contract, the receive function in TestPayable will be called. + require(address(test).send(2 ether)); + // results in test.x becoming == 2 and test.y becoming 2 ether. + } } .. index:: ! overload diff --git a/docs/contracts/inheritance.rst b/docs/contracts/inheritance.rst index 28df381ee054..ee766e474e41 100644 --- a/docs/contracts/inheritance.rst +++ b/docs/contracts/inheritance.rst @@ -6,9 +6,18 @@ Inheritance Solidity supports multiple inheritance including polymorphism. -All function calls are virtual, which means that the most derived function -is called, except when the contract name is explicitly given or the -``super`` keyword is used. +Polymorphism means that a function call (internal and external) +always executes the function of the same name (and parameter types) +in the most derived contract in the inheritance hierarchy. +This has to be explicitly enabled on each function in the +hierarchy using the ``virtual`` and ``override`` keywords. +See :ref:`Function Overriding ` for more details. + +It is possible to call functions further up in the inheritance +hierarchy internally by explicitly specifying the contract +using ``ContractName.functionName()`` or using ``super.functionName()`` +if you want to call the function one level higher up in +the flattened inheritance hierarchy (see below). When a contract inherits from other contracts, only a single contract is created on the blockchain, and the code from all the base contracts @@ -16,6 +25,10 @@ is compiled into the created contract. This means that all internal calls to functions of base contracts also just use internal function calls (``super.f(..)`` will use JUMP and not a message call). +State variable shadowing is considered as an error. A derived contract can +only declare a state variable ``x``, if there is no visible state variable +with the same name in any of its bases. + The general inheritance system is very similar to `Python's `_, especially concerning multiple inheritance, but there are also @@ -25,7 +38,7 @@ Details are given in the following example. :: - pragma solidity >=0.5.0 <0.7.0; + pragma solidity ^0.6.0; contract Owned { @@ -39,7 +52,9 @@ Details are given in the following example. // internal functions and state variables. These cannot be // accessed externally via `this`, though. contract Mortal is Owned { - function kill() public { + // The keyword `virtual` means that the function can change + // its behaviour in derived classes ("overriding"). + function kill() virtual public { if (msg.sender == owner) selfdestruct(owner); } } @@ -49,14 +64,14 @@ Details are given in the following example. // interface known to the compiler. Note the function // without body. If a contract does not implement all // functions it can only be used as an interface. - contract Config { - function lookup(uint id) public returns (address adr); + abstract contract Config { + function lookup(uint id) public virtual returns (address adr); } - contract NameReg { - function register(bytes32 name) public; - function unregister() public; + abstract contract NameReg { + function register(bytes32 name) public virtual; + function unregister() public virtual; } @@ -74,7 +89,10 @@ Details are given in the following example. // types of output parameters, that causes an error. // Both local and message-based function calls take these overrides // into account. - function kill() public { + // If you want the function to override, you need to use the + // `override` keyword. You need to specify the `virtual` keyword again + // if you want this function to be overridden again. + function kill() public virtual override { if (msg.sender == owner) { Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); NameReg(config.lookup(1)).unregister(); @@ -94,6 +112,10 @@ Details are given in the following example. if (msg.sender == owner) info = newInfo; } + // Here, we only specify `override` and not `virtual`. + // This means that contracts deriving from `PriceFeed` + // cannot change the behaviour of `kill` anymore. + function kill() public override(Mortal, Named) { Named.kill(); } function get() public view returns(uint r) { return info; } uint info; @@ -103,7 +125,7 @@ Note that above, we call ``mortal.kill()`` to "forward" the destruction request. The way this is done is problematic, as seen in the following example:: - pragma solidity >=0.4.22 <0.7.0; + pragma solidity ^0.6.0; contract owned { constructor() public { owner = msg.sender; } @@ -111,26 +133,26 @@ seen in the following example:: } contract mortal is owned { - function kill() public { + function kill() public virtual { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { - function kill() public { /* do cleanup 1 */ mortal.kill(); } + function kill() public virtual override { /* do cleanup 1 */ mortal.kill(); } } contract Base2 is mortal { - function kill() public { /* do cleanup 2 */ mortal.kill(); } + function kill() public virtual override { /* do cleanup 2 */ mortal.kill(); } } contract Final is Base1, Base2 { + function kill() public override(Base1, Base2) { Base2.kill(); } } -A call to ``Final.kill()`` will call ``Base2.kill`` as the most -derived override, but this function will bypass -``Base1.kill``, basically because it does not even know about -``Base1``. The way around this is to use ``super``:: +A call to ``Final.kill()`` will call ``Base2.kill`` because we specify it +explicitly in the final override, but this function will bypass +``Base1.kill``. The way around this is to use ``super``:: pragma solidity >=0.4.22 <0.7.0; @@ -140,21 +162,22 @@ derived override, but this function will bypass } contract mortal is owned { - function kill() public { + function kill() virtual public { if (msg.sender == owner) selfdestruct(owner); } } contract Base1 is mortal { - function kill() public { /* do cleanup 1 */ super.kill(); } + function kill() public virtual override { /* do cleanup 1 */ super.kill(); } } contract Base2 is mortal { - function kill() public { /* do cleanup 2 */ super.kill(); } + function kill() public virtual override { /* do cleanup 2 */ super.kill(); } } contract Final is Base1, Base2 { + function kill() public override(Base1, Base2) { super.kill(); } } If ``Base2`` calls a function of ``super``, it does not simply @@ -168,6 +191,176 @@ not known in the context of the class where it is used, although its type is known. This is similar for ordinary virtual method lookup. +.. _function-overriding: + +.. index:: ! overriding;function + +Function Overriding +=================== + +Base functions can be overridden by inheriting contracts to change their +behavior if they are marked as ``virtual``. The overriding function must then +use the ``override`` keyword in the function header as shown in this example: + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract Base + { + function foo() virtual public {} + } + + contract Middle is Base {} + + contract Inherited is Middle + { + function foo() public override {} + } + +For multiple inheritance, the most derived base contracts that define the same +function must be specified explicitly after the ``override`` keyword. +In other words, you have to specify all base contracts that define the same function +and have not yet been overridden by another base contract (on some path through the inheritance graph). +Additionally, if a contract inherits the same function from multiple (unrelated) +bases, it has to explicitly override it: + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract Base1 + { + function foo() virtual public {} + } + + contract Base2 + { + function foo() virtual public {} + } + + contract Inherited is Base1, Base2 + { + // Derives from multiple bases defining foo(), so we must explicitly + // override it + function foo() public override(Base1, Base2) {} + } + +An explicit override specifier is not required if +the function is defined in a common base contract +or if there is a unique function in a common base contract +that already overrides all other functions. + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract A { function f() public pure{} } + contract B is A {} + contract C is A {} + // No explicit override required + contract D is B, C {} + +More formally, it is not required to override a function (directly or +indirectly) inherited from multiple bases if there is a base contract +that is part of all override paths for the signature, and (1) that +base implements the function and no paths from the current contract +to the base mentions a function with that signature or (2) that base +does not implement the function and there is at most one mention of +the function in all paths from the current contract to that base. + +In this sense, an override path for a signature is a path through +the inheritance graph that starts at the contract under consideration +and ends at a contract mentioning a function with that signature +that does not override. + +If you do not mark a function that overrides as ``virtual``, derived +contracts can no longer change the behaviour of that function. + +.. note:: + + Functions with the ``private`` visibility cannot be ``virtual``. + +.. note:: + + Functions without implementation have to be marked ``virtual`` + outside of interfaces. In interfaces, all functions are + automatically considered ``virtual``. + +Public state variables can override external functions if the +parameter and return types of the function matches the getter function +of the variable: + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract A + { + function f() external pure virtual returns(uint) { return 5; } + } + + contract B is A + { + uint public override f; + } + +.. note:: + + While public state variables can override external functions, they themselves cannot + be overridden. + +.. _modifier-overriding: + +.. index:: ! overriding;modifier + +Modifier Overriding +=================== + +Function modifiers can override each other. This works in the same way as +`function overriding `_ (except that there is no overloading for modifiers). The +``virtual`` keyword must be used on the overridden modifier +and the ``override`` keyword must be used in the overriding modifier: + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract Base + { + modifier foo() virtual {_;} + } + + contract Inherited is Base + { + modifier foo() override {_;} + } + + +In case of multiple inheritance, all direct base contracts must be specified +explicitly: + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract Base1 + { + modifier foo() virtual {_;} + } + + contract Base2 + { + modifier foo() virtual {_;} + } + + contract Inherited is Base1, Base2 + { + modifier foo() override(Base1, Base2) {_;} + } + + + .. index:: ! constructor .. _constructor: @@ -297,6 +490,10 @@ The reason for this is that ``C`` requests ``X`` to override ``A`` requests to override ``X``, which is a contradiction that cannot be resolved. +Due to the fact that you have to explicitly override a function +that is inherited from multiple bases without a unique override, +C3 linearization is not too important in practice. + One area where inheritance linearization is especially important and perhaps not as clear is when there are multiple constructors in the inheritance hierarchy. The constructors will always be executed in the linearized order, regardless of the order in which their arguments are provided in the inheriting contract's constructor. For example: :: @@ -339,6 +536,9 @@ One area where inheritance linearization is especially important and perhaps not Inheriting Different Kinds of Members of the Same Name ====================================================== -When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error. -This error is produced also by an event and a modifier of the same name, and a function and an event of the same name. -As an exception, a state variable getter can override a public function. +It is an error when any of the following pairs in a contract have the same name due to inheritance: + - a function and a modifier + - a function and an event + - an event and a modifier + +As an exception, a state variable getter can override an external function. diff --git a/docs/contracts/interfaces.rst b/docs/contracts/interfaces.rst index 91e7d594772f..69d4917ccadc 100644 --- a/docs/contracts/interfaces.rst +++ b/docs/contracts/interfaces.rst @@ -32,6 +32,11 @@ Interfaces are denoted by their own keyword: Contracts can inherit interfaces as they would inherit other contracts. +All functions declared in interfaces are implicitly ``virtual``, which means that +they can be overridden. This does not automatically mean that an overriding function +can be overridden again - this is only possible if the overriding +function is marked ``virtual``. + Types defined inside interfaces and other contract-like structures can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``. diff --git a/docs/contracts/libraries.rst b/docs/contracts/libraries.rst index 2e77af746928..803e53f081df 100644 --- a/docs/contracts/libraries.rst +++ b/docs/contracts/libraries.rst @@ -36,12 +36,12 @@ if the library were a base contract. Of course, calls to internal functions use the internal calling convention, which means that all internal types can be passed and types :ref:`stored in memory ` will be passed by reference and not copied. To realize this in the EVM, code of internal library functions -and all functions called from therein will at compile time be pulled into the calling +and all functions called from therein will at compile time be included in the calling contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``. .. index:: using for, set -The following example illustrates how to use libraries (but manual method +The following example illustrates how to use libraries (but using a manual method, be sure to check out :ref:`using for ` for a more advanced example to implement a set). @@ -50,11 +50,11 @@ more advanced example to implement a set). pragma solidity >=0.4.22 <0.7.0; - library Set { - // We define a new struct datatype that will be used to - // hold its data in the calling contract. - struct Data { mapping(uint => bool) flags; } + // We define a new struct datatype that will be used to + // hold its data in the calling contract. + struct Data { mapping(uint => bool) flags; } + library Set { // Note that the first parameter is of type "storage // reference" and thus only its storage address and not // its contents is passed as part of the call. This is a @@ -92,7 +92,7 @@ more advanced example to implement a set). contract C { - Set.Data knownValues; + Data knownValues; function register(uint value) public { // The library functions can be called without a @@ -125,11 +125,11 @@ custom types without the overhead of external function calls: pragma solidity >=0.4.16 <0.7.0; - library BigInt { - struct bigint { - uint[] limbs; - } + struct bigint { + uint[] limbs; + } + library BigInt { function fromUint(uint x) internal pure returns (bigint memory r) { r.limbs = new uint[](1); r.limbs[0] = x; @@ -168,12 +168,12 @@ custom types without the overhead of external function calls: } contract C { - using BigInt for BigInt.bigint; + using BigInt for bigint; function f() public pure { - BigInt.bigint memory x = BigInt.fromUint(7); - BigInt.bigint memory y = BigInt.fromUint(uint(-1)); - BigInt.bigint memory z = x.add(y); + bigint memory x = BigInt.fromUint(7); + bigint memory y = BigInt.fromUint(uint(-1)); + bigint memory z = x.add(y); assert(z.limb(1) > 0); } } @@ -194,17 +194,18 @@ encoding of the address of the library contract. .. note:: Manually linking libraries on the generated bytecode is discouraged, because - it is restricted to 36 characters. + in this way, the library name is restricted to 36 characters. You should ask the compiler to link the libraries at the time a contract is compiled by either using the ``--libraries`` option of ``solc`` or the ``libraries`` key if you use the standard-JSON interface to the compiler. -Restrictions for libraries in comparison to contracts: +In comparison to contracts, libraries are restricted in the following ways: -- No state variables -- Cannot inherit nor be inherited -- Cannot receive Ether +- they cannot have state variables +- they cannot inherit nor be inherited +- they cannot receive Ether +- they cannot be destroyed (These might be lifted at a later point.) @@ -275,3 +276,7 @@ this causes the deploy time address to be the first constant to be pushed onto the stack and the dispatcher code compares the current address against this constant for any non-view and non-pure function. + +This means that the actual code stored on chain for a library +is different from the code reported by the compiler as +``deployedBytecode``. \ No newline at end of file diff --git a/docs/contracts/using-for.rst b/docs/contracts/using-for.rst index ed19dd046986..32aa71799822 100644 --- a/docs/contracts/using-for.rst +++ b/docs/contracts/using-for.rst @@ -7,7 +7,8 @@ Using For ********* The directive ``using A for B;`` can be used to attach library -functions (from the library ``A``) to any type (``B``). +functions (from the library ``A``) to any type (``B``) +in the context of a contract. These functions will receive the object they are called on as their first parameter (like the ``self`` variable in Python). @@ -25,9 +26,6 @@ contract, including within all of its functions, and has no effect outside of the contract in which it is used. The directive may only be used inside a contract, not inside any of its functions. -By including a library, its data types including library functions are -available without having to add further code. - Let us rewrite the set example from the :ref:`libraries` in this way:: @@ -35,9 +33,9 @@ Let us rewrite the set example from the // This is the same code as before, just without comments - library Set { - struct Data { mapping(uint => bool) flags; } + struct Data { mapping(uint => bool) flags; } + library Set { function insert(Data storage self, uint value) public returns (bool) @@ -69,11 +67,11 @@ Let us rewrite the set example from the contract C { - using Set for Set.Data; // this is the crucial change - Set.Data knownValues; + using Set for Data; // this is the crucial change + Data knownValues; function register(uint value) public { - // Here, all variables of type Set.Data have + // Here, all variables of type Data have // corresponding member functions. // The following function call is identical to // `Set.insert(knownValues, value)` @@ -115,7 +113,8 @@ It is also possible to extend elementary types in that way:: } } -Note that all library calls are actual EVM function calls. This means that +Note that all external library calls are actual EVM function calls. This means that if you pass memory or value types, a copy will be performed, even of the ``self`` variable. The only situation where no copy will be performed -is when storage reference variables are used. +is when storage reference variables are used or when internal library +functions are called. diff --git a/docs/contracts/visibility-and-getters.rst b/docs/contracts/visibility-and-getters.rst index 56d5b0e25d71..3a0905155f10 100644 --- a/docs/contracts/visibility-and-getters.rst +++ b/docs/contracts/visibility-and-getters.rst @@ -6,10 +6,10 @@ Visibility and Getters ********************** -Since Solidity knows two kinds of function calls (internal +Solidity knows two kinds of function calls: internal ones that do not create an actual EVM call (also called a "message call") and external -ones that do), there are four types of visibilities for +ones that do. Because of that, there are four types of visibility for functions and state variables. Functions have to be specified as being ``external``, @@ -22,7 +22,8 @@ For state variables, ``external`` is not possible. via transactions. An external function ``f`` cannot be called internally (i.e. ``f()`` does not work, but ``this.f()`` works). External functions are sometimes more efficient when - they receive large arrays of data. + they receive large arrays of data, because the data + is not copied from calldata to memory. ``public``: Public functions are part of the contract interface diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 00b341e825ac..d962a4e26e57 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -15,6 +15,10 @@ Most of the control structures known from curly-braces languages are available i There is: ``if``, ``else``, ``while``, ``do``, ``for``, ``break``, ``continue``, ``return``, with the usual semantics known from C or JavaScript. +Solidity also supports exception handling in the form of ``try``/``catch``-statements, +but only for :ref:`external function calls ` and +contract creation calls. + Parentheses can *not* be omitted for conditionals, but curly brances can be omitted around single-statement bodies. @@ -47,10 +51,10 @@ this nonsensical example:: These function calls are translated into simple jumps inside the EVM. This has the effect that the current memory is not cleared, i.e. passing memory references to internally-called functions is very efficient. Only functions of the same -contract can be called internally. +contract instance can be called internally. You should still avoid excessive recursion, as every internal function call -uses up at least one stack slot and there are at most 1024 slots available. +uses up at least one stack slot and there are only 1024 slots available. .. _external-function-calls: @@ -70,7 +74,10 @@ all function arguments have to be copied to memory. A function call from one contract to another does not create its own transaction, it is a message call as part of the overall transaction. -When calling functions of other contracts, you can specify the amount of Wei or gas sent with the call with the special options ``.value()`` and ``.gas()``, respectively. Any Wei you send to the contract is added to the total balance of the contract: +When calling functions of other contracts, you can specify the amount of Wei or +gas sent with the call with the special options ``.value()`` and ``.gas()``, +respectively. Any Wei you send to the contract is added to the total balance +of the contract: :: @@ -90,7 +97,10 @@ You need to use the modifier ``payable`` with the ``info`` function because otherwise, the ``.value()`` option would not be available. .. warning:: - Be careful that ``feed.info.value(10).gas(800)`` only locally sets the ``value`` and amount of ``gas`` sent with the function call, and the parentheses at the end perform the actual call. So in this case, the function is not called and the ``value`` and ``gas`` settings are lost. + Be careful that ``feed.info.value(10).gas(800)`` only locally sets the + ``value`` and amount of ``gas`` sent with the function call, and the + parentheses at the end perform the actual call. So in this case, the + function is not called and the ``value`` and ``gas`` settings are lost. Function calls cause exceptions if the called contract does not exist (in the sense that the account does not contain code) or if the called contract itself @@ -216,8 +226,11 @@ Assignment Destructuring Assignments and Returning Multiple Values ------------------------------------------------------- -Solidity internally allows tuple types, i.e. a list of objects of potentially different types whose number is a constant at compile-time. Those tuples can be used to return multiple values at the same time. -These can then either be assigned to newly declared variables or to pre-existing variables (or LValues in general). +Solidity internally allows tuple types, i.e. a list of objects +of potentially different types whose number is a constant at +compile-time. Those tuples can be used to return multiple values at the same time. +These can then either be assigned to newly declared variables +or to pre-existing variables (or LValues in general). Tuples are not proper types in Solidity, they can only be used to form syntactic groupings of expressions. @@ -227,7 +240,7 @@ groupings of expressions. pragma solidity >0.4.23 <0.7.0; contract C { - uint[] data; + uint index; function f() public pure returns (uint, bool, uint) { return (7, true, 2); @@ -240,7 +253,7 @@ groupings of expressions. // Common trick to swap values -- does not work for non-value storage types. (x, y) = (y, x); // Components can be left out (also for variable declarations). - (data.length, , ) = f(); // Sets the length to 7 + (index, , ) = f(); // Sets the index to 7 } } @@ -260,8 +273,18 @@ i.e. the following is not valid: ``(x, uint y) = (1, 2);`` Complications for Arrays and Structs ------------------------------------ -The semantics of assignments are a bit more complicated for non-value types like arrays and structs. -Assigning *to* a state variable always creates an independent copy. On the other hand, assigning to a local variable creates an independent copy only for elementary types, i.e. static types that fit into 32 bytes. If structs or arrays (including ``bytes`` and ``string``) are assigned from a state variable to a local variable, the local variable holds a reference to the original state variable. A second assignment to the local variable does not modify the state but only changes the reference. Assignments to members (or elements) of the local variable *do* change the state. +The semantics of assignments are a bit more complicated for +non-value types like arrays and structs. +Assigning *to* a state variable always creates an independent +copy. On the other hand, assigning to a local variable creates +an independent copy only for elementary types, i.e. static +types that fit into 32 bytes. If structs or arrays (including +``bytes`` and ``string``) are assigned from a state variable +to a local variable, the local variable holds a reference to +the original state variable. A second assignment to the local +variable does not modify the state but only changes the +reference. Assignments to members (or elements) of the local +variable *do* change the state. In the example below the call to ``g(x)`` has no effect on ``x`` because it creates an independent copy of the storage value in memory. However, ``h(x)`` successfully modifies ``x`` @@ -295,17 +318,29 @@ because only a reference and not a copy is passed. Scoping and Declarations ======================== -A variable which is declared will have an initial default value whose byte-representation is all zeros. -The "default values" of variables are the typical "zero-state" of whatever the type is. For example, the default value for a ``bool`` -is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For statically-sized arrays and ``bytes1`` to ``bytes32``, each individual -element will be initialized to the default value corresponding to its type. For dynamically-sized arrays, ``bytes`` -and ``string``, the default value is an empty array or string. For the ``enum`` type, the default value is its first member. +A variable which is declared will have an initial default +value whose byte-representation is all zeros. +The "default values" of variables are the typical "zero-state" +of whatever the type is. For example, the default value for a ``bool`` +is ``false``. The default value for the ``uint`` or ``int`` +types is ``0``. For statically-sized arrays and ``bytes1`` to +``bytes32``, each individual +element will be initialized to the default value corresponding +to its type. For dynamically-sized arrays, ``bytes`` +and ``string``, the default value is an empty array or string. +For the ``enum`` type, the default value is its first member. Scoping in Solidity follows the widespread scoping rules of C99 (and many other languages): Variables are visible from the point right after their declaration -until the end of the smallest ``{ }``-block that contains the declaration. As an exception to this rule, variables declared in the +until the end of the smallest ``{ }``-block that contains the declaration. +As an exception to this rule, variables declared in the initialization part of a for-loop are only visible until the end of the for-loop. +Variables that are parameter-like (function parameters, modifier parameters, +catch parameters, ...) are visible inside the code block that follows - +the body of the function/modifier for a function and modifier parameter and the catch block +for a catch parameter. + Variables and other items declared outside of a code block, for example functions, contracts, user-defined types, etc., are visible even before they were declared. This means you can use state variables before they are declared and call functions recursively. @@ -350,7 +385,8 @@ In any case, you will get a warning about the outer variable being shadowed. } .. warning:: - Before version 0.5.0 Solidity followed the same scoping rules as JavaScript, that is, a variable declared anywhere within a function would be in scope + Before version 0.5.0 Solidity followed the same scoping rules as + JavaScript, that is, a variable declared anywhere within a function would be in scope for the entire function, regardless where it was declared. The following example shows a code snippet that used to compile but leads to an error starting from version 0.5.0. @@ -373,17 +409,24 @@ In any case, you will get a warning about the outer variable being shadowed. Error handling: Assert, Require, Revert and Exceptions ====================================================== -Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the -state in the current call (and all its sub-calls) and flags an error to the caller. +Solidity uses state-reverting exceptions to handle errors. +Such an exception undoes all changes made to the +state in the current call (and all its sub-calls) and +flags an error to the caller. -When exceptions happen in a sub-call, they "bubble up" (i.e., exceptions are rethrown) automatically. Exceptions to this rule are ``send`` -and the low-level functions ``call``, ``delegatecall`` and ``staticcall``, they return ``false`` as their first return value in case +When exceptions happen in a sub-call, they "bubble up" (i.e., +exceptions are rethrown) automatically. Exceptions to this rule are ``send`` +and the low-level functions ``call``, ``delegatecall`` and +``staticcall``: they return ``false`` as their first return value in case of an exception instead of "bubbling up". .. warning:: - The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the account called is non-existent, as part of the design of EVM. Existence must be checked prior to calling if needed. + The low-level functions ``call``, ``delegatecall`` and + ``staticcall`` return ``true`` as their first return value + if the account called is non-existent, as part of the design + of the EVM. Account existence must be checked prior to calling if needed. -It is not yet possible to catch exceptions with Solidity. +Exceptions can be caught with the ``try``/``catch`` statement. ``assert`` and ``require`` -------------------------- @@ -391,11 +434,16 @@ It is not yet possible to catch exceptions with Solidity. The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception if the condition is not met. -The ``assert`` function should only be used to test for internal errors, and to check invariants. Properly functioning code should never reach a failing ``assert`` statement; if this happens there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. +The ``assert`` function should only be used to test for internal +errors, and to check invariants. Properly functioning code should +never reach a failing ``assert`` statement; if this happens there +is a bug in your contract which you should fix. Language analysis +tools can evaluate your contract to identify the conditions and +function calls which will reach a failing ``assert``. An ``assert``-style exception is generated in the following situations: -#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). +#. If you access an array or an array slice at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). #. If you access a fixed-length ``bytesN`` at a too large or negative index. #. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). #. If you shift by a negative amount. @@ -403,16 +451,25 @@ An ``assert``-style exception is generated in the following situations: #. If you call a zero-initialized variable of internal function type. #. If you call ``assert`` with an argument that evaluates to false. -The ``require`` function should be used to ensure valid conditions that cannot be detected until execution time. -These conditions include inputs, or contract state variables are met, or to validate return values from calls to external contracts. +The ``require`` function should be used to ensure valid conditions +that cannot be detected until execution time. +This includes conditions on inputs +or return values from calls to external contracts. A ``require``-style exception is generated in the following situations: #. Calling ``require`` with an argument that evaluates to ``false``. -#. If you call a function via a message call but it does not finish properly (i.e., it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. -#. If you create a contract using the ``new`` keyword but the contract creation :ref:`does not finish properly`. +#. If you call a function via a message call but it does not finish + properly (i.e., it runs out of gas, has no matching function, or + throws an exception itself), except when a low level operation + ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` + is used. The low level operations never throw exceptions but + indicate failures by returning ``false``. +#. If you create a contract using the ``new`` keyword but the contract + creation :ref:`does not finish properly`. #. If you perform an external function call targeting a contract that contains no code. -#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). +#. If your contract receives Ether via a public function without + ``payable`` modifier (including the constructor and the fallback function). #. If your contract receives Ether via a public getter function. #. If a ``.transfer()`` fails. @@ -438,21 +495,30 @@ and ``assert`` for internal error checking. } } -Internally, Solidity performs a revert operation (instruction ``0xfd``) for a ``require``-style exception and executes an invalid operation +Internally, Solidity performs a revert operation (instruction +``0xfd``) for a ``require``-style exception and executes an invalid operation (instruction ``0xfe``) to throw an ``assert``-style exception. In both cases, this causes -the EVM to revert all changes made to the state. The reason for reverting is that there is no safe way to continue execution, because an expected effect -did not occur. Because we want to keep the atomicity of transactions, the safest action is to revert all changes and make the whole transaction +the EVM to revert all changes made to the state. The reason for reverting +is that there is no safe way to continue execution, because an expected effect +did not occur. Because we want to keep the atomicity of transactions, the +safest action is to revert all changes and make the whole transaction (or at least call) without effect. +In both cases, the caller can react on such failures using ``try``/``catch`` +(in the failing ``assert``-style exception only if enough gas is left), but +the changes in the caller will always be reverted. + .. note:: - ``assert``-style exceptions consume all gas available to the call, while ``require``-style exceptions do not consume any gas starting from the Metropolis release. + ``assert``-style exceptions consume all gas available to the call, + while ``require``-style exceptions do not consume any gas starting from the Metropolis release. ``revert`` ---------- The ``revert`` function is another way to trigger exceptions from within other code blocks to flag an error and -revert the current call. The function takes an optional string message containing details about the error that is passed back to the caller. +revert the current call. The function takes an optional string +message containing details about the error that is passed back to the caller. The following example shows how to use an error string together with ``revert`` and the equivalent ``require``: @@ -485,6 +551,99 @@ In the above example, ``revert("Not enough Ether provided.");`` returns the foll 0x000000000000000000000000000000000000000000000000000000000000001a // String length 0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data +The provided message can be retrieved by the caller using ``try``/``catch`` as shown below. + .. note:: There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which was deprecated in version 0.4.13 and removed in version 0.5.0. + + +.. _try-catch: + +``try``/``catch`` +----------------- + +A failure in an external call can be caught using a try/catch statement, as follows: + +:: + + pragma solidity ^0.6.0; + + interface DataFeed { function getData(address token) external returns (uint value); } + + contract FeedConsumer { + DataFeed feed; + uint errorCount; + function rate(address token) public returns (uint value, bool success) { + // Permanently disable the mechanism if there are + // more than 10 errors. + require(errorCount < 10); + try feed.getData(token) returns (uint v) { + return (v, true); + } catch Error(string memory /*reason*/) { + // This is executed in case + // revert was called inside getData + // and a reason string was provided. + errorCount++; + return (0, false); + } catch (bytes memory /*lowLevelData*/) { + // This is executed in case revert() was used + // or there was a failing assertion, division + // by zero, etc. inside getData. + errorCount++; + return (0, false); + } + } + } + +The ``try`` keyword has to be followed by an expression representing an external function call +or a contract creation (``new ContractName()``). +Errors inside the expression are not caught (for example if it is a complex expression +that also involves internal function calls), only a revert happening inside the external +call itself. The ``returns`` part (which is optional) that follows declares return variables +matching the types returned by the external call. In case there was no error, +these variables are assigned and the contract's execution continues inside the +first success block. If the end of the success block is reached, execution continues after the ``catch`` blocks. + +Currently, Solidity supports different kinds of catch blocks depending on the +type of error. If the error was caused by ``revert("reasonString")`` or +``require(false, "reasonString")`` (or an internal error that causes such an +exception), then the catch clause +of the type ``catch Error(string memory reason)`` will be executed. + +It is planned to support other types of error data in the future. +The string ``Error`` is currently parsed as is and is not treated as an identifier. + +The clause ``catch (bytes memory lowLevelData)`` is executed if the error signature +does not match any other clause, there was an error during decoding of the error +message, if there was a failing assertion in the external +call (for example due to a division by zero or a failing ``assert()``) or +if no error data was provided with the exception. +The declared variable provides access to the low-level error data in that case. + +If you are not interested in the error data, you can just use +``catch { ... }`` (even as the only catch clause). + +In order to catch all error cases, you have to have at least the clause +``catch { ...}`` or the clause ``catch (bytes memory lowLevelData) { ... }``. + +The variables declared in the ``returns`` and the ``catch`` clause are only +in scope in the block that follows. + +.. note:: + + If an error happens during the decoding of the return data + inside a try/catch-statement, this causes an exception in the currently + executing contract and because of that, it is not caught in the catch clause. + If there is an error during decoding of ``catch Error(string memory reason)`` + and there is a low-level catch clause, this error is caught there. + +.. note:: + + If execution reaches a catch-block, then the state-changing effects of + the external call have been reverted. If execution reaches + the success block, the effects were not reverted. + If the effects have been reverted, then execution either continues + in a catch block or the execution of the try/catch statement itself + reverts (for example due to decoding failures as noted above or + due to not providing a low-level catch clause). diff --git a/docs/examples/blind-auction.rst b/docs/examples/blind-auction.rst index 0b13621ec9a5..92a7559738f8 100644 --- a/docs/examples/blind-auction.rst +++ b/docs/examples/blind-auction.rst @@ -4,12 +4,10 @@ Blind Auction ************* -In this section, we will show how easy it is to create a -completely blind auction contract on Ethereum. -We will start with an open auction where everyone -can see the bids that are made and then extend this -contract into a blind auction where it is not -possible to see the actual bid until the bidding +In this section, we will show how easy it is to create a completely blind +auction contract on Ethereum. We will start with an open auction where +everyone can see the bids that are made and then extend this contract into a +blind auction where it is not possible to see the actual bid until the bidding period ends. .. _simple_auction: @@ -17,16 +15,12 @@ period ends. Simple Open Auction =================== -The general idea of the following simple auction contract -is that everyone can send their bids during -a bidding period. The bids already include sending -money / ether in order to bind the bidders to their -bid. If the highest bid is raised, the previously -highest bidder gets her money back. -After the end of the bidding period, the -contract has to be called manually for the -beneficiary to receive their money - contracts cannot -activate themselves. +The general idea of the following simple auction contract is that everyone can +send their bids during a bidding period. The bids already include sending money +/ Ether in order to bind the bidders to their bid. If the highest bid is +raised, the previously highest bidder gets their money back. After the end of +the bidding period, the contract has to be called manually for the beneficiary +to receive their money - contracts cannot activate themselves. :: @@ -89,7 +83,10 @@ activate themselves. ); // If the bid is not higher, send the - // money back. + // money back (the failing require + // will revert all changes in this + // function execution including + // it having received the money). require( msg.value > highestBid, "There already is a higher bid." @@ -158,38 +155,31 @@ activate themselves. Blind Auction ============= -The previous open auction is extended to a blind auction -in the following. The advantage of a blind auction is -that there is no time pressure towards the end of -the bidding period. Creating a blind auction on a -transparent computing platform might sound like a -contradiction, but cryptography comes to the rescue. - -During the **bidding period**, a bidder does not -actually send her bid, but only a hashed version of it. -Since it is currently considered practically impossible -to find two (sufficiently long) values whose hash -values are equal, the bidder commits to the bid by that. -After the end of the bidding period, the bidders have -to reveal their bids: They send their values -unencrypted and the contract checks that the hash value -is the same as the one provided during the bidding period. - -Another challenge is how to make the auction -**binding and blind** at the same time: The only way to -prevent the bidder from just not sending the money -after they won the auction is to make her send it -together with the bid. Since value transfers cannot -be blinded in Ethereum, anyone can see the value. - -The following contract solves this problem by -accepting any value that is larger than the highest -bid. Since this can of course only be checked during -the reveal phase, some bids might be **invalid**, and -this is on purpose (it even provides an explicit -flag to place invalid bids with high value transfers): -Bidders can confuse competition by placing several -high or low invalid bids. +The previous open auction is extended to a blind auction in the following. The +advantage of a blind auction is that there is no time pressure towards the end +of the bidding period. Creating a blind auction on a transparent computing +platform might sound like a contradiction, but cryptography comes to the +rescue. + +During the **bidding period**, a bidder does not actually send their bid, but +only a hashed version of it. Since it is currently considered practically +impossible to find two (sufficiently long) values whose hash values are equal, +the bidder commits to the bid by that. After the end of the bidding period, +the bidders have to reveal their bids: They send their values unencrypted and +the contract checks that the hash value is the same as the one provided during +the bidding period. + +Another challenge is how to make the auction **binding and blind** at the same +time: The only way to prevent the bidder from just not sending the money after +they won the auction is to make them send it together with the bid. Since value +transfers cannot be blinded in Ethereum, anyone can see the value. + +The following contract solves this problem by accepting any value that is +larger than the highest bid. Since this can of course only be checked during +the reveal phase, some bids might be **invalid**, and this is on purpose (it +even provides an explicit flag to place invalid bids with high value +transfers): Bidders can confuse competition by placing several high or low +invalid bids. :: @@ -293,24 +283,6 @@ high or low invalid bids. msg.sender.transfer(refund); } - // This is an "internal" function which means that it - // can only be called from the contract itself (or from - // derived contracts). - function placeBid(address bidder, uint value) internal - returns (bool success) - { - if (value <= highestBid) { - return false; - } - if (highestBidder != address(0)) { - // Refund the previously highest bidder. - pendingReturns[highestBidder] += highestBid; - } - highestBid = value; - highestBidder = bidder; - return true; - } - /// Withdraw a bid that was overbid. function withdraw() public { uint amount = pendingReturns[msg.sender]; @@ -336,4 +308,22 @@ high or low invalid bids. ended = true; beneficiary.transfer(highestBid); } + + // This is an "internal" function which means that it + // can only be called from the contract itself (or from + // derived contracts). + function placeBid(address bidder, uint value) internal + returns (bool success) + { + if (value <= highestBid) { + return false; + } + if (highestBidder != address(0)) { + // Refund the previously highest bidder. + pendingReturns[highestBidder] += highestBid; + } + highestBid = value; + highestBidder = bidder; + return true; + } } \ No newline at end of file diff --git a/docs/examples/micropayment.rst b/docs/examples/micropayment.rst index a428463bd8b5..f675586ddfec 100644 --- a/docs/examples/micropayment.rst +++ b/docs/examples/micropayment.rst @@ -33,8 +33,11 @@ The contract will work as follows: Creating the signature ---------------------- -Alice does not need to interact with the Ethereum network to sign the transaction, the process is completely offline. -In this tutorial, we will sign messages in the browser using `web3.js `_ and `MetaMask `_, using the method described in `EIP-762 `_, +Alice does not need to interact with the Ethereum network +to sign the transaction, the process is completely offline. +In this tutorial, we will sign messages in the browser +using `web3.js `_ and +`MetaMask `_, using the method described in `EIP-762 `_, as it provides a number of other security benefits. :: @@ -44,7 +47,10 @@ as it provides a number of other security benefits. web3.eth.personal.sign(hash, web3.eth.defaultAccount, function () { console.log("Signed"); }); .. note:: - The ``web3.eth.personal.sign`` prepends the length of the message to the signed data. Since we hash first, the message will always be exactly 32 bytes long, and thus this length prefix is always the same. + The ``web3.eth.personal.sign`` prepends the length of the + message to the signed data. Since we hash first, the message + will always be exactly 32 bytes long, and thus this length + prefix is always the same. What to Sign ------------ @@ -62,9 +68,18 @@ themselves, a so-called nonce, which is the number of transactions sent by an account. The smart contract checks if a nonce is used multiple times. -Another type of replay attack can occur when the owner deploys a ``ReceiverPays`` smart contract, makes some payments, and then destroys the contract. Later, they decide to deploy the ``RecipientPays`` smart contract again, but the new contract does not know the nonces used in the previous deployment, so the attacker can use the old messages again. +Another type of replay attack can occur when the owner +deploys a ``ReceiverPays`` smart contract, makes some +payments, and then destroys the contract. Later, they decide +to deploy the ``RecipientPays`` smart contract again, but the +new contract does not know the nonces used in the previous +deployment, so the attacker can use the old messages again. -Alice can protect against this attack by including the contract's address in the message, and only messages containing the contract's address itself will be accepted. You can find an example of this in the first two lines of the ``claimPayment()`` function of the full contract at the end of this section. +Alice can protect against this attack by including the +contract's address in the message, and only messages containing +the contract's address itself will be accepted. You can find +an example of this in the first two lines of the ``claimPayment()`` +function of the full contract at the end of this section. Packing arguments ----------------- @@ -94,12 +109,26 @@ Here is a JavaScript function that creates the proper signature for the ``Receiv Recovering the Message Signer in Solidity ----------------------------------------- -In general, ECDSA signatures consist of two parameters, ``r`` and ``s``. Signatures in Ethereum include a third parameter called ``v``, that you can use to verify which account's private key was used to sign the message, and the transaction's sender. Solidity provides a built-in function `ecrecover `_ that accepts a message along with the ``r``, ``s`` and ``v`` parameters and returns the address that was used to sign the message. +In general, ECDSA signatures consist of two parameters, +``r`` and ``s``. Signatures in Ethereum include a third +parameter called ``v``, that you can use to verify which +account's private key was used to sign the message, and +the transaction's sender. Solidity provides a built-in +function `ecrecover `_ that +accepts a message along with the ``r``, ``s`` and ``v`` parameters +and returns the address that was used to sign the message. Extracting the Signature Parameters ----------------------------------- -Signatures produced by web3.js are the concatenation of ``r``, ``s`` and ``v``, so the first step is to split these parameters apart. You can do this on the client-side, but doing it inside the smart contract means you only need to send one signature parameter rather than three. Splitting apart a byte array into component parts is a mess, so we use `inline assembly `_ to do the job in the ``splitSignature`` function (the third function in the full contract at the end of this section). +Signatures produced by web3.js are the concatenation of ``r``, +``s`` and ``v``, so the first step is to split these parameters +apart. You can do this on the client-side, but doing it inside +the smart contract means you only need to send one signature +parameter rather than three. Splitting apart a byte array into +its constituent parts is a mess, so we use +`inline assembly `_ to do the job in the ``splitSignature`` +function (the third function in the full contract at the end of this section). Computing the Message Hash -------------------------- @@ -180,26 +209,45 @@ The full contract Writing a Simple Payment Channel ================================ -Alice now builds a simple but complete implementation of a payment channel. Payment channels use cryptographic signatures to make repeated transfers of Ether securely, instantaneously, and without transaction fees. +Alice now builds a simple but complete implementation of a payment +channel. Payment channels use cryptographic signatures to make +repeated transfers of Ether securely, instantaneously, and without transaction fees. What is a Payment Channel? -------------------------- -Payment channels allow participants to make repeated transfers of Ether without using transactions. This means that you can avoid the delays and fees associated with transactions. We are going to explore a simple unidirectional payment channel between two parties (Alice and Bob). It involves three steps: +Payment channels allow participants to make repeated transfers of Ether +without using transactions. This means that you can avoid the delays and +fees associated with transactions. We are going to explore a simple +unidirectional payment channel between two parties (Alice and Bob). It involves three steps: 1. Alice funds a smart contract with Ether. This "opens" the payment channel. 2. Alice signs messages that specify how much of that Ether is owed to the recipient. This step is repeated for each payment. 3. Bob "closes" the payment channel, withdrawing their portion of the Ether and sending the remainder back to the sender. .. note:: - Only steps 1 and 3 require Ethereum transactions, step 2 means that the sender transmits a cryptographically signed message to the recipient via off chain methods (e.g. email). This means only two transactions are required to support any number of transfers. - -Bob is guaranteed to receive their funds because the smart contract escrows the Ether and honours a valid signed message. The smart contract also enforces a timeout, so Alice is guaranteed to eventually recover their funds even if the recipient refuses to close the channel. It is up to the participants in a payment channel to decide how long to keep it open. For a short-lived transaction, such as paying an internet café for each minute of network access, or for a longer relationship, such as paying an employee an hourly wage, a payment could last for months or years. + Only steps 1 and 3 require Ethereum transactions, step 2 means that the sender + transmits a cryptographically signed message to the recipient via off chain + methods (e.g. email). This means only two transactions are required to support + any number of transfers. + +Bob is guaranteed to receive their funds because the smart contract escrows the +Ether and honours a valid signed message. The smart contract also enforces a +timeout, so Alice is guaranteed to eventually recover their funds even if the +recipient refuses to close the channel. It is up to the participants in a payment +channel to decide how long to keep it open. For a short-lived transaction, +such as paying an internet café for each minute of network access, the payment +channel may be kept open for a limited duration. On the other hand, for a +recurring payment, such as paying an employee an hourly wage, the payment channel +may be kept open for several months or years. Opening the Payment Channel --------------------------- -To open the payment channel, Alice deploys the smart contract, attaching the Ether to be escrowed and specifying the intended recipient and a maximum duration for the channel to exist. This is the function ``SimplePaymentChannel`` in the contract, at the end of this section. +To open the payment channel, Alice deploys the smart contract, attaching +the Ether to be escrowed and specifying the intended recipient and a +maximum duration for the channel to exist. This is the function +``SimplePaymentChannel`` in the contract, at the end of this section. Making Payments --------------- @@ -218,7 +266,8 @@ Because of this, only one of the messages sent is redeemed. This is why each message specifies a cumulative total amount of Ether owed, rather than the amount of the individual micropayment. The recipient will naturally choose to redeem the most recent message because that is the one with the highest total. -The nonce per-message is not needed anymore, because the smart contract only honors a single message. The address of the smart contract is still used +The nonce per-message is not needed anymore, because the smart contract only +honours a single message. The address of the smart contract is still used to prevent a message intended for one payment channel from being used for a different channel. Here is the modified JavaScript code to cryptographically sign a message from the previous section: @@ -254,7 +303,9 @@ Closing the Payment Channel When Bob is ready to receive their funds, it is time to close the payment channel by calling a ``close`` function on the smart contract. -Closing the channel pays the recipient the Ether they are owed and destroys the contract, sending any remaining Ether back to Alice. To close the channel, Bob needs to provide a message signed by Alice. +Closing the channel pays the recipient the Ether they are owed and +destroys the contract, sending any remaining Ether back to Alice. To +close the channel, Bob needs to provide a message signed by Alice. The smart contract must verify that the message contains a valid signature from the sender. The process for doing this verification is the same as the process the recipient uses. @@ -303,17 +354,6 @@ The full contract expiration = now + duration; } - function isValidSignature(uint256 amount, bytes memory signature) - internal - view - returns (bool) - { - bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount))); - - // check that the signature is from the payment sender - return recoverSigner(message, signature) == sender; - } - /// the recipient can close the channel at any time by presenting a /// signed amount from the sender. the recipient will be sent that amount, /// and the remainder will go back to the sender @@ -340,6 +380,17 @@ The full contract selfdestruct(sender); } + function isValidSignature(uint256 amount, bytes memory signature) + internal + view + returns (bool) + { + bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount))); + + // check that the signature is from the payment sender + return recoverSigner(message, signature) == sender; + } + /// All functions below this are just taken from the chapter /// 'creating and verifying signatures' chapter. diff --git a/docs/examples/modular.rst b/docs/examples/modular.rst index 928d473bcfd6..a3d932b099e6 100644 --- a/docs/examples/modular.rst +++ b/docs/examples/modular.rst @@ -38,9 +38,6 @@ and the sum of all balances is an invariant across the lifetime of the contract. event Transfer(address from, address to, uint amount); event Approval(address owner, address spender, uint amount); - function balanceOf(address tokenOwner) public view returns (uint balance) { - return balances[tokenOwner]; - } function transfer(address to, uint amount) public returns (bool success) { balances.move(msg.sender, to, amount); emit Transfer(msg.sender, to, amount); @@ -62,4 +59,8 @@ and the sum of all balances is an invariant across the lifetime of the contract. emit Approval(msg.sender, spender, tokens); return true; } + + function balanceOf(address tokenOwner) public view returns (uint balance) { + return balances[tokenOwner]; + } } diff --git a/docs/examples/safe-remote.rst b/docs/examples/safe-remote.rst index 9ba3c4c85580..d79c6526e36f 100644 --- a/docs/examples/safe-remote.rst +++ b/docs/examples/safe-remote.rst @@ -31,19 +31,11 @@ you can use state machine-like constructs inside a contract. uint public value; address payable public seller; address payable public buyer; + enum State { Created, Locked, Release, Inactive } // The state variable has a default value of the first member, `State.created` State public state; - // Ensure that `msg.value` is an even number. - // Division will truncate if it is an odd number. - // Check via multiplication that it wasn't an odd number. - constructor() public payable { - seller = msg.sender; - value = msg.value / 2; - require((2 * value) == msg.value, "Value has to be even."); - } - modifier condition(bool _condition) { require(_condition); _; @@ -78,6 +70,15 @@ you can use state machine-like constructs inside a contract. event ItemReceived(); event SellerRefunded(); + // 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. + constructor() public payable { + seller = msg.sender; + value = msg.value / 2; + require((2 * value) == msg.value, "Value has to be even."); + } + /// Abort the purchase and reclaim the ether. /// Can only be called by the seller before /// the contract is locked. diff --git a/docs/grammar.txt b/docs/grammar.txt index cf0ddd1046c7..f70ec1342ea5 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -7,7 +7,7 @@ ImportDirective = 'import' StringLiteral ('as' Identifier)? ';' | 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';' | 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';' -ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier +ContractDefinition = 'abstract'? ( 'contract' | 'library' | 'interface' ) Identifier ( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )? '{' ContractPart* '}' @@ -16,17 +16,20 @@ ContractPart = StateVariableDeclaration | UsingForDeclaration InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )? -StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )* Identifier ('=' Expression)? ';' +StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' | OverrideSpecifier )* Identifier ('=' Expression)? ';' UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';' StructDefinition = 'struct' Identifier '{' ( VariableDeclaration ';' (VariableDeclaration ';')* ) '}' -ModifierDefinition = 'modifier' Identifier ParameterList? Block +ModifierDefinition = 'modifier' Identifier ParameterList? ( 'virtual' | OverrideSpecifier )* Block ModifierInvocation = Identifier ( '(' ExpressionList? ')' )? FunctionDefinition = 'function' Identifier? ParameterList - ( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' )* + ( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' | 'virtual' | OverrideSpecifier )* ( 'returns' ParameterList )? ( ';' | Block ) + +OverrideSpecifier = 'override' ( '(' UserDefinedTypeName (',' UserDefinedTypeName)* ')' )? + EventDefinition = 'event' Identifier EventParameterList 'anonymous'? ';' EnumValue = Identifier @@ -62,12 +65,14 @@ StorageLocation = 'memory' | 'storage' | 'calldata' StateMutability = 'pure' | 'view' | 'payable' Block = '{' Statement* '}' -Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement | +Statement = IfStatement | TryStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement | ( DoWhileStatement | PlaceholderStatement | Continue | Break | Return | Throw | EmitStatement | SimpleStatement ) ';' ExpressionStatement = Expression IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? +TryStatement = 'try' Expression ( 'returns' ParameterList )? Block CatchClause+ +CatchClause = 'catch' Identifier? ParameterList Block WhileStatement = 'while' '(' Expression ')' Statement PlaceholderStatement = '_' SimpleStatement = VariableDefinition | ExpressionStatement @@ -86,6 +91,7 @@ Expression = Expression ('++' | '--') | NewExpression | IndexAccess + | IndexRangeAccess | MemberAccess | FunctionCall | '(' Expression ')' @@ -123,6 +129,7 @@ FunctionCallArguments = '{' NameValueList? '}' NewExpression = 'new' TypeName MemberAccess = Expression '.' Identifier IndexAccess = Expression '[' Expression? ']' +IndexRangeAccess = Expression '[' Expression? ':' Expression? ']' BooleanLiteral = 'true' | 'false' NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)? @@ -164,6 +171,7 @@ AssemblyStatement = AssemblyBlock | AssemblySwitch | AssemblyForLoop | AssemblyBreakContinue + | AssemblyLeave AssemblyFunctionDefinition = 'function' Identifier '(' AssemblyIdentifierList? ')' ( '->' AssemblyIdentifierList )? AssemblyBlock @@ -176,6 +184,7 @@ AssemblyCase = 'case' Literal AssemblyBlock AssemblyDefault = 'default' AssemblyBlock AssemblyForLoop = 'for' AssemblyBlock AssemblyExpression AssemblyBlock AssemblyBlock AssemblyBreakContinue = 'break' | 'continue' +AssemblyLeave = 'leave' AssemblyFunctionCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')' AssemblyIdentifierList = Identifier ( ',' Identifier )* diff --git a/docs/index.rst b/docs/index.rst index 15dea172d9f7..19924301829d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,11 +19,15 @@ user-defined types among other features. With Solidity you can create contracts for uses such as voting, crowdfunding, blind auctions, and multi-signature wallets. -When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number `to indicate this fast pace of change `_. +When deploying contracts, you should use the latest released +version of Solidity. This is because breaking changes as well as +new features and bug fixes are introduced regularly. We currently use +a 0.x version number `to indicate this fast pace of change `_. .. warning:: - Solidity recently released the 0.5.x version that introduced a lot of breaking changes. Make sure you read :doc:`the full list <050-breaking-changes>`. + Solidity recently released the 0.6.x version that introduced a lot of breaking + changes. Make sure you read :doc:`the full list <060-breaking-changes>`. Language Documentation ---------------------- @@ -31,7 +35,8 @@ Language Documentation If you are new to the concept of smart contracts we recommend you start with :ref:`an example smart contract ` written in Solidity. When you are ready for more detail, we recommend you read the -:doc:`"Solidity by Example" ` and :doc:`"Solidity in Depth" ` sections to learn the core concepts of the language. +:doc:`"Solidity by Example" ` and +:doc:`"Solidity in Depth" ` sections to learn the core concepts of the language. For further reading, try :ref:`the basics of blockchains ` and details of the :ref:`Ethereum Virtual Machine `. @@ -52,9 +57,11 @@ and details of the :ref:`Ethereum Virtual Machine :ref:`security_considerations` section. If you have any questions, you can try searching for answers or asking on the -`Ethereum Stackexchange `_, or our `gitter channel `_. +`Ethereum Stackexchange `_, or +our `gitter channel `_. -Ideas for improving Solidity or this documentation are always welcome, read our :doc:`contributors guide ` for more details. +Ideas for improving Solidity or this documentation are always welcome, +read our :doc:`contributors guide ` for more details. .. _translations: diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index ade678087b52..7211610820f6 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -116,7 +116,9 @@ The nightly version can be installed using these commands: sudo apt-get update sudo apt-get install solc -We are also releasing a `snap package `_, which is installable in all the `supported Linux distros `_. To install the latest stable version of solc: +We are also releasing a `snap package `_, which is +installable in all the `supported Linux distros `_. To +install the latest stable version of solc: .. code-block:: bash @@ -129,6 +131,12 @@ with the most recent changes, please use the following: sudo snap install solc --edge +.. note:: + + The ``solc`` snap uses strict confinement. This is the most secure mode for snap packages + but it comes with limitations, like accessing only the files in your ``/home`` and ``/media`` directories. + For more information, go to `Demystifying Snap Confinement `_. + Arch Linux also has packages, albeit limited to the latest development version: .. code-block:: bash @@ -146,7 +154,8 @@ currently not supported. brew tap ethereum/ethereum brew install solidity -To install the most recent 0.4.x version of Solidity you can also use ``brew install solidity@4``. +To install the most recent 0.4.x / 0.5.x version of Solidity you can also use ``brew install solidity@4`` +and ``brew install solidity@5``, respectively. If you need a specific version of Solidity you can install a Homebrew formula directly from Github. diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index f59200b24606..fada5d2916af 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -42,7 +42,8 @@ data (its *state*) that resides at a specific address on the Ethereum blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of type ``uint`` (*u*\nsigned *int*\eger of *256* bits). You can think of it as a single slot in a database that you can query and alter by calling functions of the -code that manages the database. In this example, the contract defines the functions ``set`` and ``get`` that can be used to modify +code that manages the database. In this example, the contract defines the +functions ``set`` and ``get`` that can be used to modify or retrieve the value of the variable. To access a state variable, you do not need the prefix ``this.`` as is common in @@ -70,7 +71,7 @@ Subcurrency Example =================== The following contract implements the simplest form of a -cryptocurrency. The contract allows only its creator to create new coins (different issuance scheme are possible). +cryptocurrency. The contract allows only its creator to create new coins (different issuance schemes are possible). Anyone can send coins to each other without a need for registering with a username and password, all you need is an Ethereum keypair. @@ -116,7 +117,8 @@ This contract introduces some new concepts, let us go through them one by one. The line ``address public minter;`` declares a state variable of type :ref:`address
`. The ``address`` type is a 160-bit value that does not allow any arithmetic operations. -It is suitable for storing addresses of contracts, or a hash of the public half of a keypair belonging to :ref:`external accounts`. +It is suitable for storing addresses of contracts, or a hash of the public half +of a keypair belonging to :ref:`external accounts`. The keyword ``public`` automatically generates a function that allows you to access the current value of the state variable from outside of the contract. Without this keyword, other contracts have no way to access the variable. @@ -190,7 +192,7 @@ The functions that make up the contract, and that users and contracts can call a The ``mint`` function sends an amount of newly created coins to another address. The :ref:`require ` function call defines conditions that reverts all changes if not met. In this example, ``require(msg.sender == minter);`` ensures that only the creator of the contract can call ``mint``, -and ``require(amount < 1e60);`` ensures a maximum amount of tokens, without which could cause overflow errors in the future. +and ``require(amount < 1e60);`` ensures a maximum amount of tokens. This ensures that there are no overflow errors in the future. The ``send`` function can be used by anyone (who already has some of these coins) to send coins to anyone else. If the sender does not have @@ -214,7 +216,9 @@ Blockchain Basics ***************** Blockchains as a concept are not too hard to understand for programmers. The reason is that -most of the complications (mining, `hashing `_, `elliptic-curve cryptography `_, `peer-to-peer networks `_, etc.) +most of the complications (mining, `hashing `_, +`elliptic-curve cryptography `_, +`peer-to-peer networks `_, etc.) are just there to provide a certain set of features and promises for the platform. Once you accept these features as given, you do not have to worry about the underlying technology - or do you have to know how Amazon's AWS works internally in order to use it? @@ -508,12 +512,22 @@ receives the address of the new contract on the stack. Deactivate and Self-destruct ============================ -The only way to remove code from the blockchain is when a contract at that address performs the ``selfdestruct`` operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed from the state. Removing the contract in theory sounds like a good idea, but it is potentially dangerous, as if someone sends Ether to removed contracts, the Ether is forever lost. +The only way to remove code from the blockchain is when a contract at that +address performs the ``selfdestruct`` operation. The remaining Ether stored +at that address is sent to a designated target and then the storage and code +is removed from the state. Removing the contract in theory sounds like a good +idea, but it is potentially dangerous, as if someone sends Ether to removed +contracts, the Ether is forever lost. .. warning:: - Even if a contract is removed by "selfdestruct", it is still part of the history of the blockchain and probably retained by most Ethereum nodes. So using "selfdestruct" is not the same as deleting data from a hard disk. + Even if a contract is removed by "selfdestruct", it is still part of the + history of the blockchain and probably retained by most Ethereum nodes. + So using "selfdestruct" is not the same as deleting data from a hard disk. .. note:: - Even if a contract's code does not contain a call to ``selfdestruct``, it can still perform that operation using ``delegatecall`` or ``callcode``. + Even if a contract's code does not contain a call to ``selfdestruct``, + it can still perform that operation using ``delegatecall`` or ``callcode``. -If you want to deactivate your contracts, you should instead **disable** them by changing some internal state which causes all functions to revert. This makes it impossible to use the contract, as it returns Ether immediately. +If you want to deactivate your contracts, you should instead **disable** them +by changing some internal state which causes all functions to revert. This +makes it impossible to use the contract, as it returns Ether immediately. diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index 7a811fa4b49d..1fec22aec7ea 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -3,8 +3,9 @@ Layout of a Solidity Source File ******************************** Source files can contain an arbitrary number of -:ref:`contract definitions`, import_ directives -and :ref:`pragma directives`. +:ref:`contract definitions`, import_ directives, +:ref:`pragma directives` and +:ref:`struct` and :ref:`enum` definitions. .. index:: ! pragma @@ -16,8 +17,8 @@ Pragmas The ``pragma`` keyword is used to enable certain compiler features or checks. A pragma directive is always local to a source file, so you have to add the pragma to all your files if you want enable it -in all of your project. If you :ref:`import` another file, the pragma -from that file does not automatically apply to the importing file. +in your whole project. If you :ref:`import` another file, the pragma +from that file does *not* automatically apply to the importing file. .. index:: ! pragma, version @@ -35,14 +36,12 @@ a good idea to read through the changelog at least for releases that contain breaking changes. These releases always have versions of the form ``0.x.0`` or ``x.0.0``. -The version pragma is used as follows:: - - pragma solidity ^0.5.2; +The version pragma is used as follows: ``pragma solidity ^0.5.2;`` A source file with the line above does not compile with a compiler earlier than version 0.5.2, and it also does not work on a compiler starting from version 0.6.0 (this -second condition is added by using ``^``). This is because -there will be no breaking changes until version ``0.6.0``, so you can always +second condition is added by using ``^``). Because +there will be no breaking changes until version ``0.6.0``, you can be sure that your code compiles the way you intended. The exact version of the compiler is not fixed, so that bugfix releases are still possible. @@ -72,10 +71,12 @@ ABIEncoderV2 ~~~~~~~~~~~~ The new ABI encoder is able to encode and decode arbitrarily nested -arrays and structs. It produces less optimal code (the optimizer -for this part of the code is still under development) and has not -received as much testing as the old encoder. You can activate it -using ``pragma experimental ABIEncoderV2;``. +arrays and structs. It might produce less optimal code and has not +received as much testing as the old encoder, but is considered +non-experimental as of Solidity 0.6.0. You still have to explicitly +activate it using ``pragma experimental ABIEncoderV2;`` - we kept +the same pragma, even though it is not considered experimental +anymore. .. _smt_checker: @@ -86,8 +87,10 @@ This component has to be enabled when the Solidity compiler is built and therefore it is not available in all Solidity binaries. The :ref:`build instructions` explain how to activate this option. It is activated for the Ubuntu PPA releases in most versions, -but not for solc-js, the Docker images, Windows binaries or the -statically-built Linux binaries. +but not for the Docker images, Windows binaries or the +statically-built Linux binaries. It can be activated for solc-js via the +`smtCallback `_ if you have an SMT solver +installed locally and run solc-js via node (not via the browser). If you use ``pragma experimental SMTChecker;``, then you get additional :ref:`safety warnings` which are obtained by querying an @@ -106,8 +109,10 @@ Importing other Source Files Syntax and Semantics -------------------- -Solidity supports import statements to help modularise your code that are similar to those available in JavaScript -(from ES6 on). However, Solidity does not support the concept of a `default export `_. +Solidity supports import statements to help modularise your code that +are similar to those available in JavaScript +(from ES6 on). However, Solidity does not support the concept of +a `default export `_. At a global level, you can use import statements of the following form: @@ -283,12 +288,11 @@ for the two function parameters and two return variables. /** @title Shape calculator. */ contract ShapeCalculator { - /** @dev Calculates a rectangle's surface and perimeter. - * @param w Width of the rectangle. - * @param h Height of the rectangle. - * @return s The calculated surface. - * @return p The calculated perimeter. - */ + /// @dev Calculates a rectangle's surface and perimeter. + /// @param w Width of the rectangle. + /// @param h Height of the rectangle. + /// @return s The calculated surface. + /// @return p The calculated perimeter. function rectangle(uint w, uint h) public pure returns (uint s, uint p) { s = w * h; p = 2 * (w + h); diff --git a/docs/metadata.rst b/docs/metadata.rst index 0250a06d1c0f..f7d16b23a65f 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -5,21 +5,23 @@ Contract Metadata .. index:: metadata, contract verification The Solidity compiler automatically generates a JSON file, the contract -metadata, that contains information about the current contract. You can use +metadata, that contains information about the compiled contract. You can use this file to query the compiler version, the sources used, the ABI and NatSpec documentation to more safely interact with the contract and 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. +The compiler appends by default the IPFS 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. The other available options are the Swarm hash and +not appending the metadata hash to the bytecode. These can be configured via +the :ref:`Standard JSON Interface`. -You have to publish the metadata file to Swarm (or another service) so that -others can access it. You create the file by using the ``solc --metadata`` +You have to publish the metadata file to IPFS, Swarm, or another service so +that others can access it. You create the file by using the ``solc --metadata`` command that generates a file called ``ContractName_meta.json``. It contains -Swarm references to the source code, so you have to upload all source files and -the metadata file. +IPFS and 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, @@ -86,7 +88,9 @@ explanatory purposes. }, metadata: { // Reflects the setting used in the input json, defaults to false - useLiteralContent: true + useLiteralContent: true, + // Reflects the setting used in the input json, defaults to "ipfs" + bytecodeHash: "ipfs" } // Required for Solidity: File and name of the contract or library this // metadata is created for. @@ -111,20 +115,24 @@ explanatory purposes. } .. warning:: - Since the bytecode of the resulting contract contains the metadata hash, any - change to the metadata results in a change of the bytecode. This includes + Since the bytecode of the resulting contract contains the metadata hash by default, any + change to the metadata might result in a change of the bytecode. This includes changes to a filename or path, and since the metadata includes a hash of all the sources used, a single whitespace change results in different metadata, and different bytecode. .. note:: - Note the ABI definition above has no fixed order. It can change with compiler versions. + The ABI definition above has no fixed order. It can change with compiler versions. + Starting from Solidity version 0.5.12, though, the array maintains a certain + order. + +.. _encoding-of-the-metadata-hash-in-the-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 ``{"bzzr1": , "solc": }`` is stored +the mapping ``{"ipfs": , "solc": }`` is stored `CBOR `_-encoded. Since the mapping might contain more keys (see below) and the beginning of that encoding is not easy to find, its length is added in a two-byte big-endian @@ -132,12 +140,12 @@ encoding. The current version of the Solidity compiler usually adds the followin to the end of the deployed bytecode:: 0xa2 - 0x65 'b' 'z' 'z' 'r' '1' 0x58 0x20 <32 bytes swarm hash> + 0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash> 0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding> 0x00 0x32 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. +to match that pattern and use the IPFS hash to retrieve the file. Whereas release builds of solc use a 3 byte encoding of the version as shown above (one byte each for major, minor and patch version number), prerelease builds @@ -145,25 +153,25 @@ will instead use a complete version string including commit hash and build date. .. note:: The CBOR mapping can also contain other keys, so it is better to fully - decode the data instead of relying on it starting with ``0xa265``. + decode the data instead of relying on it starting with ``0xa264``. For example, if any experimental features that affect code generation are used, the mapping will also contain ``"experimental": true``. .. note:: - The compiler currently uses the "swarm version 1" hash of the metadata, - but this might change in the future, so do not rely on this sequence - to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '1'``. We might also - add additional data to this CBOR structure, so the - best option is to use a proper CBOR parser. + The compiler currently uses the IPFS hash of the metadata by default, but + it may also use the bzzr1 hash or some other hash in the future, so do + not rely on this sequence to start with ``0xa2 0x64 'i' 'p' 'f' 's'``. We + might also add additional data to this CBOR structure, so the best option + is to use a proper CBOR parser. 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 or any wallet) 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. +with a contract (e.g. Mist or any wallet) retrieves the code of the contract, +from that the IPFS/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. @@ -177,7 +185,7 @@ For additional information, read :doc:`Ethereum Natural Language Specification ( Usage for Source Code Verification ================================== -In order to verify the compilation, sources can be retrieved from Swarm +In order to verify the compilation, sources can be retrieved from IPFS/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 diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 74c2d0326554..085bf99e7638 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -10,12 +10,16 @@ Layout of State Variables in Storage .. _storage-inplace-encoding: -Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position ``0``. Multiple, contiguous items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules: +Statically-sized variables (everything except mapping and dynamically-sized +array types) are laid out contiguously in storage starting from position ``0``. +Multiple, contiguous items that need less than 32 bytes are packed into a single +storage slot if possible, according to the following rules: - The first item in a storage slot is stored lower-order aligned. - Elementary types use only as many bytes as are necessary to store them. - If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot. -- Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules). +- Structs and array data always start a new slot and occupy whole slots + (but items inside a struct or array are packed tightly according to these rules). For contracts that use inheritance, the ordering of state variables is determined by the C3-linearized order of contracts starting with the most base-ward contract. If allowed @@ -54,17 +58,22 @@ Mappings and Dynamic Arrays .. _storage-hashed-encoding: Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash -computation to find the starting position of the value or the array data. These starting positions are always full stack slots. +computation to find the starting position of the value or the array data. +These starting positions are always full stack slots. The mapping or the dynamic array itself occupies a slot in storage at some position ``p`` -according to the above rule (or by recursively applying this rule for mappings of mappings or arrays of arrays). For dynamic arrays, -this slot stores the number of elements in the array (byte arrays and strings are an exception, see :ref:`below `). +according to the above rule (or by recursively applying this rule for +mappings of mappings or arrays of arrays). For dynamic arrays, +this slot stores the number of elements in the array (byte arrays and +strings are an exception, see :ref:`below `). For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different hash distribution). Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key ``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. If the value is again a non-elementary type, the positions are found by adding an offset of ``keccak256(k . p)``. -So for the following contract snippet:: +So for the following contract snippet +the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``:: + pragma solidity >=0.4.0 <0.7.0; @@ -75,8 +84,6 @@ So for the following contract snippet:: mapping(uint => mapping(uint => S)) data; } -The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``. - .. _bytes-and-string: ``bytes`` and ``string`` @@ -97,7 +104,8 @@ JSON Output .. _storage-layout-top-level: -The storage layout of a contract can be requested via the :ref:`standard JSON interface `. The output is a JSON object containing two keys, +The storage layout of a contract can be requested via +the :ref:`standard JSON interface `. The output is a JSON object containing two keys, ``storage`` and ``types``. The ``storage`` object is an array where each element has the following form: @@ -114,7 +122,7 @@ element has the following form: "type": "t_uint256" } -where the example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA`` +The example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA`` and - ``astId`` is the id of the AST node of the state variable's declaration @@ -148,7 +156,8 @@ where - ``bytes``: single slot or Keccak-256 hash-based depending on the data size (see :ref:`above `). - ``label`` is the canonical type name. -- ``numberOfBytes`` is the number of used bytes (as a decimal string). Note that if ``numberOfBytes > 32`` this means that more than one slot is used. +- ``numberOfBytes`` is the number of used bytes (as a decimal string). + Note that if ``numberOfBytes > 32`` this means that more than one slot is used. Some types have extra information besides the four above. Mappings contain its ``key`` and ``value`` types (again referencing an entry in this mapping @@ -369,12 +378,26 @@ Scratch space can be used between statements (i.e. within inline assembly). The is used as initial value for dynamic memory arrays and should never be written to (the free memory pointer points to ``0x80`` initially). -Solidity always places new objects at the free memory pointer and memory is never freed (this might change in the future). +Solidity always places new objects at the free memory pointer and +memory is never freed (this might change in the future). + +Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this +is even true for ``byte[]``, but not for ``bytes`` and ``string``). +Multi-dimensional memory arrays are pointers to memory arrays. The length of a +dynamic array is stored at the first slot of the array and followed by the array +elements. .. warning:: - There are some operations in Solidity that need a temporary memory area larger than 64 bytes and therefore will not fit into the scratch space. They will be placed where the free memory points to, but given their short lifetime, the pointer is not updated. The memory may or may not be zeroed out. Because of this, one shouldn't expect the free memory to point to zeroed out memory. + There are some operations in Solidity that need a temporary memory area + larger than 64 bytes and therefore will not fit into the scratch space. + They will be placed where the free memory points to, but given their + short lifetime, the pointer is not updated. The memory may or may not + be zeroed out. Because of this, one should not expect the free memory + to point to zeroed out memory. - While it may seem like a good idea to use ``msize`` to arrive at a definitely zeroed out memory area, using such a pointer non-temporarily without updating the free memory pointer can have adverse results. + While it may seem like a good idea to use ``msize`` to arrive at a + definitely zeroed out memory area, using such a pointer non-temporarily + without updating the free memory pointer can have unexpected results. .. index: calldata layout @@ -398,11 +421,11 @@ data to the code. Internals - Cleaning Up Variables ********************************* -When a value is shorter than 256-bit, in some cases the remaining bits +When a value is shorter than 256 bit, in some cases the remaining bits must be cleaned. The Solidity compiler is designed to clean such remaining bits before any operations that might be adversely affected by the potential garbage in the remaining bits. -For example, before writing a value to the memory, the remaining bits need +For example, before writing a value to memory, the remaining bits need to be cleared because the memory contents can be used for computing hashes or sent as the data of a message call. Similarly, before storing a value in the storage, the remaining bits need to be cleaned @@ -446,13 +469,54 @@ Different types have different rules for cleaning up invalid values: Internals - The Optimiser ************************* -The Solidity optimiser operates on assembly so that other languages can use it. It splits the sequence of instructions into basic blocks at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser analyses the instructions and records every modification to the stack, memory, or storage as an expression which consists of an instruction and a list of arguments which are pointers to other expressions. The optimiser uses a component called "CommonSubexpressionEliminator" that amongst other tasks, finds expressions that are always equal (on every input) and combines them into an expression class. The optimiser first tries to find each new expression in a list of already known expressions. If this does not work, it simplifies the expression according to rules like ``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is a recursive process, we can also apply the latter rule if the second factor is a more complex expression where we know that it always evaluates 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 do not know what is stored at x after we wrote to y. If 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. - -After this process, we know which expressions have to be on the stack at 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 the optimiser finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it to an unconditional jump. - -As the last step, the code in each block is re-generated. The optimiser creates a dependency graph from the expressions on the stack at the end of the block, and it drops every operation that is not part of this graph. It generates code 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). Finally, it 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. Thus code like +This section discusses the optimiser that was first added to Solidity, +which operates on opcode streams. For information on the new Yul-based optimiser, +please see the `readme on github `_. + +The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks +at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser +analyses the instructions and records every modification to the stack, +memory, or storage as an expression which consists of an instruction and +a list of arguments which are pointers to other expressions. The optimiser +uses a component called "CommonSubexpressionEliminator" that amongst other +tasks, finds expressions that are always equal (on every input) and combines +them into an expression class. The optimiser first tries to find each new +expression in a list of already known expressions. If this does not work, +it simplifies the expression according to rules like +``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is +a recursive process, we can also apply the latter rule if the second factor +is a more complex expression where we know that it always evaluates 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 do not know what is stored at x after +we wrote to y. If 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. + +After this process, we know which expressions have to be on the stack at +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 the optimiser +finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it +to an unconditional jump. + +As the last step, the code in each block is re-generated. The optimiser creates +a dependency graph from the expressions on the stack at the end of the block, +and it drops every operation that is not part of this graph. It generates code +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). Finally, it 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. Thus code like :: @@ -487,7 +551,8 @@ Furthermore, the compiler can also generate a mapping from the bytecode to the range in the source code that generated the instruction. This is again important for static analysis tools that operate on bytecode level and for displaying the current position in the source code inside a debugger -or for breakpoint handling. +or for breakpoint handling. This mapping also contains other information, +like the jump type and the modifier depth (see below). Both kinds of source mappings use integer identifiers to refer to source files. The identifier of a source file is stored in @@ -509,12 +574,17 @@ Where ``s`` is the byte-offset to the start of the range in the source file, index mentioned above. The encoding in the source mapping for the bytecode is more complicated: -It is a list of ``s:l:f:j`` separated by ``;``. Each of these +It is a list of ``s:l:f:j:m`` separated by ``;``. Each of these elements corresponds to an instruction, i.e. you cannot use the byte offset but have to use the instruction offset (push instructions are longer than a single byte). -The fields ``s``, ``l`` and ``f`` are as above and ``j`` can be either +The fields ``s``, ``l`` and ``f`` are as above. ``j`` can be either ``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a function, returns from a function or is a regular jump as part of e.g. a loop. +The last field, ``m``, is an integer that denotes the "modifier depth". This depth +is increased whenever the placeholder statement (``_``) is entered in a modifier +and decreased when it is left again. This allows debuggers to track tricky cases +like the same modifier being used twice or multiple placeholder statements being +used in a single modifier. In order to compress these source mappings especially for bytecode, the following rules are used: @@ -533,13 +603,18 @@ 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 creates :ref:`getters ` for you automatically. * If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`. * Initialize storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});`` .. note:: - If the storage struct has tightly packed properties, initialize it with separate assignments: ``x.a = 1; x.b = 2;``. In this way it will be easier for the optimizer to update storage in one go, thus making assignment cheaper. + If the storage struct has tightly packed properties, initialize it with separate + assignments: ``x.a = 1; x.b = 2;``. In this way it will be easier for the + optimizer to update storage in one go, thus making assignment cheaper. ********** Cheatsheet @@ -615,12 +690,16 @@ The following is the order of precedence for operators, listed in order of evalu Global Variables ================ -- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI `-decodes the provided data. The types are given in parentheses as second argument. Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))`` +- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI `-decodes + the provided data. The types are given in parentheses as second argument. + Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))`` - ``abi.encode(...) returns (bytes memory)``: :ref:`ABI `-encodes the given arguments -- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding ` of the given arguments. Note that this encoding can be ambiguous! -- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI `-encodes the given arguments - starting from the second and prepends the given four-byte selector -- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)``` +- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding ` of + the given arguments. Note that this encoding can be ambiguous! +- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI `-encodes + the given arguments starting from the second and prepends the given four-byte selector +- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent + to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)``` - ``block.coinbase`` (``address payable``): current block miner's address - ``block.difficulty`` (``uint``): current block difficulty - ``block.gaslimit`` (``uint``): current block gaslimit @@ -634,22 +713,28 @@ Global Variables - ``tx.gasprice`` (``uint``): gas price of the transaction - ``tx.origin`` (``address payable``): sender of the transaction (full call chain) - ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error) -- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component) -- ``require(bool condition, string memory message)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component). Also provide error message. +- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use + for malformed input or error in external component) +- ``require(bool condition, string memory message)``: abort execution and revert state changes if + condition is ``false`` (use for malformed input or error in external component). Also provide error message. - ``revert()``: abort execution and revert state changes - ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string - ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks - ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input - ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input - ``ripemd160(bytes memory) returns (bytes20)``: compute the RIPEMD-160 hash of the input -- ``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 -- ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. -- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. +- ``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 +- ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with + arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. +- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed + with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0. - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` or ``address payable`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address payable recipient)``: destroy the current contract, sending its funds to the given address - ``
.balance`` (``uint256``): balance of the :ref:`address` in Wei -- ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure +- ``
.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, + returns ``false`` on failure - ``
.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure - ``type(C).name`` (``string``): the name of the contract - ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information`. @@ -705,16 +790,20 @@ Modifiers - ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot. - ``anonymous`` for events: Does not store event signature as topic. - ``indexed`` for event parameters: Stores the parameter as topic. +- ``virtual`` for functions and modifiers: Allows the function's or modifier's + behaviour to be changed in derived contracts. +- ``override``: States that this function, modifier or public state variable changes + the behaviour of a function or modifier in a base contract. Reserved Keywords ================= These keywords are reserved in Solidity. They might become part of the syntax in the future: -``abstract``, ``after``, ``alias``, ``apply``, ``auto``, ``case``, ``catch``, ``copyof``, ``default``, +``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``, ``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``, -``mutable``, ``null``, ``of``, ``override``, ``partial``, ``promise``, ``reference``, ``relocatable``, -``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``typedef``, ``typeof``, +``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``, +``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``, ``unchecked``. Language Grammar diff --git a/docs/natspec-format.rst b/docs/natspec-format.rst index cfc94d81b8ea..ae429580a3ce 100644 --- a/docs/natspec-format.rst +++ b/docs/natspec-format.rst @@ -49,7 +49,7 @@ The following example shows a contract and a function using all available tags. .. code:: solidity - pragma solidity ^0.5.6; + pragma solidity >=0.5.0 <0.7.0; /// @title A simulator for trees /// @author Larry A. Gardner @@ -84,7 +84,7 @@ Tag ``@notice`` Explain to an end user what this does contract, interface, function ``@dev`` Explain to a developer any extra details contract, interface, function ``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function -``@return`` Documents the return type of a contract's function function +``@return`` Documents the return variables of a contract's function function =========== =============================================================================== ============================= If your function returns multiple values, like ``(int quotient, int remainder)`` diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 0b37d2fb5ef3..96187403be6f 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -20,14 +20,12 @@ to take too much care, but if you manage your bank account using that web servic you should be more careful. This section will list some pitfalls and general security recommendations but -can, of course, never be complete. -Also, keep in mind that even if your -smart contract code is bug-free, the compiler or the platform itself might -have a bug. A list of some publicly known security-relevant bugs of the compiler -can be found in the -:ref:`list of known bugs`, which is also machine-readable. Note -that there is a bug bounty program that covers the code generator of the -Solidity compiler. +can, of course, never be complete. Also, keep in mind that even if your smart +contract code is bug-free, the compiler or the platform itself might have a +bug. A list of some publicly known security-relevant bugs of the compiler can +be found in the :ref:`list of known bugs`, which is also +machine-readable. Note that there is a bug bounty program that covers the code +generator of the Solidity compiler. As always, with open source documentation, please help us extend this section (especially, some examples would not hurt)! @@ -138,12 +136,16 @@ Sending and Receiving Ether to move Ether without creating a message call. One way is to simply "mine to" the contract address and the second way is using ``selfdestruct(x)``. -- If a contract receives Ether (without a function being called), the fallback function is executed. - If it does not have a fallback function, the Ether will be rejected (by throwing an exception). - During the execution of the fallback function, the contract can only rely - on the "gas stipend" it is passed (2300 gas) being available to it at that time. This stipend is not enough to modify storage - (do not take this for granted though, the stipend might change with future hard forks). - To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function +- If a contract receives Ether (without a function being called), + either the :ref:`receive Ether ` + or the :ref:`fallback ` function is executed. + If it does not have a receive nor a fallback function, the Ether will be + rejected (by throwing an exception). During the execution of one of these + functions, the contract can only rely on the "gas stipend" it is passed (2300 + gas) being available to it at that time. This stipend is not enough to modify + storage (do not take this for granted though, the stipend might change with + future hard forks). To be sure that your contract can receive Ether in that + way, check the gas requirements of the receive and fallback functions (for example in the "details" section in Remix). - There is a way to forward more gas to the receiving contract using @@ -159,17 +161,22 @@ Sending and Receiving Ether - If you want to send Ether using ``address.transfer``, there are certain details to be aware of: - 1. If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract. - 2. Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the call - depth, they can force the transfer to fail; take this possibility into account or use ``send`` and make sure to always check its return value. Better yet, - write your contract using a pattern where the recipient can withdraw Ether instead. - 3. Sending Ether can also fail because the execution of the recipient contract - requires more than the allotted amount of gas (explicitly by using ``require``, - ``assert``, ``revert``, ``throw`` or - because the operation is just too expensive) - it "runs out of gas" (OOG). - If you use ``transfer`` or ``send`` with a return value check, this might provide a - means for the recipient to block progress in the sending contract. Again, the best practice here is to use - a :ref:`"withdraw" pattern instead of a "send" pattern `. + 1. If the recipient is a contract, it causes its receive or fallback function + to be executed which can, in turn, call back the sending contract. + 2. Sending Ether can fail due to the call depth going above 1024. Since the + caller is in total control of the call depth, they can force the + transfer to fail; take this possibility into account or use ``send`` and + make sure to always check its return value. Better yet, write your + contract using a pattern where the recipient can withdraw Ether instead. + 3. Sending Ether can also fail because the execution of the recipient + contract requires more than the allotted amount of gas (explicitly by + using :ref:`require `, :ref:`assert `, + :ref:`revert ` or because the + operation is too expensive) - it "runs out of gas" (OOG). If you + use ``transfer`` or ``send`` with a return value check, this might + provide a means for the recipient to block progress in the sending + contract. Again, the best practice here is to use a :ref:`"withdraw" + pattern instead of a "send" pattern `. Callstack Depth =============== @@ -181,8 +188,7 @@ before they interact with your contract. Note that ``.send()`` does **not** throw an exception if the call stack is depleted but rather returns ``false`` in that case. The low-level functions -``.call()``, ``.callcode()``, ``.delegatecall()`` and ``.staticcall()`` behave -in the same way. +``.call()``, ``.delegatecall()`` and ``.staticcall()`` behave in the same way. tx.origin ========= @@ -207,11 +213,11 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like } } -Now someone tricks you into sending ether to the address of this attack wallet: +Now someone tricks you into sending Ether to the address of this attack wallet: :: - pragma solidity >=0.5.0 <0.7.0; + pragma solidity ^0.6.0; interface TxUserWallet { function transferTo(address payable dest, uint amount) external; @@ -224,7 +230,7 @@ Now someone tricks you into sending ether to the address of this attack wallet: owner = msg.sender; } - function() external { + receive() external payable { TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance); } } @@ -247,8 +253,7 @@ In general, read about the limits of two's complement representation, which even more special edge cases for signed numbers. Try to use ``require`` to limit the size of inputs to a reasonable range and use the -:ref:`SMT checker` to find potential overflows, or -use a library like +:ref:`SMT checker` to find potential overflows, or use a library like `SafeMath `_ if you want all overflows to cause a revert. @@ -259,16 +264,16 @@ Code such as ``require((balanceOf[_to] + _value) >= balanceOf[_to])`` can also h Clearing Mappings ================= -The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only key-value data structure that -does not keep track of the keys that were assigned a non-zero value. -Because of that, cleaning a mapping without extra information about the written -keys is not possible. +The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only +key-value data structure that does not keep track of the keys that were +assigned a non-zero value. Because of that, cleaning a mapping without extra +information about the written keys is not possible. If a ``mapping`` is used as the base type of a dynamic storage array, deleting -or popping the array will have no effect over the ``mapping`` elements. -The same happens, for example, if a ``mapping`` is used as the type of a member -field of a ``struct`` that is the base type of a dynamic storage array. -The ``mapping`` is also ignored in assignments of structs or arrays containing -a ``mapping``. +or popping the array will have no effect over the ``mapping`` elements. The +same happens, for example, if a ``mapping`` is used as the type of a member +field of a ``struct`` that is the base type of a dynamic storage array. The +``mapping`` is also ignored in assignments of structs or arrays containing a +``mapping``. :: @@ -278,7 +283,8 @@ a ``mapping``. mapping (uint => uint)[] array; function allocate(uint _newMaps) public { - array.length += _newMaps; + for (uint i = 0; i < _newMaps; i++) + array.push(); } function writeMap(uint _map, uint _key, uint _value) public { @@ -306,8 +312,7 @@ another call to ``writeMap``. If your ``mapping`` information must be deleted, consider using a library similar to `iterable mapping `_, -allowing you to traverse the keys and delete their values in the appropriate -``mapping``. +allowing you to traverse the keys and delete their values in the appropriate ``mapping``. Minor Details ============= @@ -427,17 +432,11 @@ The SMTChecker traverses the Solidity AST creating and collecting program constr When it encounters a verification target, an SMT solver is invoked to determine the outcome. If a check fails, the SMTChecker provides specific input values that lead to the failure. -For more details on how the SMT encoding works internally, see the paper -`SMT-based Verification of Solidity Smart Contracts `_. +While the SMTChecker encodes Solidity code into SMT constraints, it contains two +reasoning engines that use that encoding in different ways. -Abstraction and False Positives -=============================== - -The SMTChecker implements abstractions in an incomplete and sound way: If a bug -is reported, it might be a false positive introduced by abstractions (due to -erasing knowledge or using a non-precise type). If it determines that a -verification target is safe, it is indeed safe, that is, there are no false -negatives (unless there is a bug in the SMTChecker). +SMT Encoding +============ The SMT encoding tries to be as precise as possible, mapping Solidity types and expressions to their closest `SMT-LIB `_ @@ -451,13 +450,60 @@ representation, as shown in the table below. |intN, uintN, address, |Integer |LIA, NIA | |bytesN, enum | | | +-----------------------+--------------+-----------------------------+ -|array, mapping |Array |Arrays | +|array, mapping, bytes, |Array |Arrays | +|string | | | +-----------------------+--------------+-----------------------------+ |other types |Integer |LIA | +-----------------------+--------------+-----------------------------+ -Types that are not yet supported are abstracted by a single 256-bit unsigned integer, -where their unsupported operations are ignored. +Types that are not yet supported are abstracted by a single 256-bit unsigned +integer, where their unsupported operations are ignored. + +For more details on how the SMT encoding works internally, see the paper +`SMT-based Verification of Solidity Smart Contracts `_. + +Model Checking Engines +====================== + +The SMTChecker module implements two different reasoning engines that use the +SMT encoding above, a Bounded Model Checker (BMC) and a system of Constrained +Horn Clauses (CHC). Both engines are currently under development, and have +different characteristics. + +Bounded Model Checker (BMC) +--------------------------- + +The BMC engine analyzes functions in isolation, that is, it does not take the +overall behavior of the contract throughout many transactions into account when +analyzing each function. Loops are also ignored in this engine at the moment. +Internal function calls are inlined as long as they are not recursive, direct +or indirectly. External function calls are inlined if possible, and knowledge +that is potentially affected by reentrancy is erased. + +The characteristics above make BMC easily prone to reporting false positives, +but it is also lightweight and should be able to quickly find small local bugs. + +Constrained Horn Clauses (CHC) +------------------------------ + +The Solidity contract's Control Flow Graph (CFG) is modelled as a system of +Horn clauses, where the lifecycle of the contract is represented by a loop +that can visit every public/external function non-deterministically. This way, +the behavior of the entire contract over an unbounded number of transactions +is taken into account when analyzing any function. Loops are fully supported +by this engine. Function calls are currently unsupported. + +The CHC engine is much more powerful than BMC in terms of what it can prove, +and might require more computing resources. + +Abstraction and False Positives +=============================== + +The SMTChecker implements abstractions in an incomplete and sound way: If a bug +is reported, it might be a false positive introduced by abstractions (due to +erasing knowledge or using a non-precise type). If it determines that a +verification target is safe, it is indeed safe, that is, there are no false +negatives (unless there is a bug in the SMTChecker). Function calls to the same contract (or base contracts) are inlined when possible, that is, when their implementation is available. diff --git a/docs/solidity-in-depth.rst b/docs/solidity-in-depth.rst index ddfddb77b8ea..111a100d7e95 100644 --- a/docs/solidity-in-depth.rst +++ b/docs/solidity-in-depth.rst @@ -19,3 +19,4 @@ If something is missing here, please contact us on assembly.rst miscellaneous.rst 050-breaking-changes.rst + 060-breaking-changes.rst diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index 01ed23233145..b6d70c5a3250 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -67,6 +67,11 @@ Function Modifiers Function modifiers can be used to amend the semantics of functions in a declarative way (see :ref:`modifiers` in the contracts section). +Overloading, that is, having the same modifier name with different parameters, +is not possible. + +Like functions, modifiers can be :ref:`overridden `. + :: pragma solidity >=0.4.22 <0.7.0; diff --git a/docs/style-guide.rst b/docs/style-guide.rst index aa2c1c305e49..84602542f9e3 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -89,20 +89,20 @@ Blank lines may be omitted between groups of related one-liners (such as stub fu Yes:: - pragma solidity >=0.4.0 <0.7.0; + pragma solidity ^0.6.0; - contract A { - function spam() public pure; - function ham() public pure; + abstract contract A { + function spam() public virtual pure; + function ham() public virtual pure; } contract B is A { - function spam() public pure { + function spam() public pure override { // ... } - function ham() public pure { + function ham() public pure override { // ... } } @@ -111,11 +111,17 @@ No:: pragma solidity >=0.4.0 <0.7.0; - contract A { - function spam() public pure { + abstract contract A { + function spam() virtual pure public; + function ham() public virtual pure; + } + + + contract B is A { + function spam() public pure override { // ... } - function ham() public pure { + function ham() public pure override { // ... } } @@ -273,6 +279,7 @@ Ordering helps readers identify which functions they can call and to find the co Functions should be grouped according to their visibility and ordered: - constructor +- receive function (if exists) - fallback function (if exists) - external - public @@ -283,14 +290,18 @@ Within a grouping, place the ``view`` and ``pure`` functions last. Yes:: - pragma solidity >=0.4.0 <0.7.0; + pragma solidity ^0.6.0; contract A { constructor() public { // ... } - function() external { + receive() external payable { + // ... + } + + fallback() external { // ... } @@ -322,7 +333,10 @@ No:: // External functions // ... - function() external { + fallback() external { + // ... + } + receive() external payable { // ... } @@ -384,20 +398,29 @@ No:: y = 2; long_variable = 3; -Don't include a whitespace in the fallback function: +Don't include a whitespace in the receive and fallback functions: Yes:: - function() external { + receive() external payable { + ... + } + + fallback() external { ... } No:: - function () external { + receive () external payable { ... } + fallback () external { + ... + } + + Control Structures ================== @@ -547,31 +570,30 @@ No:: function increment(uint x) public pure returns (uint) { return x + 1;} -You should explicitly label the visibility of all functions, including constructors. +The modifier order for a function should be: -Yes:: - - function explicitlyPublic(uint val) public { - doSomething(); - } +1. Visibility +2. Mutability +3. Virtual +4. Override +5. Custom modifiers -No:: +Yes:: - function implicitlyPublic(uint val) { - doSomething(); + function balance(uint from) public view override returns (uint) { + return balanceOf[from]; } -The visibility modifier for a function should come before any custom -modifiers. - -Yes:: - function kill() public onlyowner { selfdestruct(owner); } No:: + function balance(uint from) public override view returns (uint) { + return balanceOf[from]; + } + function kill() onlyowner public { selfdestruct(owner); } diff --git a/docs/types/conversion.rst b/docs/types/conversion.rst index 51e37340525c..7442a17b57f8 100644 --- a/docs/types/conversion.rst +++ b/docs/types/conversion.rst @@ -8,9 +8,8 @@ Conversions between Elementary Types Implicit Conversions -------------------- -If an operator is applied to different types, the compiler tries to implicitly -convert one of the operands to the type of the other (the same is true for assignments). -This means that operations are always performed in the type of one of the operands. +An implicit type conversion is automatically applied by the compiler in some cases +during assignments, when passing arguments to functions and when applying operators. In general, an implicit conversion between value-types is possible if it makes sense semantically and no information is lost. @@ -18,7 +17,27 @@ For example, ``uint8`` is convertible to ``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256``, because ``uint256`` cannot hold values such as ``-1``. -For more details, please consult the sections about the types themselves. +If an operator is applied to different types, the compiler tries to implicitly +convert one of the operands to the type of the other (the same is true for assignments). +This means that operations are always performed in the type of one of the operands. + +For more details about which implicit conversions are possible, +please consult the sections about the types themselves. + +In the example below, ``y`` and ``z``, the operands of the addition, +do not have the same type, but ``uint8`` can +be implicitly converted to ``uint16`` and not vice-versa. Because of that, +``y`` is converted to the type of ``z`` before the addition is performed +in the ``uint16`` type. The resulting type of the expression ``y + z`` is ``uint16`. +Because it is assigned to a variable of type ``uint32`` another implicit conversion +is performed after the addition. + +:: + + uint8 y; + uint16 z; + uint32 x = y + z; + Explicit Conversions -------------------- @@ -128,3 +147,5 @@ As described in :ref:`address_literals`, hex literals of the correct size that p test are of ``address`` type. No other literals can be implicitly converted to the ``address`` type. Explicit conversions from ``bytes20`` or any integer type to ``address`` result in ``address payable``. + +An ``address a`` can be converted to ``address payable`` via ``payable(a)``. diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst index 24eb1bc9dab4..5b41d4bd0cc8 100644 --- a/docs/types/mapping-types.rst +++ b/docs/types/mapping-types.rst @@ -5,16 +5,17 @@ Mapping Types ============= Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables -are declared as a mapping type using the syntax ``mapping (_KeyType => _ValueType) _VariableModifiers _VariableName``. -The ``_KeyType`` can be any elementary type. This means it can be any of -the built-in value types plus ``bytes`` and ``string``. User-defined -or complex types like contract types, enums, mappings, structs and any array type +of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``. +The ``_KeyType`` can be any +built-in value type plus ``bytes`` and ``string``. User-defined +or complex types such as contract types, enums, mappings, structs or array types apart from ``bytes`` and ``string`` are not allowed. -``_ValueType`` can be any type, including mappings. +``_ValueType`` can be any type, including mappings, arrays and structs. You can think of mappings as `hash tables `_, which are virtually initialised such that every possible key exists and is mapped to a value whose -byte-representation is all zeros, a type's :ref:`default value `. The similarity ends there, the key data is not stored in a +byte-representation is all zeros, a type's :ref:`default value `. +The similarity ends there, the key data is not stored in a mapping, only its ``keccak256`` hash is used to look up the value. Because of this, mappings do not have a length or a concept of a key or @@ -59,7 +60,8 @@ contract that returns the value at the specified address. } } -The example below is a simplified version of an `ERC20 token `_. +The example below is a simplified version of an +`ERC20 token `_. ``_allowances`` is an example of a mapping type inside another mapping type. The example below uses ``_allowances`` to record the amount someone else is allowed to withdraw from your account. @@ -111,33 +113,34 @@ The example below uses ``_allowances`` to record the amount someone else is allo Iterable Mappings ----------------- -Mappings are not iterable, but it is possible to implement a data structure on +You cannot iterate over mappings, i.e. you cannot enumerate their keys. +It is possible, though, to implement a data structure on top of them and iterate over that. For example, the code below implements an ``IterableMapping`` library that the ``User`` contract then adds data too, and the ``sum`` function iterates over to sum all the values. :: - pragma solidity >=0.4.0 <0.7.0; - - library IterableMapping { + pragma solidity >=0.5.99 <0.7.0; - struct itmap { - mapping(uint => IndexValue) data; - KeyFlag[] keys; - uint size; - } + struct IndexValue { uint keyIndex; uint value; } + struct KeyFlag { uint key; bool deleted; } - struct IndexValue { uint keyIndex; uint value; } - struct KeyFlag { uint key; bool deleted; } + struct itmap { + mapping(uint => IndexValue) data; + KeyFlag[] keys; + uint size; + } + library IterableMapping { function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) { uint keyIndex = self.data[key].keyIndex; self.data[key].value = value; if (keyIndex > 0) return true; else { - keyIndex = self.keys.length++; + self.keys.push(); + keyIndex = self.keys.length; self.data[key].keyIndex = keyIndex + 1; self.keys[keyIndex].key = key; self.size++; @@ -182,23 +185,28 @@ the ``sum`` function iterates over to sum all the values. // How to use it contract User { // Just a struct holding our data. - IterableMapping.itmap data; + itmap data; + // Apply library functions to the data type. + using IterableMapping for itmap; // Insert something function insert(uint k, uint v) public returns (uint size) { - // Actually calls itmap_impl.insert, auto-supplying the first parameter for us. - IterableMapping.insert(data, k, v); - // We can still access members of the struct - but we should take care not to mess with them. + // This calls IterableMapping.insert(data, k, v) + data.insert(k, v); + // We can still access members of the struct, + // but we should take care not to mess with them. return data.size; } // Computes the sum of all stored data. function sum() public view returns (uint s) { - for (uint i = IterableMapping.iterate_start(data); - IterableMapping.iterate_valid(data, i); - i = IterableMapping.iterate_next(data, i)) { - (, uint value) = IterableMapping.iterate_get(data, i); + for ( + uint i = data.iterate_start(); + data.iterate_valid(i); + i = data.iterate_next(i) + ) { + (, uint value) = data.iterate_get(i); s += value; } } - } \ No newline at end of file + } diff --git a/docs/types/operators.rst b/docs/types/operators.rst index f53d6ee57bc6..c65e545c5446 100644 --- a/docs/types/operators.rst +++ b/docs/types/operators.rst @@ -3,9 +3,14 @@ Operators Involving LValues =========================== -If ``a`` is an LValue (i.e. a variable or something that can be assigned to), the following operators are available as shorthands: +If ``a`` is an LValue (i.e. a variable or something that can be assigned to), the +following operators are available as shorthands: -``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, ``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but return the value after the change. +``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, +``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent +to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value +of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but +return the value after the change. .. _delete: @@ -19,12 +24,20 @@ initial value. ``delete a[x]`` deletes the item at index ``x`` of the array and all other elements and the length of the array untouched. This especially means that it leaves a gap in the array. If you plan to remove items, a :ref:`mapping ` is probably a better choice. -For structs, it assigns a struct with all members reset. In other words, the value of ``a`` after ``delete a`` is the same as if ``a`` would be declared without assignment, with the following caveat: +For structs, it assigns a struct with all members reset. In other words, +the value of ``a`` after ``delete a`` is the same as if ``a`` would be declared +without assignment, with the following caveat: -``delete`` has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown). So if you delete a struct, it will reset all members that are not mappings and also recurse into the members unless they are mappings. However, individual keys and what they map to can be deleted: If ``a`` is a mapping, then ``delete a[x]`` will delete the value stored at ``x``. +``delete`` has no effect on mappings (as the keys of mappings may be arbitrary and +are generally unknown). So if you delete a struct, it will reset all members that +are not mappings and also recurse into the members unless they are mappings. +However, individual keys and what they map to can be deleted: If ``a`` is a +mapping, then ``delete a[x]`` will delete the value stored at ``x``. -It is important to note that ``delete a`` really behaves like an assignment to ``a``, i.e. it stores a new object in ``a``. -This distinction is visible when ``a`` is reference variable: It will only reset ``a`` itself, not the +It is important to note that ``delete a`` really behaves like an +assignment to ``a``, i.e. it stores a new object in ``a``. +This distinction is visible when ``a`` is reference variable: It +will only reset ``a`` itself, not the value it referred to previously. :: diff --git a/docs/types/reference-types.rst b/docs/types/reference-types.rst index de1e5f543fb8..15cd110ddc1d 100644 --- a/docs/types/reference-types.rst +++ b/docs/types/reference-types.rst @@ -11,7 +11,8 @@ a variable of value type is used. Because of that, reference types have to be ha more carefully than value types. Currently, reference types comprise structs, arrays and mappings. If you use a reference type, you always have to explicitly provide the data area where the type is stored: ``memory`` (whose lifetime is limited -to a function call), ``storage`` (the location where the state variables are stored) +to an external function call), ``storage`` (the location where the state variables +are stored, where the lifetime is limited to the lifetime of a contract) or ``calldata`` (special data location that contains the function arguments, only available for external function call parameters). @@ -23,7 +24,7 @@ while assignments inside the same data location only copy in some cases for stor Data location ------------- -Every reference type, i.e. *arrays* and *structs*, has an additional +Every reference type has an additional annotation, the "data location", about where it is stored. There are three data locations: ``memory``, ``storage`` and ``calldata``. Calldata is only valid for parameters of external contract functions and is required for this type of parameter. Calldata is a non-modifiable, @@ -42,24 +43,34 @@ Data location and assignment behaviour Data locations are not only relevant for persistency of data, but also for the semantics of assignments: -* Assignments between ``storage`` and ``memory`` (or from ``calldata``) always create an independent copy. -* Assignments from ``memory`` to ``memory`` only create references. This means that changes to one memory variable are also visible in all other memory variables that refer to the same data. -* Assignments from ``storage`` to a local storage variable also only assign a reference. -* All other assignments to ``storage`` always copy. Examples for this case are assignments to state variables or to members of local variables of storage struct type, even if the local variable itself is just a reference. +* Assignments between ``storage`` and ``memory`` (or from ``calldata``) + always create an independent copy. +* Assignments from ``memory`` to ``memory`` only create references. This means + that changes to one memory variable are also visible in all other memory + variables that refer to the same data. +* Assignments from ``storage`` to a **local** storage variable also only + assign a reference. +* All other assignments to ``storage`` always copy. Examples for this + case are assignments to state variables or to members of local + variables of storage struct type, even if the local variable + itself is just a reference. :: pragma solidity >=0.4.0 <0.7.0; contract C { - uint[] x; // the data location of x is storage + // The data location of x is storage. + // This is the only place where the + // data location can be omitted. + uint[] x; - // the data location of memoryArray is memory + // The data location of memoryArray is memory. function f(uint[] memory memoryArray) public { x = memoryArray; // works, copies the whole array to storage uint[] storage y = x; // works, assigns a pointer, data location of y is storage y[7]; // fine, returns the 8th element - y.length = 2; // fine, modifies x through y + y.pop(); // fine, modifies x through y delete x; // fine, clears the array, also modifies y // The following does not work; it would need to create a new temporary / // unnamed array in storage, but storage is "statically" allocated: @@ -96,7 +107,7 @@ as C. Indices are zero-based, and access is in the opposite direction of the declaration. -For example, if you have a variable ``uint[][5] x memory``, you access the +For example, if you have a variable ``uint[][5] memory x``, you access the second ``uint`` in the third dynamic array using ``x[2][1]``, and to access the third dynamic array, use ``x[2]``. Again, if you have an array ``T[5] a`` for a type ``T`` that can also be an array, @@ -109,8 +120,9 @@ restrictions for types apply, in that mappings can only be stored in the It is possible to mark state variable arrays ``public`` and have Solidity create a :ref:`getter `. The numeric index becomes a required parameter for the getter. -Accessing an array past its end causes a failing assertion. You can use the ``.push()`` method to append a new element at the end or assign to the ``.length`` :ref:`member ` to change the size (see below for caveats). -method or increase the ``.length`` :ref:`member ` to add elements. +Accessing an array past its end causes a failing assertion. Methods ``.push()`` and ``.push(value)`` can be used +to append a new element at the end of the array, where ``.push()`` appends a zero-initialized element and returns +a reference to it. ``bytes`` and ``strings`` as Arrays ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -121,9 +133,11 @@ length or index access. Solidity does not have string manipulation functions, but there are third-party string libraries. You can also compare two strings by their keccak256-hash using -``keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))`` and concatenate two strings using ``abi.encodePacked(s1, s2)``. +``keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))`` and +concatenate two strings using ``abi.encodePacked(s1, s2)``. -You should use ``bytes`` over ``byte[]`` because it is cheaper, since ``byte[]`` adds 31 padding bytes between the elements. As a general rule, +You should use ``bytes`` over ``byte[]`` because it is cheaper, +since ``byte[]`` adds 31 padding bytes between the elements. As a general rule, use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length string (UTF-8) data. If you can limit the length to a certain number of bytes, always use one of the value types ``bytes1`` to ``bytes32`` because they are much cheaper. @@ -139,9 +153,10 @@ always use one of the value types ``bytes1`` to ``bytes32`` because they are muc Allocating Memory Arrays ^^^^^^^^^^^^^^^^^^^^^^^^ -You must use the ``new`` keyword to create arrays with a runtime-dependent length in memory. -As opposed to storage arrays, it is **not** possible to resize memory arrays (e.g. by assigning to -the ``.length`` member). You either have to calculate the required size in advance +Memory arrays with dynamic length can be created using the ``new`` operator. +As opposed to storage arrays, it is **not** possible to resize memory arrays (e.g. +the ``.push`` member functions are not available). +You either have to calculate the required size in advance or create a new memory array and copy every element. :: @@ -171,7 +186,9 @@ type of the array. Array literals are always statically-sized memory arrays. In the example below, the type of ``[1, 2, 3]`` is -``uint8[3] memory``. Because the type of each of these constants is ``uint8``, if you want the result to be a ``uint[3] memory`` type, you need to convert the first element to ``uint``. +``uint8[3] memory``. Because the type of each of these constants is ``uint8``, if +you want the result to be a ``uint[3] memory`` type, you need to convert +the first element to ``uint``. :: @@ -186,7 +203,8 @@ In the example below, the type of ``[1, 2, 3]`` is } } -Fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, i.e. the following is not possible: +Fixed size memory arrays cannot be assigned to dynamically-sized +memory arrays, i.e. the following is not possible: :: @@ -213,32 +231,34 @@ Array Members **length**: Arrays have a ``length`` member that contains their number of elements. - The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created. - For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array. - Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion. - Increasing the length adds new zero-initialised elements to the array. - Reducing the length performs an implicit :ref:`delete` on each of the - removed elements. If you try to resize a non-dynamic array that isn't in - storage, you receive a ``Value must be an lvalue`` error. -**push**: - Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length. + The length of memory arrays is fixed (but dynamic, i.e. it can depend on + runtime parameters) once they are created. +**push()**: + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function + called ``push()`` that you can use to append a zero-initialised element at the end of the array. + It returns a reference to the element, so that it can be used like + ``x.push().t = 2`` or ``x.push() = b``. +**push(x)**: + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function + called ``push(x)`` that you can use to append a given element at the end of the array. + The function returns nothing. **pop**: - Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:`delete` on the removed element. - -.. warning:: - If you use ``.length--`` on an empty array, it causes an underflow and - thus sets the length to ``2**256-1``. + Dynamic storage arrays and ``bytes`` (not ``string``) have a member + function called ``pop`` that you can use to remove an element from the + end of the array. This also implicitly calls :ref:`delete` on the removed element. .. note:: - Increasing the length of a storage array has constant gas costs because - storage is assumed to be zero-initialised, while decreasing - the length has at least linear cost (but in most cases worse than linear), - because it includes explicitly clearing the removed + Increasing the length of a storage array by calling ``push()`` + has constant gas costs because storage is zero-initialised, + while decreasing the length by calling ``pop()`` has a + cost that depends on the "size" of the element being removed. + If that element is an array, it can be very costly, because + it includes explicitly clearing the removed elements similar to calling :ref:`delete` on them. .. note:: - It is not yet possible to use arrays of arrays in external functions - (but they are supported in public functions). + To use arrays of arrays in external (instead of public) functions, you need to + activate ABIEncoderV2. .. note:: In EVM versions before Byzantium, it was not possible to access @@ -291,8 +311,15 @@ Array Members } function changeFlagArraySize(uint newSize) public { - // if the new size is smaller, removed array elements will be cleared - m_pairsOfFlags.length = newSize; + // using push and pop is the only way to change the + // length of an array + if (newSize < m_pairsOfFlags.length) { + while (m_pairsOfFlags.length > newSize) + m_pairsOfFlags.pop(); + } else if (newSize > m_pairsOfFlags.length) { + while (m_pairsOfFlags.length < newSize) + m_pairsOfFlags.push(); + } } function clear() public { @@ -300,7 +327,7 @@ Array Members delete m_pairsOfFlags; delete m_aLotOfIntegers; // identical effect here - m_pairsOfFlags.length = 0; + m_pairsOfFlags = new bool[2][](0); } bytes m_byteData; @@ -309,13 +336,15 @@ Array Members // byte arrays ("bytes") are different as they are stored without padding, // but can be treated identical to "uint8[]" m_byteData = data; - m_byteData.length += 7; + for (uint i = 0; i < 7; i++) + m_byteData.push(); m_byteData[3] = 0x08; delete m_byteData[2]; } function addFlag(bool[2] memory flag) public returns (uint) { - return m_pairsOfFlags.push(flag); + m_pairsOfFlags.push(flag); + return m_pairsOfFlags.length; } function createMemoryArray(uint size) public pure returns (bytes memory) { @@ -334,6 +363,67 @@ Array Members } } +.. index:: ! array;slice + +.. _array-slices: + +Array Slices +------------ + + +Array slices are a view on a contiguous portion of an array. +They are written as ``x[start:end]``, where ``start`` and +``end`` are expressions resulting in a uint256 type (or +implicitly convertible to it). The first element of the +slice is ``x[start]`` and the last element is ``x[end - 1]``. + +If ``start`` is greater than ``end`` or if ``end`` is greater +than the length of the array, an exception is thrown. + +Both ``start`` and ``end`` are optional: ``start`` defaults + to ``0`` and ``end`` defaults to the length of the array. + +Array slices do not have any members. They are implicitly +convertible to arrays of their underlying type +and support index access. Index access is not absolute +in the underlying array, but relative to the start of +the slice. + +Array slices do not have a type name which means +no variable can have an array slices as type, +they only exist in intermediate expressions. + +.. note:: + As of now, array slices are only implemented for calldata arrays. + +Array slices are useful to ABI-decode secondary data passed in function parameters: + +:: + + pragma solidity >=0.4.99 <0.7.0; + + contract Proxy { + /// Address of the client contract managed by proxy i.e., this contract + address client; + + constructor(address _client) public { + client = _client; + } + + /// Forward call to "setOwner(address)" that is implemented by client + /// after doing basic validation on the address argument. + function forward(bytes calldata _payload) external { + bytes4 sig = abi.decode(_payload[:4], (bytes4)); + if (sig == bytes4(keccak256("setOwner(address)"))) { + address owner = abi.decode(_payload[4:], (address)); + require(owner != address(0), "Address of owner cannot be zero."); + } + (bool status,) = client.delegatecall(_payload); + require(status, "Forwarded call failed."); + } + } + + .. index:: ! struct, ! type;struct @@ -349,13 +439,18 @@ shown in the following example: pragma solidity >=0.4.11 <0.7.0; - contract CrowdFunding { - // Defines a new type with two fields. - struct Funder { - address addr; - uint amount; - } + // Defines a new type with two fields. + // Declaring a struct outside of a contract allows + // it to be shared by multiple contracts. + // Here, this is not really needed. + struct Funder { + address addr; + uint amount; + } + contract CrowdFunding { + // Structs can also be defined inside contracts, which makes them + // visible only there and in derived contracts. struct Campaign { address payable beneficiary; uint fundingGoal; diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 426bdbe377db..be7ca1d6e8be 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -123,8 +123,9 @@ results in the same sign as its left operand (or zero) and ``a % n == -(-a % n)` Exponentiation ^^^^^^^^^^^^^^ -Exponentiation is only available for unsigned types. Please take care that the types -you are using are large enough to hold the result and prepare for potential wrapping behaviour. +Exponentiation is only available for unsigned types in the exponent. The resulting type +of an exponentiation is always equal to the type of the base. Please take care that it is +large enough to hold the result and prepare for potential wrapping behaviour. .. note:: Note that ``0**0`` is defined by the EVM as ``1``. @@ -153,7 +154,7 @@ Operators: 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, staticcall, transfer +.. index:: address, balance, send, call, delegatecall, staticcall, transfer .. _address: @@ -170,18 +171,22 @@ while a plain ``address`` cannot be sent Ether. Type conversions: -Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable`` are -not possible (the only way to perform such a conversion is by using an intermediate conversion to ``uint160``). +Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable`` +must be explicit via ``payable(
)``. :ref:`Address literals` can be implicitly converted to ``address payable``. Explicit conversions to and from ``address`` are allowed for integers, integer literals, ``bytes20`` and contract types with the following caveat: -Conversions of the form ``address payable(x)`` are not allowed. Instead the result of a conversion of the form ``address(x)`` -has the type ``address payable``, if ``x`` is of integer or fixed bytes type, a literal or a contract with a payable fallback function. -If ``x`` is a contract without payable fallback function, then ``address(x)`` will be of type ``address``. +The result of a conversion of the form ``address(x)`` +has the type ``address payable``, if ``x`` is of integer or fixed bytes type, +a literal or a contract with a receive or payable fallback function. +If ``x`` is a contract without a receive or payable fallback function, +then ``address(x)`` will be of type ``address``. In external function signatures ``address`` is used for both the ``address`` and the ``address payable`` type. +Only expressions of type ``address`` can be converted to type ``address payable`` via ``payable(
)``. + .. note:: It might very well be that you do not need to care about the distinction between ``address`` and ``address payable`` and just use ``address`` everywhere. For example, @@ -204,7 +209,7 @@ Operators: .. note:: The distinction between ``address`` and ``address payable`` was introduced with version 0.5.0. Also starting from that version, contracts do not derive from the address type, but can still be explicitly converted to - ``address`` or to ``address payable``, if they have a payable fallback function. + ``address`` or to ``address payable``, if they have a receive or payable fallback function. .. _members-of-addresses: @@ -229,7 +234,7 @@ or if the Ether transfer is rejected by the receiving account. The ``transfer`` reverts on failure. .. note:: - If ``x`` is a contract address, its code (more specifically: its :ref:`fallback-function`, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. + If ``x`` is a contract address, its code (more specifically: its :ref:`receive-ether-function`, if present, or otherwise its :ref:`fallback-function`, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. * ``send`` @@ -309,10 +314,12 @@ Every :ref:`contract` defines its own type. You can implicitly convert contracts to contracts they inherit from. Contracts can be explicitly converted to and from the ``address`` type. -Explicit conversion to and from the ``address payable`` type -is only possible if the contract type has a payable fallback function. -The conversion is still performed using ``address(x)`` and not -using ``address payable(x)``. You can find more information in the section about +Explicit conversion to and from the ``address payable`` type is only possible +if the contract type has a receive or payable fallback function. The conversion is still +performed using ``address(x)``. If the contract type does not have a receive or payable +fallback function, the conversion to ``address payable`` can be done using +``payable(address(x))``. +You can find more information in the section about the :ref:`address type
`. .. note:: @@ -405,7 +412,7 @@ Octal literals do not exist in Solidity and leading zeros are invalid. Decimal fraction literals are formed by a ``.`` with at least one number on one side. Examples include ``1.``, ``.1`` and ``1.3``. -Scientific notation is also supported, where the base can have fractions, while the exponent cannot. +Scientific notation is also supported, where the base can have fractions and the exponent cannot. Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``. Underscores can be used to separate the digits of a numeric literal to aid readability. @@ -498,7 +505,14 @@ terminate the string literal. Newline only terminates the string literal if it i Hexadecimal Literals -------------------- -Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double or single-quotes (``hex"001122FF"``), and they can also be split into multiple consecutive parts (``hex"00112233" hex"44556677"`` is equivalent to ``hex"0011223344556677"``). Their content must be a hexadecimal string and their value will be the binary representation of those values. +Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double +or single-quotes (``hex"001122FF"``, ``hex'0011_22_FF'``). Their content must be +hexadecimal digits which can optionally use a single underscore as separator between +byte boundaries. The value of the literal will be the binary representation +of the hexadecimal sequence. + +Multiple hexadecimal literals separated by whitespace are concatenated into a single literal: +``hex"00112233" hex"44556677"`` is equivalent to ``hex"0011223344556677"`` Hexadecimal literals behave like :ref:`string literals ` and have the same convertibility restrictions. @@ -545,6 +559,10 @@ subsequent unsigned integer values starting from ``0``. } } +.. note:: + Enums can also be declared on the file level, outside of contract or library definitions. + + .. index:: ! function type, ! type; function .. _function_types: @@ -582,9 +600,6 @@ do not have a default. Conversions: -A value of external function type can be explicitly converted to ``address`` -resulting in the address of the contract of the function. - A function type ``A`` is implicitly convertible to a function type ``B`` if and only if their parameter types are identical, their return types are identical, their internal/external property is identical and the state mutability of ``A`` @@ -616,8 +631,9 @@ just use ``f``, if you want to use its external form, use ``this.f``. Members: -Public (or external) functions have the following members: +External (or public) functions have the following members: +* ``.address`` returns the address of the contract of the function. * ``.selector`` returns the :ref:`ABI function selector ` * ``.gas(uint)`` returns a callable function object which, when called, will send the specified amount of gas to the target function. See :ref:`External Function Calls ` for more information. * ``.value(uint)`` returns a callable function object which, when called, will send the specified amount of wei to the target function. See :ref:`External Function Calls ` for more information. @@ -629,6 +645,7 @@ Example that shows how to use the members:: contract Example { function f() public payable returns (bytes4) { + assert(this.f.address == address(this)); return this.f.selector; } diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index fe2d316ba31a..9921c1c1b0e3 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -14,7 +14,7 @@ Using the Commandline Compiler One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler. Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage. -If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``. +If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast-json --asm sourceFile.sol``. Before you deploy your contract, activate the optimizer when compiling using ``solc --optimize --bin sourceFile.sol``. By default, the optimizer will optimize the contract assuming it is called 200 times across its lifetime @@ -222,7 +222,6 @@ Input Description "constantOptimizer": false, // The new Yul optimizer. Mostly operates on the code of ABIEncoderV2. // It can only be activated through the details here. - // This feature is still considered experimental. "yul": false, // Tuning options for the Yul optimizer. "yulDetails": { @@ -236,10 +235,25 @@ Input Description // Affects type checking and code generation. Can be homestead, // tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin "evmVersion": "byzantium", + // Optional: Debugging settings + "debug": { + // How to treat revert (and require) reason strings. Settings are + // "default", "strip", "debug" and "verboseDebug". + // "default" does not inject compiler-generated revert strings and keeps user-supplied ones. + // "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects + // "debug" injects strings for compiler-generated internal reverts (not yet implemented) + // "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented) + "revertStrings": "default" + } // Metadata settings (optional) "metadata": { // Use only literal content and not URLs (false by default) - "useLiteralContent": true + "useLiteralContent": true, + // Use the given hash method for the metadata hash that is appended to the bytecode. + // The metadata hash can be removed from the bytecode via option "none". + // The other options are "ipfs" and "bzzr1". + // If the option is omitted, "ipfs" is used by default. + "bytecodeHash": "ipfs" }, // Addresses of the libraries. If not all libraries are given here, // it can result in unlinked objects whose output data is different. diff --git a/docs/yul.rst b/docs/yul.rst index 304a4b7e58e5..739a83996eb5 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -100,7 +100,8 @@ Grammar:: Expression | Switch | ForLoop | - BreakContinue + BreakContinue | + Leave FunctionDefinition = 'function' Identifier '(' TypedIdentifierList? ')' ( '->' TypedIdentifierList )? Block @@ -122,6 +123,7 @@ Grammar:: 'for' Block Expression Block Block BreakContinue = 'break' | 'continue' + Leave = 'leave' FunctionCall = Identifier '(' ( Expression ( ',' Expression )* )? ')' Identifier = [a-zA-Z_$] [a-zA-Z_$0-9.]* @@ -167,6 +169,7 @@ In all other situations, expressions have to evaluate to exactly one value. The ``continue`` and ``break`` statements can only be used inside loop bodies and have to be in the same function as the loop (or both have to be at the top level). +The ``leave`` statement can only be used inside a function. The condition part of the for-loop has to evaluate to exactly one value. Functions cannot be defined anywhere inside for loop init blocks. @@ -220,7 +223,7 @@ The two state objects are the global state object blockchain) and the local state object (the state of local variables, i.e. a segment of the stack in the EVM). If the AST node is a statement, E returns the two state objects and a "mode", -which is used for the ``break`` and ``continue`` statements. +which is used for the ``break``, ``continue`` and ``leave`` statements. If the AST node is an expression, E returns the two state objects and as many values as the expression evaluates to. @@ -261,12 +264,13 @@ We will use a destructuring notation for the AST nodes. G, L2, regular E(G, L, : ForLoop) = if n >= 1: - let G1, L1, mode = E(G, L, i1, ..., in) - // mode has to be regular due to the syntactic restrictions - let G2, L2, mode = E(G1, L1, for {} condition post body) - // mode has to be regular due to the syntactic restrictions - let L3 be the restriction of L2 to only variables of L - G2, L3, regular + let G1, L, mode = E(G, L, i1, ..., in) + // mode has to be regular or leave due to the syntactic restrictions + if mode is leave then + G1, L1 restricted to variables of L, leave + otherwise + let G2, L2, mode = E(G1, L1, for {} condition post body) + G2, L2 restricted to variables of L, mode else: let G1, L1, v = E(G, L, condition) if v is false: @@ -275,13 +279,20 @@ We will use a destructuring notation for the AST nodes. let G2, L2, mode = E(G1, L, body) if mode is break: G2, L2, regular + otherwise if mode is leave: + G2, L2, leave else: G3, L3, mode = E(G2, L2, post) - E(G3, L3, for {} condition post body) + if mode is leave: + G2, L3, leave + otherwise + E(G3, L3, for {} condition post body) E(G, L, break: BreakContinue) = G, L, break E(G, L, continue: BreakContinue) = G, L, continue + E(G, L, leave: Leave) = + G, L, leave E(G, L, : If) = let G0, L0, v = E(G, L, condition) if v is true: diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 9db1e3cf53e7..6128e1dd1a40 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -23,6 +23,7 @@ #pragma once +#include #include #include @@ -49,14 +50,27 @@ template std::vector& operator+=(std::vector& _a, U&& _ std::move(_b.begin(), _b.end(), std::back_inserter(_a)); return _a; } +/// Concatenate the contents of a container onto a multiset +template std::multiset& operator+=(std::multiset& _a, U const& _b) +{ + _a.insert(_b.begin(), _b.end()); + return _a; +} +/// Concatenate the contents of a container onto a multiset, move variant. +template std::multiset& operator+=(std::multiset& _a, U&& _b) +{ + for (auto&& x: _b) + _a.insert(std::move(x)); + return _a; +} /// Concatenate the contents of a container onto a set -template std::set& operator+=(std::set& _a, U const& _b) +template std::set& operator+=(std::set& _a, U const& _b) { _a.insert(_b.begin(), _b.end()); return _a; } /// Concatenate the contents of a container onto a set, move variant. -template std::set& operator+=(std::set& _a, U&& _b) +template std::set& operator+=(std::set& _a, U&& _b) { for (auto&& x: _b) _a.insert(std::move(x)); @@ -97,9 +111,27 @@ inline std::set operator+(std::set&& _a, U&& _b) ret += std::forward(_b); return ret; } -/// Remove one set from another one. -template -inline std::set& operator-=(std::set& _a, std::set const& _b) + +/// Remove the elements of a container from a set. +template +inline std::set& operator-=(std::set& _a, C const& _b) +{ + for (auto const& x: _b) + _a.erase(x); + return _a; +} + +template +inline std::set operator-(std::set const& _a, C const& _b) +{ + auto result = _a; + result -= _b; + return result; +} + +/// Remove the elements of a container from a multiset. +template +inline std::multiset& operator-=(std::multiset& _a, C const& _b) { for (auto const& x: _b) _a.erase(x); @@ -109,6 +141,21 @@ inline std::set& operator-=(std::set& _a, std::set const& _b) namespace dev { +template +T convertContainer(U const& _from) +{ + return T{_from.cbegin(), _from.cend()}; +} + +template +T convertContainer(U&& _from) +{ + return T{ + std::make_move_iterator(_from.begin()), + std::make_move_iterator(_from.end()) + }; +} + // String conversion functions, mainly to/from hex/nibble/byte representations. enum class WhenError @@ -260,6 +307,12 @@ bool contains(T const& _t, V const& _v) return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v); } +template +bool contains_if(T const& _t, Predicate const& _p) +{ + return std::end(_t) != std::find_if(std::begin(_t), std::end(_t), _p); +} + /// Function that iterates over a vector, calling a function on each of its /// elements. If that function returns a vector, the element is replaced by /// the returned vector. During the iteration, the original vector is only valid diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 0795671932f0..062ad672dfee 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -86,6 +86,7 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i) m_items.emplace_back(_i); if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty()) m_items.back().setLocation(m_currentSourceLocation); + m_items.back().m_modifierDepth = m_currentModifierDepth; return back(); } diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index ebf032ac1531..f79da0e7f4f9 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -181,6 +181,8 @@ class Assembly int m_deposit = 0; langutil::SourceLocation m_currentSourceLocation; +public: + size_t m_currentModifierDepth = 0; }; inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a) diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 95b09d47f1c0..c1d079bb1d56 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -146,6 +146,8 @@ class AssemblyItem std::string toAssemblyText() const; + size_t m_modifierDepth = 0; + private: AssemblyItemType m_type; Instruction m_instruction; ///< Only valid if m_type == Operation diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index 3ddb23cb115b..4d5b34b9f91f 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -68,7 +68,7 @@ string langutil::to_string(ScannerError _errorCode) { case ScannerError::NoError: return "No error."; case ScannerError::IllegalToken: return "Invalid token."; - case ScannerError::IllegalHexString: return "Expected even number of hex-nibbles within double-quotes."; + case ScannerError::IllegalHexString: return "Expected even number of hex-nibbles."; case ScannerError::IllegalHexDigit: return "Hexadecimal digit missing or invalid."; case ScannerError::IllegalCommentTerminator: return "Expected multi-line comment-terminator."; case ScannerError::IllegalEscapeSequence: return "Invalid escape sequence."; @@ -784,13 +784,25 @@ Token Scanner::scanHexString() char const quote = m_char; advance(); // consume quote LiteralScope literal(this, LITERAL_TYPE_STRING); + bool allowUnderscore = false; while (m_char != quote && !isSourcePastEndOfInput()) { char c = m_char; - if (!scanHexByte(c)) - // can only return false if hex-byte is incomplete (only one hex digit instead of two) + + if (scanHexByte(c)) + { + addLiteralChar(c); + allowUnderscore = true; + } + else if (c == '_') + { + advance(); + if (!allowUnderscore || m_char == quote) + return setError(ScannerError::IllegalNumberSeparator); + allowUnderscore = false; + } + else return setError(ScannerError::IllegalHexString); - addLiteralChar(c); } if (m_char != quote) diff --git a/liblangutil/Token.cpp b/liblangutil/Token.cpp index b03fa3185599..4ac548cf37db 100644 --- a/liblangutil/Token.cpp +++ b/liblangutil/Token.cpp @@ -72,6 +72,9 @@ void ElementaryTypeNameToken::assertDetails(Token _baseType, unsigned const& _fi "No elementary type " + string(TokenTraits::toString(_baseType)) + to_string(_first) + "x" + to_string(_second) + "." ); } + else + solAssert(_first == 0 && _second == 0, "Unexpected size arguments"); + m_token = _baseType; m_firstNumber = _first; m_secondNumber = _second; diff --git a/liblangutil/Token.h b/liblangutil/Token.h index c5e8295248cd..98d832fe3e1e 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -143,6 +143,7 @@ namespace langutil /* Inline Assembly Operators */ \ T(AssemblyAssign, ":=", 2) \ /* Keywords */ \ + K(Abstract, "abstract", 0) \ K(Anonymous, "anonymous", 0) \ K(As, "as", 0) \ K(Assembly, "assembly", 0) \ @@ -157,6 +158,7 @@ namespace langutil K(Emit, "emit", 0) \ K(Event, "event", 0) \ K(External, "external", 0) \ + K(Fallback, "fallback", 0) \ K(For, "for", 0) \ K(Function, "function", 0) \ K(Hex, "hex", 0) \ @@ -171,11 +173,13 @@ namespace langutil K(Memory, "memory", 0) \ K(Modifier, "modifier", 0) \ K(New, "new", 0) \ + K(Override, "override", 0) \ K(Payable, "payable", 0) \ K(Public, "public", 0) \ K(Pragma, "pragma", 0) \ K(Private, "private", 0) \ K(Pure, "pure", 0) \ + K(Receive, "receive", 0) \ K(Return, "return", 0) \ K(Returns, "returns", 0) \ K(Storage, "storage", 0) \ @@ -186,6 +190,7 @@ namespace langutil K(Using, "using", 0) \ K(Var, "var", 0) \ K(View, "view", 0) \ + K(Virtual, "virtual", 0) \ K(While, "while", 0) \ \ /* Ether subdenominations */ \ @@ -228,7 +233,6 @@ namespace langutil T(Identifier, nullptr, 0) \ \ /* Keywords reserved for future use. */ \ - K(Abstract, "abstract", 0) \ K(After, "after", 0) \ K(Alias, "alias", 0) \ K(Apply, "apply", 0) \ @@ -249,7 +253,6 @@ namespace langutil K(Mutable, "mutable", 0) \ K(NullLiteral, "null", 0) \ K(Of, "of", 0) \ - K(Override, "override", 0) \ K(Partial, "partial", 0) \ K(Promise, "promise", 0) \ K(Reference, "reference", 0) \ @@ -311,7 +314,7 @@ namespace TokenTraits constexpr bool isEtherSubdenomination(Token op) { return op == Token::SubWei || op == Token::SubSzabo || op == Token::SubFinney || op == Token::SubEther; } constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; } - constexpr bool isReservedKeyword(Token op) { return (Token::Abstract <= op && op <= Token::Unchecked); } + constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Unchecked); } inline Token AssignmentToBinaryOp(Token op) { diff --git a/libsolc/CMakeLists.txt b/libsolc/CMakeLists.txt index 7fb4aa0507d4..5935015befa1 100644 --- a/libsolc/CMakeLists.txt +++ b/libsolc/CMakeLists.txt @@ -2,7 +2,7 @@ if (EMSCRIPTEN) # Specify which functions to export in soljson.js. # Note that additional Emscripten-generated methods needed by solc-js are # defined to be exported in cmake/EthCompilerSettings.cmake. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_solidity_license\",\"_solidity_version\",\"_solidity_compile\"]' -s RESERVED_FUNCTION_POINTERS=20") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_solidity_license\",\"_solidity_version\",\"_solidity_compile\",\"_solidity_alloc\",\"_solidity_free\",\"_solidity_reset\"]' -s RESERVED_FUNCTION_POINTERS=20") add_executable(soljson libsolc.cpp libsolc.h) target_link_libraries(soljson PRIVATE solidity) else() diff --git a/libsolc/libsolc.cpp b/libsolc/libsolc.cpp index 060baaa2ac96..23b9fb899ecf 100644 --- a/libsolc/libsolc.cpp +++ b/libsolc/libsolc.cpp @@ -27,6 +27,8 @@ #include #include +#include +#include #include #include "license.h" @@ -38,51 +40,78 @@ using namespace solidity; namespace { -ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = nullptr) +// The strings in this list must not be resized after they have been added here (via solidity_alloc()), because +// this may potentially change the pointer that was passed to the caller from solidity_alloc(). +static list solidityAllocations; + +/// Find the equivalent to @p _data in the list of allocations of solidity_alloc(), +/// removes it from the list and returns its value. +/// +/// If any invalid argument is being passed, it is considered a programming error +/// on the caller-side and hence, will call abort() then. +string takeOverAllocation(char const* _data) +{ + for (auto iter = begin(solidityAllocations); iter != end(solidityAllocations); ++iter) + if (iter->data() == _data) + { + string chunk = move(*iter); + solidityAllocations.erase(iter); + return chunk; + } + + abort(); +} + +/// Resizes a std::string to the proper length based on the occurrence of a zero terminator. +void truncateCString(string& _data) +{ + size_t pos = _data.find('\0'); + if (pos != string::npos) + _data.resize(pos); +} + +ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, void* _readContext) { ReadCallback::Callback readCallback; if (_readCallback) { - readCallback = [=](string const& _path) + readCallback = [=](string const& _kind, string const& _data) { char* contents_c = nullptr; char* error_c = nullptr; - _readCallback(_path.c_str(), &contents_c, &error_c); + _readCallback(_readContext, _kind.data(), _data.data(), &contents_c, &error_c); ReadCallback::Result result; result.success = true; if (!contents_c && !error_c) { result.success = false; - result.responseOrErrorMessage = "File not found."; + result.responseOrErrorMessage = "Callback not supported."; } if (contents_c) { result.success = true; - result.responseOrErrorMessage = string(contents_c); - free(contents_c); + result.responseOrErrorMessage = takeOverAllocation(contents_c); } if (error_c) { result.success = false; - result.responseOrErrorMessage = string(error_c); - free(error_c); + result.responseOrErrorMessage = takeOverAllocation(error_c); } + truncateCString(result.responseOrErrorMessage); return result; }; } return readCallback; } -string compile(string _input, CStyleReadFileCallback _readCallback = nullptr) +string compile(string _input, CStyleReadFileCallback _readCallback, void* _readContext) { - StandardCompiler compiler(wrapReadCallback(_readCallback)); - return compiler.compile(std::move(_input)); + StandardCompiler compiler(wrapReadCallback(_readCallback, _readContext)); + return compiler.compile(move(_input)); } } -static string s_outputBuffer; - extern "C" { extern char const* solidity_license() noexcept @@ -90,20 +119,40 @@ extern char const* solidity_license() noexcept static string fullLicenseText = otherLicenses + licenseText; return fullLicenseText.c_str(); } + extern char const* solidity_version() noexcept { return VersionString.c_str(); } -extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) noexcept + +extern char* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) noexcept { - s_outputBuffer = compile(_input, _readCallback); - return s_outputBuffer.c_str(); + return solidityAllocations.emplace_back(compile(_input, _readCallback, _readContext)).data(); } -extern void solidity_free() noexcept + +extern char* solidity_alloc(size_t _size) noexcept +{ + try + { + return solidityAllocations.emplace_back(_size, '\0').data(); + } + catch (...) + { + // most likely a std::bad_alloc(), if at all. + return nullptr; + } +} + +extern void solidity_free(char* _data) noexcept +{ + takeOverAllocation(_data); +} + +extern void solidity_reset() noexcept { // This is called right before each compilation, but not at the end, so additional memory // can be freed here. yul::YulStringRepository::reset(); - s_outputBuffer.clear(); + solidityAllocations.clear(); } } diff --git a/libsolc/libsolc.h b/libsolc/libsolc.h index 2c55c2342391..655c9e7b54a6 100644 --- a/libsolc/libsolc.h +++ b/libsolc/libsolc.h @@ -23,6 +23,7 @@ #pragma once #include +#include #ifdef __cplusplus #define SOLC_NOEXCEPT noexcept @@ -34,31 +35,65 @@ extern "C" { #endif -/// Callback used to retrieve additional source files. +/// Callback used to retrieve additional source files or data. /// -/// "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); +/// @param _context The readContext passed to solidity_compile. Can be NULL. +/// @param _kind The kind of callback (a string). +/// @param _data The data for the callback (a string). +/// @param o_contents A pointer to the contents of the file, if found. Allocated via solidity_alloc(). +/// @param o_error A pointer to an error message, if there is one. +/// +/// The file (as well as error) contents that is to be allocated by the callback +/// implementor must use the solidity_alloc() API to allocate its underlying +/// storage. Ownership is then transferred to the compiler which will take care +/// of the deallocation. +/// +/// If the callback is not supported, *o_contents and *o_error must be set to NULL. +typedef void (*CStyleReadFileCallback)(void* _context, char const* _kind, char const* _data, char** o_contents, char** o_error); /// Returns the complete license document. /// -/// The pointer returned must not be freed by the caller. +/// The pointer returned must NOT be freed by the caller. char const* solidity_license() SOLC_NOEXCEPT; /// Returns the compiler version. /// -/// The pointer returned must not be freed by the caller. +/// The pointer returned must NOT be freed by the caller. char const* solidity_version() SOLC_NOEXCEPT; +/// Allocates a chunk of memory of @p _size bytes. +/// +/// Use this function inside callbacks to allocate data that is to be passed to +/// the compiler. You may use solidity_free() or solidity_reset() to free this +/// memory again but it is not required as the compiler takes ownership for any +/// data passed to it via callbacks. +/// +/// This function will return NULL if the requested memory region could not be allocated. +char* solidity_alloc(size_t _size) SOLC_NOEXCEPT; + +/// Explicitly frees the memory (@p _data) that was being allocated with solidity_alloc() +/// or returned by a call to solidity_compile(). +/// +/// Important, this call will abort() in case of any invalid argument being passed to this call. +void solidity_free(char* _data) SOLC_NOEXCEPT; + /// Takes a "Standard Input JSON" and an optional callback (can be set to null). Returns /// a "Standard Output JSON". Both are to be UTF-8 encoded. /// -/// The pointer returned must not be freed by the caller. -char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) SOLC_NOEXCEPT; +/// @param _input The input JSON to process. +/// @param _readCallback The optional callback pointer. Can be NULL, but if not NULL, +/// it can be called by the compiler to request additional input. +/// Please see the documentation of the type for details. +/// @param _readContext An optional context pointer passed to _readCallback. Can be NULL. +/// +/// @returns A pointer to the result. The pointer returned must be freed by the caller using solidity_free() or solidity_reset(). +char* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) SOLC_NOEXCEPT; /// Frees up any allocated memory. /// -/// NOTE: the pointer returned by solidity_compile is invalid after calling this! -void solidity_free() SOLC_NOEXCEPT; +/// NOTE: the pointer returned by solidity_compile as well as any other pointer retrieved via solidity_alloc() +/// is invalid after calling this! +void solidity_reset() SOLC_NOEXCEPT; #ifdef __cplusplus } diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 3de6b68ec386..87eb0e9b7b5b 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -18,6 +18,8 @@ set(sources analysis/GlobalContext.h analysis/NameAndTypeResolver.cpp analysis/NameAndTypeResolver.h + analysis/OverrideChecker.cpp + analysis/OverrideChecker.h analysis/PostTypeChecker.cpp analysis/PostTypeChecker.h analysis/ReferencesResolver.cpp @@ -39,8 +41,6 @@ set(sources ast/ASTForward.h ast/ASTJsonConverter.cpp ast/ASTJsonConverter.h - ast/ASTPrinter.cpp - ast/ASTPrinter.h ast/ASTUtils.cpp ast/ASTUtils.h ast/ASTVisitor.h @@ -107,6 +107,7 @@ set(sources interface/ABI.h interface/CompilerStack.cpp interface/CompilerStack.h + interface/DebugSettings.h interface/GasEstimator.cpp interface/GasEstimator.h interface/Natspec.cpp diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index 95626bb2ab4f..069bd4d94489 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -26,6 +26,7 @@ #include #include #include +#include using namespace std; @@ -33,20 +34,33 @@ using namespace dev; using namespace langutil; using namespace dev::solidity; +namespace +{ + +template +bool hasEqualNameAndParameters(T const& _a, B const& _b) +{ + return + _a.name() == _b.name() && + FunctionType(_a).asCallableFunction(false)->hasEqualParameterTypes( + *FunctionType(_b).asCallableFunction(false) + ); +} + +} bool ContractLevelChecker::check(ContractDefinition const& _contract) { checkDuplicateFunctions(_contract); checkDuplicateEvents(_contract); - checkIllegalOverrides(_contract); - checkAbstractFunctions(_contract); + m_overrideChecker.check(_contract); checkBaseConstructorArguments(_contract); - checkConstructor(_contract); - checkFallbackFunction(_contract); + checkAbstractFunctions(_contract); checkExternalTypeClashes(_contract); checkHashCollisions(_contract); checkLibraryRequirements(_contract); checkBaseABICompatibility(_contract); + checkPayableFallbackWithoutReceive(_contract); return Error::containsOnlyWarnings(m_errorReporter.errors()); } @@ -58,6 +72,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co map> functions; FunctionDefinition const* constructor = nullptr; FunctionDefinition const* fallback = nullptr; + FunctionDefinition const* receive = nullptr; for (FunctionDefinition const* function: _contract.definedFunctions()) if (function->isConstructor()) { @@ -79,6 +94,16 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co ); fallback = function; } + else if (function->isReceive()) + { + if (receive) + m_errorReporter.declarationError( + function->location(), + SecondarySourceLocation().append("Another declaration is here:", receive->location()), + "Only one receive function is allowed." + ); + receive = function; + } else { solAssert(!function->name().empty(), ""); @@ -111,9 +136,7 @@ void ContractLevelChecker::findDuplicateDefinitions(map> const SecondarySourceLocation ssl; for (size_t j = i + 1; j < overloads.size(); ++j) - if (FunctionType(*overloads[i]).asCallableFunction(false)->hasEqualParameterTypes( - *FunctionType(*overloads[j]).asCallableFunction(false)) - ) + if (hasEqualNameAndParameters(*overloads[i], *overloads[j])) { ssl.append("Other declaration is here:", overloads[j]->location()); reported.insert(j); @@ -133,87 +156,6 @@ void ContractLevelChecker::findDuplicateDefinitions(map> const } } -void ContractLevelChecker::checkIllegalOverrides(ContractDefinition const& _contract) -{ - // TODO unify this at a later point. for this we need to put the constness and the access specifier - // into the types - map> functions; - map modifiers; - - // We search from derived to base, so the stored item causes the error. - for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) - { - for (FunctionDefinition const* function: contract->definedFunctions()) - { - if (function->isConstructor()) - continue; // constructors can neither be overridden nor override anything - string const& name = function->name(); - if (modifiers.count(name)) - m_errorReporter.typeError(modifiers[name]->location(), "Override changes function to modifier."); - - for (FunctionDefinition const* overriding: functions[name]) - checkFunctionOverride(*overriding, *function); - - functions[name].push_back(function); - } - for (ModifierDefinition const* modifier: contract->functionModifiers()) - { - string const& name = modifier->name(); - ModifierDefinition const*& override = modifiers[name]; - if (!override) - override = modifier; - else if (ModifierType(*override) != ModifierType(*modifier)) - m_errorReporter.typeError(override->location(), "Override changes modifier signature."); - if (!functions[name].empty()) - m_errorReporter.typeError(override->location(), "Override changes modifier to function."); - } - } -} - -void ContractLevelChecker::checkFunctionOverride(FunctionDefinition const& _function, FunctionDefinition const& _super) -{ - FunctionTypePointer functionType = FunctionType(_function).asCallableFunction(false); - FunctionTypePointer superType = FunctionType(_super).asCallableFunction(false); - - if (!functionType->hasEqualParameterTypes(*superType)) - return; - if (!functionType->hasEqualReturnTypes(*superType)) - overrideError(_function, _super, "Overriding function return types differ."); - - if (!_function.annotation().superFunction) - _function.annotation().superFunction = &_super; - - if (_function.visibility() != _super.visibility()) - { - // Visibility change from external to public is fine. - // Any other change is disallowed. - if (!( - _super.visibility() == FunctionDefinition::Visibility::External && - _function.visibility() == FunctionDefinition::Visibility::Public - )) - overrideError(_function, _super, "Overriding function visibility differs."); - } - if (_function.stateMutability() != _super.stateMutability()) - overrideError( - _function, - _super, - "Overriding function changes state mutability from \"" + - stateMutabilityToString(_super.stateMutability()) + - "\" to \"" + - stateMutabilityToString(_function.stateMutability()) + - "\"." - ); -} - -void ContractLevelChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message) -{ - m_errorReporter.typeError( - function.location(), - SecondarySourceLocation().append("Overridden function is here:", super.location()), - message - ); -} - void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _contract) { // Mapping from name to function definition (exactly one per argument type equality class) and @@ -230,11 +172,6 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con }); if (it == overloads.end()) overloads.emplace_back(_type, _implemented); - else if (it->second) - { - if (!_implemented) - m_errorReporter.typeError(_declaration.location(), "Redeclaring an already implemented function as abstract"); - } else if (_implemented) it->second = true; }; @@ -257,6 +194,8 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con } // Set to not fully implemented if at least one flag is false. + // Note that `_contract.annotation().unimplementedFunctions` has already been + // pre-filled by `checkBaseConstructorArguments`. for (auto const& it: functions) for (auto const& funAndFlag: it.second) if (!funAndFlag.second) @@ -266,6 +205,32 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con _contract.annotation().unimplementedFunctions.push_back(function); break; } + + if (_contract.abstract()) + { + if (_contract.contractKind() == ContractDefinition::ContractKind::Interface) + m_errorReporter.typeError(_contract.location(), "Interfaces do not need the \"abstract\" keyword, they are abstract implicitly."); + else if (_contract.contractKind() == ContractDefinition::ContractKind::Library) + m_errorReporter.typeError(_contract.location(), "Libraries cannot be abstract."); + else + solAssert(_contract.contractKind() == ContractDefinition::ContractKind::Contract, ""); + } + + // For libraries, we emit errors on function-level, so this is fine as long as we do + // not have inheritance for libraries. + if ( + _contract.contractKind() == ContractDefinition::ContractKind::Contract && + !_contract.abstract() && + !_contract.annotation().unimplementedFunctions.empty() + ) + { + SecondarySourceLocation ssl; + for (auto function: _contract.annotation().unimplementedFunctions) + ssl.append("Missing implementation:", function->location()); + m_errorReporter.typeError(_contract.location(), ssl, + "Contract \"" + _contract.annotation().canonicalName + + "\" should be marked as abstract."); + } } @@ -358,48 +323,6 @@ void ContractLevelChecker::annotateBaseConstructorArguments( } -void ContractLevelChecker::checkConstructor(ContractDefinition const& _contract) -{ - FunctionDefinition const* constructor = _contract.constructor(); - if (!constructor) - return; - - if (!constructor->returnParameters().empty()) - m_errorReporter.typeError(constructor->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); - if (constructor->stateMutability() != StateMutability::NonPayable && constructor->stateMutability() != StateMutability::Payable) - m_errorReporter.typeError( - constructor->location(), - "Constructor must be payable or non-payable, but is \"" + - stateMutabilityToString(constructor->stateMutability()) + - "\"." - ); - if (constructor->visibility() != FunctionDefinition::Visibility::Public && constructor->visibility() != FunctionDefinition::Visibility::Internal) - m_errorReporter.typeError(constructor->location(), "Constructor must be public or internal."); -} - -void ContractLevelChecker::checkFallbackFunction(ContractDefinition const& _contract) -{ - FunctionDefinition const* fallback = _contract.fallbackFunction(); - if (!fallback) - return; - - if (_contract.isLibrary()) - m_errorReporter.typeError(fallback->location(), "Libraries cannot have fallback functions."); - if (fallback->stateMutability() != StateMutability::NonPayable && fallback->stateMutability() != StateMutability::Payable) - m_errorReporter.typeError( - fallback->location(), - "Fallback function must be payable or non-payable, but is \"" + - stateMutabilityToString(fallback->stateMutability()) + - "\"." - ); - if (!fallback->parameters().empty()) - m_errorReporter.typeError(fallback->parameterList().location(), "Fallback function cannot take parameters."); - if (!fallback->returnParameters().empty()) - m_errorReporter.typeError(fallback->returnParameterList()->location(), "Fallback function cannot return values."); - if (fallback->visibility() != FunctionDefinition::Visibility::External) - m_errorReporter.typeError(fallback->location(), "Fallback function must be defined as \"external\"."); -} - void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _contract) { map>> externalDeclarations; @@ -493,7 +416,7 @@ void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _ for (TypePointer const& paramType: func.second->parameterTypes() + func.second->parameterTypes()) if (!TypeChecker::typeSupportedByOldABIEncoder(*paramType, false)) { - errors.append("Type only supported by the new experimental ABI encoder", currentLoc); + errors.append("Type only supported by ABIEncoderV2", currentLoc); break; } } @@ -504,9 +427,20 @@ void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _ errors, std::string("Contract \"") + _contract.name() + - "\" does not use the new experimental ABI encoder but wants to inherit from a contract " + + "\" does not use ABIEncoderV2 but wants to inherit from a contract " + "which uses types that require it. " + "Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature." ); } + +void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition const& _contract) +{ + if (auto const* fallback = _contract.fallbackFunction()) + if (fallback->isPayable() && !_contract.interfaceFunctionList().empty() && !_contract.receiveFunction()) + m_errorReporter.warning( + _contract.location(), + "This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function.", + SecondarySourceLocation{}.append("The payable fallback function is defined here.", fallback->location()) + ); +} diff --git a/libsolidity/analysis/ContractLevelChecker.h b/libsolidity/analysis/ContractLevelChecker.h index f2cd9887f89b..b9ac5b7a962d 100644 --- a/libsolidity/analysis/ContractLevelChecker.h +++ b/libsolidity/analysis/ContractLevelChecker.h @@ -22,7 +22,11 @@ #pragma once #include +#include +#include #include +#include +#include namespace langutil { @@ -41,8 +45,10 @@ namespace solidity class ContractLevelChecker { public: + /// @param _errorReporter provides the error logging functionality. explicit ContractLevelChecker(langutil::ErrorReporter& _errorReporter): + m_overrideChecker{_errorReporter}, m_errorReporter(_errorReporter) {} @@ -57,20 +63,15 @@ class ContractLevelChecker void checkDuplicateEvents(ContractDefinition const& _contract); template void findDuplicateDefinitions(std::map> const& _definitions, std::string _message); - void checkIllegalOverrides(ContractDefinition const& _contract); - /// Reports a type error with an appropriate message if overridden 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 checkAbstractFunctions(ContractDefinition const& _contract); + /// Checks that the base constructor arguments are properly provided. + /// Fills the list of unimplemented functions in _contract's annotations. void checkBaseConstructorArguments(ContractDefinition const& _contract); void annotateBaseConstructorArguments( ContractDefinition const& _currentContract, FunctionDefinition const* _baseConstructor, ASTNode const* _argumentNode ); - void checkConstructor(ContractDefinition const& _contract); - void checkFallbackFunction(ContractDefinition const& _contract); /// Checks that different functions with external visibility end up having different /// external argument types (i.e. different signature). void checkExternalTypeClashes(ContractDefinition const& _contract); @@ -81,6 +82,10 @@ class ContractLevelChecker /// Checks base contracts for ABI compatibility void checkBaseABICompatibility(ContractDefinition const& _contract); + /// Warns if the contract has a payable fallback, but no receive ether function. + void checkPayableFallbackWithoutReceive(ContractDefinition const& _contract); + + OverrideChecker m_overrideChecker; langutil::ErrorReporter& m_errorReporter; }; diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index 37d8c824f517..235118604f2b 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -85,6 +85,18 @@ bool ControlFlowBuilder::visit(Conditional const& _conditional) return false; } +bool ControlFlowBuilder::visit(TryStatement const& _tryStatement) +{ + appendControlFlow(_tryStatement.externalCall()); + + auto nodes = splitFlow(_tryStatement.clauses().size()); + for (size_t i = 0; i < _tryStatement.clauses().size(); ++i) + nodes[i] = createFlow(nodes[i], _tryStatement.clauses()[i]->block()); + mergeFlow(nodes); + + return false; +} + bool ControlFlowBuilder::visit(IfStatement const& _ifStatement) { solAssert(!!m_currentNode, ""); @@ -384,7 +396,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration) _variableDeclaration.value().get() ); // Function arguments are considered to be immediately assigned as well (they are "externally assigned"). - else if (_variableDeclaration.isCallableParameter() && !_variableDeclaration.isReturnParameter()) + else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter()) m_currentNode->variableOccurrences.emplace_back( _variableDeclaration, VariableOccurrence::Kind::Assignment, diff --git a/libsolidity/analysis/ControlFlowBuilder.h b/libsolidity/analysis/ControlFlowBuilder.h index b17483954b48..91b112eb5873 100644 --- a/libsolidity/analysis/ControlFlowBuilder.h +++ b/libsolidity/analysis/ControlFlowBuilder.h @@ -45,6 +45,7 @@ class ControlFlowBuilder: private ASTConstVisitor // Visits for constructing the control flow. bool visit(BinaryOperation const& _operation) override; bool visit(Conditional const& _conditional) override; + bool visit(TryStatement const& _tryStatement) override; bool visit(IfStatement const& _ifStatement) override; bool visit(ForStatement const& _forStatement) override; bool visit(WhileStatement const& _whileStatement) override; @@ -98,12 +99,27 @@ class ControlFlowBuilder: private ASTConstVisitor return result; } + /// Splits the control flow starting at the current node into @a _n paths. + /// m_currentNode is set to nullptr and has to be set manually or + /// using mergeFlow later. + std::vector splitFlow(size_t n) + { + std::vector result(n); + for (auto& node: result) + { + node = m_nodeContainer.newNode(); + connect(m_currentNode, node); + } + m_currentNode = nullptr; + return result; + } + /// Merges the control flow of @a _nodes to @a _endNode. /// If @a _endNode is nullptr, a new node is creates and used as end node. /// Sets the merge destination as current node. /// Note: @a _endNode may be one of the nodes in @a _nodes. - template - void mergeFlow(std::array const& _nodes, CFGNode* _endNode = nullptr) + template + void mergeFlow(C const& _nodes, CFGNode* _endNode = nullptr) { CFGNode* mergeDestination = (_endNode == nullptr) ? m_nodeContainer.newNode() : _endNode; for (auto& node: _nodes) diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index 108a18564d45..3bcc87660482 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -129,9 +129,36 @@ void DocStringAnalyser::parseDocStrings( m_errorOccured = true; _annotation.docTags = parser.tags(); } + + size_t returnTagsVisited = 0; for (auto const& docTag: _annotation.docTags) + { if (!_validTags.count(docTag.first)) - appendError("Doc tag @" + docTag.first + " not valid for " + _nodeName + "."); + appendError("Documentation tag @" + docTag.first + " not valid for " + _nodeName + "."); + else + if (docTag.first == "return") + { + returnTagsVisited++; + if (auto* function = dynamic_cast(&_node)) + { + string content = docTag.second.content; + string firstWord = content.substr(0, content.find_first_of(" \t")); + + if (returnTagsVisited > function->returnParameters().size()) + appendError("Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" + + " exceedes the number of return parameters." + ); + else + { + auto parameter = function->returnParameters().at(returnTagsVisited - 1); + if (!parameter->name().empty() && parameter->name() != firstWord) + appendError("Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" + + " does not contain the name of its return parameter." + ); + } + } + } + } } void DocStringAnalyser::appendError(string const& _description) diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 008e37bdbaa4..21f8a5d19194 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -346,12 +346,20 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) solAssert(conflictingDeclaration, ""); // Usual shadowing is not an error - if (dynamic_cast(declaration) && dynamic_cast(conflictingDeclaration)) + if ( + dynamic_cast(declaration) && + dynamic_cast(conflictingDeclaration) + ) continue; - // Usual shadowing is not an error - if (dynamic_cast(declaration) && dynamic_cast(conflictingDeclaration)) - continue; + // Public state variable can override functions + if (auto varDecl = dynamic_cast(conflictingDeclaration)) + if ( + dynamic_cast(declaration) && + varDecl->isStateVariable() && + varDecl->isPublic() + ) + continue; if (declaration->location().start < conflictingDeclaration->location().start) { @@ -572,6 +580,7 @@ bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract) m_globalContext.setCurrentContract(_contract); m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, false, true); m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, false, true); + m_currentContract = &_contract; registerDeclaration(_contract, true); _contract.annotation().canonicalName = currentCanonicalName(); @@ -580,6 +589,7 @@ bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract) void DeclarationRegistrationHelper::endVisit(ContractDefinition&) { + m_currentContract = nullptr; closeCurrentScope(); } @@ -617,6 +627,7 @@ bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) { registerDeclaration(_function, true); m_currentFunction = &_function; + _function.annotation().contract = m_currentContract; return true; } @@ -626,6 +637,18 @@ void DeclarationRegistrationHelper::endVisit(FunctionDefinition&) closeCurrentScope(); } +bool DeclarationRegistrationHelper::visit(TryCatchClause& _tryCatchClause) +{ + _tryCatchClause.setScope(m_currentScope); + enterNewSubScope(_tryCatchClause); + return true; +} + +void DeclarationRegistrationHelper::endVisit(TryCatchClause&) +{ + closeCurrentScope(); +} + bool DeclarationRegistrationHelper::visit(ModifierDefinition& _modifier) { registerDeclaration(_modifier, true); diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 0c4452cde3f0..4499071bc841 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -181,6 +181,8 @@ class DeclarationRegistrationHelper: private ASTVisitor bool visit(EnumValue& _value) override; bool visit(FunctionDefinition& _function) override; void endVisit(FunctionDefinition& _function) override; + bool visit(TryCatchClause& _tryCatchClause) override; + void endVisit(TryCatchClause& _tryCatchClause) override; bool visit(ModifierDefinition& _modifier) override; void endVisit(ModifierDefinition& _modifier) override; bool visit(FunctionTypeName& _funTypeName) override; @@ -206,6 +208,7 @@ class DeclarationRegistrationHelper: private ASTVisitor std::map>& m_scopes; ASTNode const* m_currentScope = nullptr; VariableScope* m_currentFunction = nullptr; + ContractDefinition const* m_currentContract = nullptr; langutil::ErrorReporter& m_errorReporter; GlobalContext& m_globalContext; }; diff --git a/libsolidity/analysis/OverrideChecker.cpp b/libsolidity/analysis/OverrideChecker.cpp new file mode 100644 index 000000000000..addb5dd9b403 --- /dev/null +++ b/libsolidity/analysis/OverrideChecker.cpp @@ -0,0 +1,857 @@ +/* + 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 . +*/ +/** + * Component that verifies overloads, abstract contracts, function clashes and others + * checks at contract or function level. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + + +using namespace std; +using namespace dev; +using namespace langutil; +using namespace dev::solidity; + +namespace +{ + +// Helper struct to do a search by name +struct MatchByName +{ + string const& m_name; + bool operator()(OverrideProxy const& _item) + { + return _item.name() == m_name; + } +}; + +/** + * Construct the override graph for this signature. + * Reserve node 0 for the current contract and node + * 1 for an artificial top node to which all override paths + * connect at the end. + */ +struct OverrideGraph +{ + OverrideGraph(set const& _baseCallables) + { + for (auto const& baseFunction: _baseCallables) + addEdge(0, visit(baseFunction)); + } + std::map nodes; + std::map nodeInv; + std::map> edges; + int numNodes = 2; + void addEdge(int _a, int _b) + { + edges[_a].insert(_b); + edges[_b].insert(_a); + } +private: + /// Completes the graph starting from @a _function and + /// @returns the node ID. + int visit(OverrideProxy const& _function) + { + auto it = nodes.find(_function); + if (it != nodes.end()) + return it->second; + int currentNode = numNodes++; + nodes[_function] = currentNode; + nodeInv[currentNode] = _function; + if (_function.overrides()) + for (auto const& baseFunction: _function.baseFunctions()) + addEdge(currentNode, visit(baseFunction)); + else + addEdge(currentNode, 1); + + return currentNode; + } +}; + +/** + * Detect cut vertices following https://en.wikipedia.org/wiki/Biconnected_component#Pseudocode + * Can ignore the root node, since it is never a cut vertex in our case. + */ +struct CutVertexFinder +{ + CutVertexFinder(OverrideGraph const& _graph): m_graph(_graph) + { + run(); + } + std::set const& cutVertices() const { return m_cutVertices; } + +private: + OverrideGraph const& m_graph; + + std::vector m_visited = std::vector(m_graph.numNodes, false); + std::vector m_depths = std::vector(m_graph.numNodes, -1); + std::vector m_low = std::vector(m_graph.numNodes, -1); + std::vector m_parent = std::vector(m_graph.numNodes, -1); + std::set m_cutVertices{}; + + void run(int _u = 0, int _depth = 0) + { + m_visited.at(_u) = true; + m_depths.at(_u) = m_low.at(_u) = _depth; + for (int v: m_graph.edges.at(_u)) + if (!m_visited.at(v)) + { + m_parent[v] = _u; + run(v, _depth + 1); + if (m_low[v] >= m_depths[_u] && m_parent[_u] != -1) + m_cutVertices.insert(m_graph.nodeInv.at(_u)); + m_low[_u] = min(m_low[_u], m_low[v]); + } + else if (v != m_parent[_u]) + m_low[_u] = min(m_low[_u], m_depths[v]); + } +}; + +vector resolveDirectBaseContracts(ContractDefinition const& _contract) +{ + vector resolvedContracts; + + for (ASTPointer const& specifier: _contract.baseContracts()) + { + Declaration const* baseDecl = + specifier->name().annotation().referencedDeclaration; + auto contract = dynamic_cast(baseDecl); + solAssert(contract, "contract is null"); + resolvedContracts.emplace_back(contract); + } + + return resolvedContracts; +} + +vector> sortByContract(vector> const& _list) +{ + auto sorted = _list; + + stable_sort(sorted.begin(), sorted.end(), + [] (ASTPointer _a, ASTPointer _b) { + if (!_a || !_b) + return _a < _b; + + Declaration const* aDecl = _a->annotation().referencedDeclaration; + Declaration const* bDecl = _b->annotation().referencedDeclaration; + + if (!aDecl || !bDecl) + return aDecl < bDecl; + + return aDecl->id() < bDecl->id(); + } + ); + + return sorted; +} + +OverrideProxy makeOverrideProxy(CallableDeclaration const& _callable) +{ + if (auto const* fun = dynamic_cast(&_callable)) + return OverrideProxy{fun}; + else if (auto const* mod = dynamic_cast(&_callable)) + return OverrideProxy{mod}; + else + solAssert(false, "Invalid call to makeOverrideProxy."); + return {}; +} + +} + +bool OverrideProxy::operator<(OverrideProxy const& _other) const +{ + return id() < _other.id(); +} + +bool OverrideProxy::isVariable() const +{ + return holds_alternative(m_item); +} + +bool OverrideProxy::isFunction() const +{ + return holds_alternative(m_item); +} + +bool OverrideProxy::isModifier() const +{ + return holds_alternative(m_item); +} + +bool OverrideProxy::CompareBySignature::operator()(OverrideProxy const& _a, OverrideProxy const& _b) const +{ + return _a.overrideComparator() < _b.overrideComparator(); +} + +size_t OverrideProxy::id() const +{ + return std::visit(GenericVisitor{ + [&](auto const* _item) -> size_t { return _item->id(); } + }, m_item); +} + +shared_ptr OverrideProxy::overrides() const +{ + return std::visit(GenericVisitor{ + [&](auto const* _item) { return _item->overrides(); } + }, m_item); +} + +set OverrideProxy::baseFunctions() const +{ + return std::visit(GenericVisitor{ + [&](auto const* _item) -> set { + set ret; + for (auto const* f: _item->annotation().baseFunctions) + ret.insert(makeOverrideProxy(*f)); + return ret; + } + }, m_item); +} + +void OverrideProxy::storeBaseFunction(OverrideProxy const& _base) const +{ + std::visit(GenericVisitor{ + [&](FunctionDefinition const* _item) { + _item->annotation().baseFunctions.emplace(std::get(_base.m_item)); + }, + [&](ModifierDefinition const* _item) { + _item->annotation().baseFunctions.emplace(std::get(_base.m_item)); + }, + [&](VariableDeclaration const* _item) { + _item->annotation().baseFunctions.emplace(std::get(_base.m_item)); + } + }, m_item); +} + +string const& OverrideProxy::name() const +{ + return std::visit(GenericVisitor{ + [&](auto const* _item) -> string const& { return _item->name(); } + }, m_item); +} + +ContractDefinition const& OverrideProxy::contract() const +{ + return std::visit(GenericVisitor{ + [&](auto const* _item) -> ContractDefinition const& { + return dynamic_cast(*_item->scope()); + } + }, m_item); +} + +string const& OverrideProxy::contractName() const +{ + return contract().name(); +} + +Visibility OverrideProxy::visibility() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const* _item) { return _item->visibility(); }, + [&](ModifierDefinition const* _item) { return _item->visibility(); }, + [&](VariableDeclaration const*) { return Visibility::External; } + }, m_item); +} + +StateMutability OverrideProxy::stateMutability() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const* _item) { return _item->stateMutability(); }, + [&](ModifierDefinition const*) { solAssert(false, "Requested state mutability from modifier."); return StateMutability{}; }, + [&](VariableDeclaration const*) { return StateMutability::View; } + }, m_item); +} + +bool OverrideProxy::virtualSemantics() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const* _item) { return _item->virtualSemantics(); }, + [&](ModifierDefinition const* _item) { return _item->virtualSemantics(); }, + [&](VariableDeclaration const*) { return false; } + }, m_item); +} + +Token OverrideProxy::functionKind() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const* _item) { return _item->kind(); }, + [&](ModifierDefinition const*) { return Token::Function; }, + [&](VariableDeclaration const*) { return Token::Function; } + }, m_item); +} + +FunctionType const* OverrideProxy::functionType() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const* _item) { return FunctionType(*_item).asCallableFunction(false); }, + [&](VariableDeclaration const* _item) { return FunctionType(*_item).asCallableFunction(false); }, + [&](ModifierDefinition const*) -> FunctionType const* { solAssert(false, "Requested function type of modifier."); return nullptr; } + }, m_item); +} + +ModifierType const* OverrideProxy::modifierType() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const*) -> ModifierType const* { solAssert(false, "Requested modifier type of function."); return nullptr; }, + [&](VariableDeclaration const*) -> ModifierType const* { solAssert(false, "Requested modifier type of variable."); return nullptr; }, + [&](ModifierDefinition const* _modifier) -> ModifierType const* { return TypeProvider::modifier(*_modifier); } + }, m_item); +} + +SourceLocation const& OverrideProxy::location() const +{ + return std::visit(GenericVisitor{ + [&](auto const* _item) -> SourceLocation const& { return _item->location(); } + }, m_item); +} + +string OverrideProxy::astNodeName() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const*) { return "function"; }, + [&](ModifierDefinition const*) { return "modifier"; }, + [&](VariableDeclaration const*) { return "public state variable"; }, + }, m_item); +} + +string OverrideProxy::astNodeNameCapitalized() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const*) { return "Function"; }, + [&](ModifierDefinition const*) { return "Modifier"; }, + [&](VariableDeclaration const*) { return "Public state variable"; }, + }, m_item); +} + +string OverrideProxy::distinguishingProperty() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const*) { return "name and parameter types"; }, + [&](ModifierDefinition const*) { return "name"; }, + [&](VariableDeclaration const*) { return "name and parameter types"; }, + }, m_item); +} + +bool OverrideProxy::unimplemented() const +{ + return std::visit(GenericVisitor{ + [&](FunctionDefinition const* _item) { return !_item->isImplemented(); }, + [&](ModifierDefinition const*) { return false; }, + [&](VariableDeclaration const*) { return false; } + }, m_item); +} + +bool OverrideProxy::OverrideComparator::operator<(OverrideComparator const& _other) const +{ + if (name != _other.name) + return name < _other.name; + + if (!functionKind || !_other.functionKind) + return false; + + if (functionKind != _other.functionKind) + return *functionKind < *_other.functionKind; + + if (!parameterTypes || !_other.parameterTypes) + return false; + + return boost::lexicographical_compare(*parameterTypes, *_other.parameterTypes); +} + +OverrideProxy::OverrideComparator const& OverrideProxy::overrideComparator() const +{ + if (!m_comparator) + { + m_comparator = make_shared(std::visit(GenericVisitor{ + [&](FunctionDefinition const* _function) + { + vector paramTypes; + for (Type const* t: functionType()->parameterTypes()) + paramTypes.emplace_back(t->richIdentifier()); + return OverrideComparator{ + _function->name(), + _function->kind(), + std::move(paramTypes) + }; + }, + [&](VariableDeclaration const* _var) + { + vector paramTypes; + for (Type const* t: functionType()->parameterTypes()) + paramTypes.emplace_back(t->richIdentifier()); + return OverrideComparator{ + _var->name(), + Token::Function, + std::move(paramTypes) + }; + }, + [&](ModifierDefinition const* _mod) + { + return OverrideComparator{ + _mod->name(), + {}, + {} + }; + } + }, m_item)); + } + + return *m_comparator; +} + +bool OverrideChecker::CompareByID::operator()(ContractDefinition const* _a, ContractDefinition const* _b) const +{ + if (!_a || !_b) + return _a < _b; + + return _a->id() < _b->id(); +} + +void OverrideChecker::check(ContractDefinition const& _contract) +{ + checkIllegalOverrides(_contract); + checkAmbiguousOverrides(_contract); +} + +void OverrideChecker::checkIllegalOverrides(ContractDefinition const& _contract) +{ + OverrideProxyBySignatureMultiSet const& inheritedFuncs = inheritedFunctions(_contract); + OverrideProxyBySignatureMultiSet const& inheritedMods = inheritedModifiers(_contract); + + for (ModifierDefinition const* modifier: _contract.functionModifiers()) + { + if (contains_if(inheritedFuncs, MatchByName{modifier->name()})) + m_errorReporter.typeError( + modifier->location(), + "Override changes function or public state variable to modifier." + ); + + checkOverrideList(OverrideProxy{modifier}, inheritedMods); + } + + for (FunctionDefinition const* function: _contract.definedFunctions()) + { + if (function->isConstructor()) + continue; + + if (contains_if(inheritedMods, MatchByName{function->name()})) + m_errorReporter.typeError(function->location(), "Override changes modifier to function."); + + checkOverrideList(OverrideProxy{function}, inheritedFuncs); + } + for (auto const* stateVar: _contract.stateVariables()) + { + if (!stateVar->isPublic()) + continue; + + if (contains_if(inheritedMods, MatchByName{stateVar->name()})) + m_errorReporter.typeError(stateVar->location(), "Override changes modifier to public state variable."); + + checkOverrideList(OverrideProxy{stateVar}, inheritedFuncs); + } + +} + +void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverrideProxy const& _super) +{ + solAssert(_super.isModifier() == _overriding.isModifier(), ""); + + if (_super.isFunction() || _super.isModifier()) + _overriding.storeBaseFunction(_super); + + if (_overriding.isModifier() && *_overriding.modifierType() != *_super.modifierType()) + m_errorReporter.typeError( + _overriding.location(), + "Override changes modifier signature." + ); + + if (!_overriding.overrides()) + overrideError(_overriding, _super, "Overriding " + _overriding.astNodeName() + " is missing \"override\" specifier."); + + if (_super.isVariable()) + overrideError( + _super, + _overriding, + "Cannot override public state variable.", + "Overriding " + _overriding.astNodeName() + " is here:" + ); + else if (!_super.virtualSemantics()) + overrideError( + _super, + _overriding, + "Trying to override non-virtual " + _super.astNodeName() + ". Did you forget to add \"virtual\"?", + "Overriding " + _overriding.astNodeName() + " is here:" + ); + + if (_overriding.isVariable()) + { + if (_super.visibility() != Visibility::External) + overrideError(_overriding, _super, "Public state variables can only override functions with external visibility."); + solAssert(_overriding.visibility() == Visibility::External, ""); + } + else if (_overriding.visibility() != _super.visibility()) + { + // Visibility change from external to public is fine. + // Any other change is disallowed. + if (!( + _super.visibility() == Visibility::External && + _overriding.visibility() == Visibility::Public + )) + overrideError(_overriding, _super, "Overriding " + _overriding.astNodeName() + " visibility differs."); + } + + if (_super.isFunction()) + { + FunctionType const* functionType = _overriding.functionType(); + FunctionType const* superType = _super.functionType(); + + solAssert(functionType->hasEqualParameterTypes(*superType), "Override doesn't have equal parameters!"); + + if (!functionType->hasEqualReturnTypes(*superType)) + overrideError(_overriding, _super, "Overriding " + _overriding.astNodeName() + " return types differ."); + + // This is only relevant for a function overriding a function. + if (_overriding.isFunction()) + { + if (_overriding.stateMutability() != _super.stateMutability()) + overrideError( + _overriding, + _super, + "Overriding function changes state mutability from \"" + + stateMutabilityToString(_super.stateMutability()) + + "\" to \"" + + stateMutabilityToString(_overriding.stateMutability()) + + "\"." + ); + + if (_overriding.unimplemented() && !_super.unimplemented()) + overrideError( + _overriding, + _super, + "Overriding an implemented function with an unimplemented function is not allowed." + ); + } + } +} + +void OverrideChecker::overrideListError( + OverrideProxy const& _item, + set _secondary, + string const& _message1, + string const& _message2 +) +{ + // Using a set rather than a vector so the order is always the same + set names; + SecondarySourceLocation ssl; + for (Declaration const* c: _secondary) + { + ssl.append("This contract: ", c->location()); + names.insert("\"" + c->name() + "\""); + } + string contractSingularPlural = "contract "; + if (_secondary.size() > 1) + contractSingularPlural = "contracts "; + + m_errorReporter.typeError( + _item.overrides() ? _item.overrides()->location() : _item.location(), + ssl, + _message1 + + contractSingularPlural + + _message2 + + joinHumanReadable(names, ", ", " and ") + + "." + ); +} + +void OverrideChecker::overrideError(Declaration const& _overriding, Declaration const& _super, string const& _message, string const& _secondaryMsg) +{ + m_errorReporter.typeError( + _overriding.location(), + SecondarySourceLocation().append(_secondaryMsg, _super.location()), + _message + ); +} + + +void OverrideChecker::overrideError(OverrideProxy const& _overriding, OverrideProxy const& _super, string const& _message, string const& _secondaryMsg) +{ + m_errorReporter.typeError( + _overriding.location(), + SecondarySourceLocation().append(_secondaryMsg, _super.location()), + _message + ); +} + +void OverrideChecker::checkAmbiguousOverrides(ContractDefinition const& _contract) const +{ + { + // Fetch inherited functions and sort them by signature. + // We get at least one function per signature and direct base contract, which is + // enough because we re-construct the inheritance graph later. + OverrideProxyBySignatureMultiSet nonOverriddenFunctions = inheritedFunctions(_contract); + + // Remove all functions that match the signature of a function in the current contract. + for (FunctionDefinition const* f: _contract.definedFunctions()) + nonOverriddenFunctions.erase(OverrideProxy{f}); + for (VariableDeclaration const* v: _contract.stateVariables()) + if (v->isPublic()) + nonOverriddenFunctions.erase(OverrideProxy{v}); + + // Walk through the set of functions signature by signature. + for (auto it = nonOverriddenFunctions.cbegin(); it != nonOverriddenFunctions.cend();) + { + std::set baseFunctions; + for (auto nextSignature = nonOverriddenFunctions.upper_bound(*it); it != nextSignature; ++it) + baseFunctions.insert(*it); + + checkAmbiguousOverridesInternal(std::move(baseFunctions), _contract.location()); + } + } + + { + OverrideProxyBySignatureMultiSet modifiers = inheritedModifiers(_contract); + for (ModifierDefinition const* mod: _contract.functionModifiers()) + modifiers.erase(OverrideProxy{mod}); + + for (auto it = modifiers.cbegin(); it != modifiers.cend();) + { + std::set baseModifiers; + for (auto next = modifiers.upper_bound(*it); it != next; ++it) + baseModifiers.insert(*it); + + checkAmbiguousOverridesInternal(std::move(baseModifiers), _contract.location()); + } + + } +} + +void OverrideChecker::checkAmbiguousOverridesInternal(set _baseCallables, SourceLocation const& _location) const +{ + if (_baseCallables.size() <= 1) + return; + + OverrideGraph overrideGraph(_baseCallables); + CutVertexFinder cutVertexFinder{overrideGraph}; + + // Remove all base functions overridden by cut vertices (they don't need to be overridden). + for (OverrideProxy const& function: cutVertexFinder.cutVertices()) + { + std::set toTraverse = function.baseFunctions(); + while (!toTraverse.empty()) + { + OverrideProxy base = *toTraverse.begin(); + toTraverse.erase(toTraverse.begin()); + _baseCallables.erase(base); + for (OverrideProxy const& f: base.baseFunctions()) + toTraverse.insert(f); + } + // Remove unimplemented base functions at the cut vertices itself as well. + if (function.unimplemented()) + _baseCallables.erase(function); + } + + // If more than one function is left, they have to be overridden. + if (_baseCallables.size() <= 1) + return; + + SecondarySourceLocation ssl; + for (OverrideProxy const& baseFunction: _baseCallables) + ssl.append("Definition in \"" + baseFunction.contractName() + "\": ", baseFunction.location()); + + string callableName = _baseCallables.begin()->astNodeName(); + if (_baseCallables.begin()->isVariable()) + callableName = "function"; + string distinguishigProperty = _baseCallables.begin()->distinguishingProperty(); + + bool foundVariable = false; + for (auto const& base: _baseCallables) + if (base.isVariable()) + foundVariable = true; + + string message = + "Derived contract must override " + callableName + " \"" + + _baseCallables.begin()->name() + + "\". Two or more base classes define " + callableName + " with same " + distinguishigProperty + "."; + + if (foundVariable) + message += + " Since one of the bases defines a public state variable which cannot be overridden, " + "you have to change the inheritance layout or the names of the functions."; + + m_errorReporter.typeError(_location, ssl, message); +} + +set OverrideChecker::resolveOverrideList(OverrideSpecifier const& _overrides) const +{ + set resolved; + + for (ASTPointer const& override: _overrides.overrides()) + { + Declaration const* decl = override->annotation().referencedDeclaration; + solAssert(decl, "Expected declaration to be resolved."); + + // If it's not a contract it will be caught + // in the reference resolver + if (ContractDefinition const* contract = dynamic_cast(decl)) + resolved.insert(contract); + } + + return resolved; +} + +void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited) +{ + set specifiedContracts = + _item.overrides() ? + resolveOverrideList(*_item.overrides()) : + decltype(specifiedContracts){}; + + // Check for duplicates in override list + if (_item.overrides() && specifiedContracts.size() != _item.overrides()->overrides().size()) + { + // Sort by contract id to find duplicate for error reporting + vector> list = + sortByContract(_item.overrides()->overrides()); + + // Find duplicates and output error + for (size_t i = 1; i < list.size(); i++) + { + Declaration const* aDecl = list[i]->annotation().referencedDeclaration; + Declaration const* bDecl = list[i-1]->annotation().referencedDeclaration; + if (!aDecl || !bDecl) + continue; + + if (aDecl->id() == bDecl->id()) + { + SecondarySourceLocation ssl; + ssl.append("First occurrence here: ", list[i-1]->location()); + m_errorReporter.typeError( + list[i]->location(), + ssl, + "Duplicate contract \"" + + joinHumanReadable(list[i]->namePath(), ".") + + "\" found in override list of \"" + + _item.name() + + "\"." + ); + } + } + } + + set expectedContracts; + + // Build list of expected contracts + for (auto [begin, end] = _inherited.equal_range(_item); begin != end; begin++) + { + // Validate the override + checkOverride(_item, *begin); + + expectedContracts.insert(&begin->contract()); + } + + if (_item.overrides() && expectedContracts.empty()) + m_errorReporter.typeError( + _item.overrides()->location(), + _item.astNodeNameCapitalized() + " has override specified but does not override anything." + ); + + set missingContracts; + // If we expect only one contract, no contract needs to be specified + if (expectedContracts.size() > 1) + missingContracts = expectedContracts - specifiedContracts; + + if (!missingContracts.empty()) + overrideListError( + _item, + missingContracts, + _item.astNodeNameCapitalized() + " needs to specify overridden ", + "" + ); + + auto surplusContracts = specifiedContracts - expectedContracts; + if (!surplusContracts.empty()) + overrideListError( + _item, + surplusContracts, + "Invalid ", + "specified in override list: " + ); +} + +OverrideChecker::OverrideProxyBySignatureMultiSet const& OverrideChecker::inheritedFunctions(ContractDefinition const& _contract) const +{ + if (!m_inheritedFunctions.count(&_contract)) + { + OverrideProxyBySignatureMultiSet result; + + for (auto const* base: resolveDirectBaseContracts(_contract)) + { + set functionsInBase; + for (FunctionDefinition const* fun: base->definedFunctions()) + if (!fun->isConstructor()) + functionsInBase.emplace(OverrideProxy{fun}); + for (VariableDeclaration const* var: base->stateVariables()) + if (var->isPublic()) + functionsInBase.emplace(OverrideProxy{var}); + + for (OverrideProxy const& func: inheritedFunctions(*base)) + functionsInBase.insert(func); + + result += functionsInBase; + } + + m_inheritedFunctions[&_contract] = result; + } + + return m_inheritedFunctions[&_contract]; +} + +OverrideChecker::OverrideProxyBySignatureMultiSet const& OverrideChecker::inheritedModifiers(ContractDefinition const& _contract) const +{ + if (!m_inheritedModifiers.count(&_contract)) + { + OverrideProxyBySignatureMultiSet result; + + for (auto const* base: resolveDirectBaseContracts(_contract)) + { + set modifiersInBase; + for (ModifierDefinition const* mod: base->functionModifiers()) + modifiersInBase.emplace(OverrideProxy{mod}); + + for (OverrideProxy const& mod: inheritedModifiers(*base)) + modifiersInBase.insert(mod); + + result += modifiersInBase; + } + + m_inheritedModifiers[&_contract] = result; + } + + return m_inheritedModifiers[&_contract]; +} diff --git a/libsolidity/analysis/OverrideChecker.h b/libsolidity/analysis/OverrideChecker.h new file mode 100644 index 000000000000..6ad6b0631506 --- /dev/null +++ b/libsolidity/analysis/OverrideChecker.h @@ -0,0 +1,196 @@ +/* + 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 . +*/ +/** + * Component that verifies override properties. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + +namespace dev +{ +namespace solidity +{ +class FunctionType; +class ModifierType; + +/** + * Class that represents a function, public state variable or modifier + * and helps with overload checking. + * Regular comparison is performed based on AST node, while CompareBySignature + * results in two elements being equal when they can override each + * other. + */ +class OverrideProxy +{ +public: + OverrideProxy() {} + explicit OverrideProxy(FunctionDefinition const* _fun): m_item{_fun} {} + explicit OverrideProxy(ModifierDefinition const* _mod): m_item{_mod} {} + explicit OverrideProxy(VariableDeclaration const* _var): m_item{_var} {} + + bool operator<(OverrideProxy const& _other) const; + + struct CompareBySignature + { + bool operator()(OverrideProxy const& _a, OverrideProxy const& _b) const; + }; + + bool isVariable() const; + bool isFunction() const; + bool isModifier() const; + + size_t id() const; + std::shared_ptr overrides() const; + std::set baseFunctions() const; + /// This stores the item in the list of base items. + void storeBaseFunction(OverrideProxy const& _base) const; + + std::string const& name() const; + ContractDefinition const& contract() const; + std::string const& contractName() const; + Visibility visibility() const; + StateMutability stateMutability() const; + bool virtualSemantics() const; + + /// @returns receive / fallback / function (only the latter for modifiers and variables); + langutil::Token functionKind() const; + + FunctionType const* functionType() const; + ModifierType const* modifierType() const; + + langutil::SourceLocation const& location() const; + + std::string astNodeName() const; + std::string astNodeNameCapitalized() const; + std::string distinguishingProperty() const; + + /// @returns true if this AST elements supports the feature of being unimplemented + /// and is actually not implemented. + bool unimplemented() const; + + /** + * Struct to help comparing override items about whether they override each other. + * Does not produce a total order. + */ + struct OverrideComparator + { + std::string name; + std::optional functionKind; + std::optional> parameterTypes; + + bool operator<(OverrideComparator const& _other) const; + }; + + /// @returns a structure used to compare override items with regards to whether + /// they override each other. + OverrideComparator const& overrideComparator() const; + +private: + std::variant< + FunctionDefinition const*, + ModifierDefinition const*, + VariableDeclaration const* + > m_item; + + std::shared_ptr mutable m_comparator; +}; + + +/** + * Component that verifies override properties. + */ +class OverrideChecker +{ +public: + + /// @param _errorReporter provides the error logging functionality. + explicit OverrideChecker(langutil::ErrorReporter& _errorReporter): + m_errorReporter(_errorReporter) + {} + + void check(ContractDefinition const& _contract); + +private: + struct CompareByID + { + bool operator()(ContractDefinition const* _a, ContractDefinition const* _b) const; + }; + + void checkIllegalOverrides(ContractDefinition const& _contract); + /// Performs various checks related to @a _overriding overriding @a _super like + /// different return type, invalid visibility change, etc. + /// Works on functions, modifiers and public state variables. + /// Also stores @a _super as a base function of @a _function in its AST annotations. + void checkOverride(OverrideProxy const& _overriding, OverrideProxy const& _super); + void overrideListError( + OverrideProxy const& _item, + std::set _secondary, + std::string const& _message1, + std::string const& _message2 + ); + void overrideError( + Declaration const& _overriding, + Declaration const& _super, + std::string const& _message, + std::string const& _secondaryMsg = "Overridden function is here:" + ); + void overrideError( + OverrideProxy const& _overriding, + OverrideProxy const& _super, + std::string const& _message, + std::string const& _secondaryMsg = "Overridden function is here:" + ); + /// Checks for functions in different base contracts which conflict with each + /// other and thus need to be overridden explicitly. + void checkAmbiguousOverrides(ContractDefinition const& _contract) const; + void checkAmbiguousOverridesInternal(std::set _baseCallables, langutil::SourceLocation const& _location) const; + /// Resolves an override list of UserDefinedTypeNames to a list of contracts. + std::set resolveOverrideList(OverrideSpecifier const& _overrides) const; + + using OverrideProxyBySignatureMultiSet = std::multiset; + + void checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited); + + /// Returns all functions of bases (including public state variables) that have not yet been overwritten. + /// May contain the same function multiple times when used with shared bases. + OverrideProxyBySignatureMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const; + OverrideProxyBySignatureMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const; + + langutil::ErrorReporter& m_errorReporter; + + /// Cache for inheritedFunctions(). + std::map mutable m_inheritedFunctions; + std::map mutable m_inheritedModifiers; +}; + +} +} diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index 936e133cae7d..40c4f22d85d0 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -60,6 +60,27 @@ void PostTypeChecker::endVisit(ContractDefinition const&) m_constVariableDependencies.clear(); } +void PostTypeChecker::endVisit(OverrideSpecifier const& _overrideSpecifier) +{ + for (ASTPointer const& override: _overrideSpecifier.overrides()) + { + Declaration const* decl = override->annotation().referencedDeclaration; + solAssert(decl, "Expected declaration to be resolved."); + + if (dynamic_cast(decl)) + continue; + + TypeType const* actualTypeType = dynamic_cast(decl->type()); + + m_errorReporter.typeError( + override->location(), + "Expected contract but got " + + actualTypeType->actualType()->toString(true) + + "." + ); + } +} + bool PostTypeChecker::visit(VariableDeclaration const& _variable) { solAssert(!m_currentConstVariable, ""); diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h index e428b81aec6b..b4441b395398 100644 --- a/libsolidity/analysis/PostTypeChecker.h +++ b/libsolidity/analysis/PostTypeChecker.h @@ -37,6 +37,7 @@ namespace solidity /** * This module performs analyses on the AST that are done after type checking and assignments of types: * - whether there are circular references in constant state variables + * - whether override specifiers are actually contracts * @TODO factor out each use-case into an individual class (but do the traversal only once) */ class PostTypeChecker: private ASTConstVisitor @@ -53,6 +54,7 @@ class PostTypeChecker: private ASTConstVisitor bool visit(ContractDefinition const& _contract) override; void endVisit(ContractDefinition const& _contract) override; + void endVisit(OverrideSpecifier const& _overrideSpecifier) override; bool visit(VariableDeclaration const& _variable) override; void endVisit(VariableDeclaration const& _variable) override; diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index d8627d49de3d..815cf184a38a 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -198,21 +198,21 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) { switch (_typeName.visibility()) { - case VariableDeclaration::Visibility::Internal: - case VariableDeclaration::Visibility::External: + case Visibility::Internal: + case Visibility::External: break; default: fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); return; } - if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External) + if (_typeName.isPayable() && _typeName.visibility() != Visibility::External) { fatalTypeError(_typeName.location(), "Only external function types can be payable."); return; } - if (_typeName.visibility() == VariableDeclaration::Visibility::External) + if (_typeName.visibility() == Visibility::External) for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); @@ -279,10 +279,34 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) ErrorList errors; ErrorReporter errorsIgnored(errors); yul::ExternalIdentifierAccess::Resolver resolver = - [&](yul::Identifier const& _identifier, yul::IdentifierContext, bool _crossesFunctionBoundary) { - auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str()); + [&](yul::Identifier const& _identifier, yul::IdentifierContext _context, bool _crossesFunctionBoundary) { bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), "_slot"); bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), "_offset"); + if (_context == yul::IdentifierContext::VariableDeclaration) + { + string namePrefix = _identifier.name.str().substr(0, _identifier.name.str().find('.')); + if (isSlot || isOffset) + declarationError(_identifier.location, "In variable declarations _slot and _offset can not be used as a suffix."); + else if ( + auto declarations = m_resolver.nameFromCurrentScope(namePrefix); + !declarations.empty() + ) + { + SecondarySourceLocation ssl; + for (auto const* decl: declarations) + ssl.append("The shadowed declaration is here:", decl->location()); + if (!ssl.infos.empty()) + declarationError( + _identifier.location, + ssl, + namePrefix.size() < _identifier.name.str().size() ? + "The prefix of this declaration conflicts with a declaration outside the inline assembly block." : + "This declaration shadows a declaration outside the inline assembly block." + ); + } + return size_t(-1); + } + auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str()); if (isSlot || isOffset) { // special mode to access storage variables @@ -323,12 +347,10 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) // Will be re-generated later with correct information // We use the latest EVM version because we will re-run it anyway. yul::AsmAnalysisInfo analysisInfo; - std::optional errorTypeForLoose = Error::Type::SyntaxError; yul::AsmAnalyzer( analysisInfo, errorsIgnored, - errorTypeForLoose, - yul::EVMDialect::looseAssemblyForEVM(m_evmVersion), + yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), resolver ).analyze(_inlineAssembly.operations()); return false; @@ -388,7 +410,7 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) ", ", " or " ); - if (_variable.isCallableParameter()) + if (_variable.isCallableOrCatchParameter()) errorString += " for " + string(_variable.isReturnParameter() ? "return " : "") + @@ -466,6 +488,12 @@ void ReferencesResolver::declarationError(SourceLocation const& _location, strin m_errorReporter.declarationError(_location, _description); } +void ReferencesResolver::declarationError(SourceLocation const& _location, SecondarySourceLocation const& _ssl, string const& _description) +{ + m_errorOccurred = true; + m_errorReporter.declarationError(_location, _ssl, _description); +} + void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, string const& _description) { m_errorOccurred = true; diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 207eecdda6f3..f1e512ab0bbe 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -94,6 +94,9 @@ class ReferencesResolver: private ASTConstVisitor /// Adds a new error to the list of errors. void declarationError(langutil::SourceLocation const& _location, std::string const& _description); + /// Adds a new error to the list of errors. + void declarationError(langutil::SourceLocation const& _location, langutil::SecondarySourceLocation const& _ssl, std::string const& _description); + /// Adds a new error to the list of errors and throws to abort reference resolving. void fatalDeclarationError(langutil::SourceLocation const& _location, std::string const& _description); diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 0d2a978db0e9..bfdd3ba22bcb 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -118,10 +118,12 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&) for (auto const& var: m_localVarUseCount) if (var.second == 0) { - if (var.first.second->isCallableParameter()) + if (var.first.second->isCallableOrCatchParameter()) m_errorReporter.warning( var.first.second->location(), - "Unused function parameter. Remove or comment out the variable name to silence this warning." + "Unused " + + string(var.first.second->isTryCatchParameter() ? "try/catch" : "function") + + " parameter. Remove or comment out the variable name to silence this warning." ); else m_errorReporter.warning(var.first.second->location(), "Unused local variable."); diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 7bd917d0d41c..aa90d174ff2f 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -106,7 +106,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma) { auto feature = ExperimentalFeatureNames.at(literal); m_sourceUnit->annotation().experimentalFeatures.insert(feature); - if (!ExperimentalFeatureOnlyAnalysis.count(feature)) + if (!ExperimentalFeatureWithoutWarning.count(feature)) m_errorReporter.warning(_pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments."); } } @@ -295,7 +295,7 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function) { if (_function.noVisibilitySpecified()) { - string suggestedVisibility = _function.isFallback() || m_isInterface ? "external" : "public"; + string suggestedVisibility = _function.isFallback() || _function.isReceive() || m_isInterface ? "external" : "public"; m_errorReporter.syntaxError( _function.location(), "No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?" diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 4982ee1582e4..26f473ee1714 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -89,7 +89,6 @@ bool TypeChecker::visit(ContractDefinition const& _contract) for (auto const& n: _contract.subNodes()) n->accept(*this); - return false; } @@ -137,19 +136,17 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c ); if (arguments.size() >= 1) - { - BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()); - - if (!result) - m_errorReporter.typeErrorConcatenateDescriptions( + if ( + !type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) && + !type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()) + ) + m_errorReporter.typeError( arguments.front()->location(), - "Invalid type for argument in function call. " - "Invalid implicit conversion from " + + "The first argument to \"abi.decode\" must be implicitly convertible to " + "bytes memory or bytes calldata, but is of type " + type(*arguments.front())->toString() + - " to bytes memory requested.", - result.message() + "." ); - } if (arguments.size() < 2) return {}; @@ -330,11 +327,20 @@ bool TypeChecker::visit(StructDefinition const& _struct) bool TypeChecker::visit(FunctionDefinition const& _function) { bool isLibraryFunction = _function.inContractKind() == ContractDefinition::ContractKind::Library; + + if (_function.markedVirtual()) + { + if (_function.annotation().contract->isInterface()) + m_errorReporter.warning(_function.location(), "Interface functions are implicitly \"virtual\""); + if (_function.visibility() == Visibility::Private) + m_errorReporter.typeError(_function.location(), "\"virtual\" and \"private\" cannot be used together."); + } + if (_function.isPayable()) { if (isLibraryFunction) m_errorReporter.typeError(_function.location(), "Library functions cannot be payable."); - if (!_function.isConstructor() && !_function.isFallback() && !_function.isPartOfExternalInterface()) + if (_function.isOrdinary() && !_function.isPartOfExternalInterface()) m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable."); } auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) { @@ -374,7 +380,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) ) m_errorReporter.typeError( var.location(), - "This type is only supported in the new experimental ABI encoder. " + "This type is only supported in ABIEncoderV2. " "Use \"pragma experimental ABIEncoderV2;\" to enable the feature." ); }; @@ -413,7 +419,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) if (_function.isImplemented()) m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation."); - if (_function.visibility() != FunctionDefinition::Visibility::External) + if (_function.visibility() != Visibility::External) m_errorReporter.typeError(_function.location(), "Functions in interfaces must be declared external."); if (_function.isConstructor()) @@ -426,8 +432,19 @@ bool TypeChecker::visit(FunctionDefinition const& _function) _function.body().accept(*this); else if (_function.isConstructor()) m_errorReporter.typeError(_function.location(), "Constructor must be implemented if declared."); - else if (isLibraryFunction && _function.visibility() <= FunctionDefinition::Visibility::Internal) - m_errorReporter.typeError(_function.location(), "Internal library function must be implemented if declared."); + else if (isLibraryFunction) + m_errorReporter.typeError(_function.location(), "Library functions must be implemented if declared."); + else if (!_function.virtualSemantics()) + m_errorReporter.typeError(_function.location(), "Functions without implementation must be marked virtual."); + + + if (_function.isFallback()) + typeCheckFallbackFunction(_function); + else if (_function.isReceive()) + typeCheckReceiveFunction(_function); + else if (_function.isConstructor()) + typeCheckConstructor(_function); + return false; } @@ -438,7 +455,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) // * or inside of a struct definition. if ( m_scope->isInterface() - && !_variable.isCallableParameter() + && !_variable.isCallableOrCatchParameter() && !m_insideStruct ) m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces."); @@ -481,7 +498,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) if (!varType->canLiveOutsideStorage()) m_errorReporter.typeError(_variable.location(), "Type " + varType->toString() + " is only valid in storage."); } - else if (_variable.visibility() >= VariableDeclaration::Visibility::Public) + else if (_variable.visibility() >= Visibility::Public) { FunctionType getter(_variable); if (!_variable.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2)) @@ -492,7 +509,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) unsupportedTypes.emplace_back(param->toString()); if (!unsupportedTypes.empty()) m_errorReporter.typeError(_variable.location(), - "The following types are only supported for getters in the new experimental ABI encoder: " + + "The following types are only supported for getters in ABIEncoderV2: " + joinHumanReadable(unsupportedTypes) + ". Either remove \"public\" or use \"pragma experimental ABIEncoderV2;\" to enable the feature." ); @@ -587,7 +604,7 @@ void TypeChecker::visitManually( bool TypeChecker::visit(EventDefinition const& _eventDef) { - solAssert(_eventDef.visibility() > Declaration::Visibility::Internal, ""); + solAssert(_eventDef.visibility() > Visibility::Internal, ""); unsigned numIndexed = 0; for (ASTPointer const& var: _eventDef.parameters()) { @@ -603,7 +620,7 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) ) m_errorReporter.typeError( var->location(), - "This type is only supported in the new experimental ABI encoder. " + "This type is only supported in ABIEncoderV2. " "Use \"pragma experimental ABIEncoderV2;\" to enable the feature." ); } @@ -743,7 +760,6 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) yul::AsmAnalyzer analyzer( *_inlineAssembly.annotation().analysisInfo, m_errorReporter, - Error::Type::SyntaxError, _inlineAssembly.dialect(), identifierAccess ); @@ -761,6 +777,133 @@ bool TypeChecker::visit(IfStatement const& _ifStatement) return false; } +void TypeChecker::endVisit(TryStatement const& _tryStatement) +{ + FunctionCall const* externalCall = dynamic_cast(&_tryStatement.externalCall()); + if (!externalCall || externalCall->annotation().kind != FunctionCallKind::FunctionCall) + { + m_errorReporter.typeError( + _tryStatement.externalCall().location(), + "Try can only be used with external function calls and contract creation calls." + ); + return; + } + + FunctionType const& functionType = dynamic_cast(*externalCall->expression().annotation().type); + if ( + functionType.kind() != FunctionType::Kind::External && + functionType.kind() != FunctionType::Kind::Creation && + functionType.kind() != FunctionType::Kind::DelegateCall + ) + { + m_errorReporter.typeError( + _tryStatement.externalCall().location(), + "Try can only be used with external function calls and contract creation calls." + ); + return; + } + + externalCall->annotation().tryCall = true; + + solAssert(_tryStatement.clauses().size() >= 2, ""); + solAssert(_tryStatement.clauses().front(), ""); + + TryCatchClause const& successClause = *_tryStatement.clauses().front(); + if (successClause.parameters()) + { + TypePointers returnTypes = + m_evmVersion.supportsReturndata() ? + functionType.returnParameterTypes() : + functionType.returnParameterTypesWithoutDynamicTypes(); + std::vector> const& parameters = + successClause.parameters()->parameters(); + if (returnTypes.size() != parameters.size()) + m_errorReporter.typeError( + successClause.location(), + "Function returns " + + to_string(functionType.returnParameterTypes().size()) + + " values, but returns clause has " + + to_string(parameters.size()) + + " variables." + ); + size_t len = min(returnTypes.size(), parameters.size()); + for (size_t i = 0; i < len; ++i) + { + solAssert(returnTypes[i], ""); + if (parameters[i] && *parameters[i]->annotation().type != *returnTypes[i]) + m_errorReporter.typeError( + parameters[i]->location(), + "Invalid type, expected " + + returnTypes[i]->toString(false) + + " but got " + + parameters[i]->annotation().type->toString() + + "." + ); + } + } + + TryCatchClause const* errorClause = nullptr; + TryCatchClause const* lowLevelClause = nullptr; + for (size_t i = 1; i < _tryStatement.clauses().size(); ++i) + { + TryCatchClause const& clause = *_tryStatement.clauses()[i]; + if (clause.errorName() == "") + { + if (lowLevelClause) + m_errorReporter.typeError( + clause.location(), + SecondarySourceLocation{}.append("The first clause is here:", lowLevelClause->location()), + "This try statement already has a low-level catch clause." + ); + lowLevelClause = &clause; + if (clause.parameters() && !clause.parameters()->parameters().empty()) + { + if ( + clause.parameters()->parameters().size() != 1 || + *clause.parameters()->parameters().front()->type() != *TypeProvider::bytesMemory() + ) + m_errorReporter.typeError(clause.location(), "Expected `catch (bytes memory ...) { ... }` or `catch { ... }`."); + if (!m_evmVersion.supportsReturndata()) + m_errorReporter.typeError( + clause.location(), + "This catch clause type cannot be used on the selected EVM version (" + + m_evmVersion.name() + + "). You need at least a Byzantium-compatible EVM or use `catch { ... }`." + ); + } + } + else if (clause.errorName() == "Error") + { + if (!m_evmVersion.supportsReturndata()) + m_errorReporter.typeError( + clause.location(), + "This catch clause type cannot be used on the selected EVM version (" + + m_evmVersion.name() + + "). You need at least a Byzantium-compatible EVM or use `catch { ... }`." + ); + + if (errorClause) + m_errorReporter.typeError( + clause.location(), + SecondarySourceLocation{}.append("The first clause is here:", errorClause->location()), + "This try statement already has an \"Error\" catch clause." + ); + errorClause = &clause; + if ( + !clause.parameters() || + clause.parameters()->parameters().size() != 1 || + *clause.parameters()->parameters().front()->type() != *TypeProvider::stringMemory() + ) + m_errorReporter.typeError(clause.location(), "Expected `catch Error(string memory ...) { ... }`."); + } + else + m_errorReporter.typeError( + clause.location(), + "Invalid catch clause name. Expected either `catch (...)` or `catch Error(...)`." + ); + } +} + bool TypeChecker::visit(WhileStatement const& _whileStatement) { expectType(_whileStatement.condition(), *TypeProvider::boolean()); @@ -1413,6 +1556,23 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) "might overflow. Silence this warning by converting the literal to the " "expected type." ); + if ( + commonType->category() == Type::Category::Integer && + rightType->category() == Type::Category::Integer && + dynamic_cast(*commonType).numBits() < + dynamic_cast(*rightType).numBits() + ) + m_errorReporter.warning( + _operation.location(), + "The result type of the " + + operation + + " operation is equal to the type of the first operand (" + + commonType->toString() + + ") ignoring the (larger) type of the second operand (" + + rightType->toString() + + ") which might be unexpected. Silence this warning by either converting " + "the first or the second operand to the type of the other." + ); } } @@ -1494,12 +1654,28 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( variableDeclaration->location() ); m_errorReporter.typeError( - _functionCall.location(), ssl, + _functionCall.location(), + ssl, "Explicit type conversion not allowed from non-payable \"address\" to \"" + resultType->toString() + "\", which has a payable fallback function." ); } + else if ( + auto const* functionType = dynamic_cast(argType); + functionType && + functionType->kind() == FunctionType::Kind::External && + resultType->category() == Type::Category::Address + ) + m_errorReporter.typeError( + _functionCall.location(), + "Explicit type conversion not allowed from \"" + + argType->toString() + + "\" to \"" + + resultType->toString() + + "\". To obtain the address of the contract of the function, " + + "you can use the .address member of the function." + ); else m_errorReporter.typeError( _functionCall.location(), @@ -1510,11 +1686,14 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( "\"." ); } - if (resultType->category() == Type::Category::Address) - { - bool const payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress()); - resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address(); - } + if (auto addressType = dynamic_cast(resultType)) + if (addressType->stateMutability() != StateMutability::Payable) + { + bool payable = false; + if (argType->category() != Type::Category::Address) + payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress()); + resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address(); + } } return resultType; } @@ -1550,6 +1729,71 @@ void TypeChecker::typeCheckFunctionCall( typeCheckFunctionGeneralChecks(_functionCall, _functionType); } +void TypeChecker::typeCheckFallbackFunction(FunctionDefinition const& _function) +{ + solAssert(_function.isFallback(), ""); + + if (_function.inContractKind() == ContractDefinition::ContractKind::Library) + m_errorReporter.typeError(_function.location(), "Libraries cannot have fallback functions."); + if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable) + m_errorReporter.typeError( + _function.location(), + "Fallback function must be payable or non-payable, but is \"" + + stateMutabilityToString(_function.stateMutability()) + + "\"." + ); + if (_function.visibility() != Visibility::External) + m_errorReporter.typeError(_function.location(), "Fallback function must be defined as \"external\"."); + if (!_function.returnParameters().empty()) + { + if (_function.returnParameters().size() > 1 || *type(*_function.returnParameters().front()) != *TypeProvider::bytesMemory()) + m_errorReporter.typeError(_function.returnParameterList()->location(), "Fallback function can only have a single \"bytes memory\" return value."); + else + m_errorReporter.typeError(_function.returnParameterList()->location(), "Return values for fallback functions are not yet implemented."); + } + if (!_function.parameters().empty()) + m_errorReporter.typeError(_function.parameterList().location(), "Fallback function cannot take parameters."); +} + +void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function) +{ + solAssert(_function.isReceive(), ""); + + if (_function.inContractKind() == ContractDefinition::ContractKind::Library) + m_errorReporter.typeError(_function.location(), "Libraries cannot have receive ether functions."); + + if (_function.stateMutability() != StateMutability::Payable) + m_errorReporter.typeError( + _function.location(), + "Receive ether function must be payable, but is \"" + + stateMutabilityToString(_function.stateMutability()) + + "\"." + ); + if (_function.visibility() != Visibility::External) + m_errorReporter.typeError(_function.location(), "Receive ether function must be defined as \"external\"."); + if (!_function.returnParameters().empty()) + m_errorReporter.typeError(_function.returnParameterList()->location(), "Receive ether function cannot return values."); + if (!_function.parameters().empty()) + m_errorReporter.typeError(_function.parameterList().location(), "Receive ether function cannot take parameters."); +} + + +void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function) +{ + solAssert(_function.isConstructor(), ""); + if (!_function.returnParameters().empty()) + m_errorReporter.typeError(_function.returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); + if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable) + m_errorReporter.typeError( + _function.location(), + "Constructor must be payable or non-payable, but is \"" + + stateMutabilityToString(_function.stateMutability()) + + "\"." + ); + if (_function.visibility() != Visibility::Public && _function.visibility() != Visibility::Internal) + m_errorReporter.typeError(_function.location(), "Constructor must be public or internal."); +} + void TypeChecker::typeCheckABIEncodeFunctions( FunctionCall const& _functionCall, FunctionTypePointer _functionType @@ -1871,7 +2115,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) FunctionCallAnnotation& funcCallAnno = _functionCall.annotation(); FunctionTypePointer functionType = nullptr; - // Determine and assign function call kind, purity and function type for this FunctionCall node + // Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node switch (expressionType->category()) { case Type::Category::Function: @@ -1885,6 +2129,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) functionType && functionType->isPure(); + if ( + functionType->kind() == FunctionType::Kind::ArrayPush || + functionType->kind() == FunctionType::Kind::ByteArrayPush + ) + funcCallAnno.isLValue = functionType->parameterTypes().empty(); + break; case Type::Category::TypeType: @@ -1992,21 +2242,10 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract."); if (contract->isInterface()) 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()); - string msg = "Trying to create an instance of an abstract contract."; - ssl.limitSize(msg); - m_errorReporter.typeError( - _newExpression.location(), - ssl, - msg - ); - } if (!contract->constructorIsPublic()) m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly."); + if (contract->abstract()) + m_errorReporter.typeError(_newExpression.location(), "Cannot instantiate an abstract contract."); solAssert(!!m_scope, ""); m_scope->annotation().contractDependencies.insert(contract); @@ -2169,14 +2408,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (auto const* structType = dynamic_cast(exprType)) annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); else if (exprType->category() == Type::Category::Array) - { - auto const& arrayType(dynamic_cast(*exprType)); - annotation.isLValue = ( - memberName == "length" && - arrayType.location() == DataLocation::Storage && - arrayType.isDynamicallySized() - ); - } + annotation.isLValue = false; else if (exprType->category() == Type::Category::FixedBytes) annotation.isLValue = false; else if (TypeType const* typeType = dynamic_cast(exprType)) @@ -2225,6 +2457,14 @@ bool TypeChecker::visit(IndexAccess const& _access) Expression const* index = _access.indexExpression(); switch (baseType->category()) { + case Type::Category::ArraySlice: + { + auto const& arrayType = dynamic_cast(*baseType).arrayType(); + if (arrayType.location() != DataLocation::CallData || !arrayType.isDynamicallySized()) + m_errorReporter.typeError(_access.location(), "Index access is only implemented for slices of dynamic calldata arrays."); + baseType = &arrayType; + [[fallthrough]]; + } case Type::Category::Array: { ArrayType const& actualType = dynamic_cast(*baseType); @@ -2321,6 +2561,50 @@ bool TypeChecker::visit(IndexAccess const& _access) return false; } +bool TypeChecker::visit(IndexRangeAccess const& _access) +{ + _access.baseExpression().accept(*this); + + bool isLValue = false; // TODO: set this correctly when implementing slices for memory and storage arrays + bool isPure = _access.baseExpression().annotation().isPure; + + if (Expression const* start = _access.startExpression()) + { + expectType(*start, *TypeProvider::uint256()); + if (!start->annotation().isPure) + isPure = false; + } + if (Expression const* end = _access.endExpression()) + { + expectType(*end, *TypeProvider::uint256()); + if (!end->annotation().isPure) + isPure = false; + } + + TypePointer exprType = type(_access.baseExpression()); + if (exprType->category() == Type::Category::TypeType) + { + m_errorReporter.typeError(_access.location(), "Types cannot be sliced."); + _access.annotation().type = exprType; + return false; + } + + ArrayType const* arrayType = nullptr; + if (auto const* arraySlice = dynamic_cast(exprType)) + arrayType = &arraySlice->arrayType(); + else if (!(arrayType = dynamic_cast(exprType))) + m_errorReporter.fatalTypeError(_access.location(), "Index range access is only possible for arrays and array slices."); + + + if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized()) + m_errorReporter.typeError(_access.location(), "Index range access is only supported for dynamic calldata arrays."); + _access.annotation().type = TypeProvider::arraySlice(*arrayType); + _access.annotation().isLValue = isLValue; + _access.annotation().isPure = isPure; + + return false; +} + bool TypeChecker::visit(Identifier const& _identifier) { IdentifierAnnotation& annotation = _identifier.annotation(); @@ -2433,7 +2717,7 @@ bool TypeChecker::visit(Identifier const& _identifier) void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) { - _expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.typeName())); + _expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.type().typeName(), _expr.type().stateMutability())); _expr.annotation().isPure = true; } @@ -2581,17 +2865,9 @@ void TypeChecker::requireLValue(Expression const& _expression) if (structType->dataStoredIn(DataLocation::CallData)) return "Calldata structs are read-only."; } - else if (auto arrayType = dynamic_cast(type(memberAccess->expression()))) + else if (dynamic_cast(type(memberAccess->expression()))) if (memberAccess->memberName() == "length") - switch (arrayType->location()) - { - case DataLocation::Memory: - return "Memory arrays cannot be resized."; - case DataLocation::CallData: - return "Calldata arrays cannot be resized."; - case DataLocation::Storage: - break; - } + return "Member \"length\" is read-only and cannot be used to resize arrays."; } if (auto identifier = dynamic_cast(&_expression)) diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 003915b2e197..94bed89e564c 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -96,6 +96,10 @@ class TypeChecker: private ASTConstVisitor FunctionTypePointer _functionType ); + void typeCheckFallbackFunction(FunctionDefinition const& _function); + void typeCheckReceiveFunction(FunctionDefinition const& _function); + void typeCheckConstructor(FunctionDefinition const& _function); + /// Performs general number and type checks of arguments against function call and struct ctor FunctionCall node parameters. void typeCheckFunctionGeneralChecks( FunctionCall const& _functionCall, @@ -120,6 +124,7 @@ class TypeChecker: private ASTConstVisitor void endVisit(FunctionTypeName const& _funType) override; bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(IfStatement const& _ifStatement) override; + void endVisit(TryStatement const& _tryStatement) override; bool visit(WhileStatement const& _whileStatement) override; bool visit(ForStatement const& _forStatement) override; void endVisit(Return const& _return) override; @@ -136,6 +141,7 @@ class TypeChecker: private ASTConstVisitor void endVisit(NewExpression const& _newExpression) override; bool visit(MemberAccess const& _memberAccess) override; bool visit(IndexAccess const& _indexAccess) override; + bool visit(IndexRangeAccess const& _indexRangeAccess) override; bool visit(Identifier const& _identifier) override; void endVisit(ElementaryTypeNameExpression const& _expr) override; void endVisit(Literal const& _literal) override; diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index b18261113da6..aa97ae7bbdc5 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -43,24 +43,16 @@ class AssemblyViewPureChecker m_dialect(_dialect), m_reportMutability(_reportMutability) {} - void operator()(yul::Label const&) { } void operator()(yul::Instruction const& _instruction) { checkInstruction(_instruction.location, _instruction.instruction); } void operator()(yul::Literal const&) {} void operator()(yul::Identifier const&) {} - void operator()(yul::FunctionalInstruction const& _instr) - { - checkInstruction(_instr.location, _instr.instruction); - for (auto const& arg: _instr.arguments) - std::visit(*this, arg); - } void operator()(yul::ExpressionStatement const& _expr) { std::visit(*this, _expr.expression); } - void operator()(yul::StackAssignment const&) {} void operator()(yul::Assignment const& _assignment) { std::visit(*this, *_assignment.value); @@ -112,6 +104,9 @@ class AssemblyViewPureChecker void operator()(yul::Continue const&) { } + void operator()(yul::Leave const&) + { + } void operator()(yul::Block const& _block) { for (auto const& s: _block.statements) @@ -175,7 +170,8 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef) !_funDef.body().statements().empty() && !_funDef.isConstructor() && !_funDef.isFallback() && - !_funDef.annotation().superFunction + !_funDef.isReceive() && + !_funDef.overrides() ) m_errorReporter.warning( _funDef.location(), @@ -387,7 +383,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) { auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type); if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage)) - mutability = writes ? StateMutability::NonPayable : StateMutability::View; + mutability = StateMutability::View; break; } default: @@ -415,6 +411,13 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess) } } +void ViewPureChecker::endVisit(IndexRangeAccess const& _indexRangeAccess) +{ + bool writes = _indexRangeAccess.annotation().lValueRequested; + if (_indexRangeAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage)) + reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexRangeAccess.location()); +} + void ViewPureChecker::endVisit(ModifierInvocation const& _modifier) { solAssert(_modifier.name(), ""); diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h index edcabf2f0ecf..f49c25ca73ae 100644 --- a/libsolidity/analysis/ViewPureChecker.h +++ b/libsolidity/analysis/ViewPureChecker.h @@ -59,6 +59,7 @@ class ViewPureChecker: private ASTConstVisitor bool visit(MemberAccess const& _memberAccess) override; void endVisit(MemberAccess const& _memberAccess) override; void endVisit(IndexAccess const& _indexAccess) override; + void endVisit(IndexRangeAccess const& _indexAccess) override; void endVisit(ModifierInvocation const& _modifier) override; void endVisit(FunctionCall const& _functionCall) override; void endVisit(InlineAssembly const& _inlineAssembly) override; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 90df0deb2554..140fe7a993e0 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -146,7 +146,7 @@ bool ContractDefinition::constructorIsPublic() const bool ContractDefinition::canBeDeployed() const { - return constructorIsPublic() && annotation().unimplementedFunctions.empty(); + return constructorIsPublic() && !abstract() && !isInterface(); } FunctionDefinition const* ContractDefinition::fallbackFunction() const @@ -158,6 +158,15 @@ FunctionDefinition const* ContractDefinition::fallbackFunction() const return nullptr; } +FunctionDefinition const* ContractDefinition::receiveFunction() const +{ + for (ContractDefinition const* contract: annotation().linearizedBaseContracts) + for (FunctionDefinition const* f: contract->definedFunctions()) + if (f->isReceive()) + return f; + return nullptr; +} + vector const& ContractDefinition::interfaceEvents() const { if (!m_interfaceEvents) @@ -303,19 +312,29 @@ ContractDefinition::ContractKind FunctionDefinition::inContractKind() const return contractDef->contractKind(); } +CallableDeclarationAnnotation& CallableDeclaration::annotation() const +{ + solAssert( + m_annotation, + "CallableDeclarationAnnotation is an abstract base, need to call annotation on the concrete class first." + ); + return dynamic_cast(*m_annotation); +} + + FunctionTypePointer FunctionDefinition::functionType(bool _internal) const { if (_internal) { switch (visibility()) { - case Declaration::Visibility::Default: + case Visibility::Default: solAssert(false, "visibility() should not return Default"); - case Declaration::Visibility::Private: - case Declaration::Visibility::Internal: - case Declaration::Visibility::Public: + case Visibility::Private: + case Visibility::Internal: + case Visibility::Public: return TypeProvider::function(*this, _internal); - case Declaration::Visibility::External: + case Visibility::External: return {}; } } @@ -323,13 +342,13 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const { switch (visibility()) { - case Declaration::Visibility::Default: + case Visibility::Default: solAssert(false, "visibility() should not return Default"); - case Declaration::Visibility::Private: - case Declaration::Visibility::Internal: + case Visibility::Private: + case Visibility::Internal: return {}; - case Declaration::Visibility::Public: - case Declaration::Visibility::External: + case Visibility::Public: + case Visibility::External: return TypeProvider::function(*this, _internal); } } @@ -340,7 +359,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const TypePointer FunctionDefinition::type() const { - solAssert(visibility() != Declaration::Visibility::External, ""); + solAssert(visibility() != Visibility::External, ""); return TypeProvider::function(*this); } @@ -349,6 +368,11 @@ string FunctionDefinition::externalSignature() const return TypeProvider::function(*this)->externalSignature(); } +string FunctionDefinition::externalIdentifierHex() const +{ + return TypeProvider::function(*this)->externalIdentifierHex(); +} + FunctionDefinitionAnnotation& FunctionDefinition::annotation() const { if (!m_annotation) @@ -443,12 +467,13 @@ bool VariableDeclaration::isLocalVariable() const dynamic_cast(s) || dynamic_cast(s) || dynamic_cast(s) || + dynamic_cast(s) || dynamic_cast(s); } -bool VariableDeclaration::isCallableParameter() const +bool VariableDeclaration::isCallableOrCatchParameter() const { - if (isReturnParameter()) + if (isReturnParameter() || isTryCatchParameter()) return true; vector> const* parameters = nullptr; @@ -467,7 +492,7 @@ bool VariableDeclaration::isCallableParameter() const bool VariableDeclaration::isLocalOrReturn() const { - return isReturnParameter() || (isLocalVariable() && !isCallableParameter()); + return isReturnParameter() || (isLocalVariable() && !isCallableOrCatchParameter()); } bool VariableDeclaration::isReturnParameter() const @@ -487,13 +512,18 @@ bool VariableDeclaration::isReturnParameter() const return false; } +bool VariableDeclaration::isTryCatchParameter() const +{ + return dynamic_cast(scope()); +} + bool VariableDeclaration::isExternalCallableParameter() const { - if (!isCallableParameter()) + if (!isCallableOrCatchParameter()) return false; if (auto const* callable = dynamic_cast(scope())) - if (callable->visibility() == Declaration::Visibility::External) + if (callable->visibility() == Visibility::External) return !isReturnParameter(); return false; @@ -501,19 +531,19 @@ bool VariableDeclaration::isExternalCallableParameter() const bool VariableDeclaration::isInternalCallableParameter() const { - if (!isCallableParameter()) + if (!isCallableOrCatchParameter()) return false; if (auto const* funTypeName = dynamic_cast(scope())) - return funTypeName->visibility() == Declaration::Visibility::Internal; + return funTypeName->visibility() == Visibility::Internal; else if (auto const* callable = dynamic_cast(scope())) - return callable->visibility() <= Declaration::Visibility::Internal; + return callable->visibility() <= Visibility::Internal; return false; } bool VariableDeclaration::isLibraryFunctionParameter() const { - if (!isCallableParameter()) + if (!isCallableOrCatchParameter()) return false; if (auto const* funDef = dynamic_cast(scope())) return dynamic_cast(*funDef->scope()).isLibrary(); @@ -549,10 +579,10 @@ set VariableDeclaration::allowedDataLocations() c locations.insert(Location::Storage); return locations; } - else if (isCallableParameter()) + else if (isCallableOrCatchParameter()) { set locations{ Location::Memory }; - if (isInternalCallableParameter() || isLibraryFunctionParameter()) + if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter()) locations.insert(Location::Storage); return locations; } @@ -571,6 +601,12 @@ set VariableDeclaration::allowedDataLocations() c return set{ Location::Unspecified }; } +string VariableDeclaration::externalIdentifierHex() const +{ + solAssert(isStateVariable() && isPublic(), "Can only be called for public state variables"); + return TypeProvider::function(*this)->externalIdentifierHex(); +} + TypePointer VariableDeclaration::type() const { return annotation().type; @@ -582,13 +618,13 @@ FunctionTypePointer VariableDeclaration::functionType(bool _internal) const return nullptr; switch (visibility()) { - case Declaration::Visibility::Default: + case Visibility::Default: solAssert(false, "visibility() should not return Default"); - case Declaration::Visibility::Private: - case Declaration::Visibility::Internal: + case Visibility::Private: + case Visibility::Internal: return nullptr; - case Declaration::Visibility::Public: - case Declaration::Visibility::External: + case Visibility::Public: + case Visibility::External: return TypeProvider::function(*this); } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index dbde1c9f9ef4..f761fe7afcf9 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -182,20 +182,18 @@ class Scopable class Declaration: public ASTNode, public Scopable { public: - /// Visibility ordered from restricted to unrestricted. - enum class Visibility { Default, Private, Internal, Public, External }; - static std::string visibilityToString(Declaration::Visibility _visibility) + static std::string visibilityToString(Visibility _visibility) { switch (_visibility) { - case Declaration::Visibility::Public: + case Visibility::Public: return "public"; - case Declaration::Visibility::Internal: + case Visibility::Internal: return "internal"; - case Declaration::Visibility::Private: + case Visibility::Private: return "private"; - case Declaration::Visibility::External: + case Visibility::External: return "external"; default: solAssert(false, "Invalid visibility specifier."); @@ -388,13 +386,15 @@ class ContractDefinition: public Declaration, public Documented ASTPointer const& _documentation, std::vector> const& _baseContracts, std::vector> const& _subNodes, - ContractKind _contractKind = ContractKind::Contract + ContractKind _contractKind = ContractKind::Contract, + bool _abstract = false ): Declaration(_location, _name), Documented(_documentation), m_baseContracts(_baseContracts), m_subNodes(_subNodes), - m_contractKind(_contractKind) + m_contractKind(_contractKind), + m_abstract(_abstract) {} void accept(ASTVisitor& _visitor) override; @@ -433,6 +433,9 @@ class ContractDefinition: public Declaration, public Documented /// Returns the fallback function or nullptr if no fallback function was specified. FunctionDefinition const* fallbackFunction() const; + /// Returns the ether receiver function or nullptr if no receive function was specified. + FunctionDefinition const* receiveFunction() const; + std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); } TypePointer type() const override; @@ -441,10 +444,13 @@ class ContractDefinition: public Declaration, public Documented ContractKind contractKind() const { return m_contractKind; } + bool abstract() const { return m_abstract; } + private: std::vector> m_baseContracts; std::vector> m_subNodes; ContractKind m_contractKind; + bool m_abstract{false}; mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; mutable std::unique_ptr> m_interfaceEvents; @@ -563,7 +569,7 @@ class EnumValue: public Declaration }; /** - * Parameter list, used as function parameter list and return list. + * Parameter list, used as function parameter list, return list and for try and catch. * None of the parameters is allowed to contain mappings (not even recursively * inside structs). */ @@ -594,24 +600,61 @@ class CallableDeclaration: public Declaration, public VariableScope CallableDeclaration( SourceLocation const& _location, ASTPointer const& _name, - Declaration::Visibility _visibility, + Visibility _visibility, ASTPointer const& _parameters, + bool _isVirtual = false, + ASTPointer const& _overrides = nullptr, ASTPointer const& _returnParameters = ASTPointer() ): Declaration(_location, _name, _visibility), m_parameters(_parameters), - m_returnParameters(_returnParameters) + m_overrides(_overrides), + m_returnParameters(_returnParameters), + m_isVirtual(_isVirtual) { } std::vector> const& parameters() const { return m_parameters->parameters(); } + ASTPointer const& overrides() const { return m_overrides; } std::vector> const& returnParameters() const { return m_returnParameters->parameters(); } ParameterList const& parameterList() const { return *m_parameters; } ASTPointer const& returnParameterList() const { return m_returnParameters; } + bool markedVirtual() const { return m_isVirtual; } + virtual bool virtualSemantics() const { return markedVirtual(); } + + CallableDeclarationAnnotation& annotation() const override; protected: ASTPointer m_parameters; + ASTPointer m_overrides; ASTPointer m_returnParameters; + bool m_isVirtual = false; +}; + +/** + * Function override specifier. Consists of a single override keyword + * potentially followed by a parenthesized list of base contract names. + */ +class OverrideSpecifier: public ASTNode +{ +public: + OverrideSpecifier( + SourceLocation const& _location, + std::vector> const& _overrides + ): + ASTNode(_location), + m_overrides(_overrides) + { + } + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + /// @returns the list of specific overrides, if any + std::vector> const& overrides() const { return m_overrides; } + +protected: + std::vector> m_overrides; }; class FunctionDefinition: public CallableDeclaration, public Documented, public ImplementationOptional @@ -620,44 +663,54 @@ class FunctionDefinition: public CallableDeclaration, public Documented, public FunctionDefinition( SourceLocation const& _location, ASTPointer const& _name, - Declaration::Visibility _visibility, + Visibility _visibility, StateMutability _stateMutability, - bool _isConstructor, + Token _kind, + bool _isVirtual, + ASTPointer const& _overrides, ASTPointer const& _documentation, ASTPointer const& _parameters, std::vector> const& _modifiers, ASTPointer const& _returnParameters, ASTPointer const& _body ): - CallableDeclaration(_location, _name, _visibility, _parameters, _returnParameters), + CallableDeclaration(_location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), Documented(_documentation), ImplementationOptional(_body != nullptr), m_stateMutability(_stateMutability), - m_isConstructor(_isConstructor), + m_kind(_kind), m_functionModifiers(_modifiers), m_body(_body) - {} + { + solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, ""); + } void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; StateMutability stateMutability() const { return m_stateMutability; } - bool isConstructor() const { return m_isConstructor; } - bool isFallback() const { return !m_isConstructor && name().empty(); } + bool isOrdinary() const { return m_kind == Token::Function; } + bool isConstructor() const { return m_kind == Token::Constructor; } + bool isFallback() const { return m_kind == Token::Fallback; } + bool isReceive() const { return m_kind == Token::Receive; } + Token kind() const { return m_kind; } bool isPayable() const { return m_stateMutability == StateMutability::Payable; } std::vector> const& modifiers() const { return m_functionModifiers; } Block const& body() const { solAssert(m_body, ""); return *m_body; } bool isVisibleInContract() const override { - return Declaration::isVisibleInContract() && !isConstructor() && !isFallback(); + return Declaration::isVisibleInContract() && isOrdinary(); } - bool isPartOfExternalInterface() const override { return isPublic() && !isConstructor() && !isFallback(); } + bool isPartOfExternalInterface() const override { return isPublic() && isOrdinary(); } /// @returns the external signature of the function /// That consists of the name of the function followed by the types of the /// arguments separated by commas all enclosed in parentheses without any spaces. std::string externalSignature() const; + /// @returns the external identifier of this function (the hash of the signature) as a hex string. + std::string externalIdentifierHex() const; + ContractDefinition::ContractKind inContractKind() const; TypePointer type() const override; @@ -668,9 +721,15 @@ class FunctionDefinition: public CallableDeclaration, public Documented, public FunctionDefinitionAnnotation& annotation() const override; + bool virtualSemantics() const override + { + return + CallableDeclaration::virtualSemantics() || + annotation().contract->isInterface(); + } private: StateMutability m_stateMutability; - bool m_isConstructor; + Token const m_kind; std::vector> m_functionModifiers; ASTPointer m_body; }; @@ -693,6 +752,7 @@ class VariableDeclaration: public Declaration bool _isStateVar = false, bool _isIndexed = false, bool _isConstant = false, + ASTPointer const& _overrides = nullptr, Location _referenceLocation = Location::Unspecified ): Declaration(_sourceLocation, _name, _visibility), @@ -701,8 +761,10 @@ class VariableDeclaration: public Declaration m_isStateVariable(_isStateVar), m_isIndexed(_isIndexed), m_isConstant(_isConstant), + m_overrides(_overrides), m_location(_referenceLocation) {} + void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -716,9 +778,12 @@ class VariableDeclaration: public Declaration /// (or function type name or event) or declared inside a function body. bool isLocalVariable() const; /// @returns true if this variable is a parameter or return parameter of a function. - bool isCallableParameter() const; + bool isCallableOrCatchParameter() const; /// @returns true if this variable is a return parameter of a function. bool isReturnParameter() const; + /// @returns true if this variable is a parameter of the success or failure clausse + /// of a try/catch statement. + bool isTryCatchParameter() const; /// @returns true if this variable is a local variable or return parameter. bool isLocalOrReturn() const; /// @returns true if this variable is a parameter (not return parameter) of an external function. @@ -740,10 +805,14 @@ class VariableDeclaration: public Declaration bool isStateVariable() const { return m_isStateVariable; } bool isIndexed() const { return m_isIndexed; } bool isConstant() const { return m_isConstant; } + ASTPointer const& overrides() const { return m_overrides; } Location referenceLocation() const { return m_location; } /// @returns a set of allowed storage locations for the variable. std::set allowedDataLocations() const; + /// @returns the external identifier of this variable (the hash of the signature) as a hex string (works only for public state variables). + std::string externalIdentifierHex() const; + TypePointer type() const override; /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. @@ -760,10 +829,11 @@ class VariableDeclaration: public Declaration /// Initially assigned value, can be missing. For local variables, this is stored inside /// VariableDeclarationStatement and not here. ASTPointer m_value; - bool m_isStateVariable; ///< Whether or not this is a contract state variable - bool m_isIndexed; ///< Whether this is an indexed variable (used by events). - bool m_isConstant; ///< Whether the variable is a compile-time constant. - Location m_location; ///< Location of the variable if it is of reference type. + bool m_isStateVariable = false; ///< Whether or not this is a contract state variable + bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events). + bool m_isConstant = false; ///< Whether the variable is a compile-time constant. + ASTPointer m_overrides; ///< Contains the override specifier node + Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type. }; /** @@ -777,9 +847,11 @@ class ModifierDefinition: public CallableDeclaration, public Documented ASTPointer const& _name, ASTPointer const& _documentation, ASTPointer const& _parameters, + bool _isVirtual, + ASTPointer const& _overrides, ASTPointer const& _body ): - CallableDeclaration(_location, _name, Visibility::Internal, _parameters), + CallableDeclaration(_location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides), Documented(_documentation), m_body(_body) { @@ -960,7 +1032,7 @@ class FunctionTypeName: public TypeName SourceLocation const& _location, ASTPointer const& _parameterTypes, ASTPointer const& _returnTypes, - Declaration::Visibility _visibility, + Visibility _visibility, StateMutability _stateMutability ): TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), @@ -974,9 +1046,9 @@ class FunctionTypeName: public TypeName ASTPointer const& parameterTypeList() const { return m_parameterTypes; } ASTPointer const& returnParameterTypeList() const { return m_returnTypes; } - Declaration::Visibility visibility() const + Visibility visibility() const { - return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility; + return m_visibility == Visibility::Default ? Visibility::Internal : m_visibility; } StateMutability stateMutability() const { return m_stateMutability; } bool isPayable() const { return m_stateMutability == StateMutability::Payable; } @@ -984,7 +1056,7 @@ class FunctionTypeName: public TypeName private: ASTPointer m_parameterTypes; ASTPointer m_returnTypes; - Declaration::Visibility m_visibility; + Visibility m_visibility; StateMutability m_stateMutability; }; @@ -1150,6 +1222,76 @@ class IfStatement: public Statement ASTPointer m_falseBody; ///< "else" part, optional }; +/** + * Clause of a try-catch block. Includes both the successful case and the + * unsuccessful cases. + * Names are only allowed for the unsuccessful cases. + */ +class TryCatchClause: public ASTNode, public Scopable +{ +public: + TryCatchClause( + SourceLocation const& _location, + ASTPointer const& _errorName, + ASTPointer const& _parameters, + ASTPointer const& _block + ): + ASTNode(_location), + m_errorName(_errorName), + m_parameters(_parameters), + m_block(_block) + {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + ASTString const& errorName() const { return *m_errorName; } + ParameterList const* parameters() const { return m_parameters.get(); } + Block const& block() const { return *m_block; } + +private: + ASTPointer m_errorName; + ASTPointer m_parameters; + ASTPointer m_block; +}; + +/** + * Try-statement with a variable number of catch statements. + * Syntax: + * try returns (uint x, uint y) { + * // success code + * } catch Error(string memory cause) { + * // error code, reason provided + * } catch (bytes memory lowLevelData) { + * // error code, no reason provided or non-matching error signature. + * } + * + * The last statement given above can also be specified as + * } catch () { + */ +class TryStatement: public Statement +{ +public: + TryStatement( + SourceLocation const& _location, + ASTPointer const& _docString, + ASTPointer const& _externalCall, + std::vector> const& _clauses + ): + Statement(_location, _docString), + m_externalCall(_externalCall), + m_clauses(_clauses) + {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + Expression const& externalCall() const { return *m_externalCall; } + std::vector> const& clauses() const { return m_clauses; } + +private: + ASTPointer m_externalCall; + std::vector> m_clauses; +}; + /** * Statement in which a break statement is legal (abstract class). */ @@ -1618,6 +1760,32 @@ class IndexAccess: public Expression ASTPointer m_index; }; +/** + * Index range access to an array. Example: a[2:3] + */ +class IndexRangeAccess: public Expression +{ +public: + IndexRangeAccess( + SourceLocation const& _location, + ASTPointer const& _base, + ASTPointer const& _start, + ASTPointer const& _end + ): + Expression(_location), m_base(_base), m_start(_start), m_end(_end) {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + Expression const& baseExpression() const { return *m_base; } + Expression const* startExpression() const { return m_start.get(); } + Expression const* endExpression() const { return m_end.get(); } + +private: + ASTPointer m_base; + ASTPointer m_start; + ASTPointer m_end; +}; + /** * Primary expression, i.e. an expression that cannot be divided any further. Examples are literals * or variable references. @@ -1658,16 +1826,21 @@ class Identifier: public PrimaryExpression class ElementaryTypeNameExpression: public PrimaryExpression { public: - ElementaryTypeNameExpression(SourceLocation const& _location, ElementaryTypeNameToken const& _type): - PrimaryExpression(_location), m_typeToken(_type) - {} + ElementaryTypeNameExpression( + SourceLocation const& _location, + ASTPointer const& _type + ): + PrimaryExpression(_location), + m_type(_type) + { + } void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; - ElementaryTypeNameToken const& typeName() const { return m_typeToken; } + ElementaryTypeName const& type() const { return *m_type; } private: - ElementaryTypeNameToken m_typeToken; + ASTPointer m_type; }; /** diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index eb9002a03989..6d3c890927ab 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -104,18 +104,23 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota std::map baseConstructorArguments; }; -struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation +struct CallableDeclarationAnnotation: ASTAnnotation { - /// The function this function overrides, if any. This is always the closest - /// in the linearized inheritance hierarchy. - FunctionDefinition const* superFunction = nullptr; + /// The set of functions/modifiers/events this callable overrides. + std::set baseFunctions; }; -struct EventDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation +struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation { + /// Pointer to the contract this function is defined in + ContractDefinition const* contract = nullptr; }; -struct ModifierDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation +struct EventDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation +{ +}; + +struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation { }; @@ -123,6 +128,8 @@ struct VariableDeclarationAnnotation: ASTAnnotation { /// Type of variable (type of identifier referencing this variable). TypePointer type = nullptr; + /// The set of functions this (public state) variable overrides. + std::set baseFunctions; }; struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation @@ -217,6 +224,8 @@ enum class FunctionCallKind struct FunctionCallAnnotation: ExpressionAnnotation { FunctionCallKind kind = FunctionCallKind::Unset; + /// If true, this is the external call of a try statement. + bool tryCall = false; }; } diff --git a/libsolidity/ast/ASTEnums.h b/libsolidity/ast/ASTEnums.h index 11e539a8cbfc..3ad329498e31 100644 --- a/libsolidity/ast/ASTEnums.h +++ b/libsolidity/ast/ASTEnums.h @@ -34,6 +34,9 @@ namespace solidity // How a function can mutate the EVM state. enum class StateMutability { Pure, View, NonPayable, Payable }; +/// Visibility ordered from restricted to unrestricted. +enum class Visibility { Default, Private, Internal, Public, External }; + inline std::string stateMutabilityToString(StateMutability const& _stateMutability) { switch (_stateMutability) diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index aad4ad558b1f..b84554563e08 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -26,7 +26,12 @@ #include #include -// Forward-declare all AST node types +// Forward-declare all AST node types and related enums. + +namespace langutil +{ +enum class Token : unsigned int; +} namespace dev { @@ -39,6 +44,7 @@ class PragmaDirective; class ImportDirective; class Declaration; class CallableDeclaration; +class OverrideSpecifier; class ContractDefinition; class InheritanceSpecifier; class UsingForDirective; @@ -63,6 +69,8 @@ class Statement; class Block; class PlaceholderStatement; class IfStatement; +class TryCatchClause; +class TryStatement; class BreakableStatement; class WhileStatement; class ForStatement; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 4d686b6f1232..b7b8e9cbcf8a 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -22,11 +22,15 @@ #include #include + +#include #include #include #include #include + #include +#include #include #include @@ -124,11 +128,17 @@ void ASTJsonConverter::setJsonNode( } } -string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location) const +size_t ASTJsonConverter::sourceIndexFromLocation(SourceLocation const& _location) const { - int sourceIndex{-1}; if (_location.source && m_sourceIndices.count(_location.source->name())) - sourceIndex = m_sourceIndices.at(_location.source->name()); + return m_sourceIndices.at(_location.source->name()); + else + return size_t(-1); +} + +string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location) const +{ + size_t sourceIndex = sourceIndexFromLocation(_location); int length = -1; if (_location.start >= 0 && _location.end >= 0) length = _location.end - _location.start; @@ -245,7 +255,7 @@ bool ASTJsonConverter::visit(ImportDirective const& _node) { Json::Value tuple(Json::objectValue); solAssert(symbolAlias.symbol, ""); - tuple["foreign"] = nodeId(*symbolAlias.symbol); + tuple["foreign"] = toJson(*symbolAlias.symbol); tuple["local"] = symbolAlias.alias ? Json::Value(*symbolAlias.alias) : Json::nullValue; symbolAliases.append(tuple); } @@ -260,6 +270,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) make_pair("name", _node.name()), make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), make_pair("contractKind", contractKind(_node.contractKind())), + make_pair("abstract", _node.abstract()), make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()), make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)), make_pair("baseContracts", toJson(_node.baseContracts())), @@ -326,15 +337,24 @@ bool ASTJsonConverter::visit(ParameterList const& _node) return false; } +bool ASTJsonConverter::visit(OverrideSpecifier const& _node) +{ + setJsonNode(_node, "OverrideSpecifier", { + make_pair("overrides", toJson(_node.overrides())) + }); + return false; +} + bool ASTJsonConverter::visit(FunctionDefinition const& _node) { std::vector> attributes = { make_pair("name", _node.name()), make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), - make_pair("kind", _node.isConstructor() ? "constructor" : (_node.isFallback() ? "fallback" : "function")), + make_pair("kind", TokenTraits::toString(_node.kind())), make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), - make_pair("superFunction", idOrNull(_node.annotation().superFunction)), make_pair("visibility", Declaration::visibilityToString(_node.visibility())), + make_pair("virtual", _node.markedVirtual()), + make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), make_pair("parameters", toJson(_node.parameterList())), make_pair("returnParameters", toJson(*_node.returnParameterList())), make_pair("modifiers", toJson(_node.modifiers())), @@ -342,6 +362,10 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node) make_pair("implemented", _node.isImplemented()), make_pair("scope", idOrNull(_node.scope())) }; + if (_node.isPartOfExternalInterface()) + attributes.emplace_back("functionSelector", _node.externalIdentifierHex()); + if (!_node.annotation().baseFunctions.empty()) + attributes.emplace_back(make_pair("baseFunctions", getContainerIds(_node.annotation().baseFunctions, true))); if (m_legacy) attributes.emplace_back("isConstructor", _node.isConstructor()); setJsonNode(_node, "FunctionDefinition", std::move(attributes)); @@ -356,26 +380,36 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node) make_pair("constant", _node.isConstant()), make_pair("stateVariable", _node.isStateVariable()), make_pair("storageLocation", location(_node.referenceLocation())), + make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue), make_pair("scope", idOrNull(_node.scope())), make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }; + if (_node.isStateVariable() && _node.isPublic()) + attributes.emplace_back("functionSelector", _node.externalIdentifierHex()); if (m_inEvent) attributes.emplace_back("indexed", _node.isIndexed()); + if (!_node.annotation().baseFunctions.empty()) + attributes.emplace_back(make_pair("baseFunctions", getContainerIds(_node.annotation().baseFunctions, true))); setJsonNode(_node, "VariableDeclaration", std::move(attributes)); return false; } bool ASTJsonConverter::visit(ModifierDefinition const& _node) { - setJsonNode(_node, "ModifierDefinition", { + std::vector> attributes = { make_pair("name", _node.name()), make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("parameters", toJson(_node.parameterList())), + make_pair("virtual", _node.markedVirtual()), + make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue), make_pair("body", toJson(_node.body())) - }); + }; + if (!_node.annotation().baseFunctions.empty()) + attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true))); + setJsonNode(_node, "ModifierDefinition", std::move(attributes)); return false; } @@ -459,17 +493,22 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node) bool ASTJsonConverter::visit(InlineAssembly const& _node) { - Json::Value externalReferences(Json::arrayValue); + vector> externalReferences; for (auto const& it: _node.annotation().externalReferences) if (it.first) - { - Json::Value tuple(Json::objectValue); - tuple[it.first->name.str()] = inlineAssemblyIdentifierToJson(it); - externalReferences.append(tuple); - } + externalReferences.emplace_back(make_pair( + it.first->name.str(), + inlineAssemblyIdentifierToJson(it) + )); + Json::Value externalReferencesJson = Json::arrayValue; + for (auto&& it: boost::range::sort(externalReferences)) + externalReferencesJson.append(std::move(it.second)); + setJsonNode(_node, "InlineAssembly", { - make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))), - make_pair("externalReferences", std::move(externalReferences)) + m_legacy ? + make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))) : + make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))), + make_pair("externalReferences", std::move(externalReferencesJson)) }); return false; } @@ -498,6 +537,25 @@ bool ASTJsonConverter::visit(IfStatement const& _node) return false; } +bool ASTJsonConverter::visit(TryCatchClause const& _node) +{ + setJsonNode(_node, "TryCatchClause", { + make_pair("errorName", _node.errorName()), + make_pair("parameters", toJsonOrNull(_node.parameters())), + make_pair("block", toJson(_node.block())) + }); + return false; +} + +bool ASTJsonConverter::visit(TryStatement const& _node) +{ + setJsonNode(_node, "TryStatement", { + make_pair("externalCall", toJson(_node.externalCall())), + make_pair("clauses", toJson(_node.clauses())) + }); + return false; +} + bool ASTJsonConverter::visit(WhileStatement const& _node) { setJsonNode( @@ -646,7 +704,8 @@ bool ASTJsonConverter::visit(FunctionCall const& _node) std::vector> attributes = { make_pair("expression", toJson(_node.expression())), make_pair("names", std::move(names)), - make_pair("arguments", toJson(_node.arguments())) + make_pair("arguments", toJson(_node.arguments())), + make_pair("tryCall", _node.annotation().tryCall) }; if (m_legacy) { @@ -693,6 +752,18 @@ bool ASTJsonConverter::visit(IndexAccess const& _node) return false; } +bool ASTJsonConverter::visit(IndexRangeAccess const& _node) +{ + std::vector> attributes = { + make_pair("baseExpression", toJson(_node.baseExpression())), + make_pair("startExpression", toJsonOrNull(_node.startExpression())), + make_pair("endExpression", toJsonOrNull(_node.endExpression())), + }; + appendExpressionAttributes(attributes, _node.annotation()); + setJsonNode(_node, "IndexRangeAccess", std::move(attributes)); + return false; +} + bool ASTJsonConverter::visit(Identifier const& _node) { Json::Value overloads(Json::arrayValue); @@ -711,7 +782,7 @@ bool ASTJsonConverter::visit(Identifier const& _node) bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node) { std::vector> attributes = { - make_pair(m_legacy ? "value" : "typeName", _node.typeName().toString()) + make_pair(m_legacy ? "value" : "typeName", toJson(_node.type())) }; appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "ElementaryTypeNameExpression", std::move(attributes)); diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 6ebb7e9640ab..10cc5581d3d1 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -81,6 +81,7 @@ class ASTJsonConverter: public ASTConstVisitor bool visit(EnumDefinition const& _node) override; bool visit(EnumValue const& _node) override; bool visit(ParameterList const& _node) override; + bool visit(OverrideSpecifier const& _node) override; bool visit(FunctionDefinition const& _node) override; bool visit(VariableDeclaration const& _node) override; bool visit(ModifierDefinition const& _node) override; @@ -95,6 +96,8 @@ class ASTJsonConverter: public ASTConstVisitor bool visit(Block const& _node) override; bool visit(PlaceholderStatement const& _node) override; bool visit(IfStatement const& _node) override; + bool visit(TryCatchClause const& _node) override; + bool visit(TryStatement const& _node) override; bool visit(WhileStatement const& _node) override; bool visit(ForStatement const& _node) override; bool visit(Continue const& _node) override; @@ -113,6 +116,7 @@ class ASTJsonConverter: public ASTConstVisitor bool visit(NewExpression const& _node) override; bool visit(MemberAccess const& _node) override; bool visit(IndexAccess const& _node) override; + bool visit(IndexRangeAccess const& _node) override; bool visit(Identifier const& _node) override; bool visit(ElementaryTypeNameExpression const& _node) override; bool visit(Literal const& _node) override; @@ -130,6 +134,7 @@ class ASTJsonConverter: public ASTConstVisitor std::string const& _nodeName, std::vector>&& _attributes ); + size_t sourceIndexFromLocation(langutil::SourceLocation const& _location) const; std::string sourceLocationToString(langutil::SourceLocation const& _location) const; static std::string namePathToString(std::vector const& _namePath); static Json::Value idOrNull(ASTNode const* _pt) diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp deleted file mode 100644 index 7d77ff7b9af3..000000000000 --- a/libsolidity/ast/ASTPrinter.cpp +++ /dev/null @@ -1,644 +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 . -*/ -/** - * @author Christian - * @date 2014 - * Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging. - */ - -#include - -#include -#include -#include - -using namespace std; -using namespace langutil; - -namespace dev -{ -namespace solidity -{ - -ASTPrinter::ASTPrinter( - ASTNode const& _ast, - string const& _source, - GasEstimator::ASTGasConsumption const& _gasCosts -): m_indentation(0), m_source(_source), m_ast(&_ast), m_gasCosts(_gasCosts) -{ -} - -void ASTPrinter::print(ostream& _stream) -{ - m_ostream = &_stream; - m_ast->accept(*this); - m_ostream = nullptr; -} - - -bool ASTPrinter::visit(PragmaDirective const& _node) -{ - writeLine("PragmaDirective"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(ImportDirective const& _node) -{ - writeLine("ImportDirective \"" + _node.path() + "\""); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(ContractDefinition const& _node) -{ - writeLine("ContractDefinition \"" + _node.name() + "\""); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(InheritanceSpecifier const& _node) -{ - writeLine("InheritanceSpecifier"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(UsingForDirective const& _node) -{ - writeLine("UsingForDirective"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(StructDefinition const& _node) -{ - writeLine("StructDefinition \"" + _node.name() + "\""); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(EnumDefinition const& _node) -{ - writeLine("EnumDefinition \"" + _node.name() + "\""); - return goDeeper(); -} - -bool ASTPrinter::visit(EnumValue const& _node) -{ - writeLine("EnumValue \"" + _node.name() + "\""); - return goDeeper(); -} - -bool ASTPrinter::visit(ParameterList const& _node) -{ - writeLine("ParameterList"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(FunctionDefinition const& _node) -{ - writeLine( - "FunctionDefinition \"" + - _node.name() + - "\"" + - (_node.isPublic() ? " - public" : "") + - (_node.stateMutability() == StateMutability::View ? " - const" : "") - ); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(VariableDeclaration const& _node) -{ - writeLine("VariableDeclaration \"" + _node.name() + "\""); - *m_ostream << indentation() << ( - _node.annotation().type ? - string(" Type: ") + _node.annotation().type->toString() : - string(" Type unknown.") - ) << "\n"; - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(ModifierDefinition const& _node) -{ - writeLine("ModifierDefinition \"" + _node.name() + "\""); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(ModifierInvocation const& _node) -{ - writeLine("ModifierInvocation \"" + _node.name()->name() + "\""); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(EventDefinition const& _node) -{ - writeLine("EventDefinition \"" + _node.name() + "\""); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(ElementaryTypeName const& _node) -{ - writeLine(string("ElementaryTypeName ") + _node.typeName().toString()); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(UserDefinedTypeName const& _node) -{ - writeLine("UserDefinedTypeName \"" + boost::algorithm::join(_node.namePath(), ".") + "\""); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(FunctionTypeName const& _node) -{ - writeLine("FunctionTypeName"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Mapping const& _node) -{ - writeLine("Mapping"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(ArrayTypeName const& _node) -{ - writeLine("ArrayTypeName"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(InlineAssembly const& _node) -{ - writeLine("InlineAssembly"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Block const& _node) -{ - writeLine("Block"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(PlaceholderStatement const& _node) -{ - writeLine("PlaceholderStatement"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(IfStatement const& _node) -{ - writeLine("IfStatement"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(WhileStatement const& _node) -{ - writeLine(_node.isDoWhile() ? "DoWhileStatement" : "WhileStatement"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(ForStatement const& _node) -{ - writeLine("ForStatement"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Continue const& _node) -{ - writeLine("Continue"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Break const& _node) -{ - writeLine("Break"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Return const& _node) -{ - writeLine("Return"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Throw const& _node) -{ - writeLine("Throw"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(EmitStatement const& _node) -{ - writeLine("EmitStatement"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(VariableDeclarationStatement const& _node) -{ - writeLine("VariableDeclarationStatement"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(ExpressionStatement const& _node) -{ - writeLine("ExpressionStatement"); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Conditional const& _node) -{ - writeLine("Conditional"); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Assignment const& _node) -{ - writeLine(string("Assignment using operator ") + TokenTraits::toString(_node.assignmentOperator())); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(TupleExpression const& _node) -{ - writeLine(string("TupleExpression")); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(UnaryOperation const& _node) -{ - writeLine( - string("UnaryOperation (") + - (_node.isPrefixOperation() ? "prefix" : "postfix") + - ") " + - TokenTraits::toString(_node.getOperator()) - ); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(BinaryOperation const& _node) -{ - writeLine(string("BinaryOperation using operator ") + TokenTraits::toString(_node.getOperator())); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(FunctionCall const& _node) -{ - writeLine("FunctionCall"); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(NewExpression const& _node) -{ - writeLine("NewExpression"); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(MemberAccess const& _node) -{ - writeLine("MemberAccess to member " + _node.memberName()); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(IndexAccess const& _node) -{ - writeLine("IndexAccess"); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Identifier const& _node) -{ - writeLine(string("Identifier ") + _node.name()); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(ElementaryTypeNameExpression const& _node) -{ - writeLine(string("ElementaryTypeNameExpression ") + _node.typeName().toString()); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -bool ASTPrinter::visit(Literal const& _node) -{ - char const* tokenString = TokenTraits::toString(_node.token()); - if (!tokenString) - tokenString = "[no token]"; - writeLine(string("Literal, token: ") + tokenString + " value: " + _node.value()); - printType(_node); - printSourcePart(_node); - return goDeeper(); -} - -void ASTPrinter::endVisit(PragmaDirective const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ImportDirective const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ContractDefinition const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(InheritanceSpecifier const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(UsingForDirective const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(StructDefinition const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(EnumDefinition const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(EnumValue const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ParameterList const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(FunctionDefinition const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(VariableDeclaration const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ModifierDefinition const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ModifierInvocation const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(EventDefinition const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ElementaryTypeName const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(UserDefinedTypeName const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(FunctionTypeName const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Mapping const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ArrayTypeName const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(InlineAssembly const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Block const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(PlaceholderStatement const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(IfStatement const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(WhileStatement const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ForStatement const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Continue const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Break const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Return const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Throw const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(EmitStatement const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(VariableDeclarationStatement const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ExpressionStatement const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Conditional const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Assignment const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(TupleExpression const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(UnaryOperation const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(BinaryOperation const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(FunctionCall const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(NewExpression const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(MemberAccess const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(IndexAccess const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Identifier const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(ElementaryTypeNameExpression const&) -{ - m_indentation--; -} - -void ASTPrinter::endVisit(Literal const&) -{ - m_indentation--; -} - -void ASTPrinter::printSourcePart(ASTNode const& _node) -{ - if (m_gasCosts.count(&_node)) - *m_ostream << indentation() << " Gas costs: " << m_gasCosts.at(&_node) << endl; - if (!m_source.empty()) - { - SourceLocation const& location(_node.location()); - *m_ostream << - indentation() << - " Source: " << - Json::valueToQuotedString(m_source.substr(location.start, location.end - location.start).c_str()) << - endl; - } -} - -void ASTPrinter::printType(Expression const& _expression) -{ - if (_expression.annotation().type) - *m_ostream << indentation() << " Type: " << _expression.annotation().type->toString() << "\n"; - else - *m_ostream << indentation() << " Type unknown.\n"; -} - -string ASTPrinter::indentation() const -{ - return string(m_indentation * 2, ' '); -} - -void ASTPrinter::writeLine(string const& _line) -{ - *m_ostream << indentation() << _line << endl; -} - -} -} diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h deleted file mode 100644 index 2e9cb4fc6ec8..000000000000 --- a/libsolidity/ast/ASTPrinter.h +++ /dev/null @@ -1,155 +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 . -*/ -/** - * @author Christian - * @date 2014 - * Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging. - */ - -#pragma once - -#include -#include -#include - -namespace dev -{ -namespace solidity -{ - -/** - * Pretty-printer for the abstract syntax tree (the "pretty" is arguable) for debugging purposes. - */ -class ASTPrinter: public ASTConstVisitor -{ -public: - /// Create a printer for the given abstract syntax tree. If the source is specified, - /// the corresponding parts of the source are printed with each node. - ASTPrinter( - ASTNode const& _ast, - std::string const& _source = std::string(), - GasEstimator::ASTGasConsumption const& _gasCosts = GasEstimator::ASTGasConsumption() - ); - /// Output the string representation of the AST to _stream. - void print(std::ostream& _stream); - - bool visit(PragmaDirective const& _node) override; - bool visit(ImportDirective const& _node) override; - bool visit(ContractDefinition const& _node) override; - bool visit(InheritanceSpecifier const& _node) override; - bool visit(UsingForDirective const& _node) override; - bool visit(StructDefinition const& _node) override; - bool visit(EnumDefinition const& _node) override; - bool visit(EnumValue const& _node) override; - bool visit(ParameterList const& _node) override; - bool visit(FunctionDefinition const& _node) override; - bool visit(VariableDeclaration const& _node) override; - bool visit(ModifierDefinition const& _node) override; - bool visit(ModifierInvocation const& _node) override; - bool visit(EventDefinition const& _node) override; - bool visit(ElementaryTypeName const& _node) override; - bool visit(UserDefinedTypeName const& _node) override; - bool visit(FunctionTypeName const& _node) override; - bool visit(Mapping const& _node) override; - bool visit(ArrayTypeName const& _node) override; - bool visit(InlineAssembly const& _node) override; - bool visit(Block const& _node) override; - bool visit(PlaceholderStatement const& _node) override; - bool visit(IfStatement const& _node) override; - bool visit(WhileStatement const& _node) override; - bool visit(ForStatement const& _node) override; - bool visit(Continue const& _node) override; - bool visit(Break const& _node) override; - bool visit(Return const& _node) override; - bool visit(Throw const& _node) override; - bool visit(EmitStatement const& _node) override; - bool visit(VariableDeclarationStatement const& _node) override; - bool visit(ExpressionStatement const& _node) override; - bool visit(Conditional const& _node) override; - bool visit(Assignment const& _node) override; - bool visit(TupleExpression const& _node) override; - bool visit(UnaryOperation const& _node) override; - bool visit(BinaryOperation const& _node) override; - bool visit(FunctionCall const& _node) override; - bool visit(NewExpression const& _node) override; - bool visit(MemberAccess const& _node) override; - bool visit(IndexAccess const& _node) override; - bool visit(Identifier const& _node) override; - bool visit(ElementaryTypeNameExpression const& _node) override; - bool visit(Literal const& _node) override; - - void endVisit(PragmaDirective const&) override; - void endVisit(ImportDirective const&) override; - void endVisit(ContractDefinition const&) override; - void endVisit(InheritanceSpecifier const&) override; - void endVisit(UsingForDirective const&) override; - void endVisit(StructDefinition const&) override; - void endVisit(EnumDefinition const&) override; - void endVisit(EnumValue const&) override; - void endVisit(ParameterList const&) override; - void endVisit(FunctionDefinition const&) override; - void endVisit(VariableDeclaration const&) override; - void endVisit(ModifierDefinition const&) override; - void endVisit(ModifierInvocation const&) override; - void endVisit(EventDefinition const&) override; - void endVisit(ElementaryTypeName const&) override; - void endVisit(UserDefinedTypeName const&) override; - void endVisit(FunctionTypeName const&) override; - void endVisit(Mapping const&) override; - void endVisit(ArrayTypeName const&) override; - void endVisit(InlineAssembly const&) override; - void endVisit(Block const&) override; - void endVisit(PlaceholderStatement const&) override; - void endVisit(IfStatement const&) override; - void endVisit(WhileStatement const&) override; - void endVisit(ForStatement const&) override; - void endVisit(Continue const&) override; - void endVisit(Break const&) override; - void endVisit(Return const&) override; - void endVisit(Throw const&) override; - void endVisit(EmitStatement const&) override; - void endVisit(VariableDeclarationStatement const&) override; - void endVisit(ExpressionStatement const&) override; - void endVisit(Conditional const&) override; - void endVisit(Assignment const&) override; - void endVisit(TupleExpression const&) override; - void endVisit(UnaryOperation const&) override; - void endVisit(BinaryOperation const&) override; - void endVisit(FunctionCall const&) override; - void endVisit(NewExpression const&) override; - void endVisit(MemberAccess const&) override; - void endVisit(IndexAccess const&) override; - void endVisit(Identifier const&) override; - void endVisit(ElementaryTypeNameExpression const&) override; - void endVisit(Literal const&) override; - -private: - void printSourcePart(ASTNode const& _node); - void printType(Expression const& _expression); - std::string indentation() const; - void writeLine(std::string const& _line); - bool goDeeper() { m_indentation++; return true; } - - int m_indentation; - std::string m_source; - ASTNode const* m_ast; - GasEstimator::ASTGasConsumption m_gasCosts; - std::ostream* m_ostream = nullptr; -}; - -} -} diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 5b97bb06a13d..ed328633fb8b 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -54,6 +54,7 @@ class ASTVisitor virtual bool visit(EnumDefinition& _node) { return visitNode(_node); } virtual bool visit(EnumValue& _node) { return visitNode(_node); } virtual bool visit(ParameterList& _node) { return visitNode(_node); } + virtual bool visit(OverrideSpecifier& _node) { return visitNode(_node); } virtual bool visit(FunctionDefinition& _node) { return visitNode(_node); } virtual bool visit(VariableDeclaration& _node) { return visitNode(_node); } virtual bool visit(ModifierDefinition& _node) { return visitNode(_node); } @@ -68,6 +69,8 @@ class ASTVisitor virtual bool visit(Block& _node) { return visitNode(_node); } virtual bool visit(PlaceholderStatement& _node) { return visitNode(_node); } virtual bool visit(IfStatement& _node) { return visitNode(_node); } + virtual bool visit(TryCatchClause& _node) { return visitNode(_node); } + virtual bool visit(TryStatement& _node) { return visitNode(_node); } virtual bool visit(WhileStatement& _node) { return visitNode(_node); } virtual bool visit(ForStatement& _node) { return visitNode(_node); } virtual bool visit(Continue& _node) { return visitNode(_node); } @@ -86,6 +89,7 @@ class ASTVisitor virtual bool visit(NewExpression& _node) { return visitNode(_node); } virtual bool visit(MemberAccess& _node) { return visitNode(_node); } virtual bool visit(IndexAccess& _node) { return visitNode(_node); } + virtual bool visit(IndexRangeAccess& _node) { return visitNode(_node); } virtual bool visit(Identifier& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); } virtual bool visit(Literal& _node) { return visitNode(_node); } @@ -100,6 +104,7 @@ class ASTVisitor virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); } virtual void endVisit(EnumValue& _node) { endVisitNode(_node); } virtual void endVisit(ParameterList& _node) { endVisitNode(_node); } + virtual void endVisit(OverrideSpecifier& _node) { endVisitNode(_node); } virtual void endVisit(FunctionDefinition& _node) { endVisitNode(_node); } virtual void endVisit(VariableDeclaration& _node) { endVisitNode(_node); } virtual void endVisit(ModifierDefinition& _node) { endVisitNode(_node); } @@ -114,6 +119,8 @@ class ASTVisitor virtual void endVisit(Block& _node) { endVisitNode(_node); } virtual void endVisit(PlaceholderStatement& _node) { endVisitNode(_node); } virtual void endVisit(IfStatement& _node) { endVisitNode(_node); } + virtual void endVisit(TryCatchClause& _node) { endVisitNode(_node); } + virtual void endVisit(TryStatement& _node) { endVisitNode(_node); } virtual void endVisit(WhileStatement& _node) { endVisitNode(_node); } virtual void endVisit(ForStatement& _node) { endVisitNode(_node); } virtual void endVisit(Continue& _node) { endVisitNode(_node); } @@ -132,6 +139,7 @@ class ASTVisitor virtual void endVisit(NewExpression& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); } virtual void endVisit(IndexAccess& _node) { endVisitNode(_node); } + virtual void endVisit(IndexRangeAccess& _node) { endVisitNode(_node); } virtual void endVisit(Identifier& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); } virtual void endVisit(Literal& _node) { endVisitNode(_node); } @@ -159,6 +167,7 @@ class ASTConstVisitor virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); } virtual bool visit(EnumValue const& _node) { return visitNode(_node); } virtual bool visit(ParameterList const& _node) { return visitNode(_node); } + virtual bool visit(OverrideSpecifier const& _node) { return visitNode(_node); } virtual bool visit(FunctionDefinition const& _node) { return visitNode(_node); } virtual bool visit(VariableDeclaration const& _node) { return visitNode(_node); } virtual bool visit(ModifierDefinition const& _node) { return visitNode(_node); } @@ -172,6 +181,8 @@ class ASTConstVisitor virtual bool visit(Block const& _node) { return visitNode(_node); } virtual bool visit(PlaceholderStatement const& _node) { return visitNode(_node); } virtual bool visit(IfStatement const& _node) { return visitNode(_node); } + virtual bool visit(TryCatchClause const& _node) { return visitNode(_node); } + virtual bool visit(TryStatement const& _node) { return visitNode(_node); } virtual bool visit(WhileStatement const& _node) { return visitNode(_node); } virtual bool visit(ForStatement const& _node) { return visitNode(_node); } virtual bool visit(Continue const& _node) { return visitNode(_node); } @@ -191,6 +202,7 @@ class ASTConstVisitor virtual bool visit(NewExpression const& _node) { return visitNode(_node); } virtual bool visit(MemberAccess const& _node) { return visitNode(_node); } virtual bool visit(IndexAccess const& _node) { return visitNode(_node); } + virtual bool visit(IndexRangeAccess const& _node) { return visitNode(_node); } virtual bool visit(Identifier const& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); } virtual bool visit(Literal const& _node) { return visitNode(_node); } @@ -205,6 +217,7 @@ class ASTConstVisitor virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); } virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); } virtual void endVisit(ParameterList const& _node) { endVisitNode(_node); } + virtual void endVisit(OverrideSpecifier const& _node) { endVisitNode(_node); } virtual void endVisit(FunctionDefinition const& _node) { endVisitNode(_node); } virtual void endVisit(VariableDeclaration const& _node) { endVisitNode(_node); } virtual void endVisit(ModifierDefinition const& _node) { endVisitNode(_node); } @@ -218,6 +231,8 @@ class ASTConstVisitor virtual void endVisit(Block const& _node) { endVisitNode(_node); } virtual void endVisit(PlaceholderStatement const& _node) { endVisitNode(_node); } virtual void endVisit(IfStatement const& _node) { endVisitNode(_node); } + virtual void endVisit(TryCatchClause const& _node) { endVisitNode(_node); } + virtual void endVisit(TryStatement const& _node) { endVisitNode(_node); } virtual void endVisit(WhileStatement const& _node) { endVisitNode(_node); } virtual void endVisit(ForStatement const& _node) { endVisitNode(_node); } virtual void endVisit(Continue const& _node) { endVisitNode(_node); } @@ -237,6 +252,7 @@ class ASTConstVisitor virtual void endVisit(NewExpression const& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); } virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); } + virtual void endVisit(IndexRangeAccess const& _node) { endVisitNode(_node); } virtual void endVisit(Identifier const& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); } virtual void endVisit(Literal const& _node) { endVisitNode(_node); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index 5538180d9035..05adf73523a9 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -187,10 +187,26 @@ void ParameterList::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void OverrideSpecifier::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + listAccept(m_overrides, _visitor); + _visitor.endVisit(*this); +} + +void OverrideSpecifier::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + listAccept(m_overrides, _visitor); + _visitor.endVisit(*this); +} + void FunctionDefinition::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) { + if (m_overrides) + m_overrides->accept(_visitor); m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); @@ -205,6 +221,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) { + if (m_overrides) + m_overrides->accept(_visitor); m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); @@ -221,6 +239,8 @@ void VariableDeclaration::accept(ASTVisitor& _visitor) { if (m_typeName) m_typeName->accept(_visitor); + if (m_overrides) + m_overrides->accept(_visitor); if (m_value) m_value->accept(_visitor); } @@ -233,6 +253,8 @@ void VariableDeclaration::accept(ASTConstVisitor& _visitor) const { if (m_typeName) m_typeName->accept(_visitor); + if (m_overrides) + m_overrides->accept(_visitor); if (m_value) m_value->accept(_visitor); } @@ -244,6 +266,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor) if (_visitor.visit(*this)) { m_parameters->accept(_visitor); + if (m_overrides) + m_overrides->accept(_visitor); m_body->accept(_visitor); } _visitor.endVisit(*this); @@ -254,6 +278,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const if (_visitor.visit(*this)) { m_parameters->accept(_visitor); + if (m_overrides) + m_overrides->accept(_visitor); m_body->accept(_visitor); } _visitor.endVisit(*this); @@ -443,6 +469,48 @@ void IfStatement::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void TryCatchClause::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + if (m_parameters) + m_parameters->accept(_visitor); + m_block->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void TryCatchClause::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + if (m_parameters) + m_parameters->accept(_visitor); + m_block->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void TryStatement::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_externalCall->accept(_visitor); + listAccept(m_clauses, _visitor); + } + _visitor.endVisit(*this); +} + +void TryStatement::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_externalCall->accept(_visitor); + listAccept(m_clauses, _visitor); + } + _visitor.endVisit(*this); +} + void WhileStatement::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) @@ -765,6 +833,32 @@ void IndexAccess::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void IndexRangeAccess::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_base->accept(_visitor); + if (m_start) + m_start->accept(_visitor); + if (m_end) + m_end->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void IndexRangeAccess::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_base->accept(_visitor); + if (m_start) + m_start->accept(_visitor); + if (m_end) + m_end->accept(_visitor); + } + _visitor.endVisit(*this); +} + void Identifier::accept(ASTVisitor& _visitor) { _visitor.visit(*this); diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h index 1d94ae67fe69..e0757564b064 100644 --- a/libsolidity/ast/ExperimentalFeatures.h +++ b/libsolidity/ast/ExperimentalFeatures.h @@ -21,6 +21,7 @@ #pragma once #include +#include namespace dev { @@ -35,10 +36,11 @@ enum class ExperimentalFeature TestOnlyAnalysis }; -static std::map const ExperimentalFeatureOnlyAnalysis = +static std::set const ExperimentalFeatureWithoutWarning = { - { ExperimentalFeature::SMTChecker, true }, - { ExperimentalFeature::TestOnlyAnalysis, true }, + ExperimentalFeature::ABIEncoderV2, + ExperimentalFeature::SMTChecker, + ExperimentalFeature::TestOnlyAnalysis, }; static std::map const ExperimentalFeatureNames = diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index 1962df9d5d40..e36710e90535 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -31,6 +31,7 @@ InaccessibleDynamicType const TypeProvider::m_inaccessibleDynamic{}; /// they rely on `byte` being available which we cannot guarantee in the static init context. unique_ptr TypeProvider::m_bytesStorage; unique_ptr TypeProvider::m_bytesMemory; +unique_ptr TypeProvider::m_bytesCalldata; unique_ptr TypeProvider::m_stringStorage; unique_ptr TypeProvider::m_stringMemory; @@ -177,6 +178,7 @@ void TypeProvider::reset() clearCache(m_inaccessibleDynamic); clearCache(m_bytesStorage); clearCache(m_bytesMemory); + clearCache(m_bytesCalldata); clearCache(m_stringStorage); clearCache(m_stringMemory); clearCache(m_emptyTuple); @@ -200,7 +202,7 @@ inline T const* TypeProvider::createAndGet(Args&& ... _args) return static_cast(instance().m_generalTypes.back().get()); } -Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& _type) +Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& _type, std::optional _stateMutability) { solAssert( TokenTraits::isElementaryTypeName(_type.token()), @@ -233,7 +235,14 @@ Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& case Token::UFixed: return fixedPoint(128, 18, FixedPointType::Modifier::Unsigned); case Token::Address: + { + if (_stateMutability) + { + solAssert(*_stateMutability == StateMutability::Payable, ""); + return payableAddress(); + } return address(); + } case Token::Bool: return boolean(); case Token::Bytes: @@ -307,6 +316,13 @@ ArrayType const* TypeProvider::bytesMemory() return m_bytesMemory.get(); } +ArrayType const* TypeProvider::bytesCalldata() +{ + if (!m_bytesCalldata) + m_bytesCalldata = make_unique(DataLocation::CallData, false); + return m_bytesCalldata.get(); +} + ArrayType const* TypeProvider::stringStorage() { if (!m_stringStorage) @@ -494,6 +510,11 @@ ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseTy return createAndGet(_location, _baseType, _length); } +ArraySliceType const* TypeProvider::arraySlice(ArrayType const& _arrayType) +{ + return createAndGet(_arrayType); +} + ContractType const* TypeProvider::contract(ContractDefinition const& _contractDef, bool _isSuper) { return createAndGet(_contractDef, _isSuper); diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index 0aefc9eddee0..4f33c556bd0c 100644 --- a/libsolidity/ast/TypeProvider.h +++ b/libsolidity/ast/TypeProvider.h @@ -55,7 +55,7 @@ class TypeProvider /// @name Factory functions /// Factory functions that convert an AST @ref TypeName to a Type. - static Type const* fromElementaryTypeName(ElementaryTypeNameToken const& _type); + static Type const* fromElementaryTypeName(ElementaryTypeNameToken const& _type, std::optional _stateMutability = {}); /// Converts a given elementary type name with optional data location /// suffix " storage", " calldata" or " memory" to a type pointer. If suffix not given, defaults to " storage". @@ -69,6 +69,7 @@ class TypeProvider static ArrayType const* bytesStorage(); static ArrayType const* bytesMemory(); + static ArrayType const* bytesCalldata(); static ArrayType const* stringStorage(); static ArrayType const* stringMemory(); @@ -81,6 +82,8 @@ class TypeProvider /// Constructor for a fixed-size array type ("type[20]") static ArrayType const* array(DataLocation _location, Type const* _baseType, u256 const& _length); + static ArraySliceType const* arraySlice(ArrayType const& _arrayType); + static AddressType const* payableAddress() noexcept { return &m_payableAddress; } static AddressType const* address() noexcept { return &m_address; } @@ -204,6 +207,7 @@ class TypeProvider /// These are lazy-initialized because they depend on `byte` being available. static std::unique_ptr m_bytesStorage; static std::unique_ptr m_bytesMemory; + static std::unique_ptr m_bytesCalldata; static std::unique_ptr m_stringStorage; static std::unique_ptr m_stringMemory; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index d1c579d2b053..b8228f1211fa 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -52,23 +52,6 @@ using namespace dev::solidity; namespace { -unsigned int mostSignificantBit(bigint const& _number) -{ -#if BOOST_VERSION < 105500 - solAssert(_number > 0, ""); - bigint number = _number; - unsigned int result = 0; - while (number != 0) - { - number >>= 1; - ++result; - } - return --result; -#else - return boost::multiprecision::msb(_number); -#endif -} - /// Check whether (_base ** _exp) fits into 4096 bits. bool fitsPrecisionExp(bigint const& _base, bigint const& _exp) { @@ -79,7 +62,7 @@ bool fitsPrecisionExp(bigint const& _base, bigint const& _exp) size_t const bitsMax = 4096; - unsigned mostSignificantBaseBit = mostSignificantBit(_base); + unsigned mostSignificantBaseBit = boost::multiprecision::msb(_base); if (mostSignificantBaseBit == 0) // _base == 1 return true; if (mostSignificantBaseBit > bitsMax) // _base >= 2 ^ 4096 @@ -105,7 +88,7 @@ bool fitsPrecisionBaseX( size_t const bitsMax = 4096; - unsigned mostSignificantMantissaBit = mostSignificantBit(_mantissa); + unsigned mostSignificantMantissaBit = boost::multiprecision::msb(_mantissa); if (mostSignificantMantissaBit > bitsMax) // _mantissa >= 2 ^ 4096 return false; @@ -412,7 +395,9 @@ BoolResult AddressType::isImplicitlyConvertibleTo(Type const& _other) const BoolResult AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - if (auto const* contractType = dynamic_cast(&_convertTo)) + if (_convertTo.category() == category()) + return true; + else if (auto const* contractType = dynamic_cast(&_convertTo)) return (m_stateMutability >= StateMutability::Payable) || !contractType->isPayable(); return isImplicitlyConvertibleTo(_convertTo) || _convertTo.category() == Category::Integer || @@ -600,6 +585,17 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other else return nullptr; } + else if (Token::Exp == _operator) + { + if (auto otherIntType = dynamic_cast(_other)) + { + if (otherIntType->isSigned()) + return TypeResult::err("Exponentiation power is not allowed to be a signed integer type."); + } + else if (dynamic_cast(_other)) + return nullptr; + return this; + } auto commonType = Type::commonType(this, _other); //might be an integer or fixed point if (!commonType) @@ -610,14 +606,6 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other return commonType; if (TokenTraits::isBooleanOp(_operator)) return nullptr; - if (auto intType = dynamic_cast(commonType)) - { - if (Token::Exp == _operator && intType->isSigned()) - return TypeResult::err("Exponentiation is not allowed for signed integer types."); - } - else if (dynamic_cast(commonType)) - if (Token::Exp == _operator) - return nullptr; return commonType; } @@ -1095,7 +1083,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const* else { uint32_t exponent = other.m_value.numerator().convert_to(); - if (exponent > mostSignificantBit(boost::multiprecision::abs(m_value.numerator()))) + if (exponent > boost::multiprecision::msb(boost::multiprecision::abs(m_value.numerator()))) value = m_value.numerator() < 0 ? -1 : 0; else { @@ -1119,7 +1107,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const* } // verify that numerator and denominator fit into 4096 bit after every operation - if (value.numerator() != 0 && max(mostSignificantBit(abs(value.numerator())), mostSignificantBit(abs(value.denominator()))) > 4096) + if (value.numerator() != 0 && max(boost::multiprecision::msb(abs(value.numerator())), boost::multiprecision::msb(abs(value.denominator()))) > 4096) return TypeResult::err("Precision of rational constants is limited to 4096 bits."); return TypeResult{TypeProvider::rationalNumber(value)}; @@ -1464,8 +1452,9 @@ BoolResult ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const bool ContractType::isPayable() const { + auto receiveFunction = m_contract.receiveFunction(); auto fallbackFunction = m_contract.fallbackFunction(); - return fallbackFunction && fallbackFunction->isPayable(); + return receiveFunction || (fallbackFunction && fallbackFunction->isPayable()); } TypeResult ContractType::unaryOperatorResult(Token _operator) const @@ -1783,10 +1772,17 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const if (isDynamicallySized() && location() == DataLocation::Storage) { members.emplace_back("push", TypeProvider::function( + TypePointers{}, TypePointers{baseType()}, - TypePointers{TypeProvider::uint256()}, + strings{}, strings{string()}, + isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush + )); + members.emplace_back("push", TypeProvider::function( + TypePointers{baseType()}, + TypePointers{}, strings{string()}, + strings{}, isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush )); members.emplace_back("pop", TypeProvider::function( @@ -1871,6 +1867,30 @@ std::unique_ptr ArrayType::copyForLocation(DataLocation _location return copy; } +BoolResult ArraySliceType::isImplicitlyConvertibleTo(Type const& _other) const +{ + if (m_arrayType.location() == DataLocation::CallData && m_arrayType.isDynamicallySized() && m_arrayType == _other) + return true; + return (*this) == _other; +} + +string ArraySliceType::richIdentifier() const +{ + return m_arrayType.richIdentifier() + "_slice"; +} + +bool ArraySliceType::operator==(Type const& _other) const +{ + if (auto const* other = dynamic_cast(&_other)) + return m_arrayType == other->m_arrayType; + return false; +} + +string ArraySliceType::toString(bool _short) const +{ + return m_arrayType.toString(_short) + " slice"; +} + string ContractType::richIdentifier() const { return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id()); @@ -2564,7 +2584,7 @@ FunctionType::FunctionType(EventDefinition const& _event): FunctionType::FunctionType(FunctionTypeName const& _typeName): m_parameterNames(_typeName.parameterTypes().size(), ""), m_returnParameterNames(_typeName.returnParameterTypes().size(), ""), - m_kind(_typeName.visibility() == VariableDeclaration::Visibility::External ? Kind::External : Kind::Internal), + m_kind(_typeName.visibility() == Visibility::External ? Kind::External : Kind::Internal), m_stateMutability(_typeName.stateMutability()) { if (_typeName.isPayable()) @@ -2735,8 +2755,6 @@ bool FunctionType::operator==(Type const& _other) const BoolResult FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - if (m_kind == Kind::External && _convertTo == *TypeProvider::address()) - return true; return _convertTo.category() == category(); } @@ -2931,7 +2949,10 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con { MemberList::MemberMap members; if (m_kind == Kind::External) + { members.emplace_back("selector", TypeProvider::fixedBytes(4)); + members.emplace_back("address", TypeProvider::address()); + } if (m_kind != Kind::BareDelegateCall) { if (isPayable()) @@ -2973,8 +2994,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con { auto const* functionDefinition = dynamic_cast(m_declaration); solAssert(functionDefinition, ""); - solAssert(functionDefinition->visibility() != Declaration::Visibility::Private, ""); - if (functionDefinition->visibility() != Declaration::Visibility::Internal) + solAssert(functionDefinition->visibility() != Visibility::Private, ""); + if (functionDefinition->visibility() != Visibility::Internal) { auto const* contract = dynamic_cast(m_declaration->scope()); solAssert(contract, ""); @@ -3153,6 +3174,11 @@ u256 FunctionType::externalIdentifier() const return FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(externalSignature()))); } +string FunctionType::externalIdentifierHex() const +{ + return FixedHash<4>(dev::keccak256(externalSignature())).hex(); +} + bool FunctionType::isPure() const { // TODO: replace this with m_stateMutability == StateMutability::Pure once diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index ac50c5862258..3bc2aa0aef1b 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -161,7 +161,7 @@ class Type enum class Category { - Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, + Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice, FixedBytes, Contract, Struct, Function, Enum, Tuple, Mapping, TypeType, Modifier, Magic, Module, InaccessibleDynamic @@ -773,6 +773,35 @@ class ArrayType: public ReferenceType mutable std::optional m_interfaceType_library; }; +class ArraySliceType: public ReferenceType +{ +public: + explicit ArraySliceType(ArrayType const& _arrayType): ReferenceType(_arrayType.location()), m_arrayType(_arrayType) {} + Category category() const override { return Category::ArraySlice; } + + BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + unsigned calldataEncodedSize(bool) const override { solAssert(false, ""); } + unsigned calldataEncodedTailSize() const override { return 32; } + bool isDynamicallySized() const override { return true; } + bool isDynamicallyEncoded() const override { return true; } + bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); } + unsigned sizeOnStack() const override { return 2; } + std::string toString(bool _short) const override; + + /// @returns true if this is valid to be stored in calldata + bool validForCalldata() const { return m_arrayType.validForCalldata(); } + + ArrayType const& arrayType() const { return m_arrayType; } + u256 memoryDataSize() const override { solAssert(false, ""); } + + std::unique_ptr copyForLocation(DataLocation, bool) const override { solAssert(false, ""); } + +private: + ArrayType const& m_arrayType; +}; + /** * The type of a contract instance or library, there is one distinct type for each contract definition. */ @@ -817,7 +846,8 @@ class ContractType: public Type /// See documentation of m_super bool isSuper() const { return m_super; } - // @returns true if and only if the contract has a payable fallback function + // @returns true if and only if the contract has a receive ether function or a payable fallback function, i.e. + // if it has code that will be executed on plain ether transfers bool isPayable() const; ContractDefinition const& contractDefinition() const { return m_contract; } @@ -1160,6 +1190,8 @@ class FunctionType: public Type std::string externalSignature() const; /// @returns the external identifier of this function (the hash of the signature). u256 externalIdentifier() const; + /// @returns the external identifier of this function (the hash of the signature) as a hex string. + std::string externalIdentifierHex() const; Declaration const& declaration() const { solAssert(m_declaration, "Requested declaration from a FunctionType that has none"); diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 55db6290dea6..121470e28229 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -35,7 +35,7 @@ void Compiler::compileContract( bytes const& _metadata ) { - ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings, m_revertStrings); runtimeCompiler.compileContract(_contract, _otherCompilers); m_runtimeContext.appendAuxiliaryData(_metadata); @@ -45,7 +45,7 @@ void Compiler::compileContract( // The creation code will be executed at most once, so we modify the optimizer // settings accordingly. creationSettings.expectedExecutionsPerDeployment = 1; - ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings); + ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings, m_revertStrings); m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); m_context.optimise(m_optimiserSettings); diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 34bae7a27a51..c93fa5df1cd3 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -35,8 +36,9 @@ namespace solidity { class Compiler { public: - explicit Compiler(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings): m_optimiserSettings(std::move(_optimiserSettings)), + m_revertStrings(_revertStrings), m_runtimeContext(_evmVersion), m_context(_evmVersion, &m_runtimeContext) { } @@ -79,6 +81,7 @@ class Compiler private: OptimiserSettings const m_optimiserSettings; + RevertStrings const m_revertStrings; CompilerContext m_runtimeContext; size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. CompilerContext m_context; diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3ec0124e3d84..c5096de7287b 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -411,7 +411,6 @@ void CompilerContext::appendInlineAssembly( analyzerResult = yul::AsmAnalyzer( analysisInfo, errorReporter, - std::nullopt, dialect, identifierAccess.resolve ).analyze(*parserResult); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 869dd0dc80dd..f396e59de08e 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -262,6 +262,8 @@ class CompilerContext ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); } }; + void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; } + private: /// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns /// the first function definition that is overwritten by _function. diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 5333cfddb62c..f068b156cc4d 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -95,6 +95,26 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType) m_context << Instruction::REVERT; } +void CompilerUtils::returnDataToArray() +{ + if (m_context.evmVersion().supportsReturndata()) + { + m_context << Instruction::RETURNDATASIZE; + m_context.appendInlineAssembly(R"({ + switch v case 0 { + v := 0x60 + } default { + v := mload(0x40) + mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f)))) + mstore(v, returndatasize()) + returndatacopy(add(v, 0x20), 0, returndatasize()) + } + })", {"v"}); + } + else + pushZeroPointer(); +} + void CompilerUtils::accessCalldataTail(Type const& _type) { solAssert(_type.dataStoredIn(DataLocation::CallData), ""); @@ -984,6 +1004,17 @@ void CompilerUtils::convertType( } break; } + case Type::Category::ArraySlice: + { + auto& typeOnStack = dynamic_cast(_typeOnStack); + solAssert(_targetType == typeOnStack.arrayType(), ""); + solUnimplementedAssert( + typeOnStack.arrayType().location() == DataLocation::CallData && + typeOnStack.arrayType().isDynamicallySized(), + "" + ); + break; + } case Type::Category::Struct: { solAssert(targetTypeCategory == stackTypeCategory, ""); @@ -1115,37 +1146,28 @@ void CompilerUtils::convertType( m_context << Instruction::ISZERO << Instruction::ISZERO; break; default: - if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address) + // we used to allow conversions from function to address + solAssert(!(stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address), ""); + if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Function) { FunctionType const& typeOnStack = dynamic_cast(_typeOnStack); - solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted."); - - // stack:
- m_context << Instruction::POP; + FunctionType const& targetType = dynamic_cast(_targetType); + solAssert( + typeOnStack.isImplicitlyConvertibleTo(targetType) && + typeOnStack.sizeOnStack() == targetType.sizeOnStack() && + (typeOnStack.kind() == FunctionType::Kind::Internal || typeOnStack.kind() == FunctionType::Kind::External) && + typeOnStack.kind() == targetType.kind(), + "Invalid function type conversion requested." + ); } else - { - if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Function) - { - FunctionType const& typeOnStack = dynamic_cast(_typeOnStack); - FunctionType const& targetType = dynamic_cast(_targetType); - solAssert( - typeOnStack.isImplicitlyConvertibleTo(targetType) && - typeOnStack.sizeOnStack() == targetType.sizeOnStack() && - (typeOnStack.kind() == FunctionType::Kind::Internal || typeOnStack.kind() == FunctionType::Kind::External) && - typeOnStack.kind() == targetType.kind(), - "Invalid function type conversion requested." - ); - } - else - // All other types should not be convertible to non-equal types. - solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); + // All other types should not be convertible to non-equal types. + solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); - if (_cleanupNeeded && _targetType.canBeStored() && _targetType.storageBytes() < 32) - m_context - << ((u256(1) << (8 * _targetType.storageBytes())) - 1) - << Instruction::AND; - } + if (_cleanupNeeded && _targetType.canBeStored() && _targetType.storageBytes() < 32) + m_context + << ((u256(1) << (8 * _targetType.storageBytes())) - 1) + << Instruction::AND; break; } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 8c9f1dc4373e..90ea8a15679c 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -67,6 +67,10 @@ class CompilerUtils /// Stack post: void revertWithStringData(Type const& _argumentType); + /// Allocates a new array and copies the return data to it. + /// If the EVM does not support return data, creates an empty array. + void returnDataToArray(); + /// Computes the absolute calldata offset of a tail given a base reference and the (absolute) /// offset of the tail pointer. Performs bounds checks. If @a _type is a dynamically sized array it also /// returns the array length on the stack. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 4ad432bf837e..1fe96bbac6f9 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -35,6 +35,8 @@ #include +#include + #include #include @@ -335,6 +337,9 @@ namespace // Helper function to check if any function is payable bool hasPayableFunctions(ContractDefinition const& _contract) { + if (_contract.receiveFunction()) + return true; + FunctionDefinition const* fallback = _contract.fallbackFunction(); if (fallback && fallback->isPayable()) return true; @@ -361,6 +366,9 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac FunctionDefinition const* fallback = _contract.fallbackFunction(); solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have fallback functions"); + FunctionDefinition const* etherReceiver = _contract.receiveFunction(); + solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have ether receiver functions"); + bool needToAddCallvalueCheck = true; if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary()) { @@ -368,11 +376,15 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac needToAddCallvalueCheck = false; } - eth::AssemblyItem notFound = m_context.newTag(); - // directly jump to fallback if the data is too short to contain a function selector + eth::AssemblyItem notFoundOrReceiveEther = m_context.newTag(); + // If there is neither a fallback nor a receive ether function, we only need one label to jump to, which + // always reverts. + eth::AssemblyItem notFound = (!fallback && !etherReceiver) ? notFoundOrReceiveEther : m_context.newTag(); + + // directly jump to fallback or ether receiver if the data is too short to contain a function selector // also guards against short data m_context << u256(4) << Instruction::CALLDATASIZE << Instruction::LT; - m_context.appendConditionalJumpTo(notFound); + m_context.appendConditionalJumpTo(notFoundOrReceiveEther); // retrieve the function signature hash from the calldata if (!interfaceFunctions.empty()) @@ -390,23 +402,44 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimiserSettings.expectedExecutionsPerDeployment); } - m_context << notFound; + m_context << notFoundOrReceiveEther; - if (fallback) + if (!fallback && !etherReceiver) + m_context.appendRevert(); + else { - solAssert(!_contract.isLibrary(), ""); - if (!fallback->isPayable() && needToAddCallvalueCheck) - appendCallValueCheck(); + if (etherReceiver) + { + // directly jump to fallback, if there is calldata + m_context << Instruction::CALLDATASIZE; + m_context.appendConditionalJumpTo(notFound); + + solAssert(!_contract.isLibrary(), ""); + solAssert(etherReceiver->isReceive(), ""); + solAssert(FunctionType(*etherReceiver).parameterTypes().empty(), ""); + solAssert(FunctionType(*etherReceiver).returnParameterTypes().empty(), ""); + etherReceiver->accept(*this); + m_context << Instruction::STOP; + } - solAssert(fallback->isFallback(), ""); - solAssert(FunctionType(*fallback).parameterTypes().empty(), ""); - solAssert(FunctionType(*fallback).returnParameterTypes().empty(), ""); - fallback->accept(*this); - m_context << Instruction::STOP; + m_context << notFound; + if (fallback) + { + solAssert(!_contract.isLibrary(), ""); + if (!fallback->isPayable() && needToAddCallvalueCheck) + appendCallValueCheck(); + + solAssert(fallback->isFallback(), ""); + solAssert(FunctionType(*fallback).parameterTypes().empty(), ""); + solAssert(FunctionType(*fallback).returnParameterTypes().empty(), ""); + fallback->accept(*this); + m_context << Instruction::STOP; + } + else + // TODO: error message here? + m_context.appendRevert(); } - else - // TODO: error message here? - m_context.appendRevert(); + for (auto const& it: interfaceFunctions) { @@ -480,7 +513,7 @@ void ContractCompiler::initializeStateVariables(ContractDefinition const& _contr solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); for (VariableDeclaration const* variable: _contract.stateVariables()) if (variable->value() && !variable->isConstant()) - ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); + ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); } bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) @@ -493,9 +526,11 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) m_continueTags.clear(); if (_variableDeclaration.isConstant()) - ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendConstStateVariableAccessor(_variableDeclaration); + ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals) + .appendConstStateVariableAccessor(_variableDeclaration); else - ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableAccessor(_variableDeclaration); + ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals) + .appendStateVariableAccessor(_variableDeclaration); return false; } @@ -532,8 +567,10 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) m_currentFunction = &_function; m_modifierDepth = -1; m_scopeStackHeight.clear(); + m_context.setModifierDepth(0); appendModifierOrFunctionCode(); + m_context.setModifierDepth(0); solAssert(m_returnTags.empty(), ""); // Now we need to re-shuffle the stack. For this we keep a record of the stack layout @@ -581,7 +618,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function) if (!_function.isConstructor()) { solAssert(m_context.numberOfLocalVariables() == 0, ""); - if (!_function.isFallback()) + if (!_function.isFallback() && !_function.isReceive()) m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); } @@ -763,6 +800,183 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) return false; } +bool ContractCompiler::visit(TryStatement const& _tryStatement) +{ + StackHeightChecker checker(m_context); + CompilerContext::LocationSetter locationSetter(m_context, _tryStatement); + + compileExpression(_tryStatement.externalCall()); + int const returnSize = static_cast(_tryStatement.externalCall().annotation().type->sizeOnStack()); + + // Stack: [ return values] + eth::AssemblyItem successTag = m_context.appendConditionalJump(); + + // Catch case. + m_context.adjustStackOffset(-returnSize); + + handleCatch(_tryStatement.clauses()); + + eth::AssemblyItem endTag = m_context.appendJumpToNew(); + + m_context << successTag; + m_context.adjustStackOffset(returnSize); + { + // Success case. + // Stack: return values + TryCatchClause const& successClause = *_tryStatement.clauses().front(); + if (successClause.parameters()) + { + vector exprTypes{_tryStatement.externalCall().annotation().type}; + if (auto tupleType = dynamic_cast(exprTypes.front())) + exprTypes = tupleType->components(); + vector> const& params = successClause.parameters()->parameters(); + solAssert(exprTypes.size() == params.size(), ""); + for (size_t i = 0; i < exprTypes.size(); ++i) + solAssert(params[i] && exprTypes[i] && *params[i]->annotation().type == *exprTypes[i], ""); + } + else + CompilerUtils(m_context).popStackSlots(returnSize); + + _tryStatement.clauses().front()->accept(*this); + } + + m_context << endTag; + checker.check(); + return false; +} + +void ContractCompiler::handleCatch(vector> const& _catchClauses) +{ + // Stack is empty. + ASTPointer structured{}; + ASTPointer fallback{}; + for (size_t i = 1; i < _catchClauses.size(); ++i) + if (_catchClauses[i]->errorName() == "Error") + structured = _catchClauses[i]; + else if (_catchClauses[i]->errorName().empty()) + fallback = _catchClauses[i]; + else + solAssert(false, ""); + + solAssert(_catchClauses.size() == size_t(1 + (structured ? 1 : 0) + (fallback ? 1 : 0)), ""); + + eth::AssemblyItem endTag = m_context.newTag(); + eth::AssemblyItem fallbackTag = m_context.newTag(); + if (structured) + { + solAssert( + structured->parameters() && + structured->parameters()->parameters().size() == 1 && + structured->parameters()->parameters().front() && + *structured->parameters()->parameters().front()->annotation().type == *TypeProvider::stringMemory(), + "" + ); + solAssert(m_context.evmVersion().supportsReturndata(), ""); + + string errorHash = FixedHash<4>(dev::keccak256("Error(string)")).hex(); + + // Try to decode the error message. + // If this fails, leaves 0 on the stack, otherwise the pointer to the data string. + m_context << u256(0); + m_context.appendInlineAssembly( + Whiskers(R"({ + data := mload(0x40) + mstore(data, 0) + for {} 1 {} { + if lt(returndatasize(), 0x44) { data := 0 break } + returndatacopy(0, 0, 4) + let sig := + if iszero(eq(sig, 0x)) { data := 0 break } + returndatacopy(data, 4, sub(returndatasize(), 4)) + let offset := mload(data) + if or( + gt(offset, 0xffffffffffffffff), + gt(add(offset, 0x24), returndatasize()) + ) { + data := 0 + break + } + let msg := add(data, offset) + let length := mload(msg) + if gt(length, 0xffffffffffffffff) { data := 0 break } + let end := add(add(msg, 0x20), length) + if gt(end, add(data, returndatasize())) { data := 0 break } + mstore(0x40, and(add(end, 0x1f), not(0x1f))) + data := msg + break + } + })") + ("ErrorSignature", errorHash) + ("getSig", + m_context.evmVersion().hasBitwiseShifting() ? + "shr(224, mload(0))" : + "div(mload(0), " + (u256(1) << 224).str() + ")" + ).render(), + {"data"} + ); + m_context << Instruction::DUP1; + AssemblyItem decodeSuccessTag = m_context.appendConditionalJump(); + m_context << Instruction::POP; + m_context.appendJumpTo(fallbackTag); + m_context.adjustStackOffset(1); + + m_context << decodeSuccessTag; + structured->accept(*this); + m_context.appendJumpTo(endTag); + } + m_context << fallbackTag; + if (fallback) + { + if (fallback->parameters()) + { + solAssert(m_context.evmVersion().supportsReturndata(), ""); + solAssert( + fallback->parameters()->parameters().size() == 1 && + fallback->parameters()->parameters().front() && + *fallback->parameters()->parameters().front()->annotation().type == *TypeProvider::bytesMemory(), + "" + ); + CompilerUtils(m_context).returnDataToArray(); + } + + fallback->accept(*this); + } + else + { + // re-throw + if (m_context.evmVersion().supportsReturndata()) + m_context.appendInlineAssembly(R"({ + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + })"); + else + m_context.appendRevert(); + } + m_context << endTag; +} + +bool ContractCompiler::visit(TryCatchClause const& _clause) +{ + CompilerContext::LocationSetter locationSetter(m_context, _clause); + + unsigned varSize = 0; + + if (_clause.parameters()) + for (ASTPointer const& varDecl: _clause.parameters()->parameters() | boost::adaptors::reversed) + { + solAssert(varDecl, ""); + varSize += varDecl->annotation().type->sizeOnStack(); + m_context.addVariable(*varDecl, varSize); + } + + _clause.block().accept(*this); + + m_context.removeVariablesAboveStackHeight(m_context.stackHeight() - varSize); + CompilerUtils(m_context).popStackSlots(varSize); + + return false; +} + bool ContractCompiler::visit(IfStatement const& _ifStatement) { StackHeightChecker checker(m_context); @@ -1032,6 +1246,7 @@ void ContractCompiler::appendModifierOrFunctionCode() vector addedVariables; m_modifierDepth++; + m_context.setModifierDepth(m_modifierDepth); if (m_modifierDepth >= m_currentFunction->modifiers().size()) { @@ -1085,6 +1300,7 @@ void ContractCompiler::appendModifierOrFunctionCode() m_context.removeVariable(*var); } m_modifierDepth--; + m_context.setModifierDepth(m_modifierDepth); } void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable) @@ -1096,7 +1312,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) { - ExpressionCompiler expressionCompiler(m_context, m_optimiserSettings.runOrderLiterals); + ExpressionCompiler expressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals); expressionCompiler.compile(_expression); if (_targetType) CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 86013eb87cbd..494cc350377c 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -43,9 +44,11 @@ class ContractCompiler: private ASTConstVisitor explicit ContractCompiler( ContractCompiler* _runtimeCompiler, CompilerContext& _context, - OptimiserSettings _optimiserSettings + OptimiserSettings _optimiserSettings, + RevertStrings _revertStrings ): m_optimiserSettings(std::move(_optimiserSettings)), + m_revertStrings(_revertStrings), m_runtimeCompiler(_runtimeCompiler), m_context(_context) { @@ -104,6 +107,9 @@ class ContractCompiler: private ASTConstVisitor bool visit(VariableDeclaration const& _variableDeclaration) override; bool visit(FunctionDefinition const& _function) override; bool visit(InlineAssembly const& _inlineAssembly) override; + bool visit(TryStatement const& _tryStatement) override; + void handleCatch(std::vector> const& _catchClauses); + bool visit(TryCatchClause const& _clause) override; bool visit(IfStatement const& _ifStatement) override; bool visit(WhileStatement const& _whileStatement) override; bool visit(ForStatement const& _forStatement) override; @@ -135,6 +141,7 @@ class ContractCompiler: private ASTConstVisitor void storeStackHeight(ASTNode const* _node); OptimiserSettings const m_optimiserSettings; + RevertStrings const m_revertStrings; /// Pointer to the runtime compiler in case this is a creation compiler. ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 9a7b454120cf..dddc1494d914 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -601,13 +601,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context.adjustStackOffset(returnParametersSize - parameterSize - 1); break; } - case FunctionType::Kind::External: - case FunctionType::Kind::DelegateCall: case FunctionType::Kind::BareCall: case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareStaticCall: + solAssert(!_functionCall.annotation().tryCall, ""); + [[fallthrough]]; + case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: _functionCall.expression().accept(*this); - appendExternalFunctionCall(function, arguments); + appendExternalFunctionCall(function, arguments, _functionCall.annotation().tryCall); break; case FunctionType::Kind::BareCallCode: solAssert(false, "Callcode has been removed."); @@ -635,12 +637,21 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) else m_context << u256(0); m_context << Instruction::CREATE; - // Check if zero (out of stack or not enough balance). - m_context << Instruction::DUP1 << Instruction::ISZERO; - // TODO: Can we bubble up here? There might be different reasons for failure, I think. - m_context.appendConditionalRevert(true); if (function.valueSet()) m_context << swapInstruction(1) << Instruction::POP; + // Check if zero (reverted) + m_context << Instruction::DUP1 << Instruction::ISZERO; + if (_functionCall.annotation().tryCall) + { + // If this is a try call, return "
1" in the success case and + // "0" in the error case. + AssemblyItem errorCase = m_context.appendConditionalJump(); + m_context << u256(1); + m_context << errorCase; + } + else + // TODO: Can we bubble up here? There might be different reasons for failure, I think. + m_context.appendConditionalRevert(true); break; } case FunctionType::Kind::SetGas: @@ -690,7 +701,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) true, true ), - {} + {}, + false ); if (function.kind() == FunctionType::Kind::Transfer) { @@ -706,16 +718,28 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case FunctionType::Kind::Revert: { - if (!arguments.empty()) + if (arguments.empty()) + m_context.appendRevert(); + else { // function-sel(Error(string)) + encoding solAssert(arguments.size() == 1, ""); solAssert(function.parameterTypes().size() == 1, ""); - arguments.front()->accept(*this); - utils().revertWithStringData(*arguments.front()->annotation().type); + if (m_revertStrings == RevertStrings::Strip) + { + if (!arguments.front()->annotation().isPure) + { + arguments.front()->accept(*this); + utils().popStackElement(*arguments.front()->annotation().type); + } + m_context.appendRevert(); + } + else + { + arguments.front()->accept(*this); + utils().revertWithStringData(*arguments.front()->annotation().type); + } } - else - m_context.appendRevert(); break; } case FunctionType::Kind::KECCAK256: @@ -850,48 +874,74 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << contractAddresses.at(function.kind()); for (unsigned i = function.sizeOnStack(); i > 0; --i) m_context << swapInstruction(i); - appendExternalFunctionCall(function, arguments); + solAssert(!_functionCall.annotation().tryCall, ""); + appendExternalFunctionCall(function, arguments, false); break; } case FunctionType::Kind::ByteArrayPush: case FunctionType::Kind::ArrayPush: { _functionCall.expression().accept(*this); - solAssert(function.parameterTypes().size() == 1, ""); - solAssert(!!function.parameterTypes()[0], ""); - TypePointer paramType = function.parameterTypes()[0]; - ArrayType const* arrayType = - function.kind() == FunctionType::Kind::ArrayPush ? - TypeProvider::array(DataLocation::Storage, paramType) : - TypeProvider::array(DataLocation::Storage); - - // stack: ArrayReference - arguments[0]->accept(*this); - TypePointer const& argType = arguments[0]->annotation().type; - // stack: ArrayReference argValue - utils().moveToStackTop(argType->sizeOnStack(), 1); - // stack: argValue ArrayReference - m_context << Instruction::DUP1; - ArrayUtils(m_context).incrementDynamicArraySize(*arrayType); - // stack: argValue ArrayReference newLength - m_context << Instruction::SWAP1; - // stack: argValue newLength ArrayReference - m_context << u256(1) << Instruction::DUP3 << Instruction::SUB; - // stack: argValue newLength ArrayReference (newLength-1) - ArrayUtils(m_context).accessIndex(*arrayType, false); - // stack: argValue newLength storageSlot slotOffset - utils().moveToStackTop(3, argType->sizeOnStack()); - // stack: newLength storageSlot slotOffset argValue - TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); - solAssert(type, ""); - utils().convertType(*argType, *type); - utils().moveToStackTop(1 + type->sizeOnStack()); - utils().moveToStackTop(1 + type->sizeOnStack()); - // stack: newLength argValue storageSlot slotOffset - if (function.kind() == FunctionType::Kind::ArrayPush) - StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true); + + if (function.parameterTypes().size() == 0) + { + auto paramType = function.returnParameterTypes().at(0); + solAssert(paramType, ""); + + ArrayType const* arrayType = + function.kind() == FunctionType::Kind::ArrayPush ? + TypeProvider::array(DataLocation::Storage, paramType) : + TypeProvider::bytesStorage(); + + // stack: ArrayReference + m_context << u256(1) << Instruction::DUP2; + ArrayUtils(m_context).incrementDynamicArraySize(*arrayType); + // stack: ArrayReference 1 newLength + m_context << Instruction::SUB; + // stack: ArrayReference (newLength-1) + ArrayUtils(m_context).accessIndex(*arrayType, false); + + if (arrayType->isByteArray()) + setLValue(_functionCall); + else + setLValueToStorageItem(_functionCall); + } else - StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); + { + solAssert(function.parameterTypes().size() == 1, ""); + solAssert(!!function.parameterTypes()[0], ""); + TypePointer paramType = function.parameterTypes()[0]; + ArrayType const* arrayType = + function.kind() == FunctionType::Kind::ArrayPush ? + TypeProvider::array(DataLocation::Storage, paramType) : + TypeProvider::bytesStorage(); + + // stack: ArrayReference + arguments[0]->accept(*this); + TypePointer const& argType = arguments[0]->annotation().type; + // stack: ArrayReference argValue + utils().moveToStackTop(argType->sizeOnStack(), 1); + // stack: argValue ArrayReference + m_context << Instruction::DUP1; + ArrayUtils(m_context).incrementDynamicArraySize(*arrayType); + // stack: argValue ArrayReference newLength + m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB; + // stack: argValue ArrayReference (newLength-1) + ArrayUtils(m_context).accessIndex(*arrayType, false); + // stack: argValue storageSlot slotOffset + utils().moveToStackTop(2, argType->sizeOnStack()); + // stack: storageSlot slotOffset argValue + TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); + solAssert(type, ""); + utils().convertType(*argType, *type); + utils().moveToStackTop(1 + type->sizeOnStack()); + utils().moveToStackTop(1 + type->sizeOnStack()); + // stack: argValue storageSlot slotOffset + if (function.kind() == FunctionType::Kind::ArrayPush) + StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true); + else + StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); + } break; } case FunctionType::Kind::ArrayPop: @@ -954,6 +1004,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::Require: { acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false); + + bool haveReasonString = arguments.size() > 1 && m_revertStrings != RevertStrings::Strip; + if (arguments.size() > 1) { // Users probably expect the second argument to be evaluated @@ -961,8 +1014,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // function call. solAssert(arguments.size() == 2, ""); solAssert(function.kind() == FunctionType::Kind::Require, ""); - arguments.at(1)->accept(*this); - utils().moveIntoStack(1, arguments.at(1)->annotation().type->sizeOnStack()); + if (m_revertStrings == RevertStrings::Strip) + { + if (!arguments.at(1)->annotation().isPure) + { + arguments.at(1)->accept(*this); + utils().popStackElement(*arguments.at(1)->annotation().type); + } + } + else + { + arguments.at(1)->accept(*this); + utils().moveIntoStack(1, arguments.at(1)->annotation().type->sizeOnStack()); + } } // Stack: // jump if condition was met @@ -971,7 +1035,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) if (function.kind() == FunctionType::Kind::Assert) // condition was not met, flag an error m_context.appendInvalid(); - else if (arguments.size() > 1) + else if (haveReasonString) { utils().revertWithStringData(*arguments.at(1)->annotation().type); // Here, the argument is consumed, but in the other branch, it is still there. @@ -981,7 +1045,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context.appendRevert(); // the success branch m_context << success; - if (arguments.size() > 1) + if (haveReasonString) utils().popStackElement(*arguments.at(1)->annotation().type); break; } @@ -1094,10 +1158,14 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) else targetTypes = TypePointers{_functionCall.annotation().type}; if ( - *firstArgType == ArrayType(DataLocation::CallData) || - *firstArgType == ArrayType(DataLocation::CallData, true) + auto referenceType = dynamic_cast(firstArgType); + referenceType && referenceType->dataStoredIn(DataLocation::CallData) ) + { + solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()), ""); + utils().convertType(*referenceType, *TypeProvider::bytesCalldata()); utils().abiDecode(targetTypes, false); + } else { utils().convertType(*firstArgType, *TypeProvider::bytesMemory()); @@ -1254,7 +1322,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) if (FunctionCall const* funCall = dynamic_cast(&_memberAccess.expression())) if (auto const* addr = dynamic_cast(&funCall->expression())) if ( - addr->typeName().token() == Token::Address && + addr->type().typeName().token() == Token::Address && funCall->arguments().size() == 1 ) if (auto arg = dynamic_cast( funCall->arguments().front().get())) @@ -1340,6 +1408,13 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) /// need to store it as bytes4 utils().leftShiftNumberOnStack(224); } + else if (member == "address") + { + auto const& functionType = dynamic_cast(*_memberAccess.expression().annotation().type); + solAssert(functionType.kind() == FunctionType::Kind::External, ""); + // stack:
+ m_context << Instruction::POP; + } else solAssert( !!_memberAccess.expression().annotation().type->memberType(member), @@ -1490,7 +1565,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) m_context << Instruction::SWAP1 << Instruction::POP; break; case DataLocation::Storage: - setLValue(_memberAccess, type); + ArrayUtils(m_context).retrieveLength(type); + m_context << Instruction::SWAP1 << Instruction::POP; break; case DataLocation::Memory: m_context << Instruction::MLOAD; @@ -1533,90 +1609,149 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) Type const& baseType = *_indexAccess.baseExpression().annotation().type; - if (baseType.category() == Type::Category::Mapping) + switch (baseType.category()) { - // stack: storage_base_ref - TypePointer keyType = dynamic_cast(baseType).keyType(); - solAssert(_indexAccess.indexExpression(), "Index expression expected."); - if (keyType->isDynamicallySized()) + case Type::Category::Mapping: { - _indexAccess.indexExpression()->accept(*this); - utils().fetchFreeMemoryPointer(); - // stack: base index mem - // note: the following operations must not allocate memory! - utils().packedEncode( - TypePointers{_indexAccess.indexExpression()->annotation().type}, - TypePointers{keyType} - ); - m_context << Instruction::SWAP1; - utils().storeInMemoryDynamic(*TypeProvider::uint256()); - utils().toSizeAfterFreeMemoryPointer(); - } - else - { - m_context << u256(0); // memory position - appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression()); - m_context << Instruction::SWAP1; - solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - utils().storeInMemoryDynamic(*TypeProvider::uint256()); + // stack: storage_base_ref + TypePointer keyType = dynamic_cast(baseType).keyType(); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + if (keyType->isDynamicallySized()) + { + _indexAccess.indexExpression()->accept(*this); + utils().fetchFreeMemoryPointer(); + // stack: base index mem + // note: the following operations must not allocate memory! + utils().packedEncode( + TypePointers{_indexAccess.indexExpression()->annotation().type}, + TypePointers{keyType} + ); + m_context << Instruction::SWAP1; + utils().storeInMemoryDynamic(*TypeProvider::uint256()); + utils().toSizeAfterFreeMemoryPointer(); + } + else + { + m_context << u256(0); // memory position + appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression()); + m_context << Instruction::SWAP1; + solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); + utils().storeInMemoryDynamic(*TypeProvider::uint256()); + m_context << u256(0); + } + m_context << Instruction::KECCAK256; m_context << u256(0); + setLValueToStorageItem(_indexAccess); + break; } - m_context << Instruction::KECCAK256; - m_context << u256(0); - setLValueToStorageItem(_indexAccess); - } - else if (baseType.category() == Type::Category::Array) - { - ArrayType const& arrayType = dynamic_cast(baseType); - solAssert(_indexAccess.indexExpression(), "Index expression expected."); + case Type::Category::ArraySlice: + { + auto const& arrayType = dynamic_cast(baseType).arrayType(); + solAssert(arrayType.location() == DataLocation::CallData && arrayType.isDynamicallySized(), ""); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); + ArrayUtils(m_context).accessCallDataArrayElement(arrayType); + break; - acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); - // stack layout: [] - switch (arrayType.location()) + } + case Type::Category::Array: { - case DataLocation::Storage: - ArrayUtils(m_context).accessIndex(arrayType); - if (arrayType.isByteArray()) + ArrayType const& arrayType = dynamic_cast(baseType); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); + // stack layout: [] + switch (arrayType.location()) { - solAssert(!arrayType.isString(), "Index access to string is not allowed."); - setLValue(_indexAccess); + case DataLocation::Storage: + ArrayUtils(m_context).accessIndex(arrayType); + if (arrayType.isByteArray()) + { + solAssert(!arrayType.isString(), "Index access to string is not allowed."); + setLValue(_indexAccess); + } + else + setLValueToStorageItem(_indexAccess); + break; + case DataLocation::Memory: + ArrayUtils(m_context).accessIndex(arrayType); + setLValue(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); + break; + case DataLocation::CallData: + ArrayUtils(m_context).accessCallDataArrayElement(arrayType); + break; } - else - setLValueToStorageItem(_indexAccess); break; - case DataLocation::Memory: - ArrayUtils(m_context).accessIndex(arrayType); - setLValue(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); + } + case Type::Category::FixedBytes: + { + FixedBytesType const& fixedBytesType = dynamic_cast(baseType); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); + // stack layout: + // check out-of-bounds access + m_context << u256(fixedBytesType.numBytes()); + m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; + // out-of-bounds access throws exception + m_context.appendConditionalInvalid(); + + m_context << Instruction::BYTE; + utils().leftShiftNumberOnStack(256 - 8); break; - case DataLocation::CallData: - ArrayUtils(m_context).accessCallDataArrayElement(arrayType); + } + case Type::Category::TypeType: + { + solAssert(baseType.sizeOnStack() == 0, ""); + solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, ""); + // no-op - this seems to be a lone array type (`structType[];`) break; } + default: + solAssert(false, "Index access only allowed for mappings or arrays."); + break; } - else if (baseType.category() == Type::Category::FixedBytes) - { - FixedBytesType const& fixedBytesType = dynamic_cast(baseType); - solAssert(_indexAccess.indexExpression(), "Index expression expected."); - - acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); - // stack layout: - // check out-of-bounds access - m_context << u256(fixedBytesType.numBytes()); - m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; - // out-of-bounds access throws exception - m_context.appendConditionalInvalid(); - m_context << Instruction::BYTE; - utils().leftShiftNumberOnStack(256 - 8); - } - else if (baseType.category() == Type::Category::TypeType) - { - solAssert(baseType.sizeOnStack() == 0, ""); - solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, ""); - // no-op - this seems to be a lone array type (`structType[];`) - } + return false; +} + +bool ExpressionCompiler::visit(IndexRangeAccess const& _indexAccess) +{ + CompilerContext::LocationSetter locationSetter(m_context, _indexAccess); + _indexAccess.baseExpression().accept(*this); + + Type const& baseType = *_indexAccess.baseExpression().annotation().type; + + ArrayType const *arrayType = dynamic_cast(&baseType); + if (!arrayType) + if (ArraySliceType const* sliceType = dynamic_cast(&baseType)) + arrayType = &sliceType->arrayType(); + + solAssert(arrayType, ""); + solUnimplementedAssert(arrayType->location() == DataLocation::CallData && arrayType->isDynamicallySized(), ""); + + if (_indexAccess.startExpression()) + acceptAndConvert(*_indexAccess.startExpression(), *TypeProvider::uint256()); else - solAssert(false, "Index access only allowed for mappings or arrays."); + m_context << u256(0); + if (_indexAccess.endExpression()) + acceptAndConvert(*_indexAccess.endExpression(), *TypeProvider::uint256()); + else + m_context << Instruction::DUP2; + + m_context.appendInlineAssembly( + Whiskers(R"({ + if gt(sliceStart, sliceEnd) { revert(0, 0) } + if gt(sliceEnd, length) { revert(0, 0) } + + offset := add(offset, mul(sliceStart, )) + length := sub(sliceEnd, sliceStart) + })")("stride", toString(arrayType->calldataStride())).render(), + {"offset", "length", "sliceStart", "sliceEnd"} + ); + + m_context << Instruction::POP << Instruction::POP; return false; } @@ -1906,7 +2041,8 @@ void ExpressionCompiler::appendShiftOperatorCode(Token _operator, Type const& _v void ExpressionCompiler::appendExternalFunctionCall( FunctionType const& _functionType, - vector> const& _arguments + vector> const& _arguments, + bool _tryCall ) { solAssert( @@ -1942,6 +2078,12 @@ void ExpressionCompiler::appendExternalFunctionCall( bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); + if (_tryCall) + { + solAssert(!returnSuccessConditionAndReturndata, ""); + solAssert(!_functionType.isBareCall(), ""); + } + bool haveReturndatacopy = m_context.evmVersion().supportsReturndata(); unsigned retSize = 0; bool dynamicReturnSize = false; @@ -2113,17 +2255,27 @@ void ExpressionCompiler::appendExternalFunctionCall( (_functionType.gasSet() ? 1 : 0) + (!_functionType.isBareCall() ? 1 : 0); - if (returnSuccessConditionAndReturndata) - m_context << swapInstruction(remainsSize); - else + eth::AssemblyItem endTag = m_context.newTag(); + + if (!returnSuccessConditionAndReturndata && !_tryCall) { - //Propagate error condition (if CALL pushes 0 on stack). + // Propagate error condition (if CALL pushes 0 on stack). m_context << Instruction::ISZERO; m_context.appendConditionalRevert(true); } - + else + m_context << swapInstruction(remainsSize); utils().popStackSlots(remainsSize); + // Only success flag is remaining on stack. + + if (_tryCall) + { + m_context << Instruction::DUP1 << Instruction::ISZERO; + m_context.appendConditionalJumpTo(endTag); + m_context << Instruction::POP; + } + if (returnSuccessConditionAndReturndata) { // success condition is already there @@ -2131,24 +2283,7 @@ void ExpressionCompiler::appendExternalFunctionCall( // an internal helper function e.g. for ``send`` and ``transfer``. In that // case we're only interested in the success condition, not the return data. if (!_functionType.returnParameterTypes().empty()) - { - if (haveReturndatacopy) - { - m_context << Instruction::RETURNDATASIZE; - m_context.appendInlineAssembly(R"({ - switch v case 0 { - v := 0x60 - } default { - v := mload(0x40) - mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f)))) - mstore(v, returndatasize()) - returndatacopy(add(v, 0x20), 0, returndatasize()) - } - })", {"v"}); - } - else - utils().pushZeroPointer(); - } + utils().returnDataToArray(); } else if (funKind == FunctionType::Kind::RIPEMD160) { @@ -2202,6 +2337,13 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().abiDecode(returnTypes, true); } + + if (_tryCall) + { + // Success branch will reach this, failure branch will directly jump to endTag. + m_context << u256(1); + m_context << endTag; + } } void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 07754dedc26e..7b2fcf6d2a3c 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -55,8 +56,15 @@ class ArrayType; class ExpressionCompiler: private ASTConstVisitor { public: - explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimiseOrderLiterals): - m_optimiseOrderLiterals(_optimiseOrderLiterals), m_context(_compilerContext) {} + ExpressionCompiler( + CompilerContext& _compilerContext, + RevertStrings _revertStrings, + bool _optimiseOrderLiterals + ): + m_revertStrings(_revertStrings), + m_optimiseOrderLiterals(_optimiseOrderLiterals), + m_context(_compilerContext) + {} /// Compile the given @a _expression and leave its value on the stack. void compile(Expression const& _expression); @@ -80,6 +88,7 @@ class ExpressionCompiler: private ASTConstVisitor bool visit(NewExpression const& _newExpression) override; bool visit(MemberAccess const& _memberAccess) override; bool visit(IndexAccess const& _indexAccess) override; + bool visit(IndexRangeAccess const& _indexAccess) override; void endVisit(Identifier const& _identifier) override; void endVisit(Literal const& _literal) override; @@ -95,9 +104,12 @@ class ExpressionCompiler: private ASTConstVisitor /// @} /// Appends code to call a function of the given type with the given arguments. + /// @param _tryCall if true, this is the external call of a try statement. In that case, + /// returns success flag on top of stack and does not revert on failure. void appendExternalFunctionCall( FunctionType const& _functionType, - std::vector> const& _arguments + std::vector> const& _arguments, + bool _tryCall ); /// Appends code that evaluates a single expression and moves the result to memory. The memory offset is /// expected to be on the stack and is updated by this call. @@ -126,6 +138,7 @@ class ExpressionCompiler: private ASTConstVisitor /// @returns the CompilerUtils object containing the current context. CompilerUtils utils(); + RevertStrings m_revertStrings; bool m_optimiseOrderLiterals; CompilerContext& m_context; std::unique_ptr m_currentLValue; diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index a6b8ae77965b..618293419a47 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -478,36 +478,6 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer m_context << Instruction::SWAP1 << Instruction::SSTORE; } -StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType): - LValue(_compilerContext, _arrayType.memberType("length")), - m_arrayType(_arrayType) -{ - solAssert(m_arrayType.isDynamicallySized(), ""); -} - -void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const -{ - ArrayUtils(m_context).retrieveLength(m_arrayType); - if (_remove) - m_context << Instruction::SWAP1 << Instruction::POP; -} - -void StorageArrayLength::storeValue(Type const&, SourceLocation const&, bool _move) const -{ - if (_move) - m_context << Instruction::SWAP1; - else - m_context << Instruction::DUP2; - ArrayUtils(m_context).resizeDynamicArray(m_arrayType); -} - -void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) const -{ - solAssert(_removeReference, ""); - ArrayUtils(m_context).clearDynamicArray(m_arrayType); -} - - TupleObject::TupleObject( CompilerContext& _compilerContext, std::vector>&& _lvalues diff --git a/libsolidity/codegen/LValue.h b/libsolidity/codegen/LValue.h index 3072ff11a956..a7352c6f6a8f 100644 --- a/libsolidity/codegen/LValue.h +++ b/libsolidity/codegen/LValue.h @@ -171,31 +171,6 @@ class StorageByteArrayElement: public LValue ) const override; }; -/** - * Reference to the "length" member of a dynamically-sized array. This is an LValue with special - * semantics since assignments to it might reduce its length and thus arrays members have to be - * deleted. - */ -class StorageArrayLength: public LValue -{ -public: - /// Constructs the LValue, assumes that the reference to the array head is already on the stack. - StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType); - void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; - virtual void storeValue( - Type const& _sourceType, - langutil::SourceLocation const& _location = {}, - bool _move = false - ) const override; - virtual void setToZero( - langutil::SourceLocation const& _location = {}, - bool _removeReference = true - ) const override; - -private: - ArrayType const& m_arrayType; -}; - /** * Tuple object that can itself hold several LValues. */ diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 1699bf9d3356..fe319682b695 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -595,6 +595,88 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type) }); } +string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type) +{ + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!"); + solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); + + string functionName = "array_pop_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (array) { + let oldLen := (array) + if iszero(oldLen) { invalid() } + let newLen := sub(oldLen, 1) + + let slot, offset := (array, newLen) + (slot, offset) + + sstore(array, newLen) + })") + ("functionName", functionName) + ("fetchLength", arrayLengthFunction(_type)) + ("indexAccess", storageArrayIndexAccessFunction(_type)) + ("setToZero", storageSetToZeroFunction(*_type.baseType())) + .render(); + }); +} + +string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type) +{ + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!"); + solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); + + string functionName = "array_push_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (array, value) { + let oldLen := (array) + if iszero(lt(oldLen, )) { invalid() } + sstore(array, add(oldLen, 1)) + + let slot, offset := (array, oldLen) + (slot, offset, value) + })") + ("functionName", functionName) + ("fetchLength", arrayLengthFunction(_type)) + ("indexAccess", storageArrayIndexAccessFunction(_type)) + ("storeValue", updateStorageValueFunction(*_type.baseType())) + ("maxArrayLength", (u256(1) << 64).str()) + .render(); + }); +} + +string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type) +{ + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!"); + solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented."); + + string functionName = "array_push_zero_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (array) -> slot, offset { + let oldLen := (array) + if iszero(lt(oldLen, )) { invalid() } + sstore(array, add(oldLen, 1)) + slot, offset := (array, oldLen) + (slot, offset, ()) + })") + ("functionName", functionName) + ("fetchLength", arrayLengthFunction(_type)) + ("indexAccess", storageArrayIndexAccessFunction(_type)) + ("storeValue", updateStorageValueFunction(*_type.baseType())) + ("maxArrayLength", (u256(1) << 64).str()) + ("zeroValueFunction", zeroValueFunction(*_type.baseType())) + .render(); + }); +} + string YulUtilFunctions::clearStorageRangeFunction(Type const& _type) { string functionName = "clear_storage_range_" + _type.identifier(); @@ -825,11 +907,6 @@ string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type) }); } -string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& /*_type*/) -{ - solUnimplemented("Calldata arrays not yet implemented!"); -} - string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) { solAssert(!_type.isByteArray(), ""); diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 68d57a6fd7eb..c6a339ca9233 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -121,6 +121,18 @@ class YulUtilFunctions /// signature: (array, newLen) std::string resizeDynamicArrayFunction(ArrayType const& _type); + /// @returns the name of a function that reduces the size of a storage array by one element + /// signature: (array) + std::string storageArrayPopFunction(ArrayType const& _type); + + /// @returns the name of a function that pushes an element to a storage array + /// signature: (array, value) + std::string storageArrayPushFunction(ArrayType const& _type); + + /// @returns the name of a function that pushes the base type's zero element to a storage array and returns storage slot and offset of the added element. + /// signature: (array) -> slot, offset + std::string storageArrayPushZeroFunction(ArrayType const& _type); + /// @returns the name of a function that will clear the storage area given /// by the start and end (exclusive) parameters (slots). /// signature: (start, end) diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 9c7834ff7bb6..c34a448d7fda 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -132,10 +132,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function) return m_context.functionCollector()->createFunction(functionName, [&]() { Whiskers t(R"( function () { - for { let return_flag := 1 } return_flag {} { - - break - } + } )"); t("functionName", functionName); @@ -267,6 +264,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) default {} } + if iszero(calldatasize()) { } )X"); t("shr224", m_utils.shiftRightFunction(224)); @@ -313,6 +311,10 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) } else t("fallback", "revert(0, 0)"); + if (FunctionDefinition const* etherReceiver = _contract.receiveFunction()) + t("receiveEther", generateFunction(*etherReceiver) + "() stop()"); + else + t("receiveEther", ""); return t.render(); } diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index b2d47292f853..0808230b3498 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -296,7 +296,7 @@ void IRGeneratorForStatements::endVisit(Return const& _return) expressionAsType(*value, *types.front()) << "\n"; } - m_code << "return_flag := 0\n" << "break\n"; + m_code << "leave\n"; } void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation) @@ -640,6 +640,47 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) array << "))\n"; + break; + } + case FunctionType::Kind::ArrayPop: + { + ArrayType const& arrayType = dynamic_cast( + *dynamic_cast(_functionCall.expression()).expression().annotation().type + ); + defineExpression(_functionCall) << + m_utils.storageArrayPopFunction(arrayType) << + "(" << + m_context.variable(_functionCall.expression()) << + ")\n"; + break; + } + case FunctionType::Kind::ArrayPush: + { + ArrayType const& arrayType = dynamic_cast( + *dynamic_cast(_functionCall.expression()).expression().annotation().type + ); + if (arguments.empty()) + { + auto slotName = m_context.newYulVariable(); + auto offsetName = m_context.newYulVariable(); + m_code << "let " << slotName << ", " << offsetName << " := " << + m_utils.storageArrayPushZeroFunction(arrayType) << + "(" << m_context.variable(_functionCall.expression()) << ")\n"; + setLValue(_functionCall, make_unique( + m_context.utils(), + slotName, + offsetName, + *arrayType.baseType() + )); + } + else + m_code << + m_utils.storageArrayPushFunction(arrayType) << + "(" << + m_context.variable(_functionCall.expression()) << + ", " << + expressionAsType(*arguments.front(), *arrayType.baseType()) << + ")\n"; break; } default: @@ -718,6 +759,10 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { solUnimplementedAssert(false, ""); } + else if (member == "address") + { + solUnimplementedAssert(false, ""); + } else solAssert( !!_memberAccess.expression().annotation().type->memberType(member), @@ -784,33 +829,45 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) { auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type); - solAssert(member == "length", ""); - - if (!type.isDynamicallySized()) - defineExpression(_memberAccess) << type.length() << "\n"; + if (member == "length") + { + if (!type.isDynamicallySized()) + defineExpression(_memberAccess) << type.length() << "\n"; + else + switch (type.location()) + { + case DataLocation::CallData: + solUnimplementedAssert(false, ""); + //m_context << Instruction::SWAP1 << Instruction::POP; + break; + case DataLocation::Storage: + { + string slot = m_context.variable(_memberAccess.expression()); + defineExpression(_memberAccess) << + m_utils.arrayLengthFunction(type) + "(" + slot + ")\n"; + break; + } + case DataLocation::Memory: + defineExpression(_memberAccess) << + "mload(" << + m_context.variable(_memberAccess.expression()) << + ")\n"; + break; + } + } + else if (member == "pop") + { + solAssert(type.location() == DataLocation::Storage, ""); + defineExpression(_memberAccess) << m_context.variable(_memberAccess.expression()) << "\n"; + } + else if (member == "push") + { + solAssert(type.location() == DataLocation::Storage, ""); + defineExpression(_memberAccess) << m_context.variable(_memberAccess.expression()) << "\n"; + } else - switch (type.location()) - { - case DataLocation::CallData: - solUnimplementedAssert(false, ""); - //m_context << Instruction::SWAP1 << Instruction::POP; - break; - case DataLocation::Storage: - setLValue(_memberAccess, make_unique( - m_context.utils(), - m_context.variable(_memberAccess.expression()), - *_memberAccess.annotation().type, - type - )); + solAssert(false, "Invalid array member access."); - break; - case DataLocation::Memory: - defineExpression(_memberAccess) << - "mload(" << - m_context.variable(_memberAccess.expression()) << - ")\n"; - break; - } break; } case Type::Category::FixedBytes: @@ -937,6 +994,11 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) solAssert(false, "Index access only allowed for mappings or arrays."); } +void IRGeneratorForStatements::endVisit(IndexRangeAccess const&) +{ + solUnimplementedAssert(false, "Index range accesses not yet implemented."); +} + void IRGeneratorForStatements::endVisit(Identifier const& _identifier) { Declaration const* declaration = _identifier.annotation().referencedDeclaration; @@ -1340,7 +1402,7 @@ void IRGeneratorForStatements::generateLoop( m_code << "for {\n"; if (_initExpression) _initExpression->accept(*this); - m_code << "} return_flag {\n"; + m_code << "} 1 {\n"; if (_loopExpression) _loopExpression->accept(*this); m_code << "}\n"; @@ -1364,8 +1426,6 @@ void IRGeneratorForStatements::generateLoop( _body.accept(*this); m_code << "}\n"; - // Bubble up the return condition. - m_code << "if iszero(return_flag) { break }\n"; } Type const& IRGeneratorForStatements::type(Expression const& _expression) diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index d64ab9e0a51d..74cef0b8aae6 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -63,6 +63,7 @@ class IRGeneratorForStatements: public ASTConstVisitor void endVisit(MemberAccess const& _memberAccess) override; bool visit(InlineAssembly const& _inlineAsm) override; void endVisit(IndexAccess const& _indexAccess) override; + void endVisit(IndexRangeAccess const& _indexRangeAccess) override; void endVisit(Identifier const& _identifier) override; bool visit(Literal const& _literal) override; diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp index 6ded09c050af..6355420f7a92 100644 --- a/libsolidity/codegen/ir/IRLValue.cpp +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -148,39 +148,6 @@ string IRStorageItem::setToZero() const ")\n"; } -IRStorageArrayLength::IRStorageArrayLength( - YulUtilFunctions _utils, - string _slot, - Type const& _type, - ArrayType const& _arrayType -): - IRLValue(std::move(_utils), &_type), m_arrayType(_arrayType), m_slot(move(_slot)) -{ - solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!"); -} - -string IRStorageArrayLength::retrieveValue() const -{ - return m_utils.arrayLengthFunction(m_arrayType) + "(" + m_slot + ")"; -} - -string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const -{ - solAssert(_type == *m_type, "Different type, but might not be an error."); - - return m_utils.resizeDynamicArrayFunction(m_arrayType) + - "(" + - m_slot + - ", " + - _value + - ")\n"; -} - -string IRStorageArrayLength::setToZero() const -{ - return storeValue("0", *TypeProvider::uint256()); -} - IRMemoryItem::IRMemoryItem( YulUtilFunctions _utils, std::string _address, diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h index eeb5a890a371..38609c387b00 100644 --- a/libsolidity/codegen/ir/IRLValue.h +++ b/libsolidity/codegen/ir/IRLValue.h @@ -110,30 +110,6 @@ class IRStorageItem: public IRLValue boost::variant const m_offset; }; -/** - * Reference to the "length" member of a dynamically-sized storage array. This is an LValue with special - * semantics since assignments to it might reduce its length and thus the array's members have to be - * deleted. - */ -class IRStorageArrayLength: public IRLValue -{ -public: - IRStorageArrayLength( - YulUtilFunctions _utils, - std::string _slot, - Type const& _type, - ArrayType const& _arrayType - ); - - std::string retrieveValue() const override; - std::string storeValue(std::string const& _value, Type const& _type) const override; - std::string setToZero() const override; - -private: - ArrayType const& m_arrayType; - std::string const m_slot; -}; - class IRMemoryItem: public IRLValue { public: diff --git a/libsolidity/formal/BMC.cpp b/libsolidity/formal/BMC.cpp index cea8e3d4fc84..dc1be1035bab 100644 --- a/libsolidity/formal/BMC.cpp +++ b/libsolidity/formal/BMC.cpp @@ -31,11 +31,12 @@ BMC::BMC( smt::EncodingContext& _context, ErrorReporter& _errorReporter, map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, smt::SMTSolverChoice _enabledSolvers ): SMTEncoder(_context), m_outerErrorReporter(_errorReporter), - m_interface(make_shared(_smtlib2Responses, _enabledSolvers)) + m_interface(make_shared(_smtlib2Responses, _smtCallback, _enabledSolvers)) { #if defined (HAVE_Z3) || defined (HAVE_CVC4) if (_enabledSolvers.some()) diff --git a/libsolidity/formal/BMC.h b/libsolidity/formal/BMC.h index 01ef363692a9..473b5dfeee2a 100644 --- a/libsolidity/formal/BMC.h +++ b/libsolidity/formal/BMC.h @@ -57,6 +57,7 @@ class BMC: public SMTEncoder smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, smt::SMTSolverChoice _enabledSolvers ); diff --git a/libsolidity/formal/CHC.cpp b/libsolidity/formal/CHC.cpp index 5e9495192563..e911ce8d4a78 100644 --- a/libsolidity/formal/CHC.cpp +++ b/libsolidity/formal/CHC.cpp @@ -36,6 +36,7 @@ CHC::CHC( smt::EncodingContext& _context, ErrorReporter& _errorReporter, map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, smt::SMTSolverChoice _enabledSolvers ): SMTEncoder(_context), @@ -43,30 +44,39 @@ CHC::CHC( m_interface( _enabledSolvers.z3 ? dynamic_pointer_cast(make_shared()) : - dynamic_pointer_cast(make_shared(_smtlib2Responses)) + dynamic_pointer_cast(make_shared(_smtlib2Responses, _smtCallback)) ), #else - m_interface(make_shared(_smtlib2Responses)), + m_interface(make_shared(_smtlib2Responses, _smtCallback)), #endif - m_outerErrorReporter(_errorReporter) + m_outerErrorReporter(_errorReporter), + m_enabledSolvers(_enabledSolvers) { (void)_smtlib2Responses; (void)_enabledSolvers; + (void)_smtCallback; } void CHC::analyze(SourceUnit const& _source) { solAssert(_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker), ""); + bool usesZ3 = false; #ifdef HAVE_Z3 - auto z3Interface = dynamic_pointer_cast(m_interface); - solAssert(z3Interface, ""); - m_context.setSolver(z3Interface->z3Interface()); -#else - auto smtlib2Interface = dynamic_pointer_cast(m_interface); - solAssert(smtlib2Interface, ""); - m_context.setSolver(smtlib2Interface->smtlib2Interface()); + usesZ3 = m_enabledSolvers.z3; + if (usesZ3) + { + auto z3Interface = dynamic_pointer_cast(m_interface); + solAssert(z3Interface, ""); + m_context.setSolver(z3Interface->z3Interface()); + } #endif + if (!usesZ3) + { + auto smtlib2Interface = dynamic_pointer_cast(m_interface); + solAssert(smtlib2Interface, ""); + m_context.setSolver(smtlib2Interface->smtlib2Interface()); + } m_context.clear(); m_context.setAssertionAccumulation(false); m_variableUsage.setFunctionInlining(false); @@ -680,12 +690,9 @@ string CHC::predicateName(ASTNode const* _node) string prefix; if (auto funDef = dynamic_cast(_node)) { - prefix = funDef->isConstructor() ? - "constructor" : - funDef->isFallback() ? - "fallback" : - "function_" + funDef->name(); - prefix += "_"; + prefix += TokenTraits::toString(funDef->kind()); + if (!funDef->name().empty()) + prefix += "_" + funDef->name() + "_"; } return prefix + to_string(_node->id()); } diff --git a/libsolidity/formal/CHC.h b/libsolidity/formal/CHC.h index a5ab9e06b824..664ae98bceb1 100644 --- a/libsolidity/formal/CHC.h +++ b/libsolidity/formal/CHC.h @@ -34,6 +34,8 @@ #include +#include + #include namespace dev @@ -48,6 +50,7 @@ class CHC: public SMTEncoder smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, smt::SMTSolverChoice _enabledSolvers ); @@ -208,6 +211,9 @@ class CHC: public SMTEncoder /// ErrorReporter that comes from CompilerStack. langutil::ErrorReporter& m_outerErrorReporter; + + /// SMT solvers that are chosen at runtime. + smt::SMTSolverChoice m_enabledSolvers; }; } diff --git a/libsolidity/formal/CHCSmtLib2Interface.cpp b/libsolidity/formal/CHCSmtLib2Interface.cpp index fb424d78263b..ceb12bbac5da 100644 --- a/libsolidity/formal/CHCSmtLib2Interface.cpp +++ b/libsolidity/formal/CHCSmtLib2Interface.cpp @@ -34,9 +34,13 @@ using namespace dev; using namespace dev::solidity; using namespace dev::solidity::smt; -CHCSmtLib2Interface::CHCSmtLib2Interface(map const& _queryResponses): - m_smtlib2(make_shared(_queryResponses)), - m_queryResponses(_queryResponses) +CHCSmtLib2Interface::CHCSmtLib2Interface( + map const& _queryResponses, + ReadCallback::Callback const& _smtCallback +): + m_smtlib2(make_shared(_queryResponses, _smtCallback)), + m_queryResponses(_queryResponses), + m_smtCallback(_smtCallback) { reset(); } @@ -152,9 +156,12 @@ string CHCSmtLib2Interface::querySolver(string const& _input) h256 inputHash = dev::keccak256(_input); if (m_queryResponses.count(inputHash)) return m_queryResponses.at(inputHash); - else + if (m_smtCallback) { - m_unhandledQueries.push_back(_input); - return "unknown\n"; + auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input); + if (result.success) + return result.responseOrErrorMessage; } + m_unhandledQueries.push_back(_input); + return "unknown\n"; } diff --git a/libsolidity/formal/CHCSmtLib2Interface.h b/libsolidity/formal/CHCSmtLib2Interface.h index 22bbba859fe2..8aebdd8b2087 100644 --- a/libsolidity/formal/CHCSmtLib2Interface.h +++ b/libsolidity/formal/CHCSmtLib2Interface.h @@ -35,7 +35,10 @@ namespace smt class CHCSmtLib2Interface: public CHCSolverInterface { public: - explicit CHCSmtLib2Interface(std::map const& _queryResponses); + explicit CHCSmtLib2Interface( + std::map const& _queryResponses, + ReadCallback::Callback const& _smtCallback + ); void reset(); @@ -68,6 +71,8 @@ class CHCSmtLib2Interface: public CHCSolverInterface std::map const& m_queryResponses; std::vector m_unhandledQueries; + + ReadCallback::Callback m_smtCallback; }; } diff --git a/libsolidity/formal/ModelChecker.cpp b/libsolidity/formal/ModelChecker.cpp index 50850980532d..afa06ff2c88d 100644 --- a/libsolidity/formal/ModelChecker.cpp +++ b/libsolidity/formal/ModelChecker.cpp @@ -25,10 +25,11 @@ using namespace dev::solidity; ModelChecker::ModelChecker( ErrorReporter& _errorReporter, map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, smt::SMTSolverChoice _enabledSolvers ): - m_bmc(m_context, _errorReporter, _smtlib2Responses, _enabledSolvers), - m_chc(m_context, _errorReporter, _smtlib2Responses, _enabledSolvers), + m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers), + m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers), m_context() { } @@ -46,3 +47,15 @@ vector ModelChecker::unhandledQueries() { return m_bmc.unhandledQueries() + m_chc.unhandledQueries(); } + +smt::SMTSolverChoice ModelChecker::availableSolvers() +{ + smt::SMTSolverChoice available = smt::SMTSolverChoice::None(); +#ifdef HAVE_Z3 + available.z3 = true; +#endif +#ifdef HAVE_CVC4 + available.cvc4 = true; +#endif + return available; +} diff --git a/libsolidity/formal/ModelChecker.h b/libsolidity/formal/ModelChecker.h index d7ec7c1b324d..d540266ffb5a 100644 --- a/libsolidity/formal/ModelChecker.h +++ b/libsolidity/formal/ModelChecker.h @@ -49,6 +49,7 @@ class ModelChecker ModelChecker( langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback = ReadCallback::Callback(), smt::SMTSolverChoice _enabledSolvers = smt::SMTSolverChoice::All() ); @@ -59,6 +60,9 @@ class ModelChecker /// the constructor. std::vector unhandledQueries(); + /// @returns SMT solvers that are available via the C++ API. + static smt::SMTSolverChoice availableSolvers(); + private: /// Bounded Model Checker engine. BMC m_bmc; diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 0f04f412bb02..70b4a6c9f448 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -70,6 +70,7 @@ bool SMTEncoder::visit(ContractDefinition const& _contract) for (auto const& function: resolvedFunctions) if ( function->name() == baseFunction->name() && + function->kind() == baseFunction->kind() && FunctionType(*function).asCallableFunction(false)-> hasEqualParameterTypes(*FunctionType(*baseFunction).asCallableFunction(false)) ) @@ -915,6 +916,15 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess) m_uninterpretedTerms.insert(&_indexAccess); } +void SMTEncoder::endVisit(IndexRangeAccess const& _indexRangeAccess) +{ + createExpr(_indexRangeAccess); + m_errorReporter.warning( + _indexRangeAccess.location(), + "Assertion checker does not yet implement this expression." + ); +} + void SMTEncoder::arrayAssignment() { m_arrayAssignmentHappened = true; diff --git a/libsolidity/formal/SMTEncoder.h b/libsolidity/formal/SMTEncoder.h index 8931e8dcfae3..b39fff27046d 100644 --- a/libsolidity/formal/SMTEncoder.h +++ b/libsolidity/formal/SMTEncoder.h @@ -89,6 +89,7 @@ class SMTEncoder: public ASTConstVisitor void endVisit(Return const& _node) override; bool visit(MemberAccess const& _node) override; void endVisit(IndexAccess const& _node) override; + void endVisit(IndexRangeAccess const& _node) override; bool visit(InlineAssembly const& _node) override; void endVisit(Break const&) override {} void endVisit(Continue const&) override {} diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp index 3c0f386010ac..77a44abe1d74 100644 --- a/libsolidity/formal/SMTLib2Interface.cpp +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -34,8 +34,12 @@ using namespace dev; using namespace dev::solidity; using namespace dev::solidity::smt; -SMTLib2Interface::SMTLib2Interface(map const& _queryResponses): - m_queryResponses(_queryResponses) +SMTLib2Interface::SMTLib2Interface( + map const& _queryResponses, + ReadCallback::Callback const& _smtCallback +): + m_queryResponses(_queryResponses), + m_smtCallback(_smtCallback) { reset(); } @@ -230,9 +234,12 @@ string SMTLib2Interface::querySolver(string const& _input) h256 inputHash = dev::keccak256(_input); if (m_queryResponses.count(inputHash)) return m_queryResponses.at(inputHash); - else + if (m_smtCallback) { - m_unhandledQueries.push_back(_input); - return "unknown\n"; + auto result = m_smtCallback(ReadCallback::kindString(ReadCallback::Kind::SMTQuery), _input); + if (result.success) + return result.responseOrErrorMessage; } + m_unhandledQueries.push_back(_input); + return "unknown\n"; } diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h index 21c11f80dced..f3e242f9333c 100644 --- a/libsolidity/formal/SMTLib2Interface.h +++ b/libsolidity/formal/SMTLib2Interface.h @@ -41,7 +41,10 @@ namespace smt class SMTLib2Interface: public SolverInterface, public boost::noncopyable { public: - explicit SMTLib2Interface(std::map const& _queryResponses); + explicit SMTLib2Interface( + std::map const& _queryResponses, + ReadCallback::Callback const& _smtCallback + ); void reset() override; @@ -78,6 +81,8 @@ class SMTLib2Interface: public SolverInterface, public boost::noncopyable std::map const& m_queryResponses; std::vector m_unhandledQueries; + + ReadCallback::Callback m_smtCallback; }; } diff --git a/libsolidity/formal/SMTPortfolio.cpp b/libsolidity/formal/SMTPortfolio.cpp index 9b9795e9a2ec..26d0f4b4e078 100644 --- a/libsolidity/formal/SMTPortfolio.cpp +++ b/libsolidity/formal/SMTPortfolio.cpp @@ -32,10 +32,11 @@ using namespace dev::solidity::smt; SMTPortfolio::SMTPortfolio( map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, SMTSolverChoice _enabledSolvers ) { - m_solvers.emplace_back(make_unique(_smtlib2Responses)); + m_solvers.emplace_back(make_unique(_smtlib2Responses, _smtCallback)); #ifdef HAVE_Z3 if (_enabledSolvers.z3) m_solvers.emplace_back(make_unique()); diff --git a/libsolidity/formal/SMTPortfolio.h b/libsolidity/formal/SMTPortfolio.h index 41472c1875ba..47691dff5858 100644 --- a/libsolidity/formal/SMTPortfolio.h +++ b/libsolidity/formal/SMTPortfolio.h @@ -44,6 +44,7 @@ class SMTPortfolio: public SolverInterface, public boost::noncopyable public: SMTPortfolio( std::map const& _smtlib2Responses, + ReadCallback::Callback const& _smtCallback, SMTSolverChoice _enabledSolvers ); diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index a21053d9fd98..359b8c9762cd 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -58,9 +58,6 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) Json::Value method; method["type"] = "function"; method["name"] = it.second->declaration().name(); - // TODO: deprecate constant in a future release - 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(), @@ -83,7 +80,6 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = "constructor"; - method["payable"] = externalFunctionType->isPayable(); method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability()); method["inputs"] = formatTypeList( externalFunctionType->parameterNames(), @@ -93,16 +89,16 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) ); abi.emplace(std::move(method)); } - if (_contractDef.fallbackFunction()) - { - FunctionType const* externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType(); - solAssert(!!externalFunctionType, ""); - Json::Value method; - method["type"] = "fallback"; - method["payable"] = externalFunctionType->isPayable(); - method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability()); - abi.emplace(std::move(method)); - } + for (auto const* fallbackOrReceive: {_contractDef.fallbackFunction(), _contractDef.receiveFunction()}) + if (fallbackOrReceive) + { + FunctionType const* externalFunctionType = FunctionType(*fallbackOrReceive, false).interfaceFunctionType(); + solAssert(!!externalFunctionType, ""); + Json::Value method; + method["type"] = TokenTraits::toString(fallbackOrReceive->kind()); + method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability()); + abi.emplace(std::move(method)); + } for (auto const& it: _contractDef.interfaceEvents()) { Json::Value event; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index e605c6b485c8..9d9a0a7b30d0 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -79,6 +79,7 @@ static int g_compilerStackCounts = 0; CompilerStack::CompilerStack(ReadCallback::Callback const& _readFile): m_readFile{_readFile}, + m_enabledSMTSolvers{smt::SMTSolverChoice::All()}, m_generateIR{false}, m_generateEWasm{false}, m_errorList{}, @@ -132,6 +133,13 @@ void CompilerStack::setEVMVersion(langutil::EVMVersion _version) m_evmVersion = _version; } +void CompilerStack::setSMTSolverChoice(smt::SMTSolverChoice _enabledSMTSolvers) +{ + if (m_stackState >= ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set enabled SMT solvers before parsing.")); + m_enabledSMTSolvers = _enabledSMTSolvers; +} + void CompilerStack::setLibraries(std::map const& _libraries) { if (m_stackState >= ParsingPerformed) @@ -153,6 +161,14 @@ void CompilerStack::setOptimiserSettings(OptimiserSettings _settings) m_optimiserSettings = std::move(_settings); } +void CompilerStack::setRevertStringBehaviour(RevertStrings _revertStrings) +{ + if (m_stackState >= ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set revert string settings before parsing.")); + solUnimplementedAssert(_revertStrings == RevertStrings::Default || _revertStrings == RevertStrings::Strip, ""); + m_revertStrings = _revertStrings; +} + void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources) { if (m_stackState >= ParsingPerformed) @@ -160,6 +176,13 @@ void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources) m_metadataLiteralSources = _metadataLiteralSources; } +void CompilerStack::setMetadataHash(MetadataHash _metadataHash) +{ + if (m_stackState >= ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set metadata hash before parsing.")); + m_metadataHash = _metadataHash; +} + void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _response) { if (m_stackState >= ParsingPerformed) @@ -179,10 +202,13 @@ void CompilerStack::reset(bool _keepSettings) m_remappings.clear(); m_libraries.clear(); m_evmVersion = langutil::EVMVersion(); + m_enabledSMTSolvers = smt::SMTSolverChoice::All(); m_generateIR = false; m_generateEWasm = false; + m_revertStrings = RevertStrings::Default; m_optimiserSettings = OptimiserSettings::minimal(); m_metadataLiteralSources = false; + m_metadataHash = MetadataHash::IPFS; } m_globalContext.reset(); m_scopes.clear(); @@ -213,12 +239,6 @@ bool CompilerStack::parse() if (SemVerVersion{string(VersionString)}.isPrerelease()) m_errorReporter.warning("This is a pre-release compiler version, please do not use it in production."); - if (m_optimiserSettings.runYulOptimiser) - m_errorReporter.warning( - "The Yul optimiser is still experimental. " - "Do not use it in production unless correctness of generated code is verified with extensive tests." - ); - vector sourcesToParse; for (auto const& s: m_sources) sourcesToParse.push_back(s.first); @@ -261,41 +281,42 @@ bool CompilerStack::analyze() { SyntaxChecker syntaxChecker(m_errorReporter, m_optimiserSettings.runYulOptimiser); for (Source const* source: m_sourceOrder) - if (!syntaxChecker.checkSyntax(*source->ast)) + if (source->ast && !syntaxChecker.checkSyntax(*source->ast)) noErrors = false; DocStringAnalyser docStringAnalyser(m_errorReporter); for (Source const* source: m_sourceOrder) - if (!docStringAnalyser.analyseDocStrings(*source->ast)) + if (source->ast && !docStringAnalyser.analyseDocStrings(*source->ast)) noErrors = false; m_globalContext = make_shared(); NameAndTypeResolver resolver(*m_globalContext, m_evmVersion, m_scopes, m_errorReporter); for (Source const* source: m_sourceOrder) - if (!resolver.registerDeclarations(*source->ast)) + if (source->ast && !resolver.registerDeclarations(*source->ast)) return false; map sourceUnitsByName; for (auto& source: m_sources) sourceUnitsByName[source.first] = source.second.ast.get(); for (Source const* source: m_sourceOrder) - if (!resolver.performImports(*source->ast, sourceUnitsByName)) + if (source->ast && !resolver.performImports(*source->ast, sourceUnitsByName)) return false; // This is the main name and type resolution loop. Needs to be run for every contract, because // the special variables "this" and "super" must be set appropriately. for (Source const* source: m_sourceOrder) - for (ASTPointer const& node: source->ast->nodes()) - if (ContractDefinition* contract = dynamic_cast(node.get())) + if (source->ast) + for (ASTPointer const& node: source->ast->nodes()) { - - if (!resolver.resolveNamesAndTypes(*contract)) return 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 (!resolver.resolveNamesAndTypes(*node)) + return false; + if (ContractDefinition* contract = dynamic_cast(node.get())) + // 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; } // Next, we check inheritance, overrides, function collisions and other things at @@ -304,10 +325,11 @@ bool CompilerStack::analyze() // type checker. ContractLevelChecker contractLevelChecker(m_errorReporter); for (Source const* source: m_sourceOrder) - for (ASTPointer const& node: source->ast->nodes()) - if (ContractDefinition* contract = dynamic_cast(node.get())) - if (!contractLevelChecker.check(*contract)) - noErrors = false; + if (source->ast) + for (ASTPointer const& node: source->ast->nodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + if (!contractLevelChecker.check(*contract)) + noErrors = false; // New we run full type checks that go down to the expression level. This // cannot be done earlier, because we need cross-contract types and information @@ -318,17 +340,18 @@ bool CompilerStack::analyze() // which is only done one step later. TypeChecker typeChecker(m_evmVersion, m_errorReporter); for (Source const* source: m_sourceOrder) - for (ASTPointer const& node: source->ast->nodes()) - if (ContractDefinition* contract = dynamic_cast(node.get())) - if (!typeChecker.checkTypeRequirements(*contract)) - noErrors = false; + if (source->ast) + for (ASTPointer const& node: source->ast->nodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + if (!typeChecker.checkTypeRequirements(*contract)) + noErrors = false; if (noErrors) { // Checks that can only be done when all types of all AST nodes are known. PostTypeChecker postTypeChecker(m_errorReporter); for (Source const* source: m_sourceOrder) - if (!postTypeChecker.check(*source->ast)) + if (source->ast && !postTypeChecker.check(*source->ast)) noErrors = false; } @@ -338,14 +361,14 @@ bool CompilerStack::analyze() // variable is used before it is assigned to. CFG cfg(m_errorReporter); for (Source const* source: m_sourceOrder) - if (!cfg.constructFlow(*source->ast)) + if (source->ast && !cfg.constructFlow(*source->ast)) noErrors = false; if (noErrors) { ControlFlowAnalyzer controlFlowAnalyzer(cfg, m_errorReporter); for (Source const* source: m_sourceOrder) - if (!controlFlowAnalyzer.analyze(*source->ast)) + if (source->ast && !controlFlowAnalyzer.analyze(*source->ast)) noErrors = false; } } @@ -355,7 +378,7 @@ bool CompilerStack::analyze() // Checks for common mistakes. Only generates warnings. StaticAnalyzer staticAnalyzer(m_errorReporter); for (Source const* source: m_sourceOrder) - if (!staticAnalyzer.analyze(*source->ast)) + if (source->ast && !staticAnalyzer.analyze(*source->ast)) noErrors = false; } @@ -364,7 +387,8 @@ bool CompilerStack::analyze() // Check for state mutability in every function. vector> ast; for (Source const* source: m_sourceOrder) - ast.push_back(source->ast); + if (source->ast) + ast.push_back(source->ast); if (!ViewPureChecker(ast, m_errorReporter).check()) noErrors = false; @@ -372,9 +396,10 @@ bool CompilerStack::analyze() if (noErrors) { - ModelChecker modelChecker(m_errorReporter, m_smtlib2Responses); + ModelChecker modelChecker(m_errorReporter, m_smtlib2Responses, m_readFile, m_enabledSMTSolvers); for (Source const* source: m_sourceOrder) - modelChecker.analyze(*source->ast); + if (source->ast) + modelChecker.analyze(*source->ast); m_unhandledSMTLib2Queries += modelChecker.unhandledQueries(); } } @@ -869,7 +894,7 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string ReadCallback::Result result{false, string("File not supplied initially.")}; if (m_readFile) - result = m_readFile(importPath); + result = m_readFile(ReadCallback::kindString(ReadCallback::Kind::ReadFile), importPath); if (result.success) newSources[importPath] = result.responseOrErrorMessage; @@ -965,7 +990,7 @@ namespace bool onlySafeExperimentalFeaturesActivated(set const& features) { for (auto const feature: features) - if (!ExperimentalFeatureOnlyAnalysis.count(feature)) + if (!ExperimentalFeatureWithoutWarning.count(feature)) return false; return true; } @@ -987,7 +1012,7 @@ void CompilerStack::compileContract( Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); - shared_ptr compiler = make_shared(m_evmVersion, m_optimiserSettings); + shared_ptr compiler = make_shared(m_evmVersion, m_revertStrings, m_optimiserSettings); compiledContract.compiler = compiler; bytes cborEncodedMetadata = createCBORMetadata( @@ -1178,8 +1203,15 @@ string CompilerStack::createMetadata(Contract const& _contract) const meta["settings"]["optimizer"]["details"] = std::move(details); } + if (m_revertStrings != RevertStrings::Default) + meta["settings"]["debug"]["revertStrings"] = revertStringsToString(m_revertStrings); + if (m_metadataLiteralSources) meta["settings"]["metadata"]["useLiteralContent"] = true; + + static vector hashes{"ipfs", "bzzr1", "none"}; + meta["settings"]["metadata"]["bytecodeHash"] = hashes.at(unsigned(m_metadataHash)); + meta["settings"]["evmVersion"] = m_evmVersion.name(); meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] = _contract.contract->annotation().canonicalName; @@ -1288,7 +1320,17 @@ class MetadataCBOREncoder bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimentalMode) { MetadataCBOREncoder encoder; - encoder.pushBytes("bzzr1", dev::bzzr1Hash(_metadata).asBytes()); + + if (m_metadataHash == MetadataHash::IPFS) + { + solAssert(_metadata.length() < 1024 * 256, "Metadata too large."); + encoder.pushBytes("ipfs", dev::ipfsHash(_metadata)); + } + else if (m_metadataHash == MetadataHash::Bzzr1) + encoder.pushBytes("bzzr1", dev::bzzr1Hash(_metadata).asBytes()); + else + solAssert(m_metadataHash == MetadataHash::None, "Invalid metadata hash"); + if (_experimentalMode) encoder.pushBool("experimental", true); if (m_release) @@ -1308,6 +1350,7 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con int prevStart = -1; int prevLength = -1; int prevSourceIndex = -1; + size_t prevModifierDepth = -1; char prevJump = 0; for (auto const& item: _items) { @@ -1325,19 +1368,24 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con jump = 'i'; else if (item.getJumpType() == eth::AssemblyItem::JumpType::OutOfFunction) jump = 'o'; + size_t modifierDepth = item.m_modifierDepth; - unsigned components = 4; - if (jump == prevJump) + unsigned components = 5; + if (modifierDepth == prevModifierDepth) { components--; - if (sourceIndex == prevSourceIndex) + if (jump == prevJump) { components--; - if (length == prevLength) + if (sourceIndex == prevSourceIndex) { components--; - if (location.start == prevStart) + if (length == prevLength) + { components--; + if (location.start == prevStart) + components--; + } } } } @@ -1361,6 +1409,12 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con ret += ':'; if (jump != prevJump) ret += jump; + if (components-- > 0) + { + ret += ':'; + if (modifierDepth != prevModifierDepth) + ret += to_string(modifierDepth); + } } } } @@ -1370,6 +1424,7 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con prevLength = length; prevSourceIndex = sourceIndex; prevJump = jump; + prevModifierDepth = modifierDepth; } return ret; } @@ -1437,8 +1492,8 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const Json::Value internalFunctions(Json::objectValue); for (auto const& it: contract.definedFunctions()) { - /// Exclude externally visible functions, constructor and the fallback function - if (it->isPartOfExternalInterface() || it->isConstructor() || it->isFallback()) + /// Exclude externally visible functions, constructor, fallback and receive ether function + if (it->isPartOfExternalInterface() || !it->isOrdinary()) continue; size_t entry = functionEntryPoint(_contractName, *it); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 005ef911f592..3133acfe8baf 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include @@ -92,6 +94,12 @@ class CompilerStack: boost::noncopyable CompilationSuccessful }; + enum class MetadataHash { + IPFS, + Bzzr1, + None + }; + struct Remapping { std::string context; @@ -100,7 +108,7 @@ class CompilerStack: boost::noncopyable }; /// Creates a new compiler stack. - /// @param _readFile callback to used to read files for import statements. Must return + /// @param _readFile callback used to read files for import statements. Must return /// and must not emit exceptions. explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback()); @@ -139,6 +147,9 @@ class CompilerStack: boost::noncopyable /// Must be set before parsing. void setOptimiserSettings(OptimiserSettings _settings); + /// Sets whether to strip revert strings, add additional strings or do nothing at all. + void setRevertStringBehaviour(RevertStrings _revertStrings); + /// Set whether or not parser error is desired. /// When called without an argument it will revert to the default. /// Must be set before parsing. @@ -152,6 +163,9 @@ class CompilerStack: boost::noncopyable /// Must be set before parsing. void setEVMVersion(langutil::EVMVersion _version = langutil::EVMVersion{}); + /// Set which SMT solvers should be enabled. + void setSMTSolverChoice(smt::SMTSolverChoice _enabledSolvers); + /// Sets the requested contract names by source. /// If empty, no filtering is performed and every contract /// found in the supplied sources is compiled. @@ -171,6 +185,11 @@ class CompilerStack: boost::noncopyable /// Must be set before parsing. void useMetadataLiteralSources(bool _metadataLiteralSources); + /// Sets whether and which hash should be used + /// to store the metadata in the bytecode. + /// @param _metadataHash can be IPFS, Bzzr1, None + void setMetadataHash(MetadataHash _metadataHash); + /// Sets the sources. Must be set before parsing. void setSources(StringMap _sources); @@ -412,7 +431,9 @@ class CompilerStack: boost::noncopyable ReadCallback::Callback m_readFile; OptimiserSettings m_optimiserSettings; + RevertStrings m_revertStrings = RevertStrings::Default; langutil::EVMVersion m_evmVersion; + smt::SMTSolverChoice m_enabledSMTSolvers; std::map> m_requestedContractNames; bool m_generateIR; bool m_generateEWasm; @@ -431,6 +452,7 @@ class CompilerStack: boost::noncopyable langutil::ErrorList m_errorList; langutil::ErrorReporter m_errorReporter; bool m_metadataLiteralSources = false; + MetadataHash m_metadataHash = MetadataHash::IPFS; bool m_parserErrorRecovery = false; State m_stackState = Empty; /// Whether or not there has been an error during processing. diff --git a/libsolidity/interface/DebugSettings.h b/libsolidity/interface/DebugSettings.h new file mode 100644 index 000000000000..5b3e00850154 --- /dev/null +++ b/libsolidity/interface/DebugSettings.h @@ -0,0 +1,63 @@ +/* + 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 . +*/ +/** + * Settings to aid debugging. + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +enum class RevertStrings +{ + + Default, // no compiler-generated strings, keep user-supplied strings + Strip, // no compiler-generated strings, remove user-supplied strings (if possible) + Debug, // add strings for internal reverts, keep user-supplied strings + VerboseDebug // add strings for internal reverts, add user-supplied strings if not provided +}; + +inline std::string revertStringsToString(RevertStrings _str) +{ + switch (_str) + { + case RevertStrings::Default: return "default"; + case RevertStrings::Strip: return "strip"; + case RevertStrings::Debug: return "debug"; + case RevertStrings::VerboseDebug: return "verboseDebug"; + } + // Cannot reach this. + return "INVALID"; +} + +inline std::optional revertStringsFromString(std::string const& _str) +{ + for (auto i: {RevertStrings::Default, RevertStrings::Strip, RevertStrings::Debug, RevertStrings::VerboseDebug}) + if (revertStringsToString(i) == _str) + return i; + return {}; +} + +} +} diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp index e28d8703e4ad..2c8c68694378 100644 --- a/libsolidity/interface/Natspec.cpp +++ b/libsolidity/interface/Natspec.cpp @@ -100,15 +100,54 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef) { Json::Value method(devDocumentation(fun->annotation().docTags)); if (!method.empty()) + { // add the function, only if we have any documentation to add + Json::Value jsonReturn = extractReturnParameterDocs(fun->annotation().docTags, *fun); + + if (!jsonReturn.empty()) + method["returns"] = jsonReturn; + methods[it.second->externalSignature()] = method; + } } } + doc["methods"] = methods; return doc; } +Json::Value Natspec::extractReturnParameterDocs(std::multimap const& _tags, FunctionDefinition const& _functionDef) +{ + Json::Value jsonReturn{Json::objectValue}; + auto returnDocs = _tags.equal_range("return"); + + if (!_functionDef.returnParameters().empty()) + { + size_t n = 0; + for (auto i = returnDocs.first; i != returnDocs.second; i++) + { + string paramName = _functionDef.returnParameters().at(n)->name(); + string content = i->second.content; + + if (paramName.empty()) + paramName = "_" + std::to_string(n); + else + { + //check to make sure the first word of the doc str is the same as the return name + auto nameEndPos = content.find_first_of(" \t"); + solAssert(content.substr(0, nameEndPos) == paramName, "No return param name given: " + paramName); + content = content.substr(nameEndPos+1); + } + + jsonReturn[paramName] = Json::Value(content); + n++; + } + } + + return jsonReturn; +} + string Natspec::extractDoc(multimap const& _tags, string const& _name) { string value; @@ -129,12 +168,6 @@ Json::Value Natspec::devDocumentation(std::multimap const& if (!author.empty()) json["author"] = author; - // for constructors, the "return" node will never exist. invalid tags - // will already generate an error within dev::solidity::DocStringAnalyzer. - auto ret = extractDoc(_tags, "return"); - if (!ret.empty()) - json["return"] = ret; - Json::Value params(Json::objectValue); auto paramRange = _tags.equal_range("param"); for (auto i = paramRange.first; i != paramRange.second; ++i) diff --git a/libsolidity/interface/Natspec.h b/libsolidity/interface/Natspec.h index 7a0c40a1a85f..c37a4ac0ea17 100644 --- a/libsolidity/interface/Natspec.h +++ b/libsolidity/interface/Natspec.h @@ -28,6 +28,7 @@ #include #include #include +#include namespace dev { @@ -60,6 +61,13 @@ class Natspec /// @return A JSON representation /// of the contract's developer documentation static Json::Value devDocumentation(std::multimap const& _tags); + + /// Helper-function that will create a json object for the "returns" field for a given function definition. + /// @param _tags docTags that are used. + /// @param _functionDef functionDefinition that is used to determine which return parameters are named. + /// @return A JSON representation + /// of a method's return notice documentation + static Json::Value extractReturnParameterDocs(std::multimap const& _tags, FunctionDefinition const& _functionDef); }; } //solidity NS diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index 83203865f746..0b1880516133 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -54,19 +54,15 @@ struct OptimiserSettings s.runDeduplicate = true; s.runCSE = true; s.runConstantOptimiser = true; - // The only disabled ones - s.optimizeStackAllocation = false; - s.runYulOptimiser = false; + s.runYulOptimiser = true; + s.optimizeStackAllocation = true; s.expectedExecutionsPerDeployment = 200; return s; } - /// Standard optimisations plus yul and stack optimiser. + /// Full optimisations. Currently an alias for standard optimisations. static OptimiserSettings full() { - OptimiserSettings s = standard(); - s.optimizeStackAllocation = true; - s.runYulOptimiser = true; - return s; + return standard(); } bool operator==(OptimiserSettings const& _other) const diff --git a/libsolidity/interface/ReadFile.h b/libsolidity/interface/ReadFile.h index 3b3d747eda44..05fd2c5a2d50 100644 --- a/libsolidity/interface/ReadFile.h +++ b/libsolidity/interface/ReadFile.h @@ -17,6 +17,8 @@ #pragma once +#include + #include #include #include @@ -37,8 +39,27 @@ class ReadCallback: boost::noncopyable std::string responseOrErrorMessage; }; + enum class Kind + { + ReadFile, + SMTQuery + }; + + static std::string kindString(Kind _kind) + { + switch (_kind) + { + case Kind::ReadFile: + return "source"; + case Kind::SMTQuery: + return "smt-query"; + default: + solAssert(false, ""); + } + } + /// File reading or generic query callback. - using Callback = std::function; + using Callback = std::function; }; } diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 8e226a7df17f..25f6a5199fca 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -351,7 +351,7 @@ std::optional checkAuxiliaryInputKeys(Json::Value const& _input) std::optional checkSettingsKeys(Json::Value const& _input) { - static set keys{"parserErrorRecovery", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"}; + static set keys{"parserErrorRecovery", "debug", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"}; return checkKeys(_input, keys, "settings"); } @@ -380,9 +380,16 @@ std::optional checkOptimizerDetail(Json::Value const& _details, std std::optional checkMetadataKeys(Json::Value const& _input) { - if (_input.isObject() && _input.isMember("useLiteralContent") && !_input["useLiteralContent"].isBool()) - return formatFatalError("JSONError", "\"settings.metadata.useLiteralContent\" must be Boolean"); - static set keys{"useLiteralContent"}; + if (_input.isObject()) + { + if (_input.isMember("useLiteralContent") && !_input["useLiteralContent"].isBool()) + return formatFatalError("JSONError", "\"settings.metadata.useLiteralContent\" must be Boolean"); + + static set hashes{"ipfs", "bzzr1", "none"}; + if (_input.isMember("bytecodeHash") && !hashes.count(_input["bytecodeHash"].asString())) + return formatFatalError("JSONError", "\"settings.metadata.bytecodeHash\" must be \"ipfs\", \"bzzr1\" or \"none\""); + } + static set keys{"useLiteralContent", "bytecodeHash"}; return checkKeys(_input, keys, "settings.metadata"); } @@ -474,8 +481,7 @@ boost::variant parseOptimizerSettings(Json::Valu return *error; if (auto error = checkOptimizerDetail(details, "yul", settings.runYulOptimiser)) return *error; - if (settings.runYulOptimiser) - settings.optimizeStackAllocation = true; + settings.optimizeStackAllocation = settings.runYulOptimiser; if (details.isMember("yulDetails")) { if (!settings.runYulOptimiser) @@ -549,7 +555,7 @@ boost::variant StandardCompile { if (!url.isString()) return formatFatalError("JSONError", "URL must be a string."); - ReadCallback::Result result = m_readFile(url.asString()); + ReadCallback::Result result = m_readFile(ReadCallback::kindString(ReadCallback::Kind::ReadFile), url.asString()); if (result.success) { if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage)) @@ -643,6 +649,27 @@ boost::variant StandardCompile ret.evmVersion = *version; } + if (settings.isMember("debug")) + { + if (auto result = checkKeys(settings["debug"], {"revertStrings"}, "settings.debug")) + return *result; + + if (settings["debug"].isMember("revertStrings")) + { + if (!settings["debug"]["revertStrings"].isString()) + return formatFatalError("JSONError", "settings.debug.revertStrings must be a string."); + std::optional revertStrings = revertStringsFromString(settings["debug"]["revertStrings"].asString()); + if (!revertStrings) + return formatFatalError("JSONError", "Invalid value for settings.debug.revertStrings."); + if (*revertStrings != RevertStrings::Default && *revertStrings != RevertStrings::Strip) + return formatFatalError( + "UnimplementedFeatureError", + "Only \"default\" and \"strip\" are implemented for settings.debug.revertStrings for now." + ); + ret.revertStrings = *revertStrings; + } + } + if (settings.isMember("remappings") && !settings["remappings"].isArray()) return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings."); @@ -712,6 +739,16 @@ boost::variant StandardCompile return *result; ret.metadataLiteralSources = metadataSettings.get("useLiteralContent", Json::Value(false)).asBool(); + if (metadataSettings.isMember("bytecodeHash")) + { + auto metadataHash = metadataSettings["bytecodeHash"].asString(); + ret.metadataHash = + metadataHash == "ipfs" ? + CompilerStack::MetadataHash::IPFS : + metadataHash == "bzzr1" ? + CompilerStack::MetadataHash::Bzzr1 : + CompilerStack::MetadataHash::None; + } Json::Value outputSelection = settings.get("outputSelection", Json::Value()); @@ -735,8 +772,10 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting compilerStack.setParserErrorRecovery(_inputsAndSettings.parserErrorRecovery); compilerStack.setRemappings(_inputsAndSettings.remappings); compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); + compilerStack.setRevertStringBehaviour(_inputsAndSettings.revertStrings); compilerStack.setLibraries(_inputsAndSettings.libraries); compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources); + compilerStack.setMetadataHash(_inputsAndSettings.metadataHash); compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection)); compilerStack.enableIRGeneration(isIRRequested(_inputsAndSettings.outputSelection)); @@ -985,6 +1024,8 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) return formatFatalError("JSONError", "Field \"settings.remappings\" cannot be used for Yul."); if (!_inputsAndSettings.libraries.empty()) return formatFatalError("JSONError", "Field \"settings.libraries\" cannot be used for Yul."); + if (_inputsAndSettings.revertStrings != RevertStrings::Default) + return formatFatalError("JSONError", "Field \"settings.debug.revertStrings\" cannot be used for Yul."); Json::Value output = Json::objectValue; diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index 63ceb4f6b3c6..346ef7dcdcc3 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -41,7 +41,7 @@ class StandardCompiler: boost::noncopyable { public: /// Creates a new StandardCompiler. - /// @param _readFile callback to used to read files for import statements. Must return + /// @param _readFile callback used to read files for import statements. Must return /// and must not emit exceptions. explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()): m_readFile(_readFile) @@ -65,9 +65,11 @@ class StandardCompiler: boost::noncopyable std::map smtLib2Responses; langutil::EVMVersion evmVersion; std::vector remappings; + RevertStrings revertStrings = RevertStrings::Default; OptimiserSettings optimiserSettings = OptimiserSettings::minimal(); std::map libraries; bool metadataLiteralSources = false; + CompilerStack::MetadataHash metadataHash = CompilerStack::MetadataHash::IPFS; Json::Value outputSelection; }; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 07f0ef5f7089..881bd3cab4b7 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -91,13 +91,20 @@ ASTPointer Parser::parse(shared_ptr const& _scanner) case Token::Import: nodes.push_back(parseImportDirective()); break; + case Token::Abstract: case Token::Interface: case Token::Contract: case Token::Library: nodes.push_back(parseContractDefinition()); break; + case Token::Struct: + nodes.push_back(parseStructDefinition()); + break; + case Token::Enum: + nodes.push_back(parseEnumDefinition()); + break; default: - fatalParserError(string("Expected pragma, import directive or contract/interface/library definition.")); + fatalParserError(string("Expected pragma, import directive or contract/interface/library/struct/enum definition.")); } } solAssert(m_recursionDepth == 0, ""); @@ -238,9 +245,15 @@ ASTPointer Parser::parseImportDirective() return nodeFactory.createNode(path, unitAlias, move(symbolAliases)); } -ContractDefinition::ContractKind Parser::parseContractKind() +std::pair Parser::parseContractKind() { ContractDefinition::ContractKind kind; + bool abstract = false; + if (m_scanner->currentToken() == Token::Abstract) + { + abstract = true; + m_scanner->next(); + } switch (m_scanner->currentToken()) { case Token::Interface: @@ -256,7 +269,7 @@ ContractDefinition::ContractKind Parser::parseContractKind() solAssert(false, "Invalid contract kind."); } m_scanner->next(); - return kind; + return std::make_pair(kind, abstract); } ASTPointer Parser::parseContractDefinition() @@ -267,7 +280,7 @@ ASTPointer Parser::parseContractDefinition() ASTPointer docString; vector> baseContracts; vector> subNodes; - ContractDefinition::ContractKind contractKind = ContractDefinition::ContractKind::Contract; + std::pair contractKind{}; try { if (m_scanner->currentCommentLiteral() != "") @@ -287,19 +300,23 @@ ASTPointer Parser::parseContractDefinition() Token currentTokenValue = m_scanner->currentToken(); if (currentTokenValue == Token::RBrace) break; - else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor) - // This can be a function or a state variable of function type (especially - // complicated to distinguish fallback function from function type state variable) - subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable()); + else if ( + (currentTokenValue == Token::Function && m_scanner->peekNextToken() != Token::LParen) || + currentTokenValue == Token::Constructor || + currentTokenValue == Token::Receive || + currentTokenValue == Token::Fallback + ) + subNodes.push_back(parseFunctionDefinition()); else if (currentTokenValue == Token::Struct) subNodes.push_back(parseStructDefinition()); else if (currentTokenValue == Token::Enum) subNodes.push_back(parseEnumDefinition()); else if ( - currentTokenValue == Token::Identifier || - currentTokenValue == Token::Mapping || - TokenTraits::isElementaryTypeName(currentTokenValue) - ) + currentTokenValue == Token::Identifier || + currentTokenValue == Token::Mapping || + TokenTraits::isElementaryTypeName(currentTokenValue) || + (currentTokenValue == Token::Function && m_scanner->peekNextToken() == Token::LParen) + ) { VarDeclParserOptions options; options.isStateVariable = true; @@ -337,7 +354,8 @@ ASTPointer Parser::parseContractDefinition() docString, baseContracts, subNodes, - contractKind + contractKind.first, + contractKind.second ); } @@ -359,23 +377,23 @@ ASTPointer Parser::parseInheritanceSpecifier() return nodeFactory.createNode(name, std::move(arguments)); } -Declaration::Visibility Parser::parseVisibilitySpecifier() +Visibility Parser::parseVisibilitySpecifier() { - Declaration::Visibility visibility(Declaration::Visibility::Default); + Visibility visibility(Visibility::Default); Token token = m_scanner->currentToken(); switch (token) { case Token::Public: - visibility = Declaration::Visibility::Public; + visibility = Visibility::Public; break; case Token::Internal: - visibility = Declaration::Visibility::Internal; + visibility = Visibility::Internal; break; case Token::Private: - visibility = Declaration::Visibility::Private; + visibility = Visibility::Private; break; case Token::External: - visibility = Declaration::Visibility::External; + visibility = Visibility::External; break; default: solAssert(false, "Invalid visibility specifier."); @@ -384,6 +402,36 @@ Declaration::Visibility Parser::parseVisibilitySpecifier() return visibility; } +ASTPointer Parser::parseOverrideSpecifier() +{ + solAssert(m_scanner->currentToken() == Token::Override, ""); + + ASTNodeFactory nodeFactory(*this); + std::vector> overrides; + + nodeFactory.markEndPosition(); + m_scanner->next(); + + if (m_scanner->currentToken() == Token::LParen) + { + m_scanner->next(); + while (true) + { + overrides.push_back(parseUserDefinedTypeName()); + + if (m_scanner->currentToken() == Token::RParen) + break; + + expectToken(Token::Comma); + } + + nodeFactory.markEndPosition(); + expectToken(Token::RParen); + } + + return nodeFactory.createNode(move(overrides)); +} + StateMutability Parser::parseStateMutability() { StateMutability stateMutability(StateMutability::NonPayable); @@ -413,63 +461,26 @@ StateMutability Parser::parseStateMutability() return stateMutability; } -Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers) +Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _isStateVariable) { RecursionGuard recursionGuard(*this); FunctionHeaderParserResult result; - result.isConstructor = false; - - if (m_scanner->currentToken() == Token::Constructor) - result.isConstructor = true; - else if (m_scanner->currentToken() != Token::Function) - solAssert(false, "Function or constructor expected."); - m_scanner->next(); - - if (result.isConstructor) - result.name = make_shared(); - else if (_forceEmptyName || m_scanner->currentToken() == Token::LParen) - result.name = make_shared(); - else if (m_scanner->currentToken() == Token::Constructor) - fatalParserError(string( - "This function is named \"constructor\" but is not the constructor of the contract. " - "If you intend this to be a constructor, use \"constructor(...) { ... }\" without the \"function\" keyword to define it." - )); - else - result.name = expectIdentifierToken(); - - VarDeclParserOptions options; options.allowLocationSpecifier = true; result.parameters = parseParameterList(options); while (true) { Token token = m_scanner->currentToken(); - if (_allowModifiers && token == Token::Identifier) - { - // If the name is empty (and this is not a constructor), - // then this can either be a modifier (fallback function declaration) - // or the name of the state variable (function type name plus variable). - if ((result.name->empty() && !result.isConstructor) && ( - m_scanner->peekNextToken() == Token::Semicolon || - m_scanner->peekNextToken() == Token::Assign - )) - // Variable declaration, break here. - break; - else - result.modifiers.push_back(parseModifierInvocation()); - } + if (!_isStateVariable && token == Token::Identifier) + result.modifiers.push_back(parseModifierInvocation()); else if (TokenTraits::isVisibilitySpecifier(token)) { - if (result.visibility != Declaration::Visibility::Default) + if (result.visibility != Visibility::Default) { // There is the special case of a public state variable of function type. // Detect this and return early. - if ( - (result.visibility == Declaration::Visibility::External || result.visibility == Declaration::Visibility::Internal) && - result.modifiers.empty() && - (result.name->empty() && !result.isConstructor) - ) + if (_isStateVariable && (result.visibility == Visibility::External || result.visibility == Visibility::Internal)) break; parserError(string( "Visibility already specified as \"" + @@ -495,6 +506,21 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN else result.stateMutability = parseStateMutability(); } + else if (!_isStateVariable && token == Token::Override) + { + if (result.overrides) + parserError("Override already specified."); + + result.overrides = parseOverrideSpecifier(); + } + else if (!_isStateVariable && token == Token::Virtual) + { + if (result.isVirtual) + parserError("Virtual already specified."); + + result.isVirtual = true; + m_scanner->next(); + } else break; } @@ -509,7 +535,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN return result; } -ASTPointer Parser::parseFunctionDefinitionOrFunctionTypeStateVariable() +ASTPointer Parser::parseFunctionDefinition() { RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); @@ -517,55 +543,68 @@ ASTPointer Parser::parseFunctionDefinitionOrFunctionTypeStateVariable() if (m_scanner->currentCommentLiteral() != "") docstring = make_shared(m_scanner->currentCommentLiteral()); - FunctionHeaderParserResult header = parseFunctionHeader(false, true); - - if ( - header.isConstructor || - !header.modifiers.empty() || - !header.name->empty() || - m_scanner->currentToken() == Token::Semicolon || - m_scanner->currentToken() == Token::LBrace - ) + Token kind = m_scanner->currentToken(); + ASTPointer name; + if (kind == Token::Function) { - // this has to be a function - ASTPointer block = ASTPointer(); - nodeFactory.markEndPosition(); - if (m_scanner->currentToken() != Token::Semicolon) + m_scanner->next(); + if ( + m_scanner->currentToken() == Token::Constructor || + m_scanner->currentToken() == Token::Fallback || + m_scanner->currentToken() == Token::Receive + ) { - block = parseBlock(); - nodeFactory.setEndPositionFromNode(block); + std::string expected = std::map{ + {Token::Constructor, "constructor"}, + {Token::Fallback, "fallback function"}, + {Token::Receive, "receive function"}, + }.at(m_scanner->currentToken()); + name = make_shared(TokenTraits::toString(m_scanner->currentToken())); + string message{ + "This function is named \"" + *name + "\" but is not the " + expected + " of the contract. " + "If you intend this to be a " + expected + ", use \"" + *name + "(...) { ... }\" without " + "the \"function\" keyword to define it." + }; + if (m_scanner->currentToken() == Token::Constructor) + parserError(message); + else + parserWarning(message); + m_scanner->next(); } else - m_scanner->next(); // just consume the ';' - return nodeFactory.createNode( - header.name, - header.visibility, - header.stateMutability, - header.isConstructor, - docstring, - header.parameters, - header.modifiers, - header.returnParameters, - block - ); + name = expectIdentifierToken(); } else { - // this has to be a state variable - ASTPointer type = nodeFactory.createNode( - header.parameters, - header.returnParameters, - header.visibility, - header.stateMutability - ); - type = parseTypeNameSuffix(type, nodeFactory); - VarDeclParserOptions options; - options.isStateVariable = true; - options.allowInitialValue = true; - auto node = parseVariableDeclaration(options, type); - expectToken(Token::Semicolon); - return node; + solAssert(kind == Token::Constructor || kind == Token::Fallback || kind == Token::Receive, ""); + m_scanner->next(); + name = make_shared(); } + + FunctionHeaderParserResult header = parseFunctionHeader(false); + + ASTPointer block; + nodeFactory.markEndPosition(); + if (m_scanner->currentToken() != Token::Semicolon) + { + block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + } + else + m_scanner->next(); // just consume the ';' + return nodeFactory.createNode( + name, + header.visibility, + header.stateMutability, + kind, + header.isVirtual, + header.overrides, + docstring, + header.parameters, + header.modifiers, + header.returnParameters, + block + ); } ASTPointer Parser::parseStructDefinition() @@ -637,9 +676,18 @@ ASTPointer Parser::parseVariableDeclaration( if (type != nullptr) nodeFactory.setEndPositionFromNode(type); } + + if (dynamic_cast(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace) + fatalParserError( + "Expected a state variable declaration. If you intended this as a fallback function " + "or a function to handle plain ether transactions, use the \"fallback\" keyword " + "or the \"ether\" keyword instead." + ); + bool isIndexed = false; bool isDeclaredConst = false; - Declaration::Visibility visibility(Declaration::Visibility::Default); + ASTPointer overrides = nullptr; + Visibility visibility(Visibility::Default); VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified; ASTPointer identifier; @@ -649,7 +697,7 @@ ASTPointer Parser::parseVariableDeclaration( if (_options.isStateVariable && TokenTraits::isVariableVisibilitySpecifier(token)) { nodeFactory.markEndPosition(); - if (visibility != Declaration::Visibility::Default) + if (visibility != Visibility::Default) { parserError(string( "Visibility already specified as \"" + @@ -661,6 +709,13 @@ ASTPointer Parser::parseVariableDeclaration( else visibility = parseVisibilitySpecifier(); } + else if (_options.isStateVariable && token == Token::Override) + { + if (overrides) + parserError("Override already specified."); + + overrides = parseOverrideSpecifier(); + } else { if (_options.allowIndexed && token == Token::Indexed) @@ -726,6 +781,7 @@ ASTPointer Parser::parseVariableDeclaration( _options.isStateVariable, isIndexed, isDeclaredConst, + overrides, location ); } @@ -753,9 +809,34 @@ ASTPointer Parser::parseModifierDefinition() } else parameters = createEmptyParameterList(); + + ASTPointer overrides; + bool isVirtual = false; + + while (true) + { + if (m_scanner->currentToken() == Token::Override) + { + if (overrides) + parserError("Override already specified."); + overrides = parseOverrideSpecifier(); + } + else if (m_scanner->currentToken() == Token::Virtual) + { + if (isVirtual) + parserError("Virtual already specified."); + + isVirtual = true; + m_scanner->next(); + } + else + break; + } + + ASTPointer block = parseBlock(); nodeFactory.setEndPositionFromNode(block); - return nodeFactory.createNode(name, docstring, parameters, block); + return nodeFactory.createNode(name, docstring, parameters, isVirtual, overrides, block); } ASTPointer Parser::parseEventDefinition() @@ -917,8 +998,8 @@ ASTPointer Parser::parseFunctionType() { RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); - FunctionHeaderParserResult header = parseFunctionHeader(true, false); - solAssert(!header.isConstructor, "Tried to parse type as constructor."); + expectToken(Token::Function); + FunctionHeaderParserResult header = parseFunctionHeader(true); return nodeFactory.createNode( header.parameters, header.returnParameters, @@ -1048,13 +1129,15 @@ ASTPointer Parser::parseStatement() } statement = nodeFactory.createNode(docString, expression); break; - } + } case Token::Throw: - { - statement = ASTNodeFactory(*this).createNode(docString); - m_scanner->next(); - break; - } + { + statement = ASTNodeFactory(*this).createNode(docString); + m_scanner->next(); + break; + } + case Token::Try: + return parseTryStatement(docString); case Token::Assembly: return parseInlineAssembly(docString); case Token::Emit: @@ -1097,7 +1180,7 @@ ASTPointer Parser::parseInlineAssembly(ASTPointer con SourceLocation location{position(), -1, source()}; expectToken(Token::Assembly); - yul::Dialect const& dialect = yul::EVMDialect::looseAssemblyForEVM(m_evmVersion); + yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion); if (m_scanner->currentToken() == Token::StringLiteral) { if (m_scanner->currentLiteral() != "evmasm") @@ -1136,6 +1219,62 @@ ASTPointer Parser::parseIfStatement(ASTPointer const& _d return nodeFactory.createNode(_docString, condition, trueBody, falseBody); } +ASTPointer Parser::parseTryStatement(ASTPointer const& _docString) +{ + RecursionGuard recursionGuard(*this); + ASTNodeFactory nodeFactory(*this); + expectToken(Token::Try); + ASTPointer externalCall = parseExpression(); + vector> clauses; + + ASTNodeFactory successClauseFactory(*this); + ASTPointer returnsParameters; + if (m_scanner->currentToken() == Token::Returns) + { + m_scanner->next(); + VarDeclParserOptions options; + options.allowEmptyName = true; + options.allowLocationSpecifier = true; + returnsParameters = parseParameterList(options, false); + } + ASTPointer successBlock = parseBlock(); + successClauseFactory.setEndPositionFromNode(successBlock); + clauses.emplace_back(successClauseFactory.createNode( + make_shared(), returnsParameters, successBlock + )); + + do + { + clauses.emplace_back(parseCatchClause()); + } + while (m_scanner->currentToken() == Token::Catch); + nodeFactory.setEndPositionFromNode(clauses.back()); + return nodeFactory.createNode( + _docString, externalCall, clauses + ); +} + +ASTPointer Parser::parseCatchClause() +{ + RecursionGuard recursionGuard(*this); + ASTNodeFactory nodeFactory(*this); + expectToken(Token::Catch); + ASTPointer errorName = make_shared(); + ASTPointer errorParameters; + if (m_scanner->currentToken() != Token::LBrace) + { + if (m_scanner->currentToken() == Token::Identifier) + errorName = expectIdentifierToken(); + VarDeclParserOptions options; + options.allowEmptyName = true; + options.allowLocationSpecifier = true; + errorParameters = parseParameterList(options, !errorName->empty()); + } + ASTPointer block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + return nodeFactory.createNode(errorName, errorParameters, block); +} + ASTPointer Parser::parseWhileStatement(ASTPointer const& _docString) { RecursionGuard recursionGuard(*this); @@ -1395,7 +1534,7 @@ ASTPointer Parser::parseVariableDeclarationStateme ASTPointer(), name, ASTPointer(), - VariableDeclaration::Visibility::Default + Visibility::Default ); } variables.push_back(var); @@ -1534,6 +1673,17 @@ ASTPointer Parser::parseLeftHandSideExpression( nodeFactory.markEndPosition(); expression = nodeFactory.createNode(typeName); } + else if (m_scanner->currentToken() == Token::Payable) + { + expectToken(Token::Payable); + nodeFactory.markEndPosition(); + auto expressionType = nodeFactory.createNode( + ElementaryTypeNameToken(Token::Address, 0, 0), + std::make_optional(StateMutability::Payable) + ); + expression = nodeFactory.createNode(expressionType); + expectToken(Token::LParen, false); + } else expression = parsePrimaryExpression(); @@ -1545,18 +1695,37 @@ ASTPointer Parser::parseLeftHandSideExpression( { m_scanner->next(); ASTPointer index; - if (m_scanner->currentToken() != Token::RBrack) + ASTPointer endIndex; + if (m_scanner->currentToken() != Token::RBrack && m_scanner->currentToken() != Token::Colon) index = parseExpression(); - nodeFactory.markEndPosition(); - expectToken(Token::RBrack); - expression = nodeFactory.createNode(expression, index); + if (m_scanner->currentToken() == Token::Colon) + { + expectToken(Token::Colon); + if (m_scanner->currentToken() != Token::RBrack) + endIndex = parseExpression(); + nodeFactory.markEndPosition(); + expectToken(Token::RBrack); + expression = nodeFactory.createNode(expression, index, endIndex); + } + else + { + nodeFactory.markEndPosition(); + expectToken(Token::RBrack); + expression = nodeFactory.createNode(expression, index); + } break; } case Token::Period: { m_scanner->next(); nodeFactory.markEndPosition(); - expression = nodeFactory.createNode(expression, expectIdentifierToken()); + if (m_scanner->currentToken() == Token::Address) + { + expression = nodeFactory.createNode(expression, make_shared("address")); + m_scanner->next(); + } + else + expression = nodeFactory.createNode(expression, expectIdentifierToken()); break; } case Token::LParen: @@ -1681,8 +1850,10 @@ ASTPointer Parser::parsePrimaryExpression() unsigned firstSize; unsigned secondSize; tie(firstSize, secondSize) = m_scanner->currentTokenInfo(); - ElementaryTypeNameToken elementaryExpression(m_scanner->currentToken(), firstSize, secondSize); - expression = nodeFactory.createNode(elementaryExpression); + auto expressionType = nodeFactory.createNode( + ElementaryTypeNameToken(m_scanner->currentToken(), firstSize, secondSize) + ); + expression = nodeFactory.createNode(expressionType); m_scanner->next(); } else @@ -1794,20 +1965,35 @@ Parser::IndexAccessedPath Parser::parseIndexAccessedPath() unsigned firstNum; unsigned secondNum; tie(firstNum, secondNum) = m_scanner->currentTokenInfo(); - ElementaryTypeNameToken elemToken(m_scanner->currentToken(), firstNum, secondNum); - iap.path.push_back(ASTNodeFactory(*this).createNode(elemToken)); + auto expressionType = ASTNodeFactory(*this).createNode( + ElementaryTypeNameToken(m_scanner->currentToken(), firstNum, secondNum) + ); + iap.path.push_back(ASTNodeFactory(*this).createNode(expressionType)); m_scanner->next(); } while (m_scanner->currentToken() == Token::LBrack) { expectToken(Token::LBrack); ASTPointer index; - if (m_scanner->currentToken() != Token::RBrack) + if (m_scanner->currentToken() != Token::RBrack && m_scanner->currentToken() != Token::Colon) index = parseExpression(); SourceLocation indexLocation = iap.path.front()->location(); - indexLocation.end = endPosition(); - iap.indices.emplace_back(index, indexLocation); - expectToken(Token::RBrack); + if (m_scanner->currentToken() == Token::Colon) + { + expectToken(Token::Colon); + ASTPointer endIndex; + if (m_scanner->currentToken() != Token::RBrack) + endIndex = parseExpression(); + indexLocation.end = endPosition(); + iap.indices.emplace_back(IndexAccessedPath::Index{index, {endIndex}, indexLocation}); + expectToken(Token::RBrack); + } + else + { + indexLocation.end = endPosition(); + iap.indices.emplace_back(IndexAccessedPath::Index{index, {}, indexLocation}); + expectToken(Token::RBrack); + } } return iap; @@ -1828,7 +2014,7 @@ ASTPointer Parser::typeNameFromIndexAccessStructure(Parser::IndexAcces if (auto typeName = dynamic_cast(_iap.path.front().get())) { solAssert(_iap.path.size() == 1, ""); - type = nodeFactory.createNode(typeName->typeName()); + type = nodeFactory.createNode(typeName->type().typeName()); } else { @@ -1839,8 +2025,10 @@ ASTPointer Parser::typeNameFromIndexAccessStructure(Parser::IndexAcces } for (auto const& lengthExpression: _iap.indices) { - nodeFactory.setLocation(lengthExpression.second); - type = nodeFactory.createNode(type, lengthExpression.first); + if (lengthExpression.end) + parserError(lengthExpression.location, "Expected array length expression."); + nodeFactory.setLocation(lengthExpression.location); + type = nodeFactory.createNode(type, lengthExpression.start); } return type; } @@ -1868,8 +2056,11 @@ ASTPointer Parser::expressionFromIndexAccessStructure( } for (auto const& index: _iap.indices) { - nodeFactory.setLocation(index.second); - expression = nodeFactory.createNode(expression, index.first); + nodeFactory.setLocation(index.location); + if (index.end) + expression = nodeFactory.createNode(expression, index.start, *index.end); + else + expression = nodeFactory.createNode(expression, index.start); } return expression; } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index cb3d75f51db8..f9b6197b4406 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -70,11 +70,11 @@ class Parser: public langutil::ParserBase /// This struct is shared for parsing a function header and a function type. struct FunctionHeaderParserResult { - bool isConstructor; - ASTPointer name; + bool isVirtual = false; + ASTPointer overrides; ASTPointer parameters; ASTPointer returnParameters; - Declaration::Visibility visibility = Declaration::Visibility::Default; + Visibility visibility = Visibility::Default; StateMutability stateMutability = StateMutability::NonPayable; std::vector> modifiers; }; @@ -84,14 +84,16 @@ class Parser: public langutil::ParserBase void parsePragmaVersion(langutil::SourceLocation const& _location, std::vector const& _tokens, std::vector const& _literals); ASTPointer parsePragmaDirective(); ASTPointer parseImportDirective(); - ContractDefinition::ContractKind parseContractKind(); + /// @returns an std::pair, where + /// result.second is set to true, if an abstract contract was parsed, false otherwise. + std::pair parseContractKind(); ASTPointer parseContractDefinition(); ASTPointer parseInheritanceSpecifier(); - Declaration::Visibility parseVisibilitySpecifier(); + Visibility parseVisibilitySpecifier(); + ASTPointer parseOverrideSpecifier(); StateMutability parseStateMutability(); - FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); - ASTPointer parseFunctionDefinitionOrFunctionTypeStateVariable(); - ASTPointer parseFunctionDefinition(ASTString const* _contractName); + FunctionHeaderParserResult parseFunctionHeader(bool _isStateVariable); + ASTPointer parseFunctionDefinition(); ASTPointer parseStructDefinition(); ASTPointer parseEnumDefinition(); ASTPointer parseEnumValue(); @@ -117,6 +119,8 @@ class Parser: public langutil::ParserBase ASTPointer parseStatement(); ASTPointer parseInlineAssembly(ASTPointer const& _docString = {}); ASTPointer parseIfStatement(ASTPointer const& _docString); + ASTPointer parseTryStatement(ASTPointer const& _docString); + ASTPointer parseCatchClause(); ASTPointer parseWhileStatement(ASTPointer const& _docString); ASTPointer parseDoWhileStatement(ASTPointer const& _docString); ASTPointer parseForStatement(ASTPointer const& _docString); @@ -160,8 +164,14 @@ class Parser: public langutil::ParserBase /// or to a type name. For this to be valid, path cannot be empty, but indices can be empty. struct IndexAccessedPath { + struct Index + { + ASTPointer start; + std::optional> end; + langutil::SourceLocation location; + }; std::vector> path; - std::vector, langutil::SourceLocation>> indices; + std::vector indices; bool empty() const; }; diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 9ea6cf2bcfcf..64b1ad4c5e70 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -78,7 +78,6 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, bool success = yul::AsmAnalyzer( analysisInfo, errors, - Error::Type::SyntaxError, _dialect, {}, _object.dataNames() @@ -87,24 +86,9 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, return analysisInfo; } -bool AsmAnalyzer::operator()(Label const& _label) -{ - yulAssert(!_label.name.empty(), ""); - checkLooseFeature( - _label.location, - "The use of labels is disallowed. Please use \"if\", \"switch\", \"for\" or function calls instead." - ); - m_info.stackHeightInfo[&_label] = m_stackHeight; - warnOnInstructions(dev::eth::Instruction::JUMPDEST, _label.location); - return true; -} - bool AsmAnalyzer::operator()(yul::Instruction const& _instruction) { - checkLooseFeature( - _instruction.location, - "The use of non-functional instructions is disallowed. Please use functional notation instead." - ); + yulAssert(false, "The use of non-functional instructions is disallowed. Please use functional notation instead."); auto const& info = instructionInfo(_instruction.instruction); m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instruction] = m_stackHeight; @@ -195,54 +179,25 @@ bool AsmAnalyzer::operator()(Identifier const& _identifier) return success; } -bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) -{ - yulAssert(m_dialect.flavour != AsmFlavour::Yul, ""); - bool success = true; - for (auto const& arg: _instr.arguments | boost::adaptors::reversed) - if (!expectExpression(arg)) - success = false; - // Parser already checks that the number of arguments is correct. - auto const& info = instructionInfo(_instr.instruction); - yulAssert(info.args == int(_instr.arguments.size()), ""); - m_stackHeight += info.ret - info.args; - m_info.stackHeightInfo[&_instr] = m_stackHeight; - warnOnInstructions(_instr.instruction, _instr.location); - return success; -} - bool AsmAnalyzer::operator()(ExpressionStatement const& _statement) { int initialStackHeight = m_stackHeight; bool success = std::visit(*this, _statement.expression); - if (m_stackHeight != initialStackHeight && (m_dialect.flavour != AsmFlavour::Loose || m_errorTypeForLoose)) + if (success && m_stackHeight != initialStackHeight) { - Error::Type errorType = m_dialect.flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError; string msg = "Top-level expressions are not supposed to return values (this expression returns " + to_string(m_stackHeight - initialStackHeight) + " value" + (m_stackHeight - initialStackHeight == 1 ? "" : "s") + "). Use ``pop()`` or assign them."; - m_errorReporter.error(errorType, _statement.location, msg); - if (errorType != Error::Type::Warning) - success = false; + m_errorReporter.error(Error::Type::TypeError, _statement.location, msg); + success = false; } m_info.stackHeightInfo[&_statement] = m_stackHeight; return success; } -bool AsmAnalyzer::operator()(StackAssignment const& _assignment) -{ - checkLooseFeature( - _assignment.location, - "The use of stack assignment is disallowed. Please use assignment in functional notation instead." - ); - bool success = checkAssignment(_assignment.variableName, size_t(-1)); - m_info.stackHeightInfo[&_assignment] = m_stackHeight; - return success; -} - bool AsmAnalyzer::operator()(Assignment const& _assignment) { yulAssert(_assignment.value, ""); @@ -273,6 +228,14 @@ bool AsmAnalyzer::operator()(VariableDeclaration const& _varDecl) { bool success = true; int const numVariables = _varDecl.variables.size(); + if (m_resolver) + for (auto const& variable: _varDecl.variables) + // Call the resolver for variable declarations to allow it to raise errors on shadowing. + m_resolver( + yul::Identifier{variable.location, variable.name}, + yul::IdentifierContext::VariableDeclaration, + m_currentScope->insideFunction() + ); if (_varDecl.value) { int const stackHeight = m_stackHeight; @@ -366,7 +329,8 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) } })) { - m_errorReporter.declarationError(_funCall.functionName.location, "Function not found."); + if (!warnOnInstructions(_funCall.functionName.name.str(), _funCall.functionName.location)) + m_errorReporter.declarationError(_funCall.functionName.location, "Function not found."); success = false; } if (success) @@ -547,6 +511,12 @@ bool AsmAnalyzer::operator()(Continue const& _continue) return true; } +bool AsmAnalyzer::operator()(Leave const& _leaveStatement) +{ + m_info.stackHeightInfo[&_leaveStatement] = m_stackHeight; + return true; +} + bool AsmAnalyzer::operator()(Block const& _block) { bool success = true; @@ -562,7 +532,7 @@ bool AsmAnalyzer::operator()(Block const& _block) m_stackHeight -= scope(&_block).numberOfVariables(); int const stackDiff = m_stackHeight - initialStackHeight; - if (stackDiff != 0) + if (success && stackDiff != 0) { m_errorReporter.declarationError( _block.location, @@ -587,7 +557,7 @@ bool AsmAnalyzer::expectExpression(Expression const& _expr) int const initialHeight = m_stackHeight; if (!std::visit(*this, _expr)) success = false; - if (!expectDeposit(1, initialHeight, locationOf(_expr))) + if (success && !expectDeposit(1, initialHeight, locationOf(_expr))) success = false; return success; } @@ -682,7 +652,16 @@ void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _loc ); } -void AsmAnalyzer::warnOnInstructions(dev::eth::Instruction _instr, SourceLocation const& _location) +bool AsmAnalyzer::warnOnInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location) +{ + auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier)); + if (builtin) + return warnOnInstructions(builtin->instruction.value(), _location); + else + return false; +} + +bool AsmAnalyzer::warnOnInstructions(dev::eth::Instruction _instr, SourceLocation const& _location) { // We assume that returndatacopy, returndatasize and staticcall are either all available // or all not available. @@ -746,29 +725,16 @@ void AsmAnalyzer::warnOnInstructions(dev::eth::Instruction _instr, SourceLocatio _instr == dev::eth::Instruction::JUMPDEST ) { - if (m_dialect.flavour == AsmFlavour::Loose) - m_errorReporter.error( - m_errorTypeForLoose ? *m_errorTypeForLoose : Error::Type::Warning, - _location, - "Jump instructions and labels are low-level EVM features that can lead to " - "incorrect stack access. Because of that they are discouraged. " - "Please consider using \"switch\", \"if\" or \"for\" statements instead." - ); - else - m_errorReporter.error( - Error::Type::SyntaxError, - _location, - "Jump instructions and labels are low-level EVM features that can lead to " - "incorrect stack access. Because of that they are disallowed in strict assembly. " - "Use functions, \"switch\", \"if\" or \"for\" statements instead." - ); + m_errorReporter.error( + Error::Type::SyntaxError, + _location, + "Jump instructions and labels are low-level EVM features that can lead to " + "incorrect stack access. Because of that they are disallowed in strict assembly. " + "Use functions, \"switch\", \"if\" or \"for\" statements instead." + ); } -} + else + return false; -void AsmAnalyzer::checkLooseFeature(SourceLocation const& _location, string const& _description) -{ - if (m_dialect.flavour != AsmFlavour::Loose) - yulAssert(false, _description); - else if (m_errorTypeForLoose) - m_errorReporter.error(*m_errorTypeForLoose, _location, _description); + return true; } diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 40d9a1d51171..2ee77f71b4b6 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -57,7 +57,6 @@ class AsmAnalyzer explicit AsmAnalyzer( AsmAnalysisInfo& _analysisInfo, langutil::ErrorReporter& _errorReporter, - std::optional _errorTypeForLoose, Dialect const& _dialect, ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver(), std::set const& _dataNames = {} @@ -66,7 +65,6 @@ class AsmAnalyzer m_info(_analysisInfo), m_errorReporter(_errorReporter), m_dialect(_dialect), - m_errorTypeForLoose(_errorTypeForLoose), m_dataNames(_dataNames) { if (EVMDialect const* evmDialect = dynamic_cast(&m_dialect)) @@ -82,10 +80,7 @@ class AsmAnalyzer bool operator()(Instruction const&); bool operator()(Literal const& _literal); bool operator()(Identifier const&); - bool operator()(FunctionalInstruction const& _functionalInstruction); - bool operator()(Label const& _label); bool operator()(ExpressionStatement const&); - bool operator()(StackAssignment const&); bool operator()(Assignment const& _assignment); bool operator()(VariableDeclaration const& _variableDeclaration); bool operator()(FunctionDefinition const& _functionDefinition); @@ -95,6 +90,7 @@ class AsmAnalyzer bool operator()(ForLoop const& _forLoop); bool operator()(Break const&); bool operator()(Continue const&); + bool operator()(Leave const&); bool operator()(Block const& _block); private: @@ -108,12 +104,8 @@ class AsmAnalyzer Scope& scope(Block const* _block); void expectValidType(std::string const& type, langutil::SourceLocation const& _location); - void warnOnInstructions(dev::eth::Instruction _instr, langutil::SourceLocation const& _location); - - /// Depending on @a m_flavour and @a m_errorTypeForLoose, throws an internal compiler - /// exception (if the flavour is not Loose), reports an error/warning - /// (if m_errorTypeForLoose is set) or does nothing. - void checkLooseFeature(langutil::SourceLocation const& _location, std::string const& _description); + bool warnOnInstructions(dev::eth::Instruction _instr, langutil::SourceLocation const& _location); + bool warnOnInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location); int m_stackHeight = 0; yul::ExternalIdentifierAccess::Resolver m_resolver; @@ -125,7 +117,6 @@ class AsmAnalyzer langutil::ErrorReporter& m_errorReporter; langutil::EVMVersion m_evmVersion; Dialect const& m_dialect; - std::optional m_errorTypeForLoose; /// Names of data objects to be referenced by builtin functions with literal arguments. std::set m_dataNames; ForLoop const* m_currentForLoop = nullptr; diff --git a/libyul/AsmData.h b/libyul/AsmData.h index 33bf48090ca1..c80fa7cfa360 100644 --- a/libyul/AsmData.h +++ b/libyul/AsmData.h @@ -48,10 +48,6 @@ enum class LiteralKind { Number, Boolean, String }; struct Literal { langutil::SourceLocation location; LiteralKind kind; YulString value; Type type; }; /// External / internal identifier or label reference struct Identifier { langutil::SourceLocation location; YulString name; }; -/// Jump label ("name:") -struct Label { langutil::SourceLocation location; YulString name; }; -/// Assignment from stack (":= x", moves stack top into x, potentially multiple slots) -struct StackAssignment { langutil::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. /// @@ -59,8 +55,6 @@ struct StackAssignment { langutil::SourceLocation location; Identifier variableN /// 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 { langutil::SourceLocation location; std::vector variableNames; std::unique_ptr value; }; -/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" -struct FunctionalInstruction { langutil::SourceLocation location; dev::eth::Instruction instruction; std::vector arguments; }; struct FunctionCall { langutil::SourceLocation location; Identifier functionName; std::vector arguments; }; /// Statement that contains only a single expression struct ExpressionStatement { langutil::SourceLocation location; Expression expression; }; @@ -81,6 +75,8 @@ struct ForLoop { langutil::SourceLocation location; Block pre; std::unique_ptr; -using Statement = std::variant; +using Expression = std::variant; +using Statement = std::variant; } diff --git a/libyul/AsmJsonConverter.cpp b/libyul/AsmJsonConverter.cpp new file mode 100644 index 000000000000..429da10fda8d --- /dev/null +++ b/libyul/AsmJsonConverter.cpp @@ -0,0 +1,203 @@ +/* + 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 . +*/ +/** + * @date 2019 + * Converts inline assembly AST to JSON format + */ + +#include +#include +#include +#include + +using namespace std; + +namespace yul +{ + +Json::Value AsmJsonConverter::operator()(Block const& _node) const +{ + Json::Value ret = createAstNode(_node.location, "YulBlock"); + ret["statements"] = vectorOfVariantsToJson(_node.statements); + return ret; +} + +Json::Value AsmJsonConverter::operator()(TypedName const& _node) const +{ + solAssert(!_node.name.empty(), "Invalid variable name."); + Json::Value ret = createAstNode(_node.location, "YulTypedName"); + ret["name"] = _node.name.str(); + ret["type"] = _node.type.str(); + return ret; +} + +Json::Value AsmJsonConverter::operator()(Literal const& _node) const +{ + Json::Value ret = createAstNode(_node.location, "YulLiteral"); + switch (_node.kind) + { + case LiteralKind::Number: + solAssert( + dev::isValidDecimal(_node.value.str()) || dev::isValidHex(_node.value.str()), + "Invalid number literal" + ); + ret["kind"] = "number"; + break; + case LiteralKind::Boolean: + ret["kind"] = "bool"; + break; + case LiteralKind::String: + ret["kind"] = "string"; + break; + } + ret["type"] = _node.type.str(); + ret["value"] = _node.value.str(); + return ret; +} + +Json::Value AsmJsonConverter::operator()(Identifier const& _node) const +{ + solAssert(!_node.name.empty(), "Invalid identifier"); + Json::Value ret = createAstNode(_node.location, "YulIdentifier"); + ret["name"] = _node.name.str(); + return ret; +} + +Json::Value AsmJsonConverter::operator()(Assignment const& _node) const +{ + solAssert(_node.variableNames.size() >= 1, "Invalid assignment syntax"); + Json::Value ret = createAstNode(_node.location, "YulAssignment"); + for (auto const& var: _node.variableNames) + ret["variableNames"].append((*this)(var)); + ret["value"] = _node.value ? std::visit(*this, *_node.value) : Json::nullValue; + return ret; +} + +Json::Value AsmJsonConverter::operator()(FunctionCall const& _node) const +{ + Json::Value ret = createAstNode(_node.location, "YulFunctionCall"); + ret["functionName"] = (*this)(_node.functionName); + ret["arguments"] = vectorOfVariantsToJson(_node.arguments); + return ret; +} + +Json::Value AsmJsonConverter::operator()(ExpressionStatement const& _node) const +{ + Json::Value ret = createAstNode(_node.location, "YulExpressionStatement"); + ret["expression"] = std::visit(*this, _node.expression); + return ret; +} + +Json::Value AsmJsonConverter::operator()(VariableDeclaration const& _node) const +{ + Json::Value ret = createAstNode(_node.location, "YulVariableDeclaration"); + for (auto const& var: _node.variables) + ret["variables"].append((*this)(var)); + + ret["value"] = _node.value ? std::visit(*this, *_node.value) : Json::nullValue; + + return ret; +} + +Json::Value AsmJsonConverter::operator()(FunctionDefinition const& _node) const +{ + solAssert(!_node.name.empty(), "Invalid function name."); + Json::Value ret = createAstNode(_node.location, "YulFunctionDefinition"); + ret["name"] = _node.name.str(); + for (auto const& var: _node.parameters) + ret["parameters"].append((*this)(var)); + for (auto const& var: _node.returnVariables) + ret["returnVariables"].append((*this)(var)); + ret["body"] = (*this)(_node.body); + return ret; +} + +Json::Value AsmJsonConverter::operator()(If const& _node) const +{ + solAssert(_node.condition, "Invalid if condition."); + Json::Value ret = createAstNode(_node.location, "YulIf"); + ret["condition"] = std::visit(*this, *_node.condition); + ret["body"] = (*this)(_node.body); + return ret; +} + +Json::Value AsmJsonConverter::operator()(Switch const& _node) const +{ + solAssert(_node.expression, "Invalid expression pointer."); + Json::Value ret = createAstNode(_node.location, "YulSwitch"); + ret["expression"] = std::visit(*this, *_node.expression); + for (auto const& var: _node.cases) + ret["cases"].append((*this)(var)); + return ret; +} + +Json::Value AsmJsonConverter::operator()(Case const& _node) const +{ + Json::Value ret = createAstNode(_node.location, "YulCase"); + ret["value"] = _node.value ? (*this)(*_node.value) : "default"; + ret["body"] = (*this)(_node.body); + return ret; +} + +Json::Value AsmJsonConverter::operator()(ForLoop const& _node) const +{ + solAssert(_node.condition, "Invalid for loop condition."); + Json::Value ret = createAstNode(_node.location, "YulForLoop"); + ret["pre"] = (*this)(_node.pre); + ret["condition"] = std::visit(*this, *_node.condition); + ret["post"] = (*this)(_node.post); + ret["body"] = (*this)(_node.body); + return ret; + } + +Json::Value AsmJsonConverter::operator()(Break const& _node) const +{ + return createAstNode(_node.location, "YulBreak"); +} + +Json::Value AsmJsonConverter::operator()(Continue const& _node) const +{ + return createAstNode(_node.location, "YulContinue"); +} + +Json::Value AsmJsonConverter::operator()(Leave const& _node) const +{ + return createAstNode(_node.location, "YulLeave"); +} + +Json::Value AsmJsonConverter::createAstNode(langutil::SourceLocation const& _location, string _nodeType) const +{ + Json::Value ret{Json::objectValue}; + ret["nodeType"] = std::move(_nodeType); + int length = -1; + if (_location.start >= 0 && _location.end >= 0) + length = _location.end - _location.start; + ret["src"] = to_string(_location.start) + ":" + to_string(length) + ":" + m_sourceIndex; + return ret; +} + +template +Json::Value AsmJsonConverter::vectorOfVariantsToJson(vector const& _vec) const +{ + Json::Value ret{Json::arrayValue}; + for (auto const& var: _vec) + ret.append(std::visit(*this, var)); + + return ret; +} + +} diff --git a/libyul/AsmJsonConverter.h b/libyul/AsmJsonConverter.h new file mode 100644 index 000000000000..85d17c78d20f --- /dev/null +++ b/libyul/AsmJsonConverter.h @@ -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 . +*/ +/** + * @date 2019 + * @author julius + * Converts inline assembly AST to JSON format + */ + +#include +#include +#include +#include +#include + +namespace yul +{ + +/** + * Converter of the yul AST into JSON format + */ +class AsmJsonConverter: public boost::static_visitor +{ +public: + /// Create a converter to JSON for any block of inline assembly + /// @a _sourceIndex to be used to abbreviate source name in the source locations + explicit AsmJsonConverter(size_t _sourceIndex): m_sourceIndex(std::to_string(_sourceIndex)) {} + + Json::Value operator()(Block const& _node) const; + Json::Value operator()(TypedName const& _node) const; + Json::Value operator()(Literal const& _node) const; + Json::Value operator()(Identifier const& _node) const; + Json::Value operator()(Assignment const& _node) const; + Json::Value operator()(VariableDeclaration const& _node) const; + Json::Value operator()(FunctionDefinition const& _node) const; + Json::Value operator()(FunctionCall const& _node) const; + Json::Value operator()(If const& _node) const; + Json::Value operator()(Switch const& _node) const; + Json::Value operator()(Case const& _node) const; + Json::Value operator()(ForLoop const& _node) const; + Json::Value operator()(Break const& _node) const; + Json::Value operator()(Continue const& _node) const; + Json::Value operator()(Leave const& _node) const; + Json::Value operator()(ExpressionStatement const& _node) const; + Json::Value operator()(Label const& _node) const; + +private: + Json::Value createAstNode(langutil::SourceLocation const& _location, std::string _nodeType) const; + template + Json::Value vectorOfVariantsToJson(std::vector const& vec) const; + + std::string const m_sourceIndex; +}; + +} diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 5de7417d288c..1123487e55f5 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -145,23 +145,16 @@ Statement Parser::parseStatement() m_scanner->next(); return stmt; } - case Token::Assign: - { - if (m_dialect.flavour != AsmFlavour::Loose) - break; - StackAssignment assignment = createWithLocation(); - advance(); - expectToken(Token::Colon); - assignment.variableName.location = location(); - assignment.variableName.name = YulString(currentLiteral()); - if (m_dialect.builtin(assignment.variableName.name)) - fatalParserError("Identifier expected, got builtin symbol."); - else if (instructions().count(assignment.variableName.name.str())) - fatalParserError("Identifier expected, got instruction name."); - assignment.location.end = endPosition(); - expectToken(Token::Identifier); - return Statement{move(assignment)}; - } + case Token::Identifier: + if (currentLiteral() == "leave") + { + Statement stmt{createWithLocation()}; + if (!m_insideFunction) + m_errorReporter.syntaxError(location(), "Keyword \"leave\" can only be used inside a function."); + m_scanner->next(); + return stmt; + } + break; default: break; } @@ -223,26 +216,8 @@ Statement Parser::parseStatement() return Statement{std::move(assignment)}; } - case Token::Colon: - { - if (!holds_alternative(elementary)) - fatalParserError("Label name must precede \":\"."); - - Identifier const& identifier = std::get(elementary); - - advance(); - - // label - if (m_dialect.flavour != AsmFlavour::Loose) - fatalParserError("Labels are not supported."); - - Label label = createWithLocation