Merge pull request #9536 from ethereum/develop

Merge develop into release for 0.7.0
This commit is contained in:
chriseth 2020-07-28 14:33:04 +02:00 committed by GitHub
commit 9e61f92bd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1066 changed files with 5903 additions and 4121 deletions

View File

@ -9,16 +9,20 @@ version: 2.1
parameters:
ubuntu-1804-docker-image:
type: string
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:4484ac3da8fdc337cc77a7a7be1af71cd0f28f9c890d934f1d6ae7532beb66b1"
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1804-2
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:9ab317e583c395e50884ba82e9f99811c374344cea4c550725be8ec836e07acc"
ubuntu-2004-docker-image:
type: string
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:48b5bb6b91ac7dddfe9345c88876ebed126c652258800f114caed69db73b29bf"
# solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-2
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:cbfa42d8ecbe94391ba8837e218869242666de7a0da6ccac065a856c85b6a6a0"
ubuntu-2004-clang-docker-image:
type: string
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d8775de58167db5a11690fdb6ef639317fe1e579ec5d46e9732d2d903b55d135"
# solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-2
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:7a4d5271b5552139d9f2caefc50d42f401bf74132cf8f253e199e11c80ab42de"
ubuntu-1604-clang-ossfuzz-docker-image:
type: string
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:db52f3257396814215744a19904e42c07e040ab36b68be72a27ba71ad2f1083c"
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-2
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:efaabb3c143f64171be596932c62013bcfd7f73b1fbcb832025a34dd2b6e6922"
emscripten-docker-image:
type: string
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d557d015918c3cf68b0d22839bab41013f0757b651a7fef21595f89721dbebcc"
@ -385,11 +389,13 @@ jobs:
- run: *run_docs_pragma_min_version
b_ubu_clang: &build_ubuntu2004_clang
resource_class: xlarge
docker:
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
environment:
CC: clang
CXX: clang++
MAKEFLAGS: -j 10
steps:
- checkout
- run: *run_build

View File

@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.6.12")
set(PROJECT_VERSION "0.7.0")
# OSX target needed in order to support std::visit
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)

View File

@ -1,3 +1,49 @@
### 0.7.0 (2020-07-28)
Breaking changes:
* Inline Assembly: Disallow ``.`` in user-defined function and variable names.
* Inline Assembly: Slot and offset of storage pointer variable ``x`` are accessed via ``x.slot`` and ``x.offset`` instead of ``x_slot`` and ``x_offset``.
* JSON AST: Mark hex string literals with ``kind: "hexString"``.
* JSON AST: Remove members with ``null`` value from JSON output.
* Parser: Disallow ``gwei`` as identifier.
* Parser: Disallow dot syntax for ``value`` and ``gas``.
* Parser: Disallow non-printable characters in string literals.
* Parser: Introduce Unicode string literals: ``unicode"😃"``.
* Parser: NatSpec comments on variables are only allowed for public state variables.
* Parser: Remove the ``finney`` and ``szabo`` denominations.
* Parser: Remove the identifier ``now`` (replaced by ``block.timestamp``).
* Reference Resolver: ``using A for B`` only affects the contract it is mentioned in and not all derived contracts
* Type Checker: Disallow ``virtual`` for library functions.
* Type Checker: Disallow assignments to state variables that contain nested mappings.
* Type checker: Disallow events with same name and parameter types in inheritance hierarchy.
* Type Checker: Disallow shifts by signed types.
* Type Checker: Disallow structs and arrays in memory or calldata if they contain nested mappings.
* Type Checker: Exponentiation and shifts of literals by non-literals will always use ``uint256`` or ``int256`` as a type.
* Yul: Disallow consecutive and trailing dots in identifiers. Leading dots were already disallowed.
* Yul: Disallow EVM instruction `pc()`.
Language Features:
* Inheritance: Allow overrides to have stricter state mutability: ``view`` can override ``nonpayable`` and ``pure`` can override ``view``.
* Parser: Deprecate visibility for constructors.
* State mutability: Do not issue recommendation for stricter mutability for virtual functions but do issue it for functions that override.
Compiler Features:
* SMTChecker: Report multi-transaction counterexamples including the function calls that initiate the transactions. This does not include concrete values for reference types and reentrant calls.
* Variable declarations using the ``var`` keyword are not recognized anymore.
Bugfixes:
* Immutables: Fix internal compiler error when immutables are not assigned.
* Inheritance: Disallow public state variables overwriting ``pure`` functions.
* NatSpec: Constructors and functions have consistent userdoc output.
* SMTChecker: Fix internal error when assigning to a 1-tuple.
* SMTChecker: Fix internal error when tuples have extra effectless parenthesis.
* State Mutability: Constant public state variables are considered ``pure`` functions.
* Type Checker: Fixing deduction issues on function types when function call has named arguments.
### 0.6.12 (2020-07-22)
Language Features:
@ -29,6 +75,7 @@ Build System:
### 0.6.11 (2020-07-07)
Language Features:
* General: Add unit denomination ``gwei``
* Yul: Support ``linkersymbol`` builtin in standalone assembly mode to refer to library addresses.

View File

