diff options
142 files changed, 3889 insertions, 659 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d4ff161..208e405d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.4.21") +set(PROJECT_VERSION "0.4.22") project(solidity VERSION ${PROJECT_VERSION}) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) diff --git a/Changelog.md b/Changelog.md index acd3aa54..e9a9548d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,20 +1,46 @@ -### 0.4.21 (unreleased) +### 0.4.22 (unreleased) Features: - * Code Generator: Assert that ``k != 0`` for ``molmod(a, b, k)`` and ``addmod(a, b, k)`` as experimental 0.5.0 feature. + +Bugfixes: + * Code Generator: Allow ``block.blockhash`` without being called. + * Code Generator: Properly skip unneeded storgae array cleanup when not reducing length. + * Code Generator: Bugfix in modifier lookup in libraries. + * Commandline interface: Support ``--evm-version constantinople`` properly. + * DocString Parser: Fix error message for empty descriptions. + * Standard JSON: Support ``constantinople`` as ``evmVersion`` properly. + * Type System: Make external library functions accessible. + +### 0.4.21 (2018-03-07) + +Features: + * Code Generator: Assert that ``k != 0`` for ``mulmod(a, b, k)`` and ``addmod(a, b, k)`` as experimental 0.5.0 feature. + * Code Generator: Do not retain any gas in calls (except if EVM version is set to homestead). + * Code Generator: Use ``STATICCALL`` opcode for calling ``view`` and ``pure`` functions as experimenal 0.5.0 feature. + * General: C99/C++-style scoping rules (instead of JavaScript function scoping) take effect as experimental v0.5.0 feature. + * General: Improved messaging when error spans multiple lines of a sourcefile + * General: Support and recommend using ``emit EventName();`` to call events explicitly. + * Inline Assembly: Enforce strict mode as experimental 0.5.0 feature. + * Interface: Provide ability to select target EVM version (homestead or byzantium, with byzantium being the default). * Standard JSON: Reject badly formatted invalid JSON inputs. * Type Checker: Disallow uninitialized storage pointers as experimental 0.5.0 feature. - * Support and recommend using ``emit EventName();`` to call events explicitly. * Syntax Analyser: Do not warn about experimental features if they do not concern code generation. + * Syntax Analyser: Do not warn about ``pragma experimental "v0.5.0"`` and do not set the experimental flag in the bytecode for this. * Syntax Checker: Mark ``throw`` as an error as experimental 0.5.0 feature. + * Syntax Checker: Issue error if no visibility is specified on contract functions as experimental 0.5.0 feature. + * Syntax Checker: Issue warning when using overloads of ``address`` on contract instances. + * Type Checker: disallow combining hex numbers and unit denominations as experimental 0.5.0 feature. Bugfixes: * Assembly: Raise error on oversized number literals in assembly. * JSON-AST: Add "documentation" property to function, event and modifier definition. * Resolver: Properly determine shadowing for imports with aliases. * Standalone Assembly: Do not ignore input after closing brace of top level block. - * Standard JSON: catch errors properly when invalid "sources" are passed + * Standard JSON: Catch errors properly when invalid "sources" are passed. + * Standard JSON: Ensure that library addresses supplied are of correct length and hex prefixed. + * Type Checker: Properly detect which array and struct types are unsupported by the old ABI encoder. * Type Checker: Properly warn when using ``_offset`` and ``_slot`` for constants in inline assembly. + * Commandline interface: throw error if option is unknown ### 0.4.20 (2018-02-14) @@ -4,7 +4,7 @@ ## Useful links To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](https://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](https://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts. -You can start using [Solidity in your browser](https://ethereum.github.io/browser-solidity/) with no need to download or compile anything. +You can start using [Solidity in your browser](http://remix.ethereum.org) with no need to download or compile anything. The changelog for this project can be found [here](https://github.com/ethereum/solidity/blob/develop/Changelog.md). diff --git a/ReleaseChecklist.md b/ReleaseChecklist.md index b5df9fda..82b1308c 100644 --- a/ReleaseChecklist.md +++ b/ReleaseChecklist.md @@ -11,6 +11,7 @@ Checklist for making a release: - [ ] Run ``scripts/release_ppa.sh release`` to create the PPA release (you need the relevant openssl key). - [ ] Check that the Docker release was pushed to Docker Hub (this still seems to have problems). - [ ] Update the homebrew realease in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb (version and hash) + - [ ] Update the default version on readthedocs. - [ ] Make a release of ``solc-js``: Increment the version number, create a pull request for that, merge it after tests succeeded. - [ ] Run ``npm publish`` in the updated ``solc-js`` repository. - [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry. diff --git a/appveyor.yml b/appveyor.yml index ef5f6907..5fd85482 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -71,7 +71,7 @@ build_script: test_script: - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - - soltest.exe --show-progress -- --no-ipc --no-smt + - soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-ipc --no-smt # Skip bytecode compare if private key is not available - cd %APPVEYOR_BUILD_FOLDER% - ps: if ($env:priv_key) { @@ -5,10 +5,6 @@ jobs: - image: trzeci/emscripten:sdk-tag-1.37.21-64bit steps: - checkout - - run: - name: Init submodules - command: | - git submodule update --init - restore_cache: name: Restore Boost build key: &boost-cache-key emscripten-boost-{{ checksum "scripts/travis-emscripten/install_deps.sh" }}{{ checksum "scripts/travis-emscripten/build_emscripten.sh" }} @@ -95,23 +91,31 @@ jobs: apt-get -qq update apt-get -qy install ccache cmake libboost-all-dev libz3-dev - run: - name: Init submodules - command: | - git submodule update --init - - run: name: Store commit hash and prerelease command: | if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi echo -n "$CIRCLE_SHA1" > commit_hash.txt - restore_cache: - key: ccache-{{ arch }}-{{ .Branch }} - key: ccache-{{ arch }} - key: ccache + key: ccache-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} + key: ccache-{{ arch }}-{{ .Branch }}- + key: ccache-{{ arch }}-develop- + key: ccache-{{ arch }}- + - run: + name: Configure ccache + command: ccache -M 200M && ccache -c && ccache -s && ccache -z - run: name: Build - command: ./scripts/build.sh RelWithDebInfo + command: | + mkdir -p build + cd build + cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo + make -j4 + - run: + name: CCache statistics + command: ccache -s - save_cache: - key: ccache-{{ arch }}-{{ .Branch }} + key: ccache-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} + when: always paths: - ~/.ccache - store_artifacts: @@ -122,7 +126,7 @@ jobs: paths: - solc/solc - test/soltest - - test/solfuzzer + - test/tools/solfuzzer test_x86: docker: diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 07c8e0ce..4d84a7da 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -26,6 +26,10 @@ The first four bytes of the call data for a function call specifies the function first (left, high-order in big-endian) four bytes of the Keccak (SHA-3) hash of the signature of the function. The signature is defined as the canonical expression of the basic prototype, 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 <overload-function>` return types are not considered. The reason is to keep function call resolution context-independent. + The JSON description of the ABI however contains both inputs and outputs. See (the :ref:`JSON ABI <abi_json>`) + Argument Encoding ================= @@ -54,7 +58,7 @@ The following elementary types exist: - ``bytes<M>``: binary type of ``M`` bytes, ``0 < M <= 32``. -- ``function``: an address (20 bytes) folled by a function selector (4 bytes). Encoded identical to ``bytes24``. +- ``function``: an address (20 bytes) followed by a function selector (4 bytes). Encoded identical to ``bytes24``. The following (fixed-size) array type exists: @@ -290,6 +294,8 @@ In effect, a log entry using this ABI is described as: For all fixed-length Solidity types, the ``EVENT_INDEXED_ARGS`` array contains the 32-byte encoded value directly. However, for *types of dynamic length*, which include ``string``, ``bytes``, and arrays, ``EVENT_INDEXED_ARGS`` will contain the *Keccak hash* of the encoded value, rather than the encoded value directly. This allows applications to efficiently query for values of dynamic-length types (by setting the hash of the encoded value as the topic), but leaves applications unable to decode indexed values they have not queried for. For dynamic-length types, application developers face a trade-off between fast search for predetermined values (if the argument is indexed) and legibility of arbitrary values (which requires that the arguments not be indexed). Developers may overcome this tradeoff and achieve both efficient search and arbitrary legibility by defining events with two arguments — one indexed, one not — intended to hold the same value. +.. _abi_json: + JSON ==== diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 5a4c9e29..4c976a32 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -418,6 +418,10 @@ "bugs": [], "released": "2018-02-14" }, + "0.4.21": { + "bugs": [], + "released": "2018-03-07" + }, "0.4.3": { "bugs": [ "ZeroFunctionSelector", diff --git a/docs/contracts.rst b/docs/contracts.rst index 416dc649..121c4de0 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -402,7 +402,7 @@ State variables can be declared as ``constant``. In this case, they have to be assigned from an expression which is a constant at compile time. Any expression that accesses storage, blockchain data (e.g. ``now``, ``this.balance`` or ``block.number``) or -execution data (``msg.gas``) or make calls to external contracts are disallowed. Expressions +execution data (``msg.value`` or ``gasleft()``) or make calls to external contracts are disallowed. Expressions that might have a side-effect on memory allocation are allowed, but those that might have a side-effect on other memory objects are not. The built-in functions ``keccak256``, ``sha256``, ``ripemd160``, ``ecrecover``, ``addmod`` and ``mulmod`` @@ -472,8 +472,15 @@ The following statements are considered modifying the state: .. note:: Getter methods are marked ``view``. +.. note:: + If invalid explicit type conversions are used, state modifications are possible + even though a ``view`` function was called. + You can switch the compiler to use ``STATICCALL`` when calling such functions and thus + prevent modifications to the state on the level of the EVM by adding + ``pragma experimental "v0.5.0";`` + .. warning:: - Before version 0.4.17 the compiler didn't enforce that ``view`` is not modifying the state. + The compiler does not enforce yet that a ``view`` method is not modifying state. It raises a warning though. .. index:: ! pure function, function;pure @@ -502,8 +509,20 @@ In addition to the list of state modifying statements explained above, the follo } } +.. note:: + If invalid explicit type conversions are used, state modifications are possible + even though a ``pure`` function was called. + You can switch the compiler to use ``STATICCALL`` when calling such functions and thus + prevent modifications to the state on the level of the EVM by adding + ``pragma experimental "v0.5.0";`` + +.. warning:: + It is not possible to prevent functions from reading the state at the level + of the EVM, it is only possible to prevent them from writing to the state + (i.e. only ``view`` can be enforced at the EVM level, ``pure`` can not). + .. warning:: - Before version 0.4.17 the compiler didn't enforce that ``view`` is not reading the state. + Before version 0.4.17 the compiler didn't enforce that ``pure`` is not reading the state. .. index:: ! fallback function, function;fallback @@ -523,16 +542,14 @@ Ether (without data). Additionally, in order to receive Ether, the fallback func must be marked ``payable``. If no such function exists, the contract cannot receive Ether through regular transactions. -In such a context, there is usually very little gas available to the function call (to be precise, 2300 gas), so it is important to make fallback functions as cheap as possible. Note that the gas required by a transaction (as opposed to an internal call) that invokes the fallback function is much higher, because each transaction charges an additional amount of 21000 gas or more for things like signature checking. - -In particular, the following operations will consume more gas than the stipend provided to a fallback function: +In the worst case, the fallback function can only rely on 2300 gas being available (for example when send or transfer is used), leaving not much room to perform other operations except basic logging. The following operations will consume more gas than the 2300 gas stipend: - Writing to storage - Creating a contract - Calling an external function which consumes a large amount of gas - Sending Ether -Please ensure you test your fallback function thoroughly to ensure the execution cost is less than 2300 gas before deploying a contract. +Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it. .. note:: Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve @@ -955,6 +972,31 @@ 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. +.. index:: ! constructor + +Constructors +============ +A constructor is an optional function with the same name as the contract which is executed upon contract creation. +Constructor functions can be either ``public`` or ``internal``. + +:: + + pragma solidity ^0.4.11; + + contract A { + uint public a; + + function A(uint _a) internal { + a = _a; + } + } + + contract B is A(1) { + function B() public {} + } + +A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract <abstract-contract>`. + .. index:: ! base;constructor Arguments for Base Constructors @@ -1027,11 +1069,13 @@ As an exception, a state variable getter can override a public function. .. index:: ! contract;abstract, ! abstract contract +.. _abstract-contract: + ****************** Abstract Contracts ****************** -Contract functions can lack an implementation as in the following example (note that the function declaration header is terminated by ``;``):: +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 ``;``):: pragma solidity ^0.4.0; @@ -1039,9 +1083,7 @@ Contract functions can lack an implementation as in the following example (note function utterance() public 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 contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts:: pragma solidity ^0.4.0; @@ -1065,6 +1107,8 @@ Example of a Function Type (a variable declaration, where the variable is of typ function(address) external returns (address) foo; +Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and +facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/Template_method_pattern>`_ and removing code duplication. .. index:: ! contract;interface, ! interface contract diff --git a/docs/contributing.rst b/docs/contributing.rst index a5efba8b..8b4695e4 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -69,15 +69,22 @@ Solidity includes different types of tests. They are included in the application called ``soltest``. Some of them require the ``cpp-ethereum`` client in testing mode, some others require ``libz3`` to be installed. -To disable the z3 tests, use ``./build/test/soltest -- --no-smt`` and -to run a subset of the tests that do not require ``cpp-ethereum``, use ``./build/test/soltest -- --no-ipc``. +``soltest`` reads test contracts that are annotated with expected results +stored in ``./test/libsolidity/syntaxTests``. In order for soltest to find these +tests the root test directory has to be specified using the ``--testpath`` command +line option, e.g. ``./build/test/soltest -- --testpath ./test``. + +To disable the z3 tests, use ``./build/test/soltest -- --no-smt --testpath ./test`` and +to run a subset of the tests that do not require ``cpp-ethereum``, use +``./build/test/soltest -- --no-ipc --testpath ./test``. For all other tests, you need to install `cpp-ethereum <https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/eth>`_ and run it in testing mode: ``eth --test -d /tmp/testeth``. -Then you run the actual tests: ``./build/test/soltest -- --ipcpath /tmp/testeth/geth.ipc``. +Then you run the actual tests: ``./build/test/soltest -- --ipcpath /tmp/testeth/geth.ipc --testpath ./test``. To run a subset of tests, filters can be used: -``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``. +``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc --testpath ./test``, +where ``TestName`` can be a wildcard ``*``. Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests and runs ``cpp-ethereum`` automatically if it is in the path (but does not download it). diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 7be92cfa..46e076e5 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -324,7 +324,8 @@ is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For element will be initialized to the default value corresponding to its type. Finally, for dynamically-sized arrays, ``bytes`` and ``string``, the default value is an empty array or string. -A variable declared anywhere within a function will be in scope for the *entire function*, regardless of where it is declared. +A variable declared anywhere within a function will be in scope for the *entire function*, regardless of where it is declared +(this will change soon, see below). This happens because Solidity inherits its scoping rules from JavaScript. This is in contrast to many languages where variables are only scoped where they are declared until the end of the semantic block. As a result, the following code is illegal and cause the compiler to throw an error, ``Identifier already declared``:: @@ -366,7 +367,9 @@ As a result, the following code is illegal and cause the compiler to throw an er } In addition to this, if a variable is declared, it will be initialized at the beginning of the function to its default value. -As a result, the following code is legal, despite being poorly written:: +As a result, the following code is legal, despite being poorly written: + +:: pragma solidity ^0.4.0; @@ -383,6 +386,60 @@ As a result, the following code is legal, despite being poorly written:: } } +Scoping starting from Version 0.5.0 +----------------------------------- + +Starting from version 0.5.0, Solidity will change to the more widespread scoping rules of C99 +(and many other languages): Variables are visible from the point right after their declaration +until the end of a ``{ }``-block. 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 and other items declared outside of a code block, for example functions, contracts, +user-defined types, etc., do not change their scoping behaviour. This means you can +use state variables before they are declared and call functions recursively. + +These rules are already introduced now as an experimental feature. + +As a consequence, the following examples will compile without warnings, since +the two variables have the same name but disjoint scopes. In non-0.5.0-mode, +they have the same scope (the function ``minimalScoping``) and thus it does +not compile there. + +:: + + pragma solidity ^0.4.0; + pragma experimental "v0.5.0"; + contract C { + function minimalScoping() pure public { + { + uint same2 = 0; + } + + { + uint same2 = 0; + } + } + } + +As a special example of the C99 scoping rules, note that in the following, +the first assignment to ``x`` will actually assign the outer and not the inner variable. +In any case, you will get a warning about the outer variable being shadowed. + +:: + + pragma solidity ^0.4.0; + pragma experimental "v0.5.0"; + contract C { + function f() pure public returns (uint) { + uint x = 1; + { + x = 2; // this will assign to the outer variable + uint x; + } + return x; // x has value 2 + } + } + .. index:: ! exception, ! throw, ! assert, ! require, ! revert Error handling: Assert, Require, Revert and Exceptions diff --git a/docs/index.rst b/docs/index.rst index 184d0e69..80b0d6e7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,7 +19,9 @@ crowdfunding, blind auctions, multi-signature wallets and more. .. note:: The best way to try out Solidity right now is using `Remix <https://remix.ethereum.org/>`_ - (it can take a while to load, please be patient). + (it can take a while to load, please be patient). Remix is a web browser + based IDE that allows you to write Solidity smart contracts, then deploy + and run the smart contracts. .. warning:: Since software is written by humans, it can have bugs. Thus, also @@ -37,6 +39,7 @@ This documentation is translated into several languages by community volunteers, * `Simplified Chinese <http://solidity-cn.readthedocs.io>`_ (in progress) * `Spanish <https://solidity-es.readthedocs.io>`_ * `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated) +* `Korean <http://solidity-kr.readthedocs.io>`_ (in progress) Useful links diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 11e07292..56f0fe3e 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -80,7 +80,7 @@ registering with username and password - all you need is an Ethereum keypair. :: - pragma solidity ^0.4.20; // should actually be 0.4.21 + pragma solidity ^0.4.21; contract Coin { // The keyword "public" makes those variables diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 328ec6ea..a7d5c445 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -192,6 +192,11 @@ These are regular array indices into a list of source files usually called ``"sourceList"``, which is part of the combined-json and the output of the json / npm compiler. +.. note :: + In the case of instructions that are not associated with any particular source file, + the source mapping assigns an integer identifier of ``-1``. This may happen for + bytecode sections stemming from compiler-generated inline assembly statements. + The source mappings inside the AST use the following notation: @@ -219,7 +224,7 @@ This means the following source mappings represent the same information: ``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2`` -``1:2:1;:9;2::2;;`` +``1:2:1;:9;2:1:2;;`` *************** Tips and Tricks @@ -230,7 +235,10 @@ Tips and Tricks * Make your state variables public - the compiler will create :ref:`getters <visibility-and-getters>` for you automatically. * If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`. * If your contract has a function called ``send`` but you want to use the built-in send-function, use ``address(contractVariable).send(amount)``. -* Initialise storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});`` +* 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. ********** Cheatsheet @@ -312,8 +320,9 @@ Global Variables - ``block.gaslimit`` (``uint``): current block gaslimit - ``block.number`` (``uint``): current block number - ``block.timestamp`` (``uint``): current block timestamp +- ``gasleft() returns (uint256)``: remaining gas - ``msg.data`` (``bytes``): complete calldata -- ``msg.gas`` (``uint``): remaining gas +- ``msg.gas`` (``uint``): remaining gas - deprecated in version 0.4.21 and to be replaced by ``gasleft()`` - ``msg.sender`` (``address``): sender of the message (current call) - ``msg.value`` (``uint``): number of wei sent with the message - ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``) @@ -332,7 +341,7 @@ Global Variables - ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``super``: the contract one level higher in the inheritance hierarchy - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address -- ``suicide(address recipient)``: an alias to ``selfdestruct`` +- ``suicide(address recipient)``: a deprecated alias to ``selfdestruct`` - ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei - ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure - ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 57556fa5..27fefd49 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -219,7 +219,7 @@ activate themselves. :: - pragma solidity ^0.4.20; // should actually be 0.4.21 + pragma solidity ^0.4.21; contract SimpleAuction { // Parameters of the auction. Times are either @@ -376,7 +376,7 @@ high or low invalid bids. :: - pragma solidity ^0.4.20; // should actually be 0.4.21 + pragma solidity ^0.4.21; contract BlindAuction { struct Bid { @@ -529,7 +529,7 @@ Safe Remote Purchase :: - pragma solidity ^0.4.20; // should actually be 0.4.21 + pragma solidity ^0.4.21; contract Purchase { uint public value; diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst index 4a0873df..df40b1d0 100644 --- a/docs/structure-of-a-contract.rst +++ b/docs/structure-of-a-contract.rst @@ -86,7 +86,7 @@ Events are convenience interfaces with the EVM logging facilities. :: - pragma solidity ^0.4.20; // should actually be 0.4.21 + pragma solidity ^0.4.21; contract SimpleAuction { event HighestBidIncreased(address bidder, uint amount); // Event diff --git a/docs/style-guide.rst b/docs/style-guide.rst index ade37d0b..2261746f 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -112,7 +112,9 @@ No:: } } -Maximum Line Length +.. _maximum_line_length: + +Maximum Line Length =================== Keeping lines under the `PEP 8 recommendation <https://www.python.org/dev/peps/pep-0008/#maximum-line-length>`_ of 79 (or 99) @@ -650,6 +652,50 @@ No:: doSomething(); } +Multiline output parameters and return statements should follow the same style recommended for wrapping long lines found in the :ref:`Maximum Line Length <maximum_line_length>` section. + +Yes:: + + function thisFunctionNameIsReallyLong( + address a, + address b, + address c + ) + public + returns ( + address someAddressName, + uint256 LongArgument, + uint256 Argument + ) + { + doSomething() + + return ( + veryLongReturnArg1, + veryLongReturnArg2, + veryLongReturnArg3 + ); + } + +No:: + + function thisFunctionNameIsReallyLong( + address a, + address b, + address c + ) + public + returns (address someAddressName, + uint256 LongArgument, + uint256 Argument) + { + doSomething() + + return (veryLongReturnArg1, + veryLongReturnArg1, + veryLongReturnArg1); + } + For constructor functions on inherited contracts whose bases require arguments, it is recommended to drop the base constructors onto new lines in the same manner as modifiers if the function declaration is long or hard to read. diff --git a/docs/types.rst b/docs/types.rst index 3611bc3e..e704687e 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -470,7 +470,7 @@ Example that shows how to use internal function types:: Another example that uses external function types:: - pragma solidity ^0.4.20; // should actually be 0.4.21 + pragma solidity ^0.4.21; contract Oracle { struct Request { diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index cc4d4446..d789e87f 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -58,8 +58,9 @@ Block and Transaction Properties - ``block.gaslimit`` (``uint``): current block gaslimit - ``block.number`` (``uint``): current block number - ``block.timestamp`` (``uint``): current block timestamp as seconds since unix epoch +- ``gasleft() returns (uint256)``: remaining gas - ``msg.data`` (``bytes``): complete calldata -- ``msg.gas`` (``uint``): remaining gas +- ``msg.gas`` (``uint``): remaining gas - deprecated in version 0.4.21 and to be replaced by ``gasleft()`` - ``msg.sender`` (``address``): sender of the message (current call) - ``msg.sig`` (``bytes4``): first four bytes of the calldata (i.e. function identifier) - ``msg.value`` (``uint``): number of wei sent with the message @@ -182,7 +183,7 @@ Contract Related destroy the current contract, sending its funds to the given :ref:`address` ``suicide(address recipient)``: - alias to ``selfdestruct`` + deprecated alias to ``selfdestruct`` Furthermore, all functions of the current contract are callable directly including the current function. diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 66e3ac35..df30b6b4 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -101,6 +101,7 @@ Input Description enabled: true, runs: 500 }, + evmVersion: "byzantium", // Version of the EVM to compile for. Affects type checking and code generation. Can be homestead, tangerineWhistle, spuriousDragon, byzantium or constantinople // Metadata settings (optional) metadata: { // Use only literal content and not URLs (false by default) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index b9fedf26..bd4ebf59 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -353,7 +353,7 @@ void Assembly::injectStart(AssemblyItem const& _i) m_items.insert(m_items.begin(), _i); } -Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) +Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation, size_t _runs) { OptimiserSettings settings; settings.isCreation = _isCreation; @@ -365,6 +365,7 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) settings.runCSE = true; settings.runConstantOptimiser = true; } + settings.evmVersion = _evmVersion; settings.expectedExecutionsPerDeployment = _runs; optimise(settings); return *this; @@ -482,6 +483,7 @@ map<u256, u256> Assembly::optimiseInternal( ConstantOptimisationMethod::optimiseConstants( _settings.isCreation, _settings.isCreation ? 1 : _settings.expectedExecutionsPerDeployment, + _settings.evmVersion, *this, m_items ); diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 885192e4..367c6daa 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -23,6 +23,8 @@ #include <libevmasm/LinkerObject.h> #include <libevmasm/Exceptions.h> +#include <libsolidity/interface/EVMVersion.h> + #include <libdevcore/Common.h> #include <libdevcore/Assertions.h> #include <libdevcore/SHA3.h> @@ -107,6 +109,7 @@ public: bool runDeduplicate = false; bool runCSE = false; bool runConstantOptimiser = false; + solidity::EVMVersion evmVersion; /// This specifies an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. size_t expectedExecutionsPerDeployment = 200; @@ -120,7 +123,7 @@ public: /// @a _runs specifes an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime. /// If @a _enable is not set, will perform some simple peephole optimizations. - Assembly& optimise(bool _enable, bool _isCreation = true, size_t _runs = 200); + Assembly& optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation = true, size_t _runs = 200); /// Create a text representation of the assembly. std::string assemblyString( diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index 2efd2dc9..d0b6843c 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -29,6 +29,7 @@ using namespace dev::eth; unsigned ConstantOptimisationMethod::optimiseConstants( bool _isCreation, size_t _runs, + solidity::EVMVersion _evmVersion, Assembly& _assembly, AssemblyItems& _items ) @@ -48,6 +49,7 @@ unsigned ConstantOptimisationMethod::optimiseConstants( params.multiplicity = it.second; params.isCreation = _isCreation; params.runs = _runs; + params.evmVersion = _evmVersion; LiteralMethod lit(params, item.data()); bigint literalGas = lit.gasNeeded(); CodeCopyMethod copy(params, item.data()); @@ -80,7 +82,12 @@ bigint ConstantOptimisationMethod::simpleRunGas(AssemblyItems const& _items) if (item.type() == Push) gas += GasMeter::runGas(Instruction::PUSH1); else if (item.type() == Operation) - gas += GasMeter::runGas(item.instruction()); + { + if (item.instruction() == Instruction::EXP) + gas += GasCosts::expGas; + else + gas += GasMeter::runGas(item.instruction()); + } return gas; } @@ -286,7 +293,7 @@ bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) const { size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP); return combineGas( - simpleRunGas(_routine) + numExps * (GasCosts::expGas + GasCosts::expByteGas), + simpleRunGas(_routine) + numExps * (GasCosts::expGas + GasCosts::expByteGas(m_params.evmVersion)), // Data gas for routine: Some bytes are zero, but we ignore them. bytesRequired(_routine) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas), 0 diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index c450b0b4..9b60b26b 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -23,6 +23,8 @@ #include <libevmasm/Exceptions.h> +#include <libsolidity/interface/EVMVersion.h> + #include <libdevcore/Assertions.h> #include <libdevcore/CommonData.h> #include <libdevcore/CommonIO.h> @@ -50,6 +52,7 @@ public: static unsigned optimiseConstants( bool _isCreation, size_t _runs, + solidity::EVMVersion _evmVersion, Assembly& _assembly, AssemblyItems& _items ); @@ -59,6 +62,7 @@ public: bool isCreation; ///< Whether this is called during contract creation or runtime. size_t runs; ///< Estimated number of calls per opcode oven the lifetime of the contract. size_t multiplicity; ///< Number of times the constant appears in the code. + solidity::EVMVersion evmVersion; ///< Version of the EVM }; explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value): diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 543f1cbc..caa06fc0 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -61,7 +61,6 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Operation: { ExpressionClasses& classes = m_state->expressionClasses(); - gas = runGas(_item.instruction()); switch (_item.instruction()) { case Instruction::SSTORE: @@ -72,26 +71,29 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ m_state->storageContent().count(slot) && classes.knownNonZero(m_state->storageContent().at(slot)) )) - gas += GasCosts::sstoreResetGas; //@todo take refunds into account + gas = GasCosts::sstoreResetGas; //@todo take refunds into account else - gas += GasCosts::sstoreSetGas; + gas = GasCosts::sstoreSetGas; break; } case Instruction::SLOAD: - gas += GasCosts::sloadGas; + gas = GasCosts::sloadGas(m_evmVersion); break; case Instruction::RETURN: case Instruction::REVERT: + gas = runGas(_item.instruction()); gas += memoryGas(0, -1); break; case Instruction::MLOAD: case Instruction::MSTORE: + gas = runGas(_item.instruction()); gas += memoryGas(classes.find(Instruction::ADD, { m_state->relativeStackElement(0), classes.find(AssemblyItem(32)) })); break; case Instruction::MSTORE8: + gas = runGas(_item.instruction()); gas += memoryGas(classes.find(Instruction::ADD, { m_state->relativeStackElement(0), classes.find(AssemblyItem(1)) @@ -105,10 +107,15 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::CALLDATACOPY: case Instruction::CODECOPY: case Instruction::RETURNDATACOPY: + gas = runGas(_item.instruction()); gas += memoryGas(0, -2); gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-2)); break; + case Instruction::EXTCODESIZE: + gas = GasCosts::extCodeGas(m_evmVersion); + break; case Instruction::EXTCODECOPY: + gas = GasCosts::extCodeGas(m_evmVersion); gas += memoryGas(-1, -3); gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-3)); break; @@ -137,7 +144,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ gas = GasConsumption::infinite(); else { - gas = GasCosts::callGas; + gas = GasCosts::callGas(m_evmVersion); if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0))) gas += (*value); else @@ -155,7 +162,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ break; } case Instruction::SELFDESTRUCT: - gas = GasCosts::selfdestructGas; + gas = GasCosts::selfdestructGas(m_evmVersion); gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. break; case Instruction::CREATE: @@ -172,11 +179,15 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::EXP: gas = GasCosts::expGas; if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) - gas += GasCosts::expByteGas * (32 - (h256(*value).firstBitSet() / 8)); + gas += GasCosts::expByteGas(m_evmVersion) * (32 - (h256(*value).firstBitSet() / 8)); else - gas += GasCosts::expByteGas * 32; + gas += GasCosts::expByteGas(m_evmVersion) * 32; + break; + case Instruction::BALANCE: + gas = GasCosts::balanceGas(m_evmVersion); break; default: + gas = runGas(_item.instruction()); break; } break; @@ -241,12 +252,9 @@ unsigned GasMeter::runGas(Instruction _instruction) case Tier::Mid: return GasCosts::tier4Gas; case Tier::High: return GasCosts::tier5Gas; case Tier::Ext: return GasCosts::tier6Gas; - case Tier::Special: return GasCosts::tier7Gas; - case Tier::ExtCode: return GasCosts::extCodeGas; - case Tier::Balance: return GasCosts::balanceGas; default: break; } - assertThrow(false, OptimizerException, "Invalid gas tier."); + assertThrow(false, OptimizerException, "Invalid gas tier for instruction " + instructionInfo(_instruction).name); return 0; } diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 2c3ecf5a..b131802f 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -21,11 +21,14 @@ #pragma once -#include <ostream> -#include <tuple> #include <libevmasm/ExpressionClasses.h> #include <libevmasm/AssemblyItem.h> +#include <libsolidity/interface/EVMVersion.h> + +#include <ostream> +#include <tuple> + namespace dev { namespace eth @@ -44,13 +47,25 @@ namespace GasCosts static unsigned const tier5Gas = 10; static unsigned const tier6Gas = 20; static unsigned const tier7Gas = 0; - static unsigned const extCodeGas = 700; - static unsigned const balanceGas = 400; + inline unsigned extCodeGas(EVMVersion _evmVersion) + { + return _evmVersion >= EVMVersion::tangerineWhistle() ? 700 : 20; + } + inline unsigned balanceGas(EVMVersion _evmVersion) + { + return _evmVersion >= EVMVersion::tangerineWhistle() ? 400 : 20; + } static unsigned const expGas = 10; - static unsigned const expByteGas = 50; + inline unsigned expByteGas(EVMVersion _evmVersion) + { + return _evmVersion >= EVMVersion::spuriousDragon() ? 50 : 10; + } static unsigned const keccak256Gas = 30; static unsigned const keccak256WordGas = 6; - static unsigned const sloadGas = 200; + inline unsigned sloadGas(EVMVersion _evmVersion) + { + return _evmVersion >= EVMVersion::tangerineWhistle() ? 200 : 50; + } static unsigned const sstoreSetGas = 20000; static unsigned const sstoreResetGas = 5000; static unsigned const sstoreRefundGas = 15000; @@ -59,11 +74,17 @@ namespace GasCosts static unsigned const logDataGas = 8; static unsigned const logTopicGas = 375; static unsigned const createGas = 32000; - static unsigned const callGas = 700; + inline unsigned callGas(EVMVersion _evmVersion) + { + return _evmVersion >= EVMVersion::tangerineWhistle() ? 700 : 40; + } static unsigned const callStipend = 2300; static unsigned const callValueTransferGas = 9000; static unsigned const callNewAccountGas = 25000; - static unsigned const selfdestructGas = 5000; + inline unsigned selfdestructGas(EVMVersion _evmVersion) + { + return _evmVersion >= EVMVersion::tangerineWhistle() ? 5000 : 0; + } static unsigned const selfdestructRefundGas = 24000; static unsigned const memoryGas = 3; static unsigned const quadCoeffDiv = 512; @@ -100,8 +121,8 @@ public: }; /// Constructs a new gas meter given the current state. - explicit GasMeter(std::shared_ptr<KnownState> const& _state, u256 const& _largestMemoryAccess = 0): - m_state(_state), m_largestMemoryAccess(_largestMemoryAccess) {} + GasMeter(std::shared_ptr<KnownState> const& _state, solidity::EVMVersion _evmVersion, u256 const& _largestMemoryAccess = 0): + m_state(_state), m_evmVersion(_evmVersion), m_largestMemoryAccess(_largestMemoryAccess) {} /// @returns an upper bound on the gas consumed by the given instruction and updates /// the state. @@ -110,6 +131,8 @@ public: u256 const& largestMemoryAccess() const { return m_largestMemoryAccess; } + /// @returns gas costs for simple instructions with constant gas costs (that do not + /// change with EVM versions) static unsigned runGas(Instruction _instruction); private: @@ -123,6 +146,7 @@ private: GasConsumption memoryGas(int _stackPosOffset, int _stackPosSize); std::shared_ptr<KnownState> m_state; + EVMVersion m_evmVersion; /// Largest point where memory was accessed since the creation of this object. u256 m_largestMemoryAccess; }; diff --git a/libevmasm/PathGasMeter.cpp b/libevmasm/PathGasMeter.cpp index c56e2f8b..3fe682b7 100644 --- a/libevmasm/PathGasMeter.cpp +++ b/libevmasm/PathGasMeter.cpp @@ -27,8 +27,8 @@ using namespace std; using namespace dev; using namespace dev::eth; -PathGasMeter::PathGasMeter(AssemblyItems const& _items): - m_items(_items) +PathGasMeter::PathGasMeter(AssemblyItems const& _items, solidity::EVMVersion _evmVersion): + m_items(_items), m_evmVersion(_evmVersion) { for (size_t i = 0; i < m_items.size(); ++i) if (m_items[i].type() == Tag) @@ -59,7 +59,7 @@ GasMeter::GasConsumption PathGasMeter::handleQueueItem() m_queue.pop_back(); shared_ptr<KnownState> state = path->state; - GasMeter meter(state, path->largestMemoryAccess); + GasMeter meter(state, m_evmVersion, path->largestMemoryAccess); ExpressionClasses& classes = state->expressionClasses(); GasMeter::GasConsumption gas = path->gas; size_t index = path->index; diff --git a/libevmasm/PathGasMeter.h b/libevmasm/PathGasMeter.h index 4826eac2..2527d7fb 100644 --- a/libevmasm/PathGasMeter.h +++ b/libevmasm/PathGasMeter.h @@ -21,10 +21,13 @@ #pragma once +#include <libevmasm/GasMeter.h> + +#include <libsolidity/interface/EVMVersion.h> + #include <set> #include <vector> #include <memory> -#include <libevmasm/GasMeter.h> namespace dev { @@ -50,7 +53,7 @@ struct GasPath class PathGasMeter { public: - explicit PathGasMeter(AssemblyItems const& _items); + explicit PathGasMeter(AssemblyItems const& _items, solidity::EVMVersion _evmVersion); GasMeter::GasConsumption estimateMax(size_t _startIndex, std::shared_ptr<KnownState> const& _state); @@ -60,6 +63,7 @@ private: std::vector<std::unique_ptr<GasPath>> m_queue; std::map<u256, size_t> m_tagPositions; AssemblyItems const& m_items; + solidity::EVMVersion m_evmVersion; }; } diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp index 1638f69e..f2c1b0be 100644 --- a/liblll/Compiler.cpp +++ b/liblll/Compiler.cpp @@ -19,17 +19,16 @@ * @date 2014 */ -#include "Compiler.h" -#include "Parser.h" -#include "CompilerState.h" -#include "CodeFragment.h" +#include <liblll/Compiler.h> +#include <liblll/Parser.h> +#include <liblll/CompilerState.h> +#include <liblll/CodeFragment.h> using namespace std; using namespace dev; using namespace dev::eth; - -bytes dev::eth::compileLLL(string const& _src, bool _opt, vector<string>* _errors, ReadCallback const& _readFile) +bytes dev::eth::compileLLL(string const& _src, dev::solidity::EVMVersion _evmVersion, bool _opt, std::vector<std::string>* _errors, dev::eth::ReadCallback const& _readFile) { try { @@ -37,7 +36,7 @@ bytes dev::eth::compileLLL(string const& _src, bool _opt, vector<string>* _error cs.populateStandard(); auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs); if (_opt) - assembly = assembly.optimise(true); + assembly = assembly.optimise(true, _evmVersion); bytes ret = assembly.assemble().bytecode; for (auto i: cs.treesToKill) killBigints(i); @@ -67,7 +66,7 @@ bytes dev::eth::compileLLL(string const& _src, bool _opt, vector<string>* _error return bytes(); } -std::string dev::eth::compileLLLToAsm(std::string const& _src, bool _opt, std::vector<std::string>* _errors, ReadCallback const& _readFile) +std::string dev::eth::compileLLLToAsm(std::string const& _src, EVMVersion _evmVersion, bool _opt, std::vector<std::string>* _errors, ReadCallback const& _readFile) { try { @@ -75,7 +74,7 @@ std::string dev::eth::compileLLLToAsm(std::string const& _src, bool _opt, std::v cs.populateStandard(); auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs); if (_opt) - assembly = assembly.optimise(true); + assembly = assembly.optimise(true, _evmVersion); string ret = assembly.assemblyString(); for (auto i: cs.treesToKill) killBigints(i); diff --git a/liblll/Compiler.h b/liblll/Compiler.h index c3395b66..06440c17 100644 --- a/liblll/Compiler.h +++ b/liblll/Compiler.h @@ -21,9 +21,12 @@ #pragma once +#include <libdevcore/Common.h> + +#include <libsolidity/interface/EVMVersion.h> + #include <string> #include <vector> -#include <libdevcore/Common.h> namespace dev { @@ -33,8 +36,8 @@ namespace eth using ReadCallback = std::function<std::string(std::string const&)>; std::string parseLLL(std::string const& _src); -std::string compileLLLToAsm(std::string const& _src, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); -bytes compileLLL(std::string const& _src, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); +std::string compileLLLToAsm(std::string const& _src, solidity::EVMVersion _evmVersion, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); +bytes compileLLL(std::string const& _src, solidity::EVMVersion _evmVersion, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); } } diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index 7508ad9e..c7ba78d6 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -79,6 +79,17 @@ Declaration const* DeclarationContainer::conflictingDeclaration( return nullptr; } +void DeclarationContainer::activateVariable(ASTString const& _name) +{ + solAssert( + m_invisibleDeclarations.count(_name) && m_invisibleDeclarations.at(_name).size() == 1, + "Tried to activate a non-inactive variable or multiple inactive variables with the same name." + ); + solAssert(m_declarations.count(_name) == 0 || m_declarations.at(_name).empty(), ""); + m_declarations[_name].emplace_back(m_invisibleDeclarations.at(_name).front()); + m_invisibleDeclarations.erase(_name); +} + bool DeclarationContainer::registerDeclaration( Declaration const& _declaration, ASTString const* _name, @@ -106,15 +117,17 @@ bool DeclarationContainer::registerDeclaration( return true; } -vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const +vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive, bool _alsoInvisible) const { solAssert(!_name.empty(), "Attempt to resolve empty name."); - auto result = m_declarations.find(_name); - if (result != m_declarations.end()) - return result->second; - if (_recursive && m_enclosingContainer) - return m_enclosingContainer->resolveName(_name, true); - return vector<Declaration const*>({}); + vector<Declaration const*> result; + if (m_declarations.count(_name)) + result = m_declarations.at(_name); + if (_alsoInvisible && m_invisibleDeclarations.count(_name)) + result += m_invisibleDeclarations.at(_name); + if (result.empty() && _recursive && m_enclosingContainer) + result = m_enclosingContainer->resolveName(_name, true, _alsoInvisible); + return result; } vector<ASTString> DeclarationContainer::similarNames(ASTString const& _name) const @@ -129,6 +142,12 @@ vector<ASTString> DeclarationContainer::similarNames(ASTString const& _name) con if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE)) similar.push_back(declarationName); } + for (auto const& declaration: m_invisibleDeclarations) + { + string const& declarationName = declaration.first; + if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE)) + similar.push_back(declarationName); + } if (m_enclosingContainer) similar += m_enclosingContainer->similarNames(_name); diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h index f9b1bda4..e4b3320a 100644 --- a/libsolidity/analysis/DeclarationContainer.h +++ b/libsolidity/analysis/DeclarationContainer.h @@ -51,13 +51,17 @@ public: /// @param _update if true, replaces a potential declaration that is already present /// @returns false if the name was already declared. bool registerDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr, bool _invisible = false, bool _update = false); - std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false) const; + std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false, bool _alsoInvisible = false) const; ASTNode const* enclosingNode() const { return m_enclosingNode; } DeclarationContainer const* enclosingContainer() const { return m_enclosingContainer; } std::map<ASTString, std::vector<Declaration const*>> const& declarations() const { return m_declarations; } /// @returns whether declaration is valid, and if not also returns previous declaration. Declaration const* conflictingDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr) const; + /// Activates a previously inactive (invisible) variable. To be used in C99 scpoing for + /// VariableDeclarationStatements. + void activateVariable(ASTString const& _name); + /// @returns existing declaration names similar to @a _name. /// Searches this and all parent containers. std::vector<ASTString> similarNames(ASTString const& _name) const; diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index fd39d860..34cb61d8 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -39,6 +39,7 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{ make_shared<MagicVariableDeclaration>("assert", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)), make_shared<MagicVariableDeclaration>("ecrecover", make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), + make_shared<MagicVariableDeclaration>("gasleft", make_shared<FunctionType>(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)), make_shared<MagicVariableDeclaration>("keccak256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("log0", make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), make_shared<MagicVariableDeclaration>("log1", make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)), diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 662792a3..2f675135 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -50,12 +50,13 @@ NameAndTypeResolver::NameAndTypeResolver( m_scopes[nullptr]->registerDeclaration(*declaration); } -bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope) +bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope) { + bool useC99Scoping = _sourceUnit.annotation().experimentalFeatures.count(ExperimentalFeature::V050); // The helper registers all declarations in m_scopes as a side-effect of its construction. try { - DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope); + DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, useC99Scoping, m_errorReporter, _currentScope); } catch (FatalError const&) { @@ -106,7 +107,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So else for (Declaration const* declaration: declarations) if (!DeclarationRegistrationHelper::registerDeclaration( - target, *declaration, alias.second.get(), &imp->location(), true, m_errorReporter + target, *declaration, alias.second.get(), &imp->location(), true, false, m_errorReporter )) error = true; } @@ -114,7 +115,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So for (auto const& nameAndDeclaration: scope->second->declarations()) for (auto const& declaration: nameAndDeclaration.second) if (!DeclarationRegistrationHelper::registerDeclaration( - target, *declaration, &nameAndDeclaration.first, &imp->location(), true, m_errorReporter + target, *declaration, &nameAndDeclaration.first, &imp->location(), true, false, m_errorReporter )) error = true; } @@ -151,6 +152,12 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) return true; } +void NameAndTypeResolver::activateVariable(string const& _name) +{ + solAssert(m_currentScope, ""); + m_currentScope->activateVariable(_name); +} + vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _name, ASTNode const* _scope) const { auto iterator = m_scopes.find(_scope); @@ -159,15 +166,15 @@ vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _na return iterator->second->resolveName(_name, false); } -vector<Declaration const*> NameAndTypeResolver::nameFromCurrentScope(ASTString const& _name, bool _recursive) const +vector<Declaration const*> NameAndTypeResolver::nameFromCurrentScope(ASTString const& _name, bool _includeInvisibles) const { - return m_currentScope->resolveName(_name, _recursive); + return m_currentScope->resolveName(_name, true, _includeInvisibles); } -Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> const& _path, bool _recursive) const +Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> const& _path) const { solAssert(!_path.empty(), ""); - vector<Declaration const*> candidates = m_currentScope->resolveName(_path.front(), _recursive); + vector<Declaration const*> candidates = m_currentScope->resolveName(_path.front(), true); for (size_t i = 1; i < _path.size() && candidates.size() == 1; i++) { if (!m_scopes.count(candidates.front())) @@ -229,7 +236,7 @@ void NameAndTypeResolver::warnVariablesNamedLikeInstructions() for (auto const& instruction: c_instructions) { string const instructionName{boost::algorithm::to_lower_copy(instruction.first)}; - auto declarations = nameFromCurrentScope(instructionName); + auto declarations = nameFromCurrentScope(instructionName, true); for (Declaration const* const declaration: declarations) { solAssert(!!declaration, ""); @@ -244,19 +251,24 @@ void NameAndTypeResolver::warnVariablesNamedLikeInstructions() } } +void NameAndTypeResolver::setScope(ASTNode const* _node) +{ + m_currentScope = m_scopes[_node].get(); +} + bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode) { if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node)) { bool success = true; - m_currentScope = m_scopes[contract->scope()].get(); + setScope(contract->scope()); solAssert(!!m_currentScope, ""); for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts()) if (!resolveNamesAndTypes(*baseContract, true)) success = false; - m_currentScope = m_scopes[contract].get(); + setScope(contract); if (success) { @@ -273,7 +285,7 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res // these can contain code, only resolve parameters for now for (ASTPointer<ASTNode> const& node: contract->subNodes()) { - m_currentScope = m_scopes[contract].get(); + setScope(contract); if (!resolveNamesAndTypes(*node, false)) { success = false; @@ -287,12 +299,12 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res if (!_resolveInsideCode) return success; - m_currentScope = m_scopes[contract].get(); + setScope(contract); // now resolve references inside the code for (ASTPointer<ASTNode> const& node: contract->subNodes()) { - m_currentScope = m_scopes[contract].get(); + setScope(contract); if (!resolveNamesAndTypes(*node, true)) success = false; } @@ -301,7 +313,7 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res else { if (m_scopes.count(&_node)) - m_currentScope = m_scopes[&_node].get(); + setScope(&_node); return ReferencesResolver(m_errorReporter, *this, _resolveInsideCode).resolve(_node); } } @@ -434,9 +446,11 @@ string NameAndTypeResolver::similarNameSuggestions(ASTString const& _name) const DeclarationRegistrationHelper::DeclarationRegistrationHelper( map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes, ASTNode& _astRoot, + bool _useC99Scoping, ErrorReporter& _errorReporter, ASTNode const* _currentScope ): + m_useC99Scoping(_useC99Scoping), m_scopes(_scopes), m_currentScope(_currentScope), m_errorReporter(_errorReporter) @@ -451,6 +465,7 @@ bool DeclarationRegistrationHelper::registerDeclaration( string const* _name, SourceLocation const* _errorLocation, bool _warnOnShadow, + bool _inactive, ErrorReporter& _errorReporter ) { @@ -460,10 +475,13 @@ bool DeclarationRegistrationHelper::registerDeclaration( string name = _name ? *_name : _declaration.name(); Declaration const* shadowedDeclaration = nullptr; if (_warnOnShadow && !name.empty() && _container.enclosingContainer()) - for (auto const* decl: _container.enclosingContainer()->resolveName(name, true)) + for (auto const* decl: _container.enclosingContainer()->resolveName(name, true, true)) shadowedDeclaration = decl; - if (!_container.registerDeclaration(_declaration, _name, !_declaration.isVisibleInContract())) + // We use "invisible" for both inactive variables in blocks and for members invisible in contracts. + // They cannot both be true at the same time. + solAssert(!(_inactive && !_declaration.isVisibleInContract()), ""); + if (!_container.registerDeclaration(_declaration, _name, !_declaration.isVisibleInContract() || _inactive)) { SourceLocation firstDeclarationLocation; SourceLocation secondDeclarationLocation; @@ -605,6 +623,34 @@ void DeclarationRegistrationHelper::endVisit(ModifierDefinition&) closeCurrentScope(); } +bool DeclarationRegistrationHelper::visit(Block& _block) +{ + _block.setScope(m_currentScope); + if (m_useC99Scoping) + enterNewSubScope(_block); + return true; +} + +void DeclarationRegistrationHelper::endVisit(Block&) +{ + if (m_useC99Scoping) + closeCurrentScope(); +} + +bool DeclarationRegistrationHelper::visit(ForStatement& _for) +{ + _for.setScope(m_currentScope); + if (m_useC99Scoping) + enterNewSubScope(_for); + return true; +} + +void DeclarationRegistrationHelper::endVisit(ForStatement&) +{ + if (m_useC99Scoping) + closeCurrentScope(); +} + void DeclarationRegistrationHelper::endVisit(VariableDeclarationStatement& _variableDeclarationStatement) { // Register the local variables with the function @@ -632,14 +678,14 @@ void DeclarationRegistrationHelper::endVisit(EventDefinition&) closeCurrentScope(); } -void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declaration) +void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _subScope) { map<ASTNode const*, shared_ptr<DeclarationContainer>>::iterator iter; bool newlyAdded; shared_ptr<DeclarationContainer> container(new DeclarationContainer(m_currentScope, m_scopes[m_currentScope].get())); - tie(iter, newlyAdded) = m_scopes.emplace(&_declaration, move(container)); + tie(iter, newlyAdded) = m_scopes.emplace(&_subScope, move(container)); solAssert(newlyAdded, "Unable to add new scope."); - m_currentScope = &_declaration; + m_currentScope = &_subScope; } void DeclarationRegistrationHelper::closeCurrentScope() @@ -667,7 +713,12 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio if (fun->isConstructor()) warnAboutShadowing = false; - registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, m_errorReporter); + // Register declaration as inactive if we are in block scope and C99 mode. + bool inactive = + m_useC99Scoping && + (dynamic_cast<Block const*>(m_currentScope) || dynamic_cast<ForStatement const*>(m_currentScope)); + + registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter); _declaration.setScope(m_currentScope); if (_opensScope) diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 9aea07ab..3d10fbd8 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -56,7 +56,7 @@ public: /// @returns false in case of error. /// @param _currentScope should be nullptr but can be used to inject new declarations into /// existing scopes, used by the snippets feature. - bool registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope = nullptr); + bool registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope = nullptr); /// Applies the effect of import directives. bool performImports(SourceUnit& _sourceUnit, std::map<std::string, SourceUnit const*> const& _sourceUnits); /// Resolves all names and types referenced from the given AST Node. @@ -69,20 +69,24 @@ public: /// that create their own scope. /// @returns false in case of error. bool updateDeclaration(Declaration const& _declaration); + /// Activates a previously inactive (invisible) variable. To be used in C99 scpoing for + /// VariableDeclarationStatements. + void activateVariable(std::string const& _name); /// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted, /// the global scope is used (i.e. the one containing only the pre-defined global variables). /// @returns a pointer to the declaration on success or nullptr on failure. + /// SHOULD only be used for testing. std::vector<Declaration const*> resolveName(ASTString const& _name, ASTNode const* _scope = nullptr) const; - /// Resolves a name in the "current" scope. Should only be called during the initial - /// resolving phase. - std::vector<Declaration const*> nameFromCurrentScope(ASTString const& _name, bool _recursive = true) const; + /// Resolves a name in the "current" scope, but also searches parent scopes. + /// Should only be called during the initial resolving phase. + std::vector<Declaration const*> nameFromCurrentScope(ASTString const& _name, bool _includeInvisibles = false) const; - /// Resolves a path starting from the "current" scope. Should only be called during the initial - /// resolving phase. + /// Resolves a path starting from the "current" scope, but also searches parent scopes. + /// Should only be called during the initial resolving phase. /// @note Returns a null pointer if any component in the path was not unique or not found. - Declaration const* pathFromCurrentScope(std::vector<ASTString> const& _path, bool _recursive = true) const; + Declaration const* pathFromCurrentScope(std::vector<ASTString> const& _path) const; /// returns the vector of declarations without repetitions std::vector<Declaration const*> cleanedDeclarations( @@ -96,6 +100,9 @@ public: /// @returns a list of similar identifiers in the current and enclosing scopes. May return empty string if no suggestions. std::string similarNameSuggestions(ASTString const& _name) const; + /// Sets the current scope. + void setScope(ASTNode const* _node); + private: /// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors. bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true); @@ -135,6 +142,7 @@ public: DeclarationRegistrationHelper( std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes, ASTNode& _astRoot, + bool _useC99Scoping, ErrorReporter& _errorReporter, ASTNode const* _currentScope = nullptr ); @@ -145,6 +153,7 @@ public: std::string const* _name, SourceLocation const* _errorLocation, bool _warnOnShadow, + bool _inactive, ErrorReporter& _errorReporter ); @@ -163,12 +172,16 @@ private: void endVisit(FunctionDefinition& _function) override; bool visit(ModifierDefinition& _modifier) override; void endVisit(ModifierDefinition& _modifier) override; + bool visit(Block& _block) override; + void endVisit(Block& _block) override; + bool visit(ForStatement& _forLoop) override; + void endVisit(ForStatement& _forLoop) override; void endVisit(VariableDeclarationStatement& _variableDeclarationStatement) override; bool visit(VariableDeclaration& _declaration) override; bool visit(EventDefinition& _event) override; void endVisit(EventDefinition& _event) override; - void enterNewSubScope(Declaration const& _declaration); + void enterNewSubScope(ASTNode& _subScope); void closeCurrentScope(); void registerDeclaration(Declaration& _declaration, bool _opensScope); @@ -177,6 +190,7 @@ private: /// @returns the canonical name of the current scope. std::string currentCanonicalName() const; + bool m_useC99Scoping = false; std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes; ASTNode const* m_currentScope = nullptr; VariableScope* m_currentFunction = nullptr; diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 0bb5e3fe..f91eaf6e 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -43,6 +43,56 @@ bool ReferencesResolver::resolve(ASTNode const& _root) return !m_errorOccurred; } +bool ReferencesResolver::visit(Block const& _block) +{ + if (!m_resolveInsideCode) + return false; + m_experimental050Mode = _block.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); + // C99-scoped variables + if (m_experimental050Mode) + m_resolver.setScope(&_block); + return true; +} + +void ReferencesResolver::endVisit(Block const& _block) +{ + if (!m_resolveInsideCode) + return; + + // C99-scoped variables + if (m_experimental050Mode) + m_resolver.setScope(_block.scope()); +} + +bool ReferencesResolver::visit(ForStatement const& _for) +{ + if (!m_resolveInsideCode) + return false; + m_experimental050Mode = _for.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); + // C99-scoped variables + if (m_experimental050Mode) + m_resolver.setScope(&_for); + return true; +} + +void ReferencesResolver::endVisit(ForStatement const& _for) +{ + if (!m_resolveInsideCode) + return; + if (m_experimental050Mode) + m_resolver.setScope(_for.scope()); +} + +void ReferencesResolver::endVisit(VariableDeclarationStatement const& _varDeclStatement) +{ + if (!m_resolveInsideCode) + return; + if (m_experimental050Mode) + for (auto const& var: _varDeclStatement.declarations()) + if (var) + m_resolver.activateVariable(var->name()); +} + bool ReferencesResolver::visit(Identifier const& _identifier) { auto declarations = m_resolver.nameFromCurrentScope(_identifier.name()); @@ -228,8 +278,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. assembly::AsmAnalysisInfo analysisInfo; - assembly::AsmAnalyzer(analysisInfo, errorsIgnored, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); + boost::optional<Error::Type> errorTypeForLoose = m_experimental050Mode ? Error::Type::SyntaxError : Error::Type::Warning; + assembly::AsmAnalyzer(analysisInfo, errorsIgnored, EVMVersion(), errorTypeForLoose, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); return false; } diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index fef2e73f..4e8f54b5 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -57,7 +57,11 @@ public: bool resolve(ASTNode const& _root); private: - virtual bool visit(Block const&) override { return m_resolveInsideCode; } + virtual bool visit(Block const& _block) override; + virtual void endVisit(Block const& _block) override; + virtual bool visit(ForStatement const& _for) override; + virtual void endVisit(ForStatement const& _for) override; + virtual void endVisit(VariableDeclarationStatement const& _varDeclStatement) override; virtual bool visit(Identifier const& _identifier) override; virtual bool visit(ElementaryTypeName const& _typeName) override; virtual bool visit(FunctionDefinition const& _functionDefinition) override; @@ -90,6 +94,7 @@ private: std::vector<ParameterList const*> m_returnParameters; bool const m_resolveInsideCode; bool m_errorOccurred = false; + bool m_experimental050Mode = false; }; } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index bd8ee597..d4de219a 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -139,6 +139,23 @@ bool StaticAnalyzer::visit(ExpressionStatement const& _statement) bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) { + bool const v050 = m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); + + if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get())) + if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas") + { + if (v050) + m_errorReporter.typeError( + _memberAccess.location(), + "\"msg.gas\" has been deprecated in favor of \"gasleft()\"" + ); + else + m_errorReporter.warning( + _memberAccess.location(), + "\"msg.gas\" has been deprecated in favor of \"gasleft()\"" + ); + } + if (m_nonPayablePublic && !m_library) if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get())) if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "value") @@ -151,7 +168,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get())) if (type->kind() == FunctionType::Kind::BareCallCode) { - if (m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050)) + if (v050) m_errorReporter.typeError( _memberAccess.location(), "\"callcode\" has been deprecated in favour of \"delegatecall\"." diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 1dcfeb27..ddac194b 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -212,13 +212,20 @@ bool SyntaxChecker::visit(PlaceholderStatement const&) bool SyntaxChecker::visit(FunctionDefinition const& _function) { + bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); + if (_function.noVisibilitySpecified()) - m_errorReporter.warning( - _function.location(), - "No visibility specified. Defaulting to \"" + - Declaration::visibilityToString(_function.visibility()) + - "\"." - ); + { + if (v050) + m_errorReporter.syntaxError(_function.location(), "No visibility specified."); + else + m_errorReporter.warning( + _function.location(), + "No visibility specified. Defaulting to \"" + + Declaration::visibilityToString(_function.visibility()) + + "\"." + ); + } return true; } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 2914472a..bebdb9b6 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -34,6 +34,29 @@ using namespace std; using namespace dev; using namespace dev::solidity; +namespace +{ + +bool typeSupportedByOldABIEncoder(Type const& _type) +{ + if (_type.dataStoredIn(DataLocation::Storage)) + return true; + else if (_type.category() == Type::Category::Struct) + return false; + else if (_type.category() == Type::Category::Array) + { + auto const& arrayType = dynamic_cast<ArrayType const&>(_type); + auto base = arrayType.baseType(); + if (!typeSupportedByOldABIEncoder(*base)) + return false; + else if (base->category() == Type::Category::Array && base->isDynamicallySized()) + return false; + } + return true; +} + +} + bool TypeChecker::checkTypeRequirements(ASTNode const& _contract) { @@ -561,13 +584,12 @@ bool TypeChecker::visit(FunctionDefinition const& _function) m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions."); if ( _function.visibility() > FunctionDefinition::Visibility::Internal && - type(*var)->category() == Type::Category::Struct && - !type(*var)->dataStoredIn(DataLocation::Storage) && - !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) + !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) && + !typeSupportedByOldABIEncoder(*type(*var)) ) m_errorReporter.typeError( var->location(), - "Structs are only supported in the new experimental ABI encoder. " + "This type is only supported in the new experimental ABI encoder. " "Use \"pragma experimental ABIEncoderV2;\" to enable the feature." ); @@ -872,9 +894,15 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) }; solAssert(!_inlineAssembly.annotation().analysisInfo, ""); _inlineAssembly.annotation().analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); + boost::optional<Error::Type> errorTypeForLoose = + m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050) ? + Error::Type::SyntaxError : + Error::Type::Warning; assembly::AsmAnalyzer analyzer( *_inlineAssembly.annotation().analysisInfo, m_errorReporter, + m_evmVersion, + errorTypeForLoose, assembly::AsmFlavour::Loose, identifierAccess ); @@ -1830,6 +1858,20 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (exprType->category() == Type::Category::Contract) { + // Warn about using address members on contracts + bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); + for (auto const& addressMember: IntegerType(160, IntegerType::Modifier::Address).nativeMembers(nullptr)) + if (addressMember.name == memberName && *annotation.type == *addressMember.type) + { + solAssert(!v050, "Address member still present on contract in v0.5.0."); + m_errorReporter.warning( + _memberAccess.location(), + "Using contract member \"" + memberName +"\" inherited from the address type is deprecated." + + " Convert the contract to \"address\" type to access the member." + ); + } + + // Warn about using send or transfer with a non-payable fallback function. if (auto callType = dynamic_cast<FunctionType const*>(type(_memberAccess).get())) { auto kind = callType->kind(); @@ -2021,6 +2063,8 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) void TypeChecker::endVisit(Literal const& _literal) { + bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); + if (_literal.looksLikeAddress()) { if (_literal.passesAddressChecksum()) @@ -2034,6 +2078,21 @@ void TypeChecker::endVisit(Literal const& _literal) "For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals" ); } + if (_literal.isHexNumber() && _literal.subDenomination() != Literal::SubDenomination::None) + { + if (v050) + m_errorReporter.fatalTypeError( + _literal.location(), + "Hexadecimal numbers cannot be used with unit denominations. " + "You can use an expression of the form \"0x1234 * 1 day\" instead." + ); + else + m_errorReporter.warning( + _literal.location(), + "Hexadecimal numbers with unit denominations are deprecated. " + "You can use an expression of the form \"0x1234 * 1 day\" instead." + ); + } if (!_literal.annotation().type) _literal.annotation().type = Type::forLiteral(_literal); diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 16796b63..2ba31232 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -22,6 +22,8 @@ #pragma once +#include <libsolidity/interface/EVMVersion.h> + #include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> #include <libsolidity/ast/ASTForward.h> @@ -43,7 +45,10 @@ class TypeChecker: private ASTConstVisitor { public: /// @param _errorReporter provides the error logging functionality. - TypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + TypeChecker(EVMVersion _evmVersion, ErrorReporter& _errorReporter): + m_evmVersion(_evmVersion), + m_errorReporter(_errorReporter) + {} /// Performs type checking on the given contract and all of its sub-nodes. /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings @@ -132,6 +137,8 @@ private: ContractDefinition const* m_scope = nullptr; + EVMVersion m_evmVersion; + /// Flag indicating whether we are currently inside an EmitStatement. bool m_insideEmitStatement = false; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 8da6964e..27220b1f 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -96,20 +96,6 @@ set<SourceUnit const*> SourceUnit::referencedSourceUnits(bool _recurse, set<Sour return sourceUnits; } -SourceUnit const& Declaration::sourceUnit() const -{ - solAssert(!!m_scope, ""); - ASTNode const* scope = m_scope; - while (dynamic_cast<Declaration const*>(scope) && dynamic_cast<Declaration const*>(scope)->m_scope) - scope = dynamic_cast<Declaration const*>(scope)->m_scope; - return dynamic_cast<SourceUnit const&>(*scope); -} - -string Declaration::sourceUnitName() const -{ - return sourceUnit().annotation().path; -} - ImportAnnotation& ImportDirective::annotation() const { if (!m_annotation) @@ -408,12 +394,36 @@ UserDefinedTypeNameAnnotation& UserDefinedTypeName::annotation() const return dynamic_cast<UserDefinedTypeNameAnnotation&>(*m_annotation); } +SourceUnit const& Scopable::sourceUnit() const +{ + ASTNode const* s = scope(); + solAssert(s, ""); + // will not always be a declaratoion + while (dynamic_cast<Scopable const*>(s) && dynamic_cast<Scopable const*>(s)->scope()) + s = dynamic_cast<Scopable const*>(s)->scope(); + return dynamic_cast<SourceUnit const&>(*s); +} + +string Scopable::sourceUnitName() const +{ + return sourceUnit().annotation().path; +} + bool VariableDeclaration::isLValue() const { // External function parameters and constant declared variables are Read-Only return !isExternalCallableParameter() && !m_isConstant; } +bool VariableDeclaration::isLocalVariable() const +{ + auto s = scope(); + return + dynamic_cast<CallableDeclaration const*>(s) || + dynamic_cast<Block const*>(s) || + dynamic_cast<ForStatement const*>(s); +} + bool VariableDeclaration::isCallableParameter() const { auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()); @@ -459,8 +469,7 @@ bool VariableDeclaration::isExternalCallableParameter() const bool VariableDeclaration::canHaveAutoType() const { - auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()); - return (!!callable && !isCallableParameter()); + return isLocalVariable() && !isCallableParameter(); } TypePointer VariableDeclaration::type() const diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index c0d55aec..a25df64b 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -140,9 +140,32 @@ private: }; /** + * Abstract class that is added to each AST node that is stored inside a scope + * (including scopes). + */ +class Scopable +{ +public: + /// @returns the scope this declaration resides in. Can be nullptr if it is the global scope. + /// Available only after name and type resolution step. + ASTNode const* scope() const { return m_scope; } + void setScope(ASTNode const* _scope) { m_scope = _scope; } + + /// @returns the source unit this scopable is present in. + SourceUnit const& sourceUnit() const; + + /// @returns the source name this scopable is present in. + /// Can be combined with annotation().canonicalName (if present) to form a globally unique name. + std::string sourceUnitName() const; + +protected: + ASTNode const* m_scope = nullptr; +}; + +/** * Abstract AST class for a declaration (contract, function, struct, variable, import directive). */ -class Declaration: public ASTNode +class Declaration: public ASTNode, public Scopable { public: /// Visibility ordered from restricted to unrestricted. @@ -171,7 +194,7 @@ public: ASTPointer<ASTString> const& _name, Visibility _visibility = Visibility::Default ): - ASTNode(_location), m_name(_name), m_visibility(_visibility), m_scope(nullptr) {} + ASTNode(_location), m_name(_name), m_visibility(_visibility) {} /// @returns the declared name. ASTString const& name() const { return *m_name; } @@ -180,18 +203,8 @@ public: bool isPublic() const { return visibility() >= Visibility::Public; } virtual bool isVisibleInContract() const { return visibility() != Visibility::External; } bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; } + bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; } - /// @returns the scope this declaration resides in. Can be nullptr if it is the global scope. - /// Available only after name and type resolution step. - ASTNode const* scope() const { return m_scope; } - void setScope(ASTNode const* _scope) { m_scope = _scope; } - - /// @returns the source unit this declaration is present in. - SourceUnit const& sourceUnit() const; - - /// @returns the source name this declaration is present in. - /// Can be combined with annotation().canonicalName to form a globally unique name. - std::string sourceUnitName() const; std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); } virtual bool isLValue() const { return false; } @@ -213,7 +226,6 @@ protected: private: ASTPointer<ASTString> m_name; Visibility m_visibility; - ASTNode const* m_scope; }; /** @@ -289,6 +301,8 @@ private: /** * Abstract class that is added to each AST node that can store local variables. + * Local variables in functions are always added to functions, even though they are not + * in scope for the whole function. */ class VariableScope { @@ -662,7 +676,7 @@ public: virtual bool isLValue() const override; virtual bool isPartOfExternalInterface() const override { return isPublic(); } - bool isLocalVariable() const { return !!dynamic_cast<CallableDeclaration const*>(scope()); } + bool isLocalVariable() const; /// @returns true if this variable is a parameter or return parameter of a function. bool isCallableParameter() const; /// @returns true if this variable is a return parameter of a function. @@ -1004,7 +1018,7 @@ private: /** * Brace-enclosed block containing zero or more statements. */ -class Block: public Statement +class Block: public Statement, public Scopable { public: Block( @@ -1111,7 +1125,7 @@ private: /** * For loop statement */ -class ForStatement: public BreakableStatement +class ForStatement: public BreakableStatement, public Scopable { public: ForStatement( diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h index 3ecfac7b..30ea7ec5 100644 --- a/libsolidity/ast/ExperimentalFeatures.h +++ b/libsolidity/ast/ExperimentalFeatures.h @@ -29,8 +29,8 @@ namespace solidity enum class ExperimentalFeature { - SMTChecker, ABIEncoderV2, // new ABI encoder that makes use of JULIA + SMTChecker, V050, // v0.5.0 breaking changes Test, TestOnlyAnalysis @@ -40,12 +40,13 @@ static const std::map<ExperimentalFeature, bool> ExperimentalFeatureOnlyAnalysis { { ExperimentalFeature::SMTChecker, true }, { ExperimentalFeature::TestOnlyAnalysis, true }, + { ExperimentalFeature::V050, true } }; static const std::map<std::string, ExperimentalFeature> ExperimentalFeatureNames = { - { "SMTChecker", ExperimentalFeature::SMTChecker }, { "ABIEncoderV2", ExperimentalFeature::ABIEncoderV2 }, + { "SMTChecker", ExperimentalFeature::SMTChecker }, { "v0.5.0", ExperimentalFeature::V050 }, { "__test", ExperimentalFeature::Test }, { "__testOnlyAnalysis", ExperimentalFeature::TestOnlyAnalysis }, diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 771ae643..b2881bea 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -304,7 +304,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition ); for (FunctionDefinition const* function: library.definedFunctions()) { - if (!function->isVisibleInDerivedContracts() || seenFunctions.count(function)) + if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function)) continue; seenFunctions.insert(function); FunctionType funType(*function, false); @@ -1589,8 +1589,6 @@ bool ArrayType::canBeUsedExternally(bool _inLibrary) const return true; else if (!m_baseType->canBeUsedExternally(_inLibrary)) return false; - else if (m_baseType->category() == Category::Array && m_baseType->isDynamicallySized()) - return false; else return true; } @@ -2346,6 +2344,7 @@ string FunctionType::richIdentifier() const case Kind::Log2: id += "log2"; break; case Kind::Log3: id += "log3"; break; case Kind::Log4: id += "log4"; break; + case Kind::GasLeft: id += "gasleft"; break; case Kind::Event: id += "event"; break; case Kind::SetGas: id += "setgas"; break; case Kind::SetValue: id += "setvalue"; break; @@ -2876,7 +2875,7 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current } if (contract.isLibrary()) for (FunctionDefinition const* function: contract.definedFunctions()) - if (function->isVisibleInDerivedContracts()) + if (function->isVisibleAsLibraryMember()) members.push_back(MemberList::Member( function->name(), FunctionType(*function).asMemberFunction(true), diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 7985521e..c20a025f 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -902,7 +902,8 @@ public: ByteArrayPush, ///< .push() to a dynamically sized byte array in storage ObjectCreation, ///< array creation using new Assert, ///< assert() - Require ///< require() + Require, ///< require() + GasLeft ///< gasleft() }; virtual Category category() const override { return Category::Function; } diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index ce8cbb5f..4703fc1f 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -741,10 +741,10 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const if (_type.isByteArray()) // For a "long" byte array, store length as 2*length+1 _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; - _context<< Instruction::DUP4 << Instruction::SSTORE; + _context << Instruction::DUP4 << Instruction::SSTORE; // skip if size is not reduced _context << Instruction::DUP2 << Instruction::DUP2 - << Instruction::ISZERO << Instruction::GT; + << Instruction::GT << Instruction::ISZERO; _context.appendConditionalJumpTo(resizeEnd); // size reduced, clear the end of the array diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 06654486..f6865d75 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -22,22 +22,25 @@ #pragma once -#include <ostream> -#include <functional> #include <libsolidity/codegen/CompilerContext.h> +#include <libsolidity/interface/EVMVersion.h> + #include <libevmasm/Assembly.h> +#include <ostream> +#include <functional> + namespace dev { namespace solidity { class Compiler { public: - explicit Compiler(bool _optimize = false, unsigned _runs = 200): + explicit Compiler(EVMVersion _evmVersion = EVMVersion{}, bool _optimize = false, unsigned _runs = 200): m_optimize(_optimize), m_optimizeRuns(_runs), - m_runtimeContext(), - m_context(&m_runtimeContext) + m_runtimeContext(_evmVersion), + m_context(_evmVersion, &m_runtimeContext) { } /// Compiles a contract. diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 0198a107..47333046 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -193,14 +193,22 @@ Declaration const* CompilerContext::nextFunctionToCompile() const return m_functionCompilationQueue.nextFunctionToCompile(); } -ModifierDefinition const& CompilerContext::functionModifier(string const& _name) const +ModifierDefinition const& CompilerContext::resolveVirtualFunctionModifier( + ModifierDefinition const& _modifier +) const { + // Libraries do not allow inheritance and their functions can be inlined, so we should not + // search the inheritance hierarchy (which will be the wrong one in case the function + // is inlined). + if (auto scope = dynamic_cast<ContractDefinition const*>(_modifier.scope())) + if (scope->isLibrary()) + return _modifier; solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); for (ContractDefinition const* contract: m_inheritanceHierarchy) for (ModifierDefinition const* modifier: contract->functionModifiers()) - if (modifier->name() == _name) + if (modifier->name() == _modifier.name()) return *modifier; - solAssert(false, "Function modifier " + _name + " not found."); + solAssert(false, "Function modifier " + _modifier.name() + " not found in inheritance hierarchy."); } unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const @@ -329,6 +337,8 @@ void CompilerContext::appendInlineAssembly( analyzerResult = assembly::AsmAnalyzer( analysisInfo, errorReporter, + m_evmVersion, + boost::none, assembly::AsmFlavour::Strict, identifierAccess.resolve ).analyze(*parserResult); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index a155a3a5..7b663277 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -24,6 +24,8 @@ #include <libsolidity/codegen/ABIFunctions.h> +#include <libsolidity/interface/EVMVersion.h> + #include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> @@ -50,14 +52,17 @@ namespace solidity { class CompilerContext { public: - explicit CompilerContext(CompilerContext* _runtimeContext = nullptr): + explicit CompilerContext(EVMVersion _evmVersion = EVMVersion{}, CompilerContext* _runtimeContext = nullptr): m_asm(std::make_shared<eth::Assembly>()), + m_evmVersion(_evmVersion), m_runtimeContext(_runtimeContext) { if (m_runtimeContext) m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); } + EVMVersion const& evmVersion() const { return m_evmVersion; } + /// Update currently enabled set of experimental features. void setExperimentalFeatures(std::set<ExperimentalFeature> const& _features) { m_experimentalFeatures = _features; } /// @returns true if the given feature is enabled. @@ -125,7 +130,7 @@ public: void appendMissingLowLevelFunctions(); ABIFunctions& abiFunctions() { return m_abiFunctions; } - ModifierDefinition const& functionModifier(std::string const& _name) const; + ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const; /// Returns the distance of the given local variable from the bottom of the stack (of the current function). unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const; /// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns @@ -204,7 +209,7 @@ public: void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); } /// Run optimisation step. - void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, true, _runs); } + void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, m_evmVersion, true, _runs); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. CompilerContext* runtimeContext() { return m_runtimeContext; } @@ -287,6 +292,8 @@ private: } m_functionCompilationQueue; eth::AssemblyPointer m_asm; + /// Version of the EVM to compile against. + EVMVersion m_evmVersion; /// Activated experimental features. std::set<ExperimentalFeature> m_experimentalFeatures; /// Other already compiled contracts to be used in contract creation calls. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index ebb718a5..95d6c8b5 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -1002,7 +1002,10 @@ void ContractCompiler::appendModifierOrFunctionCode() appendModifierOrFunctionCode(); else { - ModifierDefinition const& modifier = m_context.functionModifier(modifierInvocation->name()->name()); + ModifierDefinition const& nonVirtualModifier = dynamic_cast<ModifierDefinition const&>( + *modifierInvocation->name()->annotation().referencedDeclaration + ); + ModifierDefinition const& modifier = m_context.resolveVirtualFunctionModifier(nonVirtualModifier); CompilerContext::LocationSetter locationSetter(m_context, modifier); solAssert(modifier.parameters().size() == modifierInvocation->arguments().size(), ""); for (unsigned i = 0; i < modifier.parameters().size(); ++i) @@ -1059,7 +1062,7 @@ void ContractCompiler::compileExpression(Expression const& _expression, TypePoin CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); } -eth::AssemblyPointer ContractCompiler::cloneRuntime() +eth::AssemblyPointer ContractCompiler::cloneRuntime() const { eth::Assembly a; a << Instruction::CALLDATASIZE; @@ -1070,7 +1073,7 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime() // this is the address which has to be substituted by the linker. //@todo implement as special "marker" AssemblyItem. a << u256("0xcafecafecafecafecafecafecafecafecafecafe"); - a << u256(eth::GasCosts::callGas + 10) << Instruction::GAS << Instruction::SUB; + a << u256(eth::GasCosts::callGas(m_context.evmVersion()) + 10) << Instruction::GAS << Instruction::SUB; a << Instruction::DELEGATECALL; //Propagate error condition (if DELEGATECALL pushes 0 on stack). a << Instruction::ISZERO; diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index d698dc71..8559ea58 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -43,7 +43,7 @@ public: m_runtimeCompiler(_runtimeCompiler), m_context(_context) { - m_context = CompilerContext(_runtimeCompiler ? &_runtimeCompiler->m_context : nullptr); + m_context = CompilerContext(_context.evmVersion(), _runtimeCompiler ? &_runtimeCompiler->m_context : nullptr); } void compileContract( @@ -125,7 +125,7 @@ private: void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); /// @returns the runtime assembly for clone contracts. - static eth::AssemblyPointer cloneRuntime(); + eth::AssemblyPointer cloneRuntime() const; bool const m_optimise; /// Pointer to the runtime compiler in case this is a creation compiler. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 61920592..f50628ff 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -906,6 +906,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << success; break; } + case FunctionType::Kind::GasLeft: + m_context << Instruction::GAS; + break; default: solAssert(false, "Invalid function type."); } @@ -1144,6 +1147,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else if (member == "sig") m_context << u256(0) << Instruction::CALLDATALOAD << (u256(0xffffffff) << (256 - 32)) << Instruction::AND; + else if (member == "blockhash") + { + } else solAssert(false, "Unknown magic member."); break; @@ -1607,6 +1613,10 @@ void ExpressionCompiler::appendExternalFunctionCall( bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall; bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; + bool useStaticCall = + _functionType.stateMutability() <= StateMutability::View && + m_context.experimentalFeatureActive(ExperimentalFeature::V050) && + m_context.evmVersion().hasStaticCall(); unsigned retSize = 0; if (returnSuccessCondition) @@ -1671,16 +1681,19 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().storeFreeMemoryPointer(); } - // Touch the end of the output area so that we do not pay for memory resize during the call - // (which we would have to subtract from the gas left) - // We could also just use MLOAD; POP right before the gas calculation, but the optimizer - // would remove that, so we use MSTORE here. - if (!_functionType.gasSet() && retSize > 0) + if (!m_context.evmVersion().canOverchargeGasForCall()) { - m_context << u256(0); - utils().fetchFreeMemoryPointer(); - // This touches too much, but that way we save some rounding arithmetics - m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE; + // Touch the end of the output area so that we do not pay for memory resize during the call + // (which we would have to subtract from the gas left) + // We could also just use MLOAD; POP right before the gas calculation, but the optimizer + // would remove that, so we use MSTORE here. + if (!_functionType.gasSet() && retSize > 0) + { + m_context << u256(0); + utils().fetchFreeMemoryPointer(); + // This touches too much, but that way we save some rounding arithmetics + m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE; + } } // Copy function identifier to memory. @@ -1732,6 +1745,8 @@ void ExpressionCompiler::appendExternalFunctionCall( // [value,] addr, gas (stack top) if (isDelegateCall) solAssert(!_functionType.valueSet(), "Value set for delegatecall"); + else if (useStaticCall) + solAssert(!_functionType.valueSet(), "Value set for staticcall"); else if (_functionType.valueSet()) m_context << dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos)); else @@ -1749,24 +1764,27 @@ void ExpressionCompiler::appendExternalFunctionCall( if (_functionType.gasSet()) m_context << dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); - else if (m_context.experimentalFeatureActive(ExperimentalFeature::V050)) + else if (m_context.evmVersion().canOverchargeGasForCall()) // Send all gas (requires tangerine whistle EVM) m_context << Instruction::GAS; else { // send all gas except the amount needed to execute "SUB" and "CALL" // @todo this retains too much gas for now, needs to be fine-tuned. - u256 gasNeededByCaller = eth::GasCosts::callGas + 10; + u256 gasNeededByCaller = eth::GasCosts::callGas(m_context.evmVersion()) + 10; if (_functionType.valueSet()) gasNeededByCaller += eth::GasCosts::callValueTransferGas; if (!existenceChecked) gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know m_context << gasNeededByCaller << Instruction::GAS << Instruction::SUB; } + // Order is important here, STATICCALL might overlap with DELEGATECALL. if (isDelegateCall) m_context << Instruction::DELEGATECALL; else if (isCallCode) m_context << Instruction::CALLCODE; + else if (useStaticCall) + m_context << Instruction::STATICCALL; else m_context << Instruction::CALL; diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index a64024b3..8f4abdc2 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -23,6 +23,8 @@ #include <libsolidity/formal/SMTLib2Interface.h> #endif +#include <libsolidity/formal/SSAVariable.h> +#include <libsolidity/formal/SymbolicIntVariable.h> #include <libsolidity/formal/VariableUsage.h> #include <libsolidity/interface/ErrorReporter.h> @@ -69,8 +71,7 @@ bool SMTChecker::visit(FunctionDefinition const& _function) // We only handle local variables, so we clear at the beginning of the function. // If we add storage variables, those should be cleared differently. m_interface->reset(); - m_currentSequenceCounter.clear(); - m_nextFreeSequenceCounter.clear(); + m_variables.clear(); m_pathConditions.clear(); m_conditionalExecutionHappened = false; initializeLocalVariables(_function); @@ -91,14 +92,18 @@ bool SMTChecker::visit(IfStatement const& _node) checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE."); - auto countersEndFalse = m_currentSequenceCounter; auto countersEndTrue = visitBranch(_node.trueStatement(), expr(_node.condition())); vector<Declaration const*> touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement()); + decltype(countersEndTrue) countersEndFalse; if (_node.falseStatement()) { countersEndFalse = visitBranch(*_node.falseStatement(), !expr(_node.condition())); touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement()); } + else + { + countersEndFalse = m_variables; + } mergeVariables(touchedVariables, expr(_node.condition()), countersEndTrue, countersEndFalse); @@ -152,7 +157,7 @@ bool SMTChecker::visit(ForStatement const& _node) checkBooleanNotConstant(*_node.condition(), "For loop condition is always $VALUE."); } - VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter; + VariableSequenceCounters sequenceCountersStart = m_variables; m_interface->push(); if (_node.condition()) m_interface->addAssertion(expr(*_node.condition())); @@ -163,7 +168,7 @@ bool SMTChecker::visit(ForStatement const& _node) m_interface->pop(); m_conditionalExecutionHappened = true; - m_currentSequenceCounter = sequenceCountersStart; + std::swap(sequenceCountersStart, m_variables); resetVariables(touchedVariables); @@ -200,7 +205,7 @@ void SMTChecker::endVisit(Assignment const& _assignment) _assignment.location(), "Assertion checker does not yet implement compound assignment." ); - else if (_assignment.annotation().type->category() != Type::Category::Integer) + else if (!SSAVariable::isSupportedType(_assignment.annotation().type->category())) m_errorReporter.warning( _assignment.location(), "Assertion checker does not yet implement type " + _assignment.annotation().type->toString() @@ -240,14 +245,14 @@ void SMTChecker::endVisit(TupleExpression const& _tuple) void SMTChecker::checkUnderOverflow(smt::Expression _value, IntegerType const& _type, SourceLocation const& _location) { checkCondition( - _value < minValue(_type), + _value < SymbolicIntVariable::minValue(_type), _location, "Underflow (resulting value less than " + formatNumber(_type.minValue()) + ")", "value", &_value ); checkCondition( - _value > maxValue(_type), + _value > SymbolicIntVariable::maxValue(_type), _location, "Overflow (resulting value larger than " + formatNumber(_type.maxValue()) + ")", "value", @@ -261,14 +266,15 @@ void SMTChecker::endVisit(UnaryOperation const& _op) { case Token::Not: // ! { - solAssert(_op.annotation().type->category() == Type::Category::Bool, ""); + solAssert(SSAVariable::isBool(_op.annotation().type->category()), ""); defineExpr(_op, !expr(_op.subExpression())); break; } case Token::Inc: // ++ (pre- or postfix) case Token::Dec: // -- (pre- or postfix) { - solAssert(_op.annotation().type->category() == Type::Category::Integer, ""); + + solAssert(SSAVariable::isInteger(_op.annotation().type->category()), ""); solAssert(_op.subExpression().annotation().lValueRequested, ""); if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression())) { @@ -365,7 +371,7 @@ void SMTChecker::endVisit(Identifier const& _identifier) { // Will be translated as part of the node that requested the lvalue. } - else if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get())) + else if (SSAVariable::isSupportedType(_identifier.annotation().type->category())) defineExpr(_identifier, currentValue(*decl)); else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) { @@ -439,21 +445,37 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op) void SMTChecker::compareOperation(BinaryOperation const& _op) { solAssert(_op.annotation().commonType, ""); - if (_op.annotation().commonType->category() == Type::Category::Integer) + if (SSAVariable::isSupportedType(_op.annotation().commonType->category())) { smt::Expression left(expr(_op.leftExpression())); smt::Expression right(expr(_op.rightExpression())); Token::Value op = _op.getOperator(); - smt::Expression value = ( - op == Token::Equal ? (left == right) : - op == Token::NotEqual ? (left != right) : - op == Token::LessThan ? (left < right) : - op == Token::LessThanOrEqual ? (left <= right) : - op == Token::GreaterThan ? (left > right) : - /*op == Token::GreaterThanOrEqual*/ (left >= right) - ); + shared_ptr<smt::Expression> value; + if (SSAVariable::isInteger(_op.annotation().commonType->category())) + { + value = make_shared<smt::Expression>( + op == Token::Equal ? (left == right) : + op == Token::NotEqual ? (left != right) : + op == Token::LessThan ? (left < right) : + op == Token::LessThanOrEqual ? (left <= right) : + op == Token::GreaterThan ? (left > right) : + /*op == Token::GreaterThanOrEqual*/ (left >= right) + ); + } + else // Bool + { + solAssert(SSAVariable::isBool(_op.annotation().commonType->category()), ""); + value = make_shared<smt::Expression>( + op == Token::Equal ? (left == right) : + op == Token::NotEqual ? (left != right) : + op == Token::LessThan ? (!left && right) : + op == Token::LessThanOrEqual ? (!left || right) : + op == Token::GreaterThan ? (left && !right) : + /*op == Token::GreaterThanOrEqual*/ (left || !right) + ); + } // TODO: check that other values for op are not possible. - defineExpr(_op, value); + defineExpr(_op, *value); } else m_errorReporter.warning( @@ -514,7 +536,7 @@ SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _s SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) { - VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter; + VariableSequenceCounters beforeVars = m_variables; if (_condition) pushPathCondition(*_condition); @@ -523,8 +545,9 @@ SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _s popPathCondition(); m_conditionalExecutionHappened = true; - std::swap(sequenceCountersStart, m_currentSequenceCounter); - return sequenceCountersStart; + std::swap(m_variables, beforeVars); + + return beforeVars; } void SMTChecker::checkCondition( @@ -709,8 +732,8 @@ void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, sm set<Declaration const*> uniqueVars(_variables.begin(), _variables.end()); for (auto const* decl: uniqueVars) { - int trueCounter = _countersEndTrue.at(decl); - int falseCounter = _countersEndFalse.at(decl); + int trueCounter = _countersEndTrue.at(decl).index(); + int falseCounter = _countersEndFalse.at(decl).index(); solAssert(trueCounter != falseCounter, ""); m_interface->addAssertion(newValue(*decl) == smt::Expression::ite( _condition, @@ -722,14 +745,10 @@ void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, sm bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) { - if (dynamic_cast<IntegerType const*>(_varDecl.type().get())) + if (SSAVariable::isSupportedType(_varDecl.type()->category())) { - solAssert(m_currentSequenceCounter.count(&_varDecl) == 0, ""); - solAssert(m_nextFreeSequenceCounter.count(&_varDecl) == 0, ""); solAssert(m_variables.count(&_varDecl) == 0, ""); - m_currentSequenceCounter[&_varDecl] = 0; - m_nextFreeSequenceCounter[&_varDecl] = 1; - m_variables.emplace(&_varDecl, m_interface->newFunction(uniqueSymbol(_varDecl), smt::Sort::Int, smt::Sort::Int)); + m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface)); return true; } else @@ -742,11 +761,6 @@ bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) } } -string SMTChecker::uniqueSymbol(Declaration const& _decl) -{ - return _decl.name() + "_" + to_string(_decl.id()); -} - string SMTChecker::uniqueSymbol(Expression const& _expr) { return "expr_" + to_string(_expr.id()); @@ -754,48 +768,38 @@ string SMTChecker::uniqueSymbol(Expression const& _expr) bool SMTChecker::knownVariable(Declaration const& _decl) { - return m_currentSequenceCounter.count(&_decl); + return m_variables.count(&_decl); } smt::Expression SMTChecker::currentValue(Declaration const& _decl) { - solAssert(m_currentSequenceCounter.count(&_decl), ""); - return valueAtSequence(_decl, m_currentSequenceCounter.at(&_decl)); + solAssert(knownVariable(_decl), ""); + return m_variables.at(&_decl)(); } -smt::Expression SMTChecker::valueAtSequence(const Declaration& _decl, int _sequence) +smt::Expression SMTChecker::valueAtSequence(Declaration const& _decl, int _sequence) { - return var(_decl)(_sequence); + solAssert(knownVariable(_decl), ""); + return m_variables.at(&_decl)(_sequence); } smt::Expression SMTChecker::newValue(Declaration const& _decl) { - solAssert(m_nextFreeSequenceCounter.count(&_decl), ""); - m_currentSequenceCounter[&_decl] = m_nextFreeSequenceCounter[&_decl]++; - return currentValue(_decl); + solAssert(knownVariable(_decl), ""); + ++m_variables.at(&_decl); + return m_variables.at(&_decl)(); } void SMTChecker::setZeroValue(Declaration const& _decl) { - solAssert(_decl.type()->category() == Type::Category::Integer, ""); - m_interface->addAssertion(currentValue(_decl) == 0); + solAssert(knownVariable(_decl), ""); + m_variables.at(&_decl).setZeroValue(); } void SMTChecker::setUnknownValue(Declaration const& _decl) { - auto const& intType = dynamic_cast<IntegerType const&>(*_decl.type()); - m_interface->addAssertion(currentValue(_decl) >= minValue(intType)); - m_interface->addAssertion(currentValue(_decl) <= maxValue(intType)); -} - -smt::Expression SMTChecker::minValue(IntegerType const& _t) -{ - return smt::Expression(_t.minValue()); -} - -smt::Expression SMTChecker::maxValue(IntegerType const& _t) -{ - return smt::Expression(_t.maxValue()); + solAssert(knownVariable(_decl), ""); + m_variables.at(&_decl).setUnknownValue(); } smt::Expression SMTChecker::expr(Expression const& _e) @@ -842,12 +846,6 @@ void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value) m_interface->addAssertion(expr(_e) == _value); } -smt::Expression SMTChecker::var(Declaration const& _decl) -{ - solAssert(m_variables.count(&_decl), ""); - return m_variables.at(&_decl); -} - void SMTChecker::popPathCondition() { solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty."); diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index b57f0f96..7e7996cf 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -20,6 +20,8 @@ #include <libsolidity/formal/SolverInterface.h> +#include <libsolidity/formal/SSAVariable.h> + #include <libsolidity/ast/ASTVisitor.h> #include <libsolidity/interface/ReadFile.h> @@ -76,7 +78,7 @@ private: void assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location); /// Maps a variable to an SSA index. - using VariableSequenceCounters = std::map<Declaration const*, int>; + using VariableSequenceCounters = std::map<Declaration const*, SSAVariable>; /// Visits the branch given by the statement, pushes and pops the current path conditions. /// @param _condition if present, asserts that this condition is true within the branch. @@ -118,7 +120,6 @@ private: /// This fails if the type is not supported. bool createVariable(VariableDeclaration const& _varDecl); - static std::string uniqueSymbol(Declaration const& _decl); static std::string uniqueSymbol(Expression const& _expr); /// @returns true if _delc is a variable that is known at the current point, i.e. @@ -139,18 +140,12 @@ private: /// Resets the variable to an unknown value (in its range). void setUnknownValue(Declaration const& decl); - static smt::Expression minValue(IntegerType const& _t); - static smt::Expression maxValue(IntegerType const& _t); - /// Returns the expression corresponding to the AST node. Throws if the expression does not exist. smt::Expression expr(Expression const& _e); /// Creates the expression (value can be arbitrary) void createExpr(Expression const& _e); /// Creates the expression and sets its value. void defineExpr(Expression const& _e, smt::Expression _value); - /// Returns the function declaration corresponding to the given variable. - /// The function takes one argument which is the "sequence number". - smt::Expression var(Declaration const& _decl); /// Adds a new path condition void pushPathCondition(smt::Expression const& _e); @@ -166,10 +161,8 @@ private: std::shared_ptr<smt::SolverInterface> m_interface; std::shared_ptr<VariableUsage> m_variableUsage; bool m_conditionalExecutionHappened = false; - std::map<Declaration const*, int> m_currentSequenceCounter; - std::map<Declaration const*, int> m_nextFreeSequenceCounter; std::map<Expression const*, smt::Expression> m_expressions; - std::map<Declaration const*, smt::Expression> m_variables; + std::map<Declaration const*, SSAVariable> m_variables; std::vector<smt::Expression> m_pathConditions; ErrorReporter& m_errorReporter; diff --git a/libsolidity/formal/SSAVariable.cpp b/libsolidity/formal/SSAVariable.cpp new file mode 100644 index 00000000..f3213e03 --- /dev/null +++ b/libsolidity/formal/SSAVariable.cpp @@ -0,0 +1,86 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/formal/SSAVariable.h> + +#include <libsolidity/formal/SymbolicBoolVariable.h> +#include <libsolidity/formal/SymbolicIntVariable.h> + +#include <libsolidity/ast/AST.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +SSAVariable::SSAVariable( + Declaration const& _decl, + smt::SolverInterface& _interface +) +{ + resetIndex(); + + if (isInteger(_decl.type()->category())) + m_symbolicVar = make_shared<SymbolicIntVariable>(_decl, _interface); + else if (isBool(_decl.type()->category())) + m_symbolicVar = make_shared<SymbolicBoolVariable>(_decl, _interface); + else + { + solAssert(false, ""); + } +} + +bool SSAVariable::isSupportedType(Type::Category _category) +{ + return isInteger(_category) || isBool(_category); +} + +bool SSAVariable::isInteger(Type::Category _category) +{ + return _category == Type::Category::Integer; +} + +bool SSAVariable::isBool(Type::Category _category) +{ + return _category == Type::Category::Bool; +} + +void SSAVariable::resetIndex() +{ + m_currentSequenceCounter = 0; + m_nextFreeSequenceCounter.reset (new int); + *m_nextFreeSequenceCounter = 1; +} + +int SSAVariable::index() const +{ + return m_currentSequenceCounter; +} + +int SSAVariable::next() const +{ + return *m_nextFreeSequenceCounter; +} + +void SSAVariable::setZeroValue() +{ + m_symbolicVar->setZeroValue(index()); +} + +void SSAVariable::setUnknownValue() +{ + m_symbolicVar->setUnknownValue(index()); +} diff --git a/libsolidity/formal/SSAVariable.h b/libsolidity/formal/SSAVariable.h new file mode 100644 index 00000000..bf5dae3b --- /dev/null +++ b/libsolidity/formal/SSAVariable.h @@ -0,0 +1,90 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/formal/SymbolicVariable.h> + +#include <memory> + +namespace dev +{ +namespace solidity +{ + +class Declaration; + +/** + * This class represents the SSA representation of a program variable. + */ +class SSAVariable +{ +public: + /// @param _decl Used to determine the type and forwarded to the symbolic var. + /// @param _interface Forwarded to the symbolic var such that it can give constraints to the solver. + SSAVariable( + Declaration const& _decl, + smt::SolverInterface& _interface + ); + + void resetIndex(); + + /// This function returns the current index of this SSA variable. + int index() const; + /// This function returns the next free index of this SSA variable. + int next() const; + + int operator++() + { + return m_currentSequenceCounter = (*m_nextFreeSequenceCounter)++; + } + + smt::Expression operator()() const + { + return valueAtSequence(index()); + } + + smt::Expression operator()(int _seq) const + { + return valueAtSequence(_seq); + } + + /// These two functions forward the call to the symbolic var + /// which generates the constraints according to the type. + void setZeroValue(); + void setUnknownValue(); + + /// So far Int and Bool are supported. + static bool isSupportedType(Type::Category _category); + static bool isInteger(Type::Category _category); + static bool isBool(Type::Category _category); + +private: + smt::Expression valueAtSequence(int _seq) const + { + return (*m_symbolicVar)(_seq); + } + + std::shared_ptr<SymbolicVariable> m_symbolicVar = nullptr; + int m_currentSequenceCounter; + /// The next free sequence counter is a shared pointer because we want + /// the copy and the copied to share it. + std::shared_ptr<int> m_nextFreeSequenceCounter; +}; + +} +} diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 88487310..0bdebb6c 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -46,7 +46,8 @@ enum class Sort { Int, Bool, - IntIntFun // Function of one Int returning a single Int + IntIntFun, // Function of one Int returning a single Int + IntBoolFun // Function of one Int returning a single Bool }; /// C++ representation of an SMTLIB2 expression. @@ -132,10 +133,22 @@ public: Expression operator()(Expression _a) const { solAssert( - sort == Sort::IntIntFun && arguments.empty(), + arguments.empty(), "Attempted function application to non-function." ); - return Expression(name, _a, Sort::Int); + switch (sort) + { + case Sort::IntIntFun: + return Expression(name, _a, Sort::Int); + case Sort::IntBoolFun: + return Expression(name, _a, Sort::Bool); + default: + solAssert( + false, + "Attempted function application to invalid type." + ); + break; + } } std::string const name; @@ -167,9 +180,18 @@ public: virtual Expression newFunction(std::string _name, Sort _domain, Sort _codomain) { - solAssert(_domain == Sort::Int && _codomain == Sort::Int, "Function sort not supported."); + solAssert(_domain == Sort::Int, "Function sort not supported."); // Subclasses should do something here - return Expression(std::move(_name), {}, Sort::IntIntFun); + switch (_codomain) + { + case Sort::Int: + return Expression(std::move(_name), {}, Sort::IntIntFun); + case Sort::Bool: + return Expression(std::move(_name), {}, Sort::IntBoolFun); + default: + solAssert(false, "Function sort not supported."); + break; + } } virtual Expression newInteger(std::string _name) { diff --git a/libsolidity/formal/SymbolicBoolVariable.cpp b/libsolidity/formal/SymbolicBoolVariable.cpp new file mode 100644 index 00000000..e5c56e46 --- /dev/null +++ b/libsolidity/formal/SymbolicBoolVariable.cpp @@ -0,0 +1,43 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/formal/SymbolicBoolVariable.h> + +#include <libsolidity/ast/AST.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +SymbolicBoolVariable::SymbolicBoolVariable( + Declaration const& _decl, + smt::SolverInterface&_interface +): + SymbolicVariable(_decl, _interface) +{ + solAssert(m_declaration.type()->category() == Type::Category::Bool, ""); + m_expression = make_shared<smt::Expression>(m_interface.newFunction(uniqueSymbol(), smt::Sort::Int, smt::Sort::Bool)); +} + +void SymbolicBoolVariable::setZeroValue(int _seq) +{ + m_interface.addAssertion(valueAtSequence(_seq) == smt::Expression(false)); +} + +void SymbolicBoolVariable::setUnknownValue(int) +{ +} diff --git a/libsolidity/formal/SymbolicBoolVariable.h b/libsolidity/formal/SymbolicBoolVariable.h new file mode 100644 index 00000000..3510b770 --- /dev/null +++ b/libsolidity/formal/SymbolicBoolVariable.h @@ -0,0 +1,47 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/formal/SymbolicVariable.h> + +#include <libsolidity/ast/Types.h> + +namespace dev +{ +namespace solidity +{ + +/** + * Specialization of SymbolicVariable for Bool + */ +class SymbolicBoolVariable: public SymbolicVariable +{ +public: + SymbolicBoolVariable( + Declaration const& _decl, + smt::SolverInterface& _interface + ); + + /// Sets the var to false. + void setZeroValue(int _seq); + /// Does nothing since the SMT solver already knows the valid values. + void setUnknownValue(int _seq); +}; + +} +} diff --git a/libsolidity/formal/SymbolicIntVariable.cpp b/libsolidity/formal/SymbolicIntVariable.cpp new file mode 100644 index 00000000..eb7b1c17 --- /dev/null +++ b/libsolidity/formal/SymbolicIntVariable.cpp @@ -0,0 +1,56 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/formal/SymbolicIntVariable.h> + +#include <libsolidity/ast/AST.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +SymbolicIntVariable::SymbolicIntVariable( + Declaration const& _decl, + smt::SolverInterface& _interface +): + SymbolicVariable(_decl, _interface) +{ + solAssert(m_declaration.type()->category() == Type::Category::Integer, ""); + m_expression = make_shared<smt::Expression>(m_interface.newFunction(uniqueSymbol(), smt::Sort::Int, smt::Sort::Int)); +} + +void SymbolicIntVariable::setZeroValue(int _seq) +{ + m_interface.addAssertion(valueAtSequence(_seq) == 0); +} + +void SymbolicIntVariable::setUnknownValue(int _seq) +{ + auto const& intType = dynamic_cast<IntegerType const&>(*m_declaration.type()); + m_interface.addAssertion(valueAtSequence(_seq) >= minValue(intType)); + m_interface.addAssertion(valueAtSequence(_seq) <= maxValue(intType)); +} + +smt::Expression SymbolicIntVariable::minValue(IntegerType const& _t) +{ + return smt::Expression(_t.minValue()); +} + +smt::Expression SymbolicIntVariable::maxValue(IntegerType const& _t) +{ + return smt::Expression(_t.maxValue()); +} diff --git a/libsolidity/formal/SymbolicIntVariable.h b/libsolidity/formal/SymbolicIntVariable.h new file mode 100644 index 00000000..eb36b899 --- /dev/null +++ b/libsolidity/formal/SymbolicIntVariable.h @@ -0,0 +1,50 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/formal/SymbolicVariable.h> + +#include <libsolidity/ast/Types.h> + +namespace dev +{ +namespace solidity +{ + +/** + * Specialization of SymbolicVariable for Integers + */ +class SymbolicIntVariable: public SymbolicVariable +{ +public: + SymbolicIntVariable( + Declaration const& _decl, + smt::SolverInterface& _interface + ); + + /// Sets the var to 0. + void setZeroValue(int _seq); + /// Sets the variable to the full valid value range. + void setUnknownValue(int _seq); + + static smt::Expression minValue(IntegerType const& _t); + static smt::Expression maxValue(IntegerType const& _t); +}; + +} +} diff --git a/libsolidity/formal/SymbolicVariable.cpp b/libsolidity/formal/SymbolicVariable.cpp new file mode 100644 index 00000000..d59b55b1 --- /dev/null +++ b/libsolidity/formal/SymbolicVariable.cpp @@ -0,0 +1,40 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/formal/SymbolicVariable.h> + +#include <libsolidity/ast/AST.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +SymbolicVariable::SymbolicVariable( + Declaration const& _decl, + smt::SolverInterface& _interface +): + m_declaration(_decl), + m_interface(_interface) +{ +} + +string SymbolicVariable::uniqueSymbol() const +{ + return m_declaration.name() + "_" + to_string(m_declaration.id()); +} + + diff --git a/libsolidity/formal/SymbolicVariable.h b/libsolidity/formal/SymbolicVariable.h new file mode 100644 index 00000000..75eb9fa5 --- /dev/null +++ b/libsolidity/formal/SymbolicVariable.h @@ -0,0 +1,69 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/formal/SolverInterface.h> + +#include <libsolidity/ast/AST.h> + +#include <memory> + +namespace dev +{ +namespace solidity +{ + +class Declaration; + +/** + * This class represents the symbolic version of a program variable. + */ +class SymbolicVariable +{ +public: + SymbolicVariable( + Declaration const& _decl, + smt::SolverInterface& _interface + ); + + smt::Expression operator()(int _seq) const + { + return valueAtSequence(_seq); + } + + std::string uniqueSymbol() const; + + /// Sets the var to the default value of its type. + virtual void setZeroValue(int _seq) = 0; + /// The unknown value is the full range of valid values, + /// and that's sub-type dependent. + virtual void setUnknownValue(int _seq) = 0; + +protected: + smt::Expression valueAtSequence(int _seq) const + { + return (*m_expression)(_seq); + } + + Declaration const& m_declaration; + std::shared_ptr<smt::Expression> m_expression = nullptr; + smt::SolverInterface& m_interface; +}; + +} +} diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 769e6edb..125da00d 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -28,6 +28,7 @@ using namespace dev::solidity::smt; Z3Interface::Z3Interface(): m_solver(m_context) { + z3::set_param("rewriter.pull_cheap_ite", true); } void Z3Interface::reset() diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index 1030523a..abf7ddf2 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -54,7 +54,10 @@ bool AsmAnalyzer::analyze(Block const& _block) bool AsmAnalyzer::operator()(Label const& _label) { - solAssert(m_flavour == AsmFlavour::Loose, ""); + checkLooseFeature( + _label.location, + "The use of labels is deprecated. Please use \"if\", \"switch\", \"for\" or function calls instead." + ); m_info.stackHeightInfo[&_label] = m_stackHeight; warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location); return true; @@ -62,7 +65,10 @@ bool AsmAnalyzer::operator()(Label const& _label) bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) { - solAssert(m_flavour == AsmFlavour::Loose, ""); + checkLooseFeature( + _instruction.location, + "The use of non-functional instructions is deprecated. Please use functional notation instead." + ); auto const& info = instructionInfo(_instruction.instruction); m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instruction] = m_stackHeight; @@ -170,18 +176,31 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement) { - size_t initialStackHeight = m_stackHeight; + int initialStackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, _statement.expression); - if (m_flavour != AsmFlavour::Loose) - if (!expectDeposit(0, initialStackHeight, _statement.location)) + if (m_stackHeight != initialStackHeight && (m_flavour != AsmFlavour::Loose || m_errorTypeForLoose)) + { + Error::Type errorType = m_flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError; + string msg = + "Top-level expressions are not supposed to return values (this expression returns " + + boost::lexical_cast<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_info.stackHeightInfo[&_statement] = m_stackHeight; return success; } bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) { - solAssert(m_flavour == AsmFlavour::Loose, ""); + checkLooseFeature( + _assignment.location, + "The use of stack assignment is deprecated. Please use assignment in functional notation instead." + ); bool success = checkAssignment(_assignment.variableName, size_t(-1)); m_info.stackHeightInfo[&_assignment] = m_stackHeight; return success; @@ -533,40 +552,66 @@ void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _loc void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location) { - static set<solidity::Instruction> futureInstructions{ - solidity::Instruction::CREATE2, - solidity::Instruction::RETURNDATACOPY, - solidity::Instruction::RETURNDATASIZE, - solidity::Instruction::STATICCALL - }; - if (futureInstructions.count(_instr)) + // We assume that returndatacopy, returndatasize and staticcall are either all available + // or all not available. + solAssert(m_evmVersion.supportsReturndata() == m_evmVersion.hasStaticCall(), ""); + + if (_instr == solidity::Instruction::CREATE2) m_errorReporter.warning( _location, "The \"" + boost::to_lower_copy(instructionInfo(_instr).name) - + "\" instruction is only available after " + - "the Metropolis hard fork. Before that it acts as an invalid instruction." + + "\" instruction is not supported by the VM version \"" + + "" + m_evmVersion.name() + + "\" you are currently compiling for. " + + "It will be interpreted as an invalid instruction on this VM." ); - - static set<solidity::Instruction> experimentalInstructions{ - solidity::Instruction::SHL, - solidity::Instruction::SHR, - solidity::Instruction::SAR - }; - if (experimentalInstructions.count(_instr)) + else if (( + _instr == solidity::Instruction::RETURNDATACOPY || + _instr == solidity::Instruction::RETURNDATASIZE || + _instr == solidity::Instruction::STATICCALL + ) && !m_evmVersion.supportsReturndata()) m_errorReporter.warning( _location, "The \"" + boost::to_lower_copy(instructionInfo(_instr).name) - + "\" instruction is only available after " + - "the Constantinople hard fork. Before that it acts as an invalid instruction." + + "\" instruction is only available for Byzantium-compatible VMs. " + + "You are currently compiling for \"" + + m_evmVersion.name() + + "\", where it will be interpreted as an invalid instruction." + ); + else if (( + _instr == solidity::Instruction::SHL || + _instr == solidity::Instruction::SHR || + _instr == solidity::Instruction::SAR + ) && !m_evmVersion.hasBitwiseShifting()) + m_errorReporter.warning( + _location, + "The \"" + + boost::to_lower_copy(instructionInfo(_instr).name) + + "\" instruction is only available for Constantinople-compatible VMs. " + + "You are currently compiling for \"" + + m_evmVersion.name() + + "\", where it will be interpreted as an invalid instruction." ); if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST) - m_errorReporter.warning( + { + solAssert(m_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." ); + } +} + +void AsmAnalyzer::checkLooseFeature(SourceLocation const& _location, string const& _description) +{ + if (m_flavour != AsmFlavour::Loose) + solAssert(false, _description); + else if (m_errorTypeForLoose) + m_errorReporter.error(*m_errorTypeForLoose, _location, _description); } diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 7a81dbf8..8d2a71f0 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -21,6 +21,7 @@ #pragma once #include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/EVMVersion.h> #include <libsolidity/inlineasm/AsmScope.h> @@ -29,6 +30,7 @@ #include <libsolidity/inlineasm/AsmDataForward.h> #include <boost/variant.hpp> +#include <boost/optional.hpp> #include <functional> #include <memory> @@ -54,9 +56,18 @@ public: explicit AsmAnalyzer( AsmAnalysisInfo& _analysisInfo, ErrorReporter& _errorReporter, + EVMVersion _evmVersion, + boost::optional<Error::Type> _errorTypeForLoose, AsmFlavour _flavour = AsmFlavour::Loose, julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver() - ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_flavour(_flavour) {} + ): + m_resolver(_resolver), + m_info(_analysisInfo), + m_errorReporter(_errorReporter), + m_evmVersion(_evmVersion), + m_flavour(_flavour), + m_errorTypeForLoose(_errorTypeForLoose) + {} bool analyze(assembly::Block const& _block); @@ -89,6 +100,11 @@ private: void expectValidType(std::string const& type, SourceLocation const& _location); void warnOnInstructions(solidity::Instruction _instr, 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(SourceLocation const& _location, std::string const& _description); + int m_stackHeight = 0; julia::ExternalIdentifierAccess::Resolver m_resolver; Scope* m_currentScope = nullptr; @@ -97,7 +113,9 @@ private: std::set<Scope::Variable const*> m_activeVariables; AsmAnalysisInfo& m_info; ErrorReporter& m_errorReporter; + EVMVersion m_evmVersion; AsmFlavour m_flavour = AsmFlavour::Loose; + boost::optional<Error::Type> m_errorTypeForLoose; }; } diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index c9e534c7..7f97336b 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -91,7 +91,7 @@ bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scann bool AssemblyStack::analyzeParsed() { m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); - assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, languageToAsmFlavour(m_language)); + assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_evmVersion, boost::none, languageToAsmFlavour(m_language)); m_analysisSuccessful = analyzer.analyze(*m_parserResult); return m_analysisSuccessful; } diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h index 6ae7e8d1..720220ab 100644 --- a/libsolidity/interface/AssemblyStack.h +++ b/libsolidity/interface/AssemblyStack.h @@ -22,6 +22,8 @@ #pragma once #include <libsolidity/interface/ErrorReporter.h> +#include <libsolidity/interface/EVMVersion.h> + #include <libevmasm/LinkerObject.h> #include <string> @@ -54,8 +56,8 @@ public: enum class Language { JULIA, Assembly, StrictAssembly }; enum class Machine { EVM, EVM15, eWasm }; - explicit AssemblyStack(Language _language = Language::Assembly): - m_language(_language), m_errorReporter(m_errors) + explicit AssemblyStack(EVMVersion _evmVersion = EVMVersion(), Language _language = Language::Assembly): + m_language(_language), m_evmVersion(_evmVersion), m_errorReporter(m_errors) {} /// @returns the scanner used during parsing @@ -82,6 +84,7 @@ private: bool analyzeParsed(); Language m_language = Language::Assembly; + EVMVersion m_evmVersion; std::shared_ptr<Scanner> m_scanner; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 3b5e65e8..eacfca9c 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -74,6 +74,12 @@ void CompilerStack::setRemappings(vector<string> const& _remappings) swap(m_remappings, remappings); } +void CompilerStack::setEVMVersion(EVMVersion _version) +{ + solAssert(m_stackState < State::ParsingSuccessful, "Set EVM version after parsing."); + m_evmVersion = _version; +} + void CompilerStack::reset(bool _keepSources) { if (_keepSources) @@ -88,6 +94,7 @@ void CompilerStack::reset(bool _keepSources) m_sources.clear(); } m_libraries.clear(); + m_evmVersion = EVMVersion(); m_optimize = false; m_optimizeRuns = 200; m_globalContext.reset(); @@ -198,7 +205,7 @@ bool CompilerStack::analyze() m_contracts[contract->fullyQualifiedName()].contract = contract; } - TypeChecker typeChecker(m_errorReporter); + TypeChecker typeChecker(m_evmVersion, m_errorReporter); for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) @@ -677,7 +684,7 @@ void CompilerStack::compileContract( for (auto const* dependency: _contract.annotation().contractDependencies) compileContract(*dependency, _compiledContracts); - shared_ptr<Compiler> compiler = make_shared<Compiler>(m_optimize, m_optimizeRuns); + shared_ptr<Compiler> compiler = make_shared<Compiler>(m_evmVersion, m_optimize, m_optimizeRuns); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); string metadata = createMetadata(compiledContract); bytes cborEncodedHash = @@ -736,7 +743,7 @@ void CompilerStack::compileContract( { if (!_contract.isLibrary()) { - Compiler cloneCompiler(m_optimize, m_optimizeRuns); + Compiler cloneCompiler(m_evmVersion, m_optimize, m_optimizeRuns); cloneCompiler.compileClone(_contract, _compiledContracts); compiledContract.cloneObject = cloneCompiler.assembledObject(); } @@ -838,6 +845,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const } meta["settings"]["optimizer"]["enabled"] = m_optimize; meta["settings"]["optimizer"]["runs"] = m_optimizeRuns; + meta["settings"]["evmVersion"] = m_evmVersion.name(); meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] = _contract.contract->annotation().canonicalName; @@ -951,11 +959,12 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const return Json::Value(); using Gas = GasEstimator::GasConsumption; + GasEstimator gasEstimator(m_evmVersion); Json::Value output(Json::objectValue); if (eth::AssemblyItems const* items = assemblyItems(_contractName)) { - Gas executionGas = GasEstimator::functionalEstimation(*items); + Gas executionGas = gasEstimator.functionalEstimation(*items); u256 bytecodeSize(runtimeObject(_contractName).bytecode.size()); Gas codeDepositGas = bytecodeSize * eth::GasCosts::createDataGas; @@ -976,14 +985,14 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const for (auto it: contract.interfaceFunctions()) { string sig = it.second->externalSignature(); - externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig)); + externalFunctions[sig] = gasToJson(gasEstimator.functionalEstimation(*items, sig)); } if (contract.fallbackFunction()) /// This needs to be set to an invalid signature in order to trigger the fallback, /// without the shortcut (of CALLDATSIZE == 0), and therefore to receive the upper bound. /// An empty string ("") would work to trigger the shortcut only. - externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID")); + externalFunctions[""] = gasToJson(gasEstimator.functionalEstimation(*items, "INVALID")); if (!externalFunctions.empty()) output["external"] = externalFunctions; @@ -999,7 +1008,7 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const size_t entry = functionEntryPoint(_contractName, *it); GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); if (entry > 0) - gas = GasEstimator::functionalEstimation(*items, entry, *it); + gas = gasEstimator.functionalEstimation(*items, entry, *it); /// TODO: This could move into a method shared with externalSignature() FunctionType type(*it); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index b377b3aa..13c9cc7a 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -23,20 +23,26 @@ #pragma once +#include <libsolidity/interface/ErrorReporter.h> +#include <libsolidity/interface/ReadFile.h> +#include <libsolidity/interface/EVMVersion.h> + +#include <libevmasm/SourceLocation.h> +#include <libevmasm/LinkerObject.h> + +#include <libdevcore/Common.h> +#include <libdevcore/FixedHash.h> + +#include <json/json.h> + +#include <boost/noncopyable.hpp> +#include <boost/filesystem.hpp> + #include <ostream> #include <string> #include <memory> #include <vector> #include <functional> -#include <boost/noncopyable.hpp> -#include <boost/filesystem.hpp> -#include <json/json.h> -#include <libdevcore/Common.h> -#include <libdevcore/FixedHash.h> -#include <libevmasm/SourceLocation.h> -#include <libevmasm/LinkerObject.h> -#include <libsolidity/interface/ErrorReporter.h> -#include <libsolidity/interface/ReadFile.h> namespace dev { @@ -116,6 +122,8 @@ public: m_optimizeRuns = _runs; } + void setEVMVersion(EVMVersion _version = EVMVersion{}); + /// Sets the list of requested contract names. If empty, no filtering is performed and every contract /// found in the supplied sources is compiled. Names are cleared iff @a _contractNames is missing. void setRequestedContractNames(std::set<std::string> const& _contractNames = std::set<std::string>{}) @@ -310,6 +318,7 @@ private: ReadCallback::Callback m_smtQuery; bool m_optimize = false; unsigned m_optimizeRuns = 200; + EVMVersion m_evmVersion; std::set<std::string> m_requestedContractNames; std::map<std::string, h160> m_libraries; /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum diff --git a/libsolidity/interface/EVMVersion.h b/libsolidity/interface/EVMVersion.h new file mode 100644 index 00000000..b68e1f4e --- /dev/null +++ b/libsolidity/interface/EVMVersion.h @@ -0,0 +1,93 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * EVM versioning. + */ + +#pragma once + +#include <string> + +#include <boost/optional.hpp> +#include <boost/operators.hpp> + +namespace dev +{ +namespace solidity +{ + +/** + * A version specifier of the EVM we want to compile to. + * Defaults to the latest version. + */ +class EVMVersion: + boost::less_than_comparable<EVMVersion>, + boost::equality_comparable<EVMVersion> +{ +public: + EVMVersion() {} + + static EVMVersion homestead() { return {Version::Homestead}; } + static EVMVersion tangerineWhistle() { return {Version::TangerineWhistle}; } + static EVMVersion spuriousDragon() { return {Version::SpuriousDragon}; } + static EVMVersion byzantium() { return {Version::Byzantium}; } + static EVMVersion constantinople() { return {Version::Constantinople}; } + + static boost::optional<EVMVersion> fromString(std::string const& _version) + { + for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople()}) + if (_version == v.name()) + return v; + return {}; + } + + bool operator==(EVMVersion const& _other) const { return m_version == _other.m_version; } + bool operator<(EVMVersion const& _other) const { return m_version < _other.m_version; } + + std::string name() const + { + switch (m_version) + { + case Version::Homestead: return "homestead"; + case Version::TangerineWhistle: return "tangerineWhistle"; + case Version::SpuriousDragon: return "spuriousDragon"; + case Version::Byzantium: return "byzantium"; + case Version::Constantinople: return "constantinople"; + } + return "INVALID"; + } + + /// Has the RETURNDATACOPY and RETURNDATASIZE opcodes. + bool supportsReturndata() const { return *this >= byzantium(); } + bool hasStaticCall() const { return *this >= byzantium(); } + bool hasBitwiseShifting() const { return *this >= constantinople(); } + + /// Whether we have to retain the costs for the call opcode itself (false), + /// or whether we can just forward easily all remaining gas (true). + bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); } + +private: + enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Constantinople }; + + EVMVersion(Version _version): m_version(_version) {} + + Version m_version = Version::Byzantium; +}; + + +} +} diff --git a/libsolidity/interface/GasEstimator.cpp b/libsolidity/interface/GasEstimator.cpp index 22cc0266..2139395f 100644 --- a/libsolidity/interface/GasEstimator.cpp +++ b/libsolidity/interface/GasEstimator.cpp @@ -40,7 +40,7 @@ using namespace dev::solidity; GasEstimator::ASTGasConsumptionSelfAccumulated GasEstimator::structuralEstimation( AssemblyItems const& _items, vector<ASTNode const*> const& _ast -) +) const { solAssert(std::count(_ast.begin(), _ast.end(), nullptr) == 0, ""); map<SourceLocation, GasConsumption> particularCosts; @@ -49,7 +49,7 @@ GasEstimator::ASTGasConsumptionSelfAccumulated GasEstimator::structuralEstimatio for (BasicBlock const& block: cfg.optimisedBlocks()) { solAssert(!!block.startState, ""); - GasMeter meter(block.startState->copy()); + GasMeter meter(block.startState->copy(), m_evmVersion); auto const end = _items.begin() + block.end; for (auto iter = _items.begin() + block.begin; iter != end; ++iter) particularCosts[iter->location()] += meter.estimateMax(*iter); @@ -127,7 +127,7 @@ map<ASTNode const*, GasMeter::GasConsumption> GasEstimator::breakToStatementLeve GasEstimator::GasConsumption GasEstimator::functionalEstimation( AssemblyItems const& _items, string const& _signature -) +) const { auto state = make_shared<KnownState>(); @@ -144,7 +144,7 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( }); } - PathGasMeter meter(_items); + PathGasMeter meter(_items, m_evmVersion); return meter.estimateMax(0, state); } @@ -152,7 +152,7 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( AssemblyItems const& _items, size_t const& _offset, FunctionDefinition const& _function -) +) const { auto state = make_shared<KnownState>(); @@ -167,7 +167,7 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( if (parametersSize > 0) state->feedItem(swapInstruction(parametersSize)); - return PathGasMeter(_items).estimateMax(_offset, state); + return PathGasMeter(_items, m_evmVersion).estimateMax(_offset, state); } set<ASTNode const*> GasEstimator::finestNodesAtLocation( diff --git a/libsolidity/interface/GasEstimator.h b/libsolidity/interface/GasEstimator.h index bf63df96..ea94d988 100644 --- a/libsolidity/interface/GasEstimator.h +++ b/libsolidity/interface/GasEstimator.h @@ -22,11 +22,14 @@ #pragma once +#include <libsolidity/interface/EVMVersion.h> + +#include <libevmasm/GasMeter.h> +#include <libevmasm/Assembly.h> + #include <vector> #include <map> #include <array> -#include <libevmasm/GasMeter.h> -#include <libevmasm/Assembly.h> namespace dev { @@ -44,13 +47,15 @@ public: using ASTGasConsumptionSelfAccumulated = std::map<ASTNode const*, std::array<GasConsumption, 2>>; + explicit GasEstimator(EVMVersion _evmVersion): m_evmVersion(_evmVersion) {} + /// Estimates the gas consumption for every assembly item in the given assembly and stores /// it by source location. /// @returns a mapping from each AST node to a pair of its particular and syntactically accumulated gas costs. - static ASTGasConsumptionSelfAccumulated structuralEstimation( + ASTGasConsumptionSelfAccumulated structuralEstimation( eth::AssemblyItems const& _items, std::vector<ASTNode const*> const& _ast - ); + ) const; /// @returns a mapping from nodes with non-overlapping source locations to gas consumptions such that /// the following source locations are part of the mapping: /// 1. source locations of statements that do not contain other statements @@ -62,23 +67,24 @@ public: /// @returns the estimated gas consumption by the (public or external) function with the /// given signature. If no signature is given, estimates the maximum gas usage. - static GasConsumption functionalEstimation( + GasConsumption functionalEstimation( eth::AssemblyItems const& _items, std::string const& _signature = "" - ); + ) const; /// @returns the estimated gas consumption by the given function which starts at the given /// offset into the list of assembly items. /// @note this does not work correctly for recursive functions. - static GasConsumption functionalEstimation( + GasConsumption functionalEstimation( eth::AssemblyItems const& _items, size_t const& _offset, FunctionDefinition const& _function - ); + ) const; private: /// @returns the set of AST nodes which are the finest nodes at their location. static std::set<ASTNode const*> finestNodesAtLocation(std::vector<ASTNode const*> const& _roots); + EVMVersion m_evmVersion; }; } diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp index 9d02c498..0f014372 100644 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ b/libsolidity/interface/SourceReferenceFormatter.cpp @@ -79,8 +79,8 @@ void SourceReferenceFormatter::printSourceLocation(SourceLocation const* _locati scanner.lineAtPosition(_location->start) << endl << string(startColumn, ' ') << - "^\n" << - "Spanning multiple lines.\n"; + "^ (Relevant source part starts here and spans across multiple lines)." << + endl; } void SourceReferenceFormatter::printSourceName(SourceLocation const* _location) diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index fb973d51..ee9b1440 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -27,6 +27,8 @@ #include <libdevcore/JSON.h> #include <libdevcore/SHA3.h> +#include <boost/algorithm/string.hpp> + using namespace std; using namespace dev; using namespace dev::solidity; @@ -316,6 +318,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) Json::Value const& settings = _input.get("settings", Json::Value()); + if (settings.isMember("evmVersion")) + { + boost::optional<EVMVersion> version = EVMVersion::fromString(settings.get("evmVersion", {}).asString()); + if (!version) + return formatFatalError("JSONError", "Invalid EVM version requested."); + m_compilerStack.setEVMVersion(*version); + } + vector<string> remappings; for (auto const& remapping: settings.get("remappings", Json::Value())) remappings.push_back(remapping.asString()); @@ -327,13 +337,43 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) m_compilerStack.setOptimiserSettings(optimize, optimizeRuns); map<string, h160> libraries; - Json::Value jsonLibraries = settings.get("libraries", Json::Value()); + Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue)); + if (!jsonLibraries.isObject()) + return formatFatalError("JSONError", "\"libraries\" is not a JSON object."); for (auto const& sourceName: jsonLibraries.getMemberNames()) { auto const& jsonSourceName = jsonLibraries[sourceName]; + if (!jsonSourceName.isObject()) + return formatFatalError("JSONError", "library entry is not a JSON object."); for (auto const& library: jsonSourceName.getMemberNames()) - // @TODO use libraries only for the given source - libraries[library] = h160(jsonSourceName[library].asString()); + { + string address = jsonSourceName[library].asString(); + + if (!boost::starts_with(address, "0x")) + return formatFatalError( + "JSONError", + "Library address is not prefixed with \"0x\"." + ); + + if (address.length() != 42) + return formatFatalError( + "JSONError", + "Library address is of invalid length." + ); + + try + { + // @TODO use libraries only for the given source + libraries[library] = h160(address); + } + catch (dev::BadHexCharacter) + { + return formatFatalError( + "JSONError", + "Invalid library address (\"" + address + "\") supplied." + ); + } + } } m_compilerStack.setLibraries(libraries); diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index 0409de72..d058d556 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -119,21 +119,17 @@ DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end) return _end; } auto nameEndPos = firstSpaceOrTab(nameStartPos, _end); - if (nameEndPos == _end) - { - appendError("End of param name not found: " + string(nameStartPos, _end)); - return _end; - } auto paramName = string(nameStartPos, nameEndPos); auto descStartPos = skipWhitespace(nameEndPos, _end); - if (descStartPos == _end) + auto nlPos = find(descStartPos, _end, '\n'); + + if (descStartPos == nlPos) { appendError("No description given for param " + paramName); return _end; } - auto nlPos = find(descStartPos, _end, '\n'); auto paramDesc = string(descStartPos, nlPos); newTag("param"); m_lastTag->paramName = paramName; diff --git a/lllc/main.cpp b/lllc/main.cpp index 5679bc2b..0ca3ff13 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -133,7 +133,7 @@ int main(int argc, char** argv) } else if (mode == Binary || mode == Hex) { - auto bs = compileLLL(src, optimise ? true : false, &errors, readFileAsString); + auto bs = compileLLL(src, EVMVersion{}, optimise ? true : false, &errors, readFileAsString); if (mode == Hex) cout << toHex(bs) << endl; else if (mode == Binary) @@ -145,7 +145,7 @@ int main(int argc, char** argv) } else if (mode == Assembly) { - cout << compileLLLToAsm(src, optimise ? true : false, &errors, readFileAsString) << endl; + cout << compileLLLToAsm(src, EVMVersion{}, optimise ? true : false, &errors, readFileAsString) << endl; } for (auto const& i: errors) diff --git a/scripts/isolate_tests.py b/scripts/isolate_tests.py index cfaef210..5bf577d3 100755 --- a/scripts/isolate_tests.py +++ b/scripts/isolate_tests.py @@ -86,10 +86,15 @@ if __name__ == '__main__': for root, subdirs, files in os.walk(path): if '_build' in subdirs: subdirs.remove('_build') + if 'compilationTests' in subdirs: + subdirs.remove('compilationTests') for f in files: path = join(root, f) if docs: cases = extract_docs_cases(path) else: - cases = extract_test_cases(path) + if f.endswith(".sol"): + cases = [open(path, "r").read()] + else: + cases = extract_test_cases(path) write_cases(cases) diff --git a/scripts/soltest.sh b/scripts/soltest.sh new file mode 100755 index 00000000..00f484a1 --- /dev/null +++ b/scripts/soltest.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -e + +REPO_ROOT="$(dirname "$0")"/.. +USE_DEBUGGER=0 +DEBUGGER="gdb --args" +BOOST_OPTIONS= +SOLTEST_OPTIONS= + +while [ $# -gt 0 ] +do + case "$1" in + --debugger) + shift + DEBUGGER="$1" + USE_DEBUGGER=1 + ;; + --debug) + USE_DEBUGGER=1 + ;; + --boost-options) + shift + BOOST_OPTIONS="${BOOST_OPTIONS} $1" + ;; + -t) + shift + BOOST_OPTIONS="${BOOST_OPTIONS} -t $1" + ;; + --show-progress | -p) + BOOST_OPTIONS="${BOOST_OPTIONS} $1" + ;; + *) + SOLTEST_OPTIONS="${SOLTEST_OPTIONS} $1" + ;; + esac + shift +done +if [ "$USE_DEBUGGER" -ne "0" ]; then + DEBUG_PREFIX=${DEBUGGER} +fi + +exec ${DEBUG_PREFIX} ${REPO_ROOT}/build/test/soltest ${BOOST_OPTIONS} -- --testpath ${REPO_ROOT}/test ${SOLTEST_OPTIONS} diff --git a/scripts/tests.sh b/scripts/tests.sh index 3c80adc5..37ffafc2 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -37,11 +37,9 @@ then echo "Usage: $0 [--junit_report <report_directory>]" exit 1 fi - testargs_no_opt="--logger=JUNIT,test_suite,$2/no_opt.xml" - testargs_opt="--logger=JUNIT,test_suite,$2/opt.xml" + log_directory="$2" else - testargs_no_opt='' - testargs_opt='' + log_directory="" fi echo "Running commandline tests..." @@ -61,12 +59,15 @@ function download_eth() ETH_PATH="eth" else mkdir -p /tmp/test - ETH_BINARY=eth_byzantium_artful - ETH_HASH="e527dd3e3dc17b983529dd7dcfb74a0d3a5aed4e" if grep -i trusty /etc/lsb-release >/dev/null 2>&1 then - ETH_BINARY=eth_byzantium2 - ETH_HASH="4dc3f208475f622be7c8e53bee720e14cd254c6f" + # built from 1ecff3cac12f0fbbeea3e645f331d5ac026b24d3 at 2018-03-06 + ETH_BINARY=eth_byzantium_trusty + ETH_HASH="5432ea81c150e8a3547615bf597cd6dce9e1e27b" + else + # built from ?? at 2018-02-13 ? + ETH_BINARY=eth_byzantium_artful + ETH_HASH="e527dd3e3dc17b983529dd7dcfb74a0d3a5aed4e" fi wget -q -O /tmp/test/eth https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/$ETH_BINARY test "$(shasum /tmp/test/eth)" = "$ETH_HASH /tmp/test/eth" @@ -98,13 +99,29 @@ then progress="" fi -echo "--> Running tests without optimizer..." -"$REPO_ROOT"/build/test/soltest $testargs_no_opt $progress -- --ipcpath /tmp/test/geth.ipc -echo "--> Running tests WITH optimizer..." -"$REPO_ROOT"/build/test/soltest $testargs_opt $progress -- --optimize --ipcpath /tmp/test/geth.ipc +# And then run the Solidity unit-tests in the matrix combination of optimizer / no optimizer +# and homestead / byzantium VM, # pointing to that IPC endpoint. +for optimize in "" "--optimize" +do + for vm in homestead byzantium + do + echo "--> Running tests using "$optimize" --evm-version "$vm"..." + log="" + if [ -n "$log_directory" ] + then + if [ -n "$optimize" ] + then + log=--logger=JUNIT,test_suite,$log_directory/opt_$vm.xml $testargs + else + log=--logger=JUNIT,test_suite,$log_directory/noopt_$vm.xml $testargs_no_opt + fi + fi + "$REPO_ROOT"/build/test/soltest $progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" --ipcpath /tmp/test/geth.ipc + done +done wait $CMDLINE_PID pkill "$ETH_PID" || true sleep 4 -pgrep "$ETH_PID" && pkill -9 "$ETH_PID" || true
\ No newline at end of file +pgrep "$ETH_PID" && pkill -9 "$ETH_PID" || true diff --git a/scripts/uniqueErrors.sh b/scripts/uniqueErrors.sh index eee1df90..fa2c7b4c 100755 --- a/scripts/uniqueErrors.sh +++ b/scripts/uniqueErrors.sh @@ -9,6 +9,6 @@ do echo -n $x " # " # This subshell is a workaround to prevent the shell from printing # "Aborted" - ("$REPO"/build/test/solfuzzer < "$x" || true) 2>&1 | head -n 1 + ("$REPO"/build/test/tools/solfuzzer < "$x" || true) 2>&1 | head -n 1 done ) | sort -u -t'#' -k 2 diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 8383afed..d3d234c3 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -71,7 +71,6 @@ namespace solidity static string const g_stdinFileNameStr = "<stdin>"; static string const g_strAbi = "abi"; -static string const g_strAddStandard = "add-std"; static string const g_strAllowPaths = "allow-paths"; static string const g_strAsm = "asm"; static string const g_strAsmJson = "asm-json"; @@ -87,6 +86,7 @@ static string const g_strCompactJSON = "compact-format"; static string const g_strContracts = "contracts"; static string const g_strEVM = "evm"; static string const g_strEVM15 = "evm15"; +static string const g_strEVMVersion = "evm-version"; static string const g_streWasm = "ewasm"; static string const g_strFormal = "formal"; static string const g_strGas = "gas"; @@ -118,7 +118,6 @@ static string const g_strPrettyJson = "pretty-json"; static string const g_strVersion = "version"; static string const g_argAbi = g_strAbi; -static string const g_argAddStandard = g_strAddStandard; static string const g_argPrettyJson = g_strPrettyJson; static string const g_argAllowPaths = g_strAllowPaths; static string const g_argAsm = g_strAsm; @@ -537,13 +536,17 @@ Allowed options)", (g_argHelp.c_str(), "Show help message and exit.") (g_argVersion.c_str(), "Show version and exit.") (g_strLicense.c_str(), "Show licensing information and exit.") + ( + g_strEVMVersion.c_str(), + po::value<string>()->value_name("version"), + "Select desired EVM version. Either homestead, tangerineWhistle, spuriousDragon, byzantium (default) or constantinople." + ) (g_argOptimize.c_str(), "Enable bytecode optimizer.") ( g_argOptimizeRuns.c_str(), po::value<unsigned>()->value_name("n")->default_value(200), "Estimated number of contract runs for optimizer tuning." ) - (g_argAddStandard.c_str(), "Add standard contracts.") (g_argPrettyJson.c_str(), "Output JSON in pretty format. Currently it only works with the combined JSON output.") ( g_argLibraries.c_str(), @@ -627,6 +630,7 @@ Allowed options)", try { po::command_line_parser cmdLineParser(_argc, _argv); + cmdLineParser.style(po::command_line_style::default_style & (~po::command_line_style::allow_guessing)); cmdLineParser.options(allOptions).positional(filesPositions); po::store(cmdLineParser.run(), m_args); } @@ -744,6 +748,18 @@ bool CommandLineInterface::processInput() if (!parseLibraryOption(library)) return false; + if (m_args.count(g_strEVMVersion)) + { + string versionOptionStr = m_args[g_strEVMVersion].as<string>(); + boost::optional<EVMVersion> versionOption = EVMVersion::fromString(versionOptionStr); + if (!versionOption) + { + cerr << "Invalid option for --evm-version: " << versionOptionStr << endl; + return false; + } + m_evmVersion = *versionOption; + } + if (m_args.count(g_argAssemble) || m_args.count(g_argStrictAssembly) || m_args.count(g_argJulia)) { // switch to assembly mode @@ -791,6 +807,7 @@ bool CommandLineInterface::processInput() m_compiler->addSource(sourceCode.first, sourceCode.second); if (m_args.count(g_argLibraries)) m_compiler->setLibraries(m_libraries); + m_compiler->setEVMVersion(m_evmVersion); // TODO: Perhaps we should not compile unless requested bool optimize = m_args.count(g_argOptimize) > 0; unsigned runs = m_args[g_argOptimizeRuns].as<unsigned>(); @@ -949,7 +966,7 @@ void CommandLineInterface::handleAst(string const& _argStr) // FIXME: shouldn't this be done for every contract? if (m_compiler->runtimeAssemblyItems(m_compiler->lastContractName())) gasCosts = GasEstimator::breakToStatementLevel( - GasEstimator::structuralEstimation(*m_compiler->runtimeAssemblyItems(m_compiler->lastContractName()), asts), + GasEstimator(m_evmVersion).structuralEstimation(*m_compiler->runtimeAssemblyItems(m_compiler->lastContractName()), asts), asts ); @@ -1070,7 +1087,7 @@ bool CommandLineInterface::assemble( map<string, AssemblyStack> assemblyStacks; for (auto const& src: m_sourceCodes) { - auto& stack = assemblyStacks[src.first] = AssemblyStack(_language); + auto& stack = assemblyStacks[src.first] = AssemblyStack(m_evmVersion, _language); try { if (!stack.parseAndAnalyze(src.first, src.second)) diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 4768c9d8..303023fc 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -23,6 +23,7 @@ #include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/AssemblyStack.h> +#include <libsolidity/interface/EVMVersion.h> #include <boost/program_options.hpp> #include <boost/filesystem/path.hpp> @@ -102,6 +103,8 @@ private: std::map<std::string, h160> m_libraries; /// Solidity compiler stack std::unique_ptr<dev::solidity::CompilerStack> m_compiler; + /// EVM version to use + EVMVersion m_evmVersion; }; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f36ad4c5..522856cc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,9 +1,27 @@ -file(GLOB_RECURSE sources "*.cpp") -list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/fuzzer.cpp") -file(GLOB_RECURSE headers "*.h") +file(GLOB sources "*.cpp") +file(GLOB headers "*.h") -add_executable(soltest ${sources} ${headers}) +file(GLOB contracts_sources "contracts/*.cpp") +file(GLOB contracts_headers "contracts/*.h") +file(GLOB libdevcore_sources "libdevcore/*.cpp") +file(GLOB libdevcore_headers "libdevcore/*.h") +file(GLOB libevmasm_sources "libevmasm/*.cpp") +file(GLOB libevmasm_headers "libevmasm/*.h") +file(GLOB libjulia_sources "libjulia/*.cpp") +file(GLOB libjulia_headers "libjulia/*.h") +file(GLOB liblll_sources "liblll/*.cpp") +file(GLOB liblll_headers "liblll/*.h") +file(GLOB libsolidity_sources "libsolidity/*.cpp") +file(GLOB libsolidity_headers "libsolidity/*.h") + +add_executable(soltest ${sources} ${headers} + ${contracts_sources} ${contracts_headers} + ${libdevcore_sources} ${libdevcore_headers} + ${libevmasm_sources} ${libevmasm_headers} + ${libjulia_sources} ${libjulia_headers} + ${liblll_sources} ${liblll_headers} + ${libsolidity_sources} ${libsolidity_headers} +) target_link_libraries(soltest PRIVATE libsolc solidity lll evmasm devcore ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) -add_executable(solfuzzer fuzzer.cpp) -target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES}) +add_subdirectory(tools) diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index 85b5bd3b..a24f78fb 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -20,13 +20,15 @@ * Framework for executing contracts and testing them using RPC. */ -#include <cstdlib> -#include <boost/test/framework.hpp> -#include <libdevcore/CommonIO.h> #include <test/ExecutionFramework.h> +#include <libdevcore/CommonIO.h> + +#include <boost/test/framework.hpp> #include <boost/algorithm/string/replace.hpp> +#include <cstdlib> + using namespace std; using namespace dev; using namespace dev::test; @@ -49,6 +51,7 @@ string getIPCSocketPath() ExecutionFramework::ExecutionFramework() : m_rpc(RPCSession::instance(getIPCSocketPath())), + m_evmVersion(dev::test::Options::get().evmVersion()), m_optimize(dev::test::Options::get().optimize), m_showMessages(dev::test::Options::get().showMessages), m_sender(m_rpc.account(0)) diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 8aa99473..ee8da322 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -22,9 +22,11 @@ #pragma once -#include <test/TestHelper.h> +#include <test/Options.h> #include <test/RPCSession.h> +#include <libsolidity/interface/EVMVersion.h> + #include <libdevcore/FixedHash.h> #include <libdevcore/SHA3.h> @@ -227,6 +229,7 @@ protected: bytes data; }; + solidity::EVMVersion m_evmVersion; unsigned m_optimizeRuns = 200; bool m_optimize = false; bool m_showMessages = false; diff --git a/test/Metadata.cpp b/test/Metadata.cpp index 1ebfd468..c130d346 100644 --- a/test/Metadata.cpp +++ b/test/Metadata.cpp @@ -60,7 +60,8 @@ bool isValidMetadata(string const& _metadata) !metadata.isMember("compiler") || !metadata.isMember("settings") || !metadata.isMember("sources") || - !metadata.isMember("output") + !metadata.isMember("output") || + !metadata["settings"].isMember("evmVersion") ) return false; diff --git a/test/TestHelper.cpp b/test/Options.cpp index c8747a06..ff4a7c98 100644 --- a/test/TestHelper.cpp +++ b/test/Options.cpp @@ -19,8 +19,13 @@ * @date 2014 */ +#include <test/Options.h> + +#include <libsolidity/interface/EVMVersion.h> +#include <libsolidity/interface/Exceptions.h> + #include <boost/test/framework.hpp> -#include "TestHelper.h" + using namespace std; using namespace dev::test; @@ -39,8 +44,18 @@ Options::Options() ipcPath = suite.argv[i + 1]; i++; } + else if (string(suite.argv[i]) == "--testpath" && i + 1 < suite.argc) + { + testPath = suite.argv[i + 1]; + i++; + } else if (string(suite.argv[i]) == "--optimize") optimize = true; + else if (string(suite.argv[i]) == "--evm-version") + { + evmVersionString = i + 1 < suite.argc ? suite.argv[i + 1] : "INVALID"; + ++i; + } else if (string(suite.argv[i]) == "--show-messages") showMessages = true; else if (string(suite.argv[i]) == "--no-ipc") @@ -51,4 +66,35 @@ Options::Options() if (!disableIPC && ipcPath.empty()) if (auto path = getenv("ETH_TEST_IPC")) ipcPath = path; + + if (testPath.empty()) + if (auto path = getenv("ETH_TEST_PATH")) + testPath = path; +} + +void Options::validate() const +{ + solAssert( + !dev::test::Options::get().testPath.empty(), + "No test path specified. The --testpath argument is required." + ); + if (!disableIPC) + solAssert( + !dev::test::Options::get().ipcPath.empty(), + "No ipc path specified. The --ipcpath argument is required, unless --no-ipc is used." + ); +} + +dev::solidity::EVMVersion Options::evmVersion() const +{ + if (!evmVersionString.empty()) + { + // We do this check as opposed to in the constructor because the BOOST_REQUIRE + // macros cannot yet be used in the constructor. + auto version = solidity::EVMVersion::fromString(evmVersionString); + BOOST_REQUIRE_MESSAGE(version, "Invalid EVM version: " + evmVersionString); + return *version; + } + else + return dev::solidity::EVMVersion(); } diff --git a/test/TestHelper.h b/test/Options.h index d25c5cd8..9bc69876 100644 --- a/test/TestHelper.h +++ b/test/Options.h @@ -19,11 +19,14 @@ #pragma once -#include <functional> +#include <libsolidity/interface/EVMVersion.h> + #include <boost/test/unit_test.hpp> #include <boost/filesystem.hpp> #include <boost/version.hpp> +#include <functional> + namespace dev { namespace test @@ -32,14 +35,20 @@ namespace test struct Options: boost::noncopyable { std::string ipcPath; + boost::filesystem::path testPath; bool showMessages = false; bool optimize = false; bool disableIPC = false; bool disableSMT = false; + void validate() const; + solidity::EVMVersion evmVersion() const; + static Options const& get(); private: + std::string evmVersionString; + Options(); }; diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 69c75cee..03b1341c 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -19,7 +19,11 @@ /// @file RPCSession.cpp /// Low-level IPC communication between the test framework and the Ethereum node. -#include "RPCSession.h" +#include <test/RPCSession.h> + +#include <test/Options.h> + +#include <libsolidity/interface/EVMVersion.h> #include <libdevcore/CommonData.h> @@ -215,6 +219,13 @@ string RPCSession::personal_newAccount(string const& _password) void RPCSession::test_setChainParams(vector<string> const& _accounts) { + string forks; + if (test::Options::get().evmVersion() >= solidity::EVMVersion::tangerineWhistle()) + forks += "\"EIP150ForkBlock\": \"0x00\",\n"; + if (test::Options::get().evmVersion() >= solidity::EVMVersion::spuriousDragon()) + forks += "\"EIP158ForkBlock\": \"0x00\",\n"; + if (test::Options::get().evmVersion() >= solidity::EVMVersion::byzantium()) + forks += "\"byzantiumForkBlock\": \"0x00\",\n"; static string const c_configString = R"( { "sealEngine": "NoProof", @@ -223,9 +234,8 @@ void RPCSession::test_setChainParams(vector<string> const& _accounts) "maximumExtraDataSize": "0x1000000", "blockReward": "0x", "allowFutureBlocks": true, - "homesteadForkBlock": "0x00", - "EIP150ForkBlock": "0x00", - "EIP158ForkBlock": "0x00" + )" + forks + R"( + "homesteadForkBlock": "0x00" }, "genesis": { "author": "0000000000000010000000000000000000000000", diff --git a/test/boostTest.cpp b/test/boostTest.cpp index a3cc51c5..f16973b5 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -35,7 +35,8 @@ #pragma GCC diagnostic pop -#include <test/TestHelper.h> +#include <test/Options.h> +#include <test/libsolidity/SyntaxTest.h> using namespace boost::unit_test; @@ -54,6 +55,12 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) { master_test_suite_t& master = framework::master_test_suite(); master.p_name.value = "SolidityTests"; + dev::test::Options::get().validate(); + solAssert(dev::solidity::test::SyntaxTest::registerTests( + master, + dev::test::Options::get().testPath / "libsolidity", + "syntaxTests" + ) > 0, "no syntax tests found"); if (dev::test::Options::get().disableIPC) { for (auto suite: { diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 32456fd0..92f9569a 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -83,6 +83,21 @@ function compileWithoutWarning() test -z "$output" -a "$failed" -eq 0 } +printTask "Testing unknown options..." +( + set +e + output=$("$SOLC" --allow=test 2>&1) + failed=$? + set -e + + if [ "$output" == "unrecognised option '--allow=test'" ] && [ $failed -ne 0 ] ; then + echo "Passed" + else + printError "Incorrect response to unknown options: $STDERR" + exit 1 + fi +) + printTask "Compiling various other contracts and libraries..." ( cd "$REPO_ROOT"/test/compilationTests/ @@ -155,14 +170,14 @@ TMPDIR=$(mktemp -d) for f in *.sol do set +e - "$REPO_ROOT"/build/test/solfuzzer --quiet < "$f" + "$REPO_ROOT"/build/test/tools/solfuzzer --quiet < "$f" if [ $? -ne 0 ]; then printError "Fuzzer failed on:" cat "$f" exit 1 fi - "$REPO_ROOT"/build/test/solfuzzer --without-optimizer --quiet < "$f" + "$REPO_ROOT"/build/test/tools/solfuzzer --without-optimizer --quiet < "$f" if [ $? -ne 0 ]; then printError "Fuzzer (without optimizer) failed on:" cat "$f" diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index c9c744af..5e4991e2 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -220,13 +220,8 @@ protected: void deployRegistrar() { if (!s_compiledRegistrar) - { - m_compiler.reset(false); - m_compiler.addSource("", registrarCode); - m_compiler.setOptimiserSettings(m_optimize, m_optimizeRuns); - BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); - s_compiledRegistrar.reset(new bytes(m_compiler.object("GlobalRegistrar").bytecode)); - } + s_compiledRegistrar.reset(new bytes(compileContract(registrarCode, "GlobalRegistrar"))); + sendMessage(*s_compiledRegistrar, true); BOOST_REQUIRE(!m_output.empty()); } diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp index 8327999d..a3a27c37 100644 --- a/test/contracts/FixedFeeRegistrar.cpp +++ b/test/contracts/FixedFeeRegistrar.cpp @@ -132,13 +132,8 @@ protected: void deployRegistrar() { if (!s_compiledRegistrar) - { - m_compiler.reset(false); - m_compiler.addSource("", registrarCode); - m_compiler.setOptimiserSettings(m_optimize, m_optimizeRuns); - BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); - s_compiledRegistrar.reset(new bytes(m_compiler.object("FixedFeeRegistrar").bytecode)); - } + s_compiledRegistrar.reset(new bytes(compileContract(registrarCode, "FixedFeeRegistrar"))); + sendMessage(*s_compiledRegistrar, true); BOOST_REQUIRE(!m_output.empty()); } diff --git a/test/contracts/LLL_ENS.cpp b/test/contracts/LLL_ENS.cpp index c5fe8a82..028d58c8 100644 --- a/test/contracts/LLL_ENS.cpp +++ b/test/contracts/LLL_ENS.cpp @@ -345,7 +345,7 @@ protected: if (!s_compiledEns) { vector<string> errors; - s_compiledEns.reset(new bytes(compileLLL(ensCode, dev::test::Options::get().optimize, &errors))); + s_compiledEns.reset(new bytes(compileLLL(ensCode, dev::test::Options::get().evmVersion(), dev::test::Options::get().optimize, &errors))); BOOST_REQUIRE(errors.empty()); } sendMessage(*s_compiledEns, true); diff --git a/test/contracts/LLL_ERC20.cpp b/test/contracts/LLL_ERC20.cpp index 25665d64..60b43e4f 100644 --- a/test/contracts/LLL_ERC20.cpp +++ b/test/contracts/LLL_ERC20.cpp @@ -396,7 +396,7 @@ protected: if (!s_compiledErc20) { vector<string> errors; - s_compiledErc20.reset(new bytes(compileLLL(erc20Code, dev::test::Options::get().optimize, &errors))); + s_compiledErc20.reset(new bytes(compileLLL(erc20Code, dev::test::Options::get().evmVersion(), dev::test::Options::get().optimize, &errors))); BOOST_REQUIRE(errors.empty()); } sendMessage(*s_compiledErc20, true); diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index 90334ad6..1031e8f1 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -447,13 +447,8 @@ protected: ) { if (!s_compiledWallet) - { - m_compiler.reset(false); - m_compiler.addSource("", walletCode); - m_compiler.setOptimiserSettings(m_optimize, m_optimizeRuns); - BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); - s_compiledWallet.reset(new bytes(m_compiler.object("Wallet").bytecode)); - } + s_compiledWallet.reset(new bytes(compileContract(walletCode, "Wallet"))); + bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners); sendMessage(*s_compiledWallet + args, true, _value); BOOST_REQUIRE(!m_output.empty()); diff --git a/test/libdevcore/Checksum.cpp b/test/libdevcore/Checksum.cpp index 4eedbd99..95066b69 100644 --- a/test/libdevcore/Checksum.cpp +++ b/test/libdevcore/Checksum.cpp @@ -22,7 +22,7 @@ #include <libdevcore/Exceptions.h> -#include "../TestHelper.h" +#include <test/Options.h> using namespace std; diff --git a/test/libdevcore/IndentedWriter.cpp b/test/libdevcore/IndentedWriter.cpp index a694aa1b..916c99d0 100644 --- a/test/libdevcore/IndentedWriter.cpp +++ b/test/libdevcore/IndentedWriter.cpp @@ -20,7 +20,7 @@ #include <libdevcore/IndentedWriter.h> -#include "../TestHelper.h" +#include <test/Options.h> using namespace std; diff --git a/test/libdevcore/JSON.cpp b/test/libdevcore/JSON.cpp index 39d71b42..39d958f5 100644 --- a/test/libdevcore/JSON.cpp +++ b/test/libdevcore/JSON.cpp @@ -21,7 +21,7 @@ #include <libdevcore/JSON.h> -#include "../TestHelper.h" +#include <test/Options.h> using namespace std; diff --git a/test/libdevcore/StringUtils.cpp b/test/libdevcore/StringUtils.cpp index 597457cc..9ee93d02 100644 --- a/test/libdevcore/StringUtils.cpp +++ b/test/libdevcore/StringUtils.cpp @@ -20,7 +20,7 @@ #include <libdevcore/StringUtils.h> -#include "../TestHelper.h" +#include <test/Options.h> using namespace std; diff --git a/test/libdevcore/SwarmHash.cpp b/test/libdevcore/SwarmHash.cpp index 1ed1da18..913586f8 100644 --- a/test/libdevcore/SwarmHash.cpp +++ b/test/libdevcore/SwarmHash.cpp @@ -20,7 +20,7 @@ #include <libdevcore/SwarmHash.h> -#include "../TestHelper.h" +#include <test/Options.h> using namespace std; diff --git a/test/libdevcore/UTF8.cpp b/test/libdevcore/UTF8.cpp index 719ada72..6de4570f 100644 --- a/test/libdevcore/UTF8.cpp +++ b/test/libdevcore/UTF8.cpp @@ -21,7 +21,7 @@ #include <libdevcore/CommonData.h> #include <libdevcore/UTF8.h> -#include "../TestHelper.h" +#include <test/Options.h> using namespace std; diff --git a/test/libdevcore/Whiskers.cpp b/test/libdevcore/Whiskers.cpp index 84149173..b12acdd7 100644 --- a/test/libdevcore/Whiskers.cpp +++ b/test/libdevcore/Whiskers.cpp @@ -20,7 +20,7 @@ #include <libdevcore/Whiskers.h> -#include "../TestHelper.h" +#include <test/Options.h> using namespace std; diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 0ab95b08..f630b304 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -20,6 +20,8 @@ * Tests for the Solidity optimizer. */ +#include <test/Options.h> + #include <libevmasm/CommonSubexpressionEliminator.h> #include <libevmasm/PeepholeOptimiser.h> #include <libevmasm/JumpdestRemover.h> @@ -916,7 +918,7 @@ BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies) main.append(t1.toSubAssemblyTag(subId)); main.append(u256(8)); - main.optimise(true); + main.optimise(true, dev::test::Options::get().evmVersion()); AssemblyItems expectationMain{ AssemblyItem(PushSubSize, 0), diff --git a/test/libevmasm/SourceLocation.cpp b/test/libevmasm/SourceLocation.cpp index 6889b3e6..764da3cd 100644 --- a/test/libevmasm/SourceLocation.cpp +++ b/test/libevmasm/SourceLocation.cpp @@ -22,7 +22,7 @@ #include <libevmasm/SourceLocation.h> -#include "../TestHelper.h" +#include <test/Options.h> namespace dev { diff --git a/test/libjulia/Common.cpp b/test/libjulia/Common.cpp index 7053a68d..24519b01 100644 --- a/test/libjulia/Common.cpp +++ b/test/libjulia/Common.cpp @@ -21,6 +21,8 @@ #include <test/libjulia/Common.h> +#include <test/Options.h> + #include <libjulia/optimiser/Disambiguator.h> #include <libsolidity/parsing/Scanner.h> @@ -61,7 +63,13 @@ pair<shared_ptr<Block>, shared_ptr<assembly::AsmAnalysisInfo>> dev::julia::test: { BOOST_REQUIRE(errorReporter.errors().empty()); auto analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); - assembly::AsmAnalyzer analyzer(*analysisInfo, errorReporter, flavour); + assembly::AsmAnalyzer analyzer( + *analysisInfo, + errorReporter, + dev::test::Options::get().evmVersion(), + boost::none, + flavour + ); if (analyzer.analyze(*parserResult)) { BOOST_REQUIRE(errorReporter.errors().empty()); diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index ff9474c1..9d66658e 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -19,7 +19,7 @@ * Unit tests for parsing Julia. */ -#include "../TestHelper.h" +#include <test/Options.h> #include <test/libsolidity/ErrorCheck.h> @@ -56,7 +56,13 @@ bool parse(string const& _source, ErrorReporter& errorReporter) if (parserResult) { assembly::AsmAnalysisInfo analysisInfo; - return (assembly::AsmAnalyzer(analysisInfo, errorReporter, assembly::AsmFlavour::IULIA)).analyze(*parserResult); + return (assembly::AsmAnalyzer( + analysisInfo, + errorReporter, + dev::test::Options::get().evmVersion(), + boost::none, + assembly::AsmFlavour::IULIA + )).analyze(*parserResult); } } catch (FatalError const&) diff --git a/test/liblll/Compiler.cpp b/test/liblll/Compiler.cpp index ace97e15..ebdea185 100644 --- a/test/liblll/Compiler.cpp +++ b/test/liblll/Compiler.cpp @@ -20,11 +20,16 @@ * Unit tests for the LLL compiler. */ +#include <test/Options.h> + +#include <libdevcore/FixedHash.h> + +#include <liblll/Compiler.h> + +#include <boost/test/unit_test.hpp> + #include <string> #include <memory> -#include <boost/test/unit_test.hpp> -#include <liblll/Compiler.h> -#include <libdevcore/FixedHash.h> using namespace std; @@ -41,7 +46,7 @@ namespace bool successCompile(string const& _sourceCode) { vector<string> errors; - bytes bytecode = eth::compileLLL(_sourceCode, false, &errors); + bytes bytecode = eth::compileLLL(_sourceCode, dev::test::Options::get().evmVersion(), false, &errors); if (!errors.empty()) return false; if (bytecode.empty()) @@ -353,7 +358,7 @@ BOOST_AUTO_TEST_CASE(valid_opcodes_functional) for (size_t i = 0; i < opcodes_bytecode.size(); i++) { vector<string> errors; - bytes code = eth::compileLLL(opcodes_lll[i], false, &errors); + bytes code = eth::compileLLL(opcodes_lll[i], dev::test::Options::get().evmVersion(), false, &errors); BOOST_REQUIRE_MESSAGE(errors.empty(), opcodes_lll[i]); @@ -641,7 +646,7 @@ BOOST_AUTO_TEST_CASE(valid_opcodes_asm) for (size_t i = 0; i < opcodes_bytecode.size(); i++) { vector<string> errors; - bytes code = eth::compileLLL(opcodes_lll[i], false, &errors); + bytes code = eth::compileLLL(opcodes_lll[i], dev::test::Options::get().evmVersion(), false, &errors); BOOST_REQUIRE_MESSAGE(errors.empty(), opcodes_lll[i]); diff --git a/test/liblll/EndToEndTest.cpp b/test/liblll/EndToEndTest.cpp index 1a5bb490..fd8099f2 100644 --- a/test/liblll/EndToEndTest.cpp +++ b/test/liblll/EndToEndTest.cpp @@ -20,10 +20,13 @@ * End to end tests for LLL. */ +#include <test/liblll/ExecutionFramework.h> +#include <test/Options.h> + +#include <boost/test/unit_test.hpp> + #include <string> #include <memory> -#include <boost/test/unit_test.hpp> -#include <test/liblll/ExecutionFramework.h> using namespace std; @@ -583,24 +586,34 @@ BOOST_AUTO_TEST_CASE(allgas) BOOST_AUTO_TEST_CASE(send_two_args) { - char const* sourceCode = R"( - (returnlll - (send 0xdead 42)) - )"; - compileAndRun(sourceCode); - callFallbackWithValue(42); - BOOST_CHECK(balanceAt(Address(0xdead)) == 42); + // "send" does not retain enough gas to be able to pay for account creation. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (send 0xdead 42)) + )"; + compileAndRun(sourceCode); + callFallbackWithValue(42); + BOOST_CHECK(balanceAt(Address(0xdead)) == 42); + } } BOOST_AUTO_TEST_CASE(send_three_args) { - char const* sourceCode = R"( - (returnlll - (send allgas 0xdead 42)) - )"; - compileAndRun(sourceCode); - callFallbackWithValue(42); - BOOST_CHECK(balanceAt(Address(0xdead)) == 42); + // "send" does not retain enough gas to be able to pay for account creation. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (send allgas 0xdead 42)) + )"; + compileAndRun(sourceCode); + callFallbackWithValue(42); + BOOST_CHECK(balanceAt(Address(0xdead)) == 42); + } } // Regression test for edge case that previously failed @@ -708,56 +721,76 @@ BOOST_AUTO_TEST_CASE(msg_four_args) BOOST_AUTO_TEST_CASE(msg_three_args) { - char const* sourceCode = R"( - (returnlll - (seq - (when (= 0 (calldatasize)) - (return (msg (address) 42 0xff))) - (return (callvalue)))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42))); + // "msg" does not retain enough gas. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (seq + (when (= 0 (calldatasize)) + (return (msg (address) 42 0xff))) + (return (callvalue)))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42))); + } } BOOST_AUTO_TEST_CASE(msg_two_args) { - char const* sourceCode = R"( - (returnlll - (seq - (when (= 0 (calldatasize)) - (return (msg (address) 0xff))) - (return 42))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(42))); + // "msg" does not retain enough gas. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (seq + (when (= 0 (calldatasize)) + (return (msg (address) 0xff))) + (return 42))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallback() == encodeArgs(u256(42))); + } } BOOST_AUTO_TEST_CASE(create_one_arg) { - char const* sourceCode = R"( - (returnlll - (seq - (call allgas - (create (returnlll (return 42))) - 0 0 0 0x00 0x20) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(u256(42))); + // "call" does not retain enough gas. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (seq + (call allgas + (create (returnlll (return 42))) + 0 0 0 0x00 0x20) + (return 0x00 0x20))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallback() == encodeArgs(u256(42))); + } } BOOST_AUTO_TEST_CASE(create_two_args) { - char const* sourceCode = R"( - (returnlll - (seq - (call allgas - (create 42 (returnlll (return (balance (address))))) - 0 0 0 0x00 0x20) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42))); + // "call" does not retain enough gas. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (seq + (call allgas + (create 42 (returnlll (return (balance (address))))) + 0 0 0 0x00 0x20) + (return 0x00 0x20))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallbackWithValue(42) == encodeArgs(u256(42))); + } } BOOST_AUTO_TEST_CASE(sha3_two_args) @@ -822,77 +855,102 @@ BOOST_AUTO_TEST_CASE(makeperm) // Covers makeperm (implicit), permcount and perm BOOST_AUTO_TEST_CASE(ecrecover) { - char const* sourceCode = R"( - (returnlll - (return - (ecrecover - ; Hash of 'hello world' - 0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad - ; v = 1 + 27 - 0x1c - ; r - 0xdebaaa0cddb321b2dcaaf846d39605de7b97e77ba6106587855b9106cb104215 - ; s - 0x61a22d94fa8b8a687ff9c911c844d1c016d1a685a9166858f9c7c1bc85128aca))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs(fromHex("0x8743523d96a1b2cbe0c6909653a56da18ed484af"))); + // "ecrecover" does not retain enough gas. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (return + (ecrecover + ; Hash of 'hello world' + 0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad + ; v = 1 + 27 + 0x1c + ; r + 0xdebaaa0cddb321b2dcaaf846d39605de7b97e77ba6106587855b9106cb104215 + ; s + 0x61a22d94fa8b8a687ff9c911c844d1c016d1a685a9166858f9c7c1bc85128aca))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallback() == encodeArgs(fromHex("0x8743523d96a1b2cbe0c6909653a56da18ed484af"))); + } } BOOST_AUTO_TEST_CASE(sha256_two_args) { - char const* sourceCode = R"( - (returnlll - (seq - (lit 0x20 "abcdefghijklmnopqrstuvwxyzABCDEF") - (lit 0x40 "GHIJKLMNOPQRSTUVWXYZ0123456789?!") - (sha256 0x20 0x40) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0xcf25a9fe3d86ae228c226c81d2d8c64c687cd6dc4586d10d8e7e4e5b6706d429"))); + // "sha256" does not retain enough gas. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (seq + (lit 0x20 "abcdefghijklmnopqrstuvwxyzABCDEF") + (lit 0x40 "GHIJKLMNOPQRSTUVWXYZ0123456789?!") + (sha256 0x20 0x40) + (return 0x00 0x20))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallback() == encodeArgs( + fromHex("0xcf25a9fe3d86ae228c226c81d2d8c64c687cd6dc4586d10d8e7e4e5b6706d429"))); + } } BOOST_AUTO_TEST_CASE(ripemd160_two_args) { - char const* sourceCode = R"( - (returnlll - (seq - (lit 0x20 "abcdefghijklmnopqrstuvwxyzABCDEF") - (lit 0x40 "GHIJKLMNOPQRSTUVWXYZ0123456789?!") - (ripemd160 0x20 0x40) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0x36c6b90a49e17d4c1e1b0e634ec74124d9b207da"))); + // "ripemd160" does not retain enough gas. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (seq + (lit 0x20 "abcdefghijklmnopqrstuvwxyzABCDEF") + (lit 0x40 "GHIJKLMNOPQRSTUVWXYZ0123456789?!") + (ripemd160 0x20 0x40) + (return 0x00 0x20))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallback() == encodeArgs( + fromHex("0x36c6b90a49e17d4c1e1b0e634ec74124d9b207da"))); + } } BOOST_AUTO_TEST_CASE(sha256_one_arg) { - char const* sourceCode = R"( - (returnlll - (seq - (sha256 0x6162636465666768696a6b6c6d6e6f707172737475767778797a414243444546) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0xcfd2f1fad75a1978da0a444883db7251414b139f31f5a04704c291fdb0e175e6"))); + // "sha256" does not retain enough gas. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (seq + (sha256 0x6162636465666768696a6b6c6d6e6f707172737475767778797a414243444546) + (return 0x00 0x20))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallback() == encodeArgs( + fromHex("0xcfd2f1fad75a1978da0a444883db7251414b139f31f5a04704c291fdb0e175e6"))); + } } BOOST_AUTO_TEST_CASE(ripemd160_one_arg) { - char const* sourceCode = R"( - (returnlll - (seq - (ripemd160 0x6162636465666768696a6b6c6d6e6f707172737475767778797a414243444546) - (return 0x00 0x20))) - )"; - compileAndRun(sourceCode); - BOOST_CHECK(callFallback() == encodeArgs( - fromHex("0xac5ab22e07b0fb80c69b6207902f725e2507e546"))); + // "ripemd160" does not retain enough gas. + // Disabling for non-tangerineWhistle VMs. + if (dev::test::Options::get().evmVersion().canOverchargeGasForCall()) + { + char const* sourceCode = R"( + (returnlll + (seq + (ripemd160 0x6162636465666768696a6b6c6d6e6f707172737475767778797a414243444546) + (return 0x00 0x20))) + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callFallback() == encodeArgs( + fromHex("0xac5ab22e07b0fb80c69b6207902f725e2507e546"))); + } } BOOST_AUTO_TEST_CASE(wei_szabo_finney_ether) diff --git a/test/liblll/ExecutionFramework.h b/test/liblll/ExecutionFramework.h index 58e1f0ad..ae5cd988 100644 --- a/test/liblll/ExecutionFramework.h +++ b/test/liblll/ExecutionFramework.h @@ -56,7 +56,7 @@ public: BOOST_REQUIRE(_libraryAddresses.empty()); std::vector<std::string> errors; - bytes bytecode = eth::compileLLL(_sourceCode, m_optimize, &errors); + bytes bytecode = eth::compileLLL(_sourceCode, dev::test::Options::get().evmVersion(), m_optimize, &errors); if (!errors.empty()) { for (auto const& error: errors) diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index a165f7a9..b44dd331 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -20,12 +20,16 @@ * Tests for the json ast output. */ -#include <string> -#include <boost/test/unit_test.hpp> +#include <test/Options.h> + #include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/CompilerStack.h> #include <libsolidity/ast/ASTJsonConverter.h> +#include <boost/test/unit_test.hpp> + +#include <string> + using namespace std; namespace dev @@ -41,6 +45,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) { CompilerStack c; c.addSource("a", "contract C {}"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -52,6 +57,7 @@ BOOST_AUTO_TEST_CASE(source_location) { CompilerStack c; c.addSource("a", "contract C { function f() { var x = 2; x++; } }"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -66,6 +72,7 @@ BOOST_AUTO_TEST_CASE(inheritance_specifier) { CompilerStack c; c.addSource("a", "contract C1 {} contract C2 is C1 {}"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -81,6 +88,7 @@ BOOST_AUTO_TEST_CASE(using_for_directive) { CompilerStack c; c.addSource("a", "library L {} contract C { using L for uint; }"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -98,6 +106,7 @@ BOOST_AUTO_TEST_CASE(enum_value) { CompilerStack c; c.addSource("a", "contract C { enum E { A, B } }"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -115,6 +124,7 @@ BOOST_AUTO_TEST_CASE(modifier_definition) { CompilerStack c; c.addSource("a", "contract C { modifier M(uint i) { _; } function F() M(1) {} }"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -129,6 +139,7 @@ BOOST_AUTO_TEST_CASE(modifier_invocation) { CompilerStack c; c.addSource("a", "contract C { modifier M(uint i) { _; } function F() M(1) {} }"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -145,6 +156,7 @@ BOOST_AUTO_TEST_CASE(event_definition) { CompilerStack c; c.addSource("a", "contract C { event E(); }"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -159,6 +171,7 @@ BOOST_AUTO_TEST_CASE(array_type_name) { CompilerStack c; c.addSource("a", "contract C { uint[] i; }"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -172,6 +185,7 @@ BOOST_AUTO_TEST_CASE(placeholder_statement) { CompilerStack c; c.addSource("a", "contract C { modifier M { _; } }"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -185,6 +199,7 @@ BOOST_AUTO_TEST_CASE(non_utf8) { CompilerStack c; c.addSource("a", "contract C { function f() { var x = hex\"ff\"; } }"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -204,6 +219,7 @@ BOOST_AUTO_TEST_CASE(function_type) "contract C { function f(function() external payable returns (uint) x) " "returns (function() external constant returns (uint)) {} }" ); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 1; @@ -244,6 +260,7 @@ BOOST_AUTO_TEST_CASE(documentation) " /** Some comment on fn.*/ function fn() public {}" "}" ); + c.setEVMVersion(dev::test::Options::get().evmVersion()); c.parseAndAnalyze(); map<string, unsigned> sourceIndices; sourceIndices["a"] = 0; diff --git a/test/libsolidity/AnalysisFramework.cpp b/test/libsolidity/AnalysisFramework.cpp index a27e3222..4538757d 100644 --- a/test/libsolidity/AnalysisFramework.cpp +++ b/test/libsolidity/AnalysisFramework.cpp @@ -20,6 +20,8 @@ #include <test/libsolidity/AnalysisFramework.h> +#include <test/Options.h> + #include <libsolidity/interface/CompilerStack.h> #include <libsolidity/interface/SourceReferenceFormatter.h> @@ -46,6 +48,7 @@ AnalysisFramework::parseAnalyseAndReturnError( { m_compiler.reset(); m_compiler.addSource("", _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source); + m_compiler.setEVMVersion(dev::test::Options::get().evmVersion()); if (!m_compiler.parse()) { BOOST_ERROR("Parsing contract failed in analysis test suite:" + formatErrors()); diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 59af6d41..5519ae0d 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -20,11 +20,11 @@ * Unit tests for Assembly Items from evmasm/Assembly.h */ -#include <string> -#include <iostream> -#include <boost/test/unit_test.hpp> +#include <test/Options.h> + #include <libevmasm/SourceLocation.h> #include <libevmasm/Assembly.h> + #include <libsolidity/parsing/Scanner.h> #include <libsolidity/parsing/Parser.h> #include <libsolidity/analysis/NameAndTypeResolver.h> @@ -33,6 +33,11 @@ #include <libsolidity/analysis/TypeChecker.h> #include <libsolidity/interface/ErrorReporter.h> +#include <boost/test/unit_test.hpp> + +#include <string> +#include <iostream> + using namespace std; using namespace dev::eth; @@ -46,7 +51,7 @@ namespace test namespace { -eth::AssemblyItems compileContract(const string& _sourceCode) +eth::AssemblyItems compileContract(string const& _sourceCode) { ErrorList errors; ErrorReporter errorReporter(errors); @@ -69,7 +74,7 @@ eth::AssemblyItems compileContract(const string& _sourceCode) for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) { - TypeChecker checker(errorReporter); + TypeChecker checker(dev::test::Options::get().evmVersion(), errorReporter); BOOST_REQUIRE_NO_THROW(checker.checkTypeRequirements(*contract)); if (!Error::containsOnlyWarnings(errorReporter.errors())) return AssemblyItems(); @@ -77,7 +82,7 @@ eth::AssemblyItems compileContract(const string& _sourceCode) for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) { - Compiler compiler; + Compiler compiler(dev::test::Options::get().evmVersion()); compiler.compileContract(*contract, map<ContractDefinition const*, Assembly const*>{}, bytes()); return compiler.runtimeAssemblyItems(); diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 9d3409dd..fd2017f9 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -49,13 +49,14 @@ public: m_compiler.reset(false); m_compiler.addSource("", "pragma solidity >=0.0;\n" + _sourceCode); m_compiler.setOptimiserSettings(dev::test::Options::get().optimize); + m_compiler.setEVMVersion(m_evmVersion); BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); AssemblyItems const* items = m_compiler.runtimeAssemblyItems(m_compiler.lastContractName()); ASTNode const& sourceUnit = m_compiler.ast(""); BOOST_REQUIRE(items != nullptr); m_gasCosts = GasEstimator::breakToStatementLevel( - GasEstimator::structuralEstimation(*items, vector<ASTNode const*>({&sourceUnit})), + GasEstimator(dev::test::Options::get().evmVersion()).structuralEstimation(*items, vector<ASTNode const*>({&sourceUnit})), {&sourceUnit} ); } @@ -64,7 +65,7 @@ public: { compileAndRun(_sourceCode); auto state = make_shared<KnownState>(); - PathGasMeter meter(*m_compiler.assemblyItems(m_compiler.lastContractName())); + PathGasMeter meter(*m_compiler.assemblyItems(m_compiler.lastContractName()), dev::test::Options::get().evmVersion()); GasMeter::GasConsumption gas = meter.estimateMax(0, state); u256 bytecodeSize(m_compiler.runtimeObject(m_compiler.lastContractName()).bytecode.size()); // costs for deployment @@ -73,7 +74,7 @@ public: gas += gasForTransaction(m_compiler.object(m_compiler.lastContractName()).bytecode, true); BOOST_REQUIRE(!gas.isInfinite); - BOOST_CHECK(gas.value == m_gasUsed); + BOOST_CHECK_EQUAL(gas.value, m_gasUsed); } /// Compares the gas computed by PathGasMeter for the given signature (but unknown arguments) @@ -90,12 +91,12 @@ public: gas = max(gas, gasForTransaction(hash.asBytes() + arguments, false)); } - gas += GasEstimator::functionalEstimation( + gas += GasEstimator(dev::test::Options::get().evmVersion()).functionalEstimation( *m_compiler.runtimeAssemblyItems(m_compiler.lastContractName()), _sig ); BOOST_REQUIRE(!gas.isInfinite); - BOOST_CHECK(gas.value == m_gasUsed); + BOOST_CHECK_EQUAL(gas.value, m_gasUsed); } static GasMeter::GasConsumption gasForTransaction(bytes const& _data, bool _isCreation) diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index dc1174f4..1b5dd4a5 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -21,6 +21,7 @@ */ #include <test/libsolidity/ErrorCheck.h> +#include <test/Options.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/CompilerStack.h> @@ -44,6 +45,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) { CompilerStack c; c.addSource("a", "contract C {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -52,6 +54,7 @@ BOOST_AUTO_TEST_CASE(regular_import) CompilerStack c; c.addSource("a", "contract C {} pragma solidity >=0.0;"); c.addSource("b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -60,6 +63,7 @@ BOOST_AUTO_TEST_CASE(import_does_not_clutter_importee) CompilerStack c; c.addSource("a", "contract C { D d; } pragma solidity >=0.0;"); c.addSource("b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -69,6 +73,7 @@ BOOST_AUTO_TEST_CASE(import_is_transitive) c.addSource("a", "contract C { } pragma solidity >=0.0;"); c.addSource("b", "import \"a\"; pragma solidity >=0.0;"); c.addSource("c", "import \"b\"; contract D is C {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -77,6 +82,7 @@ BOOST_AUTO_TEST_CASE(circular_import) CompilerStack c; c.addSource("a", "import \"b\"; contract C { D d; } pragma solidity >=0.0;"); c.addSource("b", "import \"a\"; contract D { C c; } pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -86,6 +92,7 @@ BOOST_AUTO_TEST_CASE(relative_import) c.addSource("a", "import \"./dir/b\"; contract A is B {} pragma solidity >=0.0;"); c.addSource("dir/b", "contract B {} pragma solidity >=0.0;"); c.addSource("dir/c", "import \"../a\"; contract C is A {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -94,6 +101,7 @@ BOOST_AUTO_TEST_CASE(relative_import_multiplex) CompilerStack c; c.addSource("a", "contract A {} pragma solidity >=0.0;"); c.addSource("dir/a/b/c", "import \"../../.././a\"; contract B is A {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -102,6 +110,7 @@ BOOST_AUTO_TEST_CASE(simple_alias) CompilerStack c; c.addSource("a", "contract A {} pragma solidity >=0.0;"); c.addSource("dir/a/b/c", "import \"../../.././a\" as x; contract B is x.A { function() { x.A r = x.A(20); } } pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -111,6 +120,7 @@ BOOST_AUTO_TEST_CASE(library_name_clash) c.addSource("a", "library A {} pragma solidity >=0.0;"); c.addSource("b", "library A {} pragma solidity >=0.0;"); c.addSource("c", "import {A} from \"./a\"; import {A} from \"./b\";"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -119,6 +129,7 @@ BOOST_AUTO_TEST_CASE(library_name_clash_with_contract) CompilerStack c; c.addSource("a", "contract A {} pragma solidity >=0.0;"); c.addSource("b", "library A {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -128,6 +139,7 @@ BOOST_AUTO_TEST_CASE(complex_import) c.addSource("a", "contract A {} contract B {} contract C { struct S { uint a; } } pragma solidity >=0.0;"); c.addSource("b", "import \"a\" as x; import {B as b, C as c, C} from \"a\"; " "contract D is b { function f(c.S var1, x.C.S var2, C.S var3) internal {} } pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -136,14 +148,19 @@ BOOST_AUTO_TEST_CASE(name_clash_in_import) CompilerStack c; c.addSource("a", "contract A {} pragma solidity >=0.0;"); c.addSource("b", "import \"a\"; contract A {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); c.addSource("b", "import \"a\" as A; contract A {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); c.addSource("b", "import {A as b} from \"a\"; contract b {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); c.addSource("b", "import {A} from \"a\"; contract A {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); c.addSource("b", "import {A} from \"a\"; contract B {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -155,6 +172,7 @@ BOOST_AUTO_TEST_CASE(remappings) c.addSource("b", "import \"t/tee.sol\"; contract A is Tee {} pragma solidity >=0.0;"); c.addSource("s_1.4.6/s.sol", "contract S {} pragma solidity >=0.0;"); c.addSource("Tee/tee.sol", "contract Tee {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -166,6 +184,7 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings) c.addSource("b/b.sol", "import \"s/s.sol\"; contract B is SSeven {} pragma solidity >=0.0;"); c.addSource("s_1.4.6/s.sol", "contract SSix {} pragma solidity >=0.0;"); c.addSource("s_1.4.7/s.sol", "contract SSeven {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -174,6 +193,7 @@ BOOST_AUTO_TEST_CASE(filename_with_period) CompilerStack c; c.addSource("a/a.sol", "import \".b.sol\"; contract A is B {} pragma solidity >=0.0;"); c.addSource("a/.b.sol", "contract B {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -185,6 +205,7 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_ensure_default_and_module_pres c.addSource("vendor/bar/bar.sol", "import \"foo/foo.sol\"; contract Bar {Foo1 foo;} pragma solidity >=0.0;"); c.addSource("vendor/foo_1.0.0/foo.sol", "contract Foo1 {} pragma solidity >=0.0;"); c.addSource("vendor/foo_2.0.0/foo.sol", "contract Foo2 {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); } @@ -196,6 +217,7 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent) c.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); c.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;"); c.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); CompilerStack d; d.setRemappings(vector<string>{"a/b:x=e", "a:x/y/z=d"}); @@ -203,6 +225,7 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent) d.addSource("a/b/main.sol", "import \"x/y/z/z.sol\"; contract Main is E {} pragma solidity >=0.0;"); d.addSource("d/z.sol", "contract D {} pragma solidity >=0.0;"); d.addSource("e/y/z/z.sol", "contract E {} pragma solidity >=0.0;"); + d.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(d.compile()); } @@ -212,6 +235,7 @@ BOOST_AUTO_TEST_CASE(shadowing_via_import) c.addSource("a", "library A {} pragma solidity >=0.0;"); c.addSource("b", "library A {} pragma solidity >=0.0;"); c.addSource("c", "import {A} from \"./a\"; import {A} from \"./b\";"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!c.compile()); } @@ -225,6 +249,7 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_imports) contract C { } )"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); size_t errorCount = 0; for (auto const& e: c.errors()) @@ -251,6 +276,7 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_multiple_imports) contract C { } )"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); auto numErrors = c.errors().size(); // Sometimes we get the prerelease warning, sometimes not. @@ -274,6 +300,7 @@ BOOST_AUTO_TEST_CASE(shadowing_builtins_with_alias) pragma solidity >=0.0; import {C as msg} from "B.sol"; )"); + c.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(c.compile()); auto numErrors = c.errors().size(); // Sometimes we get the prerelease warning, sometimes not. diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index ea120657..34ca33e3 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -20,7 +20,7 @@ * Unit tests for inline assembly. */ -#include "../TestHelper.h" +#include <test/Options.h> #include <libsolidity/interface/AssemblyStack.h> #include <libsolidity/parsing/Scanner.h> @@ -55,7 +55,7 @@ boost::optional<Error> parseAndReturnFirstError( AssemblyStack::Machine _machine = AssemblyStack::Machine::EVM ) { - AssemblyStack stack(_language); + AssemblyStack stack(dev::test::Options::get().evmVersion(), _language); bool success = false; try { @@ -117,7 +117,7 @@ Error expectError( void parsePrintCompare(string const& _source, bool _canWarn = false) { - AssemblyStack stack; + AssemblyStack stack(dev::test::Options::get().evmVersion()); BOOST_REQUIRE(stack.parseAndAnalyze("", _source)); if (_canWarn) BOOST_REQUIRE(Error::containsOnlyWarnings(stack.errors())); @@ -567,7 +567,7 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode) { string source = "{ let x := \"\\u1bac\" }"; string parsed = "{\n let x := \"\\xe1\\xae\\xac\"\n}"; - AssemblyStack stack; + AssemblyStack stack(dev::test::Options::get().evmVersion()); BOOST_REQUIRE(stack.parseAndAnalyze("", source)); BOOST_REQUIRE(stack.errors().empty()); BOOST_CHECK_EQUAL(stack.print(), parsed); @@ -783,9 +783,9 @@ BOOST_AUTO_TEST_CASE(shift) BOOST_AUTO_TEST_CASE(shift_constantinople_warning) { - CHECK_PARSE_WARNING("{ pop(shl(10, 32)) }", Warning, "The \"shl\" instruction is only available after the Constantinople hard fork"); - CHECK_PARSE_WARNING("{ pop(shr(10, 32)) }", Warning, "The \"shr\" instruction is only available after the Constantinople hard fork"); - CHECK_PARSE_WARNING("{ pop(sar(10, 32)) }", Warning, "The \"sar\" instruction is only available after the Constantinople hard fork"); + CHECK_PARSE_WARNING("{ pop(shl(10, 32)) }", Warning, "The \"shl\" instruction is only available for Constantinople-compatible VMs."); + CHECK_PARSE_WARNING("{ pop(shr(10, 32)) }", Warning, "The \"shr\" instruction is only available for Constantinople-compatible VMs."); + CHECK_PARSE_WARNING("{ pop(sar(10, 32)) }", Warning, "The \"sar\" instruction is only available for Constantinople-compatible VMs."); } BOOST_AUTO_TEST_CASE(jump_warning) diff --git a/test/libsolidity/JSONCompiler.cpp b/test/libsolidity/JSONCompiler.cpp index 285c5604..aed0a370 100644 --- a/test/libsolidity/JSONCompiler.cpp +++ b/test/libsolidity/JSONCompiler.cpp @@ -25,8 +25,8 @@ #include <libsolidity/interface/Version.h> #include <libsolc/libsolc.h> -#include "../Metadata.h" -#include "../TestHelper.h" +#include <test/Metadata.h> +#include <test/Options.h> using namespace std; diff --git a/test/libsolidity/Metadata.cpp b/test/libsolidity/Metadata.cpp index 47cf1d3d..808bd1e1 100644 --- a/test/libsolidity/Metadata.cpp +++ b/test/libsolidity/Metadata.cpp @@ -19,8 +19,8 @@ * Unit tests for the metadata output. */ -#include "../Metadata.h" -#include "../TestHelper.h" +#include <test/Metadata.h> +#include <test/Options.h> #include <libsolidity/interface/CompilerStack.h> #include <libdevcore/SwarmHash.h> #include <libdevcore/JSON.h> @@ -46,6 +46,7 @@ BOOST_AUTO_TEST_CASE(metadata_stamp) )"; CompilerStack compilerStack; compilerStack.addSource("", std::string(sourceCode)); + compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); bytes const& bytecode = compilerStack.runtimeObject("test").bytecode; @@ -72,6 +73,7 @@ BOOST_AUTO_TEST_CASE(metadata_stamp_experimental) )"; CompilerStack compilerStack; compilerStack.addSource("", std::string(sourceCode)); + compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); bytes const& bytecode = compilerStack.runtimeObject("test").bytecode; @@ -106,6 +108,7 @@ BOOST_AUTO_TEST_CASE(metadata_relevant_sources) } )"; compilerStack.addSource("B", std::string(sourceCode)); + compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); @@ -144,6 +147,7 @@ BOOST_AUTO_TEST_CASE(metadata_relevant_sources_imports) } )"; compilerStack.addSource("C", std::string(sourceCode)); + compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index 5088ab94..beb933a4 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -329,6 +329,144 @@ BOOST_AUTO_TEST_CASE(ways_to_merge_variables) CHECK_WARNING(text, "Assertion violation happens here"); } +BOOST_AUTO_TEST_CASE(bool_simple) +{ + string text = R"( + contract C { + function f(bool x) public pure { + assert(x); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); + text = R"( + contract C { + function f(bool x, bool y) public pure { + assert(x == y); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); + text = R"( + contract C { + function f(bool x, bool y) public pure { + bool z = x || y; + assert(!(x && y) || z); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(bool x) public pure { + if(x) { + assert(x); + } else { + assert(!x); + } + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(bool x) public pure { + bool y = x; + assert(x == y); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(bool x) public pure { + require(x); + bool y; + y = false; + assert(x || y); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(bool x) public pure { + bool y; + assert(x <= y); + } + } + )"; + CHECK_WARNING(text, "Assertion violation happens here"); + text = R"( + contract C { + function f(bool x) public pure { + bool y; + assert(x >= y); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(bool x) public pure { + require(x); + bool y; + assert(x > y); + assert(y < x); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(bool_int_mixed) +{ + string text = R"( + contract C { + function f(bool x) public pure { + uint a; + if(x) + a = 1; + assert(!x || a > 0); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(bool x, uint a) public pure { + require(!x || a > 0); + uint b = a; + assert(!x || b > 0); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + text = R"( + contract C { + function f(bool x, bool y) public pure { + uint a; + if (x) { + if (y) { + a = 0; + } else { + a = 1; + } + } else { + if (y) { + a = 1; + } else { + a = 0; + } + } + bool xor_x_y = (x && !y) || (!x && y); + assert(!xor_x_y || a > 0); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_CASE(while_loop_simple) { // Check that variables are cleared @@ -466,7 +604,8 @@ BOOST_AUTO_TEST_CASE(for_loop) text = R"( contract C { function f(uint x) public pure { - for (uint y = 2; x < 10; ) { + uint y; + for (y = 2; x < 10; ) { y = 3; } assert(y == 3); @@ -477,7 +616,8 @@ BOOST_AUTO_TEST_CASE(for_loop) text = R"( contract C { function f(uint x) public pure { - for (uint y = 2; x < 10; ) { + uint y; + for (y = 2; x < 10; ) { y = 3; } assert(y == 2); diff --git a/test/libsolidity/SemVerMatcher.cpp b/test/libsolidity/SemVerMatcher.cpp index 08ef5277..07f8fba6 100644 --- a/test/libsolidity/SemVerMatcher.cpp +++ b/test/libsolidity/SemVerMatcher.cpp @@ -25,7 +25,7 @@ #include <tuple> #include <libsolidity/parsing/Scanner.h> #include <libsolidity/analysis/SemVerHandler.h> -#include "../TestHelper.h" +#include <test/Options.h> using namespace std; diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index e242508a..107abc26 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -20,7 +20,7 @@ * Unit tests for the solidity compiler JSON Interface output. */ -#include "../TestHelper.h" +#include <test/Options.h> #include <libsolidity/interface/CompilerStack.h> #include <libdevcore/Exceptions.h> @@ -44,6 +44,8 @@ public: { m_compilerStack.reset(false); m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); + m_compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); + m_compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(m_compilerStack.parseAndAnalyze(), "Parsing contract failed"); Json::Value generatedInterface = m_compilerStack.contractABI(m_compilerStack.lastContractName()); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 3882e4ea..44dc40f7 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -21,13 +21,20 @@ * Unit tests for the solidity expression compiler, testing the behaviour of the code. */ +#include <test/libsolidity/SolidityExecutionFramework.h> + +#include <test/Options.h> + +#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/EVMVersion.h> + +#include <libevmasm/Assembly.h> + +#include <boost/test/unit_test.hpp> + #include <functional> #include <string> #include <tuple> -#include <boost/test/unit_test.hpp> -#include <libevmasm/Assembly.h> -#include <libsolidity/interface/Exceptions.h> -#include <test/libsolidity/SolidityExecutionFramework.h> using namespace std; using namespace std::placeholders; @@ -284,6 +291,54 @@ BOOST_AUTO_TEST_CASE(conditional_expression_functions) ABI_CHECK(callContractFunction("f(bool)", false), encodeArgs(u256(2))); } +BOOST_AUTO_TEST_CASE(C99_scoping_activation) +{ + char const* sourceCode = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public returns (uint) { + uint x = 7; + { + x = 3; // This should still assign to the outer variable + uint x; + x = 4; // This should assign to the new one + } + return x; + } + function g() pure public returns (uint x) { + x = 7; + { + x = 3; + uint x; + return x; // This returns the new variable, i.e. 0 + } + } + function h() pure public returns (uint x, uint a, uint b) { + x = 7; + { + x = 3; + a = x; // This should read from the outer + uint x = 4; + b = x; + } + } + function i() pure public returns (uint x, uint a) { + x = 7; + { + x = 3; + uint x = x; // This should read from the outer and assign to the inner + a = x; + } + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("g()"), encodeArgs(0)); + ABI_CHECK(callContractFunction("h()"), encodeArgs(3, 3, 4)); + ABI_CHECK(callContractFunction("i()"), encodeArgs(3, 3)); +} + BOOST_AUTO_TEST_CASE(recursive_calls) { char const* sourceCode = R"( @@ -1733,6 +1788,23 @@ BOOST_AUTO_TEST_CASE(transfer_ether) ABI_CHECK(callContractFunction("b(address,uint256)", oogRecipient, 10), encodeArgs()); } +BOOST_AUTO_TEST_CASE(uncalled_blockhash) +{ + char const* code = R"( + contract C { + function f() public view returns (bytes32) + { + var x = block.blockhash; + return x(block.number - 1); + } + } + )"; + compileAndRun(code, 0, "C"); + bytes result = callContractFunction("f()"); + BOOST_REQUIRE_EQUAL(result.size(), 32); + BOOST_CHECK(result[0] != 0 || result[1] != 0 || result[2] != 0); +} + BOOST_AUTO_TEST_CASE(log0) { char const* sourceCode = R"( @@ -2824,6 +2896,58 @@ BOOST_AUTO_TEST_CASE(function_modifier_multiple_times_local_vars) ABI_CHECK(callContractFunction("a()"), encodeArgs(0)); } +BOOST_AUTO_TEST_CASE(function_modifier_library) +{ + char const* sourceCode = R"( + library L { + struct S { uint v; } + modifier mod(S storage s) { s.v++; _; } + function libFun(S storage s) mod(s) internal { s.v += 0x100; } + } + + contract Test { + using L for *; + L.S s; + + function f() public returns (uint) { + s.libFun(); + L.libFun(s); + return s.v; + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0x202)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_library_inheritance) +{ + // Tests that virtual lookup for modifiers in libraries does not consider + // the current inheritance hierarchy. + + char const* sourceCode = R"( + library L { + struct S { uint v; } + modifier mod(S storage s) { s.v++; _; } + function libFun(S storage s) mod(s) internal { s.v += 0x100; } + } + + contract Test { + using L for *; + L.S s; + modifier mod(L.S storage) { revert(); _; } + + function f() public returns (uint) { + s.libFun(); + L.libFun(s); + return s.v; + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0x202)); +} + BOOST_AUTO_TEST_CASE(crazy_elementary_typenames_on_stack) { char const* sourceCode = R"( @@ -5304,6 +5428,18 @@ BOOST_AUTO_TEST_CASE(super_overload) ABI_CHECK(callContractFunction("h()"), encodeArgs(2)); } +BOOST_AUTO_TEST_CASE(gasleft_shadow_resolution) +{ + char const* sourceCode = R"( + contract C { + function gasleft() returns(uint256) { return 0; } + function f() returns(uint256) { return gasleft(); } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); +} + BOOST_AUTO_TEST_CASE(bool_conversion) { char const* sourceCode = R"( @@ -6888,6 +7024,21 @@ BOOST_AUTO_TEST_CASE(library_call) ABI_CHECK(callContractFunction("f(uint256)", u256(33)), encodeArgs(u256(33) * 9)); } +BOOST_AUTO_TEST_CASE(library_function_external) +{ + char const* sourceCode = R"( + library Lib { function m(bytes b) external pure returns (byte) { return b[2]; } } + contract Test { + function f(bytes b) public pure returns (byte) { + return Lib.m(b); + } + } + )"; + compileAndRun(sourceCode, 0, "Lib"); + compileAndRun(sourceCode, 0, "Test", bytes(), map<string, Address>{{"Lib", m_contractAddress}}); + ABI_CHECK(callContractFunction("f(bytes)", u256(0x20), u256(5), "abcde"), encodeArgs("c")); +} + BOOST_AUTO_TEST_CASE(library_stray_values) { char const* sourceCode = R"( @@ -10718,6 +10869,51 @@ BOOST_AUTO_TEST_CASE(snark) BOOST_CHECK(callContractFunction("verifyTx()") == encodeArgs(true)); } +BOOST_AUTO_TEST_CASE(staticcall_for_view_and_pure) +{ + char const* sourceCode = R"( + pragma experimental "v0.5.0"; + contract C { + uint x; + function f() public returns (uint) { + x = 3; + return 1; + } + } + interface CView { + function f() view external returns (uint); + } + interface CPure { + function f() pure external returns (uint); + } + contract D { + function f() public returns (uint) { + return (new C()).f(); + } + function fview() public returns (uint) { + return (CView(new C())).f(); + } + function fpure() public returns (uint) { + return (CPure(new C())).f(); + } + } + )"; + compileAndRun(sourceCode, 0, "D"); + // This should work (called via CALL) + ABI_CHECK(callContractFunction("f()"), encodeArgs(1)); + if (dev::test::Options::get().evmVersion().hasStaticCall()) + { + // These should throw (called via STATICCALL) + ABI_CHECK(callContractFunction("fview()"), encodeArgs()); + ABI_CHECK(callContractFunction("fpure()"), encodeArgs()); + } + else + { + ABI_CHECK(callContractFunction("fview()"), encodeArgs(1)); + ABI_CHECK(callContractFunction("fpure()"), encodeArgs(1)); + } +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index b853d558..12687dd1 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -52,11 +52,23 @@ public: std::map<std::string, dev::test::Address> const& _libraryAddresses = std::map<std::string, dev::test::Address>() ) override { + bytes bytecode = compileContract(_sourceCode, _contractName, _libraryAddresses); + sendMessage(bytecode + _arguments, true, _value); + return m_output; + } + + bytes compileContract( + std::string const& _sourceCode, + std::string const& _contractName = "", + std::map<std::string, dev::test::Address> const& _libraryAddresses = std::map<std::string, dev::test::Address>() + ) + { // Silence compiler version warning std::string sourceCode = "pragma solidity >=0.0;\n" + _sourceCode; m_compiler.reset(false); m_compiler.addSource("", sourceCode); m_compiler.setLibraries(_libraryAddresses); + m_compiler.setEVMVersion(m_evmVersion); m_compiler.setOptimiserSettings(m_optimize, m_optimizeRuns); if (!m_compiler.compile()) { @@ -72,8 +84,7 @@ public: } eth::LinkerObject obj = m_compiler.object(_contractName.empty() ? m_compiler.lastContractName() : _contractName); BOOST_REQUIRE(obj.linkReferences.empty()); - sendMessage(obj.bytecode + _arguments, true, _value); - return m_output; + return obj.bytecode; } protected: diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 67747386..c8adfc6e 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -30,7 +30,7 @@ #include <libsolidity/ast/AST.h> #include <libsolidity/analysis/TypeChecker.h> #include <libsolidity/interface/ErrorReporter.h> -#include "../TestHelper.h" +#include <test/Options.h> using namespace std; @@ -132,7 +132,7 @@ bytes compileFirstExpression( if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) { ErrorReporter errorReporter(errors); - TypeChecker typeChecker(errorReporter); + TypeChecker typeChecker(dev::test::Options::get().evmVersion(), errorReporter); BOOST_REQUIRE(typeChecker.checkTypeRequirements(*contract)); } for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) @@ -141,7 +141,7 @@ bytes compileFirstExpression( FirstExpressionExtractor extractor(*contract); BOOST_REQUIRE(extractor.expression() != nullptr); - CompilerContext context; + CompilerContext context(dev::test::Options::get().evmVersion()); context.resetVisitedNodes(contract); context.setInheritanceHierarchy(inheritanceHierarchy); unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack @@ -322,10 +322,10 @@ BOOST_AUTO_TEST_CASE(arithmetics) { char const* sourceCode = R"( contract test { - function f(uint y) { var x = ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); } + function f(uint y) { ((((((((y ^ 8) & 7) | 6) - 5) + 4) % 3) / 2) * 1); } } )"; - bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}}); + bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}}); bytes expectation({byte(Instruction::PUSH1), 0x1, byte(Instruction::PUSH1), 0x2, byte(Instruction::PUSH1), 0x3, @@ -334,7 +334,7 @@ BOOST_AUTO_TEST_CASE(arithmetics) byte(Instruction::PUSH1), 0x6, byte(Instruction::PUSH1), 0x7, byte(Instruction::PUSH1), 0x8, - byte(Instruction::DUP10), + byte(Instruction::DUP9), byte(Instruction::XOR), byte(Instruction::AND), byte(Instruction::OR), @@ -364,13 +364,13 @@ BOOST_AUTO_TEST_CASE(unary_operators) { char const* sourceCode = R"( contract test { - function f(int y) { var x = !(~+- y == 2); } + function f(int y) { !(~+- y == 2); } } )"; - bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}, {"test", "f", "x"}}); + bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "y"}}); bytes expectation({byte(Instruction::PUSH1), 0x2, - byte(Instruction::DUP3), + byte(Instruction::DUP2), byte(Instruction::PUSH1), 0x0, byte(Instruction::SUB), byte(Instruction::NOT), @@ -383,7 +383,7 @@ BOOST_AUTO_TEST_CASE(unary_inc_dec) { char const* sourceCode = R"( contract test { - function f(uint a) { var x = --a ^ (a-- ^ (++a ^ a++)); } + function f(uint a) returns (uint x) { x = --a ^ (a-- ^ (++a ^ a++)); } } )"; bytes code = compileFirstExpression(sourceCode, {}, {{"test", "f", "a"}, {"test", "f", "x"}}); @@ -426,7 +426,10 @@ BOOST_AUTO_TEST_CASE(unary_inc_dec) byte(Instruction::POP), // second ++ // Stack here: a x a^(a+2)^(a+2) byte(Instruction::DUP3), // will change - byte(Instruction::XOR)}); + byte(Instruction::XOR), + byte(Instruction::SWAP1), + byte(Instruction::POP), + byte(Instruction::DUP1)}); // Stack here: a x a^(a+2)^(a+2)^a BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } @@ -512,6 +515,39 @@ BOOST_AUTO_TEST_CASE(blockhash) BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } +BOOST_AUTO_TEST_CASE(gas_left) +{ + char const* sourceCode = R"( + contract test { + function f() returns (uint256 val) { + return msg.gas; + } + } + )"; + bytes code = compileFirstExpression( + sourceCode, {}, {}, + {make_shared<MagicVariableDeclaration>("msg", make_shared<MagicType>(MagicType::Kind::Message))} + ); + + bytes expectation({byte(Instruction::GAS)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); + + sourceCode = R"( + contract test { + function f() returns (uint256 val) { + return gasleft(); + } + } + )"; + code = compileFirstExpression( + sourceCode, {}, {}, + {make_shared<MagicVariableDeclaration>("gasleft", make_shared<FunctionType>(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft))} + ); + + expectation = bytes({byte(Instruction::GAS)}); + BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index be147e48..c757037c 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -22,6 +22,8 @@ #include <test/libsolidity/AnalysisFramework.h> +#include <test/Options.h> + #include <libsolidity/ast/AST.h> #include <libdevcore/SHA3.h> @@ -41,50 +43,216 @@ namespace test BOOST_FIXTURE_TEST_SUITE(SolidityNameAndTypeResolution, AnalysisFramework) -BOOST_AUTO_TEST_CASE(smoke_test) + +BOOST_AUTO_TEST_CASE(double_function_declaration) { char const* text = R"( contract test { - uint256 stateVariable1; - function fun(uint256 arg1) public { uint256 y; y = arg1; } + function fun() public { } + function fun() public { } } )"; - CHECK_SUCCESS(text); + CHECK_ERROR(text, DeclarationError, "Function with same name and arguments defined twice."); } -BOOST_AUTO_TEST_CASE(double_stateVariable_declaration) +BOOST_AUTO_TEST_CASE(double_variable_declaration_disjoint_scope) { - char const* text = R"( + string text = R"( contract test { - uint256 variable; - uint128 variable; + function f() pure public { + { uint x; } + { uint x; } + } } )"; - CHECK_ERROR(text, DeclarationError, "Identifier already declared."); + CHECK_ERROR(text, DeclarationError, "Identifier already declared"); } -BOOST_AUTO_TEST_CASE(double_function_declaration) +BOOST_AUTO_TEST_CASE(double_variable_declaration_disjoint_scope_050) +{ + string text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + { uint x; } + { uint x; } + } + } + )"; + CHECK_WARNING_ALLOW_MULTI(text, (vector<string>{ + "Unused local variable", + "Unused local variable" + })); +} + +BOOST_AUTO_TEST_CASE(double_variable_declaration_disjoint_scope_activation) +{ + string text = R"( + contract test { + function f() pure public { + { uint x; } + uint x; + } + } + )"; + CHECK_ERROR(text, DeclarationError, "Identifier already declared"); +} + +BOOST_AUTO_TEST_CASE(double_variable_declaration_disjoint_scope_activation_050) +{ + string text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + { uint x; } + uint x; + } + } + )"; + CHECK_WARNING_ALLOW_MULTI(text, (vector<string>{ + "Unused local variable", + "Unused local variable" + })); +} +BOOST_AUTO_TEST_CASE(scoping_old) { char const* text = R"( contract test { - function fun() public { } - function fun() public { } + function f() pure public { + x = 4; + uint256 x = 2; + } } )"; - CHECK_ERROR(text, DeclarationError, "Function with same name and arguments defined twice."); + CHECK_SUCCESS_NO_WARNINGS(text); } -BOOST_AUTO_TEST_CASE(double_variable_declaration) +BOOST_AUTO_TEST_CASE(scoping) { char const* text = R"( + pragma experimental "v0.5.0"; contract test { function f() public { - uint256 x; - if (true) { uint256 x; } + { + uint256 x; + } + x = 2; } } )"; - CHECK_ERROR(text, DeclarationError, "Identifier already declared."); + CHECK_ERROR(text, DeclarationError, "Undeclared identifier"); +} + +BOOST_AUTO_TEST_CASE(scoping_activation_old) +{ + char const* text = R"( + contract test { + function f() pure public { + x = 3; + uint x; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(scoping_activation) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + x = 3; + uint x; + } + } + )"; + CHECK_ERROR(text, DeclarationError, "Undeclared identifier"); +} + +BOOST_AUTO_TEST_CASE(scoping_self_use) +{ + char const* text = R"( + contract test { + function f() pure public { + uint a = a; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(scoping_self_use_050) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + uint a = a; + } + } + )"; + CHECK_ERROR(text, DeclarationError, "Undeclared identifier"); +} + +BOOST_AUTO_TEST_CASE(scoping_for) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + for (uint x = 0; x < 10; x ++){ + x = 2; + } + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(scoping_for2) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + for (uint x = 0; x < 10; x ++) + x = 2; + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(scoping_for3) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + for (uint x = 0; x < 10; x ++){ + x = 2; + } + x = 4; + } + } + )"; + CHECK_ERROR(text, DeclarationError, "Undeclared identifier"); +} + +BOOST_AUTO_TEST_CASE(scoping_for_decl_in_body) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + for (;; y++){ + uint y = 3; + } + } + } + )"; + CHECK_ERROR(text, DeclarationError, "Undeclared identifier"); } BOOST_AUTO_TEST_CASE(name_shadowing) @@ -758,6 +926,62 @@ BOOST_AUTO_TEST_CASE(functions_with_stucts_of_non_external_types_in_interface_ne CHECK_ERROR(text, TypeError, "Internal or recursive type is not allowed for public or external functions."); } +BOOST_AUTO_TEST_CASE(returning_multi_dimensional_arrays_new_abi) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + function f() public pure returns (string[][]) {} + } + )"; + CHECK_WARNING(text, "Experimental features"); +} + +BOOST_AUTO_TEST_CASE(returning_multi_dimensional_arrays) +{ + char const* text = R"( + contract C { + function f() public pure returns (string[][]) {} + } + )"; + CHECK_ERROR(text, TypeError, "only supported in the new experimental ABI encoder"); +} + +BOOST_AUTO_TEST_CASE(returning_multi_dimensional_static_arrays) +{ + char const* text = R"( + contract C { + function f() public pure returns (uint[][2]) {} + } + )"; + CHECK_ERROR(text, TypeError, "only supported in the new experimental ABI encoder"); +} + +BOOST_AUTO_TEST_CASE(returning_arrays_in_structs_new_abi) +{ + char const* text = R"( + pragma experimental ABIEncoderV2; + + contract C { + struct S { string[] s; } + function f() public pure returns (S) {} + } + )"; + CHECK_WARNING(text, "Experimental features"); +} + +BOOST_AUTO_TEST_CASE(returning_arrays_in_structs_arrays) +{ + char const* text = R"( + contract C { + struct S { string[] s; } + function f() public pure returns (S x) {} + } + )"; + CHECK_ERROR(text, TypeError, "only supported in the new experimental ABI encoder"); +} + BOOST_AUTO_TEST_CASE(function_external_call_allowed_conversion) { char const* text = R"( @@ -1004,7 +1228,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation) { char const* text = R"( contract B { - function f() mod1(2, true) mod2("0123456") public { } + function f() mod1(2, true) mod2("0123456") pure public { } modifier mod1(uint a, bool b) { if (b) _; } modifier mod2(bytes7 a) { while (a == "1234567") _; } } @@ -1039,11 +1263,23 @@ BOOST_AUTO_TEST_CASE(function_modifier_invocation_local_variables) { char const* text = R"( contract B { - function f() mod(x) public { uint x = 7; } + function f() mod(x) pure public { uint x = 7; } modifier mod(uint a) { if (a > 0) _; } } )"; - CHECK_SUCCESS(text); + CHECK_SUCCESS_NO_WARNINGS(text); +} + +BOOST_AUTO_TEST_CASE(function_modifier_invocation_local_variables050) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract B { + function f() mod(x) pure public { uint x = 7; } + modifier mod(uint a) { if (a > 0) _; } + } + )"; + CHECK_ERROR(text, DeclarationError, "Undeclared identifier."); } BOOST_AUTO_TEST_CASE(function_modifier_double_invocation) @@ -2484,6 +2720,25 @@ BOOST_AUTO_TEST_CASE(explicit_conversion_from_decimal_to_bytesxx) CHECK_SUCCESS_NO_WARNINGS(text); } +BOOST_AUTO_TEST_CASE(combining_hex_and_denomination) +{ + char const* text = R"( + contract Foo { + uint constant x = 0x01 wei; + } + )"; + CHECK_WARNING(text, "Hexadecimal numbers with unit denominations are deprecated."); + + char const* textV050 = R"( + pragma experimental "v0.5.0"; + + contract Foo { + uint constant x = 0x01 wei; + } + )"; + CHECK_ERROR(textV050, TypeError, "Hexadecimal numbers cannot be used with unit denominations."); +} + BOOST_AUTO_TEST_CASE(assigning_value_to_const_variable) { char const* text = R"( @@ -5639,10 +5894,11 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_negative_stack) BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) { char const* text = R"( + pragma experimental "v0.5.0"; contract c { uint8 x; function f() public { - assembly { x pop } + assembly { pop(x) } } } )"; @@ -5652,6 +5908,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_unbalanced_two_stack_load) BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier) { char const* text = R"( + pragma experimental "v0.5.0"; contract test { modifier m { uint a = 1; @@ -5660,7 +5917,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier) } _; } - function f() m { + function f() public m { } } )"; @@ -5670,6 +5927,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_in_modifier) BOOST_AUTO_TEST_CASE(inline_assembly_storage) { char const* text = R"( + pragma experimental "v0.5.0"; contract test { uint x = 1; function f() public { @@ -5685,6 +5943,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage) BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) { char const* text = R"( + pragma experimental "v0.5.0"; contract test { uint x = 1; modifier m { @@ -5693,7 +5952,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) } _; } - function f() m { + function f() public m { } } )"; @@ -5703,6 +5962,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_storage_in_modifiers) BOOST_AUTO_TEST_CASE(inline_assembly_constant_assign) { char const* text = R"( + pragma experimental "v0.5.0"; contract test { uint constant x = 1; function f() public { @@ -5718,6 +5978,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_constant_assign) BOOST_AUTO_TEST_CASE(inline_assembly_constant_access) { char const* text = R"( + pragma experimental "v0.5.0"; contract test { uint constant x = 1; function f() public { @@ -5733,6 +5994,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_constant_access) BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions) { char const* text = R"( + pragma experimental "v0.5.0"; contract test { function f() public { uint a; @@ -5748,6 +6010,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions) BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions_storage_ptr) { char const* text = R"( + pragma experimental "v0.5.0"; contract test { uint[] r; function f() public { @@ -5764,6 +6027,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_local_variable_access_out_of_functions_stor BOOST_AUTO_TEST_CASE(inline_assembly_storage_variable_access_out_of_functions) { char const* text = R"( + pragma experimental "v0.5.0"; contract test { uint a; function f() pure public { @@ -5794,6 +6058,7 @@ BOOST_AUTO_TEST_CASE(inline_assembly_constant_variable_via_offset) BOOST_AUTO_TEST_CASE(inline_assembly_calldata_variables) { char const* text = R"( + pragma experimental "v0.5.0"; contract C { function f(bytes bytesAsCalldata) external { assembly { @@ -5805,6 +6070,182 @@ BOOST_AUTO_TEST_CASE(inline_assembly_calldata_variables) CHECK_ERROR(text, TypeError, "Call data elements cannot be accessed directly."); } +BOOST_AUTO_TEST_CASE(inline_assembly_050_literals_on_stack) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract C { + function f() pure public { + assembly { + 1 + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::SyntaxError, "are not supposed to return"}, + {Error::Type::DeclarationError, "Unbalanced stack"}, + })); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_literals_on_stack) +{ + char const* text = R"( + contract C { + function f() pure public { + assembly { + 1 + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::Warning, "are not supposed to return"}, + {Error::Type::DeclarationError, "Unbalanced stack"}, + })); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_050_bare_instructions) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract C { + function f() view public { + assembly { + address + pop + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::SyntaxError, "The use of non-functional"}, + {Error::Type::SyntaxError, "The use of non-functional"} + })); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_bare_instructions) +{ + char const* text = R"( + contract C { + function f() view public { + assembly { + address + pop + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::Warning, "The use of non-functional"}, + {Error::Type::Warning, "The use of non-functional"} + })); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_050_labels) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract C { + function f() pure public { + assembly { + label: + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::SyntaxError, "Jump instructions and labels are low-level"}, + {Error::Type::SyntaxError, "The use of labels is deprecated"} + })); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_labels) +{ + char const* text = R"( + contract C { + function f() pure public { + assembly { + label: + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::Warning, "Jump instructions and labels are low-level"}, + {Error::Type::Warning, "The use of labels is deprecated"} + })); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_050_jump) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract C { + function f() pure public { + assembly { + jump(2) + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::SyntaxError, "Jump instructions and labels are low-level"} + })); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_jump) +{ + char const* text = R"( + contract C { + function f() pure public { + assembly { + jump(2) + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::TypeError, "Function declared as pure"}, + {Error::Type::Warning, "Jump instructions and labels are low-level"} + })); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_050_leave_items_on_stack) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract C { + function f() pure public { + assembly { + mload(0) + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::SyntaxError, "are not supposed to return"}, + {Error::Type::DeclarationError, "Unbalanced stack"}, + })); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_leave_items_on_stack) +{ + char const* text = R"( + contract C { + function f() pure public { + assembly { + mload(0) + } + } + } + )"; + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::Warning, "are not supposed to return"}, + {Error::Type::DeclarationError, "Unbalanced stack"}, + })); +} + BOOST_AUTO_TEST_CASE(invalid_mobile_type) { char const* text = R"( @@ -6757,6 +7198,8 @@ BOOST_AUTO_TEST_CASE(callable_crash) BOOST_AUTO_TEST_CASE(error_transfer_non_payable_fallback) { + // This used to be a test for a.transfer to generate a warning + // because A's fallback function is not payable. char const* text = R"( contract A { function() public {} @@ -6770,12 +7213,17 @@ BOOST_AUTO_TEST_CASE(error_transfer_non_payable_fallback) } } )"; - CHECK_ERROR(text, TypeError, "Value transfer to a contract without a payable fallback function."); + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::Warning, "Using contract member \"transfer\" inherited from the address type is deprecated"}, + {Error::Type::TypeError, "Value transfer to a contract without a payable fallback function"} + })); } BOOST_AUTO_TEST_CASE(error_transfer_no_fallback) { - char const* text = R"( + // This used to be a test for a.transfer to generate a warning + // because A does not have a payable fallback function. + std::string text = R"( contract A {} contract B { @@ -6786,12 +7234,17 @@ BOOST_AUTO_TEST_CASE(error_transfer_no_fallback) } } )"; - CHECK_ERROR(text, TypeError, "Value transfer to a contract without a payable fallback function."); + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::Warning, "Using contract member \"transfer\" inherited from the address type is deprecated"}, + {Error::Type::TypeError, "Value transfer to a contract without a payable fallback function"} + })); } BOOST_AUTO_TEST_CASE(error_send_non_payable_fallback) { - char const* text = R"( + // This used to be a test for a.send to generate a warning + // because A does not have a payable fallback function. + std::string text = R"( contract A { function() public {} } @@ -6804,11 +7257,16 @@ BOOST_AUTO_TEST_CASE(error_send_non_payable_fallback) } } )"; - CHECK_ERROR(text, TypeError, "Value transfer to a contract without a payable fallback function."); + CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + {Error::Type::Warning, "Using contract member \"send\" inherited from the address type is deprecated"}, + {Error::Type::TypeError, "Value transfer to a contract without a payable fallback function"} + })); } BOOST_AUTO_TEST_CASE(does_not_error_transfer_payable_fallback) { + // This used to be a test for a.transfer to generate a warning + // because A does not have a payable fallback function. char const* text = R"( contract A { function() payable public {} @@ -6822,7 +7280,7 @@ BOOST_AUTO_TEST_CASE(does_not_error_transfer_payable_fallback) } } )"; - CHECK_SUCCESS_NO_WARNINGS(text); + CHECK_WARNING(text, "Using contract member \"transfer\" inherited from the address type is deprecated."); } BOOST_AUTO_TEST_CASE(does_not_error_transfer_regular_function) @@ -6848,11 +7306,14 @@ BOOST_AUTO_TEST_CASE(returndatacopy_as_variable) char const* text = R"( contract c { function f() public { uint returndatasize; assembly { returndatasize }}} )"; - CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ + vector<pair<Error::Type, std::string>> expectations(vector<pair<Error::Type, std::string>>{ {Error::Type::Warning, "Variable is shadowed in inline assembly by an instruction of the same name"}, - {Error::Type::DeclarationError, "Unbalanced stack"}, - {Error::Type::Warning, "only available after the Metropolis"} - })); + {Error::Type::Warning, "The use of non-functional instructions is deprecated."}, + {Error::Type::DeclarationError, "Unbalanced stack"} + }); + if (!dev::test::Options::get().evmVersion().supportsReturndata()) + expectations.emplace_back(make_pair(Error::Type::Warning, std::string("\"returndatasize\" instruction is only available for Byzantium-compatible"))); + CHECK_ALLOW_MULTI(text, expectations); } BOOST_AUTO_TEST_CASE(create2_as_variable) @@ -6862,8 +7323,9 @@ BOOST_AUTO_TEST_CASE(create2_as_variable) )"; CHECK_ALLOW_MULTI(text, (std::vector<std::pair<Error::Type, std::string>>{ {Error::Type::Warning, "Variable is shadowed in inline assembly by an instruction of the same name"}, - {Error::Type::Warning, "only available after the Metropolis"}, - {Error::Type::DeclarationError, "Unbalanced stack"} + {Error::Type::Warning, "The \"create2\" instruction is not supported by the VM version"}, + {Error::Type::DeclarationError, "Unbalanced stack"}, + {Error::Type::Warning, "not supposed to return values"} })); } @@ -7157,6 +7619,50 @@ BOOST_AUTO_TEST_CASE(builtin_reject_gas) CHECK_ERROR(text, TypeError, "Member \"gas\" not found or not visible after argument-dependent lookup"); } +BOOST_AUTO_TEST_CASE(gasleft) +{ + char const* text = R"( + contract C { + function f() public view returns (uint256 val) { return msg.gas; } + } + )"; + CHECK_WARNING(text, "\"msg.gas\" has been deprecated in favor of \"gasleft()\""); + + text = R"( + contract C { + function f() public view returns (uint256 val) { return gasleft(); } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); + + text = R"( + pragma experimental "v0.5.0"; + contract C { + function f() public returns (uint256 val) { return msg.gas; } + } + )"; + CHECK_ERROR(text, TypeError, "\"msg.gas\" has been deprecated in favor of \"gasleft()\""); +} + +BOOST_AUTO_TEST_CASE(gasleft_shadowing) +{ + char const* text = R"( + contract C { + function gasleft() public pure returns (bytes32 val) { return "abc"; } + function f() public pure returns (bytes32 val) { return gasleft(); } + } + )"; + CHECK_WARNING(text, "This declaration shadows a builtin symbol."); + + text = R"( + contract C { + uint gasleft; + function f() public { gasleft = 42; } + } + )"; + CHECK_WARNING(text, "This declaration shadows a builtin symbol."); +} + BOOST_AUTO_TEST_CASE(builtin_reject_value) { char const* text = R"( @@ -7418,7 +7924,7 @@ BOOST_AUTO_TEST_CASE(non_external_fallback) function () external { } } )"; - CHECK_WARNING(text, "Experimental features are turned on."); + CHECK_SUCCESS_NO_WARNINGS(text); text = R"( pragma experimental "v0.5.0"; contract C { @@ -7736,12 +8242,140 @@ BOOST_AUTO_TEST_CASE(array_length_invalid_expression) CHECK_ERROR(text, TypeError, "Operator / not compatible with types int_const 3 and int_const 0"); } +BOOST_AUTO_TEST_CASE(warn_about_address_members_on_contract) +{ + std::string text = R"( + contract C { + function f() view public { + this.balance; + } + } + )"; + CHECK_WARNING(text, "Using contract member \"balance\" inherited from the address type is deprecated."); + text = R"( + contract C { + function f() view public { + this.transfer; + } + } + )"; + CHECK_ALLOW_MULTI(text, (vector<pair<Error::Type, std::string>>{ + {Error::Type::Warning, "Using contract member \"transfer\" inherited from the address type is deprecated"}, + {Error::Type::TypeError, "Value transfer to a contract without a payable fallback function"} + })); + text = R"( + contract C { + function f() view public { + this.send; + } + } + )"; + CHECK_ALLOW_MULTI(text, (vector<pair<Error::Type, std::string>>{ + {Error::Type::Warning, "Using contract member \"send\" inherited from the address type is deprecated"}, + {Error::Type::TypeError, "Value transfer to a contract without a payable fallback function"} + })); + text = R"( + contract C { + function f() view public { + this.call; + } + } + )"; + CHECK_WARNING(text, "Using contract member \"call\" inherited from the address type is deprecated."); + text = R"( + contract C { + function f() view public { + this.callcode; + } + } + )"; + CHECK_ALLOW_MULTI(text, (vector<pair<Error::Type, std::string>>{ + {Error::Type::Warning, "Using contract member \"callcode\" inherited from the address type is deprecated"}, + {Error::Type::Warning, "\"callcode\" has been deprecated in favour of \"delegatecall\""} + })); + text = R"( + contract C { + function f() view public { + this.delegatecall; + } + } + )"; + CHECK_WARNING(text, "Using contract member \"delegatecall\" inherited from the address type is deprecated."); +} + +BOOST_AUTO_TEST_CASE(warn_about_address_members_on_non_this_contract) +{ + std::string text = R"( + contract C { + function f() view public { + C c; + c.balance; + } + } + )"; + CHECK_WARNING(text, "Using contract member \"balance\" inherited from the address type is deprecated"); + text = R"( + contract C { + function f() view public { + C c; + c.transfer; + } + } + )"; + CHECK_ALLOW_MULTI(text, (vector<pair<Error::Type, std::string>>{ + {Error::Type::Warning, "Using contract member \"transfer\" inherited from the address type is deprecated"}, + {Error::Type::TypeError, "Value transfer to a contract without a payable fallback function"} + })); + text = R"( + contract C { + function f() view public { + C c; + c.send; + } + } + )"; + CHECK_ALLOW_MULTI(text, (vector<pair<Error::Type, std::string>>{ + {Error::Type::Warning, "Using contract member \"send\" inherited from the address type is deprecated"}, + {Error::Type::TypeError, "Value transfer to a contract without a payable fallback function"} + })); + text = R"( + contract C { + function f() pure public { + C c; + c.call; + } + } + )"; + CHECK_WARNING(text, "Using contract member \"call\" inherited from the address type is deprecated"); + text = R"( + contract C { + function f() pure public { + C c; + c.callcode; + } + } + )"; + CHECK_WARNING_ALLOW_MULTI(text, (std::vector<std::string>{ + "Using contract member \"callcode\" inherited from the address type is deprecated", + "\"callcode\" has been deprecated in favour of \"delegatecall\"" + })); + text = R"( + contract C { + function f() pure public { + C c; + c.delegatecall; + } + } + )"; + CHECK_WARNING(text, "Using contract member \"delegatecall\" inherited from the address type is deprecated"); +} + BOOST_AUTO_TEST_CASE(no_address_members_on_contract) { char const* text = R"( pragma experimental "v0.5.0"; contract C { - function f() { + function f() public { this.balance; } } @@ -7750,7 +8384,7 @@ BOOST_AUTO_TEST_CASE(no_address_members_on_contract) text = R"( pragma experimental "v0.5.0"; contract C { - function f() { + function f() public { this.transfer; } } @@ -7759,7 +8393,7 @@ BOOST_AUTO_TEST_CASE(no_address_members_on_contract) text = R"( pragma experimental "v0.5.0"; contract C { - function f() { + function f() public { this.send; } } @@ -7768,7 +8402,7 @@ BOOST_AUTO_TEST_CASE(no_address_members_on_contract) text = R"( pragma experimental "v0.5.0"; contract C { - function f() { + function f() public { this.call; } } @@ -7777,7 +8411,7 @@ BOOST_AUTO_TEST_CASE(no_address_members_on_contract) text = R"( pragma experimental "v0.5.0"; contract C { - function f() { + function f() public { this.callcode; } } @@ -7786,7 +8420,7 @@ BOOST_AUTO_TEST_CASE(no_address_members_on_contract) text = R"( pragma experimental "v0.5.0"; contract C { - function f() { + function f() public { this.delegatecall; } } @@ -7794,6 +8428,20 @@ BOOST_AUTO_TEST_CASE(no_address_members_on_contract) CHECK_ERROR(text, TypeError, "Member \"delegatecall\" not found or not visible after argument-dependent lookup in contract"); } +BOOST_AUTO_TEST_CASE(no_warning_for_using_members_that_look_like_address_members) +{ + char const* text = R"( + pragma experimental "v0.5.0"; + contract C { + function transfer(uint) public; + function f() public { + this.transfer(10); + } + } + )"; + CHECK_SUCCESS_NO_WARNINGS(text); +} + BOOST_AUTO_TEST_CASE(emit_events) { char const* text = R"( @@ -7870,6 +8518,23 @@ BOOST_AUTO_TEST_CASE(getter_is_memory_type) } } +BOOST_AUTO_TEST_CASE(require_visibility_specifiers) +{ + char const* text = R"( + contract C { + function f() pure { } + } + )"; + CHECK_WARNING(text, "No visibility specified. Defaulting to"); + text = R"( + pragma experimental "v0.5.0"; + contract C { + function f() pure { } + } + )"; + CHECK_ERROR(text, SyntaxError, "No visibility specified."); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index e8906bb9..eeebeb74 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -20,7 +20,7 @@ * Unit tests for the solidity compiler JSON Interface output. */ -#include "../TestHelper.h" +#include <test/Options.h> #include <string> #include <libdevcore/JSON.h> #include <libsolidity/interface/CompilerStack.h> @@ -47,6 +47,7 @@ public: { m_compilerStack.reset(false); m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); + m_compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_REQUIRE_MESSAGE(m_compilerStack.parseAndAnalyze(), "Parsing contract failed"); Json::Value generatedDocumentation; @@ -67,6 +68,7 @@ public: { m_compilerStack.reset(false); m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); + m_compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); BOOST_CHECK(!m_compilerStack.parseAndAnalyze()); BOOST_REQUIRE(Error::containsErrorOfType(m_compilerStack.errors(), Error::Type::DocstringParsingError)); } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index b7097d0f..4e862f60 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -25,8 +25,8 @@ #include <libsolidity/parsing/Scanner.h> #include <libsolidity/parsing/Parser.h> #include <libsolidity/interface/ErrorReporter.h> -#include "../TestHelper.h" -#include "ErrorCheck.h" +#include <test/Options.h> +#include <test/libsolidity/ErrorCheck.h> using namespace std; @@ -1164,6 +1164,36 @@ BOOST_AUTO_TEST_CASE(constant_is_keyword) CHECK_PARSE_ERROR(text, "Expected identifier"); } +BOOST_AUTO_TEST_CASE(keyword_is_reserved) +{ + auto keywords = { + "abstract", + "after", + "case", + "catch", + "default", + "final", + "in", + "inline", + "let", + "match", + "null", + "of", + "relocatable", + "static", + "switch", + "try", + "type", + "typeof" + }; + + for (const auto& keyword: keywords) + { + auto text = std::string("contract ") + keyword + " {}"; + CHECK_PARSE_ERROR(text.c_str(), "Expected identifier"); + } +} + BOOST_AUTO_TEST_CASE(var_array) { char const* text = R"( diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 404f709d..dd6eb7c4 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -570,6 +570,198 @@ BOOST_AUTO_TEST_CASE(library_filename_with_colon) BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["git:library.sol"]["L"][0].isObject()); } +BOOST_AUTO_TEST_CASE(libraries_invalid_top_level) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "libraries": "42" + }, + "sources": { + "empty": { + "content": "" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsError(result, "JSONError", "\"libraries\" is not a JSON object.")); +} + +BOOST_AUTO_TEST_CASE(libraries_invalid_entry) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "libraries": { + "L": "42" + } + }, + "sources": { + "empty": { + "content": "" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsError(result, "JSONError", "library entry is not a JSON object.")); +} + +BOOST_AUTO_TEST_CASE(libraries_invalid_hex) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "libraries": { + "library.sol": { + "L": "0x4200000000000000000000000000000000000xx1" + } + } + }, + "sources": { + "empty": { + "content": "" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsError(result, "JSONError", "Invalid library address (\"0x4200000000000000000000000000000000000xx1\") supplied.")); +} + +BOOST_AUTO_TEST_CASE(libraries_invalid_length) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "libraries": { + "library.sol": { + "L1": "0x42", + "L2": "0x4200000000000000000000000000000000000001ff" + } + } + }, + "sources": { + "empty": { + "content": "" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsError(result, "JSONError", "Library address is of invalid length.")); +} + +BOOST_AUTO_TEST_CASE(libraries_missing_hex_prefix) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "libraries": { + "library.sol": { + "L": "4200000000000000000000000000000000000001" + } + } + }, + "sources": { + "empty": { + "content": "" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsError(result, "JSONError", "Library address is not prefixed with \"0x\".")); +} + +BOOST_AUTO_TEST_CASE(library_linking) +{ + char const* input = R"( + { + "language": "Solidity", + "settings": { + "libraries": { + "library.sol": { + "L": "0x4200000000000000000000000000000000000001" + } + }, + "outputSelection": { + "fileA": { + "A": [ + "evm.bytecode" + ] + } + } + }, + "sources": { + "fileA": { + "content": "import \"library.sol\"; import \"library2.sol\"; contract A { function f() returns (uint) { L2.g(); return L.g(); } }" + }, + "library.sol": { + "content": "library L { function g() returns (uint) { return 1; } }" + }, + "library2.sol": { + "content": "library L2 { function g() { } }" + } + } + } + )"; + Json::Value result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_CHECK(contract.isObject()); + BOOST_CHECK(contract["evm"]["bytecode"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"].isObject()); + BOOST_CHECK(!contract["evm"]["bytecode"]["linkReferences"]["library.sol"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["library2.sol"].isObject()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["library2.sol"]["L2"].isArray()); + BOOST_CHECK(contract["evm"]["bytecode"]["linkReferences"]["library2.sol"]["L2"][0].isObject()); +} + +BOOST_AUTO_TEST_CASE(evm_version) +{ + auto inputForVersion = [](string const& _version) + { + return R"( + { + "language": "Solidity", + "sources": { "fileA": { "content": "contract A { }" } }, + "settings": { + )" + _version + R"( + "outputSelection": { + "fileA": { + "A": [ "metadata" ] + } + } + } + } + )"; + }; + Json::Value result; + result = compile(inputForVersion("\"evmVersion\": \"homestead\",")); + BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"homestead\"") != string::npos); + result = compile(inputForVersion("\"evmVersion\": \"tangerineWhistle\",")); + BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"tangerineWhistle\"") != string::npos); + result = compile(inputForVersion("\"evmVersion\": \"spuriousDragon\",")); + BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"spuriousDragon\"") != string::npos); + result = compile(inputForVersion("\"evmVersion\": \"byzantium\",")); + BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"byzantium\"") != string::npos); + result = compile(inputForVersion("\"evmVersion\": \"constantinople\",")); + BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"constantinople\"") != string::npos); + // test default + result = compile(inputForVersion("")); + BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"byzantium\"") != string::npos); + // test invalid + result = compile(inputForVersion("\"evmVersion\": \"invalid\",")); + BOOST_CHECK(result["errors"][0]["message"].asString() == "Invalid EVM version requested."); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp new file mode 100644 index 00000000..45a851b6 --- /dev/null +++ b/test/libsolidity/SyntaxTest.cpp @@ -0,0 +1,209 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <test/libsolidity/SyntaxTest.h> +#include <boost/algorithm/string.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/throw_exception.hpp> +#include <cctype> +#include <fstream> +#include <memory> +#include <stdexcept> + +using namespace dev; +using namespace solidity; +using namespace dev::solidity::test; +using namespace std; +namespace fs = boost::filesystem; +using namespace boost::unit_test; + +template<typename IteratorType> +void skipWhitespace(IteratorType& it, IteratorType end) +{ + while (it != end && isspace(*it)) + ++it; +} + +template<typename IteratorType> +void skipSlashes(IteratorType& it, IteratorType end) +{ + while (it != end && *it == '/') + ++it; +} + +SyntaxTest::SyntaxTest(string const& _filename) +{ + ifstream file(_filename); + if (!file) + BOOST_THROW_EXCEPTION(runtime_error("Cannot open test contract: \"" + _filename + "\".")); + file.exceptions(ios::badbit); + + m_source = parseSource(file); + m_expectations = parseExpectations(file); +} + +bool SyntaxTest::run(ostream& _stream, string const& _indent) +{ + m_errorList = parseAnalyseAndReturnError(m_source, true, true, true).second; + if (!matchesExpectations(m_errorList)) + { + std::string nextIndentLevel = _indent + "\t"; + _stream << _indent << "Expected result:" << endl; + printExpected(_stream, nextIndentLevel); + _stream << _indent << "Obtained result:\n"; + printErrorList(_stream, m_errorList, nextIndentLevel); + return false; + } + return true; +} + +void SyntaxTest::printExpected(ostream& _stream, string const& _indent) const +{ + if (m_expectations.empty()) + _stream << _indent << "Success" << endl; + else + for (auto const& expectation: m_expectations) + _stream << _indent << expectation.type << ": " << expectation.message << endl; +} + +void SyntaxTest::printErrorList( + ostream& _stream, + ErrorList const& _errorList, + string const& _indent +) const +{ + if (_errorList.empty()) + _stream << _indent << "Success" << endl; + else + for (auto const& error: _errorList) + _stream << _indent << error->typeName() << ": " << errorMessage(*error) << endl; +} + +bool SyntaxTest::matchesExpectations(ErrorList const& _errorList) const +{ + if (_errorList.size() != m_expectations.size()) + return false; + else + for (size_t i = 0; i < _errorList.size(); i++) + if ( + (_errorList[i]->typeName() != m_expectations[i].type) || + (errorMessage(*_errorList[i]) != m_expectations[i].message) + ) + return false; + return true; +} + +string SyntaxTest::errorMessage(Error const& _e) +{ + if (_e.comment()) + return boost::replace_all_copy(*_e.comment(), "\n", "\\n"); + else + return "NONE"; +} + +string SyntaxTest::parseSource(istream& _stream) +{ + string source; + string line; + string const delimiter("// ----"); + while (getline(_stream, line)) + if (boost::algorithm::starts_with(line, delimiter)) + break; + else + source += line + "\n"; + return source; +} + +vector<SyntaxTestExpectation> SyntaxTest::parseExpectations(istream& _stream) +{ + vector<SyntaxTestExpectation> expectations; + string line; + while (getline(_stream, line)) + { + auto it = line.begin(); + + skipSlashes(it, line.end()); + skipWhitespace(it, line.end()); + + if (it == line.end()) continue; + + auto typeBegin = it; + while (it != line.end() && *it != ':') + ++it; + string errorType(typeBegin, it); + + // skip colon + if (it != line.end()) it++; + + skipWhitespace(it, line.end()); + + string errorMessage(it, line.end()); + expectations.emplace_back(SyntaxTestExpectation{move(errorType), move(errorMessage)}); + } + return expectations; +} + +#if BOOST_VERSION < 105900 +test_case *make_test_case( + function<void()> const& _fn, + string const& _name, + string const& /* _filename */, + size_t /* _line */ +) +{ + return make_test_case(_fn, _name); +} +#endif + +int SyntaxTest::registerTests( + boost::unit_test::test_suite& _suite, + boost::filesystem::path const& _basepath, + boost::filesystem::path const& _path +) +{ + int numTestsAdded = 0; + fs::path fullpath = _basepath / _path; + if (fs::is_directory(fullpath)) + { + test_suite* sub_suite = BOOST_TEST_SUITE(_path.filename().string()); + for (auto const& entry: boost::iterator_range<fs::directory_iterator>( + fs::directory_iterator(fullpath), + fs::directory_iterator() + )) + numTestsAdded += registerTests(*sub_suite, _basepath, _path / entry.path().filename()); + _suite.add(sub_suite); + } + else + { + static vector<unique_ptr<string>> filenames; + + filenames.emplace_back(new string(_path.string())); + _suite.add(make_test_case( + [fullpath] + { + std::stringstream errorStream; + if (!SyntaxTest(fullpath.string()).run(errorStream, "")) + BOOST_ERROR("Test expectation mismatch.\n" + errorStream.str()); + }, + _path.stem().string(), + *filenames.back(), + 0 + )); + numTestsAdded = 1; + } + return numTestsAdded; +} diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h new file mode 100644 index 00000000..4379c77b --- /dev/null +++ b/test/libsolidity/SyntaxTest.h @@ -0,0 +1,77 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <test/libsolidity/AnalysisFramework.h> +#include <libsolidity/interface/Exceptions.h> + +#include <boost/noncopyable.hpp> +#include <boost/test/unit_test.hpp> + +#include <iosfwd> +#include <string> +#include <vector> +#include <utility> + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +struct SyntaxTestExpectation +{ + std::string type; + std::string message; +}; + + +class SyntaxTest: AnalysisFramework +{ +public: + SyntaxTest(std::string const& _filename); + + bool run(std::ostream& _stream, std::string const& _indent); + + void printExpected(std::ostream& _stream, std::string const& _indent) const; + void printErrorList( + std::ostream& _stream, + ErrorList const& _errors, + std::string const& _indent + ) const; + + static int registerTests( + boost::unit_test::test_suite& _suite, + boost::filesystem::path const& _basepath, + boost::filesystem::path const& _path + ); +private: + bool matchesExpectations(ErrorList const& _errors) const; + static std::string errorMessage(Error const& _e); + static std::string parseSource(std::istream& _stream); + static std::vector<SyntaxTestExpectation> parseExpectations(std::istream& _stream); + + std::string m_source; + std::vector<SyntaxTestExpectation> m_expectations; + ErrorList m_errorList; +}; + +} +} +} diff --git a/test/libsolidity/ViewPureChecker.cpp b/test/libsolidity/ViewPureChecker.cpp index 3a03c877..a6ce6d91 100644 --- a/test/libsolidity/ViewPureChecker.cpp +++ b/test/libsolidity/ViewPureChecker.cpp @@ -20,6 +20,8 @@ #include <test/libsolidity/AnalysisFramework.h> +#include <test/Options.h> + #include <boost/test/unit_test.hpp> #include <string> @@ -109,6 +111,7 @@ BOOST_AUTO_TEST_CASE(environment_access) "block.difficulty", "block.number", "block.gaslimit", + "gasleft()", "msg.gas", "msg.value", "msg.sender", @@ -148,7 +151,7 @@ BOOST_AUTO_TEST_CASE(environment_access) BOOST_AUTO_TEST_CASE(view_error_for_050) { CHECK_ERROR( - "pragma experimental \"v0.5.0\"; contract C { uint x; function f() view { x = 2; } }", + "pragma experimental \"v0.5.0\"; contract C { uint x; function f() view public { x = 2; } }", TypeError, "Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable." ); @@ -277,11 +280,11 @@ BOOST_AUTO_TEST_CASE(builtin_functions) string text = R"( contract C { function f() public { - this.transfer(1); - require(this.send(2)); - selfdestruct(this); - require(this.delegatecall()); - require(this.call()); + address(this).transfer(1); + require(address(this).send(2)); + selfdestruct(address(this)); + require(address(this).delegatecall()); + require(address(this).call()); } function g() pure public { bytes32 x = keccak256("abc"); @@ -423,7 +426,10 @@ BOOST_AUTO_TEST_CASE(assembly_staticcall) } } )"; - CHECK_WARNING(text, "only available after the Metropolis"); + if (!dev::test::Options::get().evmVersion().hasStaticCall()) + CHECK_WARNING(text, "\"staticcall\" instruction is only available for Byzantium-compatible"); + else + CHECK_SUCCESS_NO_WARNINGS(text); } BOOST_AUTO_TEST_CASE(assembly_jump) diff --git a/test/libsolidity/syntaxTests/docstring_empty_description.sol b/test/libsolidity/syntaxTests/docstring_empty_description.sol new file mode 100644 index 00000000..0caa1b23 --- /dev/null +++ b/test/libsolidity/syntaxTests/docstring_empty_description.sol @@ -0,0 +1,6 @@ +contract C { + /// @param id + function vote(uint id) public; +} +// ---- +// DocstringParsingError: No description given for param id diff --git a/test/libsolidity/syntaxTests/double_stateVariable_declaration.sol b/test/libsolidity/syntaxTests/double_stateVariable_declaration.sol new file mode 100644 index 00000000..c5507b64 --- /dev/null +++ b/test/libsolidity/syntaxTests/double_stateVariable_declaration.sol @@ -0,0 +1,6 @@ +contract test { + uint256 variable; + uint128 variable; +} +// ---- +// DeclarationError: Identifier already declared. diff --git a/test/libsolidity/syntaxTests/double_variable_declaration.sol b/test/libsolidity/syntaxTests/double_variable_declaration.sol new file mode 100644 index 00000000..3349cfec --- /dev/null +++ b/test/libsolidity/syntaxTests/double_variable_declaration.sol @@ -0,0 +1,8 @@ +contract test { + function f() pure public { + uint256 x; + if (true) { uint256 x; } + } +} +// ---- +// DeclarationError: Identifier already declared. diff --git a/test/libsolidity/syntaxTests/double_variable_declaration_050.sol b/test/libsolidity/syntaxTests/double_variable_declaration_050.sol new file mode 100644 index 00000000..9c2d40d5 --- /dev/null +++ b/test/libsolidity/syntaxTests/double_variable_declaration_050.sol @@ -0,0 +1,11 @@ +pragma experimental "v0.5.0"; +contract test { + function f() pure public { + uint256 x; + if (true) { uint256 x; } + } +} +// ---- +// Warning: This declaration shadows an existing declaration. +// Warning: Unused local variable. +// Warning: Unused local variable. diff --git a/test/libsolidity/syntaxTests/smoke_test.sol b/test/libsolidity/syntaxTests/smoke_test.sol new file mode 100644 index 00000000..2d48098a --- /dev/null +++ b/test/libsolidity/syntaxTests/smoke_test.sol @@ -0,0 +1,6 @@ +contract test { + uint256 stateVariable1; + function fun(uint256 arg1) public { uint256 y; y = arg1; } +} +// ---- +// Warning: Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/virtualLookup/modifiers_in_libraries.sol b/test/libsolidity/syntaxTests/virtualLookup/modifiers_in_libraries.sol new file mode 100644 index 00000000..b033fd0c --- /dev/null +++ b/test/libsolidity/syntaxTests/virtualLookup/modifiers_in_libraries.sol @@ -0,0 +1,14 @@ +library WithModifier { + modifier mod() { require(msg.value > 10 ether); _; } + function withMod(uint self) mod() internal view { require(self > 0); } +} + +contract Test { + using WithModifier for *; + + function f(uint _value) public payable { + _value.withMod(); + WithModifier.withMod(_value); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/visibility/external_library_function.sol b/test/libsolidity/syntaxTests/visibility/external_library_function.sol new file mode 100644 index 00000000..110e74db --- /dev/null +++ b/test/libsolidity/syntaxTests/visibility/external_library_function.sol @@ -0,0 +1,14 @@ +library L { + function f(uint) pure external {} +} + +contract C { + using L for *; + + function f() public pure { + L.f(2); + uint x; + x.f(); + } +} +// ---- diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt new file mode 100644 index 00000000..a693ebab --- /dev/null +++ b/test/tools/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(solfuzzer fuzzer.cpp) +target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES}) diff --git a/test/fuzzer.cpp b/test/tools/fuzzer.cpp index 45738baa..71f38b67 100644 --- a/test/fuzzer.cpp +++ b/test/tools/fuzzer.cpp @@ -76,6 +76,7 @@ void testConstantOptimizer() ConstantOptimisationMethod::optimiseConstants( isCreation, runs, + EVMVersion{}, assembly, const_cast<AssemblyItems&>(assembly.items()) ); |