@ -37,7 +37,7 @@ found in the [Solidity documentation](https://solidity.readthedocs.io/en/latest/
A "Hello World" program in Solidity is of even less use than in other languages, but still:
```solidity
pragma solidity ^0.6.0;
pragma solidity >=0.6.0 <0.8.0;
contract HelloWorld {
function helloWorld() external pure returns (string memory) {

View File

@ -26,15 +26,26 @@
- [ ] Run ``scripts/create_source_tarball.sh`` while being on the tag to create the source tarball. Make sure to create ``prerelease.txt`` before: (``echo -n > prerelease.txt``). This will create the tarball in a directory called ``upload``.
- [ ] Take the tarball from the upload directory (its name should be ``solidity_x.x.x.tar.gz``, otherwise ``prerelease.txt`` was missing in the step before) and upload the source tarball to the release page.
### Homebrew and MacOS
- [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb
- [ ] Take the binary from the ``b_osx`` run of the released commit in circle-ci and add it to the release page as ``solc-macos``.
### Update solc-bin
- [ ] Copy ``soljson.js`` from the release page to ``solc-bin/bin/soljson-v<version>+commit.<commit>.js``
- [ ] Copy ``solc-static-linux`` from the release page to ``solc-bin/linux-amd64/solc-linux-amd64-v<version>+commit.<commit>``
- [ ] Copy ``solc-macos`` from the release page to ``solc-bin/macos-amd64/solc-macos-amd64-v<version>+commit.<commit>``
- [ ] Copy ``solc-windows.zip`` from the release page to ``solc-bin/windows-amd64/solc-windows-amd64-v<version>+commit.<commit>.zip``
- [ ] Make the linux and the macos binaries executable.
- [ ] Run ``./update`` in ``solc-bin`` and verify that the script has updated ``list.js``, ``list.txt`` and ``list.json`` files correctly and that symlinks to the new release have been added in ``solc-bin/wasm/`` and ``solc-bin/emscripten-wasm32/``.
- [ ] Create a pull request and merge.
### PPA
- [ ] Change ``scripts/release_ppa.sh`` to match your key's email and key id.
- [ ] Run ``scripts/release_ppa.sh release`` to create the PPA release (you need the relevant openssl key).
- [ ] Wait for the ``~ethereum/ubuntu/ethereum-static`` PPA build to be finished and published for *all platforms*. SERIOUSLY: DO NOT PROCEED EARLIER!!! *After* the static builds are *published*, copy the static package to the ``~ethereum/ubuntu/ethereum`` PPA for the destination series ``Trusty`` and ``Xenial`` while selecting ``Copy existing binaries``.
- [ ] Check that the Docker release was pushed to Docker Hub (this still seems to have problems, run ``./scripts/docker_deploy_manual.sh v0.x.x``).
### Homebrew and MacOS
- [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb
- [ ] Take the binary from the ``b_osx`` run of the released commit in circle-ci and add it to the release page as ``solc-macos``.
### Docker
- [ ] Check that the Docker release was pushed to Docker Hub (this still seems to have problems, run ``./scripts/docker_deploy_manual.sh v0.x.x``).
### Documentation
- [ ] Build the new version on https://readthedocs.org/projects/solidity/ (select `latest` on the bottom of the page and click `BUILD`)
@ -46,6 +57,7 @@
- [ ] Make sure to push the tag ``npm publish`` created with ``git push --tags``.
### Post-release
- [ ] Publish the blog post.
- [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry.
- [ ] Merge ``release`` back into ``develop``.
- [ ] Announce on Twitter and Reddit.

View File

@ -311,7 +311,7 @@ This will no longer compile with Solidity v0.5.0. However, you can define a comp
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
interface OldContract {
function someOldFunction(uint8 a) external;
function anotherOldFunction() external returns (bool);
@ -329,7 +329,7 @@ Given the interface defined above, you can now easily use the already deployed p
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
interface OldContract {
function someOldFunction(uint8 a) external;

View File

@ -0,0 +1,133 @@
********************************
Solidity v0.7.0 Breaking Changes
********************************
This section highlights the main breaking changes introduced in Solidity
version 0.7.0, along with the reasoning behind the changes and how to update
affected code.
For the full list check
`the release changelog <https://github.com/ethereum/solidity/releases/tag/v0.7.0>`_.
Silent Changes of the Semantics
===============================
* Exponentiation and shifts of literals by non-literals (e.g. ``1 << x`` or ``2 ** x``)
will always use either the type ``uint256`` (for non-negative literals) or
``int256`` (for negative literals) to perform the operation.
Previously, the operation was performed in the type of the shift amount / the
exponent which can be misleading.
Changes to the Syntax
=====================
* In external function and contract creation calls, Ether and gas is now specified using a new syntax:
``x.f{gas: 10000, value: 2 ether}(arg1, arg2)``.
The old syntax -- ``x.f.gas(10000).value(2 ether)(arg1, arg2)`` -- will cause an error.
* The global variable ``now`` is deprecated, ``block.timestamp`` should be used instead.
The single identifier ``now`` is too generic for a global variable and could give the impression
that it changes during transaction processing, whereas ``block.timestamp`` correctly
reflects the fact that it is just a property of the block.
* NatSpec comments on variables are only allowed for public state variables and not
for local or internal variables.
* The token ``gwei`` is a keyword now (used to specify, e.g. ``2 gwei`` as a number)
and cannot be used as an identifier.
* String literals now can only contain printable ASCII characters and this also includes a variety of
escape sequences, such as hexadecimal (``\xff``) and unicode escapes (``\u20ac``).
* Unicode string literals are supported now to accommodate valid UTF-8 sequences. They are identified
with the ``unicode`` prefix: ``unicode"Hello 😃"``.
* State Mutability: The state mutability of functions can now be restricted during inheritance:
Functions with default state mutability can be overridden by ``pure`` and ``view`` functions
while ``view`` functions can be overridden by ``pure`` functions.
At the same time, public state variables are considered ``view`` and even ``pure``
if they are constants.
Inline Assembly
---------------
* Disallow ``.`` in user-defined function and variable names in inline assembly.
It is still valid if you use Solidity in Yul-only mode.
* Slot and offset of storage pointer variable ``x`` are accessed via ``x.slot``
and ``x.offset`` instead of ``x_slot`` and ``x_offset``.
Removal of Unused or Unsafe Features
====================================
Mappings outside Storage
------------------------
* If a struct or array contains a mapping, it can only be used in storage.
Previously, mapping members were silently skipped in memory, which
is confusing and error-prone.
* Assignments to structs or arrays in storage does not work if they contain
mappings.
Previously, mappings were silently skipped during the copy operation, which
is misleading and error-prone.
Functions and Events
--------------------
* Visibility (``public`` / ``external``) is not needed for constructors anymore:
To prevent a contract from being created, it can be marked ``abstract``.
This makes the visibility concept for constructors obsolete.
* Type Checker: Disallow ``virtual`` for library functions:
Since libraries cannot be inherited from, library functions should not be virtual.
* Multiple events with the same name and parameter types in the same
inheritance hierarchy are disallowed.
* ``using A for B`` only affects the contract it is mentioned in.
Previously, the effect was inherited. Now, you have to repeat the ``using``
statement in all derived contracts that make use of the feature.
Expressions
-----------
* Shifts by signed types are disallowed.
Previously, shifts by negative amounts were allowed, but reverted at runtime.
* The ``finney`` and ``szabo`` denominations are removed.
They are rarely used and do not make the actual amount readily visible. Instead, explicit
values like ``1e20`` or the very common ``gwei`` can be used.
Declarations
------------
* The keyword ``var`` cannot be used anymore.
Previously, this keyword would parse but result in a type error and
a suggestion about which type to use. Now, it results in a parser error.
Interface Changes
=================
* JSON AST: Mark hex string literals with ``kind: "hexString"``.
* JSON AST: Members with value ``null`` are removed from JSON output.
* NatSpec: Constructors and functions have consistent userdoc output.
How to update your code
=======================
This section gives detailed instructions on how to update prior code for every breaking change.
* Change ``x.f.value(...)()`` to ``x.f{value: ...}()``. Similarly ``(new C).value(...)()`` to
``new C{value: ...}()`` and ``x.f.gas(...).value(...)()`` to ``x.f{gas: ..., value: ...}()``.
* Change ``now`` to ``block.timestamp``.
* Change types of right operand in shift operators to unsigned types. For example change ``x >> (256 - y)`` to
``x >> uint(256 - y)``.
* Repeat the ``using A for B`` statements in all derived contracts if needed.
* Remove the ``public`` keyword from every constructor.
* Remove the ``internal`` keyword from every constructor and add ``abstract`` to the contract (if not already present).
* Change ``_slot`` and ``_offset`` suffixes in inline assembly to ``.slot`` and ``.offset``, respectively.

View File

@ -253,6 +253,7 @@ primaryExpression
| numberLiteral
| hexLiteral
| stringLiteral
| unicodeStringLiteral
| identifier ('[' ']')?
| TypeKeyword
| tupleExpression
@ -306,7 +307,7 @@ assemblyBlock
: '{' assemblyItem* '}' ;
assemblyExpression
: assemblyCall | assemblyLiteral ;
: assemblyCall | assemblyLiteral | assemblyIdentifier ;
assemblyCall
: ( 'return' | 'address' | 'byte' | identifier ) ( '(' assemblyExpression? ( ',' assemblyExpression )* ')' )? ;
@ -318,7 +319,10 @@ assemblyAssignment
: assemblyIdentifierList ':=' assemblyExpression ;
assemblyIdentifierList
: identifier ( ',' identifier )* ;
: assemblyIdentifier ( ',' assemblyIdentifier )* ;
assemblyIdentifier
: identifier ( '.' identifier )* ;
assemblyStackAssignment
: '=:' identifier ;
@ -358,11 +362,12 @@ assemblyType
subAssembly
: 'assembly' identifier assemblyBlock ;
// 'finney' and 'szabo' are no longer supported as denominations by latest Solidity.
numberLiteral
: (DecimalNumber | HexNumber) (NumberUnit | Gwei)?;
: (DecimalNumber | HexNumber) (NumberUnit | Gwei | Finney | Szabo)?;
identifier
: (Gwei | 'from' | 'calldata' | 'address' | Identifier) ;
: (Gwei | Finney | Szabo | 'from' | 'calldata' | 'address' | Identifier) ;
BooleanLiteral
: 'true' | 'false' ;
@ -382,10 +387,12 @@ HexDigits
: HexCharacter ( '_'? HexCharacter )* ;
NumberUnit
: 'wei' | 'szabo' | 'finney' | 'ether'
: 'wei' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years' ;
Gwei: 'gwei' ;
Szabo: 'szabo' ;
Finney: 'finney' ;
HexLiteralFragment
: 'hex' (('"' HexDigits? '"') | ('\'' HexDigits? '\'')) ;
@ -455,6 +462,13 @@ StringLiteralFragment
: '"' DoubleQuotedStringCharacter* '"'
| '\'' SingleQuotedStringCharacter* '\'' ;
unicodeStringLiteral
: UnicodeStringLiteralFragment+ ;
UnicodeStringLiteralFragment
: 'unicode"' DoubleQuotedStringCharacter* '"'
| 'unicode\'' SingleQuotedStringCharacter* '\'' ;
fragment
DoubleQuotedStringCharacter
: ~["\r\n\\] | ('\\' .) ;

View File

@ -233,7 +233,7 @@ Given the contract:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract Foo {
function bar(bytes3[2] memory) public pure {}
@ -537,11 +537,11 @@ For example,
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract Test {
constructor() public { b = hex"12345678901234567890123456789012"; }
constructor() { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); }
@ -586,7 +586,7 @@ As an example, the code
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.19 <0.7.0;
pragma solidity >=0.4.19 <0.8.0;
pragma experimental ABIEncoderV2;
contract Test {

View File

@ -42,7 +42,7 @@ without a compiler change.
.. code::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
library GetCode {
function at(address _addr) public view returns (bytes memory o_code) {
@ -68,7 +68,7 @@ efficient code, for example:
.. code::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
library VectorSum {
@ -132,14 +132,15 @@ For local storage variables or state variables, a single Yul identifier
is not sufficient, since they do not necessarily occupy a single full storage slot.
Therefore, their "address" is composed of a slot and a byte-offset
inside that slot. To retrieve the slot pointed to by the variable ``x``, you
use ``x_slot``, and to retrieve the byte-offset you use ``x_offset``.
use ``x.slot``, and to retrieve the byte-offset you use ``x.offset``.
Using ``x`` itself will result in an error.
Local Solidity variables are available for assignments, for example:
.. code::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract C {
uint b;
@ -147,7 +148,7 @@ Local Solidity variables are available for assignments, for example:
assembly {
// We ignore the storage slot offset, we know it is zero
// in this special case.
r := mul(x, sload(b_slot))
r := mul(x, sload(b.slot))
}
}
}
@ -164,20 +165,21 @@ Local Solidity variables are available for assignments, for example:
``assembly { signextend(<num_bytes_of_x_minus_one>, x) }``
Since Solidity 0.6.0 the name of a inline assembly variable may not end in ``_offset`` or ``_slot``
and it may not shadow any declaration visible in the scope of the inline assembly block
(including variable, contract and function declarations). Similarly, if the name of a declared
variable contains a dot ``.``, the prefix up to the ``.`` may not conflict with any
declaration visible in the scope of the inline assembly block.
Since Solidity 0.6.0 the name of a inline assembly variable may not
shadow any declaration visible in the scope of the inline assembly block
(including variable, contract and function declarations).
Since Solidity 0.7.0, variables and functions declared inside the
inline assembly block may not contain ``.``, but using ``.`` is
valid to access Solidity variables from outside the inline assembly block.
Assignments are possible to assembly-local variables and to function-local
variables. Take care that when you assign to variables that point to
memory or storage, you will only change the pointer and not the data.
You can assign to the ``_slot`` part of a local storage variable pointer.
For these (structs, arrays or mappings), the ``_offset`` part is always zero.
It is not possible to assign to the ``_slot`` or ``_offset`` part of a state variable,
You can assign to the ``.slot`` part of a local storage variable pointer.
For these (structs, arrays or mappings), the ``.offset`` part is always zero.
It is not possible to assign to the ``.slot`` or ``.offset`` part of a state variable,
though.

84
docs/brand-guide.rst Normal file
View File

@ -0,0 +1,84 @@
####################
Solidity Brand Guide
####################
This brand guide features information on Solidity's brand policy and
logo usage guidelines.
The Solidity Brand
==================
The Solidity programming language is an open-source, community project
governed by a core team. The core team is sponsored by the `Ethereum
Foundation <https://ethereum.foundation/>`_.
This document aims to provide information about how to best use the
Solidity brand name and logo.
We encourage you to read this document carefully before using the
brand name or the logo. Your cooperation is highly appreciated!
Solidity Brand Name
===================
"Solidity" should be used to refer to the Solidity programming language
solely.
Please do not use "Solidity":
- To refer to any other programming language.
- In a way that is misleading or may imply association of unrelated
modules, tools, documentation, or other resources with the Solidity
programming language.
- In ways that confuse the community as to whether the Solidity
programming language is open-source and free to use.
Solidity Logo License
=====================
.. image:: https://i.creativecommons.org/l/by/4.0/88x31.png
:width: 88
:alt: Creative Commons License
The Solidity logo is distributed and licensed under a `Creative Commons
Attribution 4.0 International License <http://creativecommons.org/licenses/by/4.0/>`_.
This is the most permissive Creative Commons license and allows reuse
and modifications for any purpose.
You are free to:
- **Share** — Copy and redistribute the material in any medium or format.
- **Adapt** — Remix, transform, and build upon the material for any
purpose, even commercially.
Under the following terms:
- **Attribution** — You must give appropriate credit, provide a link to
the license, and indicate if changes were made. You may do so in any
reasonable manner, but not in any way that suggests the the Solidity
core team endorses you or your use.
When using the Solidity logo, please respect the Solidity logo guidelines.
Solidity Logo Guidelines
========================
.. image:: logo.svg
:width: 256
Please do not:
- Change the ratio of the logo (do not stretch it or cut it).
- Change the colors of the logo, unless it is absolutely necessary.
Credits
=======
This document was, in parts, derived from the `Python Software
Foundation Trademark Usage Policy <https://www.python.org/psf/trademarks/>`_
and the `Rust Media Guide <https://www.rust-lang.org/policies/media-guide>`_.

View File

@ -1181,5 +1181,9 @@
"UsingForCalldata"
],
"released": "2020-06-04"
},
"0.7.0": {
"bugs": [],
"released": "2020-07-28"
}
}

View File

@ -67,7 +67,7 @@ The following is the order of precedence for operators, listed in order of evalu
| *15* | Comma operator | ``,`` |
+------------+-------------------------------------+--------------------------------------------+
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
Global Variables
================
@ -91,7 +91,6 @@ Global Variables
- ``msg.data`` (``bytes``): complete calldata
- ``msg.sender`` (``address payable``): 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``)
- ``tx.gasprice`` (``uint``): gas price of the transaction
- ``tx.origin`` (``address payable``): sender of the transaction (full call chain)
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
@ -126,7 +125,7 @@ Global Variables
- ``type(T).max`` (``T``): the maximum value representable by the integer type ``T``, see :ref:`Type Information<meta-type>`.
.. note::
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness,
unless you know what you are doing.
Both the timestamp and the block hash can be influenced by miners to some degree.
@ -146,6 +145,8 @@ Global Variables
In version 0.5.0, the following aliases were removed: ``suicide`` as alias for ``selfdestruct``,
``msg.gas`` as alias for ``gasleft``, ``block.blockhash`` as alias for ``blockhash`` and
``sha3`` as alias for ``keccak256``.
.. note::
In version 0.7.0, the alias ``now`` (for ``block.timestamp``) was removed.
.. index:: visibility, public, private, external, internal

View File

@ -28,7 +28,7 @@ you receive the funds of the person who is now the richest.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract WithdrawalContract {
address public richest;
@ -36,7 +36,7 @@ you receive the funds of the person who is now the richest.
mapping (address => uint) pendingWithdrawals;
constructor() public payable {
constructor() payable {
richest = msg.sender;
mostSent = msg.value;
}
@ -62,13 +62,13 @@ This is as opposed to the more intuitive sending pattern:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract SendContract {
address payable public richest;
uint public mostSent;
constructor() public payable {
constructor() payable {
richest = msg.sender;
mostSent = msg.value;
}
@ -124,14 +124,14 @@ restrictions highly readable.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
contract AccessRestriction {
// These will be assigned at the construction
// phase, where `msg.sender` is the account
// creating this contract.
address public owner = msg.sender;
uint public creationTime = now;
uint public creationTime = block.timestamp;
// Modifiers can be used to change
// the body of a function.
@ -162,7 +162,7 @@ restrictions highly readable.
modifier onlyAfter(uint _time) {
require(
now >= _time,
block.timestamp >= _time,
"Function called too early."
);
_;
@ -277,7 +277,7 @@ function finishes.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
contract StateMachine {
enum Stages {
@ -291,7 +291,7 @@ function finishes.
// This is the current stage.
Stages public stage = Stages.AcceptingBlindedBids;
uint public creationTime = now;
uint public creationTime = block.timestamp;
modifier atStage(Stages _stage) {
require(
@ -310,10 +310,10 @@ function finishes.
// will not take the new stage into account.
modifier timedTransitions() {
if (stage == Stages.AcceptingBlindedBids &&
now >= creationTime + 10 days)
block.timestamp >= creationTime + 10 days)
nextStage();
if (stage == Stages.RevealBids &&
now >= creationTime + 12 days)
block.timestamp >= creationTime + 12 days)
nextStage();
// The other stages transition by transaction
_;

View File

@ -14,7 +14,7 @@ defined as abstract, because the function ``utterance()`` was defined, but no im
provided (no implementation body ``{ }`` was given).::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
abstract contract Feline {
function utterance() public virtual returns (bytes32);
@ -24,14 +24,14 @@ Such abstract contracts can not be instantiated directly. This is also true, if
all defined functions. The usage of an abstract contract as a base class is shown in the following example::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
pragma solidity >=0.6.0 <0.8.0;
abstract contract Feline {
function utterance() public virtual returns (bytes32);
function utterance() public pure virtual returns (bytes32);
}
contract Cat is Feline {
function utterance() public override returns (bytes32) { return "miaow"; }
function utterance() public pure override returns (bytes32) { return "miaow"; }
}
If a contract inherits from an abstract contract and does not implement all non-implemented

View File

@ -12,13 +12,21 @@ for ``immutable``, it can still be assigned at construction time.
The compiler does not reserve a storage slot for these variables, and every occurrence is
replaced by the respective value.
Compared to regular state variables, the gas costs of constant and immutable variables
are much lower. For a constant variable, the expression assigned to it is copied to
all the places where it is accessed and also re-evaluated each time. This allows for local
optimizations. Immutable variables are evaluated once at construction time and their value
is copied to all the places in the code where they are accessed. For these values,
32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values
can sometimes be cheaper than immutable values.
Not all types for constants and immutables are implemented at this time. The only supported types are
`strings <strings>`_ (only for constants) and `value types <value-types>`_.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >0.6.4 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract C {
uint constant X = 32**22 + 8;
@ -28,7 +36,7 @@ Not all types for constants and immutables are implemented at this time. The onl
uint immutable maxBalance;
address immutable owner = msg.sender;
constructor(uint _decimals, address _reference) public {
constructor(uint _decimals, address _reference) {
decimals = _decimals;
// Assignments to immutables can even access the environment.
maxBalance = _reference.balance;
@ -45,7 +53,7 @@ Constant
For ``constant`` variables, the value has to be a constant at compile time and it has to be
assigned where the variable is declared. Any expression
that accesses storage, blockchain data (e.g. ``now``, ``address(this).balance`` or
that accesses storage, blockchain data (e.g. ``block.timestamp``, ``address(this).balance`` or
``block.number``) or
execution data (``msg.value`` or ``gasleft()``) or makes calls to external contracts is disallowed. Expressions
that might have a side-effect on memory allocation are allowed, but those that

View File

@ -35,7 +35,7 @@ This means that cyclic creation dependencies are impossible.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
contract OwnedToken {
@ -48,7 +48,7 @@ This means that cyclic creation dependencies are impossible.
// This is the constructor which registers the
// creator and the assigned name.
constructor(bytes32 _name) public {
constructor(bytes32 _name) {
// State variables are accessed via their name
// and not via e.g. `this.owner`. Functions can
// be accessed directly or through `this.f`,

View File

@ -66,7 +66,7 @@ is that they are cheaper to deploy and call.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.21 <0.7.0;
pragma solidity >=0.4.21 <0.8.0;
contract ClientReceipt {
event Deposit(
@ -140,7 +140,7 @@ as topics. The event call above can be performed in the same way as
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.10 <0.7.0;
pragma solidity >=0.4.10 <0.8.0;
contract C {
function f() public payable {

View File

@ -18,10 +18,10 @@ if they are marked ``virtual``. For details, please see
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract owned {
constructor() public { owner = msg.sender; }
constructor() { owner = msg.sender; }
address payable owner;
// This contract only defines a modifier but does not use
@ -63,7 +63,7 @@ if they are marked ``virtual``. For details, please see
mapping (address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) public { price = initialPrice; }
constructor(uint initialPrice) { price = initialPrice; }
// It is important to also provide the
// `payable` keyword here, otherwise the function will

View File

@ -24,7 +24,7 @@ For example, if you want your contract to accept one kind of external call
with two integers, you would use something like the following::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract Simple {
uint sum;
@ -57,7 +57,7 @@ For example, suppose you want to return two results: the sum and the product of
two integers passed as function parameters, then you use something like::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract Simple {
function arithmetic(uint _a, uint _b)
@ -82,7 +82,7 @@ or you can provide return values
statement::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract Simple {
function arithmetic(uint _a, uint _b)
@ -146,11 +146,11 @@ The following statements are considered modifying the state:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
contract C {
function f(uint a, uint b) public view returns (uint) {
return a * (b + 42) + now;
return a * (b + 42) + block.timestamp;
}
}
@ -192,7 +192,7 @@ In addition to the list of state modifying statements explained above, the follo
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
contract C {
function f(uint a, uint b) public pure returns (uint) {
@ -286,7 +286,7 @@ Below you can see an example of a Sink contract that uses function ``receive``.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
pragma solidity >=0.6.0 <0.8.0;
// This contract keeps all Ether sent to it with no way
// to get it back.
@ -342,7 +342,7 @@ operations as long as there is enough gas passed on to it.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.7.0;
pragma solidity >=0.6.2 <0.8.0;
contract Test {
// This function is called for all messages sent to
@ -415,7 +415,7 @@ The following example shows overloading of the function
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract A {
function f(uint _in) public pure returns (uint out) {
@ -434,7 +434,7 @@ externally visible functions differ by their Solidity types but not by their ext
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
// This will not compile
contract A {
@ -468,7 +468,7 @@ candidate, resolution fails.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract A {
function f(uint8 _in) public pure returns (uint8 out) {

View File

@ -39,11 +39,11 @@ Details are given in the following example.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
pragma solidity >0.6.99 <0.8.0;
contract Owned {
constructor() public { owner = msg.sender; }
constructor() { owner = msg.sender; }
address payable owner;
}
@ -80,7 +80,7 @@ Details are given in the following example.
// also a base class of `Destructible`, yet there is only a single
// instance of `owned` (as for virtual inheritance in C++).
contract Named is Owned, Destructible {
constructor(bytes32 name) public {
constructor(bytes32 name) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).register(name);
}
@ -106,8 +106,8 @@ Details are given in the following example.
// If a constructor takes an argument, it needs to be
// provided in the header (or modifier-invocation-style at
// the constructor of the derived contract (see below)).
// provided in the header or modifier-invocation-style at
// the constructor of the derived contract (see below).
contract PriceFeed is Owned, Destructible, Named("GoldFeed") {
function updateInfo(uint newInfo) public {
if (msg.sender == owner) info = newInfo;
@ -127,10 +127,10 @@ destruction request. The way this is done is problematic, as
seen in the following example::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
pragma solidity >0.6.99 <0.8.0;
contract owned {
constructor() public { owner = msg.sender; }
constructor() { owner = msg.sender; }
address payable owner;
}
@ -157,10 +157,10 @@ explicitly in the final override, but this function will bypass
``Base1.destroy``. The way around this is to use ``super``::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract owned {
constructor() public { owner = msg.sender; }
constructor() { owner = msg.sender; }
address payable owner;
}
@ -203,23 +203,29 @@ Function Overriding
Base functions can be overridden by inheriting contracts to change their
behavior if they are marked as ``virtual``. The overriding function must then
use the ``override`` keyword in the function header as shown in this example:
use the ``override`` keyword in the function header.
The overriding function may only change the visibility of the overridden function from ``external`` to ``public``.
The mutability may be changed to a more strict one following the order:
``nonpayable`` can be overridden by ``view`` and ``pure``. ``view`` can be overridden by ``pure``.
``payable`` is an exception and cannot be changed to any other mutability.
The following example demonstrates changing mutability and visibility:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract Base
{
function foo() virtual public {}
function foo() virtual external view {}
}
contract Middle is Base {}
contract Inherited is Middle
{
function foo() public override {}
function foo() override public pure {}
}
For multiple inheritance, the most derived base contracts that define the same
@ -232,7 +238,7 @@ bases, it has to explicitly override it:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
contract Base1
{
@ -259,7 +265,7 @@ that already overrides all other functions.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
contract A { function f() public pure{} }
contract B is A {}
@ -300,11 +306,11 @@ of the variable:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
contract A
{
function f() external pure virtual returns(uint) { return 5; }
function f() external view virtual returns(uint) { return 5; }
}
contract B is A
@ -332,7 +338,7 @@ and the ``override`` keyword must be used in the overriding modifier:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
contract Base
{
@ -351,7 +357,7 @@ explicitly:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
contract Base1
{
@ -392,33 +398,39 @@ and all functions that are reachable from there through function calls.
It does not include the constructor code or internal functions that are
only called from the constructor.
Constructor functions can be either ``public`` or ``internal``. If there is no
If there is no
constructor, the contract will assume the default constructor, which is
equivalent to ``constructor() public {}``. For example:
equivalent to ``constructor() {}``. For example:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract A {
abstract contract A {
uint public a;
constructor(uint _a) internal {
constructor(uint _a) {
a = _a;
}
}
contract B is A(1) {
constructor() public {}
constructor() {}
}
A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract <abstract-contract>`.
You can use internal parameters in a constructor (for example storage pointers). In this case,
the contract has to be marked :ref:`abstract <abstract-contract>`, because these parameters
cannot be assigned valid values from outside but only through the constructors of derived contracts.
.. warning ::
Prior to version 0.4.22, constructors were defined as functions with the same name as the contract.
This syntax was deprecated and is not allowed anymore in version 0.5.0.
.. warning ::
Prior to version 0.7.0, you had to specify the visibility of constructors as either
``internal`` or ``public``.
.. index:: ! base;constructor
@ -430,21 +442,21 @@ linearization rules explained below. If the base constructors have arguments,
derived contracts need to specify all of them. This can be done in two ways::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract Base {
uint x;
constructor(uint _x) public { x = _x; }
constructor(uint _x) { x = _x; }
}
// Either directly specify in the inheritance list...
contract Derived1 is Base(7) {
constructor() public {}
constructor() {}
}
// or through a "modifier" of the derived constructor.
contract Derived2 is Base {
constructor(uint _y) Base(_y * _y) public {}
constructor(uint _y) Base(_y * _y) {}
}
One way is directly in the inheritance list (``is Base(7)``). The other is in
@ -490,7 +502,7 @@ error "Linearization of inheritance graph impossible".
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract X {}
contract A is X {}
@ -511,14 +523,14 @@ One area where inheritance linearization is especially important and perhaps not
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract Base1 {
constructor() public {}
constructor() {}
}
contract Base2 {
constructor() public {}
constructor() {}
}
// Constructors are executed in the following order:
@ -526,7 +538,7 @@ One area where inheritance linearization is especially important and perhaps not
// 2 - Base2
// 3 - Derived1
contract Derived1 is Base1, Base2 {
constructor() public Base1() Base2() {}
constructor() Base1() Base2() {}
}
// Constructors are executed in the following order:
@ -534,7 +546,7 @@ One area where inheritance linearization is especially important and perhaps not
// 2 - Base1
// 3 - Derived2
contract Derived2 is Base2, Base1 {
constructor() public Base2() Base1() {}
constructor() Base2() Base1() {}
}
// Constructors are still executed in the following order:
@ -542,7 +554,7 @@ One area where inheritance linearization is especially important and perhaps not
// 2 - Base1
// 3 - Derived3
contract Derived3 is Base2, Base1 {
constructor() public Base1() Base2() {}
constructor() Base1() Base2() {}
}

View File

@ -23,7 +23,7 @@ Interfaces are denoted by their own keyword:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.7.0;
pragma solidity >=0.6.2 <0.8.0;
interface Token {
enum TokenType { Fungible, NonFungible }
@ -44,7 +44,7 @@ inheritance.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.7.0;
pragma solidity >=0.6.2 <0.8.0;
interface ParentA {
function test() external returns (uint256);

View File

@ -30,9 +30,8 @@ not possible to destroy a library.
Libraries can be seen as implicit base contracts of the contracts that use them.
They will not be explicitly visible in the inheritance hierarchy, but calls
to library functions look just like calls to functions of explicit base
contracts (``L.f()`` if ``L`` is the name of the library). Furthermore,
``internal`` functions of libraries are visible in all contracts, just as
if the library were a base contract. Of course, calls to internal functions
contracts (using qualified access like ``L.f()``).
Of course, calls to internal functions
use the internal calling convention, which means that all internal types
can be passed and types :ref:`stored in memory <data-location>` will be passed by reference and not copied.
To realize this in the EVM, code of internal library functions
@ -48,7 +47,7 @@ more advanced example to implement a set).
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
// We define a new struct datatype that will be used to
@ -127,7 +126,7 @@ custom types without the overhead of external function calls:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
struct bigint {
uint[] limbs;
@ -242,7 +241,7 @@ Its value can be obtained from Solidity using the ``.selector`` member as follow
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.14 <0.7.0;
pragma solidity >=0.5.14 <0.8.0;
library L {
function f(uint256) external {}

View File

@ -30,7 +30,7 @@ Let us rewrite the set example from the
:ref:`libraries` in this way::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
// This is the same code as before, just without comments
@ -83,7 +83,7 @@ Let us rewrite the set example from the
It is also possible to extend elementary types in that way::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
library Search {
function indexOf(uint[] storage self, uint value)

View File

@ -55,7 +55,7 @@ return parameter list for functions.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract C {
function f(uint a) private pure returns (uint b) { return a + 1; }
@ -70,7 +70,7 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract C {
uint private data;
@ -115,7 +115,7 @@ when they are declared.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract C {
uint public data = 42;
@ -136,7 +136,7 @@ it evaluates to a state variable. If it is accessed externally
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract C {
uint public data;
@ -156,7 +156,7 @@ to write a function, for example:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract arrayExample {
// public state variable
@ -183,7 +183,7 @@ The next example is more complex:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract Complex {
struct Data {

View File

@ -27,10 +27,10 @@ Team Calls
If you have issues or pull requests to discuss, or are interested in hearing what
the team and contributors are working on, you can join our public team calls:
- Monday at 12pm CET
- Wednesday at 3pm CET
- Mondays at 12pm CET/CEST
- Wednesdays at 2pm CET/CEST
Both calls take place on `Google Hangouts <https://hangouts.google.com/hangouts/_/ethereum.org/solidity-weekly>`_.
Both calls take place on `Google Meet <https://meet.google.com/mrq-kbwv-edg>`_.
How to Report Issues
====================
@ -443,7 +443,7 @@ or ``interface`` using the ``./test/cmdlineTests.sh`` script when you create a P
ensure they work and pass tests before creating the PR.
Ensure that all code examples begin with a ``pragma`` version that spans the largest where the contract code is valid.
For example ``pragma solidity >=0.4.0 <0.7.0;``.
For example ``pragma solidity >=0.4.0 <0.8.0;``.
Running Documentation Tests
---------------------------

View File

@ -42,7 +42,7 @@ Functions of the current contract can be called directly ("internally"), also re
this nonsensical example::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
contract C {
function g(uint a) public pure returns (uint ret) { return a + f(); }
@ -84,7 +84,7 @@ to the total balance of that contract:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.7.0;
pragma solidity >=0.6.2 <0.8.0;
contract InfoFeed {
function info() public payable returns (uint ret) { return 42; }
@ -125,9 +125,9 @@ throws an exception or goes out of gas.
so your contract is not vulnerable to a reentrancy exploit.
.. note::
Before Solidity 0.6.2, the recommended way to specify the value and gas
was to use ``f.value(x).gas(g)()``. This is still possible but deprecated
and will be removed with Solidity 0.7.0.
Before Solidity 0.6.2, the recommended way to specify the value and gas was to
use ``f.value(x).gas(g)()``. This was deprecated in Solidity 0.6.2 and is no
longer possible since Solidity 0.7.0.
Named Calls and Anonymous Function Parameters
---------------------------------------------
@ -140,7 +140,7 @@ parameters from the function declaration, but can be in arbitrary order.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract C {
mapping(uint => uint) data;
@ -164,7 +164,7 @@ Those parameters will still be present on the stack, but they are inaccessible.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
contract C {
// omitted name for parameter
@ -188,11 +188,11 @@ is compiled so recursive creation-dependencies are not possible.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract D {
uint public x;
constructor(uint a) public payable {
constructor(uint a) payable {
x = a;
}
}
@ -244,11 +244,11 @@ which only need to be created if there is a dispute.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract D {
uint public x;
constructor(uint a) public {
constructor(uint a) {
x = a;
}
}
@ -314,7 +314,7 @@ groupings of expressions.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
contract C {
uint index;
@ -360,7 +360,7 @@ because only a reference and not a copy is passed.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
contract C {
uint[20] x;
@ -419,7 +419,7 @@ the two variables have the same name but disjoint scopes.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
contract C {
function minimalScoping() pure public {
{
@ -441,7 +441,7 @@ In any case, you will get a warning about the outer variable being shadowed.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
// This will report a warning
contract C {
function f() pure public returns (uint) {
@ -463,7 +463,7 @@ In any case, you will get a warning about the outer variable being shadowed.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
// This will not compile
contract C {
function f() pure public returns (uint) {
@ -552,7 +552,7 @@ and ``assert`` for internal error checking.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
contract Sharer {
function sendHalf(address payable addr) public payable returns (uint balance) {
@ -597,7 +597,7 @@ The following example shows how to use an error string together with ``revert``
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
contract VendingMachine {
function buy(uint amount) public payable {
@ -641,7 +641,7 @@ A failure in an external call can be caught using a try/catch statement, as foll
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
pragma solidity >=0.6.0 <0.8.0;
interface DataFeed { function getData(address token) external returns (uint value); }
@ -730,4 +730,4 @@ in scope in the block that follows.
out-of-gas situation and not a deliberate error condition:
The caller always retains 63/64th of the gas in a call and thus
even if the called contract goes out of gas, the caller still
has some gas left.
has some gas left.

View File

@ -25,7 +25,7 @@ to receive their money - contracts cannot activate themselves.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract SimpleAuction {
// Parameters of the auction. Times are either
@ -60,9 +60,9 @@ to receive their money - contracts cannot activate themselves.
constructor(
uint _biddingTime,
address payable _beneficiary
) public {
) {
beneficiary = _beneficiary;
auctionEndTime = now + _biddingTime;
auctionEndTime = block.timestamp + _biddingTime;
}
/// Bid on the auction with the value sent
@ -79,7 +79,7 @@ to receive their money - contracts cannot activate themselves.
// Revert the call if the bidding
// period is over.
require(
now <= auctionEndTime,
block.timestamp <= auctionEndTime,
"Auction already ended."
);
@ -141,7 +141,7 @@ to receive their money - contracts cannot activate themselves.
// external contracts.
// 1. Conditions
require(now >= auctionEndTime, "Auction not yet ended.");
require(block.timestamp >= auctionEndTime, "Auction not yet ended.");
require(!ended, "auctionEnd has already been called.");
// 2. Effects
@ -186,7 +186,7 @@ invalid bids.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract BlindAuction {
struct Bid {
@ -213,16 +213,16 @@ invalid bids.
/// functions. `onlyBefore` is applied to `bid` below:
/// The new function body is the modifier's body where
/// `_` is replaced by the old function body.
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }
modifier onlyBefore(uint _time) { require(block.timestamp < _time); _; }
modifier onlyAfter(uint _time) { require(block.timestamp > _time); _; }
constructor(
uint _biddingTime,
uint _revealTime,
address payable _beneficiary
) public {
) {
beneficiary = _beneficiary;
biddingEnd = now + _biddingTime;
biddingEnd = block.timestamp + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}
@ -328,4 +328,4 @@ invalid bids.
highestBidder = bidder;
return true;
}
}
}

View File

@ -143,14 +143,14 @@ The full contract
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.24 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract ReceiverPays {
address owner = msg.sender;
mapping(uint256 => bool) usedNonces;
constructor() public payable {}
constructor() payable {}
function claimPayment(uint256 amount, uint256 nonce, bytes memory signature) public {
require(!usedNonces[nonce]);
@ -340,7 +340,7 @@ The full contract
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract SimplePaymentChannel {
address payable public sender; // The account sending payments.
@ -348,12 +348,11 @@ The full contract
uint256 public expiration; // Timeout in case the recipient never closes.
constructor (address payable _recipient, uint256 duration)
public
payable
{
sender = msg.sender;
recipient = _recipient;
expiration = now + duration;
expiration = block.timestamp + duration;
}
/// the recipient can close the channel at any time by presenting a
@ -378,7 +377,7 @@ The full contract
/// if the timeout is reached without the recipient closing the channel,
/// then the Ether is released back to the sender.
function claimTimeout() public {
require(now >= expiration);
require(block.timestamp >= expiration);
selfdestruct(sender);
}

View File

@ -20,7 +20,7 @@ and the sum of all balances is an invariant across the lifetime of the contract.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
library Balances {
function move(mapping(address => uint256) storage balances, address from, address to, uint amount) internal {

View File

@ -26,7 +26,7 @@ you can use state machine-like constructs inside a contract.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract Purchase {
uint public value;
@ -74,7 +74,7 @@ you can use state machine-like constructs inside a contract.
// Ensure that `msg.value` is an even number.
// Division will truncate if it is an odd number.
// Check via multiplication that it wasn't an odd number.
constructor() public payable {
constructor() payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value, "Value has to be even.");

View File

@ -33,7 +33,7 @@ of votes.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
/// @title Voting with delegation.
contract Ballot {
@ -63,7 +63,7 @@ of votes.
Proposal[] public proposals;
/// Create a new ballot to choose one of `proposalNames`.
constructor(bytes32[] memory proposalNames) public {
constructor(bytes32[] memory proposalNames) {
chairperson = msg.sender;
voters[chairperson].weight = 1;

View File

@ -127,6 +127,7 @@ Contents
050-breaking-changes.rst
060-breaking-changes.rst
070-breaking-changes.rst
natspec-format.rst
security-considerations.rst
resources.rst
@ -136,3 +137,4 @@ Contents
common-patterns.rst
bugs.rst
contributing.rst
brand-guide.rst

View File

@ -72,7 +72,7 @@ the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint25
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract C {
@ -173,7 +173,7 @@ value and reference types, types that are encoded packed, and nested types.
.. code::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract A {
struct S {
uint128 a;

View File

@ -18,7 +18,7 @@ Storage Example
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract SimpleStorage {
uint storedData;
@ -83,7 +83,7 @@ registering with a username and password, all you need is an Ethereum keypair.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.5.99 <0.8.0;
contract Coin {
// The keyword "public" makes variables
@ -97,7 +97,7 @@ registering with a username and password, all you need is an Ethereum keypair.
// Constructor code is only run when the contract
// is created
constructor() public {
constructor() {
minter = msg.sender;
}
@ -186,7 +186,7 @@ and any user interface calls the automatically generated ``balances`` function f
.. index:: coin
The :ref:`constructor<constructor>` is a special function run during the creation of the contract and
The :ref:`constructor<constructor>` is a special function that is executed during the creation of the contract and
cannot be called afterwards. In this case, it permanently stores the address of the person creating the
contract. The ``msg`` variable (together with ``tx`` and ``block``) is a
:ref:`special global variable <special-variables-functions>` that

View File

@ -317,7 +317,7 @@ for the two function parameters and two return variables.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.21 <0.7.0;
pragma solidity >=0.4.21 <0.8.0;
/** @title Shape calculator. */
contract ShapeCalculator {

View File

@ -49,7 +49,7 @@ The following example shows a contract and a function using all available tags.
.. code:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >0.6.10 <0.7.0;
pragma solidity >0.6.10 <0.8.0;
/// @title A simulator for trees
/// @author Larry A. Gardner

View File

@ -59,7 +59,7 @@ complete contract):
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
@ -83,7 +83,7 @@ as it uses ``call`` which forwards all remaining gas by default:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.7.0;
pragma solidity >=0.6.2 <0.8.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
@ -103,7 +103,7 @@ outlined further below:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.11 <0.7.0;
pragma solidity >=0.4.11 <0.8.0;
contract Fund {
/// @dev Mapping of ether shares of the contract.
@ -201,13 +201,13 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
address owner;
constructor() public {
constructor() {
owner = msg.sender;
}
@ -222,7 +222,7 @@ Now someone tricks you into sending Ether to the address of this attack wallet:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
pragma solidity >0.6.99 <0.8.0;
interface TxUserWallet {
function transferTo(address payable dest, uint amount) external;
@ -231,7 +231,7 @@ Now someone tricks you into sending Ether to the address of this attack wallet:
contract TxAttackWallet {
address payable owner;
constructor() public {
constructor() {
owner = msg.sender;
}
@ -283,7 +283,7 @@ field of a ``struct`` that is the base type of a dynamic storage array. The
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
contract Map {
mapping (uint => uint)[] array;

View File

@ -27,7 +27,7 @@ storage.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract SimpleStorage {
uint storedData; // State variable
@ -48,7 +48,7 @@ Functions are the executable units of code within a contract.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract SimpleAuction {
function bid() public payable { // Function
@ -77,7 +77,7 @@ Like functions, modifiers can be :ref:`overridden <modifier-overriding>`.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
contract Purchase {
address public seller;
@ -105,7 +105,7 @@ Events are convenience interfaces with the EVM logging facilities.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.21 <0.7.0;
pragma solidity >=0.4.21 <0.8.0;
contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount); // Event
@ -130,7 +130,7 @@ Structs are custom defined types that can group several variables (see
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract Ballot {
struct Voter { // Struct
@ -152,7 +152,7 @@ Enums can be used to create custom types with a finite set of 'constant values'
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract Purchase {
enum State { Created, Locked, Inactive } // Enum

View File

@ -56,7 +56,7 @@ Surround top level declarations in solidity source with two blank lines.
Yes::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract A {
// ...
@ -75,7 +75,7 @@ Yes::
No::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract A {
// ...
@ -95,7 +95,7 @@ Blank lines may be omitted between groups of related one-liners (such as stub fu
Yes::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
pragma solidity >=0.6.0 <0.8.0;
abstract contract A {
function spam() public virtual pure;
@ -116,7 +116,7 @@ Yes::
No::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
abstract contract A {
function spam() virtual pure public;
@ -251,7 +251,7 @@ Import statements should always be placed at the top of the file.
Yes::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
import "./Owned.sol";
@ -266,7 +266,7 @@ Yes::
No::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract A {
// ...
@ -300,10 +300,10 @@ Within a grouping, place the ``view`` and ``pure`` functions last.
Yes::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
pragma solidity >0.6.99 <0.8.0;
contract A {
constructor() public {
constructor() {
// ...
}
@ -337,7 +337,7 @@ Yes::
No::
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.0;
pragma solidity >0.6.99 <0.8.0;
contract A {
@ -357,7 +357,7 @@ No::
// Public functions
// ...
constructor() public {
constructor() {
// ...
}
@ -445,7 +445,7 @@ should:
Yes::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract Coin {
struct Bank {
@ -457,7 +457,7 @@ Yes::
No::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract Coin
{
@ -758,19 +758,19 @@ manner as modifiers if the function declaration is long or hard to read.
Yes::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
// Base contracts just to make this compile
contract B {
constructor(uint) public {
constructor(uint) {
}
}
contract C {
constructor(uint, uint) public {
constructor(uint, uint) {
}
}
contract D {
constructor(uint) public {
constructor(uint) {
}
}
@ -781,7 +781,6 @@ Yes::
B(param1)
C(param2, param3)
D(param4)
public
{
// do something with param5
x = param5;
@ -791,24 +790,24 @@ Yes::
No::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
// Base contracts just to make this compile
contract B {
constructor(uint) public {
constructor(uint) {
}
}
contract C {
constructor(uint, uint) public {
constructor(uint, uint) {
}
}
contract D {
constructor(uint) public {
constructor(uint) {
}
}
@ -819,8 +818,7 @@ No::
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
public {
D(param4) {
x = param5;
}
}
@ -832,8 +830,7 @@ No::
constructor(uint param1, uint param2, uint param3, uint param4, uint param5)
B(param1)
C(param2, param3)
D(param4)
public {
D(param4) {
x = param5;
}
}
@ -1015,14 +1012,14 @@ As shown in the example below, if the contract name is ``Congress`` and the libr
Yes::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
// Owned.sol
contract Owned {
address public owner;
constructor() public {
constructor() {
owner = msg.sender;
}
@ -1039,7 +1036,7 @@ Yes::
and in ``Congress.sol``::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
import "./Owned.sol";
@ -1051,14 +1048,14 @@ and in ``Congress.sol``::
No::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
// owned.sol
contract owned {
address public owner;
constructor() public {
constructor() {
owner = msg.sender;
}
@ -1096,7 +1093,7 @@ Events should be named using the CapWords style. Examples: ``Deposit``, ``Transf
Function Names
==============
Functions other than constructors should use mixedCase. Examples: ``getBalance``, ``transfer``, ``verifyOwner``, ``addMember``, ``changeOwner``.
Functions should use mixedCase. Examples: ``getBalance``, ``transfer``, ``verifyOwner``, ``addMember``, ``changeOwner``.
Function Argument Names
@ -1156,7 +1153,7 @@ For example, the contract from `a simple smart contract <simple-smart-contract>`
added looks like the one below::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
/// @author The Solidity Team

View File

@ -26,6 +26,7 @@ are allowed for state variables, as storage reference types
in functions, or as parameters for library functions.
They cannot be used as parameters or return parameters
of contract functions that are publicly visible.
These restrictions are also true for arrays and structs that contain mappings.
You can mark state variables of mapping type as ``public`` and Solidity creates a
:ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a parameter for the getter.
@ -42,7 +43,7 @@ contract that returns the value at the specified address.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract MappingExample {
mapping(address => uint) public balances;
@ -68,7 +69,7 @@ The example below uses ``_allowances`` to record the amount someone else is allo
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
contract MappingExample {
@ -123,7 +124,7 @@ the ``sum`` function iterates over to sum all the values.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }

View File

@ -43,7 +43,7 @@ value it referred to previously.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract DeleteExample {
uint data;

View File

@ -63,7 +63,7 @@ Data locations are not only relevant for persistency of data, but also for the s
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.7.0;
pragma solidity >=0.5.0 <0.8.0;
contract C {
// The data location of x is storage.
@ -174,7 +174,7 @@ or create a new memory array and copy every element.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract C {
function f(uint len) public pure {
@ -206,7 +206,7 @@ the first element to ``uint``.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract C {
function f() public pure {
@ -223,7 +223,7 @@ memory arrays, i.e. the following is not possible:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
// This will not compile.
contract C {
@ -243,7 +243,7 @@ individual elements:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.4.0 <0.8.0;
contract C {
function f() public pure {
@ -301,7 +301,7 @@ Array Members
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
@ -434,13 +434,13 @@ Array slices are useful to ABI-decode secondary data passed in function paramete
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >0.6.99 <0.8.0;
contract Proxy {
/// @dev Address of the client contract managed by proxy i.e., this contract
address client;
constructor(address _client) public {
constructor(address _client) {
client = _client;
}
@ -478,7 +478,7 @@ shown in the following example:
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
pragma solidity >=0.6.0 <0.8.0;
// Defines a new type with two fields.
// Declaring a struct outside of a contract allows
@ -505,12 +505,11 @@ shown in the following example:
function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
campaignID = numCampaigns++; // campaignID is return variable
// Creates new struct in memory and copies it to storage.
// We leave out the mapping type, because it is not valid in memory.
// If structs are copied (even from storage to storage),
// types that are not valid outside of storage (ex. mappings and array of mappings)
// are always omitted, because they cannot be enumerated.
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
// We cannot use "campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0)"
// because the RHS creates a memory-struct "Campaign" that contains a mapping.
Campaign storage c = campaigns[campaignID];
c.beneficiary = beneficiary;
c.fundingGoal = goal;
}
function contribute(uint campaignID) public payable {

View File

@ -64,11 +64,11 @@ Shifts
^^^^^^
The result of a shift operation has the type of the left operand, truncating the result to match the type.
Right operand must be unsigned type. Trying to shift by signed type will produce a compilation error.
- For positive and negative ``x`` values, ``x << y`` is equivalent to ``x * 2**y``.
- For positive ``x`` values, ``x >> y`` is equivalent to ``x / 2**y``.
- For negative ``x`` values, ``x >> y`` is equivalent to ``(x + 1) / 2**y - 1`` (which is the same as dividing ``x`` by ``2**y`` while rounding down towards negative infinity).
- In all cases, shifting by a negative ``y`` throws a runtime exception.
.. warning::
Before version ``0.5.0`` a right shift ``x >> y`` for negative ``x`` was equivalent to ``x / 2**y``,
@ -370,9 +370,9 @@ Operators:
* Shift operators: ``<<`` (left shift), ``>>`` (right shift)
* Index access: If ``x`` is of type ``bytesI``, then ``x[k]`` for ``0 <= k < I`` returns the ``k`` th byte (read-only).
The shifting operator works with any integer type as right operand (but
The shifting operator works with unsigned integer type as right operand (but
returns the type of the left operand), which denotes the number of bits to shift by.
Shifting by a negative amount causes a runtime exception.
Shifting by a signed type will produce a compilation error.
Members:
@ -444,6 +444,11 @@ long as the operands are integers. If any of the two is fractional, bit operatio
and exponentiation is disallowed if the exponent is fractional (because that might result in
a non-rational number).
Shifts and exponentiation with literal numbers as left (or base) operand and integer types
as the right (exponent) operand are always performed
in the ``uint256`` (for non-negative literals) or ``int256`` (for a negative literals) type,
regardless of the type of the right (exponent) operand.
.. warning::
Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``.
@ -479,7 +484,9 @@ String literals are written with either double or single-quotes (``"foo"`` or ``
For example, with ``bytes32 samevar = "stringliteral"`` the string literal is interpreted in its raw byte form when assigned to a ``bytes32`` type.
String literals support the following escape characters:
String literals can only contain printable ASCII characters, which means the characters between and including 0x1F .. 0x7E.
Additionally, string literals also support the following escape characters:
- ``\<newline>`` (escapes an actual newline)
- ``\\`` (backslash)
@ -506,9 +513,19 @@ character sequence ``abcdef``.
"\n\"\'\\abc\
def"
Any unicode line terminator which is not a newline (i.e. LF, VF, FF, CR, NEL, LS, PS) is considered to
Any Unicode line terminator which is not a newline (i.e. LF, VF, FF, CR, NEL, LS, PS) is considered to
terminate the string literal. Newline only terminates the string literal if it is not preceded by a ``\``.
Unicode Literals
----------------
While regular string literals can only contain ASCII, Unicode literals prefixed with the keyword ``unicode`` can contain any valid UTF-8 sequence.
They also support the very same escape sequences as regular string literals.
::
string memory a = unicode"Hello 😃";
.. index:: literal, bytes
Hexadecimal Literals
@ -544,7 +561,7 @@ subsequent unsigned integer values starting from ``0``.
::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
@ -645,18 +662,19 @@ External (or public) functions have the following members:
* ``.address`` returns the address of the contract of the function.
* ``.selector`` returns the :ref:`ABI function selector <abi_function_selector>`
* ``.gas(uint)`` returns a callable function object which, when called, will send
the specified amount of gas to the target function. Deprecated - use ``{gas: ...}`` instead.
See :ref:`External Function Calls <external-function-calls>` for more information.
* ``.value(uint)`` returns a callable function object which, when called, will
send the specified amount of wei to the target function. Deprecated - use ``{value: ...}`` instead.
See :ref:`External Function Calls <external-function-calls>` for more information.
.. note::
External (or public) functions used to have the additional members
``.gas(uint)`` and ``.value(uint)``. These were deprecated in Solidity 0.6.2
and removed in Solidity 0.7.0. Instead use ``{gas: ...}`` and ``{value: ...}``
to specify the amount of gas or the amount of wei sent to a function,
respectively. See :ref:`External Function Calls <external-function-calls>` for
more information.
Example that shows how to use the members::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
// This will report a warning
pragma solidity >=0.6.4 <0.8.0;
contract Example {
function f() public payable returns (bytes4) {
@ -665,16 +683,14 @@ Example that shows how to use the members::
}
function g() public {
this.f.gas(10).value(800)();
// New syntax:
// this.f{gas: 10, value: 800}()
this.f{gas: 10, value: 800}();
}
}
Example that shows how to use internal function types::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.7.0;
pragma solidity >=0.4.16 <0.8.0;
library ArrayUtils {
// internal functions can be used in internal library functions because
@ -732,7 +748,7 @@ Example that shows how to use internal function types::
Another example that uses external function types::
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
contract Oracle {

View File

@ -2,23 +2,23 @@
Units and Globally Available Variables
**************************************
.. index:: wei, finney, szabo, ether
.. index:: wei, finney, szabo, gwei, ether
Ether Units
===========
A literal number can take a suffix of ``wei``, ``gwei``, ``finney``, ``szabo`` or ``ether`` to specify a subdenomination of Ether, where Ether numbers without a postfix are assumed to be Wei.
A literal number can take a suffix of ``wei``, ``gwei`` or ``ether`` to specify a subdenomination of Ether, where Ether numbers without a postfix are assumed to be Wei.
::
assert(1 wei == 1);
assert(1 gwei == 1e9);
assert(1 szabo == 1e12);
assert(1 finney == 1e15);
assert(1 ether == 1e18);
The only effect of the subdenomination suffix is a multiplication by a power of ten.
.. note::
The denominations ``finney`` and ``szabo`` have been removed in version 0.7.0.
.. index:: time, seconds, minutes, hours, days, weeks, years
@ -48,7 +48,7 @@ These suffixes cannot be applied to variables. For example, if you want to
interpret a function parameter in days, you can in the following way::
function f(uint start, uint daysAfter) public {
if (now >= start + daysAfter * 1 days) {
if (block.timestamp >= start + daysAfter * 1 days) {
// ...
}
}
@ -62,7 +62,7 @@ There are special variables and functions which always exist in the global
namespace and are mainly used to provide information about the blockchain
or are general-use utility functions.
.. index:: abi, block, coinbase, difficulty, encode, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin
.. index:: abi, block, coinbase, difficulty, encode, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, gas price, origin
Block and Transaction Properties
@ -79,7 +79,6 @@ Block and Transaction Properties
- ``msg.sender`` (``address payable``): 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
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
- ``tx.gasprice`` (``uint``): gas price of the transaction
- ``tx.origin`` (``address payable``): sender of the transaction (full call chain)
@ -89,7 +88,7 @@ Block and Transaction Properties
This includes calls to library functions.
.. note::
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
Do not rely on ``block.timestamp`` or ``blockhash`` as a source of randomness,
unless you know what you are doing.
Both the timestamp and the block hash can be influenced by miners to some degree.
@ -113,6 +112,9 @@ Block and Transaction Properties
The function ``gasleft`` was previously known as ``msg.gas``, which was deprecated in
version 0.4.21 and removed in version 0.5.0.
.. note::
In version 0.7.0, the alias ``now`` (for ``block.timestamp``) was removed.
.. index:: abi, encoding, packed
ABI Encoding and Decoding Functions

View File

@ -566,27 +566,40 @@ the latest version of the compiler.
Available upgrade modules
~~~~~~~~~~~~~~~~~~~~~~~~~
+-----------------+---------+--------------------------------------------------+
| Module | Version | Description |
+=================+=========+==================================================+
| ``constructor`` | 0.5.0 | Constructors must now be defined using the |
| | | ``constructor`` keyword. |
+-----------------+---------+--------------------------------------------------+
| ``visibility`` | 0.5.0 | Explicit function visibility is now mandatory, |
| | | defaults to ``public``. |
+-----------------+---------+--------------------------------------------------+
| ``abstract`` | 0.6.0 | The keyword ``abstract`` has to be used if a |
| | | contract does not implement all its functions. |
+-----------------+---------+--------------------------------------------------+
| ``virtual`` | 0.6.0 | Functions without implementation outside an |
| | | interface have to be marked ``virtual``. |
+-----------------+---------+--------------------------------------------------+
| ``override`` | 0.6.0 | When overriding a function or modifier, the new |
| | | keyword ``override`` must be used. |
+-----------------+---------+--------------------------------------------------+
+----------------------------+---------+--------------------------------------------------+
| Module | Version | Description |
+============================+=========+==================================================+
| ``constructor`` | 0.5.0 | Constructors must now be defined using the |
| | | ``constructor`` keyword. |
+----------------------------+---------+--------------------------------------------------+
| ``visibility`` | 0.5.0 | Explicit function visibility is now mandatory, |
| | | defaults to ``public``. |
+----------------------------+---------+--------------------------------------------------+
| ``abstract`` | 0.6.0 | The keyword ``abstract`` has to be used if a |
| | | contract does not implement all its functions. |
+----------------------------+---------+--------------------------------------------------+
| ``virtual`` | 0.6.0 | Functions without implementation outside an |
| | | interface have to be marked ``virtual``. |
+----------------------------+---------+--------------------------------------------------+
| ``override`` | 0.6.0 | When overriding a function or modifier, the new |
| | | keyword ``override`` must be used. |
+----------------------------+---------+--------------------------------------------------+
| ``dotsyntax`` | 0.7.0 | The following syntax is deprecated: |
| | | ``f.gas(...)()``, ``f.value(...)()`` and |
| | | ``(new C).value(...)()``. Replace these calls by |
| | | ``f{gas: ..., value: ...}()`` and |
| | | ``(new C){value: ...}()``. |
+----------------------------+---------+--------------------------------------------------+
| ``now`` | 0.7.0 | The ``now`` keyword is deprecated. Use |
| | | ``block.timestamp`` instead. |
+----------------------------+---------+--------------------------------------------------+
| ``constructor-visibility`` | 0.7.0 | Removes visibility of constructors. |
| | | |
+----------------------------+---------+--------------------------------------------------+
Please read :doc:`0.5.0 release notes <050-breaking-changes>` and
:doc:`0.6.0 release notes <060-breaking-changes>` for further details.
Please read :doc:`0.5.0 release notes <050-breaking-changes>`,
:doc:`0.6.0 release notes <060-breaking-changes>` and
:doc:`0.7.0 release notes <070-breaking-changes>` for further details.
Synopsis
~~~~~~~~
@ -622,115 +635,88 @@ If you found a bug or if you have a feature request, please
Example
~~~~~~~
Assume you have the following contracts you want to update declared in ``Source.sol``:
Assume that you have the following contract in ``Source.sol``:
.. code-block:: none
.. code-block:: solidity
// This will not compile after 0.5.0
pragma solidity >=0.6.0 <0.6.4;
// This will not compile after 0.7.0
// SPDX-License-Identifier: GPL-3.0
pragma solidity >0.4.23 <0.5.0;
contract Updateable {
function run() public view returns (bool);
function update() public;
contract C {
// FIXME: remove constructor visibility and make the contract abstract
constructor() internal {}
}
contract Upgradable {
function run() public view returns (bool);
function upgrade();
contract D {
uint time;
function f() public payable {
// FIXME: change now to block.timestamp
time = now;
}
}
contract Source is Updateable, Upgradable {
function Source() public {}
contract E {
D d;
function run()
public
view
returns (bool) {}
// FIXME: remove constructor visibility
constructor() public {}
function update() {}
function upgrade() {}
function g() public {
// FIXME: change .value(5) => {value: 5}
d.f.value(5)();
}
}
Required changes
^^^^^^^^^^^^^^^^
To bring the contracts up to date with the current Solidity version, the
following upgrade modules have to be executed: ``constructor``,
``visibility``, ``abstract``, ``override`` and ``virtual``. Please read the
documentation on :ref:`available modules <upgrade-modules>` for further details.
The above contract will not compile starting from 0.7.0. To bring the contract up to date with the
current Solidity version, the following upgrade modules have to be executed:
``constructor-visibility``, ``now`` and ``dotsyntax``. Please read the documentation on
:ref:`available modules <upgrade-modules>` for further details.
Running the upgrade
^^^^^^^^^^^^^^^^^^^
In this example, all modules needed to upgrade the contracts above,
are available and all of them are activated by default. Therefore you
do not need to specify the ``--modules`` option.
It is recommended to explicitly specify the upgrade modules by using ``--modules`` argument.
.. code-block:: none
$ solidity-upgrade Source.sol --dry-run
$ solidity-upgrade --modules constructor-visibility,now,dotsyntax Source.sol
.. code-block:: none
Running analysis (and upgrade) on given source files.
..............
After upgrade:
Found 0 errors.
Found 0 upgrades.
The above performs a dry-ran upgrade on the given file and logs statistics after all.
In this case, the upgrade was successful and no further adjustments are needed.
Finally, you can run the upgrade and also write to the source file.
.. code-block:: none
$ solidity-upgrade Source.sol
.. code-block:: none
Running analysis (and upgrade) on given source files.
..............
After upgrade:
Found 0 errors.
Found 0 upgrades.
Review changes
^^^^^^^^^^^^^^
The command above applies all changes as shown below. Please review them carefully.
The command above applies all changes as shown below. Please review them carefully (the pragmas will
have to be updated manually.)
.. code-block:: solidity
pragma solidity >0.6.99 <0.8.0;
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.7.0;
abstract contract Updateable {
function run() public view virtual returns (bool);
function update() public virtual;
abstract contract C {
// FIXME: remove constructor visibility and make the contract abstract
constructor() {}
}
abstract contract Upgradable {
function run() public view virtual returns (bool);
function upgrade() public virtual;
contract D {
uint time;
function f() public payable {
// FIXME: change now to block.timestamp
time = block.timestamp;
}
}
contract Source is Updateable, Upgradable {
constructor() public {}
contract E {
D d;
function run()
public
view
override(Updateable,Upgradable)
returns (bool) {}
// FIXME: remove constructor visibility
constructor() {}
function update() public override {}
function upgrade() public override {}
function g() public {
// FIXME: change .value(5) => {value: 5}
d.f{value: 5}();
}
}

View File

@ -30,6 +30,8 @@
#include <libevmasm/ConstantOptimiser.h>
#include <libevmasm/GasMeter.h>
#include <liblangutil/Exceptions.h>
#include <fstream>
#include <json/json.h>
@ -699,12 +701,10 @@ LinkerObject const& Assembly::assemble() const
}
}
assertThrow(
immutableReferencesBySub.empty(),
AssemblyException,
"Some immutables were read from but never assigned."
);
if (!immutableReferencesBySub.empty())
throw
langutil::Error(1284_error, langutil::Error::Type::CodeGenerationError) <<
util::errinfo_comment("Some immutables were read from but never assigned, possibly because of optimization.");
if (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty())
// Append an INVALID here to help tests find miscompilation.

View File

@ -33,6 +33,9 @@ Error::Error(ErrorId _errorId, Type _type, SourceLocation const& _location, stri
{
switch (m_type)
{
case Type::CodeGenerationError:
m_typeName = "CodeGenerationError";
break;
case Type::DeclarationError:
m_typeName = "DeclarationError";
break;

View File

@ -78,6 +78,7 @@ class Error: virtual public util::Exception
public:
enum class Type
{
CodeGenerationError,
DeclarationError,
DocstringParsingError,
ParserError,

View File

@ -73,6 +73,7 @@ string to_string(ScannerError _errorCode)
case ScannerError::IllegalHexDigit: return "Hexadecimal digit missing or invalid.";
case ScannerError::IllegalCommentTerminator: return "Expected multi-line comment-terminator.";
case ScannerError::IllegalEscapeSequence: return "Invalid escape sequence.";
case ScannerError::IllegalCharacterInString: return "Invalid character in string.";
case ScannerError::IllegalStringEndQuote: return "Expected string end-quote.";
case ScannerError::IllegalNumberSeparator: return "Invalid use of number separator '_'.";
case ScannerError::IllegalExponent: return "Invalid exponent.";
@ -508,7 +509,7 @@ void Scanner::scanToken()
{
case '"':
case '\'':
token = scanString();
token = scanString(false);
break;
case '<':
// < <= << <<=
@ -683,6 +684,18 @@ void Scanner::scanToken()
else
token = setError(ScannerError::IllegalToken);
}
else if (token == Token::Unicode)
{
// reset
m = 0;
n = 0;
// Special quoted hex string must follow
if (m_char == '"' || m_char == '\'')
token = scanString(true);
else
token = setError(ScannerError::IllegalToken);
}
}
else if (isDecimalDigit(m_char))
token = scanNumber();
@ -774,7 +787,7 @@ bool Scanner::isUnicodeLinebreak()
return false;
}
Token Scanner::scanString()
Token Scanner::scanString(bool const _isUnicode)
{
char const quote = m_char;
advance(); // consume quote
@ -789,13 +802,23 @@ Token Scanner::scanString()
return setError(ScannerError::IllegalEscapeSequence);
}
else
{
// Report error on non-printable characters in string literals, however
// allow anything for unicode string literals, because their validity will
// be verified later (in the syntax checker).
//
// We are using a manual range and not isprint() to avoid
// any potential complications with locale.
if (!_isUnicode && (static_cast<unsigned>(c) <= 0x1f || static_cast<unsigned>(c) >= 0x7f))
return setError(ScannerError::IllegalCharacterInString);
addLiteralChar(c);
}
}
if (m_char != quote)
return setError(ScannerError::IllegalStringEndQuote);
literal.complete();
advance(); // consume quote
return Token::StringLiteral;
return _isUnicode ? Token::UnicodeStringLiteral : Token::StringLiteral;
}
Token Scanner::scanHexString()

View File

@ -77,6 +77,7 @@ enum class ScannerError
IllegalHexDigit,
IllegalCommentTerminator,
IllegalEscapeSequence,
IllegalCharacterInString,
IllegalStringEndQuote,
IllegalNumberSeparator,
IllegalExponent,
@ -228,7 +229,7 @@ private:
Token scanNumber(char _charSeen = 0);
std::tuple<Token, unsigned, unsigned> scanIdentifierOrKeyword();
Token scanString();
Token scanString(bool const _isUnicode);
Token scanHexString();
/// Scans a single line comment and returns its corrected end position.
size_t scanSingleLineDocComment();

View File

@ -148,6 +148,7 @@ namespace solidity::langutil
K(As, "as", 0) \
K(Assembly, "assembly", 0) \
K(Break, "break", 0) \
K(Catch, "catch", 0) \
K(Constant, "constant", 0) \
K(Constructor, "constructor", 0) \
K(Continue, "continue", 0) \
@ -187,17 +188,17 @@ namespace solidity::langutil
K(CallData, "calldata", 0) \
K(Struct, "struct", 0) \
K(Throw, "throw", 0) \
K(Try, "try", 0) \
K(Type, "type", 0) \
K(Unicode, "unicode", 0) \
K(Using, "using", 0) \
K(Var, "var", 0) \
K(View, "view", 0) \
K(Virtual, "virtual", 0) \
K(While, "while", 0) \
\
/* Ether subdenominations */ \
K(SubWei, "wei", 0) \
K(SubSzabo, "szabo", 0) \
K(SubFinney, "finney", 0) \
K(SubGwei, "gwei", 0) \
K(SubEther, "ether", 0) \
K(SubSecond, "seconds", 0) \
K(SubMinute, "minutes", 0) \
@ -227,12 +228,12 @@ namespace solidity::langutil
K(FalseLiteral, "false", 0) \
T(Number, nullptr, 0) \
T(StringLiteral, nullptr, 0) \
T(UnicodeStringLiteral, nullptr, 0) \
T(HexStringLiteral, nullptr, 0) \
T(CommentLiteral, nullptr, 0) \
\
/* Identifiers (not keywords or future reserved words). */ \
T(Identifier, nullptr, 0) \
T(SubGwei, "gwei", 0) \
\
/* Keywords reserved for future use. */ \
K(After, "after", 0) \
@ -240,7 +241,6 @@ namespace solidity::langutil
K(Apply, "apply", 0) \
K(Auto, "auto", 0) \
K(Case, "case", 0) \
K(Catch, "catch", 0) \
K(CopyOf, "copyof", 0) \
K(Default, "default", 0) \
K(Define, "define", 0) \
@ -263,10 +263,10 @@ namespace solidity::langutil
K(Static, "static", 0) \
K(Supports, "supports", 0) \
K(Switch, "switch", 0) \
K(Try, "try", 0) \
K(Typedef, "typedef", 0) \
K(TypeOf, "typeof", 0) \
K(Unchecked, "unchecked", 0) \
K(Var, "var", 0) \
\
/* Illegal token - not able to scan. */ \
T(Illegal, "ILLEGAL", 0) \
@ -307,13 +307,12 @@ namespace TokenTraits
constexpr bool isVisibilitySpecifier(Token op) { return isVariableVisibilitySpecifier(op) || op == Token::External; }
constexpr bool isLocationSpecifier(Token op) { return op == Token::Memory || op == Token::Storage || op == Token::CallData; }
constexpr bool isStateMutabilitySpecifier(Token op, bool _allowConstant = true)
constexpr bool isStateMutabilitySpecifier(Token op)
{
return (op == Token::Constant && _allowConstant)
|| op == Token::Pure || op == Token::View || op == Token::Payable;
return op == Token::Pure || op == Token::View || op == Token::Payable;
}
constexpr bool isEtherSubdenomination(Token op) { return op == Token::SubWei || op == Token::SubSzabo || op == Token::SubFinney || op == Token::SubEther; }
constexpr bool isEtherSubdenomination(Token op) { return op >= Token::SubWei && op <= Token::SubEther; }
constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; }
constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Unchecked); }

View File

@ -83,7 +83,7 @@ void CHCSmtLib2Interface::addRule(Expression const& _expr, std::string const& _n
);
}
pair<CheckResult, vector<string>> CHCSmtLib2Interface::query(Expression const& _block)
pair<CheckResult, CHCSolverInterface::CexGraph> CHCSmtLib2Interface::query(Expression const& _block)
{
string accumulated{};
swap(m_accumulatedOutput, accumulated);
@ -108,7 +108,7 @@ pair<CheckResult, vector<string>> CHCSmtLib2Interface::query(Expression const& _
result = CheckResult::ERROR;
// TODO collect invariants or counterexamples.
return make_pair(result, vector<string>{});
return {result, {}};
}
void CHCSmtLib2Interface::declareVariable(string const& _name, SortPointer const& _sort)

View File

@ -43,7 +43,7 @@ public:
void addRule(Expression const& _expr, std::string const& _name) override;
std::pair<CheckResult, std::vector<std::string>> query(Expression const& _expr) override;
std::pair<CheckResult, CexGraph> query(Expression const& _expr) override;
void declareVariable(std::string const& _name, SortPointer const& _sort) override;

View File

@ -24,6 +24,9 @@
#include <libsmtutil/SolverInterface.h>
#include <map>
#include <vector>
namespace solidity::smtutil
{
@ -41,9 +44,18 @@ public:
/// Needs to bound all vars as universally quantified.
virtual void addRule(Expression const& _expr, std::string const& _name) = 0;
/// Takes a function application and checks
/// for reachability.
virtual std::pair<CheckResult, std::vector<std::string>> query(
/// first: predicate name
/// second: predicate arguments
using CexNode = std::pair<std::string, std::vector<std::string>>;
struct CexGraph
{
std::map<unsigned, CexNode> nodes;
std::map<unsigned, std::vector<unsigned>> edges;
};
/// Takes a function application _expr and checks for reachability.
/// @returns solving result and a counterexample graph, if possible.
virtual std::pair<CheckResult, CexGraph> query(
Expression const& _expr
) = 0;
};

View File

@ -20,6 +20,9 @@
#include <libsolutil/CommonIO.h>
#include <set>
#include <stack>
using namespace std;
using namespace solidity;
using namespace solidity::smtutil;
@ -33,17 +36,7 @@ Z3CHCInterface::Z3CHCInterface():
z3::set_param("rewriter.pull_cheap_ite", true);
z3::set_param("rlimit", Z3Interface::resourceLimit);
// Spacer options.
// These needs to be set in the solver.
// https://github.com/Z3Prover/z3/blob/master/src/muz/base/fp_params.pyg
z3::params p(*m_context);
// These are useful for solving problems with arrays and loops.
// Use quantified lemma generalizer.
p.set("fp.spacer.q3.use_qgen", true);
p.set("fp.spacer.mbqi", false);
// Ground pobs by using values from a model.
p.set("fp.spacer.ground_pobs", false);
m_solver.set(p);
setSpacerOptions();
}
void Z3CHCInterface::declareVariable(string const& _name, SortPointer const& _sort)
@ -72,10 +65,10 @@ void Z3CHCInterface::addRule(Expression const& _expr, string const& _name)
}
}
pair<CheckResult, vector<string>> Z3CHCInterface::query(Expression const& _expr)
pair<CheckResult, CHCSolverInterface::CexGraph> Z3CHCInterface::query(Expression const& _expr)
{
CheckResult result;
vector<string> values;
CHCSolverInterface::CexGraph cex;
try
{
z3::expr z3Expr = m_z3Interface->toZ3Expr(_expr);
@ -84,8 +77,9 @@ pair<CheckResult, vector<string>> Z3CHCInterface::query(Expression const& _expr)
case z3::check_result::sat:
{
result = CheckResult::SATISFIABLE;
// TODO retrieve model.
break;
auto proof = m_solver.get_answer();
auto cex = cexGraph(proof);
return {result, cex};
}
case z3::check_result::unsat:
{
@ -104,8 +98,119 @@ pair<CheckResult, vector<string>> Z3CHCInterface::query(Expression const& _expr)
catch (z3::exception const&)
{
result = CheckResult::ERROR;
values.clear();
cex = {};
}
return make_pair(result, values);
return {result, cex};
}
void Z3CHCInterface::setSpacerOptions(bool _preProcessing)
{
// Spacer options.
// These needs to be set in the solver.
// https://github.com/Z3Prover/z3/blob/master/src/muz/base/fp_params.pyg
z3::params p(*m_context);
// These are useful for solving problems with arrays and loops.
// Use quantified lemma generalizer.
p.set("fp.spacer.q3.use_qgen", true);
p.set("fp.spacer.mbqi", false);
// Ground pobs by using values from a model.
p.set("fp.spacer.ground_pobs", false);
// Spacer optimization should be
// - enabled for better solving (default)
// - disable for counterexample generation
p.set("fp.xform.slice", _preProcessing);
p.set("fp.xform.inline_linear", _preProcessing);
p.set("fp.xform.inline_eager", _preProcessing);
m_solver.set(p);
}
/**
Convert a ground refutation into a linear or nonlinear counterexample.
The counterexample is given as an implication graph of the form
`premises => conclusion` where `premises` are the predicates
from the body of nonlinear clauses, representing the proof graph.
This function is based on and similar to
https://github.com/Z3Prover/z3/blob/z3-4.8.8/src/muz/spacer/spacer_context.cpp#L2919
(spacer::context::get_ground_sat_answer)
which generates linear counterexamples.
It is modified here to accept nonlinear CHCs as well, generating a DAG
instead of a path.
*/
CHCSolverInterface::CexGraph Z3CHCInterface::cexGraph(z3::expr const& _proof)
{
CexGraph graph;
/// The root fact of the refutation proof is `false`.
/// The node itself is not a hyper resolution, so we need to
/// extract the `query` hyper resolution node from the
/// `false` node (the first child).
smtAssert(_proof.is_app(), "");
smtAssert(fact(_proof).decl().decl_kind() == Z3_OP_FALSE, "");
stack<z3::expr> proofStack;
proofStack.push(_proof.arg(0));
auto const& root = proofStack.top();
graph.nodes[root.id()] = {name(fact(root)), arguments(fact(root))};
set<unsigned> visited;
visited.insert(root.id());
while (!proofStack.empty())
{
z3::expr proofNode = proofStack.top();
smtAssert(graph.nodes.count(proofNode.id()), "");
proofStack.pop();
if (proofNode.is_app() && proofNode.decl().decl_kind() == Z3_OP_PR_HYPER_RESOLVE)
{
smtAssert(proofNode.num_args() > 0, "");
for (unsigned i = 1; i < proofNode.num_args() - 1; ++i)
{
z3::expr child = proofNode.arg(i);
if (!visited.count(child.id()))
{
visited.insert(child.id());
proofStack.push(child);
}
if (!graph.nodes.count(child.id()))
{
graph.nodes[child.id()] = {name(fact(child)), arguments(fact(child))};
graph.edges[child.id()] = {};
}
graph.edges[proofNode.id()].push_back(child.id());
}
}
}
return graph;
}
z3::expr Z3CHCInterface::fact(z3::expr const& _node)
{
smtAssert(_node.is_app(), "");
if (_node.num_args() == 0)
return _node;
return _node.arg(_node.num_args() - 1);
}
string Z3CHCInterface::name(z3::expr const& _predicate)
{
smtAssert(_predicate.is_app(), "");
return _predicate.decl().name().str();
}
vector<string> Z3CHCInterface::arguments(z3::expr const& _predicate)
{
smtAssert(_predicate.is_app(), "");
vector<string> args;
for (unsigned i = 0; i < _predicate.num_args(); ++i)
args.emplace_back(_predicate.arg(i).to_string());
return args;
}

View File

@ -25,6 +25,8 @@
#include <libsmtutil/CHCSolverInterface.h>
#include <libsmtutil/Z3Interface.h>
#include <vector>
namespace solidity::smtutil
{
@ -40,11 +42,22 @@ public:
void addRule(Expression const& _expr, std::string const& _name) override;
std::pair<CheckResult, std::vector<std::string>> query(Expression const& _expr) override;
std::pair<CheckResult, CexGraph> query(Expression const& _expr) override;
Z3Interface* z3Interface() const { return m_z3Interface.get(); }
void setSpacerOptions(bool _preProcessing = true);
private:
/// Constructs a nonlinear counterexample graph from the refutation.
CHCSolverInterface::CexGraph cexGraph(z3::expr const& _proof);
/// @returns the fact from a proof node.
z3::expr fact(z3::expr const& _node);
/// @returns @a _predicate's name.
std::string name(z3::expr const& _predicate);
/// @returns the arguments of @a _predicate.
std::vector<std::string> arguments(z3::expr const& _predicate);
// Used to handle variables.
std::unique_ptr<Z3Interface> m_z3Interface;

View File

@ -122,8 +122,9 @@ void ContractLevelChecker::checkDuplicateEvents(ContractDefinition const& _contr
/// Checks that two events with the same name defined in this contract have different
/// argument types
map<string, vector<EventDefinition const*>> events;
for (EventDefinition const* event: _contract.events())
events[event->name()].push_back(event);
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (EventDefinition const* event: contract->events())
events[event->name()].push_back(event);
findDuplicateDefinitions(events);
}

View File

@ -113,18 +113,16 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
{
Type const* memberType = member->annotation().type;
while (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
{
if (arrayType->isDynamicallySized())
break;
memberType = arrayType->baseType();
}
if (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
memberType = arrayType->finalBaseType(true);
if (auto structType = dynamic_cast<StructType const*>(memberType))
if (_cycleDetector.run(structType->structDefinition()))
return;
}
};
if (util::CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
if (util::CycleDetector<StructDefinition>(visitor).run(_struct))
m_errorReporter.fatalTypeError(2046_error, _struct.location(), "Recursive struct definition.");
return false;
@ -296,15 +294,6 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
"The \"immutable\" keyword can only be used for state variables."
);
if (!_variable.typeName())
{
// This can still happen in very unusual cases where a developer uses constructs, such as
// `var a;`, however, such code will have generated errors already.
// However, we cannot blindingly solAssert() for that here, as the TypeChecker (which is
// invoking ReferencesResolver) is generating it, so the error is most likely(!) generated
// after this step.
return;
}
using Location = VariableDeclaration::Location;
Location varLoc = _variable.referenceLocation();
DataLocation typeLoc = DataLocation::Memory;
@ -335,7 +324,9 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
", ",
" or "
);
if (_variable.isCallableOrCatchParameter())
if (_variable.isConstructorParameter())
errorString += " for constructor parameter";
else if (_variable.isCallableOrCatchParameter())
errorString +=
" for " +
string(_variable.isReturnParameter() ? "return " : "") +
@ -385,7 +376,7 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
}
TypePointer type = _variable.typeName()->annotation().type;
TypePointer type = _variable.typeName().annotation().type;
if (auto ref = dynamic_cast<ReferenceType const*>(type))
{
bool isPointer = !_variable.isStateVariable();

View File

@ -61,25 +61,12 @@ bool DocStringTagParser::visit(VariableDeclaration const& _variable)
{
if (_variable.isStateVariable())
{
static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "title", "author", "inheritdoc"};
static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "inheritdoc"};
static set<string> const validNonPublicTags = set<string>{"dev", "inheritdoc"};
if (_variable.isPublic())
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables");
else
{
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "non-public state variables");
if (_variable.annotation().docTags.count("notice") > 0)
m_errorReporter.warning(
7816_error, _variable.documentation()->location(),
"Documentation tag on non-public state variables will be disallowed in 0.7.0. "
"You will need to use the @dev tag explicitly."
);
}
if (_variable.annotation().docTags.count("title") > 0 || _variable.annotation().docTags.count("author") > 0)
m_errorReporter.warning(
8532_error, _variable.documentation()->location(),
"Documentation tag @title and @author is only allowed on contract definitions. "
"It will be disallowed in 0.7.0."
);
parseDocStrings(_variable, _variable.annotation(), validNonPublicTags, "non-public state variables");
}
return false;
}
@ -139,8 +126,8 @@ void DocStringTagParser::handleCallable(
StructurallyDocumentedAnnotation& _annotation
)
{
static set<string> const validEventTags = set<string>{"author", "dev", "notice", "return", "param"};
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param", "inheritdoc"};
static set<string> const validEventTags = set<string>{"dev", "notice", "return", "param"};
static set<string> const validTags = set<string>{"dev", "notice", "return", "param", "inheritdoc"};
if (dynamic_cast<EventDefinition const*>(&_callable))
parseDocStrings(_node, _annotation, validEventTags, "events");
@ -148,13 +135,6 @@ void DocStringTagParser::handleCallable(
parseDocStrings(_node, _annotation, validTags, "functions");
checkParameters(_callable, _node, _annotation);
if (_node.documentation() && _annotation.docTags.count("author") > 0)
m_errorReporter.warning(
9843_error, _node.documentation()->location(),
"Documentation tag @author is only allowed on contract definitions. "
"It will be disallowed in 0.7.0."
);
}
void DocStringTagParser::parseDocStrings(

View File

@ -290,7 +290,7 @@ StateMutability OverrideProxy::stateMutability() const
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return _item->stateMutability(); },
[&](ModifierDefinition const*) { solAssert(false, "Requested state mutability from modifier."); return StateMutability{}; },
[&](VariableDeclaration const*) { return StateMutability::View; }
[&](VariableDeclaration const* _var) { return _var->isConstant() ? StateMutability::Pure : StateMutability::View; }
}, m_item);
}
@ -585,29 +585,35 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
"Overridden " + _overriding.astNodeName() + " is here:"
);
// This is only relevant for a function overriding a function.
if (_overriding.isFunction())
{
if (_overriding.stateMutability() != _super.stateMutability())
overrideError(
_overriding,
_super,
6959_error,
"Overriding function changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +
stateMutabilityToString(_overriding.stateMutability()) +
"\"."
);
// Stricter mutability is always okay except when super is Payable
if (
(_overriding.isFunction() || _overriding.isVariable()) &&
(
_overriding.stateMutability() > _super.stateMutability() ||
_super.stateMutability() == StateMutability::Payable
) &&
_overriding.stateMutability() != _super.stateMutability()
)
overrideError(
_overriding,
_super,
6959_error,
"Overriding " +
_overriding.astNodeName() +
" changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +
stateMutabilityToString(_overriding.stateMutability()) +
"\"."
);
if (_overriding.unimplemented() && !_super.unimplemented())
overrideError(
_overriding,
_super,
4593_error,
"Overriding an implemented function with an unimplemented function is not allowed."
);
}
if (_overriding.unimplemented() && !_super.unimplemented())
overrideError(
_overriding,
_super,
4593_error,
"Overriding an implemented function with an unimplemented function is not allowed."
);
}
}

View File

@ -34,15 +34,16 @@
#include <liblangutil/Exceptions.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/CommonData.h>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/split.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::langutil;
using namespace solidity::frontend;
namespace solidity::frontend
{
bool ReferencesResolver::resolve(ASTNode const& _root)
{
@ -202,6 +203,10 @@ bool ReferencesResolver::visit(Return const& _return)
void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
{
validateYulIdentifierName(_function.name, _function.location);
for (yul::TypedName const& varName: _function.parameters + _function.returnVariables)
validateYulIdentifierName(varName.name, varName.location);
bool wasInsideFunction = m_yulInsideFunction;
m_yulInsideFunction = true;
this->operator()(_function.body);
@ -210,9 +215,10 @@ void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
void ReferencesResolver::operator()(yul::Identifier const& _identifier)
{
bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), "_slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), "_offset");
bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), ".slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), ".offset");
// Could also use `pathFromCurrentScope`, split by '.'
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
if (isSlot || isOffset)
{
@ -222,19 +228,22 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
return;
string realName = _identifier.name.str().substr(0, _identifier.name.str().size() - (
isSlot ?
string("_slot").size() :
string("_offset").size()
string(".slot").size() :
string(".offset").size()
));
if (realName.empty())
{
m_errorReporter.declarationError(
4794_error,
_identifier.location,
"In variable names _slot and _offset can only be used as a suffix."
"In variable names .slot and .offset can only be used as a suffix."
);
return;
}
declarations = m_resolver.nameFromCurrentScope(realName);
if (!declarations.empty())
// To support proper path resolution, we have to use pathFromCurrentScope.
solAssert(!util::contains(realName, '.'), "");
}
if (declarations.size() > 1)
{
@ -246,7 +255,18 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
return;
}
else if (declarations.size() == 0)
{
if (
boost::algorithm::ends_with(_identifier.name.str(), "_slot") ||
boost::algorithm::ends_with(_identifier.name.str(), "_offset")
)
m_errorReporter.declarationError(
9467_error,
_identifier.location,
"Identifier not found. Use ``.slot`` and ``.offset`` to access storage variables."
);
return;
}
if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front()))
if (var->isLocalVariable() && m_yulInsideFunction)
{
@ -267,18 +287,11 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
{
for (auto const& identifier: _varDecl.variables)
{
bool isSlot = boost::algorithm::ends_with(identifier.name.str(), "_slot");
bool isOffset = boost::algorithm::ends_with(identifier.name.str(), "_offset");
validateYulIdentifierName(identifier.name, identifier.location);
string namePrefix = identifier.name.str().substr(0, identifier.name.str().find('.'));
if (isSlot || isOffset)
m_errorReporter.declarationError(
9155_error,
identifier.location,
"In variable declarations _slot and _offset can not be used as a suffix."
);
else if (
auto declarations = m_resolver.nameFromCurrentScope(namePrefix);
if (
auto declarations = m_resolver.nameFromCurrentScope(identifier.name.str());
!declarations.empty()
)
{
@ -290,8 +303,6 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
3859_error,
identifier.location,
ssl,
namePrefix.size() < identifier.name.str().size() ?
"The prefix of this declaration conflicts with a declaration outside the inline assembly block." :
"This declaration shadows a declaration outside the inline assembly block."
);
}
@ -350,4 +361,12 @@ void ReferencesResolver::resolveInheritDoc(StructuredDocumentation const& _docum
}
}
void ReferencesResolver::validateYulIdentifierName(yul::YulString _name, SourceLocation const& _location)
{
if (util::contains(_name.str(), '.'))
m_errorReporter.declarationError(
3927_error,
_location,
"User-defined identifiers in inline assembly cannot contain '.'."
);
}

View File

@ -92,6 +92,9 @@ private:
void resolveInheritDoc(StructuredDocumentation const& _documentation, StructurallyDocumentedAnnotation& _annotation);
/// Checks if the name contains a '.'.
void validateYulIdentifierName(yul::YulString _name, langutil::SourceLocation const& _location);
langutil::ErrorReporter& m_errorReporter;
NameAndTypeResolver& m_resolver;
langutil::EVMVersion m_evmVersion;

View File

@ -28,6 +28,8 @@
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/SemVerHandler.h>
#include <libsolutil/UTF8.h>
#include <boost/algorithm/string.hpp>
#include <memory>
@ -37,7 +39,7 @@ using namespace std;
using namespace solidity;
using namespace solidity::langutil;
using namespace solidity::frontend;
using namespace solidity::util;
bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
{
@ -217,6 +219,13 @@ bool SyntaxChecker::visit(Throw const& _throwStatement)
bool SyntaxChecker::visit(Literal const& _literal)
{
if ((_literal.token() == Token::UnicodeStringLiteral) && !validateUTF8(_literal.value()))
m_errorReporter.syntaxError(
8452_error,
_literal.location(),
"Invalid UTF-8 sequence found"
);
if (_literal.token() != Token::Number)
return true;
@ -302,7 +311,7 @@ bool SyntaxChecker::visit(ContractDefinition const& _contract)
bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
if (_function.noVisibilitySpecified())
if (!_function.isConstructor() && _function.noVisibilitySpecified())
{
string suggestedVisibility = _function.isFallback() || _function.isReceive() || m_isInterface ? "external" : "public";
m_errorReporter.syntaxError(

View File

@ -327,10 +327,14 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
{
if (_function.markedVirtual())
{
if (_function.annotation().contract->isInterface())
if (_function.isConstructor())
m_errorReporter.typeError(7001_error, _function.location(), "Constructors cannot be virtual.");
else if (_function.annotation().contract->isInterface())
m_errorReporter.warning(5815_error, _function.location(), "Interface functions are implicitly \"virtual\"");
if (_function.visibility() == Visibility::Private)
else if (_function.visibility() == Visibility::Private)
m_errorReporter.typeError(3942_error, _function.location(), "\"virtual\" and \"private\" cannot be used together.");
else if (_function.libraryFunction())
m_errorReporter.typeError(7801_error, _function.location(), "Library functions cannot be \"virtual\".");
}
if (_function.isPayable())
@ -340,45 +344,62 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
if (_function.isOrdinary() && !_function.isPartOfExternalInterface())
m_errorReporter.typeError(5587_error, _function.location(), "\"internal\" and \"private\" functions cannot be payable.");
}
auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) {
if (type(var)->category() == Type::Category::Mapping)
{
if (var.referenceLocation() != VariableDeclaration::Location::Storage)
{
if (!_function.libraryFunction() && _function.isPublic())
m_errorReporter.typeError(3442_error, var.location(), "Mapping types can only have a data location of \"storage\" and thus only be parameters or return variables for internal or library functions.");
else
m_errorReporter.typeError(5380_error, var.location(), "Mapping types can only have a data location of \"storage\"." );
}
else
solAssert(_function.libraryFunction() || !_function.isPublic(), "Mapping types for parameters or return variables can only be used in internal or library functions.");
}
else
{
if (!type(var)->canLiveOutsideStorage() && _function.isPublic())
m_errorReporter.typeError(3312_error, var.location(), "Type is required to live outside storage.");
if (_function.isPublic())
{
auto iType = type(var)->interfaceType(_function.libraryFunction());
if (!iType)
{
solAssert(!iType.message().empty(), "Expected detailed error message!");
m_errorReporter.fatalTypeError(4103_error, var.location(), iType.message());
}
}
}
vector<VariableDeclaration const*> internalParametersInConstructor;
auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& _var) {
if (type(_var)->containsNestedMapping())
if (_var.referenceLocation() == VariableDeclaration::Location::Storage)
solAssert(
_function.libraryFunction() || _function.isConstructor() || !_function.isPublic(),
"Mapping types for parameters or return variables "
"can only be used in internal or library functions."
);
bool functionIsExternallyVisible =
(!_function.isConstructor() && _function.isPublic()) ||
(_function.isConstructor() && !m_currentContract->abstract());
if (
_function.isPublic() &&
!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) &&
!typeSupportedByOldABIEncoder(*type(var), _function.libraryFunction())
_function.isConstructor() &&
_var.referenceLocation() == VariableDeclaration::Location::Storage &&
!m_currentContract->abstract()
)
m_errorReporter.typeError(
4957_error,
var.location(),
"This type is only supported in ABIEncoderV2. "
"Use \"pragma experimental ABIEncoderV2;\" to enable the feature."
3644_error,
_var.location(),
"This parameter has a type that can only be used internally. "
"You can make the contract abstract to avoid this problem."
);
else if (functionIsExternallyVisible)
{
auto iType = type(_var)->interfaceType(_function.libraryFunction());
if (!iType)
{
string message = iType.message();
solAssert(!message.empty(), "Expected detailed error message!");
if (_function.isConstructor())
message += " You can make the contract abstract to avoid this problem.";
m_errorReporter.typeError(4103_error, _var.location(), message);
}
else if (
!experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) &&
!typeSupportedByOldABIEncoder(*type(_var), _function.libraryFunction())
)
{
string message =
"This type is only supported in ABIEncoderV2. "
"Use \"pragma experimental ABIEncoderV2;\" to enable the feature.";
if (_function.isConstructor())
message +=
" Alternatively, make the contract abstract and supply the "
"constructor arguments from a derived contract.";
m_errorReporter.typeError(
4957_error,
_var.location(),
message
);
}
}
};
for (ASTPointer<VariableDeclaration> const& var: _function.parameters())
{
@ -390,6 +411,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
checkArgumentAndReturnParameter(*var);
var->accept(*this);
}
set<Declaration const*> modifiers;
for (ASTPointer<ModifierInvocation> const& modifier: _function.modifiers())
{
@ -415,11 +437,10 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
if (_function.isImplemented())
m_errorReporter.typeError(4726_error, _function.location(), "Functions in interfaces cannot have an implementation.");
if (_function.visibility() != Visibility::External)
m_errorReporter.typeError(1560_error, _function.location(), "Functions in interfaces must be declared external.");
if (_function.isConstructor())
m_errorReporter.typeError(6482_error, _function.location(), "Constructor cannot be defined in interfaces.");
else if (_function.visibility() != Visibility::External)
m_errorReporter.typeError(1560_error, _function.location(), "Functions in interfaces must be declared external.");
}
else if (m_currentContract->contractKind() == ContractKind::Library)
if (_function.isConstructor())
@ -446,8 +467,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
bool TypeChecker::visit(VariableDeclaration const& _variable)
{
if (_variable.typeName())
_variable.typeName()->accept(*this);
_variable.typeName().accept(*this);
// type is filled either by ReferencesResolver directly from the type name or by
// TypeChecker at the VariableDeclarationStatement level.
@ -459,9 +479,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
m_errorReporter.typeError(1273_error, _variable.location(), "The type of a variable cannot be a library.");
if (_variable.value())
{
if (_variable.isStateVariable() && dynamic_cast<MappingType const*>(varType))
if (_variable.isStateVariable() && varType->containsNestedMapping())
{
m_errorReporter.typeError(6280_error, _variable.location(), "Mappings cannot be assigned to.");
m_errorReporter.typeError(
6280_error,
_variable.location(),
"Types in storage containing (nested) mappings cannot be assigned to."
);
_variable.value()->accept(*this);
}
else
@ -501,9 +525,16 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (!_variable.isStateVariable())
{
if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData))
if (!varType->canLiveOutsideStorage())
m_errorReporter.typeError(4061_error, _variable.location(), "Type " + varType->toString() + " is only valid in storage.");
if (
_variable.referenceLocation() == VariableDeclaration::Location::CallData ||
_variable.referenceLocation() == VariableDeclaration::Location::Memory
)
if (varType->containsNestedMapping())
m_errorReporter.fatalTypeError(
4061_error,
_variable.location(),
"Type " + varType->toString(true) + " is only valid in storage because it contains a (nested) mapping."
);
}
else if (_variable.visibility() >= Visibility::Public)
{
@ -534,7 +565,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (auto referenceType = dynamic_cast<ReferenceType const*>(varType))
{
auto result = referenceType->validForLocation(referenceType->location());
if (result && _variable.isPublicCallableParameter())
if (result && (_variable.isConstructorParameter() || _variable.isPublicCallableParameter()))
result = referenceType->validForLocation(DataLocation::CallData);
if (!result)
{
@ -560,15 +591,11 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (_variable.isStateVariable())
m_errorReporter.warning(3408_error, _variable.location(), collisionMessage(_variable.name(), true));
else
m_errorReporter.warning(
2332_error,
_variable.typeName() ? _variable.typeName()->location() : _variable.location(),
collisionMessage(varType->canonicalName(), false)
);
m_errorReporter.warning(2332_error, _variable.typeName().location(), collisionMessage(varType->canonicalName(), false));
}
vector<Type const*> oversizedSubtypes = frontend::oversizedSubtypes(*varType);
for (Type const* subtype: oversizedSubtypes)
m_errorReporter.warning(7325_error, _variable.typeName()->location(), collisionMessage(subtype->canonicalName(), false));
m_errorReporter.warning(7325_error, _variable.typeName().location(), collisionMessage(subtype->canonicalName(), false));
}
return false;
@ -646,8 +673,12 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
{
if (var->isIndexed())
numIndexed++;
if (!type(*var)->canLiveOutsideStorage())
m_errorReporter.typeError(3448_error, var->location(), "Type is required to live outside storage.");
if (type(*var)->containsNestedMapping())
m_errorReporter.typeError(
3448_error,
var->location(),
"Type containing a (nested) mapping is not allowed as event parameter type."
);
if (!type(*var)->interfaceType(false))
m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type.");
if (
@ -724,7 +755,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
}
else if (requiresStorage)
{
m_errorReporter.typeError(6617_error, _identifier.location, "The suffixes _offset and _slot can only be used on non-constant storage variables.");
m_errorReporter.typeError(6617_error, _identifier.location, "The suffixes .offset and .slot can only be used on non-constant storage variables.");
return false;
}
else if (var && var->value() && !var->value()->annotation().type && !dynamic_cast<Literal const*>(var->value().get()))
@ -752,7 +783,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
{
m_errorReporter.typeError(3622_error, _identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
m_errorReporter.typeError(3622_error, _identifier.location, "The suffixes .offset and .slot can only be used on storage variables.");
return false;
}
else if (_context == yul::IdentifierContext::LValue)
@ -764,7 +795,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
}
else if (identifierInfo.isOffset)
{
m_errorReporter.typeError(9739_error, _identifier.location, "Only _slot can be assigned to.");
m_errorReporter.typeError(9739_error, _identifier.location, "Only .slot can be assigned to.");
return false;
}
else
@ -773,12 +804,12 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
}
else if (!var->isConstant() && var->isStateVariable())
{
m_errorReporter.typeError(1408_error, _identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes.");
m_errorReporter.typeError(1408_error, _identifier.location, "Only local variables are supported. To access storage variables, use the .slot and .offset suffixes.");
return false;
}
else if (var->type()->dataStoredIn(DataLocation::Storage))
{
m_errorReporter.typeError(9068_error, _identifier.location, "You have to use the _slot or _offset suffix to access storage reference variables.");
m_errorReporter.typeError(9068_error, _identifier.location, "You have to use the .slot or .offset suffix to access storage reference variables.");
return false;
}
else if (var->type()->sizeOnStack() != 1)
@ -792,7 +823,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
}
else if (requiresStorage)
{
m_errorReporter.typeError(7944_error, _identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
m_errorReporter.typeError(7944_error, _identifier.location, "The suffixes .offset and .slot can only be used on storage variables.");
return false;
}
else if (_context == yul::IdentifierContext::LValue)
@ -1071,81 +1102,23 @@ void TypeChecker::endVisit(EmitStatement const& _emit)
m_errorReporter.typeError(9292_error, _emit.eventCall().expression().location(), "Expression has to be an event invocation.");
}
namespace
{
/**
* @returns a suggested left-hand-side of a multi-variable declaration contairing
* the variable declarations given in @a _decls.
*/
string createTupleDecl(vector<ASTPointer<VariableDeclaration>> const& _decls)
{
vector<string> components;
for (ASTPointer<VariableDeclaration> const& decl: _decls)
if (decl)
{
solAssert(decl->annotation().type, "");
components.emplace_back(decl->annotation().type->toString(false) + " " + decl->name());
}
else
components.emplace_back();
if (_decls.size() == 1)
return components.front();
else
return "(" + boost::algorithm::join(components, ", ") + ")";
}
bool typeCanBeExpressed(vector<ASTPointer<VariableDeclaration>> const& decls)
{
for (ASTPointer<VariableDeclaration> const& decl: decls)
{
// skip empty tuples (they can be expressed of course)
if (!decl)
continue;
if (!decl->annotation().type)
return false;
if (auto functionType = dynamic_cast<FunctionType const*>(decl->annotation().type))
if (
functionType->kind() != FunctionType::Kind::Internal &&
functionType->kind() != FunctionType::Kind::External
)
return false;
}
return true;
}
}
bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
{
if (!_statement.initialValue())
{
// No initial value is only permitted for single variables with specified type.
// This usually already results in a parser error.
if (_statement.declarations().size() != 1 || !_statement.declarations().front())
{
if (std::all_of(
_statement.declarations().begin(),
_statement.declarations().end(),
[](ASTPointer<VariableDeclaration> const& declaration) { return declaration == nullptr; }
))
{
// The syntax checker has already generated an error for this case (empty LHS tuple).
solAssert(m_errorReporter.hasErrors(), "");
solAssert(m_errorReporter.hasErrors(), "");
// It is okay to return here, as there are no named components on the
// left-hand-side that could cause any damage later.
return false;
}
else
// Bailing out *fatal* here, as those (untyped) vars may be used later, and diagnostics wouldn't be helpful then.
m_errorReporter.fatalTypeError(4626_error, _statement.location(), "Use of the \"var\" keyword is disallowed.");
// It is okay to return here, as there are no named components on the
// left-hand-side that could cause any damage later.
return false;
}
VariableDeclaration const& varDecl = *_statement.declarations().front();
if (!varDecl.annotation().type)
m_errorReporter.fatalTypeError(6983_error, _statement.location(), "Use of the \"var\" keyword is disallowed.");
solAssert(varDecl.annotation().type, "");
if (dynamic_cast<MappingType const*>(type(varDecl)))
m_errorReporter.typeError(
@ -1182,8 +1155,6 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
")."
);
bool autoTypeDeductionNeeded = false;
for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i)
{
if (!variables[i])
@ -1192,95 +1163,45 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
solAssert(!var.value(), "Value has to be tied to statement.");
TypePointer const& valueComponentType = valueTypes[i];
solAssert(!!valueComponentType, "");
if (!var.annotation().type)
{
autoTypeDeductionNeeded = true;
solAssert(var.annotation().type, "");
// Infer type from value.
solAssert(!var.typeName(), "");
var.annotation().type = valueComponentType->mobileType();
if (!var.annotation().type)
{
if (valueComponentType->category() == Type::Category::RationalNumber)
m_errorReporter.fatalTypeError(
6963_error,
_statement.initialValue()->location(),
"Invalid rational " +
valueComponentType->toString() +
" (absolute value too large or division by zero)."
);
else
solAssert(false, "");
}
else if (*var.annotation().type == *TypeProvider::emptyTuple())
solAssert(false, "Cannot declare variable with void (empty tuple) type.");
else if (valueComponentType->category() == Type::Category::RationalNumber)
{
string typeName = var.annotation().type->toString(true);
string extension;
if (auto type = dynamic_cast<IntegerType const*>(var.annotation().type))
{
unsigned numBits = type->numBits();
bool isSigned = type->isSigned();
solAssert(numBits > 0, "");
string minValue;
string maxValue;
if (isSigned)
{
numBits--;
minValue = "-" + bigint(bigint(1) << numBits).str();
}
else
minValue = "0";
maxValue = bigint((bigint(1) << numBits) - 1).str();
extension = ", which can hold values between " + minValue + " and " + maxValue;
}
else
solAssert(dynamic_cast<FixedPointType const*>(var.annotation().type), "Unknown type.");
}
var.accept(*this);
}
else
var.accept(*this);
BoolResult result = valueComponentType->isImplicitlyConvertibleTo(*var.annotation().type);
if (!result)
{
var.accept(*this);
BoolResult result = valueComponentType->isImplicitlyConvertibleTo(*var.annotation().type);
if (!result)
auto errorMsg = "Type " +
valueComponentType->toString() +
" is not implicitly convertible to expected type " +
var.annotation().type->toString();
if (
valueComponentType->category() == Type::Category::RationalNumber &&
dynamic_cast<RationalNumberType const&>(*valueComponentType).isFractional() &&
valueComponentType->mobileType()
)
{
auto errorMsg = "Type " +
valueComponentType->toString() +
" is not implicitly convertible to expected type " +
var.annotation().type->toString();
if (
valueComponentType->category() == Type::Category::RationalNumber &&
dynamic_cast<RationalNumberType const&>(*valueComponentType).isFractional() &&
valueComponentType->mobileType()
)
{
if (var.annotation().type->operator==(*valueComponentType->mobileType()))
m_errorReporter.typeError(
5107_error,
_statement.location(),
errorMsg + ", but it can be explicitly converted."
);
else
m_errorReporter.typeError(
4486_error,
_statement.location(),
errorMsg +
". Try converting to type " +
valueComponentType->mobileType()->toString() +
" or use an explicit conversion."
);
}
else
m_errorReporter.typeErrorConcatenateDescriptions(
9574_error,
if (var.annotation().type->operator==(*valueComponentType->mobileType()))
m_errorReporter.typeError(
5107_error,
_statement.location(),
errorMsg + ".",
result.message()
errorMsg + ", but it can be explicitly converted."
);
else
m_errorReporter.typeError(
4486_error,
_statement.location(),
errorMsg +
". Try converting to type " +
valueComponentType->mobileType()->toString() +
" or use an explicit conversion."
);
}
else
m_errorReporter.typeErrorConcatenateDescriptions(
9574_error,
_statement.location(),
errorMsg + ".",
result.message()
);
}
}
@ -1292,24 +1213,6 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
BOOST_THROW_EXCEPTION(FatalError());
}
if (autoTypeDeductionNeeded)
{
if (!typeCanBeExpressed(variables))
m_errorReporter.syntaxError(
3478_error,
_statement.location(),
"Use of the \"var\" keyword is disallowed. "
"Type cannot be expressed in syntax."
);
else
m_errorReporter.syntaxError(
1719_error,
_statement.location(),
"Use of the \"var\" keyword is disallowed. "
"Use explicit declaration `" + createTupleDecl(variables) + " = ...` instead."
);
}
return false;
}
@ -1390,9 +1293,9 @@ bool TypeChecker::visit(Conditional const& _conditional)
if (_conditional.annotation().willBeWrittenTo)
m_errorReporter.typeError(
2212_error,
_conditional.location(),
"Conditional expression as left value is not supported yet."
2212_error,
_conditional.location(),
"Conditional expression as left value is not supported yet."
);
return false;
@ -1420,7 +1323,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
checkExpressionAssignment(*types[i], *tupleExpression->components()[i]);
}
}
else if (_type.category() == Type::Category::Mapping)
else if (_type.nameable() && _type.containsNestedMapping())
{
bool isLocalOrReturn = false;
if (auto const* identifier = dynamic_cast<Identifier const*>(&_expression))
@ -1428,7 +1331,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
if (variableDeclaration->isLocalOrReturn())
isLocalOrReturn = true;
if (!isLocalOrReturn)
m_errorReporter.typeError(9214_error, _expression.location(), "Mappings cannot be assigned to.");
m_errorReporter.typeError(9214_error, _expression.location(), "Types in storage containing (nested) mappings cannot be assigned to.");
}
}
@ -1564,8 +1467,12 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
_tuple.location(),
"Unable to deduce nameable type for array elements. Try adding explicit type conversion for the first element."
);
else if (!inlineArrayType->canLiveOutsideStorage())
m_errorReporter.fatalTypeError(1545_error, _tuple.location(), "Type " + inlineArrayType->toString() + " is only valid in storage.");
else if (inlineArrayType->containsNestedMapping())
m_errorReporter.fatalTypeError(
1545_error,
_tuple.location(),
"Type " + inlineArrayType->toString(true) + " is only valid in storage."
);
_tuple.annotation().type = TypeProvider::array(DataLocation::Memory, inlineArrayType, types.size());
}
@ -1906,8 +1813,6 @@ void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)
void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
{
solAssert(_function.isConstructor(), "");
if (_function.markedVirtual())
m_errorReporter.typeError(7001_error, _function.location(), "Constructors cannot be virtual.");
if (_function.overrides())
m_errorReporter.typeError(1209_error, _function.location(), "Constructors cannot override.");
if (!_function.returnParameters().empty())
@ -1920,8 +1825,30 @@ void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != Visibility::Public && _function.visibility() != Visibility::Internal)
m_errorReporter.typeError(9239_error, _function.location(), "Constructor must be public or internal.");
if (!_function.noVisibilitySpecified())
{
auto const& contract = dynamic_cast<ContractDefinition const&>(*_function.scope());
if (_function.visibility() != Visibility::Public && _function.visibility() != Visibility::Internal)
m_errorReporter.typeError(9239_error, _function.location(), "Constructor cannot have visibility.");
else if (_function.isPublic() && contract.abstract())
m_errorReporter.declarationError(
8295_error,
_function.location(),
"Abstract contracts cannot have public constructors. Remove the \"public\" keyword to fix this."
);
else if (!_function.isPublic() && !contract.abstract())
m_errorReporter.declarationError(
1845_error,
_function.location(),
"Non-abstract contracts cannot have internal constructors. Remove the \"internal\" keyword and make the contract abstract to fix this."
);
else
m_errorReporter.warning(
2462_error,
_function.location(),
"Visibility for constructor is ignored. If you want the contract to be non-deployable, making it \"abstract\" is sufficient."
);
}
}
void TypeChecker::typeCheckABIEncodeFunctions(
@ -2066,24 +1993,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
toString(parameterTypes.size()) +
".";
// Extend error message in case we try to construct a struct with mapping member.
if (isStructConstructorCall)
{
/// For error message: Struct members that were removed during conversion to memory.
TypePointer const expressionType = type(_functionCall.expression());
auto const& t = dynamic_cast<TypeType const&>(*expressionType);
auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
set<string> membersRemovedForStructConstructor = structType.membersMissingInMemory();
if (!membersRemovedForStructConstructor.empty())
{
msg += " Members that have to be skipped in memory:";
for (auto const& member: membersRemovedForStructConstructor)
msg += " " + member;
}
return { isVariadic ? 1123_error : 9755_error, msg };
}
else if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
@ -2303,6 +2214,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if (actualType->category() == Type::Category::Struct)
{
if (actualType->containsNestedMapping())
m_errorReporter.fatalTypeError(
9515_error,
_functionCall.location(),
"Struct containing a (nested) mapping cannot be constructed."
);
functionType = dynamic_cast<StructType const&>(*actualType).constructorType();
funcCallAnno.kind = FunctionCallKind::StructConstructorCall;
funcCallAnno.isPure = argumentsArePure;
@ -2532,8 +2449,6 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
m_errorReporter.fatalTypeError(5540_error, _newExpression.location(), "Identifier is not a contract.");
if (contract->isInterface())
m_errorReporter.fatalTypeError(2971_error, _newExpression.location(), "Cannot instantiate an interface.");
if (!contract->constructorIsPublic())
m_errorReporter.typeError(9054_error, _newExpression.location(), "Contract with internal constructor cannot be created directly.");
if (contract->abstract())
m_errorReporter.typeError(4614_error, _newExpression.location(), "Cannot instantiate an abstract contract.");
@ -2554,11 +2469,11 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
}
else if (type->category() == Type::Category::Array)
{
if (!type->canLiveOutsideStorage())
if (type->containsNestedMapping())
m_errorReporter.fatalTypeError(
1164_error,
_newExpression.typeName().location(),
"Type cannot live outside storage."
"Array containing a (nested) mapping cannot be constructed in memory."
);
if (!type->isDynamicallySized())
m_errorReporter.typeError(
@ -2716,7 +2631,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
!annotation.referencedDeclaration &&
(memberName == "value" || memberName == "gas")
)
m_errorReporter.warning(
m_errorReporter.typeError(
1621_error,
_memberAccess.location(),
"Using \"." + memberName + "(...)\" is deprecated. Use \"{" + memberName + ": ...}\" instead."
@ -3114,6 +3029,20 @@ bool TypeChecker::visit(Identifier const& _identifier)
);
}
if (
MagicVariableDeclaration const* magicVar =
dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration)
)
if (magicVar->type()->category() == Type::Category::Integer)
{
solAssert(_identifier.name() == "now", "");
m_errorReporter.typeError(
7359_error,
_identifier.location(),
"\"now\" has been deprecated. Use \"block.timestamp\" instead."
);
}
return false;
}

View File

@ -169,7 +169,7 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
!_funDef.isConstructor() &&
!_funDef.isFallback() &&
!_funDef.isReceive() &&
!_funDef.overrides()
!_funDef.virtualSemantics()
)
m_errorReporter.warning(
2018_error,

View File

@ -123,15 +123,9 @@ FunctionDefinition const* ContractDefinition::constructor() const
return nullptr;
}
bool ContractDefinition::constructorIsPublic() const
{
FunctionDefinition const* f = constructor();
return !f || f->isPublic();
}
bool ContractDefinition::canBeDeployed() const
{
return constructorIsPublic() && !abstract() && !isInterface();
return !abstract() && !isInterface();
}
FunctionDefinition const* ContractDefinition::fallbackFunction() const
@ -295,6 +289,12 @@ bool FunctionDefinition::libraryFunction() const
return false;
}
Visibility FunctionDefinition::defaultVisibility() const
{
solAssert(!isConstructor(), "");
return Declaration::defaultVisibility();
}
FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
{
if (_internal)
@ -615,9 +615,8 @@ bool VariableDeclaration::isEventParameter() const
bool VariableDeclaration::hasReferenceOrMappingType() const
{
solAssert(typeName(), "");
solAssert(typeName()->annotation().type, "Can only be called after reference resolution");
Type const* type = typeName()->annotation().type;
solAssert(typeName().annotation().type, "Can only be called after reference resolution");
Type const* type = typeName().annotation().type;
return type->category() == Type::Category::Mapping || dynamic_cast<ReferenceType const*>(type);
}
@ -630,7 +629,12 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
else if (isCallableOrCatchParameter())
{
set<Location> locations{ Location::Memory };
if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter())
if (
isConstructorParameter() ||
isInternalCallableParameter() ||
isLibraryFunctionParameter() ||
isTryCatchParameter()
)
locations.insert(Location::Storage);
if (!isTryCatchParameter() && !isConstructorParameter())
locations.insert(Location::CallData);
@ -638,22 +642,8 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
return locations;
}
else if (isLocalVariable())
{
solAssert(typeName(), "");
auto dataLocations = [](TypePointer _type, auto&& _recursion) -> set<Location> {
solAssert(_type, "Can only be called after reference resolution");
switch (_type->category())
{
case Type::Category::Array:
return _recursion(dynamic_cast<ArrayType const*>(_type)->baseType(), _recursion);
case Type::Category::Mapping:
return set<Location>{ Location::Storage };
default:
return set<Location>{ Location::Memory, Location::Storage, Location::CallData };
}
};
return dataLocations(typeName()->annotation().type, dataLocations);
}
// Further restrictions will be imposed later on.
return set<Location>{ Location::Memory, Location::Storage, Location::CallData };
else
// Struct members etc.
return set<Location>{ Location::Unspecified };

View File

@ -506,8 +506,6 @@ public:
/// Returns the constructor or nullptr if no constructor was specified.
FunctionDefinition const* constructor() const;
/// @returns true iff the constructor of this contract is public (or non-existing).
bool constructorIsPublic() const;
/// @returns true iff the contract can be deployed, i.e. is not abstract and has a
/// public constructor.
/// Should only be called after the type checker has run.
@ -809,15 +807,16 @@ public:
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
Block const& body() const { solAssert(m_body, ""); return *m_body; }
Visibility defaultVisibility() const override;
bool isVisibleInContract() const override
{
return Declaration::isVisibleInContract() && isOrdinary();
return isOrdinary() && Declaration::isVisibleInContract();
}
bool isVisibleViaContractTypeAccess() const override
{
return visibility() >= Visibility::Public;
return isOrdinary() && visibility() >= Visibility::Public;
}
bool isPartOfExternalInterface() const override { return isPublic() && isOrdinary(); }
bool isPartOfExternalInterface() const override { return isOrdinary() && isPublic(); }
/// @returns the external signature of the function
/// That consists of the name of the function followed by the types of the
@ -897,13 +896,16 @@ public:
m_isIndexed(_isIndexed),
m_mutability(_mutability),
m_overrides(std::move(_overrides)),
m_location(_referenceLocation) {}
m_location(_referenceLocation)
{
solAssert(m_typeName, "");
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
TypeName* typeName() const { return m_typeName.get(); }
TypeName const& typeName() const { return *m_typeName; }
ASTPointer<Expression> const& value() const { return m_value; }
bool isLValue() const override;
@ -929,6 +931,7 @@ public:
/// @returns true if this variable is a parameter or return parameter of an internal function
/// or a function type of internal visibility.
bool isInternalCallableParameter() const;
/// @returns true if this variable is the parameter of a constructor.
bool isConstructorParameter() const;
/// @returns true iff this variable is a parameter(or return parameter of a library function
bool isLibraryFunctionParameter() const;
@ -965,7 +968,7 @@ protected:
Visibility defaultVisibility() const override { return Visibility::Internal; }
private:
ASTPointer<TypeName> m_typeName; ///< can be empty ("var")
ASTPointer<TypeName> m_typeName;
/// Initially assigned value, can be missing. For local variables, this is stored inside
/// VariableDeclarationStatement and not here.
ASTPointer<Expression> m_value;
@ -2087,8 +2090,6 @@ public:
None = static_cast<int>(Token::Illegal),
Wei = static_cast<int>(Token::SubWei),
Gwei = static_cast<int>(Token::SubGwei),
Szabo = static_cast<int>(Token::SubSzabo),
Finney = static_cast<int>(Token::SubFinney),
Ether = static_cast<int>(Token::SubEther),
Second = static_cast<int>(Token::SubSecond),
Minute = static_cast<int>(Token::SubMinute),

View File

@ -139,6 +139,9 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation
/// recursion immediately raises an error.
/// Will be filled in by the DeclarationTypeChecker.
std::optional<bool> recursive;
/// Whether the struct contains a mapping type, either directly or, indirectly inside another
/// struct or an array.
std::optional<bool> containsNestedMapping;
};
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation

View File

@ -203,7 +203,7 @@ Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifie
void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node)
{
_stream << util::jsonPrettyPrint(toJson(_node));
_stream << util::jsonPrettyPrint(util::removeNullMembers(toJson(_node)));
}
Json::Value&& ASTJsonConverter::toJson(ASTNode const& _node)
@ -351,12 +351,18 @@ bool ASTJsonConverter::visit(OverrideSpecifier const& _node)
bool ASTJsonConverter::visit(FunctionDefinition const& _node)
{
Visibility visibility;
if (_node.isConstructor())
visibility = _node.annotation().contract->abstract() ? Visibility::Internal : Visibility::Public;
else
visibility = _node.visibility();
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("kind", TokenTraits::toString(_node.kind())),
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("visibility", Declaration::visibilityToString(visibility)),
make_pair("virtual", _node.markedVirtual()),
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
make_pair("parameters", toJson(_node.parameterList())),
@ -366,6 +372,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
make_pair("implemented", _node.isImplemented()),
make_pair("scope", idOrNull(_node.scope()))
};
if (_node.isPartOfExternalInterface())
attributes.emplace_back("functionSelector", _node.externalIdentifierHex());
if (!_node.annotation().baseFunctions.empty())
@ -380,7 +387,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("typeName", toJsonOrNull(_node.typeName())),
make_pair("typeName", toJson(_node.typeName())),
make_pair("constant", _node.isConstant()),
make_pair("mutability", VariableDeclaration::mutabilityToString(_node.mutability())),
make_pair("stateVariable", _node.isStateVariable()),
@ -912,8 +919,11 @@ string ASTJsonConverter::literalTokenKind(Token _token)
case Token::Number:
return "number";
case Token::StringLiteral:
case Token::HexStringLiteral:
return "string";
case Token::UnicodeStringLiteral:
return "unicodeString";
case Token::HexStringLiteral:
return "hexString";
case Token::TrueLiteral:
case Token::FalseLiteral:
return "bool";

View File

@ -400,7 +400,7 @@ ASTPointer<FunctionDefinition> ASTJsonImporter::createFunctionDefinition(Json::V
return createASTNode<FunctionDefinition>(
_node,
memberAsASTString(_node, "name"),
visibility(_node),
kind == Token::Constructor ? Visibility::Default : visibility(_node),
stateMutability(_node),
kind,
memberAsBool(_node, "virtual"),
@ -886,7 +886,8 @@ ASTPointer<StructuredDocumentation> ASTJsonImporter::createDocumentation(Json::V
Json::Value ASTJsonImporter::member(Json::Value const& _node, string const& _name)
{
astAssert(_node.isMember(_name), "Node '" + _node["nodeType"].asString() + "' (id " + _node["id"].asString() + ") is missing field '" + _name + "'.");
if (!_node.isMember(_name))
return Json::nullValue;
return _node[_name];
}
@ -942,6 +943,10 @@ Token ASTJsonImporter::literalTokenKind(Json::Value const& _node)
tok = Token::Number;
else if (_node["kind"].asString() == "string")
tok = Token::StringLiteral;
else if (_node["kind"].asString() == "unicodeString")
tok = Token::UnicodeStringLiteral;
else if (_node["kind"].asString() == "hexString")
tok = Token::HexStringLiteral;
else if (_node["kind"].asString() == "bool")
tok = (member(_node, "value").asString() == "true") ? Token::TrueLiteral : Token::FalseLiteral;
else
@ -1004,10 +1009,6 @@ Literal::SubDenomination ASTJsonImporter::subdenomination(Json::Value const& _no
return Literal::SubDenomination::Wei;
else if (subDenStr == "gwei")
return Literal::SubDenomination::Gwei;
else if (subDenStr == "szabo")
return Literal::SubDenomination::Szabo;
else if (subDenStr == "finney")
return Literal::SubDenomination::Finney;
else if (subDenStr == "ether")
return Literal::SubDenomination::Ether;
else if (subDenStr == "seconds")

View File

@ -64,7 +64,8 @@ T AsmJsonImporter::createAsmNode(Json::Value const& _node)
Json::Value AsmJsonImporter::member(Json::Value const& _node, string const& _name)
{
astAssert(_node.isMember(_name), "Node is missing field '" + _name + "'.");
if (!_node.isMember(_name))
return Json::nullValue;
return _node[_name];
}

View File

@ -349,6 +349,7 @@ TypePointer TypeProvider::forLiteral(Literal const& _literal)
case Token::Number:
return rationalNumber(_literal);
case Token::StringLiteral:
case Token::UnicodeStringLiteral:
case Token::HexStringLiteral:
return stringLiteral(_literal.value());
default:

View File

@ -97,6 +97,7 @@ public:
static IntegerType const* uint(unsigned _bits) { return integer(_bits, IntegerType::Modifier::Unsigned); }
static IntegerType const* uint256() { return uint(256); }
static IntegerType const* int256() { return integer(256, IntegerType::Modifier::Signed); }
static FixedPointType const* fixedPoint(unsigned m, unsigned n, FixedPointType::Modifier _modifier);

View File

@ -417,11 +417,9 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ASTNode const& _sc
if (auto const* sourceUnit = dynamic_cast<SourceUnit const*>(&_scope))
usingForDirectives += ASTNode::filteredNodes<UsingForDirective>(sourceUnit->nodes());
else if (auto const* contract = dynamic_cast<ContractDefinition const*>(&_scope))
{
for (ContractDefinition const* contract: contract->annotation().linearizedBaseContracts)
usingForDirectives += contract->usingForDirectives();
usingForDirectives += ASTNode::filteredNodes<UsingForDirective>(contract->sourceUnit().nodes());
}
usingForDirectives +=
contract->usingForDirectives() +
ASTNode::filteredNodes<UsingForDirective>(contract->sourceUnit().nodes());
else
solAssert(false, "");
@ -570,7 +568,7 @@ bool isValidShiftAndAmountType(Token _operator, Type const& _shiftAmountType)
if (_operator == Token::SHR)
return false;
else if (IntegerType const* otherInt = dynamic_cast<decltype(otherInt)>(&_shiftAmountType))
return true;
return !otherInt->isSigned();
else if (RationalNumberType const* otherRat = dynamic_cast<decltype(otherRat)>(&_shiftAmountType))
return !otherRat->isFractional() && otherRat->integerType() && !otherRat->integerType()->isSigned();
else
@ -954,12 +952,6 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
case Literal::SubDenomination::Gwei:
value *= bigint("1000000000");
break;
case Literal::SubDenomination::Szabo:
value *= bigint("1000000000000");
break;
case Literal::SubDenomination::Finney:
value *= bigint("1000000000000000");
break;
case Literal::SubDenomination::Ether:
value *= bigint("1000000000000000000");
break;
@ -1057,10 +1049,38 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const*
{
if (_other->category() == Category::Integer || _other->category() == Category::FixedPoint)
{
auto commonType = Type::commonType(this, _other);
if (!commonType)
return nullptr;
return commonType->binaryOperatorResult(_operator, _other);
if (isFractional())
return TypeResult::err("Fractional literals not supported.");
else if (!integerType())
return TypeResult::err("Literal too large.");
// Shift and exp are not symmetric, so it does not make sense to swap
// the types as below. As an exception, we always use uint here.
if (TokenTraits::isShiftOp(_operator))
{
if (!isValidShiftAndAmountType(_operator, *_other))
return nullptr;
return isNegative() ? TypeProvider::int256() : TypeProvider::uint256();
}
else if (Token::Exp == _operator)
{
if (auto const* otherIntType = dynamic_cast<IntegerType const*>(_other))
{
if (otherIntType->isSigned())
return TypeResult::err("Exponentiation power is not allowed to be a signed integer type.");
}
else if (dynamic_cast<FixedPointType const*>(_other))
return TypeResult::err("Exponent is fractional.");
return isNegative() ? TypeProvider::int256() : TypeProvider::uint256();
}
else
{
auto commonType = Type::commonType(this, _other);
if (!commonType)
return nullptr;
return commonType->binaryOperatorResult(_operator, _other);
}
}
else if (_other->category() != category())
return nullptr;
@ -1388,7 +1408,7 @@ BoolResult StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo)
return
arrayType->isByteArray() &&
!(arrayType->dataStoredIn(DataLocation::Storage) && arrayType->isPointer()) &&
!(arrayType->isString() && !isValidUTF8());
!(arrayType->isString() && !util::validateUTF8(value()));
else
return false;
}
@ -1422,11 +1442,6 @@ TypePointer StringLiteralType::mobileType() const
return TypeProvider::stringMemory();
}
bool StringLiteralType::isValidUTF8() const
{
return util::validateUTF8(m_value);
}
FixedBytesType::FixedBytesType(unsigned _bytes): m_bytes(_bytes)
{
solAssert(
@ -2032,6 +2047,20 @@ TypeResult ArrayType::interfaceType(bool _inLibrary) const
return result;
}
Type const* ArrayType::finalBaseType(bool _breakIfDynamicArrayType) const
{
Type const* finalBaseType = this;
while (auto arrayType = dynamic_cast<ArrayType const*>(finalBaseType))
{
if (_breakIfDynamicArrayType && arrayType->isDynamicallySized())
break;
finalBaseType = arrayType->baseType();
}
return finalBaseType;
}
u256 ArrayType::memoryDataSize() const
{
solAssert(!isDynamicallySized(), "");
@ -2260,7 +2289,7 @@ unsigned StructType::calldataEncodedSize(bool) const
unsigned size = 0;
for (auto const& member: members(nullptr))
{
solAssert(member.type->canLiveOutsideStorage(), "");
solAssert(!member.type->containsNestedMapping(), "");
// Struct members are always padded.
size += member.type->calldataEncodedSize();
}
@ -2275,7 +2304,7 @@ unsigned StructType::calldataEncodedTailSize() const
unsigned size = 0;
for (auto const& member: members(nullptr))
{
solAssert(member.type->canLiveOutsideStorage(), "");
solAssert(!member.type->containsNestedMapping(), "");
// Struct members are always padded.
size += member.type->calldataHeadSize();
}
@ -2287,7 +2316,7 @@ unsigned StructType::calldataOffsetOfMember(std::string const& _member) const
unsigned offset = 0;
for (auto const& member: members(nullptr))
{
solAssert(member.type->canLiveOutsideStorage(), "");
solAssert(!member.type->containsNestedMapping(), "");
if (member.name == _member)
return offset;
// Struct members are always padded.
@ -2332,6 +2361,42 @@ u256 StructType::storageSize() const
return max<u256>(1, members(nullptr).storageSize());
}
bool StructType::containsNestedMapping() const
{
if (!m_struct.annotation().containsNestedMapping.has_value())
{
bool hasNestedMapping = false;
util::BreadthFirstSearch<StructDefinition const*> breadthFirstSearch{{&m_struct}};
breadthFirstSearch.run(
[&](StructDefinition const* _struct, auto&& _addChild)
{
for (auto const& member: _struct->members())
{
TypePointer memberType = member->annotation().type;
solAssert(memberType, "");
if (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
memberType = arrayType->finalBaseType(false);
if (dynamic_cast<MappingType const*>(memberType))
{
hasNestedMapping = true;
breadthFirstSearch.abort();
}
else if (auto structType = dynamic_cast<StructType const*>(memberType))
_addChild(&structType->structDefinition());
}
});
m_struct.annotation().containsNestedMapping = hasNestedMapping;
}
return m_struct.annotation().containsNestedMapping.value();
}
string StructType::toString(bool _short) const
{
string ret = "struct " + m_struct.annotation().canonicalName;
@ -2347,10 +2412,7 @@ MemberList::MemberMap StructType::nativeMembers(ASTNode const*) const
{
TypePointer type = variable->annotation().type;
solAssert(type, "");
// If we are not in storage, skip all members that cannot live outside of storage,
// ex. mappings and array of mappings
if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
continue;
solAssert(!(location() != DataLocation::Storage && type->containsNestedMapping()), "");
members.emplace_back(
variable->name(),
copyForLocationIfReference(type),
@ -2519,10 +2581,9 @@ FunctionTypePointer StructType::constructorType() const
{
TypePointers paramTypes;
strings paramNames;
solAssert(!containsNestedMapping(), "");
for (auto const& member: members(nullptr))
{
if (!member.type->canLiveOutsideStorage())
continue;
paramNames.push_back(member.name);
paramTypes.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, member.type));
}
@ -2556,20 +2617,12 @@ u256 StructType::memoryOffsetOfMember(string const& _name) const
TypePointers StructType::memoryMemberTypes() const
{
solAssert(!containsNestedMapping(), "");
TypePointers types;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
if (variable->annotation().type->canLiveOutsideStorage())
types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type));
return types;
}
types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type));
set<string> StructType::membersMissingInMemory() const
{
set<string> missing;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
if (!variable->annotation().type->canLiveOutsideStorage())
missing.insert(variable->name());
return missing;
return types;
}
vector<tuple<string, TypePointer>> StructType::makeStackItems() const
@ -3183,7 +3236,7 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
case Kind::DelegateCall:
slots = {
make_tuple("address", TypeProvider::address()),
make_tuple("functionIdentifier", TypeProvider::uint(32))
make_tuple("functionSelector", TypeProvider::uint(32))
};
break;
case Kind::BareCall:
@ -3367,6 +3420,28 @@ TypeResult FunctionType::interfaceType(bool /*_inLibrary*/) const
return TypeResult::err("Internal type is not allowed for public or external functions.");
}
TypePointer FunctionType::mobileType() const
{
if (m_valueSet || m_gasSet || m_saltSet || m_bound)
return nullptr;
// return function without parameter names
return TypeProvider::function(
m_parameterTypes,
m_returnParameterTypes,
strings(m_parameterTypes.size()),
strings(m_returnParameterNames.size()),
m_kind,
m_arbitraryParameters,
m_stateMutability,
m_declaration,
m_gasSet,
m_valueSet,
m_bound,
m_saltSet
);
}
bool FunctionType::canTakeArguments(
FuncCallArguments const& _arguments,
Type const* _selfType
@ -3716,7 +3791,10 @@ TypeResult MappingType::interfaceType(bool _inLibrary) const
}
}
else
return TypeResult::err("Only libraries are allowed to use the mapping type in public or external functions.");
return TypeResult::err(
"Types containing (nested) mappings can only be parameters or "
"return variables of internal or library functions."
);
return this;
}

View File

@ -271,7 +271,11 @@ public:
/// Returns true if the type can be stored in storage.
virtual bool canBeStored() const { return true; }
/// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping.
virtual bool canLiveOutsideStorage() const { return true; }
virtual bool containsNestedMapping() const
{
solAssert(nameable(), "Called for a non nameable type.");
return false;
}
/// Returns true if the type can be stored as a value (as opposed to a reference) on the stack,
/// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; }
@ -561,7 +565,6 @@ public:
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return false; }
std::string toString(bool _short) const override;
u256 literalValue(Literal const* _literal) const override;
@ -622,13 +625,10 @@ public:
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return false; }
std::string toString(bool) const override;
TypePointer mobileType() const override;
bool isValidUTF8() const;
std::string const& value() const { return m_value; }
protected:
@ -794,8 +794,9 @@ public:
bool isDynamicallyEncoded() const override;
bigint storageSizeUpperBound() const override;
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
bool containsNestedMapping() const override { return m_baseType->containsNestedMapping(); }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
std::string canonicalName() const override;
std::string signatureInExternalFunction(bool _structsByName) const override;
@ -811,6 +812,7 @@ public:
/// @returns true if this is a string
bool isString() const { return m_arrayKind == ArrayKind::String; }
Type const* baseType() const { solAssert(!!m_baseType, ""); return m_baseType; }
Type const* finalBaseType(bool breakIfDynamicArrayType) const;
u256 const& length() const { return m_length; }
u256 memoryDataSize() const override;
@ -855,7 +857,6 @@ public:
unsigned calldataEncodedTailSize() const override { return 32; }
bool isDynamicallySized() const override { return true; }
bool isDynamicallyEncoded() const override { return true; }
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
std::string toString(bool _short) const override;
TypePointer mobileType() const override;
@ -896,7 +897,6 @@ public:
}
unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
bool canLiveOutsideStorage() const override { return !isSuper(); }
bool isValueType() const override { return !isSuper(); }
bool nameable() const override { return !isSuper(); }
std::string toString(bool _short) const override;
@ -959,7 +959,7 @@ public:
u256 memoryDataSize() const override;
bigint storageSizeUpperBound() const override;
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return true; }
bool containsNestedMapping() const override;
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
@ -989,8 +989,6 @@ public:
/// @returns the vector of types of members available in memory.
TypePointers memoryMemberTypes() const;
/// @returns the set of all members that are removed in the memory version (typically mappings).
std::set<std::string> membersMissingInMemory() const;
void clearCache() const override;
@ -1021,7 +1019,6 @@ public:
}
unsigned storageBytes() const override;
bool leftAligned() const override { return false; }
bool canLiveOutsideStorage() const override { return true; }
std::string toString(bool _short) const override;
std::string canonicalName() const override;
bool isValueType() const override { return true; }
@ -1061,7 +1058,6 @@ public:
std::string toString(bool) const override;
bool canBeStored() const override { return false; }
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return false; }
bool hasSimpleZeroValueInMemory() const override { return false; }
TypePointer mobileType() const override;
/// Converts components to their temporary types and performs some wildcard matching.
@ -1232,11 +1228,11 @@ public:
unsigned storageBytes() const override;
bool isValueType() const override { return true; }
bool nameable() const override;
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
bool hasSimpleZeroValueInMemory() const override { return false; }
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
TypePointer encodingType() const override;
TypeResult interfaceType(bool _inLibrary) const override;
TypePointer mobileType() const override;
/// @returns TypePointer of a new FunctionType object. All input/return parameters are an
/// appropriate external types (i.e. the interfaceType()s) of input/return parameters of
@ -1365,7 +1361,7 @@ public:
bool operator==(Type const& _other) const override;
std::string toString(bool _short) const override;
std::string canonicalName() const override;
bool canLiveOutsideStorage() const override { return false; }
bool containsNestedMapping() const override { return true; }
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
Type const* encodingType() const override;
TypeResult interfaceType(bool _inLibrary) const override;
@ -1400,7 +1396,6 @@ public:
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return false; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override;
@ -1426,7 +1421,6 @@ public:
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
bool canBeStored() const override { return false; }
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return false; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
@ -1453,7 +1447,6 @@ public:
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return true; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
MemberList::MemberMap nativeMembers(ASTNode const*) const override;
@ -1493,7 +1486,6 @@ public:
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return true; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
MemberList::MemberMap nativeMembers(ASTNode const*) const override;
@ -1526,7 +1518,6 @@ public:
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
unsigned calldataEncodedSize(bool) const override { return 32; }
bool canBeStored() const override { return false; }
bool canLiveOutsideStorage() const override { return false; }
bool isValueType() const override { return true; }
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
std::string toString(bool) const override { return "inaccessible dynamic type"; }

View File

@ -861,8 +861,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
for (auto const& member: _to.members(nullptr))
{
solAssert(member.type, "");
if (!member.type->canLiveOutsideStorage())
continue;
solAssert(!member.type->containsNestedMapping(), "");
TypePointer memberTypeTo = member.type->fullEncodingType(_options.encodeAsLibraryTypes, true, false);
solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented.");
auto memberTypeFrom = _from.memberType(member.name);
@ -1341,7 +1340,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
for (auto const& member: _type.members(nullptr))
{
solAssert(member.type, "");
solAssert(member.type->canLiveOutsideStorage(), "");
solAssert(!member.type->containsNestedMapping(), "");
auto decodingType = member.type->decodingType();
solAssert(decodingType, "");
bool dynamic = decodingType->isDynamicallyEncoded();

View File

@ -1080,8 +1080,7 @@ void CompilerUtils::convertType(
// stack: <memory ptr> <source ref> <memory ptr>
for (auto const& member: typeOnStack->members(nullptr))
{
if (!member.type->canLiveOutsideStorage())
continue;
solAssert(!member.type->containsNestedMapping(), "");
pair<u256, unsigned> const& offsets = typeOnStack->storageOffsetsOfMember(member.name);
_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
_context << u256(offsets.second);

View File

@ -286,6 +286,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
m_currentLValue->storeValue(*rightIntermediateType, _assignment.location());
else // compound assignment
{
solAssert(binOp != Token::Exp, "Compound exp is not possible.");
solAssert(leftType.isValueType(), "Compound operators only available for value types.");
unsigned lvalueSize = m_currentLValue->sizeOnStack();
unsigned itemSize = _assignment.annotation().type->sizeOnStack();
@ -452,7 +453,10 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op);
TypePointer leftTargetType = commonType;
TypePointer rightTargetType = TokenTraits::isShiftOp(c_op) ? rightExpression.annotation().type->mobileType() : commonType;
TypePointer rightTargetType =
TokenTraits::isShiftOp(c_op) || c_op == Token::Exp ?
rightExpression.annotation().type->mobileType() :
commonType;
solAssert(rightTargetType, "");
// for commutative operators, push the literal as late as possible to allow improved optimization
@ -474,6 +478,8 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
if (TokenTraits::isShiftOp(c_op))
// shift only cares about the signedness of both sides
appendShiftOperatorCode(c_op, *leftTargetType, *rightTargetType);
else if (c_op == Token::Exp)
appendExpOperatorCode(*leftTargetType, *rightTargetType);
else if (TokenTraits::isCompareOp(c_op))
appendCompareOperatorCode(c_op, *commonType);
else
@ -1915,10 +1921,6 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
if (!dynamic_cast<ContractType const&>(*magicVar->type()).isSuper())
m_context << Instruction::ADDRESS;
break;
case Type::Category::Integer:
// "now"
m_context << Instruction::TIMESTAMP;
break;
default:
break;
}
@ -2084,9 +2086,6 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token _operator, Type cons
m_context << (c_isSigned ? Instruction::SMOD : Instruction::MOD);
break;
}
case Token::Exp:
m_context << Instruction::EXP;
break;
default:
solAssert(false, "Unknown arithmetic operator.");
}
@ -2121,7 +2120,6 @@ void ExpressionCompiler::appendShiftOperatorCode(Token _operator, Type const& _v
solAssert(dynamic_cast<FixedBytesType const*>(&_valueType), "Only integer and fixed bytes type supported for shifts.");
// The amount can be a RationalNumberType too.
bool c_amountSigned = false;
if (auto amountType = dynamic_cast<RationalNumberType const*>(&_shiftAmountType))
{
// This should be handled by the type checker.
@ -2129,17 +2127,10 @@ void ExpressionCompiler::appendShiftOperatorCode(Token _operator, Type const& _v
solAssert(!amountType->integerType()->isSigned(), "");
}
else if (auto amountType = dynamic_cast<IntegerType const*>(&_shiftAmountType))
c_amountSigned = amountType->isSigned();
solAssert(!amountType->isSigned(), "");
else
solAssert(false, "Invalid shift amount type.");
// shift by negative amount throws exception
if (c_amountSigned)
{
m_context << u256(0) << Instruction::DUP3 << Instruction::SLT;
m_context.appendConditionalInvalid();
}
m_context << Instruction::SWAP1;
// stack: value_to_shift shift_amount
@ -2189,6 +2180,14 @@ void ExpressionCompiler::appendShiftOperatorCode(Token _operator, Type const& _v
}
}
void ExpressionCompiler::appendExpOperatorCode(Type const& _valueType, Type const& _exponentType)
{
solAssert(_valueType.category() == Type::Category::Integer, "");
solAssert(!dynamic_cast<IntegerType const&>(_exponentType).isSigned(), "");
m_context << Instruction::EXP;
}
void ExpressionCompiler::appendExternalFunctionCall(
FunctionType const& _functionType,
vector<ASTPointer<Expression const>> const& _arguments,

View File

@ -101,6 +101,7 @@ private:
void appendArithmeticOperatorCode(Token _operator, Type const& _type);
void appendBitOperatorCode(Token _operator);
void appendShiftOperatorCode(Token _operator, Type const& _valueType, Type const& _shiftAmountType);
void appendExpOperatorCode(Type const& _valueType, Type const& _exponentType);
/// @}
/// Appends code to call a function of the given type with the given arguments.

View File

@ -356,13 +356,13 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
structType.structDefinition() == sourceType.structDefinition(),
"Struct assignment with conversion."
);
solAssert(!structType.containsNestedMapping(), "");
solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported.");
for (auto const& member: structType.members(nullptr))
{
// assign each member that can live outside of storage
TypePointer const& memberType = member.type;
if (!memberType->canLiveOutsideStorage())
continue;
solAssert(memberType->nameable(), "");
TypePointer sourceMemberType = sourceType.memberType(member.name);
if (sourceType.location() == DataLocation::Storage)
{

View File

@ -347,20 +347,17 @@ string YulUtilFunctions::typedShiftLeftFunction(Type const& _type, Type const& _
{
solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, "");
solAssert(_amountType.category() == Type::Category::Integer, "");
solAssert(!dynamic_cast<IntegerType const&>(_amountType).isSigned(), "");
string const functionName = "shift_left_" + _type.identifier() + "_" + _amountType.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, bits) -> result {
bits := <cleanAmount>(bits)
<?amountSigned>
if slt(bits, 0) { invalid() }
</amountSigned>
result := <cleanup>(<shift>(bits, value))
}
)")
("functionName", functionName)
("amountSigned", dynamic_cast<IntegerType const&>(_amountType).isSigned())
("cleanAmount", cleanupFunction(_amountType))
("shift", shiftLeftFunctionDynamic())
("cleanup", cleanupFunction(_type))
@ -372,6 +369,7 @@ string YulUtilFunctions::typedShiftRightFunction(Type const& _type, Type const&
{
solAssert(_type.category() == Type::Category::FixedBytes || _type.category() == Type::Category::Integer, "");
solAssert(_amountType.category() == Type::Category::Integer, "");
solAssert(!dynamic_cast<IntegerType const&>(_amountType).isSigned(), "");
IntegerType const* integerType = dynamic_cast<IntegerType const*>(&_type);
bool valueSigned = integerType && integerType->isSigned();
@ -381,14 +379,10 @@ string YulUtilFunctions::typedShiftRightFunction(Type const& _type, Type const&
Whiskers(R"(
function <functionName>(value, bits) -> result {
bits := <cleanAmount>(bits)
<?amountSigned>
if slt(bits, 0) { invalid() }
</amountSigned>
result := <cleanup>(<shift>(bits, <cleanup>(value)))
}
)")
("functionName", functionName)
("amountSigned", dynamic_cast<IntegerType const&>(_amountType).isSigned())
("cleanAmount", cleanupFunction(_amountType))
("shift", valueSigned ? shiftRightSignedFunctionDynamic() : shiftRightFunctionDynamic())
("cleanup", cleanupFunction(_type))
@ -2412,6 +2406,29 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
.render();
}
if (_from.category() == Type::Category::Array && _to.category() == Type::Category::Array)
{
auto const& fromArrayType = dynamic_cast<ArrayType const&>(_from);
auto const& toArrayType = dynamic_cast<ArrayType const&>(_to);
solAssert(!fromArrayType.baseType()->isDynamicallyEncoded(), "");
solUnimplementedAssert(fromArrayType.isByteArray() && toArrayType.isByteArray(), "");
solUnimplementedAssert(toArrayType.location() == DataLocation::Memory, "");
solUnimplementedAssert(fromArrayType.location() == DataLocation::CallData, "");
solUnimplementedAssert(toArrayType.isDynamicallySized(), "");
Whiskers templ(R"(
function <functionName>(offset, length) -> converted {
converted := <allocateMemoryArray>(length)
<copyToMemory>(offset, add(converted, 0x20), length)
}
)");
templ("functionName", functionName);
templ("allocateMemoryArray", allocateMemoryArrayFunction(toArrayType));
templ("copyToMemory", copyToMemoryFunction(fromArrayType.location() == DataLocation::CallData));
return templ.render();
}
solUnimplementedAssert(
_from.category() == Type::Category::StringLiteral,
"Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented."

View File

@ -1395,7 +1395,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
solAssert(false, "Contract member is neither variable nor function.");
define(IRVariable(_memberAccess).part("address"), _memberAccess.expression());
define(IRVariable(_memberAccess).part("functionIdentifier")) << formatNumber(identifier) << "\n";
define(IRVariable(_memberAccess).part("functionSelector")) << formatNumber(identifier) << "\n";
}
else
solAssert(false, "Invalid member access in contract");
@ -1431,7 +1431,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
*_memberAccess.expression().annotation().type
);
if (functionType.kind() == FunctionType::Kind::External)
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionSelector"));
else if (functionType.kind() == FunctionType::Kind::Declaration)
{
solAssert(functionType.hasDeclaration(), "");
@ -1683,7 +1683,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
break;
case FunctionType::Kind::DelegateCall:
define(IRVariable(_memberAccess).part("address"), _memberAccess.expression());
define(IRVariable(_memberAccess).part("functionIdentifier")) << formatNumber(memberFunctionType->externalIdentifier()) << "\n";
define(IRVariable(_memberAccess).part("functionSelector")) << formatNumber(memberFunctionType->externalIdentifier()) << "\n";
break;
case FunctionType::Kind::External:
case FunctionType::Kind::Creation:
@ -2077,7 +2077,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
// storage for arguments and returned data
let <pos> := <freeMemory>
mstore(<pos>, <shl28>(<funId>))
mstore(<pos>, <shl28>(<funSel>))
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
@ -2107,7 +2107,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("freeMemory", freeMemory());
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
templ("funSel", IRVariable(_functionCall.expression()).part("functionSelector").name());
templ("address", IRVariable(_functionCall.expression()).part("address").name());
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.

View File

@ -29,6 +29,11 @@
#include <libsmtutil/CHCSmtLib2Interface.h>
#include <libsolutil/Algorithms.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <queue>
using namespace std;
using namespace solidity;
using namespace solidity::util;
@ -122,6 +127,7 @@ bool CHC::visit(ContractDefinition const& _contract)
string suffix = _contract.name() + "_" + to_string(_contract.id());
m_errorPredicate = createSymbolicBlock(arity0FunctionSort(), "error_" + suffix);
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix);
m_symbolFunction[m_constructorSummaryPredicate->currentFunctionValue().name] = &_contract;
m_implicitConstructorPredicate = createSymbolicBlock(arity0FunctionSort(), "implicit_constructor_" + suffix);
auto stateExprs = currentStateVariables();
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
@ -142,12 +148,11 @@ void CHC::endVisit(ContractDefinition const& _contract)
else
inlineConstructorHierarchy(_contract);
auto summary = predicate(*m_constructorSummaryPredicate, vector<smtutil::Expression>{m_error.currentValue()} + currentStateVariables());
connectBlocks(m_currentBlock, summary);
connectBlocks(m_currentBlock, summary(_contract), m_error.currentValue() == 0);
clearIndices(m_currentContract, nullptr);
auto stateExprs = vector<smtutil::Expression>{m_error.currentValue()} + currentStateVariables();
setCurrentBlock(*m_constructorSummaryPredicate, &stateExprs);
vector<smtutil::Expression> symbArgs = currentFunctionVariables(*m_currentContract);
setCurrentBlock(*m_constructorSummaryPredicate, &symbArgs);
addAssertVerificationTarget(m_currentContract, m_currentBlock, smtutil::Expression(true), m_error.currentValue());
connectBlocks(m_currentBlock, interface(), m_error.currentValue() == 0);
@ -209,6 +214,8 @@ void CHC::endVisit(FunctionDefinition const& _function)
if (!_function.isImplemented())
return;
solAssert(m_currentFunction && m_currentContract, "");
// This is the case for base constructor inlining.
if (m_currentFunction != &_function)
{
@ -228,10 +235,10 @@ void CHC::endVisit(FunctionDefinition const& _function)
{
string suffix = m_currentContract->name() + "_" + to_string(m_currentContract->id());
auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix);
connectBlocks(m_currentBlock, predicate(*constructorExit, vector<smtutil::Expression>{m_error.currentValue()} + currentStateVariables()));
connectBlocks(m_currentBlock, predicate(*constructorExit, currentFunctionVariables(*m_currentContract)));
clearIndices(m_currentContract, m_currentFunction);
auto stateExprs = vector<smtutil::Expression>{m_error.currentValue()} + currentStateVariables();
auto stateExprs = currentFunctionVariables(*m_currentContract);
setCurrentBlock(*constructorExit, &stateExprs);
}
else
@ -564,6 +571,7 @@ void CHC::externalFunctionCall(FunctionCall const& _funCall)
m_context.variable(*var)->increaseIndex();
auto nondet = (*m_nondetInterfaces.at(m_currentContract))(preCallState + currentStateVariables());
m_symbolFunction[nondet.name] = &_funCall;
m_context.addAssertion(nondet);
m_context.addAssertion(m_error.currentValue() == 0);
@ -613,6 +621,7 @@ void CHC::resetSourceAnalysis()
m_errorIds.clear();
m_callGraph.clear();
m_summaries.clear();
m_symbolFunction.clear();
}
void CHC::resetContractAnalysis()
@ -683,6 +692,11 @@ vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPriva
);
}
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function)
{
return stateVariablesIncludingInheritedAndPrivate(dynamic_cast<ContractDefinition const&>(*_function.scope()));
}
vector<smtutil::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
{
return applyMap(
@ -693,6 +707,10 @@ vector<smtutil::SortPointer> CHC::stateSorts(ContractDefinition const& _contract
smtutil::SortPointer CHC::constructorSort()
{
solAssert(m_currentContract, "");
if (auto const* constructor = m_currentContract->constructor())
return sort(*constructor);
return make_shared<smtutil::FunctionSort>(
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} + m_stateSorts,
smtutil::SortProvider::boolSort
@ -835,7 +853,12 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract));
if (!base->isLibrary() && !base->isInterface() && !function->isConstructor())
if (
!function->isConstructor() &&
function->isPublic() &&
!base->isLibrary() &&
!base->isInterface()
)
{
auto state1 = stateVariablesAtIndex(1, *base);
auto state2 = stateVariablesAtIndex(2, *base);
@ -880,8 +903,13 @@ smtutil::Expression CHC::error(unsigned _idx)
return m_errorPredicate->functionValueAtIndex(_idx)({});
}
smtutil::Expression CHC::summary(ContractDefinition const&)
smtutil::Expression CHC::summary(ContractDefinition const& _contract)
{
if (auto const* constructor = _contract.constructor())
return (*m_constructorSummaryPredicate)(
currentFunctionVariables(*constructor)
);
return (*m_constructorSummaryPredicate)(
vector<smtutil::Expression>{m_error.currentValue()} +
currentStateVariables()
@ -908,21 +936,28 @@ smtutil::Expression CHC::summary(FunctionDefinition const& _function)
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
{
return createSymbolicBlock(sort(_node),
auto block = createSymbolicBlock(sort(_node),
"block_" +
uniquePrefix() +
"_" +
_prefix +
predicateName(_node));
solAssert(m_currentFunction, "");
m_symbolFunction[block->currentFunctionValue().name] = m_currentFunction;
return block;
}
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract)
{
return createSymbolicBlock(summarySort(_function, _contract),
auto block = createSymbolicBlock(summarySort(_function, _contract),
"summary_" +
uniquePrefix() +
"_" +
predicateName(&_function, &_contract));
m_symbolFunction[block->currentFunctionValue().name] = &_function;
return block;
}
void CHC::createErrorBlock()
@ -977,15 +1012,21 @@ vector<smtutil::Expression> CHC::currentStateVariables(ContractDefinition const&
}
vector<smtutil::Expression> CHC::currentFunctionVariables()
{
solAssert(m_currentFunction, "");
return currentFunctionVariables(*m_currentFunction);
}
vector<smtutil::Expression> CHC::currentFunctionVariables(FunctionDefinition const& _function)
{
vector<smtutil::Expression> initInputExprs;
vector<smtutil::Expression> mutableInputExprs;
for (auto const& var: m_currentFunction->parameters())
for (auto const& var: _function.parameters())
{
initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0));
mutableInputExprs.push_back(m_context.variable(*var)->currentValue());
}
auto returnExprs = applyMap(m_currentFunction->returnParameters(), [this](auto _var) { return currentValue(*_var); });
auto returnExprs = applyMap(_function.returnParameters(), [this](auto _var) { return currentValue(*_var); });
return vector<smtutil::Expression>{m_error.currentValue()} +
initialStateVariables() +
initInputExprs +
@ -994,6 +1035,14 @@ vector<smtutil::Expression> CHC::currentFunctionVariables()
returnExprs;
}
vector<smtutil::Expression> CHC::currentFunctionVariables(ContractDefinition const& _contract)
{
if (auto const* constructor = _contract.constructor())
return currentFunctionVariables(*constructor);
return vector<smtutil::Expression>{m_error.currentValue()} + currentStateVariables();
}
vector<smtutil::Expression> CHC::currentBlockVariables()
{
if (m_currentFunction)
@ -1074,15 +1123,33 @@ void CHC::addRule(smtutil::Expression const& _rule, string const& _ruleName)
m_interface->addRule(_rule, _ruleName);
}
pair<smtutil::CheckResult, vector<string>> CHC::query(smtutil::Expression const& _query, langutil::SourceLocation const& _location)
pair<smtutil::CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Expression const& _query, langutil::SourceLocation const& _location)
{
smtutil::CheckResult result;
vector<string> values;
tie(result, values) = m_interface->query(_query);
CHCSolverInterface::CexGraph cex;
tie(result, cex) = m_interface->query(_query);
switch (result)
{
case smtutil::CheckResult::SATISFIABLE:
{
#ifdef HAVE_Z3
// Even though the problem is SAT, Spacer's pre processing makes counterexamples incomplete.
// We now disable those optimizations and check whether we can still solve the problem.
auto* spacer = dynamic_cast<Z3CHCInterface*>(m_interface.get());
solAssert(spacer, "");
spacer->setSpacerOptions(false);
smtutil::CheckResult resultNoOpt;
CHCSolverInterface::CexGraph cexNoOpt;
tie(resultNoOpt, cexNoOpt) = m_interface->query(_query);
if (resultNoOpt == smtutil::CheckResult::SATISFIABLE)
cex = move(cexNoOpt);
spacer->setSpacerOptions(true);
#endif
break;
}
case smtutil::CheckResult::UNSATISFIABLE:
break;
case smtutil::CheckResult::UNKNOWN:
@ -1094,7 +1161,7 @@ pair<smtutil::CheckResult, vector<string>> CHC::query(smtutil::Expression const&
m_outerErrorReporter.warning(1218_error, _location, "Error trying to invoke SMT solver.");
break;
}
return {result, values};
return {result, cex};
}
void CHC::addVerificationTarget(
@ -1138,19 +1205,21 @@ void CHC::checkVerificationTargets()
{
string satMsg;
string unknownMsg;
ErrorId errorReporterId;
if (target.type == VerificationTarget::Type::PopEmptyArray)
{
solAssert(dynamic_cast<FunctionCall const*>(scope), "");
satMsg = "Empty array \"pop\" detected here.";
satMsg = "Empty array \"pop\" detected here";
unknownMsg = "Empty array \"pop\" might happen here.";
errorReporterId = 2529_error;
}
else
solAssert(false, "");
auto it = m_errorIds.find(scope->id());
solAssert(it != m_errorIds.end(), "");
checkAndReportTarget(scope, target, it->second, satMsg, unknownMsg);
checkAndReportTarget(scope, target, it->second, errorReporterId, satMsg, unknownMsg);
}
}
}
@ -1165,13 +1234,7 @@ void CHC::checkAssertTarget(ASTNode const* _scope, CHCVerificationTarget const&
solAssert(it != m_errorIds.end(), "");
unsigned errorId = it->second;
createErrorBlock();
connectBlocks(_target.value, error(), _target.constraints && (_target.errorId == errorId));
auto [result, model] = query(error(), assertion->location());
// This should be fine but it's a bug in the old compiler
(void)model;
if (result == smtutil::CheckResult::UNSATISFIABLE)
m_safeTargets[assertion].insert(_target.type);
checkAndReportTarget(assertion, _target, errorId, 6328_error, "Assertion violation happens here");
}
}
@ -1179,35 +1242,251 @@ void CHC::checkAndReportTarget(
ASTNode const* _scope,
CHCVerificationTarget const& _target,
unsigned _errorId,
ErrorId _errorReporterId,
string _satMsg,
string _unknownMsg
)
{
if (m_unsafeTargets.count(_scope) && m_unsafeTargets.at(_scope).count(_target.type))
return;
createErrorBlock();
connectBlocks(_target.value, error(), _target.constraints && (_target.errorId == _errorId));
auto [result, model] = query(error(), _scope->location());
// This should be fine but it's a bug in the old compiler
(void)model;
auto const& [result, model] = query(error(), _scope->location());
if (result == smtutil::CheckResult::UNSATISFIABLE)
m_safeTargets[_scope].insert(_target.type);
else if (result == smtutil::CheckResult::SATISFIABLE)
{
solAssert(!_satMsg.empty(), "");
m_unsafeTargets[_scope].insert(_target.type);
m_outerErrorReporter.warning(
2529_error,
_scope->location(),
_satMsg
);
auto cex = generateCounterexample(model, error().name);
if (cex)
m_outerErrorReporter.warning(
_errorReporterId,
_scope->location(),
_satMsg,
SecondarySourceLocation().append(" for:\n" + *cex, SourceLocation{})
);
else
m_outerErrorReporter.warning(
_errorReporterId,
_scope->location(),
_satMsg + "."
);
}
else if (!_unknownMsg.empty())
m_outerErrorReporter.warning(
1147_error,
_errorReporterId,
_scope->location(),
_unknownMsg
);
}
/**
The counterexample DAG has the following properties:
1) The root node represents the reachable error predicate.
2) The root node has 1 or 2 children:
- One of them is the summary of the function that was called and led to that node.
If this is the only child, this function must be the constructor.
- If it has 2 children, the function is not the constructor and the other child is the interface node,
that is, it represents the state of the contract before the function described above was called.
3) Interface nodes also have property 2.
The following algorithm starts collecting function summaries at the root node and repeats
for each interface node seen.
Each function summary collected represents a transaction, and the final order is reversed.
The first function summary seen contains the values for the state, input and output variables at the
error point.
*/
optional<string> CHC::generateCounterexample(CHCSolverInterface::CexGraph const& _graph, string const& _root)
{
optional<unsigned> rootId;
for (auto const& [id, node]: _graph.nodes)
if (node.first == _root)
{
rootId = id;
break;
}
if (!rootId)
return {};
vector<string> path;
string localState;
unsigned node = *rootId;
/// The first summary node seen in this loop represents the last transaction.
bool lastTxSeen = false;
while (_graph.edges.at(node).size() >= 1)
{
auto const& edges = _graph.edges.at(node);
solAssert(edges.size() <= 2, "");
unsigned summaryId = edges.at(0);
optional<unsigned> interfaceId;
if (edges.size() == 2)
{
interfaceId = edges.at(1);
if (_graph.nodes.at(summaryId).first.rfind("summary", 0) != 0)
swap(summaryId, *interfaceId);
solAssert(_graph.nodes.at(*interfaceId).first.rfind("interface", 0) == 0, "");
}
/// The children are unordered, so we need to check which is the summary and
/// which is the interface.
solAssert(_graph.nodes.at(summaryId).first.rfind("summary", 0) == 0, "");
/// At this point property 2 from the function description is verified for this node.
auto const& summaryNode = _graph.nodes.at(summaryId);
solAssert(m_symbolFunction.count(summaryNode.first), "");
FunctionDefinition const* calledFun = nullptr;
ContractDefinition const* calledContract = nullptr;
if (auto const* contract = dynamic_cast<ContractDefinition const*>(m_symbolFunction.at(summaryNode.first)))
{
if (auto const* constructor = contract->constructor())
calledFun = constructor;
else
calledContract = contract;
}
else if (auto const* fun = dynamic_cast<FunctionDefinition const*>(m_symbolFunction.at(summaryNode.first)))
calledFun = fun;
else
solAssert(false, "");
solAssert((calledFun && !calledContract) || (!calledFun && calledContract), "");
auto const& stateVars = calledFun ? stateVariablesIncludingInheritedAndPrivate(*calledFun) : stateVariablesIncludingInheritedAndPrivate(*calledContract);
/// calledContract != nullptr implies that the constructor of the analyzed contract is implicit and
/// therefore takes no parameters.
/// This summary node is the end of a tx.
/// If it is the first summary node seen in this loop, it is the summary
/// of the public/external function that was called when the error was reached,
/// but not necessarily the summary of the function that contains the error.
if (!lastTxSeen)
{
lastTxSeen = true;
/// Generate counterexample message local to the failed target.
localState = formatStateCounterexample(stateVars, calledFun, summaryNode.second) + "\n";
if (calledFun)
{
/// The signature of a summary predicate is: summary(error, preStateVars, preInputVars, postInputVars, outputVars).
auto const& inParams = calledFun->parameters();
unsigned initLocals = stateVars.size() * 2 + 1 + inParams.size();
/// In this loop we are interested in postInputVars.
for (unsigned i = initLocals; i < initLocals + inParams.size(); ++i)
{
auto param = inParams.at(i - initLocals);
if (param->type()->isValueType())
localState += param->name() + " = " + summaryNode.second.at(i) + "\n";
}
auto const& outParams = calledFun->returnParameters();
initLocals += inParams.size();
/// In this loop we are interested in outputVars.
for (unsigned i = initLocals; i < initLocals + outParams.size(); ++i)
{
auto param = outParams.at(i - initLocals);
if (param->type()->isValueType())
localState += param->name() + " = " + summaryNode.second.at(i) + "\n";
}
}
}
else
/// We report the state after every tx in the trace except for the last, which is reported
/// first in the code above.
path.emplace_back("State: " + formatStateCounterexample(stateVars, calledFun, summaryNode.second));
string txCex = calledContract ? "constructor()" : formatFunctionCallCounterexample(stateVars, *calledFun, summaryNode.second);
path.emplace_back(txCex);
/// Recurse on the next interface node which represents the previous transaction
/// or stop.
if (interfaceId)
node = *interfaceId;
else
break;
}
return localState + "\nTransaction trace:\n" + boost::algorithm::join(boost::adaptors::reverse(path), "\n");
}
string CHC::formatStateCounterexample(vector<VariableDeclaration const*> const& _stateVars, FunctionDefinition const* _function, vector<string> const& _summaryValues)
{
/// The signature of a function summary predicate is: summary(error, preStateVars, preInputVars, postInputVars, outputVars).
/// The signature of an implicit constructor summary predicate is: summary(error, postStateVars).
/// Here we are interested in postStateVars.
vector<string>::const_iterator stateFirst;
vector<string>::const_iterator stateLast;
if (_function)
{
stateFirst = _summaryValues.begin() + 1 + static_cast<int>(_stateVars.size()) + static_cast<int>(_function->parameters().size());
stateLast = stateFirst + static_cast<int>(_stateVars.size());
}
else
{
stateFirst = _summaryValues.begin() + 1;
stateLast = stateFirst + static_cast<int>(_stateVars.size());
}
solAssert(stateFirst >= _summaryValues.begin() && stateFirst <= _summaryValues.end(), "");
solAssert(stateLast >= _summaryValues.begin() && stateLast <= _summaryValues.end(), "");
vector<string> stateArgs(stateFirst, stateLast);
solAssert(stateArgs.size() == _stateVars.size(), "");
vector<string> stateCex;
for (unsigned i = 0; i < stateArgs.size(); ++i)
{
auto var = _stateVars.at(i);
if (var->type()->isValueType())
stateCex.emplace_back(var->name() + " = " + stateArgs.at(i));
}
return boost::algorithm::join(stateCex, ", ");
}
string CHC::formatFunctionCallCounterexample(vector<VariableDeclaration const*> const& _stateVars, FunctionDefinition const& _function, vector<string> const& _summaryValues)
{
/// The signature of a function summary predicate is: summary(error, preStateVars, preInputVars, postInputVars, outputVars).
/// Here we are interested in preInputVars.
vector<string>::const_iterator first = _summaryValues.begin() + static_cast<int>(_stateVars.size()) + 1;
vector<string>::const_iterator last = first + static_cast<int>(_function.parameters().size());
solAssert(first >= _summaryValues.begin() && first <= _summaryValues.end(), "");
solAssert(last >= _summaryValues.begin() && last <= _summaryValues.end(), "");
vector<string> functionArgsCex(first, last);
vector<string> functionArgs;
auto const& params = _function.parameters();
solAssert(params.size() == functionArgsCex.size(), "");
for (unsigned i = 0; i < params.size(); ++i)
if (params[i]->type()->isValueType())
functionArgs.emplace_back(functionArgsCex[i]);
else
functionArgs.emplace_back(params[i]->name());
string fName = _function.isConstructor() ? "constructor" :
_function.isFallback() ? "fallback" :
_function.isReceive() ? "receive" :
_function.name();
return fName + "(" + boost::algorithm::join(functionArgs, ", ") + ")";
}
string CHC::cex2dot(smtutil::CHCSolverInterface::CexGraph const& _cex)
{
string dot = "digraph {\n";
auto pred = [&](CHCSolverInterface::CexNode const& _node) {
return "\"" + _node.first + "(" + boost::algorithm::join(_node.second, ", ") + ")\"";
};
for (auto const& [u, vs]: _cex.edges)
for (auto v: vs)
dot += pred(_cex.nodes.at(v)) + " -> " + pred(_cex.nodes.at(u)) + "\n";
dot += "}";
return dot;
}
string CHC::uniquePrefix()
{
return to_string(m_blockCounter++);

View File

@ -38,6 +38,7 @@
#include <libsmtutil/CHCSolverInterface.h>
#include <map>
#include <optional>
#include <set>
namespace solidity::frontend
@ -102,6 +103,7 @@ private:
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smtutil::Expression> const* _arguments = nullptr);
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function);
//@}
/// Sort helpers.
@ -164,6 +166,9 @@ private:
/// @returns the current symbolic values of the current function's
/// input and output parameters.
std::vector<smtutil::Expression> currentFunctionVariables();
std::vector<smtutil::Expression> currentFunctionVariables(FunctionDefinition const& _function);
std::vector<smtutil::Expression> currentFunctionVariables(ContractDefinition const& _contract);
/// @returns the same as currentFunctionVariables plus
/// local variables.
std::vector<smtutil::Expression> currentBlockVariables();
@ -189,7 +194,7 @@ private:
void addRule(smtutil::Expression const& _rule, std::string const& _ruleName);
/// @returns <true, empty> if query is unsatisfiable (safe).
/// @returns <false, model> otherwise.
std::pair<smtutil::CheckResult, std::vector<std::string>> query(smtutil::Expression const& _query, langutil::SourceLocation const& _location);
std::pair<smtutil::CheckResult, smtutil::CHCSolverInterface::CexGraph> query(smtutil::Expression const& _query, langutil::SourceLocation const& _location);
void addVerificationTarget(ASTNode const* _scope, VerificationTarget::Type _type, smtutil::Expression _from, smtutil::Expression _constraints, smtutil::Expression _errorId);
void addAssertVerificationTarget(ASTNode const* _scope, smtutil::Expression _from, smtutil::Expression _constraints, smtutil::Expression _errorId);
@ -203,9 +208,25 @@ private:
ASTNode const* _scope,
CHCVerificationTarget const& _target,
unsigned _errorId,
langutil::ErrorId _errorReporterId,
std::string _satMsg,
std::string _unknownMsg
std::string _unknownMsg = ""
);
std::optional<std::string> generateCounterexample(smtutil::CHCSolverInterface::CexGraph const& _graph, std::string const& _root);
/// @returns values for the _stateVariables after a transaction calling
/// _function was executed.
/// _function = nullptr means the transaction was the deployment of a
/// contract without an explicit constructor.
std::string formatStateCounterexample(std::vector<VariableDeclaration const*> const& _stateVariables, FunctionDefinition const* _function, std::vector<std::string> const& _summaryValues);
/// @returns a formatted text representing a call to _function
/// with the concrete values for value type parameters and
/// the parameter name for reference types.
std::string formatFunctionCallCounterexample(std::vector<VariableDeclaration const*> const& _stateVariables, FunctionDefinition const& _function, std::vector<std::string> const& _summaryValues);
/// @returns a DAG in the dot format.
/// Used for debugging purposes.
std::string cex2dot(smtutil::CHCSolverInterface::CexGraph const& _graph);
//@}
/// Misc.
@ -255,6 +276,9 @@ private:
"error",
m_context
};
/// Maps predicate names to the ASTNodes they came from.
std::map<std::string, ASTNode const*> m_symbolFunction;
//@}
/// Variables.

View File

@ -425,17 +425,13 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components();
auto const* tupleComponents = &_tuple.components();
while (tupleComponents->size() == 1)
{
auto innerTuple = dynamic_pointer_cast<TupleExpression>(tupleComponents->front());
solAssert(innerTuple, "");
tupleComponents = &innerTuple->components();
}
solAssert(symbComponents.size() == tupleComponents->size(), "");
auto const* tuple = dynamic_cast<TupleExpression const*>(innermostTuple(_tuple));
solAssert(tuple, "");
auto const& tupleComponents = tuple->components();
solAssert(symbComponents.size() == tupleComponents.size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
{
auto tComponent = tupleComponents->at(i);
auto tComponent = tupleComponents.at(i);
if (tComponent)
{
if (auto varDecl = identifierToVariable(*tComponent))
@ -454,8 +450,7 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
/// Parenthesized expressions are also TupleExpression regardless their type.
auto const& components = _tuple.components();
solAssert(components.size() == 1, "");
if (smt::isSupportedType(components.front()->annotation().type->category()))
defineExpr(_tuple, expr(*components.front()));
defineExpr(_tuple, expr(*components.front()));
}
}
@ -729,11 +724,7 @@ void SMTEncoder::visitGasLeft(FunctionCall const& _funCall)
void SMTEncoder::endVisit(Identifier const& _identifier)
{
if (_identifier.annotation().willBeWrittenTo)
{
// Will be translated as part of the node that requested the lvalue.
}
else if (auto decl = identifierToVariable(_identifier))
if (auto decl = identifierToVariable(_identifier))
defineExpr(_identifier, currentValue(*decl));
else if (_identifier.annotation().type->category() == Type::Category::Function)
visitFunctionIdentifier(_identifier);
@ -1109,7 +1100,11 @@ void SMTEncoder::arrayPop(FunctionCall const& _funCall)
void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression const& _array)
{
if (auto const* id = dynamic_cast<Identifier const*>(&_expr))
Expression const* expr = &_expr;
if (auto const* tupleExpr = dynamic_cast<TupleExpression const*>(expr))
expr = innermostTuple(*tupleExpr);
if (auto const* id = dynamic_cast<Identifier const*>(expr))
{
auto varDecl = identifierToVariable(*id);
solAssert(varDecl, "");
@ -1117,9 +1112,9 @@ void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression
resetReferences(*varDecl);
m_context.addAssertion(m_context.newValue(*varDecl) == _array);
}
else if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(&_expr))
else if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(expr))
arrayIndexAssignment(*indexAccess, _array);
else if (auto const* funCall = dynamic_cast<FunctionCall const*>(&_expr))
else if (auto const* funCall = dynamic_cast<FunctionCall const*>(expr))
{
FunctionType const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
if (funType.kind() == FunctionType::Kind::ArrayPush)
@ -1141,7 +1136,7 @@ void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression
arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue());
}
}
else if (dynamic_cast<MemberAccess const*>(&_expr))
else if (dynamic_cast<MemberAccess const*>(expr))
m_errorReporter.warning(
9599_error,
_expr.location(),
@ -1449,10 +1444,14 @@ void SMTEncoder::assignment(
"Tuple assignments should be handled by tupleAssignment."
);
Expression const* left = &_left;
if (auto const* tuple = dynamic_cast<TupleExpression const*>(left))
left = innermostTuple(*tuple);
if (!smt::isSupportedType(_type->category()))
{
// Give it a new index anyway to keep the SSA scheme sound.
if (auto varDecl = identifierToVariable(_left))
if (auto varDecl = identifierToVariable(*left))
m_context.newValue(*varDecl);
m_errorReporter.warning(
@ -1461,10 +1460,10 @@ void SMTEncoder::assignment(
"Assertion checker does not yet implement type " + _type->toString()
);
}
else if (auto varDecl = identifierToVariable(_left))
else if (auto varDecl = identifierToVariable(*left))
assignment(*varDecl, _right);
else if (dynamic_cast<IndexAccess const*>(&_left))
arrayIndexAssignment(_left, _right);
else if (dynamic_cast<IndexAccess const*>(left))
arrayIndexAssignment(*left, _right);
else
m_errorReporter.warning(
8182_error,
@ -1475,7 +1474,7 @@ void SMTEncoder::assignment(
void SMTEncoder::tupleAssignment(Expression const& _left, Expression const& _right)
{
auto lTuple = dynamic_cast<TupleExpression const*>(&_left);
auto lTuple = dynamic_cast<TupleExpression const*>(innermostTuple(dynamic_cast<TupleExpression const&>(_left)));
solAssert(lTuple, "");
auto const& lComponents = lTuple->components();
@ -1901,6 +1900,20 @@ Expression const* SMTEncoder::leftmostBase(IndexAccess const& _indexAccess)
return base;
}
Expression const* SMTEncoder::innermostTuple(TupleExpression const& _tuple)
{
solAssert(!_tuple.isInlineArray(), "");
TupleExpression const* tuple = &_tuple;
Expression const* expr = tuple;
while (tuple && !tuple->isInlineArray() && tuple->components().size() == 1)
{
expr = tuple->components().front().get();
tuple = dynamic_cast<TupleExpression const*>(expr);
}
solAssert(expr, "");
return expr;
}
set<VariableDeclaration const*> SMTEncoder::touchedVariables(ASTNode const& _node)
{
vector<CallableDeclaration const*> callStack;

View File

@ -54,6 +54,9 @@ public:
/// @returns the leftmost identifier in a multi-d IndexAccess.
static Expression const* leftmostBase(IndexAccess const& _indexAccess);
/// @returns the innermost element in a chain of 1-tuples.
static Expression const* innermostTuple(TupleExpression const& _tuple);
/// @returns the FunctionDefinition of a FunctionCall
/// if possible or nullptr.
static FunctionDefinition const* functionCallToDefinition(FunctionCall const& _funCall);

View File

@ -75,7 +75,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
abi.emplace(std::move(method));
}
FunctionDefinition const* constructor = _contractDef.constructor();
if (constructor && constructor->visibility() >= Visibility::Public)
if (constructor && !_contractDef.abstract())
{
FunctionType constrType(*constructor);
FunctionType const* externalFunctionType = constrType.interfaceFunctionType();

View File

@ -516,7 +516,17 @@ bool CompilerStack::compile()
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
if (isRequestedContract(*contract))
{
compileContract(*contract, otherCompilers);
try
{
compileContract(*contract, otherCompilers);
}
catch (Error const& _error)
{
if (_error.type() != Error::Type::CodeGenerationError)
throw;
m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what());
return false;
}
if (m_generateIR || m_generateEwasm)
generateIR(*contract);
if (m_generateEwasm)

View File

@ -47,8 +47,12 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
{
string const value = extractDoc(constructorDefinition->annotation().docTags, "notice");
if (!value.empty())
{
// add the constructor, only if we have any documentation to add
methods["constructor"] = Json::Value(value);
Json::Value user;
user["notice"] = Json::Value(value);
methods["constructor"] = user;
}
}
string notice = extractDoc(_contractDef.annotation().docTags, "notice");

View File

@ -465,14 +465,6 @@ StateMutability Parser::parseStateMutability()
case Token::Pure:
stateMutability = StateMutability::Pure;
break;
case Token::Constant:
stateMutability = StateMutability::View;
parserError(
7698_error,
"The state mutability modifier \"constant\" was removed in version 0.5.0. "
"Use \"view\" or \"pure\" instead."
);
break;
default:
solAssert(false, "Invalid state mutability specifier.");
}
@ -686,19 +678,13 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory = _lookAheadArrayType ?
ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this);
ASTPointer<TypeName> type;
ASTPointer<StructuredDocumentation> const documentation = parseStructuredDocumentation();
if (_lookAheadArrayType)
type = _lookAheadArrayType;
else
{
type = parseTypeName(_options.allowVar);
if (type != nullptr)
nodeFactory.setEndPositionFromNode(type);
}
ASTPointer<TypeName> type = _lookAheadArrayType ? _lookAheadArrayType : parseTypeName();
nodeFactory.setEndPositionFromNode(type);
if (!_options.isStateVariable && documentation != nullptr)
parserWarning(2837_error, "Only state variables can have a docstring. This will be disallowed in 0.7.0.");
parserError(2837_error, "Only state variables can have a docstring.");
if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace)
fatalParserError(
@ -762,8 +748,6 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
{
if (location != VariableDeclaration::Location::Unspecified)
parserError(3548_error, "Location already specified.");
else if (!type)
parserError(7439_error, "Location specifier needs explicit type name.");
else
{
switch (token)
@ -790,10 +774,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
}
if (_options.allowEmptyName && m_scanner->currentToken() != Token::Identifier)
{
identifier = make_shared<ASTString>("");
solAssert(!_options.allowVar, ""); // allowEmptyName && allowVar makes no sense
}
else
{
nodeFactory.markEndPosition();
@ -917,7 +898,7 @@ ASTPointer<UsingForDirective> Parser::parseUsingDirective()
if (m_scanner->currentToken() == Token::Mul)
m_scanner->next();
else
typeName = parseTypeName(false);
typeName = parseTypeName();
nodeFactory.markEndPosition();
expectToken(Token::Semicolon);
return nodeFactory.createNode<UsingForDirective>(library, typeName);
@ -980,7 +961,7 @@ ASTPointer<TypeName> Parser::parseTypeNameSuffix(ASTPointer<TypeName> type, ASTN
return type;
}
ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
ASTPointer<TypeName> Parser::parseTypeName()
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
@ -998,7 +979,7 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
auto stateMutability = elemTypeName.token() == Token::Address
? optional<StateMutability>{StateMutability::NonPayable}
: nullopt;
if (TokenTraits::isStateMutabilitySpecifier(m_scanner->currentToken(), false))
if (TokenTraits::isStateMutabilitySpecifier(m_scanner->currentToken()))
{
if (elemTypeName.token() == Token::Address)
{
@ -1013,12 +994,6 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
}
type = nodeFactory.createNode<ElementaryTypeName>(elemTypeName, stateMutability);
}
else if (token == Token::Var)
{
if (!_allowVar)
parserError(7059_error, "Expected explicit type name.");
m_scanner->next();
}
else if (token == Token::Function)
type = parseFunctionType();
else if (token == Token::Mapping)
@ -1028,9 +1003,10 @@ ASTPointer<TypeName> Parser::parseTypeName(bool _allowVar)
else
fatalParserError(3546_error, "Expected type name");
if (type)
// Parse "[...]" postfixes for arrays.
type = parseTypeNameSuffix(type, nodeFactory);
solAssert(type, "");
// Parse "[...]" postfixes for arrays.
type = parseTypeNameSuffix(type, nodeFactory);
return type;
}
@ -1071,8 +1047,7 @@ ASTPointer<Mapping> Parser::parseMapping()
else
fatalParserError(1005_error, "Expected elementary type name or identifier for mapping key type");
expectToken(Token::Arrow);
bool const allowVar = false;
ASTPointer<TypeName> valueType = parseTypeName(allowVar);
ASTPointer<TypeName> valueType = parseTypeName();
nodeFactory.markEndPosition();
expectToken(Token::RParen);
return nodeFactory.createNode<Mapping>(keyType, valueType);
@ -1553,53 +1528,14 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme
ASTNodeFactory nodeFactory(*this);
if (_lookAheadArrayType)
nodeFactory.setLocation(_lookAheadArrayType->location());
VarDeclParserOptions options;
options.allowLocationSpecifier = true;
vector<ASTPointer<VariableDeclaration>> variables;
variables.emplace_back(parseVariableDeclaration(options, _lookAheadArrayType));
nodeFactory.setEndPositionFromNode(variables.back());
ASTPointer<Expression> value;
if (
!_lookAheadArrayType &&
m_scanner->currentToken() == Token::Var &&
m_scanner->peekNextToken() == Token::LParen
)
{
// Parse `var (a, b, ,, c) = ...` into a single VariableDeclarationStatement with multiple variables.
m_scanner->next();
m_scanner->next();
if (m_scanner->currentToken() != Token::RParen)
while (true)
{
ASTPointer<VariableDeclaration> var;
if (
m_scanner->currentToken() != Token::Comma &&
m_scanner->currentToken() != Token::RParen
)
{
ASTNodeFactory varDeclNodeFactory(*this);
varDeclNodeFactory.markEndPosition();
ASTPointer<ASTString> name = expectIdentifierToken();
var = varDeclNodeFactory.createNode<VariableDeclaration>(
ASTPointer<TypeName>(),
name,
ASTPointer<Expression>(),
Visibility::Default
);
}
variables.push_back(var);
if (m_scanner->currentToken() == Token::RParen)
break;
else
expectToken(Token::Comma);
}
nodeFactory.markEndPosition();
m_scanner->next();
}
else
{
VarDeclParserOptions options;
options.allowVar = true;
options.allowLocationSpecifier = true;
variables.push_back(parseVariableDeclaration(options, _lookAheadArrayType));
nodeFactory.setEndPositionFromNode(variables.back());
}
if (m_scanner->currentToken() == Token::Assign)
{
m_scanner->next();
@ -1713,11 +1649,8 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
else if (m_scanner->currentToken() == Token::New)
{
expectToken(Token::New);
ASTPointer<TypeName> typeName(parseTypeName(false));
if (typeName)
nodeFactory.setEndPositionFromNode(typeName);
else
nodeFactory.markEndPosition();
ASTPointer<TypeName> typeName(parseTypeName());
nodeFactory.setEndPositionFromNode(typeName);
expression = nodeFactory.createNode<NewExpression>(typeName);
}
else if (m_scanner->currentToken() == Token::Payable)
@ -1826,16 +1759,11 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance());
break;
case Token::Number:
if (
(m_scanner->peekNextToken() == Token::Identifier && m_scanner->peekLiteral() == "gwei") ||
TokenTraits::isEtherSubdenomination(m_scanner->peekNextToken())
)
if (TokenTraits::isEtherSubdenomination(m_scanner->peekNextToken()))
{
ASTPointer<ASTString> literal = getLiteralAndAdvance();
nodeFactory.markEndPosition();
Token actualToken = m_scanner->currentToken() == Token::Identifier ? Token::SubGwei : m_scanner->currentToken();
Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(actualToken);
Literal::SubDenomination subdenomination = static_cast<Literal::SubDenomination>(m_scanner->currentToken());
m_scanner->next();
expression = nodeFactory.createNode<Literal>(token, literal, subdenomination);
}
@ -1854,6 +1782,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
}
break;
case Token::StringLiteral:
case Token::UnicodeStringLiteral:
case Token::HexStringLiteral:
{
string literal = m_scanner->currentLiteral();
@ -2062,7 +1991,7 @@ Parser::LookAheadInfo Parser::peekStatementType() const
Token token(m_scanner->currentToken());
bool mightBeTypeName = (TokenTraits::isElementaryTypeName(token) || token == Token::Identifier);
if (token == Token::Mapping || token == Token::Function || token == Token::Var)
if (token == Token::Mapping || token == Token::Function)
return LookAheadInfo::VariableDeclaration;
if (mightBeTypeName)
{
@ -2071,7 +2000,7 @@ Parser::LookAheadInfo Parser::peekStatementType() const
// kind of statement. This means, for example, that we do not allow type expressions of the form
// ``address payable;``.
// If we want to change this in the future, we need to consider another scanner token here.
if (TokenTraits::isElementaryTypeName(token) && TokenTraits::isStateMutabilitySpecifier(next, false))
if (TokenTraits::isElementaryTypeName(token) && TokenTraits::isStateMutabilitySpecifier(next))
return LookAheadInfo::VariableDeclaration;
if (next == Token::Identifier || TokenTraits::isLocationSpecifier(next))
return LookAheadInfo::VariableDeclaration;

View File

@ -58,7 +58,6 @@ private:
// https://stackoverflow.com/questions/17430377
VarDeclParserOptions() {}
bool allowVar = false;
bool isStateVariable = false;
bool allowIndexed = false;
bool allowEmptyName = false;
@ -108,7 +107,7 @@ private:
ASTPointer<Identifier> parseIdentifier();
ASTPointer<UserDefinedTypeName> parseUserDefinedTypeName();
ASTPointer<TypeName> parseTypeNameSuffix(ASTPointer<TypeName> type, ASTNodeFactory& nodeFactory);
ASTPointer<TypeName> parseTypeName(bool _allowVar);
ASTPointer<TypeName> parseTypeName();
ASTPointer<FunctionTypeName> parseFunctionType();
ASTPointer<Mapping> parseMapping();
ASTPointer<ParameterList> parseParameterList(

View File

@ -88,8 +88,31 @@ bool parse(Json::CharReaderBuilder& _builder, string const& _input, Json::Value&
return reader->parse(_input.c_str(), _input.c_str() + _input.length(), &_json, _errs);
}
/// Takes a JSON value (@ _json) and removes all its members with value 'null' recursively.
void removeNullMembersHelper(Json::Value& _json)
{
if (_json.type() == Json::ValueType::arrayValue)
for (auto& child: _json)
removeNullMembersHelper(child);
else if (_json.type() == Json::ValueType::objectValue)
for (auto const& key: _json.getMemberNames())
{
Json::Value& value = _json[key];
if (value.isNull())
_json.removeMember(key);
else
removeNullMembersHelper(value);
}
}
} // end anonymous namespace
Json::Value removeNullMembers(Json::Value _json)
{
removeNullMembersHelper(_json);
return _json;
}
string jsonPrettyPrint(Json::Value const& _input)
{
static map<string, Json::Value> settings{{"indentation", " "}, {"enableYAMLCompatibility", true}};

View File

@ -29,6 +29,9 @@
namespace solidity::util {
/// Removes members with null value recursively from (@a _json).
Json::Value removeNullMembers(Json::Value _json);
/// Serialise the JSON object (@a _input) with indentation
std::string jsonPrettyPrint(Json::Value const& _input);

View File

@ -209,7 +209,10 @@ void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
m_currentScope->insideFunction()
);
for (auto const& variable: _varDecl.variables)
{
expectValidIdentifier(variable.name, variable.location);
expectValidType(variable.type, variable.location);
}
if (_varDecl.value)
{
@ -249,11 +252,13 @@ void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
void AsmAnalyzer::operator()(FunctionDefinition const& _funDef)
{
yulAssert(!_funDef.name.empty(), "");
expectValidIdentifier(_funDef.name, _funDef.location);
Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
yulAssert(virtualBlock, "");
Scope& varScope = scope(virtualBlock);
for (auto const& var: _funDef.parameters + _funDef.returnVariables)
{
expectValidIdentifier(var.name, var.location);
expectValidType(var.type, var.location);
m_activeVariables.insert(&std::get<Scope::Variable>(varScope.identifiers.at(var.name)));
}
@ -529,6 +534,26 @@ Scope& AsmAnalyzer::scope(Block const* _block)
return *scopePtr;
}
void AsmAnalyzer::expectValidIdentifier(YulString _identifier, SourceLocation const& _location)
{
// NOTE: the leading dot case is handled by the parser not allowing it.
if (boost::ends_with(_identifier.str(), "."))
m_errorReporter.syntaxError(
3384_error,
_location,
"\"" + _identifier.str() + "\" is not a valid identifier (ends with a dot)."
);
if (_identifier.str().find("..") != std::string::npos)
m_errorReporter.syntaxError(
7771_error,
_location,
"\"" + _identifier.str() + "\" is not a valid identifier (contains consecutive dots)."
);
}
void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location)
{
if (!m_dialect.types.count(_type))
@ -611,16 +636,16 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio
errorForVM(7110_error, "only available for Constantinople-compatible");
else if (_instr == evmasm::Instruction::CHAINID && !m_evmVersion.hasChainID())
errorForVM(1561_error, "only available for Istanbul-compatible");
else if (_instr == evmasm::Instruction::PC)
m_errorReporter.warning(
2450_error,
_location,
"The \"" +
boost::to_lower_copy(instructionInfo(_instr).name) +
"\" instruction is deprecated and will be removed in the next breaking release."
);
else if (_instr == evmasm::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance())
errorForVM(3672_error, "only available for Istanbul-compatible");
errorForVM(7721_error, "only available for Istanbul-compatible");
else if (_instr == evmasm::Instruction::PC)
m_errorReporter.error(
2450_error,
Error::Type::SyntaxError,
_location,
"PC instruction is a low-level EVM feature. "
"Because of that PC is disallowed in strict assembly."
);
else
return false;

View File

@ -108,6 +108,7 @@ private:
void checkAssignment(Identifier const& _variable, YulString _valueType);
Scope& scope(Block const* _block);
void expectValidIdentifier(YulString _identifier, langutil::SourceLocation const& _location);
void expectValidType(YulString _type, langutil::SourceLocation const& _location);
void expectType(YulString _expectedType, YulString _givenType, langutil::SourceLocation const& _location);

View File

@ -163,14 +163,21 @@ NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom):
{
for (auto& fun: m_functions)
{
size_t parameters = fun.second.parameters.size();
size_t returns = fun.second.returns.size();
fun.second.generateCode = [=](FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&, std::function<void(Expression const&)> _visitExpression)
{
for (auto const& arg: _call.arguments | boost::adaptors::reversed)
_visitExpression(arg);
size_t visited = 0;
for (size_t j = 0; j < _call.arguments.size(); j++)
{
size_t const i = _call.arguments.size() - j - 1;
if (!(fun.second.literalArguments && (*fun.second.literalArguments)[i]))
{
_visitExpression(_call.arguments[i]);
visited++;
}
}
for (size_t i = 0; i < parameters; i++)
for (size_t i = 0; i < visited; i++)
_assembly.appendInstruction(evmasm::Instruction::POP);
for (size_t i = 0; i < returns; i++)

View File

@ -1024,11 +1024,6 @@ function sstore(x1, x2, x3, x4, y1, y2, y3, y4) {
eth.storageStore(0:i32, 32:i32)
}
// Needed?
function pc() -> z1, z2, z3, z4 {
// TODO implement
unreachable()
}
function gas() -> z1, z2, z3, z4 {
z4 := eth.getGasLeft()
}

View File

@ -22,7 +22,7 @@
# (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------
FROM gcr.io/oss-fuzz-base/base-clang as base
LABEL version="1"
LABEL version="2"
ARG DEBIAN_FRONTEND=noninteractive
@ -96,6 +96,18 @@ RUN set -ex; \
ninja install/strip; \
rm -rf /usr/src/evmone
# HERA
RUN set -ex; \
cd /usr/src; \
git clone --branch="v0.3.0" --recurse-submodules https://github.com/ewasm/hera.git; \
cd hera; \
mkdir build; \
cd build; \
cmake -G Ninja -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \
ninja; \
ninja install/strip; \
rm -rf /usr/src/hera
FROM base
COPY --from=libraries /usr/lib /usr/lib
COPY --from=libraries /usr/bin /usr/bin

View File

@ -22,7 +22,7 @@
# (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------
FROM buildpack-deps:bionic AS base
LABEL version="1"
LABEL version="2"
ARG DEBIAN_FRONTEND=noninteractive
@ -88,6 +88,19 @@ RUN set -ex; \
tar xzpf $TGZFILE -C /usr; \
rm -f $TGZFILE;
# HERA
ARG HERA_HASH="622663cb3bc1fa07213c6e1fe8070c5c259096e75039a46d014b2a3448b5cf44"
ARG HERA_MAJOR="0"
ARG HERA_MINOR="3"
ARG HERA_MICRO="0"
RUN set -ex; \
HERA_VERSION="$HERA_MAJOR.$HERA_MINOR.$HERA_MICRO"; \
TGZFILE="hera-$HERA_VERSION-linux-x86_64.tar.gz"; \
wget https://github.com/ewasm/hera/releases/download/v$HERA_VERSION/$TGZFILE; \
sha256sum $TGZFILE; \
tar xzpf $TGZFILE -C /usr; \
rm -f $TGZFILE;
FROM base
COPY --from=libraries /usr/lib /usr/lib
COPY --from=libraries /usr/bin /usr/bin

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