Merge pull request #8042 from ethereum/develop

Merge develop into release for 0.6.0
This commit is contained in:
chriseth 2019-12-17 22:32:51 +01:00 committed by GitHub
commit 26b700771e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1121 changed files with 20540 additions and 5815 deletions

View File

@ -9,7 +9,7 @@ version: 2.1
parameters:
docker-image-rev:
type: string
default: "3"
default: "4"
defaults:
@ -593,19 +593,24 @@ jobs:
t_ems_solcjs:
docker:
- image: circleci/node:10
- image: ethereum/solidity-buildpack-deps:ubuntu1904
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
name: Install test dependencies
command: |
apt-get update
apt-get install -qqy --no-install-recommends nodejs npm cvc4
- run:
name: Test solcjs
command: |
node --version
npm --version
test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt)
test/externalTests/solc-js/solc-js.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt)
t_ems_compile_ext_gnosis:
docker:
@ -716,7 +721,7 @@ workflows:
# basic checks
- chk_spelling: *workflow_trigger_on_tags
- chk_coding_style: *workflow_trigger_on_tags
- chk_docs_examples: *workflow_trigger_on_tags
# DISABLED FOR 0.6.0 - chk_docs_examples: *workflow_trigger_on_tags
- chk_buglist: *workflow_trigger_on_tags
- chk_proofs: *workflow_trigger_on_tags

View File

@ -92,7 +92,7 @@ RUN set -ex; \
# EVMONE
RUN set -ex; \
cd /usr/src; \
git clone --branch="v0.3.0" --recurse-submodules https://github.com/ethereum/evmone.git; \
git clone --branch="v0.4.0" --recurse-submodules https://github.com/ethereum/evmone.git; \
cd evmone; \
mkdir build; \
cd build; \

View File

@ -75,9 +75,9 @@ RUN set -ex; \
rm -rf /var/lib/libfuzzer
# EVMONE
ARG EVMONE_HASH="fa4f40daf7cf9ccbcca6c78345977e084ea2136a8eae661e4d19471be852b15b"
ARG EVMONE_HASH="e9f8df89c52d9c60c9a38dd00687b1ec9e9ae9650b400a87c4c0cf7468e35307"
ARG EVMONE_MAJOR="0"
ARG EVMONE_MINOR="3"
ARG EVMONE_MINOR="4"
ARG EVMONE_MICRO="0"
RUN set -ex; \
EVMONE_VERSION="$EVMONE_MAJOR.$EVMONE_MINOR.$EVMONE_MICRO"; \

View File

@ -77,7 +77,7 @@ RUN set -ex; \
# EVMONE
RUN set -ex; \
cd /usr/src; \
git clone --branch="v0.3.0" --recurse-submodules https://github.com/ethereum/evmone.git; \
git clone --branch="v0.4.0" --recurse-submodules https://github.com/ethereum/evmone.git; \
cd evmone; \
mkdir build; \
cd build; \

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.5.15")
set(PROJECT_VERSION "0.6.0")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
include(TestBigEndian)

View File

@ -1,9 +1,55 @@
### 0.6.0 (2019-12-17)
Breaking changes:
* ABI: Remove the deprecated ``constant`` and ``payable`` fields.
* ABI: The ``type`` field is now required and no longer specified to default to ``function``.
* AST: Inline assembly is exported as structured JSON instead of plain string.
* C API (``libsolc``): Introduce context parameter to both ``solidity_compile`` and the callback.
* C API (``libsolc``): The provided callback now takes two parameters, kind and data. The callback can then be used for multiple purposes, such has file imports and SMT queries.
* C API (``libsolc``): ``solidity_free`` was renamed to ``solidity_reset``. Functions ``solidity_alloc`` and ``solidity_free`` were added.
* C API (``libsolc``): ``solidity_compile`` now returns a string that must be explicitly freed via ``solidity_free()``
* Commandline Interface: Remove the text-based AST printer (``--ast``).
* Commandline Interface: Switch to the new error reporter by default. ``--old-reporter`` falls back to the deprecated old error reporter.
* Commandline Interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata.
* General: Disallow explicit conversions from external function types to ``address`` and add a member called ``address`` to them as replacement.
* General: Enable Yul optimizer as part of standard optimization.
* General: New reserved keywords: ``override``, ``receive``, and ``virtual``.
* General: ``private`` cannot be used together with ``virtual``.
* General: Split unnamed fallback functions into two cases defined using ``fallback()`` and ``receive()``.
* Inheritance: State variable shadowing is now disallowed.
* Inline Assembly: Only strict inline assembly is allowed.
* Inline Assembly: Variable declarations cannot shadow declarations outside the assembly block.
* JSON AST: Replace ``superFunction`` attribute by ``baseFunctions``.
* Natspec JSON Interface: Properly support multiple ``@return`` statements in ``@dev`` documentation and enforce named return parameters to be mentioned documentation.
* Source mappings: Add "modifier depth" as a fifth field in the source mappings.
* Standard JSON Interface: Add option to disable or choose hash method between IPFS and Swarm for the bytecode metadata.
* Syntax: ``push(element)`` for dynamic storage arrays do not return the new length anymore.
* Syntax: Abstract contracts need to be marked explicitly as abstract by using the ``abstract`` keyword.
* Syntax: ``length`` member of arrays is now always read-only, even for storage arrays.
* Type Checker: Resulting type of exponentiation is equal to the type of the base. Also allow signed types for the base.
Language Features:
* Allow explicit conversions from ``address`` to ``address payable`` via ``payable(...)``.
* Allow global enums and structs.
* Allow public variables to override external functions.
* Allow underscores as delimiters in hex strings.
* Introduce syntax for array slices and implement them for dynamic calldata arrays.
* Introduce ``push()`` for dynamic storage arrays. It returns a reference to the newly allocated element, if applicable.
* Introduce ``virtual`` and ``override`` keywords.
* Modify ``push(element)`` for dynamic storage arrays such that it does not return the new length anymore.
* Yul: Introduce ``leave`` statement that exits the current function.
* JSON AST: Add the function selector of each externally-visible FunctonDefinition to the AST JSON export.
Compiler Features:
* Allow revert strings to be stripped from the binary using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting.
* ABIEncoderV2: Do not warn about enabled ABIEncoderV2 anymore (the pragma is still needed, though).
### 0.5.15 (2019-12-17)
Bugfixes:
* Yul Optimizer: Fix incorrect redundant load optimization crossing user-defined functions that contain for-loops with memory / storage writes.
### 0.5.14 (2019-12-09)
Language Features:
@ -13,7 +59,7 @@ Language Features:
Compiler Features:
* Commandline Interface: Allow translation from yul / strict assembly to EWasm using ``solc --yul --yul-dialect evm --machine eWasm``
* Commandline Interface: Allow translation from yul / strict assembly to EWasm using ``solc --yul --yul-dialect evm --machine ewasm``
* Set the default EVM version to "Istanbul".
* SMTChecker: Add support to constructors including constructor inheritance.
* Yul: When compiling via Yul, string literals from the Solidity code are kept as string literals if every character is safely printable.
@ -48,7 +94,6 @@ Compiler Features:
* TypeChecker: List possible candidates when overload resolution fails.
* TypeChecker: Disallow variables of library types.
Bugfixes:
* Code Generator: Fixed a faulty assert that would wrongly trigger for array sizes exceeding unsigned integer.
* SMTChecker: Fix internal error when accessing indices of fixed bytes.
@ -59,7 +104,6 @@ Bugfixes:
* Code Generator: Fix internal error when trying to convert ``super`` to a different type
### 0.5.12 (2019-10-01)
Language Features:

View File

@ -20,11 +20,15 @@ that run on the Ethereum Virtual Machine. Smart contracts are programs that are
network where nobody has special authority over the execution, and thus they allow to implement tokens of value,
ownership, voting and other kinds of logics.
When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number [to indicate this fast pace of change](https://semver.org/#spec-item-4).
When deploying contracts, you should use the latest released version of
Solidity. This is because breaking changes as well as new features and bug fixes are
introduced regularly. We currently use a 0.x version
number [to indicate this fast pace of change](https://semver.org/#spec-item-4).
## Build and Install
Instructions about how to build and install the Solidity compiler can be found in the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source).
Instructions about how to build and install the Solidity compiler can be
found in the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source).
## Example
@ -32,7 +36,7 @@ Instructions about how to build and install the Solidity compiler can be found i
A "Hello World" program in Solidity is of even less use than in other languages, but still:
```solidity
pragma solidity ^0.5.0;
pragma solidity ^0.6.0;
contract HelloWorld {
function helloWorld() external pure returns (string memory) {
@ -60,7 +64,8 @@ Please follow the
[Developers Guide](https://solidity.readthedocs.io/en/latest/contributing.html)
if you want to help.
You can find our current feature and bug priorities for forthcoming releases [in the projects section](https://github.com/ethereum/solidity/projects).
You can find our current feature and bug priorities for forthcoming
releases [in the projects section](https://github.com/ethereum/solidity/projects).
## Maintainers
* [@axic](https://github.com/axic)

View File

@ -10,7 +10,9 @@ include(CheckCXXCompilerFlag)
#
function(eth_add_cxx_compiler_flag_if_supported FLAG)
# Remove leading - or / from the flag name.
string(REGEX REPLACE "^-|/" "" name ${FLAG})
string(REGEX REPLACE "^[-/]" "" name ${FLAG})
# Deletes any ':' because it's invalid variable names.
string(REGEX REPLACE ":" "" name ${name})
check_cxx_compiler_flag(${FLAG} ${name})
if(${name})
add_compile_options(${FLAG})

View File

@ -345,7 +345,8 @@ commandline compiler for linking):
::
pragma solidity >=0.5.0 <0.7.0;
// This will not compile after 0.6.0
pragma solidity >=0.5.0 <0.5.99;
library OldLibrary {
function someFunction(uint8 a) public returns(bool);
@ -437,7 +438,7 @@ New version:
function f(uint y) external {
x = y;
}
function() payable external {}
receive() payable external {}
}
contract New {

View File

@ -0,0 +1,175 @@
********************************
Solidity v0.6.0 Breaking Changes
********************************
This section highlights the main breaking changes introduced in Solidity
version 0.6.0, along with the reasoning behind the changes and how to update
affected code.
For the full list check
`the release changelog <https://github.com/ethereum/solidity/releases/tag/v0.6.0>`_.
Changes the Compiler Might not Warn About
=========================================
This section lists changes where the behaviour of your code might
change without the compiler telling you about it.
* The resulting type of an exponentiation is the type of the base. It used to be the smallest type
that can hold both the type of the base and the type of the exponent, as with symmentric
operations. Additionally, signed types are allowed for the base of the exponetation.
Explicitness Requirements
=========================
This section lists changes where the code now needs to be more explicit,
but the semantics do not change.
For most of the topics the compiler will provide suggestions.
* Functions can now only be overridden when they are either marked with the
``virtual`` keyword or defined in an interface. Functions without
implementation outside an interface have to be marked ``virtual``.
When overriding a function or modifier, the new keyword ``override``
must be used. When overriding a function or modifier defined in multiple
parallel bases, all bases must be listed in parentheses after the keyword
like so: ``override(Base1, Base2)``.
* Member-access to ``length`` of arrays is now always read-only, even for storage arrays. It is no
longer possible to resize storage arrays assigning a new value to their length. Use ``push()``,
``push(value)`` or ``pop()`` instead, or assign a full array, which will of course overwrite existing content.
The reason behind this is to prevent storage collisions by gigantic
storage arrays.
* The new keyword ``abstract`` can be used to mark contracts as abstract. It has to be used
if a contract does not implement all its functions.
* Libraries have to implement all their functions, not only the internal ones.
* The names of variables declared in inline assembly may no longer end in ``_slot`` or ``_offset``.
* Variable declarations in inline assembly may no longer shadow any declaration outside the inline assembly block.
If the name contains a dot, its prefix up to the dot may not conflict with any declaration outside the inline
assembly block.
* State variable shadowing is now disallowed. A derived contract can only
declare a state variable ``x``, if there is no visible state variable with
the same name in any of its bases.
Semantic and Syntactic Changes
==============================
This section lists changes where you have to modify your code
and it does something else afterwards.
* Conversions from external function types to ``address`` are now disallowed. Instead external
function types have a member called ``address``, similar to the existing ``selector`` member.
* The function ``push(value)`` for dynamic storage arrays does not return the new length anymore (it returns nothing).
* The unnamed function commonly referred to as "fallback function" was split up into a new
fallback function that is defined using the ``fallback`` keyword and a receive ether function
defined using the ``receive`` keyword.
* If present, the receive ether function is called whenever the call data is empty (whether
or not ether is received). This function is implicitly ``payable``.
* The new fallback function is called when no other function matches (if the receive ether
function does not exist then this includes calls with empty call data).
You can make this function ``payable`` or not. If it is not ``payable`` then transactions
not matching any other function which send value will revert. You should only need to
implement the new fallback function if you are following an upgrade or proxy pattern.
New Features
============
This section lists things that were not possible prior to Solidity 0.6.0
or at least were more difficult to achieve prior to Solidity 0.6.0.
* The :ref:`try/catch statement <try-catch>` allows you to react on failed external calls.
* ``struct`` and ``enum`` types can be declared at file level.
* Array slices can be used for calldata arrays, for example ``abi.decode(msg.data[4:], (uint, uint))``
is a low-level way to decode the function call payload.
* Natspec supports multiple return parameters in developer documentation, enforcing the same naming check as ``@param``.
* Yul and Inline Assembly have a new statement called ``leave`` that exits the current function.
* Conversions from ``address`` to ``address payable`` are now possible via ``payable(x)``, where
``x`` must be of type ``address``.
Interface Changes
=================
This section lists changes that are unrelated to the language itself, but that have an effect on the interfaces of
the compiler. These may change the way how you use the compiler on the command line, how you use its programmable
interface or how you analyze the output produced by it.
New Error Reporter
~~~~~~~~~~~~~~~~~~
A new error reporter was introduced, which aims at producing more accessible error messages on the command line.
It is enabled by default, but passing ``--old-reporter`` falls back to the the deprecated old error reporter.
Metadata Hash Options
~~~~~~~~~~~~~~~~~~~~~
The compiler now appends the `IPFS <https://ipfs.io/>`_ hash of the metadata file to the end of the bytecode by default
(for details, see documentation on :doc:`contract metadata <metadata>`). Before 0.6.0, the compiler appended the
`Swarm <https://ethersphere.github.io/swarm-home/>`_ hash by default, and in order to still support this behaviour,
the new command line option ``--metadata-hash`` was introduced. It allows you to select the hash to be produced and
appended, by passing either ``ipfs`` or ``swarm`` as value to the ``--metadata-hash`` command line option.
Passing the value ``none`` completely removes the hash.
These changes can also be used via the :ref:`Standard JSON Interface<compiler-api>` and effect the metadata JSON generated by the compiler.
The recommended way to read the metadata is to read the last two bytes to determine the length of the CBOR encoding
and perform a proper decoding on that data block as explained in the :ref:`metadata section<encoding-of-the-metadata-hash-in-the-bytecode>`.
Yul Optimizer
~~~~~~~~~~~~~
Together with the legacy bytecode optimizer, the :doc:`Yul <yul>` optimizer is now enabled by default when you call the compiler
with ``--optimize``. It can be disabled by calling the compiler with ``--no-optimize-yul``.
This mostly affects code that uses ABIEncoderV2.
C API Changes
~~~~~~~~~~~~~
The client code that uses the C API of ``libsolc`` is now in control of the memory used by the compiler. To make
this change consistent, ``solidity_free`` was renamed to ``solidity_reset``, the functions ``solidity_alloc`` and
``solidity_free`` were added and ``solidity_compile`` now returns a string that must be explicitly freed via
``solidity_free()``.
How to update your code
=======================
This section gives detailed instructions on how to update prior code for every breaking change.
* Change ``address(f)`` to ``f.address`` for ``f`` being of external function type.
* Replace ``function () external [payable] { ... }`` by either ``receive() external payable { ... }``,
``fallback() external [payable] { ... }`` or both. Prefer
using a ``receive`` function only, whenever possible.
* Change ``uint length = array.push(value)`` to ``array.push(value);``. The new length can be
accessed via ``array.length``.
* Change ``array.length++`` to ``array.push()`` to increase, and use ``pop()`` to decrease
the length of a storage array.
* For every named return parameter in a function's ``@dev`` documentation define a ``@return``
entry which contains the parameter's name as the first word. E.g. if you have function ``f()`` defined
like ``function f() public returns (uint value)`` and a ``@dev`` annotating it, document its return
parameters like so: ``@return value The return value.``. You can mix named and un-named return parameters
documentation so long as the notices are in the order they appear in the tuple return type.
* Choose unique identifiers for variable declarations in inline assembly that do not conflict
with declartions outside the inline assembly block.
* Add ``virtual`` to every non-interface function you intend to override. Add ``virtual``
to all functions without implementation outside interfaces. For single inheritance, add
``override`` to every overriding function. For multiple inheritance, add ``override(A, B, ..)``,
where you list all contracts that define the overridden function in the parentheses. When
multiple bases define the same function, the inheriting contract must override all conflicting functions.

View File

@ -13,7 +13,8 @@ The Contract Application Binary Interface (ABI) is the standard way to interact
from outside the blockchain and for contract-to-contract interaction. Data is encoded according to its type,
as described in this specification. The encoding is not self describing and thus requires a schema in order to decode.
We assume the interface functions of a contract are strongly typed, known at compilation time and static. We assume that all contracts will have the interface definitions of any contracts they call available at compile-time.
We assume the interface functions of a contract are strongly typed, known at compilation time and static.
We assume that all contracts will have the interface definitions of any contracts they call available at compile-time.
This specification does not address contracts whose interface is dynamic or otherwise known only at run-time.
@ -23,17 +24,24 @@ Function Selector
=================
The first four bytes of the call data for a function call specifies the function to be called. It is the
first (left, high-order in big-endian) four bytes of the Keccak-256 (SHA-3) hash of the signature of the function. The signature is defined as the canonical expression of the basic prototype without data location specifier, i.e.
the function name with the parenthesised list of parameter types. Parameter types are split by a single comma - no spaces are used.
first (left, high-order in big-endian) four bytes of the Keccak-256 (SHA-3) hash of the signature of
the function. The signature is defined as the canonical expression of the basic prototype without data
location specifier, i.e.
the function name with the parenthesised list of parameter types. Parameter types are split by a single
comma - no spaces are used.
.. note::
The return type of a function is not part of this signature. In :ref:`Solidity's function overloading <overload-function>` return types are not considered. The reason is to keep function call resolution context-independent.
The return type of a function is not part of this signature. In
:ref:`Solidity's function overloading <overload-function>` return types are not considered.
The reason is to keep function call resolution context-independent.
The :ref:`JSON description of the ABI<abi_json>` however contains both inputs and outputs.
Argument Encoding
=================
Starting from the fifth byte, the encoded arguments follow. This encoding is also used in other places, e.g. the return values and also event arguments are encoded in the same way, without the four bytes specifying the function.
Starting from the fifth byte, the encoded arguments follow. This encoding is also used in
other places, e.g. the return values and also event arguments are encoded in the same way,
without the four bytes specifying the function.
Types
=====
@ -44,17 +52,21 @@ The following elementary types exist:
- ``int<M>``: two's complement signed integer type of ``M`` bits, ``0 < M <= 256``, ``M % 8 == 0``.
- ``address``: equivalent to ``uint160``, except for the assumed interpretation and language typing. For computing the function selector, ``address`` is used.
- ``address``: equivalent to ``uint160``, except for the assumed interpretation and language typing.
For computing the function selector, ``address`` is used.
- ``uint``, ``int``: synonyms for ``uint256``, ``int256`` respectively. For computing the function selector, ``uint256`` and ``int256`` have to be used.
- ``uint``, ``int``: synonyms for ``uint256``, ``int256`` respectively. For computing the function
selector, ``uint256`` and ``int256`` have to be used.
- ``bool``: equivalent to ``uint8`` restricted to the values 0 and 1. For computing the function selector, ``bool`` is used.
- ``fixed<M>x<N>``: signed fixed-point decimal number of ``M`` bits, ``8 <= M <= 256``, ``M % 8 ==0``, and ``0 < N <= 80``, which denotes the value ``v`` as ``v / (10 ** N)``.
- ``fixed<M>x<N>``: signed fixed-point decimal number of ``M`` bits, ``8 <= M <= 256``,
``M % 8 ==0``, and ``0 < N <= 80``, which denotes the value ``v`` as ``v / (10 ** N)``.
- ``ufixed<M>x<N>``: unsigned variant of ``fixed<M>x<N>``.
- ``fixed``, ``ufixed``: synonyms for ``fixed128x18``, ``ufixed128x18`` respectively. For computing the function selector, ``fixed128x18`` and ``ufixed128x18`` have to be used.
- ``fixed``, ``ufixed``: synonyms for ``fixed128x18``, ``ufixed128x18`` respectively. For
computing the function selector, ``fixed128x18`` and ``ufixed128x18`` have to be used.
- ``bytes<M>``: binary type of ``M`` bytes, ``0 < M <= 32``.
@ -107,15 +119,20 @@ Design Criteria for the Encoding
The encoding is designed to have the following properties, which are especially useful if some arguments are nested arrays:
1. The number of reads necessary to access a value is at most the depth of the value inside the argument array structure, i.e. four reads are needed to retrieve ``a_i[k][l][r]``. In a previous version of the ABI, the number of reads scaled linearly with the total number of dynamic parameters in the worst case.
1. The number of reads necessary to access a value is at most the depth of the value
inside the argument array structure, i.e. four reads are needed to retrieve ``a_i[k][l][r]``. In a
previous version of the ABI, the number of reads scaled linearly with the total number of dynamic
parameters in the worst case.
2. The data of a variable or array element is not interleaved with other data and it is relocatable, i.e. it only uses relative "addresses".
2. The data of a variable or array element is not interleaved with other data and it is
relocatable, i.e. it only uses relative "addresses".
Formal Specification of the Encoding
====================================
We distinguish static and dynamic types. Static types are encoded in-place and dynamic types are encoded at a separately allocated location after the current block.
We distinguish static and dynamic types. Static types are encoded in-place and dynamic types are
encoded at a separately allocated location after the current block.
**Definition:** The following types are called "dynamic":
@ -178,9 +195,12 @@ on the type of ``X`` being
- ``string``:
``enc(X) = enc(enc_utf8(X))``, i.e. ``X`` is utf-8 encoded and this value is interpreted as of ``bytes`` type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters.
``enc(X) = enc(enc_utf8(X))``, i.e. ``X`` is utf-8 encoded and this value is interpreted
as of ``bytes`` type and encoded further. Note that the length used in this subsequent
encoding is the number of bytes of the utf-8 encoded string, not its number of characters.
- ``uint<M>``: ``enc(X)`` is the big-endian encoding of ``X``, padded on the higher-order (left) side with zero-bytes such that the length is 32 bytes.
- ``uint<M>``: ``enc(X)`` is the big-endian encoding of ``X``, padded on the higher-order
(left) side with zero-bytes such that the length is 32 bytes.
- ``address``: as in the ``uint160`` case
- ``int<M>``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-order (left) side with ``0xff`` for negative ``X`` and with zero bytes for positive ``X`` such that the length is 32 bytes.
- ``bool``: as in the ``uint8`` case, where ``1`` is used for ``true`` and ``0`` for ``false``
@ -222,11 +242,15 @@ Given the contract:
}
Thus for our ``Foo`` example if we wanted to call ``baz`` with the parameters ``69`` and ``true``, we would pass 68 bytes total, which can be broken down into:
Thus for our ``Foo`` example if we wanted to call ``baz`` with the parameters ``69`` and
``true``, we would pass 68 bytes total, which can be broken down into:
- ``0xcdcd77c0``: the Method ID. This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature ``baz(uint32,bool)``.
- ``0x0000000000000000000000000000000000000000000000000000000000000045``: the first parameter, a uint32 value ``69`` padded to 32 bytes
- ``0x0000000000000000000000000000000000000000000000000000000000000001``: the second parameter - boolean ``true``, padded to 32 bytes
- ``0xcdcd77c0``: the Method ID. This is derived as the first 4 bytes of the Keccak hash of
the ASCII form of the signature ``baz(uint32,bool)``.
- ``0x0000000000000000000000000000000000000000000000000000000000000045``: the first parameter,
a uint32 value ``69`` padded to 32 bytes
- ``0x0000000000000000000000000000000000000000000000000000000000000001``: the second parameter - boolean
``true``, padded to 32 bytes
In total:
@ -234,13 +258,16 @@ In total:
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
It returns a single ``bool``. If, for example, it were to return ``false``, its output would be the single byte array ``0x0000000000000000000000000000000000000000000000000000000000000000``, a single bool.
It returns a single ``bool``. If, for example, it were to return ``false``, its output would be
the single byte array ``0x0000000000000000000000000000000000000000000000000000000000000000``, a single bool.
If we wanted to call ``bar`` with the argument ``["abc", "def"]``, we would pass 68 bytes total, broken down into:
- ``0xfce353f6``: the Method ID. This is derived from the signature ``bar(bytes3[2])``.
- ``0x6162630000000000000000000000000000000000000000000000000000000000``: the first part of the first parameter, a ``bytes3`` value ``"abc"`` (left-aligned).
- ``0x6465660000000000000000000000000000000000000000000000000000000000``: the second part of the first parameter, a ``bytes3`` value ``"def"`` (left-aligned).
- ``0x6162630000000000000000000000000000000000000000000000000000000000``: the first part of the first
parameter, a ``bytes3`` value ``"abc"`` (left-aligned).
- ``0x6465660000000000000000000000000000000000000000000000000000000000``: the second part of the first
parameter, a ``bytes3`` value ``"def"`` (left-aligned).
In total:
@ -248,7 +275,8 @@ In total:
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
If we wanted to call ``sam`` with the arguments ``"dave"``, ``true`` and ``[1,2,3]``, we would pass 292 bytes total, broken down into:
If we wanted to call ``sam`` with the arguments ``"dave"``, ``true`` and ``[1,2,3]``, we would
pass 292 bytes total, broken down into:
- ``0xa5643bf2``: the Method ID. This is derived from the signature ``sam(bytes,bool,uint256[])``. Note that ``uint`` is replaced with its canonical representation ``uint256``.
- ``0x0000000000000000000000000000000000000000000000000000000000000060``: the location of the data part of the first parameter (dynamic type), measured in bytes from the start of the arguments block. In this case, ``0x60``.
@ -270,10 +298,14 @@ In total:
Use of Dynamic Types
====================
A call to a function with the signature ``f(uint,uint32[],bytes10,bytes)`` with values ``(0x123, [0x456, 0x789], "1234567890", "Hello, world!")`` is encoded in the following way:
A call to a function with the signature ``f(uint,uint32[],bytes10,bytes)`` with values
``(0x123, [0x456, 0x789], "1234567890", "Hello, world!")`` is encoded in the following way:
We take the first four bytes of ``sha3("f(uint256,uint32[],bytes10,bytes)")``, i.e. ``0x8be65246``.
Then we encode the head parts of all four arguments. For the static types ``uint256`` and ``bytes10``, these are directly the values we want to pass, whereas for the dynamic types ``uint32[]`` and ``bytes``, we use the offset in bytes to the start of their data area, measured from the start of the value encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are:
Then we encode the head parts of all four arguments. For the static types ``uint256`` and ``bytes10``,
these are directly the values we want to pass, whereas for the dynamic types ``uint32[]`` and ``bytes``,
we use the offset in bytes to the start of their data area, measured from the start of the value
encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are:
- ``0x0000000000000000000000000000000000000000000000000000000000000123`` (``0x123`` padded to 32 bytes)
- ``0x0000000000000000000000000000000000000000000000000000000000000080`` (offset to start of data part of second parameter, 4*32 bytes, exactly the size of the head part)
@ -306,7 +338,8 @@ All together, the encoding is (newline after function selector and each 32-bytes
000000000000000000000000000000000000000000000000000000000000000d
48656c6c6f2c20776f726c642100000000000000000000000000000000000000
Let us apply the same principle to encode the data for a function with a signature ``g(uint[][],string[])`` with values ``([[1, 2], [3]], ["one", "two", "three"])`` but start from the most atomic parts of the encoding:
Let us apply the same principle to encode the data for a function with a signature ``g(uint[][],string[])``
with values ``([[1, 2], [3]], ["one", "two", "three"])`` but start from the most atomic parts of the encoding:
First we encode the length and data of the first embedded dynamic array ``[1, 2]`` of the first root array ``[[1, 2], [3]]``:
@ -319,7 +352,9 @@ Then we encode the length and data of the second embedded dynamic array ``[3]``
- ``0x0000000000000000000000000000000000000000000000000000000000000001`` (number of elements in the second array, 1; the element is ``3``)
- ``0x0000000000000000000000000000000000000000000000000000000000000003`` (first element)
Then we need to find the offsets ``a`` and ``b`` for their respective dynamic arrays ``[1, 2]`` and ``[3]``. To calculate the offsets we can take a look at the encoded data of the first root array ``[[1, 2], [3]]`` enumerating each line in the encoding:
Then we need to find the offsets ``a`` and ``b`` for their respective dynamic arrays ``[1, 2]`` and ``[3]``.
To calculate the offsets we can take a look at the encoded data of the first root array ``[[1, 2], [3]]``
enumerating each line in the encoding:
.. code-block:: none
@ -331,9 +366,11 @@ Then we need to find the offsets ``a`` and ``b`` for their respective dynamic ar
5 - 0000000000000000000000000000000000000000000000000000000000000001 - count for [3]
6 - 0000000000000000000000000000000000000000000000000000000000000003 - encoding of 3
Offset ``a`` points to the start of the content of the array ``[1, 2]`` which is line 2 (64 bytes); thus ``a = 0x0000000000000000000000000000000000000000000000000000000000000040``.
Offset ``a`` points to the start of the content of the array ``[1, 2]`` which is line
2 (64 bytes); thus ``a = 0x0000000000000000000000000000000000000000000000000000000000000040``.
Offset ``b`` points to the start of the content of the array ``[3]`` which is line 5 (160 bytes); thus ``b = 0x00000000000000000000000000000000000000000000000000000000000000a0``.
Offset ``b`` points to the start of the content of the array ``[3]`` which is line 5 (160 bytes);
thus ``b = 0x00000000000000000000000000000000000000000000000000000000000000a0``.
Then we encode the embedded strings of the second root array:
@ -359,14 +396,18 @@ In parallel to the first root array, since strings are dynamic elements we need
7 - 0000000000000000000000000000000000000000000000000000000000000005 - count for "three"
8 - 7468726565000000000000000000000000000000000000000000000000000000 - encoding of "three"
Offset ``c`` points to the start of the content of the string ``"one"`` which is line 3 (96 bytes); thus ``c = 0x0000000000000000000000000000000000000000000000000000000000000060``.
Offset ``c`` points to the start of the content of the string ``"one"`` which is line 3 (96 bytes);
thus ``c = 0x0000000000000000000000000000000000000000000000000000000000000060``.
Offset ``d`` points to the start of the content of the string ``"two"`` which is line 5 (160 bytes); thus ``d = 0x00000000000000000000000000000000000000000000000000000000000000a0``.
Offset ``d`` points to the start of the content of the string ``"two"`` which is line 5 (160 bytes);
thus ``d = 0x00000000000000000000000000000000000000000000000000000000000000a0``.
Offset ``e`` points to the start of the content of the string ``"three"`` which is line 7 (224 bytes); thus ``e = 0x00000000000000000000000000000000000000000000000000000000000000e0``.
Offset ``e`` points to the start of the content of the string ``"three"`` which is line 7 (224 bytes);
thus ``e = 0x00000000000000000000000000000000000000000000000000000000000000e0``.
Note that the encodings of the embedded elements of the root arrays are not dependent on each other and have the same encodings for a function with a signature ``g(string[],uint[][])``.
Note that the encodings of the embedded elements of the root arrays are not dependent on each other
and have the same encodings for a function with a signature ``g(string[],uint[][])``.
Then we encode the length of the first root array:
@ -376,7 +417,8 @@ Then we encode the length of the second root array:
- ``0x0000000000000000000000000000000000000000000000000000000000000003`` (number of strings in the second root array, 3; the strings themselves are ``"one"``, ``"two"`` and ``"three"``)
Finally we find the offsets ``f`` and ``g`` for their respective root dynamic arrays ``[[1, 2], [3]]`` and ``["one", "two", "three"]``, and assemble parts in the correct order:
Finally we find the offsets ``f`` and ``g`` for their respective root dynamic arrays ``[[1, 2], [3]]`` and
``["one", "two", "three"]``, and assemble parts in the correct order:
.. code-block:: none
@ -402,25 +444,36 @@ Finally we find the offsets ``f`` and ``g`` for their respective root dynamic ar
18 - 0000000000000000000000000000000000000000000000000000000000000005 - count for "three"
19 - 7468726565000000000000000000000000000000000000000000000000000000 - encoding of "three"
Offset ``f`` points to the start of the content of the array ``[[1, 2], [3]]`` which is line 2 (64 bytes); thus ``f = 0x0000000000000000000000000000000000000000000000000000000000000040``.
Offset ``f`` points to the start of the content of the array ``[[1, 2], [3]]`` which is line 2 (64 bytes);
thus ``f = 0x0000000000000000000000000000000000000000000000000000000000000040``.
Offset ``g`` points to the start of the content of the array ``["one", "two", "three"]`` which is line 10 (320 bytes); thus ``g = 0x0000000000000000000000000000000000000000000000000000000000000140``.
Offset ``g`` points to the start of the content of the array ``["one", "two", "three"]`` which is line 10 (320 bytes);
thus ``g = 0x0000000000000000000000000000000000000000000000000000000000000140``.
.. _abi_events:
Events
======
Events are an abstraction of the Ethereum logging/event-watching protocol. Log entries provide the contract's address, a series of up to four topics and some arbitrary length binary data. Events leverage the existing function ABI in order to interpret this (together with an interface spec) as a properly typed structure.
Events are an abstraction of the Ethereum logging/event-watching protocol. Log entries provide the contract's
address, a series of up to four topics and some arbitrary length binary data. Events leverage the existing function
ABI in order to interpret this (together with an interface spec) as a properly typed structure.
Given an event name and series of event parameters, we split them into two sub-series: those which are indexed and those which are not. Those which are indexed, which may number up to 3, are used alongside the Keccak hash of the event signature to form the topics of the log entry. Those which are not indexed form the byte array of the event.
Given an event name and series of event parameters, we split them into two sub-series: those which are indexed and
those which are not. Those which are indexed, which may number up to 3, are used alongside the Keccak hash of the
event signature to form the topics of the log entry. Those which are not indexed form the byte array of the event.
In effect, a log entry using this ABI is described as:
- ``address``: the address of the contract (intrinsically provided by Ethereum);
- ``topics[0]``: ``keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")`` (``canonical_type_of`` is a function that simply returns the canonical type of a given argument, e.g. for ``uint indexed foo``, it would return ``uint256``). If the event is declared as ``anonymous`` the ``topics[0]`` is not generated;
- ``topics[n]``: ``abi_encode(EVENT_INDEXED_ARGS[n - 1])`` (``EVENT_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are indexed);
- ``data``: ABI encoding of ``EVENT_NON_INDEXED_ARGS`` (``EVENT_NON_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are not indexed, ``abi_encode`` is the ABI encoding function used for returning a series of typed values from a function, as described above).
- ``topics[0]``: ``keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")`` (``canonical_type_of``
is a function that simply returns the canonical type of a given argument, e.g. for ``uint indexed foo``, it would
return ``uint256``). If the event is declared as ``anonymous`` the ``topics[0]`` is not generated;
- ``topics[n]``: ``abi_encode(EVENT_INDEXED_ARGS[n - 1])`` (``EVENT_INDEXED_ARGS`` is the series of ``EVENT_ARGS``
that are indexed);
- ``data``: ABI encoding of ``EVENT_NON_INDEXED_ARGS`` (``EVENT_NON_INDEXED_ARGS`` is the series of ``EVENT_ARGS``
that are not indexed, ``abi_encode`` is the ABI encoding function used for returning a series of typed values
from a function, as described above).
For all types of length at most 32 bytes, the ``EVENT_INDEXED_ARGS`` array contains
the value directly, padded or sign-extended (for signed integers) to 32 bytes, just as for regular ABI encoding.
@ -444,8 +497,8 @@ JSON
The JSON format for a contract's interface is given by an array of function and/or event descriptions.
A function description is a JSON object with the fields:
- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function <fallback-function>`).
- ``name``: the name of the function.
- ``type``: ``"function"``, ``"constructor"``, ``"receive"`` (the :ref:`"receive Ether" function <receive-ether-function>`) or ``"fallback"`` (the :ref:`"default" function <fallback-function>`);
- ``name``: the name of the function;
- ``inputs``: an array of objects, each of which contains:
* ``name``: the name of the parameter.
@ -453,17 +506,12 @@ A function description is a JSON object with the fields:
* ``components``: used for tuple types (more below).
- ``outputs``: an array of objects similar to ``inputs``.
- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain state <view-functions>`), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether).
- ``payable``: ``true`` if function accepts Ether, ``false`` otherwise.
- ``constant``: ``true`` if function is either ``pure`` or ``view``, ``false`` otherwise.
``type`` can be omitted, defaulting to ``"function"``, likewise ``payable`` and ``constant`` can be omitted, both defaulting to ``false``.
- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read
blockchain state <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain
state <view-functions>`), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether).
Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either.
.. warning::
The fields ``constant`` and ``payable`` are deprecated and will be removed in the future. Instead, the ``stateMutability`` field can be used to determine the same properties.
.. note::
Sending non-zero Ether to non-payable function will revert the transaction.
@ -539,8 +587,8 @@ As an example, the code
contract Test {
struct S { uint a; uint[] b; T[] c; }
struct T { uint x; uint y; }
function f(S memory s, T memory t, uint a) public;
function g() public returns (S memory s, T memory t, uint a);
function f(S memory s, T memory t, uint a) public {}
function g() public returns (S memory s, T memory t, uint a) {}
}
would result in the JSON:

View File

@ -6,7 +6,8 @@ Solidity Assembly
Solidity defines an assembly language that you can use without Solidity and also
as "inline assembly" inside Solidity source code. This guide starts with describing
how to use inline assembly, how it differs from standalone assembly, and
how to use inline assembly, how it differs from standalone assembly
(sometimes also referred to by its proper name "Yul"), and
specifies assembly itself.
.. _inline-assembly:
@ -22,6 +23,10 @@ As the EVM is a stack machine, it is often hard to address the correct stack slo
and provide arguments to opcodes at the correct point on the stack. Solidity's inline
assembly helps you do this, and with other issues that arise when writing manual assembly.
For inline assembly, the stack is actually not visible at all, but if you look
closer, there is always a very direct translation from inline assembly to
the stack based EVM opcode stream.
Inline assembly has the following features:
* functional-style opcodes: ``mul(1, add(2, 3))``
@ -48,32 +53,22 @@ these curly braces, you can use the following (see the later sections for more d
- literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters)
- opcodes in functional style, e.g. ``add(1, mload(0))``
- variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of empty (0) is assigned)
- variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of 0 is assigned)
- identifiers (assembly-local variables and externals if used as inline assembly), e.g. ``add(3, x)``, ``sstore(x_slot, 2)``
- assignments, e.g. ``x := add(y, 3)``
- blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }``
The following features are only available for standalone assembly:
- direct stack control via ``dup1``, ``swap1``, ...
- direct stack assignments (in "instruction style"), e.g. ``3 =: x``
- labels, e.g. ``name:``
- jump opcodes
.. note::
Standalone assembly is supported for backwards compatibility but is not documented
here anymore.
At the end of the ``assembly { ... }`` block, the stack must be balanced,
unless you require it otherwise. If it is not balanced, the compiler generates
a warning.
Inline assembly manages local variables and control-flow. Because of that,
opcodes that interfere with these features are not available. This includes
the ``dup`` and ``swap`` instructions as well as ``jump`` instructions and labels.
Example
-------
The following example provides library code to access the code of another contract and
load it into a ``bytes`` variable. This is not possible with "plain Solidity" and the
idea is that assembly libraries will be used to enhance the Solidity language.
idea is that reusable assembly libraries can enhance the Solidity language
without a compiler change.
.. code::
@ -157,26 +152,23 @@ Opcodes
-------
This document does not want to be a full description of the Ethereum virtual machine, but the
following list can be used as a reference of its opcodes.
following list can be used as a quick reference of its opcodes.
If an opcode takes arguments (always from the top of the stack), they are given in parentheses.
Note that the order of arguments can be seen to be reversed in non-functional style (explained below).
Opcodes marked with ``-`` do not push an item onto the stack (do not return a result),
those marked with ``*`` are special and all others push exactly one item onto the stack (their "return value").
If an opcode takes arguments, they are given in parentheses.
Opcodes marked with ``-`` do not return a result,
those marked with ``*`` are special in a certain way and all others return exactly one value.
Opcodes marked with ``F``, ``H``, ``B``, ``C`` or ``I`` are present since Frontier, Homestead,
Byzantium, Constantinople or Istanbul, respectively.
In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to
but not including position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``.
but not including position ``b`` and ``storage[p]`` signifies the storage contents at slot ``p``.
The opcodes ``pushi`` and ``jumpdest`` cannot be used directly.
In the grammar, opcodes are represented as pre-defined identifiers.
In the grammar, opcodes are represented as pre-defined identifiers ("built-in functions").
+-------------------------+-----+---+-----------------------------------------------------------------+
| Instruction | | | Explanation |
+=========================+=====+===+=================================================================+
| stop + `-` | F | stop execution, identical to return(0,0) |
| stop() + `-` | F | stop execution, identical to return(0, 0) |
+-------------------------+-----+---+-----------------------------------------------------------------+
| add(x, y) | | F | x + y |
+-------------------------+-----+---+-----------------------------------------------------------------+
@ -208,11 +200,11 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+-----+---+-----------------------------------------------------------------+
| iszero(x) | | F | 1 if x == 0, 0 otherwise |
+-------------------------+-----+---+-----------------------------------------------------------------+
| and(x, y) | | F | bitwise and of x and y |
| and(x, y) | | F | bitwise "and" of x and y |
+-------------------------+-----+---+-----------------------------------------------------------------+
| or(x, y) | | F | bitwise or of x and y |
| or(x, y) | | F | bitwise "or" of x and y |
+-------------------------+-----+---+-----------------------------------------------------------------+
| xor(x, y) | | F | bitwise xor of x and y |
| xor(x, y) | | F | bitwise "xor" of x and y |
+-------------------------+-----+---+-----------------------------------------------------------------+
| byte(n, x) | | F | nth byte of x, where the most significant byte is the 0th byte |
+-------------------------+-----+---+-----------------------------------------------------------------+
@ -220,7 +212,7 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+-----+---+-----------------------------------------------------------------+
| shr(x, y) | | C | logical shift right y by x bits |
+-------------------------+-----+---+-----------------------------------------------------------------+
| sar(x, y) | | C | arithmetic shift right y by x bits |
| sar(x, y) | | C | signed arithmetic shift right y by x bits |
+-------------------------+-----+---+-----------------------------------------------------------------+
| addmod(x, y, m) | | F | (x + y) % m with arbitrary precision arithmetic |
+-------------------------+-----+---+-----------------------------------------------------------------+
@ -230,17 +222,9 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+-----+---+-----------------------------------------------------------------+
| keccak256(p, n) | | F | keccak(mem[p...(p+n))) |
+-------------------------+-----+---+-----------------------------------------------------------------+
| jump(label) | `-` | F | jump to label / code position |
| pc() | | F | current position in code |
+-------------------------+-----+---+-----------------------------------------------------------------+
| jumpi(label, cond) | `-` | F | jump to label if cond is nonzero |
+-------------------------+-----+---+-----------------------------------------------------------------+
| pc | | F | current position in code |
+-------------------------+-----+---+-----------------------------------------------------------------+
| pop(x) | `-` | F | remove the element pushed by x |
+-------------------------+-----+---+-----------------------------------------------------------------+
| dup1 ... dup16 | | F | copy nth stack slot to the top (counting from top) |
+-------------------------+-----+---+-----------------------------------------------------------------+
| swap1 ... swap16 | `*` | F | swap topmost and nth stack slot below it |
| pop(x) | `-` | F | discard value x |
+-------------------------+-----+---+-----------------------------------------------------------------+
| mload(p) | | F | mem[p...(p+32)) |
+-------------------------+-----+---+-----------------------------------------------------------------+
@ -252,27 +236,27 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+-----+---+-----------------------------------------------------------------+
| sstore(p, v) | `-` | F | storage[p] := v |
+-------------------------+-----+---+-----------------------------------------------------------------+
| msize | | F | size of memory, i.e. largest accessed memory index |
| msize() | | F | size of memory, i.e. largest accessed memory index |
+-------------------------+-----+---+-----------------------------------------------------------------+
| gas | | F | gas still available to execution |
| gas() | | F | gas still available to execution |
+-------------------------+-----+---+-----------------------------------------------------------------+
| address | | F | address of the current contract / execution context |
| address() | | F | address of the current contract / execution context |
+-------------------------+-----+---+-----------------------------------------------------------------+
| balance(a) | | F | wei balance at address a |
+-------------------------+-----+---+-----------------------------------------------------------------+
| selfbalance() | | I | equivalent to balance(address()), but cheaper |
+-------------------------+-----+---+-----------------------------------------------------------------+
| caller | | F | call sender (excluding ``delegatecall``) |
| caller() | | F | call sender (excluding ``delegatecall``) |
+-------------------------+-----+---+-----------------------------------------------------------------+
| callvalue | | F | wei sent together with the current call |
| callvalue() | | F | wei sent together with the current call |
+-------------------------+-----+---+-----------------------------------------------------------------+
| calldataload(p) | | F | call data starting from position p (32 bytes) |
+-------------------------+-----+---+-----------------------------------------------------------------+
| calldatasize | | F | size of call data in bytes |
| calldatasize() | | F | size of call data in bytes |
+-------------------------+-----+---+-----------------------------------------------------------------+
| calldatacopy(t, f, s) | `-` | F | copy s bytes from calldata at position f to mem at position t |
+-------------------------+-----+---+-----------------------------------------------------------------+
| codesize | | F | size of the code of the current contract / execution context |
| codesize() | | F | size of the code of the current contract / execution context |
+-------------------------+-----+---+-----------------------------------------------------------------+
| codecopy(t, f, s) | `-` | F | copy s bytes from code at position f to mem at position t |
+-------------------------+-----+---+-----------------------------------------------------------------+
@ -280,7 +264,7 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+-----+---+-----------------------------------------------------------------+
| extcodecopy(a, t, f, s) | `-` | F | like codecopy(t, f, s) but take code at address a |
+-------------------------+-----+---+-----------------------------------------------------------------+
| returndatasize | | B | size of the last returndata |
| returndatasize() | | B | size of the last returndata |
+-------------------------+-----+---+-----------------------------------------------------------------+
| returndatacopy(t, f, s) | `-` | B | copy s bytes from returndata at position f to mem at position t |
+-------------------------+-----+---+-----------------------------------------------------------------+
@ -315,7 +299,7 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+-----+---+-----------------------------------------------------------------+
| selfdestruct(a) | `-` | F | end execution, destroy current contract and send funds to a |
+-------------------------+-----+---+-----------------------------------------------------------------+
| invalid | `-` | F | end execution with invalid instruction |
| invalid() | `-` | F | end execution with invalid instruction |
+-------------------------+-----+---+-----------------------------------------------------------------+
| log0(p, s) | `-` | F | log without topics and data mem[p...(p+s)) |
+-------------------------+-----+---+-----------------------------------------------------------------+
@ -328,23 +312,23 @@ In the grammar, opcodes are represented as pre-defined identifiers.
| log4(p, s, t1, t2, t3, | `-` | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) |
| t4) | | | |
+-------------------------+-----+---+-----------------------------------------------------------------+
| chainid | | I | ID of the executing chain (EIP 1344) |
| chainid() | | I | ID of the executing chain (EIP 1344) |
+-------------------------+-----+---+-----------------------------------------------------------------+
| origin | | F | transaction sender |
| origin() | | F | transaction sender |
+-------------------------+-----+---+-----------------------------------------------------------------+
| gasprice | | F | gas price of the transaction |
| gasprice() | | F | gas price of the transaction |
+-------------------------+-----+---+-----------------------------------------------------------------+
| blockhash(b) | | F | hash of block nr b - only for last 256 blocks excluding current |
+-------------------------+-----+---+-----------------------------------------------------------------+
| coinbase | | F | current mining beneficiary |
| coinbase() | | F | current mining beneficiary |
+-------------------------+-----+---+-----------------------------------------------------------------+
| timestamp | | F | timestamp of the current block in seconds since the epoch |
| timestamp() | | F | timestamp of the current block in seconds since the epoch |
+-------------------------+-----+---+-----------------------------------------------------------------+
| number | | F | current block number |
| number() | | F | current block number |
+-------------------------+-----+---+-----------------------------------------------------------------+
| difficulty | | F | difficulty of the current block |
| difficulty() | | F | difficulty of the current block |
+-------------------------+-----+---+-----------------------------------------------------------------+
| gaslimit | | F | block gas limit of the current block |
| gaslimit() | | F | block gas limit of the current block |
+-------------------------+-----+---+-----------------------------------------------------------------+
Literals
@ -423,12 +407,6 @@ Local Solidity variables are available for assignments, for example:
To clean signed types, you can use the ``signextend`` opcode:
``assembly { signextend(<num_bytes_of_x_minus_one>, x) }``
Labels
------
Support for labels has been removed in version 0.5.0 of Solidity.
Please use functions, loops, if or switch statements instead.
Declaring Assembly-Local Variables
----------------------------------
@ -439,6 +417,12 @@ for the variable and automatically removed again when the end of the block
is reached. You need to provide an initial value for the variable which can
be just ``0``, but it can also be a complex functional-style expression.
Since 0.6.0 the name of a declared variable may not end in ``_offset`` or ``_slot``
and it may not shadow any declaration visible in the scope of the inline assembly block
(including variable, contract and function declarations). Similarly, if the name of a declared
variable contains a dot ``.``, the prefix up to the ``.`` may not conflict with any
declaration visible in the scope of the inline assembly block.
.. code::
pragma solidity >=0.4.16 <0.7.0;
@ -530,6 +514,9 @@ the other two are blocks. If the initializing part
declares any variables, the scope of these variables is extended into the
body (including the condition and the post-iteration part).
The ``break`` and ``continue`` statements can be used to exit the loop
or skip to the post-part, respectively.
The following example computes the sum of an area in memory.
.. code::
@ -565,12 +552,16 @@ opcode.
Functions can be defined anywhere and are visible in the block they are
declared in. Inside a function, you cannot access local variables
defined outside of that function. There is no explicit ``return``
statement.
defined outside of that function.
If you call a function that returns multiple values, you have to assign
them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``.
The ``leave`` statement can be used to exit the current function. It
works like the ``return`` statement in other languages just that it does
not take a value to return, it just exits the functions and the function
will return whatever values are currently assigned to the return variable(s).
The following example implements the power function by square-and-multiply.
.. code::
@ -763,14 +754,13 @@ Grammar::
AssemblyExpression |
AssemblyLocalDefinition |
AssemblyAssignment |
AssemblyStackAssignment |
LabelDefinition |
AssemblyIf |
AssemblySwitch |
AssemblyFunctionDefinition |
AssemblyFor |
'break' |
'continue' |
'leave' |
SubAssembly
AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral
AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral
@ -780,8 +770,6 @@ Grammar::
AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression
IdentifierOrList = Identifier | '(' IdentifierList ')'
IdentifierList = Identifier ( ',' Identifier)*
AssemblyStackAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblyIf = 'if' AssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' AssemblyExpression AssemblyCase*
( 'default' AssemblyBlock )?

View File

@ -854,5 +854,9 @@
"ABIEncoderV2StorageArrayWithMultiSlotElement"
],
"released": "2019-05-28"
},
"0.6.0": {
"bugs": [],
"released": "2019-12-17"
}
}

View File

@ -82,7 +82,7 @@ This is as opposed to the more intuitive sending pattern:
Notice that, in this example, an attacker could trap the
contract into an unusable state by causing ``richest`` to be
the address of a contract that has a fallback function
the address of a contract that has a receive or fallback function
which fails (e.g. by using ``revert()`` or by just
consuming more than the 2300 gas stipend transferred to them). That way,
whenever ``transfer`` is called to deliver funds to the

View File

@ -7,9 +7,10 @@ Contracts
##########
Contracts in Solidity are similar to classes in object-oriented languages. They
contain persistent data in state variables and functions that can modify these
contain persistent data in state variables, and functions that can modify these
variables. Calling a function on a different contract (instance) will perform
an EVM function call and thus switch the context such that state variables are
an EVM function call and thus switch the context such that state variables
in the calling contract are
inaccessible. A contract and its functions need to be called for anything to happen.
There is no "cron" concept in Ethereum to call a function at a particular event automatically.

View File

@ -6,38 +6,49 @@
Abstract Contracts
******************
Contracts are marked as abstract when at least one of their functions lacks an implementation as in the following example (note that the function declaration header is terminated by ``;``)::
Contracts need to be marked as abstract when at least one of their functions is not implemented.
Contracts may be marked as abstract even though all functions are implemented.
This can be done by using the ``abstract`` keyword as shown in the following example. Note that this contract needs to be
defined as abstract, because the function ``utterance()`` was defined, but no implementation was
provided (no implementation body ``{ }`` was given).::
pragma solidity >=0.4.0 <0.7.0;
contract Feline {
function utterance() public returns (bytes32);
abstract contract Feline {
function utterance() public virtual returns (bytes32);
}
Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts::
Such abstract contracts can not be instantiated directly. This is also true, if an abstract contract itself does implement
all defined functions. The usage of an abstract contract as a base class is shown in the following example::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity ^0.6.0;
contract Feline {
function utterance() public returns (bytes32);
abstract contract Feline {
function utterance() public virtual returns (bytes32);
}
contract Cat is Feline {
function utterance() public returns (bytes32) { return "miaow"; }
function utterance() public override returns (bytes32) { return "miaow"; }
}
If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract.
If a contract inherits from an abstract contract and does not implement all non-implemented
functions by overriding, it needs to be marked as abstract as well.
Note that a function without implementation is different from a :ref:`Function Type <function_types>` even though their syntax looks very similar.
Note that a function without implementation is different from
a :ref:`Function Type <function_types>` even though their syntax looks very similar.
Example of function without implementation (a function declaration)::
function foo(address) external returns (address);
Example of a Function Type (a variable declaration, where the variable is of type ``function``)::
Example of a declaration of a variable whose type is a function type::
function(address) external returns (address) foo;
Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and
Abstract contracts decouple the definition of a contract from its
implementation providing better extensibility and self-documentation and
facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/Template_method_pattern>`_ and removing code duplication.
Abstract contracts are useful in the same way that defining methods in an interface is useful. It is a way for the designer of the abstract contract to say "any child of mine must implement this method".
Abstract contracts are useful in the same way that defining methods
in an interface is useful. It is a way for the designer of the
abstract contract to say "any child of mine must implement this method".

View File

@ -8,16 +8,17 @@ Contracts can be created "from outside" via Ethereum transactions or from within
IDEs, such as `Remix <https://remix.ethereum.org/>`_, make the creation process seamless using UI elements.
Creating contracts programmatically on Ethereum is best done via using the JavaScript API `web3.js <https://github.com/ethereum/web3.js>`_.
One way to create contracts programmatically on Ethereum is via the JavaScript API `web3.js <https://github.com/ethereum/web3.js>`_.
It has a function called `web3.eth.Contract <https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#new-contract>`_
to facilitate contract creation.
When a contract is created, its :ref:`constructor <constructor>` (a function declared with the ``constructor`` keyword) is executed once.
When a contract is created, its :ref:`constructor <constructor>` (a function declared with
the ``constructor`` keyword) is executed once.
A constructor is optional. Only one constructor is allowed, which means
overloading is not supported.
After the constructor has executed, the final code of the contract is deployed to the
After the constructor has executed, the final code of the contract is stored on the
blockchain. This code includes all public and external functions and all functions
that are reachable from there through function calls. The deployed code does not
include the constructor code or internal functions only called from the constructor.
@ -57,18 +58,20 @@ This means that cyclic creation dependencies are impossible.
// See the next section for details.
owner = msg.sender;
// We do an explicit type conversion from `address`
// We perform an explicit type conversion from `address`
// to `TokenCreator` and assume that the type of
// the calling contract is `TokenCreator`, there is
// no real way to check that.
// no real way to verify that.
// This does not create a new contract.
creator = TokenCreator(msg.sender);
name = _name;
}
function changeName(bytes32 newName) public {
// Only the creator can alter the name --
// the comparison is possible since contracts
// are explicitly convertible to addresses.
// Only the creator can alter the name.
// We compare the contract based on its
// address which can be retrieved by
// explicit conversion to address.
if (msg.sender == address(creator))
name = newName;
}
@ -94,9 +97,9 @@ This means that cyclic creation dependencies are impossible.
returns (OwnedToken tokenAddress)
{
// Create a new `Token` contract and return its address.
// From the JavaScript side, the return type is
// `address`, as this is the closest type available in
// the ABI.
// From the JavaScript side, the return type
// of this function is `address`, as this is
// the closest type available in the ABI.
return new OwnedToken(name);
}

View File

@ -13,12 +13,12 @@ Events are inheritable members of contracts. When you call them, they cause the
arguments to be stored in the transaction's log - a special data structure
in the blockchain. These logs are associated with the address of the contract,
are incorporated into the blockchain, and stay there as long as a block is
accessible (forever as of the Frontier and Homestead releases, but this might
accessible (forever as of now, but this might
change with Serenity). The Log and its event data is not accessible from within
contracts (not even from the contract that created them).
It is possible to request a simple payment verification (SPV) for logs, so if
an external entity supplies a contract with such a verification, it can check
It is possible to request a Merkle proof for logs, so if
an external entity supplies a contract with such a proof, it can check
that the log actually exists inside the blockchain. You have to supply block headers
because the contract can only see the last 256 block hashes.

View File

@ -6,9 +6,14 @@
Function Modifiers
******************
Modifiers can be used to easily change the behaviour of functions. For example,
they can automatically check a condition prior to executing the function. Modifiers are
inheritable properties of contracts and may be overridden by derived contracts.
Modifiers can be used to change the behaviour of functions in a declarative way.
For example,
you can use a modifier to automatically check a condition prior to executing the function.
Modifiers are
inheritable properties of contracts and may be overridden by derived contracts, but only
if they are marked ``virtual``. For details, please see
:ref:`Modifier Overriding <modifier-overriding>`.
::

View File

@ -11,8 +11,8 @@ Functions
Function Parameters and Return Variables
========================================
As in JavaScript, functions may take parameters as input. Unlike in JavaScript
and C, functions may also return an arbitrary number of values as output.
Functions take typed parameters as input and may, unlike in many other
languages, also return an arbitrary number of values as output.
Function Parameters
-------------------
@ -21,7 +21,7 @@ Function parameters are declared the same way as variables, and the name of
unused parameters can be omitted.
For example, if you want your contract to accept one kind of external call
with two integers, you would use something like::
with two integers, you would use something like the following::
pragma solidity >=0.4.16 <0.7.0;
@ -39,7 +39,7 @@ Function parameters can be used as any other local variable and they can also be
An :ref:`external function<external-function-calls>` cannot accept a
multi-dimensional array as an input
parameter. This functionality is possible if you enable the new
experimental ``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file.
``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file.
An :ref:`internal function<external-function-calls>` can accept a
multi-dimensional array without enabling the feature.
@ -70,7 +70,8 @@ two integers passed as function parameters, then you use something like::
The names of return variables can be omitted.
Return variables can be used as any other local variable and they
are initialized with their :ref:`default value <default-value>` and have that value unless explicitly set.
are initialized with their :ref:`default value <default-value>` and have that
value until they are (re-)assigned.
You can either explicitly assign to return variables and
then leave the function using ``return;``,
@ -96,7 +97,7 @@ return variables and then using ``return;`` to leave the function.
.. note::
You cannot return some types from non-internal functions, notably
multi-dimensional dynamic arrays and structs. If you enable the
new experimental ``ABIEncoderV2`` feature by adding ``pragma experimental
new ``ABIEncoderV2`` feature by adding ``pragma experimental
ABIEncoderV2;`` to your source file then more types are available, but
``mapping`` types are still limited to inside a single contract and you
cannot transfer them.
@ -107,7 +108,8 @@ Returning Multiple Values
-------------------------
When a function has multiple return types, the statement ``return (v0, v1, ..., vn)`` can be used to return multiple values.
The number of components must be the same as the number of return types.
The number of components must be the same as the number of return variables
and their types have to match, potentially after an :ref:`implicit conversion <types-conversion-elementary-types>`.
.. index:: ! view function, function;view
@ -120,7 +122,7 @@ Functions can be declared ``view`` in which case they promise not to modify the
.. note::
If the compiler's EVM target is Byzantium or newer (default) the opcode
``STATICCALL`` is used for ``view`` functions which enforces the state
``STATICCALL`` is used when ``view`` functions are called, which enforces the state
to stay unmodified as part of the EVM execution. For library ``view`` functions
``DELEGATECALL`` is used, because there is no combined ``DELEGATECALL`` and ``STATICCALL``.
This means library ``view`` functions do not have run-time checks that prevent state
@ -222,23 +224,25 @@ This behaviour is also in line with the ``STATICCALL`` opcode.
not do state-changing operations, but it cannot check that the contract that will be called
at runtime is actually of that type.
.. index:: ! fallback function, function;fallback
.. index:: ! receive ether function, function;receive ! receive
.. _fallback-function:
.. _receive-ether-function:
Fallback Function
=================
Receive Ether Function
======================
A contract can have exactly one unnamed function. This function cannot have
arguments, cannot return anything and has to have ``external`` visibility.
It is executed on a call to the contract if none of the other
functions match the given function identifier (or if no data was supplied at
all).
Furthermore, this function is executed whenever the contract receives plain
Ether (without data). To receive Ether and add it to the total balance of the contract, the fallback function
must be marked ``payable``. If no such function exists, the contract cannot receive
Ether through regular transactions and throws an exception.
A contract can have at most one ``receive`` function, declared using
``receive() external payable { ... }``
(without the ``function`` keyword).
This function cannot have arguments, cannot return anything and must have
``external`` visibility and ``payable`` state mutability. It is executed on a
call to the contract with empty calldata. This is the function that is executed
on plain Ether transfers (e.g. via `.send()` or `.transfer()`). If no such
function exists, but a payable :ref:`fallback function <fallback-function>`
exists, the fallback function will be called on a plain Ether transfer. If
neither a receive Ether nor a payable fallback function is present, the
contract cannot receive Ether through regular transactions and throws an
exception.
In the worst case, the fallback function can only rely on 2300 gas being
available (for example when `send` or `transfer` is used), leaving little
@ -250,36 +254,88 @@ will consume more gas than the 2300 gas stipend:
- Calling an external function which consumes a large amount of gas
- Sending Ether
Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it.
.. warning::
The fallback function is also executed if the caller meant to call
a function that is not available. If you want to implement the fallback
function only to receive ether, you should add a check
like ``require(msg.data.length == 0)`` to prevent invalid calls.
.. warning::
Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``)
but do not define a fallback function
but do not define a receive Ether function or a payable fallback function
throw an exception, sending back the Ether (this was different
before Solidity v0.4.0). So if you want your contract to receive Ether,
you have to implement a payable fallback function.
you have to implement a receive Ether function (using payable fallback functions for receiving Ether is
not recommended, since it would not fail on interface confusions).
.. warning::
A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`)
A contract without a receive Ether function can receive Ether as a
recipient of a `coinbase transaction` (aka `miner block reward`)
or as a destination of a ``selfdestruct``.
A contract cannot react to such Ether transfers and thus also
cannot reject them. This is a design choice of the EVM and
Solidity cannot work around it.
It also means that ``address(this).balance`` can be higher
than the sum of some manual accounting implemented in a
contract (i.e. having a counter updated in the receive Ether function).
Below you can see an example of a Sink contract that uses function ``receive``.
::
pragma solidity ^0.6.0;
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
event Received(address, uint);
receive() external payable {
emit Received(msg.sender, msg.value);
}
}
.. index:: ! fallback function, function;fallback
.. _fallback-function:
Fallback Function
=================
A contract can have at most one ``fallback`` function, declared using ``fallback () external [payable]``
(without the ``function`` keyword).
This function cannot have arguments, cannot return anything and must have ``external`` visibility.
It is executed on a call to the contract if none of the other
functions match the given function signature, or if no data was supplied at
all and there is no :ref:`receive Ether function <receive-ether-function>`.
The fallback function always receives data, but in order to also receive Ether
it must be marked ``payable``.
In the worst case, if a payable fallback function is also used in
place of a receive function, it can only rely on 2300 gas being
available (see :ref:`receive Ether function <receive-ether-function>`
for a brief description of the implications of this).
Like any function, the fallback function can execute complex
operations as long as there is enough gas passed on to it.
.. warning::
A ``payable`` fallback function is also executed for
plain Ether transfers, if no :ref:`receive Ether function <receive-ether-function>`
is present. It is recommended to always define a receive Ether
function as well, if you define a payable fallback function
to distinguish Ether transfers from interface confusions.
.. note::
Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve
any payload supplied with the call.
After having checked the first four bytes of ``msg.data``,
you can use ``abi.decode`` together with the array slice syntax to
decode ABI-encoded data:
``(c, d) = abi.decode(msg.data[4:], (uint256, uint256));``
Note that this should only be used as a last resort and
proper functions should be used instead.
A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it.
It also means that ``address(this).balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function).
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity ^0.6.0;
contract Test {
// This function is called for all messages sent to
@ -287,15 +343,23 @@ Like any function, the fallback function can execute complex operations as long
// Sending Ether to this contract will cause an exception,
// because the fallback function does not have the `payable`
// modifier.
function() external { x = 1; }
fallback() external { x = 1; }
uint x;
}
contract TestPayable {
// This function is called for all messages sent to
// this contract, except plain Ether transfers
// (there is no other function except the receive function).
// Any call with non-empty calldata to this contract will execute
// the fallback function (even if Ether is sent along with the call).
fallback() external payable { x = 1; y = msg.value; }
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
function() external payable { }
// This function is called for plain Ether transfers, i.e.
// for every call with empty calldata.
receive() external payable { x = 2; y = msg.value; }
uint x;
uint y;
}
contract Caller {
@ -305,14 +369,27 @@ Like any function, the fallback function can execute complex operations as long
// results in test.x becoming == 1.
// address(test) will not allow to call ``send`` directly, since ``test`` has no payable
// fallback function. It has to be converted to the ``address payable`` type via an
// intermediate conversion to ``uint160`` to even allow calling ``send`` on it.
address payable testPayable = address(uint160(address(test)));
// fallback function.
// It has to be converted to the ``address payable`` type to even allow calling ``send`` on it.
address payable testPayable = payable(address(test));
// If someone sends ether to that contract,
// If someone sends Ether to that contract,
// the transfer will fail, i.e. this returns false here.
return testPayable.send(2 ether);
}
function callTestPayable(TestPayable test) public returns (bool) {
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1 and test.y becoming 0.
(success,) = address(test).call.value(1)(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1 and test.y becoming 1.
// If someone sends Ether to that contract, the receive function in TestPayable will be called.
require(address(test).send(2 ether));
// results in test.x becoming == 2 and test.y becoming 2 ether.
}
}
.. index:: ! overload

View File

@ -6,9 +6,18 @@ Inheritance
Solidity supports multiple inheritance including polymorphism.
All function calls are virtual, which means that the most derived function
is called, except when the contract name is explicitly given or the
``super`` keyword is used.
Polymorphism means that a function call (internal and external)
always executes the function of the same name (and parameter types)
in the most derived contract in the inheritance hierarchy.
This has to be explicitly enabled on each function in the
hierarchy using the ``virtual`` and ``override`` keywords.
See :ref:`Function Overriding <function-overriding>` for more details.
It is possible to call functions further up in the inheritance
hierarchy internally by explicitly specifying the contract
using ``ContractName.functionName()`` or using ``super.functionName()``
if you want to call the function one level higher up in
the flattened inheritance hierarchy (see below).
When a contract inherits from other contracts, only a single
contract is created on the blockchain, and the code from all the base contracts
@ -16,6 +25,10 @@ is compiled into the created contract. This means that all internal calls
to functions of base contracts also just use internal function calls
(``super.f(..)`` will use JUMP and not a message call).
State variable shadowing is considered as an error. A derived contract can
only declare a state variable ``x``, if there is no visible state variable
with the same name in any of its bases.
The general inheritance system is very similar to
`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_,
especially concerning multiple inheritance, but there are also
@ -25,7 +38,7 @@ Details are given in the following example.
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity ^0.6.0;
contract Owned {
@ -39,7 +52,9 @@ Details are given in the following example.
// internal functions and state variables. These cannot be
// accessed externally via `this`, though.
contract Mortal is Owned {
function kill() public {
// The keyword `virtual` means that the function can change
// its behaviour in derived classes ("overriding").
function kill() virtual public {
if (msg.sender == owner) selfdestruct(owner);
}
}
@ -49,14 +64,14 @@ Details are given in the following example.
// interface known to the compiler. Note the function
// without body. If a contract does not implement all
// functions it can only be used as an interface.
contract Config {
function lookup(uint id) public returns (address adr);
abstract contract Config {
function lookup(uint id) public virtual returns (address adr);
}
contract NameReg {
function register(bytes32 name) public;
function unregister() public;
abstract contract NameReg {
function register(bytes32 name) public virtual;
function unregister() public virtual;
}
@ -74,7 +89,10 @@ Details are given in the following example.
// types of output parameters, that causes an error.
// Both local and message-based function calls take these overrides
// into account.
function kill() public {
// If you want the function to override, you need to use the
// `override` keyword. You need to specify the `virtual` keyword again
// if you want this function to be overridden again.
function kill() public virtual override {
if (msg.sender == owner) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).unregister();
@ -94,6 +112,10 @@ Details are given in the following example.
if (msg.sender == owner) info = newInfo;
}
// Here, we only specify `override` and not `virtual`.
// This means that contracts deriving from `PriceFeed`
// cannot change the behaviour of `kill` anymore.
function kill() public override(Mortal, Named) { Named.kill(); }
function get() public view returns(uint r) { return info; }
uint info;
@ -103,7 +125,7 @@ Note that above, we call ``mortal.kill()`` to "forward" the
destruction request. The way this is done is problematic, as
seen in the following example::
pragma solidity >=0.4.22 <0.7.0;
pragma solidity ^0.6.0;
contract owned {
constructor() public { owner = msg.sender; }
@ -111,26 +133,26 @@ seen in the following example::
}
contract mortal is owned {
function kill() public {
function kill() public virtual {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() public { /* do cleanup 1 */ mortal.kill(); }
function kill() public virtual override { /* do cleanup 1 */ mortal.kill(); }
}
contract Base2 is mortal {
function kill() public { /* do cleanup 2 */ mortal.kill(); }
function kill() public virtual override { /* do cleanup 2 */ mortal.kill(); }
}
contract Final is Base1, Base2 {
function kill() public override(Base1, Base2) { Base2.kill(); }
}
A call to ``Final.kill()`` will call ``Base2.kill`` as the most
derived override, but this function will bypass
``Base1.kill``, basically because it does not even know about
``Base1``. The way around this is to use ``super``::
A call to ``Final.kill()`` will call ``Base2.kill`` because we specify it
explicitly in the final override, but this function will bypass
``Base1.kill``. The way around this is to use ``super``::
pragma solidity >=0.4.22 <0.7.0;
@ -140,21 +162,22 @@ derived override, but this function will bypass
}
contract mortal is owned {
function kill() public {
function kill() virtual public {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is mortal {
function kill() public { /* do cleanup 1 */ super.kill(); }
function kill() public virtual override { /* do cleanup 1 */ super.kill(); }
}
contract Base2 is mortal {
function kill() public { /* do cleanup 2 */ super.kill(); }
function kill() public virtual override { /* do cleanup 2 */ super.kill(); }
}
contract Final is Base1, Base2 {
function kill() public override(Base1, Base2) { super.kill(); }
}
If ``Base2`` calls a function of ``super``, it does not simply
@ -168,6 +191,176 @@ not known in the context of the class where it is used,
although its type is known. This is similar for ordinary
virtual method lookup.
.. _function-overriding:
.. index:: ! overriding;function
Function Overriding
===================
Base functions can be overridden by inheriting contracts to change their
behavior if they are marked as ``virtual``. The overriding function must then
use the ``override`` keyword in the function header as shown in this example:
::
pragma solidity >=0.5.0 <0.7.0;
contract Base
{
function foo() virtual public {}
}
contract Middle is Base {}
contract Inherited is Middle
{
function foo() public override {}
}
For multiple inheritance, the most derived base contracts that define the same
function must be specified explicitly after the ``override`` keyword.
In other words, you have to specify all base contracts that define the same function
and have not yet been overridden by another base contract (on some path through the inheritance graph).
Additionally, if a contract inherits the same function from multiple (unrelated)
bases, it has to explicitly override it:
::
pragma solidity >=0.5.0 <0.7.0;
contract Base1
{
function foo() virtual public {}
}
contract Base2
{
function foo() virtual public {}
}
contract Inherited is Base1, Base2
{
// Derives from multiple bases defining foo(), so we must explicitly
// override it
function foo() public override(Base1, Base2) {}
}
An explicit override specifier is not required if
the function is defined in a common base contract
or if there is a unique function in a common base contract
that already overrides all other functions.
::
pragma solidity >=0.5.0 <0.7.0;
contract A { function f() public pure{} }
contract B is A {}
contract C is A {}
// No explicit override required
contract D is B, C {}
More formally, it is not required to override a function (directly or
indirectly) inherited from multiple bases if there is a base contract
that is part of all override paths for the signature, and (1) that
base implements the function and no paths from the current contract
to the base mentions a function with that signature or (2) that base
does not implement the function and there is at most one mention of
the function in all paths from the current contract to that base.
In this sense, an override path for a signature is a path through
the inheritance graph that starts at the contract under consideration
and ends at a contract mentioning a function with that signature
that does not override.
If you do not mark a function that overrides as ``virtual``, derived
contracts can no longer change the behaviour of that function.
.. note::
Functions with the ``private`` visibility cannot be ``virtual``.
.. note::
Functions without implementation have to be marked ``virtual``
outside of interfaces. In interfaces, all functions are
automatically considered ``virtual``.
Public state variables can override external functions if the
parameter and return types of the function matches the getter function
of the variable:
::
pragma solidity >=0.5.0 <0.7.0;
contract A
{
function f() external pure virtual returns(uint) { return 5; }
}
contract B is A
{
uint public override f;
}
.. note::
While public state variables can override external functions, they themselves cannot
be overridden.
.. _modifier-overriding:
.. index:: ! overriding;modifier
Modifier Overriding
===================
Function modifiers can override each other. This works in the same way as
`function overriding <function-overriding>`_ (except that there is no overloading for modifiers). The
``virtual`` keyword must be used on the overridden modifier
and the ``override`` keyword must be used in the overriding modifier:
::
pragma solidity >=0.5.0 <0.7.0;
contract Base
{
modifier foo() virtual {_;}
}
contract Inherited is Base
{
modifier foo() override {_;}
}
In case of multiple inheritance, all direct base contracts must be specified
explicitly:
::
pragma solidity >=0.5.0 <0.7.0;
contract Base1
{
modifier foo() virtual {_;}
}
contract Base2
{
modifier foo() virtual {_;}
}
contract Inherited is Base1, Base2
{
modifier foo() override(Base1, Base2) {_;}
}
.. index:: ! constructor
.. _constructor:
@ -297,6 +490,10 @@ The reason for this is that ``C`` requests ``X`` to override ``A``
requests to override ``X``, which is a contradiction that
cannot be resolved.
Due to the fact that you have to explicitly override a function
that is inherited from multiple bases without a unique override,
C3 linearization is not too important in practice.
One area where inheritance linearization is especially important and perhaps not as clear is when there are multiple constructors in the inheritance hierarchy. The constructors will always be executed in the linearized order, regardless of the order in which their arguments are provided in the inheriting contract's constructor. For example:
::
@ -339,6 +536,9 @@ One area where inheritance linearization is especially important and perhaps not
Inheriting Different Kinds of Members of the Same Name
======================================================
When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error.
This error is produced also by an event and a modifier of the same name, and a function and an event of the same name.
As an exception, a state variable getter can override a public function.
It is an error when any of the following pairs in a contract have the same name due to inheritance:
- a function and a modifier
- a function and an event
- an event and a modifier
As an exception, a state variable getter can override an external function.

View File

@ -32,6 +32,11 @@ Interfaces are denoted by their own keyword:
Contracts can inherit interfaces as they would inherit other contracts.
All functions declared in interfaces are implicitly ``virtual``, which means that
they can be overridden. This does not automatically mean that an overriding function
can be overridden again - this is only possible if the overriding
function is marked ``virtual``.
Types defined inside interfaces and other contract-like structures
can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``.

View File

@ -36,12 +36,12 @@ if the library were a base contract. Of course, calls to internal functions
use the internal calling convention, which means that all internal types
can be passed and types :ref:`stored in memory <data-location>` will be passed by reference and not copied.
To realize this in the EVM, code of internal library functions
and all functions called from therein will at compile time be pulled into the calling
and all functions called from therein will at compile time be included in the calling
contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``.
.. index:: using for, set
The following example illustrates how to use libraries (but manual method
The following example illustrates how to use libraries (but using a manual method,
be sure to check out :ref:`using for <using-for>` for a
more advanced example to implement a set).
@ -50,11 +50,11 @@ more advanced example to implement a set).
pragma solidity >=0.4.22 <0.7.0;
library Set {
// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data { mapping(uint => bool) flags; }
library Set {
// Note that the first parameter is of type "storage
// reference" and thus only its storage address and not
// its contents is passed as part of the call. This is a
@ -92,7 +92,7 @@ more advanced example to implement a set).
contract C {
Set.Data knownValues;
Data knownValues;
function register(uint value) public {
// The library functions can be called without a
@ -125,11 +125,11 @@ custom types without the overhead of external function calls:
pragma solidity >=0.4.16 <0.7.0;
library BigInt {
struct bigint {
uint[] limbs;
}
library BigInt {
function fromUint(uint x) internal pure returns (bigint memory r) {
r.limbs = new uint[](1);
r.limbs[0] = x;
@ -168,12 +168,12 @@ custom types without the overhead of external function calls:
}
contract C {
using BigInt for BigInt.bigint;
using BigInt for bigint;
function f() public pure {
BigInt.bigint memory x = BigInt.fromUint(7);
BigInt.bigint memory y = BigInt.fromUint(uint(-1));
BigInt.bigint memory z = x.add(y);
bigint memory x = BigInt.fromUint(7);
bigint memory y = BigInt.fromUint(uint(-1));
bigint memory z = x.add(y);
assert(z.limb(1) > 0);
}
}
@ -194,17 +194,18 @@ encoding of the address of the library contract.
.. note::
Manually linking libraries on the generated bytecode is discouraged, because
it is restricted to 36 characters.
in this way, the library name is restricted to 36 characters.
You should ask the compiler to link the libraries at the time
a contract is compiled by either using
the ``--libraries`` option of ``solc`` or the ``libraries`` key if you use
the standard-JSON interface to the compiler.
Restrictions for libraries in comparison to contracts:
In comparison to contracts, libraries are restricted in the following ways:
- No state variables
- Cannot inherit nor be inherited
- Cannot receive Ether
- they cannot have state variables
- they cannot inherit nor be inherited
- they cannot receive Ether
- they cannot be destroyed
(These might be lifted at a later point.)
@ -275,3 +276,7 @@ this causes the deploy time address to be the first
constant to be pushed onto the stack and the dispatcher
code compares the current address against this constant
for any non-view and non-pure function.
This means that the actual code stored on chain for a library
is different from the code reported by the compiler as
``deployedBytecode``.

View File

@ -7,7 +7,8 @@ Using For
*********
The directive ``using A for B;`` can be used to attach library
functions (from the library ``A``) to any type (``B``).
functions (from the library ``A``) to any type (``B``)
in the context of a contract.
These functions will receive the object they are called on
as their first parameter (like the ``self`` variable in Python).
@ -25,9 +26,6 @@ contract, including within all of its functions, and has no effect
outside of the contract in which it is used. The directive
may only be used inside a contract, not inside any of its functions.
By including a library, its data types including library functions are
available without having to add further code.
Let us rewrite the set example from the
:ref:`libraries` in this way::
@ -35,9 +33,9 @@ Let us rewrite the set example from the
// This is the same code as before, just without comments
library Set {
struct Data { mapping(uint => bool) flags; }
library Set {
function insert(Data storage self, uint value)
public
returns (bool)
@ -69,11 +67,11 @@ Let us rewrite the set example from the
contract C {
using Set for Set.Data; // this is the crucial change
Set.Data knownValues;
using Set for Data; // this is the crucial change
Data knownValues;
function register(uint value) public {
// Here, all variables of type Set.Data have
// Here, all variables of type Data have
// corresponding member functions.
// The following function call is identical to
// `Set.insert(knownValues, value)`
@ -115,7 +113,8 @@ It is also possible to extend elementary types in that way::
}
}
Note that all library calls are actual EVM function calls. This means that
Note that all external library calls are actual EVM function calls. This means that
if you pass memory or value types, a copy will be performed, even of the
``self`` variable. The only situation where no copy will be performed
is when storage reference variables are used.
is when storage reference variables are used or when internal library
functions are called.

View File

@ -6,10 +6,10 @@
Visibility and Getters
**********************
Since Solidity knows two kinds of function calls (internal
Solidity knows two kinds of function calls: internal
ones that do not create an actual EVM call (also called
a "message call") and external
ones that do), there are four types of visibilities for
ones that do. Because of that, there are four types of visibility for
functions and state variables.
Functions have to be specified as being ``external``,
@ -22,7 +22,8 @@ For state variables, ``external`` is not possible.
via transactions. An external function ``f`` cannot be called
internally (i.e. ``f()`` does not work, but ``this.f()`` works).
External functions are sometimes more efficient when
they receive large arrays of data.
they receive large arrays of data, because the data
is not copied from calldata to memory.
``public``:
Public functions are part of the contract interface

View File

@ -15,6 +15,10 @@ Most of the control structures known from curly-braces languages are available i
There is: ``if``, ``else``, ``while``, ``do``, ``for``, ``break``, ``continue``, ``return``, with
the usual semantics known from C or JavaScript.
Solidity also supports exception handling in the form of ``try``/``catch``-statements,
but only for :ref:`external function calls <external-function-calls>` and
contract creation calls.
Parentheses can *not* be omitted for conditionals, but curly brances can be omitted
around single-statement bodies.
@ -47,10 +51,10 @@ this nonsensical example::
These function calls are translated into simple jumps inside the EVM. This has
the effect that the current memory is not cleared, i.e. passing memory references
to internally-called functions is very efficient. Only functions of the same
contract can be called internally.
contract instance can be called internally.
You should still avoid excessive recursion, as every internal function call
uses up at least one stack slot and there are at most 1024 slots available.
uses up at least one stack slot and there are only 1024 slots available.
.. _external-function-calls:
@ -70,7 +74,10 @@ all function arguments have to be copied to memory.
A function call from one contract to another does not create its own transaction,
it is a message call as part of the overall transaction.
When calling functions of other contracts, you can specify the amount of Wei or gas sent with the call with the special options ``.value()`` and ``.gas()``, respectively. Any Wei you send to the contract is added to the total balance of the contract:
When calling functions of other contracts, you can specify the amount of Wei or
gas sent with the call with the special options ``.value()`` and ``.gas()``,
respectively. Any Wei you send to the contract is added to the total balance
of the contract:
::
@ -90,7 +97,10 @@ You need to use the modifier ``payable`` with the ``info`` function because
otherwise, the ``.value()`` option would not be available.
.. warning::
Be careful that ``feed.info.value(10).gas(800)`` only locally sets the ``value`` and amount of ``gas`` sent with the function call, and the parentheses at the end perform the actual call. So in this case, the function is not called and the ``value`` and ``gas`` settings are lost.
Be careful that ``feed.info.value(10).gas(800)`` only locally sets the
``value`` and amount of ``gas`` sent with the function call, and the
parentheses at the end perform the actual call. So in this case, the
function is not called and the ``value`` and ``gas`` settings are lost.
Function calls cause exceptions if the called contract does not exist (in the
sense that the account does not contain code) or if the called contract itself
@ -216,8 +226,11 @@ Assignment
Destructuring Assignments and Returning Multiple Values
-------------------------------------------------------
Solidity internally allows tuple types, i.e. a list of objects of potentially different types whose number is a constant at compile-time. Those tuples can be used to return multiple values at the same time.
These can then either be assigned to newly declared variables or to pre-existing variables (or LValues in general).
Solidity internally allows tuple types, i.e. a list of objects
of potentially different types whose number is a constant at
compile-time. Those tuples can be used to return multiple values at the same time.
These can then either be assigned to newly declared variables
or to pre-existing variables (or LValues in general).
Tuples are not proper types in Solidity, they can only be used to form syntactic
groupings of expressions.
@ -227,7 +240,7 @@ groupings of expressions.
pragma solidity >0.4.23 <0.7.0;
contract C {
uint[] data;
uint index;
function f() public pure returns (uint, bool, uint) {
return (7, true, 2);
@ -240,7 +253,7 @@ groupings of expressions.
// Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x);
// Components can be left out (also for variable declarations).
(data.length, , ) = f(); // Sets the length to 7
(index, , ) = f(); // Sets the index to 7
}
}
@ -260,8 +273,18 @@ i.e. the following is not valid: ``(x, uint y) = (1, 2);``
Complications for Arrays and Structs
------------------------------------
The semantics of assignments are a bit more complicated for non-value types like arrays and structs.
Assigning *to* a state variable always creates an independent copy. On the other hand, assigning to a local variable creates an independent copy only for elementary types, i.e. static types that fit into 32 bytes. If structs or arrays (including ``bytes`` and ``string``) are assigned from a state variable to a local variable, the local variable holds a reference to the original state variable. A second assignment to the local variable does not modify the state but only changes the reference. Assignments to members (or elements) of the local variable *do* change the state.
The semantics of assignments are a bit more complicated for
non-value types like arrays and structs.
Assigning *to* a state variable always creates an independent
copy. On the other hand, assigning to a local variable creates
an independent copy only for elementary types, i.e. static
types that fit into 32 bytes. If structs or arrays (including
``bytes`` and ``string``) are assigned from a state variable
to a local variable, the local variable holds a reference to
the original state variable. A second assignment to the local
variable does not modify the state but only changes the
reference. Assignments to members (or elements) of the local
variable *do* change the state.
In the example below the call to ``g(x)`` has no effect on ``x`` because it creates
an independent copy of the storage value in memory. However, ``h(x)`` successfully modifies ``x``
@ -295,17 +318,29 @@ because only a reference and not a copy is passed.
Scoping and Declarations
========================
A variable which is declared will have an initial default value whose byte-representation is all zeros.
The "default values" of variables are the typical "zero-state" of whatever the type is. For example, the default value for a ``bool``
is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For statically-sized arrays and ``bytes1`` to ``bytes32``, each individual
element will be initialized to the default value corresponding to its type. For dynamically-sized arrays, ``bytes``
and ``string``, the default value is an empty array or string. For the ``enum`` type, the default value is its first member.
A variable which is declared will have an initial default
value whose byte-representation is all zeros.
The "default values" of variables are the typical "zero-state"
of whatever the type is. For example, the default value for a ``bool``
is ``false``. The default value for the ``uint`` or ``int``
types is ``0``. For statically-sized arrays and ``bytes1`` to
``bytes32``, each individual
element will be initialized to the default value corresponding
to its type. For dynamically-sized arrays, ``bytes``
and ``string``, the default value is an empty array or string.
For the ``enum`` type, the default value is its first member.
Scoping in Solidity follows the widespread scoping rules of C99
(and many other languages): Variables are visible from the point right after their declaration
until the end of the smallest ``{ }``-block that contains the declaration. As an exception to this rule, variables declared in the
until the end of the smallest ``{ }``-block that contains the declaration.
As an exception to this rule, variables declared in the
initialization part of a for-loop are only visible until the end of the for-loop.
Variables that are parameter-like (function parameters, modifier parameters,
catch parameters, ...) are visible inside the code block that follows -
the body of the function/modifier for a function and modifier parameter and the catch block
for a catch parameter.
Variables and other items declared outside of a code block, for example functions, contracts,
user-defined types, etc., are visible even before they were declared. This means you can
use state variables before they are declared and call functions recursively.
@ -350,7 +385,8 @@ In any case, you will get a warning about the outer variable being shadowed.
}
.. warning::
Before version 0.5.0 Solidity followed the same scoping rules as JavaScript, that is, a variable declared anywhere within a function would be in scope
Before version 0.5.0 Solidity followed the same scoping rules as
JavaScript, that is, a variable declared anywhere within a function would be in scope
for the entire function, regardless where it was declared. The following example shows a code snippet that used
to compile but leads to an error starting from version 0.5.0.
@ -373,17 +409,24 @@ In any case, you will get a warning about the outer variable being shadowed.
Error handling: Assert, Require, Revert and Exceptions
======================================================
Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the
state in the current call (and all its sub-calls) and flags an error to the caller.
Solidity uses state-reverting exceptions to handle errors.
Such an exception undoes all changes made to the
state in the current call (and all its sub-calls) and
flags an error to the caller.
When exceptions happen in a sub-call, they "bubble up" (i.e., exceptions are rethrown) automatically. Exceptions to this rule are ``send``
and the low-level functions ``call``, ``delegatecall`` and ``staticcall``, they return ``false`` as their first return value in case
When exceptions happen in a sub-call, they "bubble up" (i.e.,
exceptions are rethrown) automatically. Exceptions to this rule are ``send``
and the low-level functions ``call``, ``delegatecall`` and
``staticcall``: they return ``false`` as their first return value in case
of an exception instead of "bubbling up".
.. warning::
The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the account called is non-existent, as part of the design of EVM. Existence must be checked prior to calling if needed.
The low-level functions ``call``, ``delegatecall`` and
``staticcall`` return ``true`` as their first return value
if the account called is non-existent, as part of the design
of the EVM. Account existence must be checked prior to calling if needed.
It is not yet possible to catch exceptions with Solidity.
Exceptions can be caught with the ``try``/``catch`` statement.
``assert`` and ``require``
--------------------------
@ -391,11 +434,16 @@ It is not yet possible to catch exceptions with Solidity.
The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception
if the condition is not met.
The ``assert`` function should only be used to test for internal errors, and to check invariants. Properly functioning code should never reach a failing ``assert`` statement; if this happens there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``.
The ``assert`` function should only be used to test for internal
errors, and to check invariants. Properly functioning code should
never reach a failing ``assert`` statement; if this happens there
is a bug in your contract which you should fix. Language analysis
tools can evaluate your contract to identify the conditions and
function calls which will reach a failing ``assert``.
An ``assert``-style exception is generated in the following situations:
#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
#. If you access an array or an array slice at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
#. If you access a fixed-length ``bytesN`` at a too large or negative index.
#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
#. If you shift by a negative amount.
@ -403,16 +451,25 @@ An ``assert``-style exception is generated in the following situations:
#. If you call a zero-initialized variable of internal function type.
#. If you call ``assert`` with an argument that evaluates to false.
The ``require`` function should be used to ensure valid conditions that cannot be detected until execution time.
These conditions include inputs, or contract state variables are met, or to validate return values from calls to external contracts.
The ``require`` function should be used to ensure valid conditions
that cannot be detected until execution time.
This includes conditions on inputs
or return values from calls to external contracts.
A ``require``-style exception is generated in the following situations:
#. Calling ``require`` with an argument that evaluates to ``false``.
#. If you call a function via a message call but it does not finish properly (i.e., it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``.
#. If you create a contract using the ``new`` keyword but the contract creation :ref:`does not finish properly<creating-contracts>`.
#. If you call a function via a message call but it does not finish
properly (i.e., it runs out of gas, has no matching function, or
throws an exception itself), except when a low level operation
``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall``
is used. The low level operations never throw exceptions but
indicate failures by returning ``false``.
#. If you create a contract using the ``new`` keyword but the contract
creation :ref:`does not finish properly<creating-contracts>`.
#. If you perform an external function call targeting a contract that contains no code.
#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
#. If your contract receives Ether via a public function without
``payable`` modifier (including the constructor and the fallback function).
#. If your contract receives Ether via a public getter function.
#. If a ``.transfer()`` fails.
@ -438,21 +495,30 @@ and ``assert`` for internal error checking.
}
}
Internally, Solidity performs a revert operation (instruction ``0xfd``) for a ``require``-style exception and executes an invalid operation
Internally, Solidity performs a revert operation (instruction
``0xfd``) for a ``require``-style exception and executes an invalid operation
(instruction ``0xfe``) to throw an ``assert``-style exception. In both cases, this causes
the EVM to revert all changes made to the state. The reason for reverting is that there is no safe way to continue execution, because an expected effect
did not occur. Because we want to keep the atomicity of transactions, the safest action is to revert all changes and make the whole transaction
the EVM to revert all changes made to the state. The reason for reverting
is that there is no safe way to continue execution, because an expected effect
did not occur. Because we want to keep the atomicity of transactions, the
safest action is to revert all changes and make the whole transaction
(or at least call) without effect.
In both cases, the caller can react on such failures using ``try``/``catch``
(in the failing ``assert``-style exception only if enough gas is left), but
the changes in the caller will always be reverted.
.. note::
``assert``-style exceptions consume all gas available to the call, while ``require``-style exceptions do not consume any gas starting from the Metropolis release.
``assert``-style exceptions consume all gas available to the call,
while ``require``-style exceptions do not consume any gas starting from the Metropolis release.
``revert``
----------
The ``revert`` function is another way to trigger exceptions from within other code blocks to flag an error and
revert the current call. The function takes an optional string message containing details about the error that is passed back to the caller.
revert the current call. The function takes an optional string
message containing details about the error that is passed back to the caller.
The following example shows how to use an error string together with ``revert`` and the equivalent ``require``:
@ -485,6 +551,99 @@ In the above example, ``revert("Not enough Ether provided.");`` returns the foll
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data
The provided message can be retrieved by the caller using ``try``/``catch`` as shown below.
.. note::
There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which
was deprecated in version 0.4.13 and removed in version 0.5.0.
.. _try-catch:
``try``/``catch``
-----------------
A failure in an external call can be caught using a try/catch statement, as follows:
::
pragma solidity ^0.6.0;
interface DataFeed { function getData(address token) external returns (uint value); }
contract FeedConsumer {
DataFeed feed;
uint errorCount;
function rate(address token) public returns (uint value, bool success) {
// Permanently disable the mechanism if there are
// more than 10 errors.
require(errorCount < 10);
try feed.getData(token) returns (uint v) {
return (v, true);
} catch Error(string memory /*reason*/) {
// This is executed in case
// revert was called inside getData
// and a reason string was provided.
errorCount++;
return (0, false);
} catch (bytes memory /*lowLevelData*/) {
// This is executed in case revert() was used
// or there was a failing assertion, division
// by zero, etc. inside getData.
errorCount++;
return (0, false);
}
}
}
The ``try`` keyword has to be followed by an expression representing an external function call
or a contract creation (``new ContractName()``).
Errors inside the expression are not caught (for example if it is a complex expression
that also involves internal function calls), only a revert happening inside the external
call itself. The ``returns`` part (which is optional) that follows declares return variables
matching the types returned by the external call. In case there was no error,
these variables are assigned and the contract's execution continues inside the
first success block. If the end of the success block is reached, execution continues after the ``catch`` blocks.
Currently, Solidity supports different kinds of catch blocks depending on the
type of error. If the error was caused by ``revert("reasonString")`` or
``require(false, "reasonString")`` (or an internal error that causes such an
exception), then the catch clause
of the type ``catch Error(string memory reason)`` will be executed.
It is planned to support other types of error data in the future.
The string ``Error`` is currently parsed as is and is not treated as an identifier.
The clause ``catch (bytes memory lowLevelData)`` is executed if the error signature
does not match any other clause, there was an error during decoding of the error
message, if there was a failing assertion in the external
call (for example due to a division by zero or a failing ``assert()``) or
if no error data was provided with the exception.
The declared variable provides access to the low-level error data in that case.
If you are not interested in the error data, you can just use
``catch { ... }`` (even as the only catch clause).
In order to catch all error cases, you have to have at least the clause
``catch { ...}`` or the clause ``catch (bytes memory lowLevelData) { ... }``.
The variables declared in the ``returns`` and the ``catch`` clause are only
in scope in the block that follows.
.. note::
If an error happens during the decoding of the return data
inside a try/catch-statement, this causes an exception in the currently
executing contract and because of that, it is not caught in the catch clause.
If there is an error during decoding of ``catch Error(string memory reason)``
and there is a low-level catch clause, this error is caught there.
.. note::
If execution reaches a catch-block, then the state-changing effects of
the external call have been reverted. If execution reaches
the success block, the effects were not reverted.
If the effects have been reverted, then execution either continues
in a catch block or the execution of the try/catch statement itself
reverts (for example due to decoding failures as noted above or
due to not providing a low-level catch clause).

View File

@ -4,12 +4,10 @@
Blind Auction
*************
In this section, we will show how easy it is to create a
completely blind auction contract on Ethereum.
We will start with an open auction where everyone
can see the bids that are made and then extend this
contract into a blind auction where it is not
possible to see the actual bid until the bidding
In this section, we will show how easy it is to create a completely blind
auction contract on Ethereum. We will start with an open auction where
everyone can see the bids that are made and then extend this contract into a
blind auction where it is not possible to see the actual bid until the bidding
period ends.
.. _simple_auction:
@ -17,16 +15,12 @@ period ends.
Simple Open Auction
===================
The general idea of the following simple auction contract
is that everyone can send their bids during
a bidding period. The bids already include sending
money / ether in order to bind the bidders to their
bid. If the highest bid is raised, the previously
highest bidder gets her money back.
After the end of the bidding period, the
contract has to be called manually for the
beneficiary to receive their money - contracts cannot
activate themselves.
The general idea of the following simple auction contract is that everyone can
send their bids during a bidding period. The bids already include sending money
/ Ether in order to bind the bidders to their bid. If the highest bid is
raised, the previously highest bidder gets their money back. After the end of
the bidding period, the contract has to be called manually for the beneficiary
to receive their money - contracts cannot activate themselves.
::
@ -89,7 +83,10 @@ activate themselves.
);
// If the bid is not higher, send the
// money back.
// money back (the failing require
// will revert all changes in this
// function execution including
// it having received the money).
require(
msg.value > highestBid,
"There already is a higher bid."
@ -158,38 +155,31 @@ activate themselves.
Blind Auction
=============
The previous open auction is extended to a blind auction
in the following. The advantage of a blind auction is
that there is no time pressure towards the end of
the bidding period. Creating a blind auction on a
transparent computing platform might sound like a
contradiction, but cryptography comes to the rescue.
The previous open auction is extended to a blind auction in the following. The
advantage of a blind auction is that there is no time pressure towards the end
of the bidding period. Creating a blind auction on a transparent computing
platform might sound like a contradiction, but cryptography comes to the
rescue.
During the **bidding period**, a bidder does not
actually send her bid, but only a hashed version of it.
Since it is currently considered practically impossible
to find two (sufficiently long) values whose hash
values are equal, the bidder commits to the bid by that.
After the end of the bidding period, the bidders have
to reveal their bids: They send their values
unencrypted and the contract checks that the hash value
is the same as the one provided during the bidding period.
During the **bidding period**, a bidder does not actually send their bid, but
only a hashed version of it. Since it is currently considered practically
impossible to find two (sufficiently long) values whose hash values are equal,
the bidder commits to the bid by that. After the end of the bidding period,
the bidders have to reveal their bids: They send their values unencrypted and
the contract checks that the hash value is the same as the one provided during
the bidding period.
Another challenge is how to make the auction
**binding and blind** at the same time: The only way to
prevent the bidder from just not sending the money
after they won the auction is to make her send it
together with the bid. Since value transfers cannot
be blinded in Ethereum, anyone can see the value.
Another challenge is how to make the auction **binding and blind** at the same
time: The only way to prevent the bidder from just not sending the money after
they won the auction is to make them send it together with the bid. Since value
transfers cannot be blinded in Ethereum, anyone can see the value.
The following contract solves this problem by
accepting any value that is larger than the highest
bid. Since this can of course only be checked during
the reveal phase, some bids might be **invalid**, and
this is on purpose (it even provides an explicit
flag to place invalid bids with high value transfers):
Bidders can confuse competition by placing several
high or low invalid bids.
The following contract solves this problem by accepting any value that is
larger than the highest bid. Since this can of course only be checked during
the reveal phase, some bids might be **invalid**, and this is on purpose (it
even provides an explicit flag to place invalid bids with high value
transfers): Bidders can confuse competition by placing several high or low
invalid bids.
::
@ -293,24 +283,6 @@ high or low invalid bids.
msg.sender.transfer(refund);
}
// This is an "internal" function which means that it
// can only be called from the contract itself (or from
// derived contracts).
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// Refund the previously highest bidder.
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
/// Withdraw a bid that was overbid.
function withdraw() public {
uint amount = pendingReturns[msg.sender];
@ -336,4 +308,22 @@ high or low invalid bids.
ended = true;
beneficiary.transfer(highestBid);
}
// This is an "internal" function which means that it
// can only be called from the contract itself (or from
// derived contracts).
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// Refund the previously highest bidder.
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
}

View File

@ -33,8 +33,11 @@ The contract will work as follows:
Creating the signature
----------------------
Alice does not need to interact with the Ethereum network to sign the transaction, the process is completely offline.
In this tutorial, we will sign messages in the browser using `web3.js <https://github.com/ethereum/web3.js>`_ and `MetaMask <https://metamask.io>`_, using the method described in `EIP-762 <https://github.com/ethereum/EIPs/pull/712>`_,
Alice does not need to interact with the Ethereum network
to sign the transaction, the process is completely offline.
In this tutorial, we will sign messages in the browser
using `web3.js <https://github.com/ethereum/web3.js>`_ and
`MetaMask <https://metamask.io>`_, using the method described in `EIP-762 <https://github.com/ethereum/EIPs/pull/712>`_,
as it provides a number of other security benefits.
::
@ -44,7 +47,10 @@ as it provides a number of other security benefits.
web3.eth.personal.sign(hash, web3.eth.defaultAccount, function () { console.log("Signed"); });
.. note::
The ``web3.eth.personal.sign`` prepends the length of the message to the signed data. Since we hash first, the message will always be exactly 32 bytes long, and thus this length prefix is always the same.
The ``web3.eth.personal.sign`` prepends the length of the
message to the signed data. Since we hash first, the message
will always be exactly 32 bytes long, and thus this length
prefix is always the same.
What to Sign
------------
@ -62,9 +68,18 @@ themselves, a so-called nonce, which is the number of transactions sent by an
account.
The smart contract checks if a nonce is used multiple times.
Another type of replay attack can occur when the owner deploys a ``ReceiverPays`` smart contract, makes some payments, and then destroys the contract. Later, they decide to deploy the ``RecipientPays`` smart contract again, but the new contract does not know the nonces used in the previous deployment, so the attacker can use the old messages again.
Another type of replay attack can occur when the owner
deploys a ``ReceiverPays`` smart contract, makes some
payments, and then destroys the contract. Later, they decide
to deploy the ``RecipientPays`` smart contract again, but the
new contract does not know the nonces used in the previous
deployment, so the attacker can use the old messages again.
Alice can protect against this attack by including the contract's address in the message, and only messages containing the contract's address itself will be accepted. You can find an example of this in the first two lines of the ``claimPayment()`` function of the full contract at the end of this section.
Alice can protect against this attack by including the
contract's address in the message, and only messages containing
the contract's address itself will be accepted. You can find
an example of this in the first two lines of the ``claimPayment()``
function of the full contract at the end of this section.
Packing arguments
-----------------
@ -94,12 +109,26 @@ Here is a JavaScript function that creates the proper signature for the ``Receiv
Recovering the Message Signer in Solidity
-----------------------------------------
In general, ECDSA signatures consist of two parameters, ``r`` and ``s``. Signatures in Ethereum include a third parameter called ``v``, that you can use to verify which account's private key was used to sign the message, and the transaction's sender. Solidity provides a built-in function `ecrecover <mathematical-and-cryptographic-functions>`_ that accepts a message along with the ``r``, ``s`` and ``v`` parameters and returns the address that was used to sign the message.
In general, ECDSA signatures consist of two parameters,
``r`` and ``s``. Signatures in Ethereum include a third
parameter called ``v``, that you can use to verify which
account's private key was used to sign the message, and
the transaction's sender. Solidity provides a built-in
function `ecrecover <mathematical-and-cryptographic-functions>`_ that
accepts a message along with the ``r``, ``s`` and ``v`` parameters
and returns the address that was used to sign the message.
Extracting the Signature Parameters
-----------------------------------
Signatures produced by web3.js are the concatenation of ``r``, ``s`` and ``v``, so the first step is to split these parameters apart. You can do this on the client-side, but doing it inside the smart contract means you only need to send one signature parameter rather than three. Splitting apart a byte array into component parts is a mess, so we use `inline assembly <assembly>`_ to do the job in the ``splitSignature`` function (the third function in the full contract at the end of this section).
Signatures produced by web3.js are the concatenation of ``r``,
``s`` and ``v``, so the first step is to split these parameters
apart. You can do this on the client-side, but doing it inside
the smart contract means you only need to send one signature
parameter rather than three. Splitting apart a byte array into
its constituent parts is a mess, so we use
`inline assembly <assembly>`_ to do the job in the ``splitSignature``
function (the third function in the full contract at the end of this section).
Computing the Message Hash
--------------------------
@ -180,26 +209,45 @@ The full contract
Writing a Simple Payment Channel
================================
Alice now builds a simple but complete implementation of a payment channel. Payment channels use cryptographic signatures to make repeated transfers of Ether securely, instantaneously, and without transaction fees.
Alice now builds a simple but complete implementation of a payment
channel. Payment channels use cryptographic signatures to make
repeated transfers of Ether securely, instantaneously, and without transaction fees.
What is a Payment Channel?
--------------------------
Payment channels allow participants to make repeated transfers of Ether without using transactions. This means that you can avoid the delays and fees associated with transactions. We are going to explore a simple unidirectional payment channel between two parties (Alice and Bob). It involves three steps:
Payment channels allow participants to make repeated transfers of Ether
without using transactions. This means that you can avoid the delays and
fees associated with transactions. We are going to explore a simple
unidirectional payment channel between two parties (Alice and Bob). It involves three steps:
1. Alice funds a smart contract with Ether. This "opens" the payment channel.
2. Alice signs messages that specify how much of that Ether is owed to the recipient. This step is repeated for each payment.
3. Bob "closes" the payment channel, withdrawing their portion of the Ether and sending the remainder back to the sender.
.. note::
Only steps 1 and 3 require Ethereum transactions, step 2 means that the sender transmits a cryptographically signed message to the recipient via off chain methods (e.g. email). This means only two transactions are required to support any number of transfers.
Only steps 1 and 3 require Ethereum transactions, step 2 means that the sender
transmits a cryptographically signed message to the recipient via off chain
methods (e.g. email). This means only two transactions are required to support
any number of transfers.
Bob is guaranteed to receive their funds because the smart contract escrows the Ether and honours a valid signed message. The smart contract also enforces a timeout, so Alice is guaranteed to eventually recover their funds even if the recipient refuses to close the channel. It is up to the participants in a payment channel to decide how long to keep it open. For a short-lived transaction, such as paying an internet café for each minute of network access, or for a longer relationship, such as paying an employee an hourly wage, a payment could last for months or years.
Bob is guaranteed to receive their funds because the smart contract escrows the
Ether and honours a valid signed message. The smart contract also enforces a
timeout, so Alice is guaranteed to eventually recover their funds even if the
recipient refuses to close the channel. It is up to the participants in a payment
channel to decide how long to keep it open. For a short-lived transaction,
such as paying an internet café for each minute of network access, the payment
channel may be kept open for a limited duration. On the other hand, for a
recurring payment, such as paying an employee an hourly wage, the payment channel
may be kept open for several months or years.
Opening the Payment Channel
---------------------------
To open the payment channel, Alice deploys the smart contract, attaching the Ether to be escrowed and specifying the intended recipient and a maximum duration for the channel to exist. This is the function ``SimplePaymentChannel`` in the contract, at the end of this section.
To open the payment channel, Alice deploys the smart contract, attaching
the Ether to be escrowed and specifying the intended recipient and a
maximum duration for the channel to exist. This is the function
``SimplePaymentChannel`` in the contract, at the end of this section.
Making Payments
---------------
@ -218,7 +266,8 @@ Because of this, only one of the messages sent is redeemed. This is why
each message specifies a cumulative total amount of Ether owed, rather than the
amount of the individual micropayment. The recipient will naturally choose to
redeem the most recent message because that is the one with the highest total.
The nonce per-message is not needed anymore, because the smart contract only honors a single message. The address of the smart contract is still used
The nonce per-message is not needed anymore, because the smart contract only
honours a single message. The address of the smart contract is still used
to prevent a message intended for one payment channel from being used for a different channel.
Here is the modified JavaScript code to cryptographically sign a message from the previous section:
@ -254,7 +303,9 @@ Closing the Payment Channel
When Bob is ready to receive their funds, it is time to
close the payment channel by calling a ``close`` function on the smart contract.
Closing the channel pays the recipient the Ether they are owed and destroys the contract, sending any remaining Ether back to Alice. To close the channel, Bob needs to provide a message signed by Alice.
Closing the channel pays the recipient the Ether they are owed and
destroys the contract, sending any remaining Ether back to Alice. To
close the channel, Bob needs to provide a message signed by Alice.
The smart contract must verify that the message contains a valid signature from the sender.
The process for doing this verification is the same as the process the recipient uses.
@ -303,17 +354,6 @@ The full contract
expiration = now + duration;
}
function isValidSignature(uint256 amount, bytes memory signature)
internal
view
returns (bool)
{
bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount)));
// check that the signature is from the payment sender
return recoverSigner(message, signature) == sender;
}
/// the recipient can close the channel at any time by presenting a
/// signed amount from the sender. the recipient will be sent that amount,
/// and the remainder will go back to the sender
@ -340,6 +380,17 @@ The full contract
selfdestruct(sender);
}
function isValidSignature(uint256 amount, bytes memory signature)
internal
view
returns (bool)
{
bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount)));
// check that the signature is from the payment sender
return recoverSigner(message, signature) == sender;
}
/// All functions below this are just taken from the chapter
/// 'creating and verifying signatures' chapter.

View File

@ -38,9 +38,6 @@ and the sum of all balances is an invariant across the lifetime of the contract.
event Transfer(address from, address to, uint amount);
event Approval(address owner, address spender, uint amount);
function balanceOf(address tokenOwner) public view returns (uint balance) {
return balances[tokenOwner];
}
function transfer(address to, uint amount) public returns (bool success) {
balances.move(msg.sender, to, amount);
emit Transfer(msg.sender, to, amount);
@ -62,4 +59,8 @@ and the sum of all balances is an invariant across the lifetime of the contract.
emit Approval(msg.sender, spender, tokens);
return true;
}
function balanceOf(address tokenOwner) public view returns (uint balance) {
return balances[tokenOwner];
}
}

View File

@ -31,19 +31,11 @@ you can use state machine-like constructs inside a contract.
uint public value;
address payable public seller;
address payable public buyer;
enum State { Created, Locked, Release, Inactive }
// The state variable has a default value of the first member, `State.created`
State public state;
// Ensure that `msg.value` is an even number.
// Division will truncate if it is an odd number.
// Check via multiplication that it wasn't an odd number.
constructor() public payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value, "Value has to be even.");
}
modifier condition(bool _condition) {
require(_condition);
_;
@ -78,6 +70,15 @@ you can use state machine-like constructs inside a contract.
event ItemReceived();
event SellerRefunded();
// Ensure that `msg.value` is an even number.
// Division will truncate if it is an odd number.
// Check via multiplication that it wasn't an odd number.
constructor() public payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value, "Value has to be even.");
}
/// Abort the purchase and reclaim the ether.
/// Can only be called by the seller before
/// the contract is locked.

View File

@ -7,7 +7,7 @@ ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier
ContractDefinition = 'abstract'? ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
@ -16,17 +16,20 @@ ContractPart = StateVariableDeclaration | UsingForDeclaration
InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )* Identifier ('=' Expression)? ';'
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' | OverrideSpecifier )* Identifier ('=' Expression)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* ) '}'
ModifierDefinition = 'modifier' Identifier ParameterList? Block
ModifierDefinition = 'modifier' Identifier ParameterList? ( 'virtual' | OverrideSpecifier )* Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
FunctionDefinition = 'function' Identifier? ParameterList
( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' )*
( ModifierInvocation | StateMutability | 'external' | 'public' | 'internal' | 'private' | 'virtual' | OverrideSpecifier )*
( 'returns' ParameterList )? ( ';' | Block )
OverrideSpecifier = 'override' ( '(' UserDefinedTypeName (',' UserDefinedTypeName)* ')' )?
EventDefinition = 'event' Identifier EventParameterList 'anonymous'? ';'
EnumValue = Identifier
@ -62,12 +65,14 @@ StorageLocation = 'memory' | 'storage' | 'calldata'
StateMutability = 'pure' | 'view' | 'payable'
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
Statement = IfStatement | TryStatement | WhileStatement | ForStatement | Block | InlineAssemblyStatement |
( DoWhileStatement | PlaceholderStatement | Continue | Break | Return |
Throw | EmitStatement | SimpleStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
TryStatement = 'try' Expression ( 'returns' ParameterList )? Block CatchClause+
CatchClause = 'catch' Identifier? ParameterList Block
WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
@ -86,6 +91,7 @@ Expression
= Expression ('++' | '--')
| NewExpression
| IndexAccess
| IndexRangeAccess
| MemberAccess
| FunctionCall
| '(' Expression ')'
@ -123,6 +129,7 @@ FunctionCallArguments = '{' NameValueList? '}'
NewExpression = 'new' TypeName
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expression? ']'
IndexRangeAccess = Expression '[' Expression? ':' Expression? ']'
BooleanLiteral = 'true' | 'false'
NumberLiteral = ( HexNumber | DecimalNumber ) (' ' NumberUnit)?
@ -164,6 +171,7 @@ AssemblyStatement = AssemblyBlock
| AssemblySwitch
| AssemblyForLoop
| AssemblyBreakContinue
| AssemblyLeave
AssemblyFunctionDefinition =
'function' Identifier '(' AssemblyIdentifierList? ')'
( '->' AssemblyIdentifierList )? AssemblyBlock
@ -176,6 +184,7 @@ AssemblyCase = 'case' Literal AssemblyBlock
AssemblyDefault = 'default' AssemblyBlock
AssemblyForLoop = 'for' AssemblyBlock AssemblyExpression AssemblyBlock AssemblyBlock
AssemblyBreakContinue = 'break' | 'continue'
AssemblyLeave = 'leave'
AssemblyFunctionCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')'
AssemblyIdentifierList = Identifier ( ',' Identifier )*

View File

@ -19,11 +19,15 @@ user-defined types among other features.
With Solidity you can create contracts for uses such as voting, crowdfunding, blind auctions,
and multi-signature wallets.
When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number `to indicate this fast pace of change <https://semver.org/#spec-item-4>`_.
When deploying contracts, you should use the latest released
version of Solidity. This is because breaking changes as well as
new features and bug fixes are introduced regularly. We currently use
a 0.x version number `to indicate this fast pace of change <https://semver.org/#spec-item-4>`_.
.. warning::
Solidity recently released the 0.5.x version that introduced a lot of breaking changes. Make sure you read :doc:`the full list <050-breaking-changes>`.
Solidity recently released the 0.6.x version that introduced a lot of breaking
changes. Make sure you read :doc:`the full list <060-breaking-changes>`.
Language Documentation
----------------------
@ -31,7 +35,8 @@ Language Documentation
If you are new to the concept of smart contracts we recommend you start with
:ref:`an example smart contract <simple-smart-contract>` written
in Solidity. When you are ready for more detail, we recommend you read the
:doc:`"Solidity by Example" <solidity-by-example>` and :doc:`"Solidity in Depth" <solidity-in-depth>` sections to learn the core concepts of the language.
:doc:`"Solidity by Example" <solidity-by-example>` and
:doc:`"Solidity in Depth" <solidity-in-depth>` sections to learn the core concepts of the language.
For further reading, try :ref:`the basics of blockchains <blockchain-basics>`
and details of the :ref:`Ethereum Virtual Machine <the-ethereum-virtual-machine>`.
@ -52,9 +57,11 @@ and details of the :ref:`Ethereum Virtual Machine <the-ethereum-virtual-machine>
:ref:`security_considerations` section.
If you have any questions, you can try searching for answers or asking on the
`Ethereum Stackexchange <https://ethereum.stackexchange.com/>`_, or our `gitter channel <https://gitter.im/ethereum/solidity/>`_.
`Ethereum Stackexchange <https://ethereum.stackexchange.com/>`_, or
our `gitter channel <https://gitter.im/ethereum/solidity/>`_.
Ideas for improving Solidity or this documentation are always welcome, read our :doc:`contributors guide <contributing>` for more details.
Ideas for improving Solidity or this documentation are always welcome,
read our :doc:`contributors guide <contributing>` for more details.
.. _translations:

View File

@ -116,7 +116,9 @@ The nightly version can be installed using these commands:
sudo apt-get update
sudo apt-get install solc
We are also releasing a `snap package <https://snapcraft.io/>`_, which is installable in all the `supported Linux distros <https://snapcraft.io/docs/core/install>`_. To install the latest stable version of solc:
We are also releasing a `snap package <https://snapcraft.io/>`_, which is
installable in all the `supported Linux distros <https://snapcraft.io/docs/core/install>`_. To
install the latest stable version of solc:
.. code-block:: bash
@ -129,6 +131,12 @@ with the most recent changes, please use the following:
sudo snap install solc --edge
.. note::
The ``solc`` snap uses strict confinement. This is the most secure mode for snap packages
but it comes with limitations, like accessing only the files in your ``/home`` and ``/media`` directories.
For more information, go to `Demystifying Snap Confinement <https://snapcraft.io/blog/demystifying-snap-confinement>`_.
Arch Linux also has packages, albeit limited to the latest development version:
.. code-block:: bash
@ -146,7 +154,8 @@ currently not supported.
brew tap ethereum/ethereum
brew install solidity
To install the most recent 0.4.x version of Solidity you can also use ``brew install solidity@4``.
To install the most recent 0.4.x / 0.5.x version of Solidity you can also use ``brew install solidity@4``
and ``brew install solidity@5``, respectively.
If you need a specific version of Solidity you can install a
Homebrew formula directly from Github.

View File

@ -42,7 +42,8 @@ data (its *state*) that resides at a specific address on the Ethereum
blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of
type ``uint`` (*u*\nsigned *int*\eger of *256* bits). You can think of it as a single slot
in a database that you can query and alter by calling functions of the
code that manages the database. In this example, the contract defines the functions ``set`` and ``get`` that can be used to modify
code that manages the database. In this example, the contract defines the
functions ``set`` and ``get`` that can be used to modify
or retrieve the value of the variable.
To access a state variable, you do not need the prefix ``this.`` as is common in
@ -70,7 +71,7 @@ Subcurrency Example
===================
The following contract implements the simplest form of a
cryptocurrency. The contract allows only its creator to create new coins (different issuance scheme are possible).
cryptocurrency. The contract allows only its creator to create new coins (different issuance schemes are possible).
Anyone can send coins to each other without a need for
registering with a username and password, all you need is an Ethereum keypair.
@ -116,7 +117,8 @@ This contract introduces some new concepts, let us go through them one by one.
The line ``address public minter;`` declares a state variable of type :ref:`address<address>`.
The ``address`` type is a 160-bit value that does not allow any arithmetic operations.
It is suitable for storing addresses of contracts, or a hash of the public half of a keypair belonging to :ref:`external accounts<accounts>`.
It is suitable for storing addresses of contracts, or a hash of the public half
of a keypair belonging to :ref:`external accounts<accounts>`.
The keyword ``public`` automatically generates a function that allows you to access the current value of the state
variable from outside of the contract. Without this keyword, other contracts have no way to access the variable.
@ -190,7 +192,7 @@ The functions that make up the contract, and that users and contracts can call a
The ``mint`` function sends an amount of newly created coins to another address.
The :ref:`require <assert-and-require>` function call defines conditions that reverts all changes if not met.
In this example, ``require(msg.sender == minter);`` ensures that only the creator of the contract can call ``mint``,
and ``require(amount < 1e60);`` ensures a maximum amount of tokens, without which could cause overflow errors in the future.
and ``require(amount < 1e60);`` ensures a maximum amount of tokens. This ensures that there are no overflow errors in the future.
The ``send`` function can be used by anyone (who already
has some of these coins) to send coins to anyone else. If the sender does not have
@ -214,7 +216,9 @@ Blockchain Basics
*****************
Blockchains as a concept are not too hard to understand for programmers. The reason is that
most of the complications (mining, `hashing <https://en.wikipedia.org/wiki/Cryptographic_hash_function>`_, `elliptic-curve cryptography <https://en.wikipedia.org/wiki/Elliptic_curve_cryptography>`_, `peer-to-peer networks <https://en.wikipedia.org/wiki/Peer-to-peer>`_, etc.)
most of the complications (mining, `hashing <https://en.wikipedia.org/wiki/Cryptographic_hash_function>`_,
`elliptic-curve cryptography <https://en.wikipedia.org/wiki/Elliptic_curve_cryptography>`_,
`peer-to-peer networks <https://en.wikipedia.org/wiki/Peer-to-peer>`_, etc.)
are just there to provide a certain set of features and promises for the platform. Once you accept these
features as given, you do not have to worry about the underlying technology - or do you have
to know how Amazon's AWS works internally in order to use it?
@ -508,12 +512,22 @@ receives the address of the new contract on the stack.
Deactivate and Self-destruct
============================
The only way to remove code from the blockchain is when a contract at that address performs the ``selfdestruct`` operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed from the state. Removing the contract in theory sounds like a good idea, but it is potentially dangerous, as if someone sends Ether to removed contracts, the Ether is forever lost.
The only way to remove code from the blockchain is when a contract at that
address performs the ``selfdestruct`` operation. The remaining Ether stored
at that address is sent to a designated target and then the storage and code
is removed from the state. Removing the contract in theory sounds like a good
idea, but it is potentially dangerous, as if someone sends Ether to removed
contracts, the Ether is forever lost.
.. warning::
Even if a contract is removed by "selfdestruct", it is still part of the history of the blockchain and probably retained by most Ethereum nodes. So using "selfdestruct" is not the same as deleting data from a hard disk.
Even if a contract is removed by "selfdestruct", it is still part of the
history of the blockchain and probably retained by most Ethereum nodes.
So using "selfdestruct" is not the same as deleting data from a hard disk.
.. note::
Even if a contract's code does not contain a call to ``selfdestruct``, it can still perform that operation using ``delegatecall`` or ``callcode``.
Even if a contract's code does not contain a call to ``selfdestruct``,
it can still perform that operation using ``delegatecall`` or ``callcode``.
If you want to deactivate your contracts, you should instead **disable** them by changing some internal state which causes all functions to revert. This makes it impossible to use the contract, as it returns Ether immediately.
If you want to deactivate your contracts, you should instead **disable** them
by changing some internal state which causes all functions to revert. This
makes it impossible to use the contract, as it returns Ether immediately.

View File

@ -3,8 +3,9 @@ Layout of a Solidity Source File
********************************
Source files can contain an arbitrary number of
:ref:`contract definitions<contract_structure>`, import_ directives
and :ref:`pragma directives<pragma>`.
:ref:`contract definitions<contract_structure>`, import_ directives,
:ref:`pragma directives<pragma>` and
:ref:`struct<structs>` and :ref:`enum<enums>` definitions.
.. index:: ! pragma
@ -16,8 +17,8 @@ Pragmas
The ``pragma`` keyword is used to enable certain compiler features
or checks. A pragma directive is always local to a source file, so
you have to add the pragma to all your files if you want enable it
in all of your project. If you :ref:`import<import>` another file, the pragma
from that file does not automatically apply to the importing file.
in your whole project. If you :ref:`import<import>` another file, the pragma
from that file does *not* automatically apply to the importing file.
.. index:: ! pragma, version
@ -35,14 +36,12 @@ a good idea to read through the changelog at least for releases that contain
breaking changes. These releases always have versions of the form
``0.x.0`` or ``x.0.0``.
The version pragma is used as follows::
pragma solidity ^0.5.2;
The version pragma is used as follows: ``pragma solidity ^0.5.2;``
A source file with the line above does not compile with a compiler earlier than version 0.5.2,
and it also does not work on a compiler starting from version 0.6.0 (this
second condition is added by using ``^``). This is because
there will be no breaking changes until version ``0.6.0``, so you can always
second condition is added by using ``^``). Because
there will be no breaking changes until version ``0.6.0``, you can
be sure that your code compiles the way you intended. The exact version of the
compiler is not fixed, so that bugfix releases are still possible.
@ -72,10 +71,12 @@ ABIEncoderV2
~~~~~~~~~~~~
The new ABI encoder is able to encode and decode arbitrarily nested
arrays and structs. It produces less optimal code (the optimizer
for this part of the code is still under development) and has not
received as much testing as the old encoder. You can activate it
using ``pragma experimental ABIEncoderV2;``.
arrays and structs. It might produce less optimal code and has not
received as much testing as the old encoder, but is considered
non-experimental as of Solidity 0.6.0. You still have to explicitly
activate it using ``pragma experimental ABIEncoderV2;`` - we kept
the same pragma, even though it is not considered experimental
anymore.
.. _smt_checker:
@ -86,8 +87,10 @@ This component has to be enabled when the Solidity compiler is built
and therefore it is not available in all Solidity binaries.
The :ref:`build instructions<smt_solvers_build>` explain how to activate this option.
It is activated for the Ubuntu PPA releases in most versions,
but not for solc-js, the Docker images, Windows binaries or the
statically-built Linux binaries.
but not for the Docker images, Windows binaries or the
statically-built Linux binaries. It can be activated for solc-js via the
`smtCallback <https://github.com/ethereum/solc-js#example-usage-with-smtsolver-callback>`_ if you have an SMT solver
installed locally and run solc-js via node (not via the browser).
If you use ``pragma experimental SMTChecker;``, then you get additional
:ref:`safety warnings<formal_verification>` which are obtained by querying an
@ -106,8 +109,10 @@ Importing other Source Files
Syntax and Semantics
--------------------
Solidity supports import statements to help modularise your code that are similar to those available in JavaScript
(from ES6 on). However, Solidity does not support the concept of a `default export <https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export#Description>`_.
Solidity supports import statements to help modularise your code that
are similar to those available in JavaScript
(from ES6 on). However, Solidity does not support the concept of
a `default export <https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export#Description>`_.
At a global level, you can use import statements of the following form:
@ -283,12 +288,11 @@ for the two function parameters and two return variables.
/** @title Shape calculator. */
contract ShapeCalculator {
/** @dev Calculates a rectangle's surface and perimeter.
* @param w Width of the rectangle.
* @param h Height of the rectangle.
* @return s The calculated surface.
* @return p The calculated perimeter.
*/
/// @dev Calculates a rectangle's surface and perimeter.
/// @param w Width of the rectangle.
/// @param h Height of the rectangle.
/// @return s The calculated surface.
/// @return p The calculated perimeter.
function rectangle(uint w, uint h) public pure returns (uint s, uint p) {
s = w * h;
p = 2 * (w + h);

View File

@ -5,21 +5,23 @@ Contract Metadata
.. index:: metadata, contract verification
The Solidity compiler automatically generates a JSON file, the contract
metadata, that contains information about the current contract. You can use
metadata, that contains information about the compiled contract. You can use
this file to query the compiler version, the sources used, the ABI and NatSpec
documentation to more safely interact with the contract and verify its source
code.
The compiler appends a Swarm hash of the metadata file to the end of the
bytecode (for details, see below) of each contract, so that you can retrieve
the file in an authenticated way without having to resort to a centralized
data provider.
The compiler appends by default the IPFS hash of the metadata file to the end
of the bytecode (for details, see below) of each contract, so that you can
retrieve the file in an authenticated way without having to resort to a
centralized data provider. The other available options are the Swarm hash and
not appending the metadata hash to the bytecode. These can be configured via
the :ref:`Standard JSON Interface<compiler-api>`.
You have to publish the metadata file to Swarm (or another service) so that
others can access it. You create the file by using the ``solc --metadata``
You have to publish the metadata file to IPFS, Swarm, or another service so
that others can access it. You create the file by using the ``solc --metadata``
command that generates a file called ``ContractName_meta.json``. It contains
Swarm references to the source code, so you have to upload all source files and
the metadata file.
IPFS and Swarm references to the source code, so you have to upload all source
files and the metadata file.
The metadata file has the following format. The example below is presented in a
human-readable way. Properly formatted metadata should use quotes correctly,
@ -86,7 +88,9 @@ explanatory purposes.
},
metadata: {
// Reflects the setting used in the input json, defaults to false
useLiteralContent: true
useLiteralContent: true,
// Reflects the setting used in the input json, defaults to "ipfs"
bytecodeHash: "ipfs"
}
// Required for Solidity: File and name of the contract or library this
// metadata is created for.
@ -111,20 +115,24 @@ explanatory purposes.
}
.. warning::
Since the bytecode of the resulting contract contains the metadata hash, any
change to the metadata results in a change of the bytecode. This includes
Since the bytecode of the resulting contract contains the metadata hash by default, any
change to the metadata might result in a change of the bytecode. This includes
changes to a filename or path, and since the metadata includes a hash of all the
sources used, a single whitespace change results in different metadata, and
different bytecode.
.. note::
Note the ABI definition above has no fixed order. It can change with compiler versions.
The ABI definition above has no fixed order. It can change with compiler versions.
Starting from Solidity version 0.5.12, though, the array maintains a certain
order.
.. _encoding-of-the-metadata-hash-in-the-bytecode:
Encoding of the Metadata Hash in the Bytecode
=============================================
Because we might support other ways to retrieve the metadata file in the future,
the mapping ``{"bzzr1": <Swarm hash>, "solc": <compiler version>}`` is stored
the mapping ``{"ipfs": <IPFS hash>, "solc": <compiler version>}`` is stored
`CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the mapping might
contain more keys (see below) and the beginning of that
encoding is not easy to find, its length is added in a two-byte big-endian
@ -132,12 +140,12 @@ encoding. The current version of the Solidity compiler usually adds the followin
to the end of the deployed bytecode::
0xa2
0x65 'b' 'z' 'z' 'r' '1' 0x58 0x20 <32 bytes swarm hash>
0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash>
0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding>
0x00 0x32
So in order to retrieve the data, the end of the deployed bytecode can be checked
to match that pattern and use the Swarm hash to retrieve the file.
to match that pattern and use the IPFS hash to retrieve the file.
Whereas release builds of solc use a 3 byte encoding of the version as shown
above (one byte each for major, minor and patch version number), prerelease builds
@ -145,25 +153,25 @@ will instead use a complete version string including commit hash and build date.
.. note::
The CBOR mapping can also contain other keys, so it is better to fully
decode the data instead of relying on it starting with ``0xa265``.
decode the data instead of relying on it starting with ``0xa264``.
For example, if any experimental features that affect code generation
are used, the mapping will also contain ``"experimental": true``.
.. note::
The compiler currently uses the "swarm version 1" hash of the metadata,
but this might change in the future, so do not rely on this sequence
to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '1'``. We might also
add additional data to this CBOR structure, so the
best option is to use a proper CBOR parser.
The compiler currently uses the IPFS hash of the metadata by default, but
it may also use the bzzr1 hash or some other hash in the future, so do
not rely on this sequence to start with ``0xa2 0x64 'i' 'p' 'f' 's'``. We
might also add additional data to this CBOR structure, so the best option
is to use a proper CBOR parser.
Usage for Automatic Interface Generation and NatSpec
====================================================
The metadata is used in the following way: A component that wants to interact
with a contract (e.g. Mist or any wallet) retrieves the code of the contract, from that
the Swarm hash of a file which is then retrieved.
That file is JSON-decoded into a structure like above.
with a contract (e.g. Mist or any wallet) retrieves the code of the contract,
from that the IPFS/Swarm hash of a file which is then retrieved. That file
is JSON-decoded into a structure like above.
The component can then use the ABI to automatically generate a rudimentary
user interface for the contract.
@ -177,7 +185,7 @@ For additional information, read :doc:`Ethereum Natural Language Specification (
Usage for Source Code Verification
==================================
In order to verify the compilation, sources can be retrieved from Swarm
In order to verify the compilation, sources can be retrieved from IPFS/Swarm
via the link in the metadata file.
The compiler of the correct version (which is checked to be part of the "official" compilers)
is invoked on that input with the specified settings. The resulting

View File

@ -10,12 +10,16 @@ Layout of State Variables in Storage
.. _storage-inplace-encoding:
Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position ``0``. Multiple, contiguous items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules:
Statically-sized variables (everything except mapping and dynamically-sized
array types) are laid out contiguously in storage starting from position ``0``.
Multiple, contiguous items that need less than 32 bytes are packed into a single
storage slot if possible, according to the following rules:
- The first item in a storage slot is stored lower-order aligned.
- Elementary types use only as many bytes as are necessary to store them.
- If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot.
- Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules).
- Structs and array data always start a new slot and occupy whole slots
(but items inside a struct or array are packed tightly according to these rules).
For contracts that use inheritance, the ordering of state variables is determined by the
C3-linearized order of contracts starting with the most base-ward contract. If allowed
@ -54,17 +58,22 @@ Mappings and Dynamic Arrays
.. _storage-hashed-encoding:
Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash
computation to find the starting position of the value or the array data. These starting positions are always full stack slots.
computation to find the starting position of the value or the array data.
These starting positions are always full stack slots.
The mapping or the dynamic array itself occupies a slot in storage at some position ``p``
according to the above rule (or by recursively applying this rule for mappings of mappings or arrays of arrays). For dynamic arrays,
this slot stores the number of elements in the array (byte arrays and strings are an exception, see :ref:`below <bytes-and-string>`).
according to the above rule (or by recursively applying this rule for
mappings of mappings or arrays of arrays). For dynamic arrays,
this slot stores the number of elements in the array (byte arrays and
strings are an exception, see :ref:`below <bytes-and-string>`).
For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different
hash distribution). Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key
``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. If the value is again a
non-elementary type, the positions are found by adding an offset of ``keccak256(k . p)``.
So for the following contract snippet::
So for the following contract snippet
the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``::
pragma solidity >=0.4.0 <0.7.0;
@ -75,8 +84,6 @@ So for the following contract snippet::
mapping(uint => mapping(uint => S)) data;
}
The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``.
.. _bytes-and-string:
``bytes`` and ``string``
@ -97,7 +104,8 @@ JSON Output
.. _storage-layout-top-level:
The storage layout of a contract can be requested via the :ref:`standard JSON interface <compiler-api>`. The output is a JSON object containing two keys,
The storage layout of a contract can be requested via
the :ref:`standard JSON interface <compiler-api>`. The output is a JSON object containing two keys,
``storage`` and ``types``. The ``storage`` object is an array where each
element has the following form:
@ -114,7 +122,7 @@ element has the following form:
"type": "t_uint256"
}
where the example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA``
The example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA``
and
- ``astId`` is the id of the AST node of the state variable's declaration
@ -148,7 +156,8 @@ where
- ``bytes``: single slot or Keccak-256 hash-based depending on the data size (see :ref:`above <bytes-and-string>`).
- ``label`` is the canonical type name.
- ``numberOfBytes`` is the number of used bytes (as a decimal string). Note that if ``numberOfBytes > 32`` this means that more than one slot is used.
- ``numberOfBytes`` is the number of used bytes (as a decimal string).
Note that if ``numberOfBytes > 32`` this means that more than one slot is used.
Some types have extra information besides the four above. Mappings contain
its ``key`` and ``value`` types (again referencing an entry in this mapping
@ -369,12 +378,26 @@ Scratch space can be used between statements (i.e. within inline assembly). The
is used as initial value for dynamic memory arrays and should never be written to
(the free memory pointer points to ``0x80`` initially).
Solidity always places new objects at the free memory pointer and memory is never freed (this might change in the future).
Solidity always places new objects at the free memory pointer and
memory is never freed (this might change in the future).
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this
is even true for ``byte[]``, but not for ``bytes`` and ``string``).
Multi-dimensional memory arrays are pointers to memory arrays. The length of a
dynamic array is stored at the first slot of the array and followed by the array
elements.
.. warning::
There are some operations in Solidity that need a temporary memory area larger than 64 bytes and therefore will not fit into the scratch space. They will be placed where the free memory points to, but given their short lifetime, the pointer is not updated. The memory may or may not be zeroed out. Because of this, one shouldn't expect the free memory to point to zeroed out memory.
There are some operations in Solidity that need a temporary memory area
larger than 64 bytes and therefore will not fit into the scratch space.
They will be placed where the free memory points to, but given their
short lifetime, the pointer is not updated. The memory may or may not
be zeroed out. Because of this, one should not expect the free memory
to point to zeroed out memory.
While it may seem like a good idea to use ``msize`` to arrive at a definitely zeroed out memory area, using such a pointer non-temporarily without updating the free memory pointer can have adverse results.
While it may seem like a good idea to use ``msize`` to arrive at a
definitely zeroed out memory area, using such a pointer non-temporarily
without updating the free memory pointer can have unexpected results.
.. index: calldata layout
@ -398,11 +421,11 @@ data to the code.
Internals - Cleaning Up Variables
*********************************
When a value is shorter than 256-bit, in some cases the remaining bits
When a value is shorter than 256 bit, in some cases the remaining bits
must be cleaned.
The Solidity compiler is designed to clean such remaining bits before any operations
that might be adversely affected by the potential garbage in the remaining bits.
For example, before writing a value to the memory, the remaining bits need
For example, before writing a value to memory, the remaining bits need
to be cleared because the memory contents can be used for computing
hashes or sent as the data of a message call. Similarly, before
storing a value in the storage, the remaining bits need to be cleaned
@ -446,13 +469,54 @@ Different types have different rules for cleaning up invalid values:
Internals - The Optimiser
*************************
The Solidity optimiser operates on assembly so that other languages can use it. It splits the sequence of instructions into basic blocks at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser analyses the instructions and records every modification to the stack, memory, or storage as an expression which consists of an instruction and a list of arguments which are pointers to other expressions. The optimiser uses a component called "CommonSubexpressionEliminator" that amongst other tasks, finds expressions that are always equal (on every input) and combines them into an expression class. The optimiser first tries to find each new expression in a list of already known expressions. If this does not work, it simplifies the expression according to rules like ``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is a recursive process, we can also apply the latter rule if the second factor is a more complex expression where we know that it always evaluates to one. Modifications to storage and memory locations have to erase knowledge about storage and memory locations which are not known to be different. If we first write to location x and then to location y and both are input variables, the second could overwrite the first, so we do not know what is stored at x after we wrote to y. If simplification of the expression x - y evaluates to a non-zero constant, we know that we can keep our knowledge about what is stored at x.
This section discusses the optimiser that was first added to Solidity,
which operates on opcode streams. For information on the new Yul-based optimiser,
please see the `readme on github <https://github.com/ethereum/solidity/blob/develop/libyul/optimiser/README.md>`_.
After this process, we know which expressions have to be on the stack at the end, and have a list of modifications to memory and storage. This information is stored together with the basic blocks and is used to link them. Furthermore, knowledge about the stack, storage and memory configuration is forwarded to the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions, we can build a complete control flow graph of the program. If there is only one target we do not know (this can happen as in principle, jump targets can be computed from inputs), we have to erase all knowledge about the input state of a block as it can be the target of the unknown ``JUMP``. If the optimiser finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it to an unconditional jump.
The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks
at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser
analyses the instructions and records every modification to the stack,
memory, or storage as an expression which consists of an instruction and
a list of arguments which are pointers to other expressions. The optimiser
uses a component called "CommonSubexpressionEliminator" that amongst other
tasks, finds expressions that are always equal (on every input) and combines
them into an expression class. The optimiser first tries to find each new
expression in a list of already known expressions. If this does not work,
it simplifies the expression according to rules like
``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is
a recursive process, we can also apply the latter rule if the second factor
is a more complex expression where we know that it always evaluates to one.
Modifications to storage and memory locations have to erase knowledge about
storage and memory locations which are not known to be different. If we first
write to location x and then to location y and both are input variables, the
second could overwrite the first, so we do not know what is stored at x after
we wrote to y. If simplification of the expression x - y evaluates to a
non-zero constant, we know that we can keep our knowledge about what is stored at x.
As the last step, the code in each block is re-generated. The optimiser creates a dependency graph from the expressions on the stack at the end of the block, and it drops every operation that is not part of this graph. It generates code that applies the modifications to memory and storage in the order they were made in the original code (dropping modifications which were found not to be needed). Finally, it generates all values that are required to be on the stack in the correct place.
After this process, we know which expressions have to be on the stack at
the end, and have a list of modifications to memory and storage. This information
is stored together with the basic blocks and is used to link them. Furthermore,
knowledge about the stack, storage and memory configuration is forwarded to
the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions,
we can build a complete control flow graph of the program. If there is only
one target we do not know (this can happen as in principle, jump targets can
be computed from inputs), we have to erase all knowledge about the input state
of a block as it can be the target of the unknown ``JUMP``. If the optimiser
finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it
to an unconditional jump.
These steps are applied to each basic block and the newly generated code is used as replacement if it is smaller. If a basic block is split at a ``JUMPI`` and during the analysis, the condition evaluates to a constant, the ``JUMPI`` is replaced depending on the value of the constant. Thus code like
As the last step, the code in each block is re-generated. The optimiser creates
a dependency graph from the expressions on the stack at the end of the block,
and it drops every operation that is not part of this graph. It generates code
that applies the modifications to memory and storage in the order they were
made in the original code (dropping modifications which were found not to be
needed). Finally, it generates all values that are required to be on the
stack in the correct place.
These steps are applied to each basic block and the newly generated code
is used as replacement if it is smaller. If a basic block is split at a
``JUMPI`` and during the analysis, the condition evaluates to a constant,
the ``JUMPI`` is replaced depending on the value of the constant. Thus code like
::
@ -487,7 +551,8 @@ Furthermore, the compiler can also generate a mapping from the bytecode
to the range in the source code that generated the instruction. This is again
important for static analysis tools that operate on bytecode level and
for displaying the current position in the source code inside a debugger
or for breakpoint handling.
or for breakpoint handling. This mapping also contains other information,
like the jump type and the modifier depth (see below).
Both kinds of source mappings use integer identifiers to refer to source files.
The identifier of a source file is stored in
@ -509,12 +574,17 @@ Where ``s`` is the byte-offset to the start of the range in the source file,
index mentioned above.
The encoding in the source mapping for the bytecode is more complicated:
It is a list of ``s:l:f:j`` separated by ``;``. Each of these
It is a list of ``s:l:f:j:m`` separated by ``;``. Each of these
elements corresponds to an instruction, i.e. you cannot use the byte offset
but have to use the instruction offset (push instructions are longer than a single byte).
The fields ``s``, ``l`` and ``f`` are as above and ``j`` can be either
The fields ``s``, ``l`` and ``f`` are as above. ``j`` can be either
``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a
function, returns from a function or is a regular jump as part of e.g. a loop.
The last field, ``m``, is an integer that denotes the "modifier depth". This depth
is increased whenever the placeholder statement (``_``) is entered in a modifier
and decreased when it is left again. This allows debuggers to track tricky cases
like the same modifier being used twice or multiple placeholder statements being
used in a single modifier.
In order to compress these source mappings especially for bytecode, the
following rules are used:
@ -533,13 +603,18 @@ Tips and Tricks
***************
* Use ``delete`` on arrays to delete all its elements.
* Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple ``SSTORE`` operations might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check!
* Use shorter types for struct elements and sort them such that short types are
grouped together. This can lower the gas costs as multiple ``SSTORE`` operations
might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is
what you want to optimise). Use the gas price estimator (with optimiser enabled) to check!
* Make your state variables public - the compiler creates :ref:`getters <visibility-and-getters>` for you automatically.
* If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`.
* Initialize storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});``
.. note::
If the storage struct has tightly packed properties, initialize it with separate assignments: ``x.a = 1; x.b = 2;``. In this way it will be easier for the optimizer to update storage in one go, thus making assignment cheaper.
If the storage struct has tightly packed properties, initialize it with separate
assignments: ``x.a = 1; x.b = 2;``. In this way it will be easier for the
optimizer to update storage in one go, thus making assignment cheaper.
**********
Cheatsheet
@ -615,12 +690,16 @@ The following is the order of precedence for operators, listed in order of evalu
Global Variables
================
- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI <ABI>`-decodes the provided data. The types are given in parentheses as second argument. Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))``
- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI <ABI>`-decodes
the provided data. The types are given in parentheses as second argument.
Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))``
- ``abi.encode(...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes the given arguments
- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding <abi_packed_mode>` of the given arguments. Note that this encoding can be ambiguous!
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes the given arguments
starting from the second and prepends the given four-byte selector
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)```
- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding <abi_packed_mode>` of
the given arguments. Note that this encoding can be ambiguous!
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes
the given arguments starting from the second and prepends the given four-byte selector
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent
to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)```
- ``block.coinbase`` (``address payable``): current block miner's address
- ``block.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit
@ -634,22 +713,28 @@ Global Variables
- ``tx.gasprice`` (``uint``): gas price of the transaction
- ``tx.origin`` (``address payable``): sender of the transaction (full call chain)
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component)
- ``require(bool condition, string memory message)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component). Also provide error message.
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use
for malformed input or error in external component)
- ``require(bool condition, string memory message)``: abort execution and revert state changes if
condition is ``false`` (use for malformed input or error in external component). Also provide error message.
- ``revert()``: abort execution and revert state changes
- ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string
- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks
- ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input
- ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input
- ``ripemd160(bytes memory) returns (bytes20)``: compute the RIPEMD-160 hash of the input
- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error
- ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with
the public key from elliptic curve signature, return zero on error
- ``addmod(uint x, uint y, uint k) returns (uint)``: compute ``(x + y) % k`` where the addition is performed with
arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed
with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` or ``address payable``
- ``super``: the contract one level higher in the inheritance hierarchy
- ``selfdestruct(address payable recipient)``: destroy the current contract, sending its funds to the given address
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
- ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure
- ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`,
returns ``false`` on failure
- ``<address payable>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
- ``type(C).name`` (``string``): the name of the contract
- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information<meta-type>`.
@ -705,16 +790,20 @@ Modifiers
- ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
- ``anonymous`` for events: Does not store event signature as topic.
- ``indexed`` for event parameters: Stores the parameter as topic.
- ``virtual`` for functions and modifiers: Allows the function's or modifier's
behaviour to be changed in derived contracts.
- ``override``: States that this function, modifier or public state variable changes
the behaviour of a function or modifier in a base contract.
Reserved Keywords
=================
These keywords are reserved in Solidity. They might become part of the syntax in the future:
``abstract``, ``after``, ``alias``, ``apply``, ``auto``, ``case``, ``catch``, ``copyof``, ``default``,
``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``,
``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
``mutable``, ``null``, ``of``, ``override``, ``partial``, ``promise``, ``reference``, ``relocatable``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``typedef``, ``typeof``,
``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``,
``unchecked``.
Language Grammar

View File

@ -49,7 +49,7 @@ The following example shows a contract and a function using all available tags.
.. code:: solidity
pragma solidity ^0.5.6;
pragma solidity >=0.5.0 <0.7.0;
/// @title A simulator for trees
/// @author Larry A. Gardner
@ -84,7 +84,7 @@ Tag
``@notice`` Explain to an end user what this does contract, interface, function
``@dev`` Explain to a developer any extra details contract, interface, function
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function
``@return`` Documents the return type of a contract's function function
``@return`` Documents the return variables of a contract's function function
=========== =============================================================================== =============================
If your function returns multiple values, like ``(int quotient, int remainder)``

View File

@ -20,14 +20,12 @@ to take too much care, but if you manage your bank account using that web servic
you should be more careful.
This section will list some pitfalls and general security recommendations but
can, of course, never be complete.
Also, keep in mind that even if your
smart contract code is bug-free, the compiler or the platform itself might
have a bug. A list of some publicly known security-relevant bugs of the compiler
can be found in the
:ref:`list of known bugs<known_bugs>`, which is also machine-readable. Note
that there is a bug bounty program that covers the code generator of the
Solidity compiler.
can, of course, never be complete. Also, keep in mind that even if your smart
contract code is bug-free, the compiler or the platform itself might have a
bug. A list of some publicly known security-relevant bugs of the compiler can
be found in the :ref:`list of known bugs<known_bugs>`, which is also
machine-readable. Note that there is a bug bounty program that covers the code
generator of the Solidity compiler.
As always, with open source documentation, please help us extend this section
(especially, some examples would not hurt)!
@ -138,12 +136,16 @@ Sending and Receiving Ether
to move Ether without creating a message call. One way is to simply "mine to"
the contract address and the second way is using ``selfdestruct(x)``.
- If a contract receives Ether (without a function being called), the fallback function is executed.
If it does not have a fallback function, the Ether will be rejected (by throwing an exception).
During the execution of the fallback function, the contract can only rely
on the "gas stipend" it is passed (2300 gas) being available to it at that time. This stipend is not enough to modify storage
(do not take this for granted though, the stipend might change with future hard forks).
To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function
- If a contract receives Ether (without a function being called),
either the :ref:`receive Ether <receive-ether-function>`
or the :ref:`fallback <fallback-function>` function is executed.
If it does not have a receive nor a fallback function, the Ether will be
rejected (by throwing an exception). During the execution of one of these
functions, the contract can only rely on the "gas stipend" it is passed (2300
gas) being available to it at that time. This stipend is not enough to modify
storage (do not take this for granted though, the stipend might change with
future hard forks). To be sure that your contract can receive Ether in that
way, check the gas requirements of the receive and fallback functions
(for example in the "details" section in Remix).
- There is a way to forward more gas to the receiving contract using
@ -159,17 +161,22 @@ Sending and Receiving Ether
- If you want to send Ether using ``address.transfer``, there are certain details to be aware of:
1. If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract.
2. Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the call
depth, they can force the transfer to fail; take this possibility into account or use ``send`` and make sure to always check its return value. Better yet,
write your contract using a pattern where the recipient can withdraw Ether instead.
3. Sending Ether can also fail because the execution of the recipient contract
requires more than the allotted amount of gas (explicitly by using ``require``,
``assert``, ``revert``, ``throw`` or
because the operation is just too expensive) - it "runs out of gas" (OOG).
If you use ``transfer`` or ``send`` with a return value check, this might provide a
means for the recipient to block progress in the sending contract. Again, the best practice here is to use
a :ref:`"withdraw" pattern instead of a "send" pattern <withdrawal_pattern>`.
1. If the recipient is a contract, it causes its receive or fallback function
to be executed which can, in turn, call back the sending contract.
2. Sending Ether can fail due to the call depth going above 1024. Since the
caller is in total control of the call depth, they can force the
transfer to fail; take this possibility into account or use ``send`` and
make sure to always check its return value. Better yet, write your
contract using a pattern where the recipient can withdraw Ether instead.
3. Sending Ether can also fail because the execution of the recipient
contract requires more than the allotted amount of gas (explicitly by
using :ref:`require <assert-and-require>`, :ref:`assert <assert-and-require>`,
:ref:`revert <assert-and-require>` or because the
operation is too expensive) - it "runs out of gas" (OOG). If you
use ``transfer`` or ``send`` with a return value check, this might
provide a means for the recipient to block progress in the sending
contract. Again, the best practice here is to use a :ref:`"withdraw"
pattern instead of a "send" pattern <withdrawal_pattern>`.
Callstack Depth
===============
@ -181,8 +188,7 @@ before they interact with your contract.
Note that ``.send()`` does **not** throw an exception if the call stack is
depleted but rather returns ``false`` in that case. The low-level functions
``.call()``, ``.callcode()``, ``.delegatecall()`` and ``.staticcall()`` behave
in the same way.
``.call()``, ``.delegatecall()`` and ``.staticcall()`` behave in the same way.
tx.origin
=========
@ -207,11 +213,11 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
}
}
Now someone tricks you into sending ether to the address of this attack wallet:
Now someone tricks you into sending Ether to the address of this attack wallet:
::
pragma solidity >=0.5.0 <0.7.0;
pragma solidity ^0.6.0;
interface TxUserWallet {
function transferTo(address payable dest, uint amount) external;
@ -224,7 +230,7 @@ Now someone tricks you into sending ether to the address of this attack wallet:
owner = msg.sender;
}
function() external {
receive() external payable {
TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
}
}
@ -247,8 +253,7 @@ In general, read about the limits of two's complement representation, which even
more special edge cases for signed numbers.
Try to use ``require`` to limit the size of inputs to a reasonable range and use the
:ref:`SMT checker<smt_checker>` to find potential overflows, or
use a library like
:ref:`SMT checker<smt_checker>` to find potential overflows, or use a library like
`SafeMath <https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol>`_
if you want all overflows to cause a revert.
@ -259,16 +264,16 @@ Code such as ``require((balanceOf[_to] + _value) >= balanceOf[_to])`` can also h
Clearing Mappings
=================
The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only key-value data structure that
does not keep track of the keys that were assigned a non-zero value.
Because of that, cleaning a mapping without extra information about the written
keys is not possible.
The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only
key-value data structure that does not keep track of the keys that were
assigned a non-zero value. Because of that, cleaning a mapping without extra
information about the written keys is not possible.
If a ``mapping`` is used as the base type of a dynamic storage array, deleting
or popping the array will have no effect over the ``mapping`` elements.
The same happens, for example, if a ``mapping`` is used as the type of a member
field of a ``struct`` that is the base type of a dynamic storage array.
The ``mapping`` is also ignored in assignments of structs or arrays containing
a ``mapping``.
or popping the array will have no effect over the ``mapping`` elements. The
same happens, for example, if a ``mapping`` is used as the type of a member
field of a ``struct`` that is the base type of a dynamic storage array. The
``mapping`` is also ignored in assignments of structs or arrays containing a
``mapping``.
::
@ -278,7 +283,8 @@ a ``mapping``.
mapping (uint => uint)[] array;
function allocate(uint _newMaps) public {
array.length += _newMaps;
for (uint i = 0; i < _newMaps; i++)
array.push();
}
function writeMap(uint _map, uint _key, uint _value) public {
@ -306,8 +312,7 @@ another call to ``writeMap``.
If your ``mapping`` information must be deleted, consider using a library similar to
`iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_,
allowing you to traverse the keys and delete their values in the appropriate
``mapping``.
allowing you to traverse the keys and delete their values in the appropriate ``mapping``.
Minor Details
=============
@ -427,17 +432,11 @@ The SMTChecker traverses the Solidity AST creating and collecting program constr
When it encounters a verification target, an SMT solver is invoked to determine the outcome.
If a check fails, the SMTChecker provides specific input values that lead to the failure.
For more details on how the SMT encoding works internally, see the paper
`SMT-based Verification of Solidity Smart Contracts <https://github.com/leonardoalt/text/blob/master/solidity_isola_2018/main.pdf>`_.
While the SMTChecker encodes Solidity code into SMT constraints, it contains two
reasoning engines that use that encoding in different ways.
Abstraction and False Positives
===============================
The SMTChecker implements abstractions in an incomplete and sound way: If a bug
is reported, it might be a false positive introduced by abstractions (due to
erasing knowledge or using a non-precise type). If it determines that a
verification target is safe, it is indeed safe, that is, there are no false
negatives (unless there is a bug in the SMTChecker).
SMT Encoding
============
The SMT encoding tries to be as precise as possible, mapping Solidity types
and expressions to their closest `SMT-LIB <http://smtlib.cs.uiowa.edu/>`_
@ -451,13 +450,60 @@ representation, as shown in the table below.
|intN, uintN, address, |Integer |LIA, NIA |
|bytesN, enum | | |
+-----------------------+--------------+-----------------------------+
|array, mapping |Array |Arrays |
|array, mapping, bytes, |Array |Arrays |
|string | | |
+-----------------------+--------------+-----------------------------+
|other types |Integer |LIA |
+-----------------------+--------------+-----------------------------+
Types that are not yet supported are abstracted by a single 256-bit unsigned integer,
where their unsupported operations are ignored.
Types that are not yet supported are abstracted by a single 256-bit unsigned
integer, where their unsupported operations are ignored.
For more details on how the SMT encoding works internally, see the paper
`SMT-based Verification of Solidity Smart Contracts <https://github.com/leonardoalt/text/blob/master/solidity_isola_2018/main.pdf>`_.
Model Checking Engines
======================
The SMTChecker module implements two different reasoning engines that use the
SMT encoding above, a Bounded Model Checker (BMC) and a system of Constrained
Horn Clauses (CHC). Both engines are currently under development, and have
different characteristics.
Bounded Model Checker (BMC)
---------------------------
The BMC engine analyzes functions in isolation, that is, it does not take the
overall behavior of the contract throughout many transactions into account when
analyzing each function. Loops are also ignored in this engine at the moment.
Internal function calls are inlined as long as they are not recursive, direct
or indirectly. External function calls are inlined if possible, and knowledge
that is potentially affected by reentrancy is erased.
The characteristics above make BMC easily prone to reporting false positives,
but it is also lightweight and should be able to quickly find small local bugs.
Constrained Horn Clauses (CHC)
------------------------------
The Solidity contract's Control Flow Graph (CFG) is modelled as a system of
Horn clauses, where the lifecycle of the contract is represented by a loop
that can visit every public/external function non-deterministically. This way,
the behavior of the entire contract over an unbounded number of transactions
is taken into account when analyzing any function. Loops are fully supported
by this engine. Function calls are currently unsupported.
The CHC engine is much more powerful than BMC in terms of what it can prove,
and might require more computing resources.
Abstraction and False Positives
===============================
The SMTChecker implements abstractions in an incomplete and sound way: If a bug
is reported, it might be a false positive introduced by abstractions (due to
erasing knowledge or using a non-precise type). If it determines that a
verification target is safe, it is indeed safe, that is, there are no false
negatives (unless there is a bug in the SMTChecker).
Function calls to the same contract (or base contracts) are inlined when
possible, that is, when their implementation is available.

View File

@ -19,3 +19,4 @@ If something is missing here, please contact us on
assembly.rst
miscellaneous.rst
050-breaking-changes.rst
060-breaking-changes.rst

View File

@ -67,6 +67,11 @@ Function Modifiers
Function modifiers can be used to amend the semantics of functions in a declarative way
(see :ref:`modifiers` in the contracts section).
Overloading, that is, having the same modifier name with different parameters,
is not possible.
Like functions, modifiers can be :ref:`overridden <modifier-overriding>`.
::
pragma solidity >=0.4.22 <0.7.0;

View File

@ -89,20 +89,20 @@ Blank lines may be omitted between groups of related one-liners (such as stub fu
Yes::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity ^0.6.0;
contract A {
function spam() public pure;
function ham() public pure;
abstract contract A {
function spam() public virtual pure;
function ham() public virtual pure;
}
contract B is A {
function spam() public pure {
function spam() public pure override {
// ...
}
function ham() public pure {
function ham() public pure override {
// ...
}
}
@ -111,11 +111,17 @@ No::
pragma solidity >=0.4.0 <0.7.0;
contract A {
function spam() public pure {
abstract contract A {
function spam() virtual pure public;
function ham() public virtual pure;
}
contract B is A {
function spam() public pure override {
// ...
}
function ham() public pure {
function ham() public pure override {
// ...
}
}
@ -273,6 +279,7 @@ Ordering helps readers identify which functions they can call and to find the co
Functions should be grouped according to their visibility and ordered:
- constructor
- receive function (if exists)
- fallback function (if exists)
- external
- public
@ -283,14 +290,18 @@ Within a grouping, place the ``view`` and ``pure`` functions last.
Yes::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity ^0.6.0;
contract A {
constructor() public {
// ...
}
function() external {
receive() external payable {
// ...
}
fallback() external {
// ...
}
@ -322,7 +333,10 @@ No::
// External functions
// ...
function() external {
fallback() external {
// ...
}
receive() external payable {
// ...
}
@ -384,20 +398,29 @@ No::
y = 2;
long_variable = 3;
Don't include a whitespace in the fallback function:
Don't include a whitespace in the receive and fallback functions:
Yes::
function() external {
receive() external payable {
...
}
fallback() external {
...
}
No::
function () external {
receive () external payable {
...
}
fallback () external {
...
}
Control Structures
==================
@ -547,31 +570,30 @@ No::
function increment(uint x) public pure returns (uint) {
return x + 1;}
You should explicitly label the visibility of all functions, including constructors.
The modifier order for a function should be:
1. Visibility
2. Mutability
3. Virtual
4. Override
5. Custom modifiers
Yes::
function explicitlyPublic(uint val) public {
doSomething();
function balance(uint from) public view override returns (uint) {
return balanceOf[from];
}
No::
function implicitlyPublic(uint val) {
doSomething();
}
The visibility modifier for a function should come before any custom
modifiers.
Yes::
function kill() public onlyowner {
selfdestruct(owner);
}
No::
function balance(uint from) public override view returns (uint) {
return balanceOf[from];
}
function kill() onlyowner public {
selfdestruct(owner);
}

View File

@ -8,9 +8,8 @@ Conversions between Elementary Types
Implicit Conversions
--------------------
If an operator is applied to different types, the compiler tries to implicitly
convert one of the operands to the type of the other (the same is true for assignments).
This means that operations are always performed in the type of one of the operands.
An implicit type conversion is automatically applied by the compiler in some cases
during assignments, when passing arguments to functions and when applying operators.
In general, an implicit conversion between value-types is possible if it makes
sense semantically and no information is lost.
@ -18,7 +17,27 @@ For example, ``uint8`` is convertible to
``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256``,
because ``uint256`` cannot hold values such as ``-1``.
For more details, please consult the sections about the types themselves.
If an operator is applied to different types, the compiler tries to implicitly
convert one of the operands to the type of the other (the same is true for assignments).
This means that operations are always performed in the type of one of the operands.
For more details about which implicit conversions are possible,
please consult the sections about the types themselves.
In the example below, ``y`` and ``z``, the operands of the addition,
do not have the same type, but ``uint8`` can
be implicitly converted to ``uint16`` and not vice-versa. Because of that,
``y`` is converted to the type of ``z`` before the addition is performed
in the ``uint16`` type. The resulting type of the expression ``y + z`` is ``uint16`.
Because it is assigned to a variable of type ``uint32`` another implicit conversion
is performed after the addition.
::
uint8 y;
uint16 z;
uint32 x = y + z;
Explicit Conversions
--------------------
@ -128,3 +147,5 @@ As described in :ref:`address_literals`, hex literals of the correct size that p
test are of ``address`` type. No other literals can be implicitly converted to the ``address`` type.
Explicit conversions from ``bytes20`` or any integer type to ``address`` result in ``address payable``.
An ``address a`` can be converted to ``address payable`` via ``payable(a)``.

View File

@ -5,16 +5,17 @@ Mapping Types
=============
Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables
are declared as a mapping type using the syntax ``mapping (_KeyType => _ValueType) _VariableModifiers _VariableName``.
The ``_KeyType`` can be any elementary type. This means it can be any of
the built-in value types plus ``bytes`` and ``string``. User-defined
or complex types like contract types, enums, mappings, structs and any array type
of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``.
The ``_KeyType`` can be any
built-in value type plus ``bytes`` and ``string``. User-defined
or complex types such as contract types, enums, mappings, structs or array types
apart from ``bytes`` and ``string`` are not allowed.
``_ValueType`` can be any type, including mappings.
``_ValueType`` can be any type, including mappings, arrays and structs.
You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised
such that every possible key exists and is mapped to a value whose
byte-representation is all zeros, a type's :ref:`default value <default-value>`. The similarity ends there, the key data is not stored in a
byte-representation is all zeros, a type's :ref:`default value <default-value>`.
The similarity ends there, the key data is not stored in a
mapping, only its ``keccak256`` hash is used to look up the value.
Because of this, mappings do not have a length or a concept of a key or
@ -59,7 +60,8 @@ contract that returns the value at the specified address.
}
}
The example below is a simplified version of an `ERC20 token <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol>`_.
The example below is a simplified version of an
`ERC20 token <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol>`_.
``_allowances`` is an example of a mapping type inside another mapping type.
The example below uses ``_allowances`` to record the amount someone else is allowed to withdraw from your account.
@ -111,16 +113,18 @@ The example below uses ``_allowances`` to record the amount someone else is allo
Iterable Mappings
-----------------
Mappings are not iterable, but it is possible to implement a data structure on
You cannot iterate over mappings, i.e. you cannot enumerate their keys.
It is possible, though, to implement a data structure on
top of them and iterate over that. For example, the code below implements an
``IterableMapping`` library that the ``User`` contract then adds data too, and
the ``sum`` function iterates over to sum all the values.
::
pragma solidity >=0.4.0 <0.7.0;
pragma solidity >=0.5.99 <0.7.0;
library IterableMapping {
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }
struct itmap {
mapping(uint => IndexValue) data;
@ -128,16 +132,15 @@ the ``sum`` function iterates over to sum all the values.
uint size;
}
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }
library IterableMapping {
function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {
uint keyIndex = self.data[key].keyIndex;
self.data[key].value = value;
if (keyIndex > 0)
return true;
else {
keyIndex = self.keys.length++;
self.keys.push();
keyIndex = self.keys.length;
self.data[key].keyIndex = keyIndex + 1;
self.keys[keyIndex].key = key;
self.size++;
@ -182,22 +185,27 @@ the ``sum`` function iterates over to sum all the values.
// How to use it
contract User {
// Just a struct holding our data.
IterableMapping.itmap data;
itmap data;
// Apply library functions to the data type.
using IterableMapping for itmap;
// Insert something
function insert(uint k, uint v) public returns (uint size) {
// Actually calls itmap_impl.insert, auto-supplying the first parameter for us.
IterableMapping.insert(data, k, v);
// We can still access members of the struct - but we should take care not to mess with them.
// This calls IterableMapping.insert(data, k, v)
data.insert(k, v);
// We can still access members of the struct,
// but we should take care not to mess with them.
return data.size;
}
// Computes the sum of all stored data.
function sum() public view returns (uint s) {
for (uint i = IterableMapping.iterate_start(data);
IterableMapping.iterate_valid(data, i);
i = IterableMapping.iterate_next(data, i)) {
(, uint value) = IterableMapping.iterate_get(data, i);
for (
uint i = data.iterate_start();
data.iterate_valid(i);
i = data.iterate_next(i)
) {
(, uint value) = data.iterate_get(i);
s += value;
}
}

View File

@ -3,9 +3,14 @@
Operators Involving LValues
===========================
If ``a`` is an LValue (i.e. a variable or something that can be assigned to), the following operators are available as shorthands:
If ``a`` is an LValue (i.e. a variable or something that can be assigned to), the
following operators are available as shorthands:
``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, ``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but return the value after the change.
``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``,
``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent
to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value
of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but
return the value after the change.
.. _delete:
@ -19,12 +24,20 @@ initial value. ``delete a[x]`` deletes the item at index ``x`` of the array and
all other elements and the length of the array untouched. This especially means that it leaves
a gap in the array. If you plan to remove items, a :ref:`mapping <mapping-types>` is probably a better choice.
For structs, it assigns a struct with all members reset. In other words, the value of ``a`` after ``delete a`` is the same as if ``a`` would be declared without assignment, with the following caveat:
For structs, it assigns a struct with all members reset. In other words,
the value of ``a`` after ``delete a`` is the same as if ``a`` would be declared
without assignment, with the following caveat:
``delete`` has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown). So if you delete a struct, it will reset all members that are not mappings and also recurse into the members unless they are mappings. However, individual keys and what they map to can be deleted: If ``a`` is a mapping, then ``delete a[x]`` will delete the value stored at ``x``.
``delete`` has no effect on mappings (as the keys of mappings may be arbitrary and
are generally unknown). So if you delete a struct, it will reset all members that
are not mappings and also recurse into the members unless they are mappings.
However, individual keys and what they map to can be deleted: If ``a`` is a
mapping, then ``delete a[x]`` will delete the value stored at ``x``.
It is important to note that ``delete a`` really behaves like an assignment to ``a``, i.e. it stores a new object in ``a``.
This distinction is visible when ``a`` is reference variable: It will only reset ``a`` itself, not the
It is important to note that ``delete a`` really behaves like an
assignment to ``a``, i.e. it stores a new object in ``a``.
This distinction is visible when ``a`` is reference variable: It
will only reset ``a`` itself, not the
value it referred to previously.
::

View File

@ -11,7 +11,8 @@ a variable of value type is used. Because of that, reference types have to be ha
more carefully than value types. Currently, reference types comprise structs,
arrays and mappings. If you use a reference type, you always have to explicitly
provide the data area where the type is stored: ``memory`` (whose lifetime is limited
to a function call), ``storage`` (the location where the state variables are stored)
to an external function call), ``storage`` (the location where the state variables
are stored, where the lifetime is limited to the lifetime of a contract)
or ``calldata`` (special data location that contains the function arguments,
only available for external function call parameters).
@ -23,7 +24,7 @@ while assignments inside the same data location only copy in some cases for stor
Data location
-------------
Every reference type, i.e. *arrays* and *structs*, has an additional
Every reference type has an additional
annotation, the "data location", about where it is stored. There are three data locations:
``memory``, ``storage`` and ``calldata``. Calldata is only valid for parameters of external contract
functions and is required for this type of parameter. Calldata is a non-modifiable,
@ -42,24 +43,34 @@ Data location and assignment behaviour
Data locations are not only relevant for persistency of data, but also for the semantics of assignments:
* Assignments between ``storage`` and ``memory`` (or from ``calldata``) always create an independent copy.
* Assignments from ``memory`` to ``memory`` only create references. This means that changes to one memory variable are also visible in all other memory variables that refer to the same data.
* Assignments from ``storage`` to a local storage variable also only assign a reference.
* All other assignments to ``storage`` always copy. Examples for this case are assignments to state variables or to members of local variables of storage struct type, even if the local variable itself is just a reference.
* Assignments between ``storage`` and ``memory`` (or from ``calldata``)
always create an independent copy.
* Assignments from ``memory`` to ``memory`` only create references. This means
that changes to one memory variable are also visible in all other memory
variables that refer to the same data.
* Assignments from ``storage`` to a **local** storage variable also only
assign a reference.
* All other assignments to ``storage`` always copy. Examples for this
case are assignments to state variables or to members of local
variables of storage struct type, even if the local variable
itself is just a reference.
::
pragma solidity >=0.4.0 <0.7.0;
contract C {
uint[] x; // the data location of x is storage
// The data location of x is storage.
// This is the only place where the
// data location can be omitted.
uint[] x;
// the data location of memoryArray is memory
// The data location of memoryArray is memory.
function f(uint[] memory memoryArray) public {
x = memoryArray; // works, copies the whole array to storage
uint[] storage y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.length = 2; // fine, modifies x through y
y.pop(); // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated:
@ -96,7 +107,7 @@ as C.
Indices are zero-based, and access is in the opposite direction of the
declaration.
For example, if you have a variable ``uint[][5] x memory``, you access the
For example, if you have a variable ``uint[][5] memory x``, you access the
second ``uint`` in the third dynamic array using ``x[2][1]``, and to access the
third dynamic array, use ``x[2]``. Again,
if you have an array ``T[5] a`` for a type ``T`` that can also be an array,
@ -109,8 +120,9 @@ restrictions for types apply, in that mappings can only be stored in the
It is possible to mark state variable arrays ``public`` and have Solidity create a :ref:`getter <visibility-and-getters>`.
The numeric index becomes a required parameter for the getter.
Accessing an array past its end causes a failing assertion. You can use the ``.push()`` method to append a new element at the end or assign to the ``.length`` :ref:`member <array-members>` to change the size (see below for caveats).
method or increase the ``.length`` :ref:`member <array-members>` to add elements.
Accessing an array past its end causes a failing assertion. Methods ``.push()`` and ``.push(value)`` can be used
to append a new element at the end of the array, where ``.push()`` appends a zero-initialized element and returns
a reference to it.
``bytes`` and ``strings`` as Arrays
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -121,9 +133,11 @@ length or index access.
Solidity does not have string manipulation functions, but there are
third-party string libraries. You can also compare two strings by their keccak256-hash using
``keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))`` and concatenate two strings using ``abi.encodePacked(s1, s2)``.
``keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))`` and
concatenate two strings using ``abi.encodePacked(s1, s2)``.
You should use ``bytes`` over ``byte[]`` because it is cheaper, since ``byte[]`` adds 31 padding bytes between the elements. As a general rule,
You should use ``bytes`` over ``byte[]`` because it is cheaper,
since ``byte[]`` adds 31 padding bytes between the elements. As a general rule,
use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length
string (UTF-8) data. If you can limit the length to a certain number of bytes,
always use one of the value types ``bytes1`` to ``bytes32`` because they are much cheaper.
@ -139,9 +153,10 @@ always use one of the value types ``bytes1`` to ``bytes32`` because they are muc
Allocating Memory Arrays
^^^^^^^^^^^^^^^^^^^^^^^^
You must use the ``new`` keyword to create arrays with a runtime-dependent length in memory.
As opposed to storage arrays, it is **not** possible to resize memory arrays (e.g. by assigning to
the ``.length`` member). You either have to calculate the required size in advance
Memory arrays with dynamic length can be created using the ``new`` operator.
As opposed to storage arrays, it is **not** possible to resize memory arrays (e.g.
the ``.push`` member functions are not available).
You either have to calculate the required size in advance
or create a new memory array and copy every element.
::
@ -171,7 +186,9 @@ type of the array.
Array literals are always statically-sized memory arrays.
In the example below, the type of ``[1, 2, 3]`` is
``uint8[3] memory``. Because the type of each of these constants is ``uint8``, if you want the result to be a ``uint[3] memory`` type, you need to convert the first element to ``uint``.
``uint8[3] memory``. Because the type of each of these constants is ``uint8``, if
you want the result to be a ``uint[3] memory`` type, you need to convert
the first element to ``uint``.
::
@ -186,7 +203,8 @@ In the example below, the type of ``[1, 2, 3]`` is
}
}
Fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, i.e. the following is not possible:
Fixed size memory arrays cannot be assigned to dynamically-sized
memory arrays, i.e. the following is not possible:
::
@ -213,32 +231,34 @@ Array Members
**length**:
Arrays have a ``length`` member that contains their number of elements.
The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created.
For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array.
Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion.
Increasing the length adds new zero-initialised elements to the array.
Reducing the length performs an implicit :ref:`delete<delete>` on each of the
removed elements. If you try to resize a non-dynamic array that isn't in
storage, you receive a ``Value must be an lvalue`` error.
**push**:
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length.
The length of memory arrays is fixed (but dynamic, i.e. it can depend on
runtime parameters) once they are created.
**push()**:
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function
called ``push()`` that you can use to append a zero-initialised element at the end of the array.
It returns a reference to the element, so that it can be used like
``x.push().t = 2`` or ``x.push() = b``.
**push(x)**:
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function
called ``push(x)`` that you can use to append a given element at the end of the array.
The function returns nothing.
**pop**:
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:`delete<delete>` on the removed element.
.. warning::
If you use ``.length--`` on an empty array, it causes an underflow and
thus sets the length to ``2**256-1``.
Dynamic storage arrays and ``bytes`` (not ``string``) have a member
function called ``pop`` that you can use to remove an element from the
end of the array. This also implicitly calls :ref:`delete<delete>` on the removed element.
.. note::
Increasing the length of a storage array has constant gas costs because
storage is assumed to be zero-initialised, while decreasing
the length has at least linear cost (but in most cases worse than linear),
because it includes explicitly clearing the removed
Increasing the length of a storage array by calling ``push()``
has constant gas costs because storage is zero-initialised,
while decreasing the length by calling ``pop()`` has a
cost that depends on the "size" of the element being removed.
If that element is an array, it can be very costly, because
it includes explicitly clearing the removed
elements similar to calling :ref:`delete<delete>` on them.
.. note::
It is not yet possible to use arrays of arrays in external functions
(but they are supported in public functions).
To use arrays of arrays in external (instead of public) functions, you need to
activate ABIEncoderV2.
.. note::
In EVM versions before Byzantium, it was not possible to access
@ -291,8 +311,15 @@ Array Members
}
function changeFlagArraySize(uint newSize) public {
// if the new size is smaller, removed array elements will be cleared
m_pairsOfFlags.length = newSize;
// using push and pop is the only way to change the
// length of an array
if (newSize < m_pairsOfFlags.length) {
while (m_pairsOfFlags.length > newSize)
m_pairsOfFlags.pop();
} else if (newSize > m_pairsOfFlags.length) {
while (m_pairsOfFlags.length < newSize)
m_pairsOfFlags.push();
}
}
function clear() public {
@ -300,7 +327,7 @@ Array Members
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// identical effect here
m_pairsOfFlags.length = 0;
m_pairsOfFlags = new bool[2][](0);
}
bytes m_byteData;
@ -309,13 +336,15 @@ Array Members
// byte arrays ("bytes") are different as they are stored without padding,
// but can be treated identical to "uint8[]"
m_byteData = data;
m_byteData.length += 7;
for (uint i = 0; i < 7; i++)
m_byteData.push();
m_byteData[3] = 0x08;
delete m_byteData[2];
}
function addFlag(bool[2] memory flag) public returns (uint) {
return m_pairsOfFlags.push(flag);
m_pairsOfFlags.push(flag);
return m_pairsOfFlags.length;
}
function createMemoryArray(uint size) public pure returns (bytes memory) {
@ -334,6 +363,67 @@ Array Members
}
}
.. index:: ! array;slice
.. _array-slices:
Array Slices
------------
Array slices are a view on a contiguous portion of an array.
They are written as ``x[start:end]``, where ``start`` and
``end`` are expressions resulting in a uint256 type (or
implicitly convertible to it). The first element of the
slice is ``x[start]`` and the last element is ``x[end - 1]``.
If ``start`` is greater than ``end`` or if ``end`` is greater
than the length of the array, an exception is thrown.
Both ``start`` and ``end`` are optional: ``start`` defaults
to ``0`` and ``end`` defaults to the length of the array.
Array slices do not have any members. They are implicitly
convertible to arrays of their underlying type
and support index access. Index access is not absolute
in the underlying array, but relative to the start of
the slice.
Array slices do not have a type name which means
no variable can have an array slices as type,
they only exist in intermediate expressions.
.. note::
As of now, array slices are only implemented for calldata arrays.
Array slices are useful to ABI-decode secondary data passed in function parameters:
::
pragma solidity >=0.4.99 <0.7.0;
contract Proxy {
/// Address of the client contract managed by proxy i.e., this contract
address client;
constructor(address _client) public {
client = _client;
}
/// Forward call to "setOwner(address)" that is implemented by client
/// after doing basic validation on the address argument.
function forward(bytes calldata _payload) external {
bytes4 sig = abi.decode(_payload[:4], (bytes4));
if (sig == bytes4(keccak256("setOwner(address)"))) {
address owner = abi.decode(_payload[4:], (address));
require(owner != address(0), "Address of owner cannot be zero.");
}
(bool status,) = client.delegatecall(_payload);
require(status, "Forwarded call failed.");
}
}
.. index:: ! struct, ! type;struct
@ -349,13 +439,18 @@ shown in the following example:
pragma solidity >=0.4.11 <0.7.0;
contract CrowdFunding {
// Defines a new type with two fields.
// Declaring a struct outside of a contract allows
// it to be shared by multiple contracts.
// Here, this is not really needed.
struct Funder {
address addr;
uint amount;
}
contract CrowdFunding {
// Structs can also be defined inside contracts, which makes them
// visible only there and in derived contracts.
struct Campaign {
address payable beneficiary;
uint fundingGoal;

View File

@ -123,8 +123,9 @@ results in the same sign as its left operand (or zero) and ``a % n == -(-a % n)`
Exponentiation
^^^^^^^^^^^^^^
Exponentiation is only available for unsigned types. Please take care that the types
you are using are large enough to hold the result and prepare for potential wrapping behaviour.
Exponentiation is only available for unsigned types in the exponent. The resulting type
of an exponentiation is always equal to the type of the base. Please take care that it is
large enough to hold the result and prepare for potential wrapping behaviour.
.. note::
Note that ``0**0`` is defined by the EVM as ``1``.
@ -153,7 +154,7 @@ Operators:
defined in the latter. Generally, in floating point almost the entire space is used to represent the number, while only a small number of bits define
where the decimal point is.
.. index:: address, balance, send, call, callcode, delegatecall, staticcall, transfer
.. index:: address, balance, send, call, delegatecall, staticcall, transfer
.. _address:
@ -170,18 +171,22 @@ while a plain ``address`` cannot be sent Ether.
Type conversions:
Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable`` are
not possible (the only way to perform such a conversion is by using an intermediate conversion to ``uint160``).
Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable``
must be explicit via ``payable(<address>)``.
:ref:`Address literals<address_literals>` can be implicitly converted to ``address payable``.
Explicit conversions to and from ``address`` are allowed for integers, integer literals, ``bytes20`` and contract types with the following
caveat:
Conversions of the form ``address payable(x)`` are not allowed. Instead the result of a conversion of the form ``address(x)``
has the type ``address payable``, if ``x`` is of integer or fixed bytes type, a literal or a contract with a payable fallback function.
If ``x`` is a contract without payable fallback function, then ``address(x)`` will be of type ``address``.
The result of a conversion of the form ``address(x)``
has the type ``address payable``, if ``x`` is of integer or fixed bytes type,
a literal or a contract with a receive or payable fallback function.
If ``x`` is a contract without a receive or payable fallback function,
then ``address(x)`` will be of type ``address``.
In external function signatures ``address`` is used for both the ``address`` and the ``address payable`` type.
Only expressions of type ``address`` can be converted to type ``address payable`` via ``payable(<address>)``.
.. note::
It might very well be that you do not need to care about the distinction between ``address``
and ``address payable`` and just use ``address`` everywhere. For example,
@ -204,7 +209,7 @@ Operators:
.. note::
The distinction between ``address`` and ``address payable`` was introduced with version 0.5.0.
Also starting from that version, contracts do not derive from the address type, but can still be explicitly converted to
``address`` or to ``address payable``, if they have a payable fallback function.
``address`` or to ``address payable``, if they have a receive or payable fallback function.
.. _members-of-addresses:
@ -229,7 +234,7 @@ or if the Ether transfer is rejected by the receiving account. The ``transfer``
reverts on failure.
.. note::
If ``x`` is a contract address, its code (more specifically: its :ref:`fallback-function`, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception.
If ``x`` is a contract address, its code (more specifically: its :ref:`receive-ether-function`, if present, or otherwise its :ref:`fallback-function`, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception.
* ``send``
@ -309,10 +314,12 @@ Every :ref:`contract<contracts>` defines its own type.
You can implicitly convert contracts to contracts they inherit from.
Contracts can be explicitly converted to and from the ``address`` type.
Explicit conversion to and from the ``address payable`` type
is only possible if the contract type has a payable fallback function.
The conversion is still performed using ``address(x)`` and not
using ``address payable(x)``. You can find more information in the section about
Explicit conversion to and from the ``address payable`` type is only possible
if the contract type has a receive or payable fallback function. The conversion is still
performed using ``address(x)``. If the contract type does not have a receive or payable
fallback function, the conversion to ``address payable`` can be done using
``payable(address(x))``.
You can find more information in the section about
the :ref:`address type<address>`.
.. note::
@ -405,7 +412,7 @@ Octal literals do not exist in Solidity and leading zeros are invalid.
Decimal fraction literals are formed by a ``.`` with at least one number on
one side. Examples include ``1.``, ``.1`` and ``1.3``.
Scientific notation is also supported, where the base can have fractions, while the exponent cannot.
Scientific notation is also supported, where the base can have fractions and the exponent cannot.
Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``.
Underscores can be used to separate the digits of a numeric literal to aid readability.
@ -498,7 +505,14 @@ terminate the string literal. Newline only terminates the string literal if it i
Hexadecimal Literals
--------------------
Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double or single-quotes (``hex"001122FF"``), and they can also be split into multiple consecutive parts (``hex"00112233" hex"44556677"`` is equivalent to ``hex"0011223344556677"``). Their content must be a hexadecimal string and their value will be the binary representation of those values.
Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double
or single-quotes (``hex"001122FF"``, ``hex'0011_22_FF'``). Their content must be
hexadecimal digits which can optionally use a single underscore as separator between
byte boundaries. The value of the literal will be the binary representation
of the hexadecimal sequence.
Multiple hexadecimal literals separated by whitespace are concatenated into a single literal:
``hex"00112233" hex"44556677"`` is equivalent to ``hex"0011223344556677"``
Hexadecimal literals behave like :ref:`string literals <string_literals>` and have the same convertibility restrictions.
@ -545,6 +559,10 @@ subsequent unsigned integer values starting from ``0``.
}
}
.. note::
Enums can also be declared on the file level, outside of contract or library definitions.
.. index:: ! function type, ! type; function
.. _function_types:
@ -582,9 +600,6 @@ do not have a default.
Conversions:
A value of external function type can be explicitly converted to ``address``
resulting in the address of the contract of the function.
A function type ``A`` is implicitly convertible to a function type ``B`` if and only if
their parameter types are identical, their return types are identical,
their internal/external property is identical and the state mutability of ``A``
@ -616,8 +631,9 @@ just use ``f``, if you want to use its external form, use ``this.f``.
Members:
Public (or external) functions have the following members:
External (or public) functions have the following members:
* ``.address`` returns the address of the contract of the function.
* ``.selector`` returns the :ref:`ABI function selector <abi_function_selector>`
* ``.gas(uint)`` returns a callable function object which, when called, will send the specified amount of gas to the target function. See :ref:`External Function Calls <external-function-calls>` for more information.
* ``.value(uint)`` returns a callable function object which, when called, will send the specified amount of wei to the target function. See :ref:`External Function Calls <external-function-calls>` for more information.
@ -629,6 +645,7 @@ Example that shows how to use the members::
contract Example {
function f() public payable returns (bytes4) {
assert(this.f.address == address(this));
return this.f.selector;
}

View File

@ -14,7 +14,7 @@ Using the Commandline Compiler
One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler.
Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage.
If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``.
If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast-json --asm sourceFile.sol``.
Before you deploy your contract, activate the optimizer when compiling using ``solc --optimize --bin sourceFile.sol``.
By default, the optimizer will optimize the contract assuming it is called 200 times across its lifetime
@ -222,7 +222,6 @@ Input Description
"constantOptimizer": false,
// The new Yul optimizer. Mostly operates on the code of ABIEncoderV2.
// It can only be activated through the details here.
// This feature is still considered experimental.
"yul": false,
// Tuning options for the Yul optimizer.
"yulDetails": {
@ -236,10 +235,25 @@ Input Description
// Affects type checking and code generation. Can be homestead,
// tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin
"evmVersion": "byzantium",
// Optional: Debugging settings
"debug": {
// How to treat revert (and require) reason strings. Settings are
// "default", "strip", "debug" and "verboseDebug".
// "default" does not inject compiler-generated revert strings and keeps user-supplied ones.
// "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects
// "debug" injects strings for compiler-generated internal reverts (not yet implemented)
// "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented)
"revertStrings": "default"
}
// Metadata settings (optional)
"metadata": {
// Use only literal content and not URLs (false by default)
"useLiteralContent": true
"useLiteralContent": true,
// Use the given hash method for the metadata hash that is appended to the bytecode.
// The metadata hash can be removed from the bytecode via option "none".
// The other options are "ipfs" and "bzzr1".
// If the option is omitted, "ipfs" is used by default.
"bytecodeHash": "ipfs"
},
// Addresses of the libraries. If not all libraries are given here,
// it can result in unlinked objects whose output data is different.

View File

@ -100,7 +100,8 @@ Grammar::
Expression |
Switch |
ForLoop |
BreakContinue
BreakContinue |
Leave
FunctionDefinition =
'function' Identifier '(' TypedIdentifierList? ')'
( '->' TypedIdentifierList )? Block
@ -122,6 +123,7 @@ Grammar::
'for' Block Expression Block Block
BreakContinue =
'break' | 'continue'
Leave = 'leave'
FunctionCall =
Identifier '(' ( Expression ( ',' Expression )* )? ')'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9.]*
@ -167,6 +169,7 @@ In all other situations, expressions have to evaluate to exactly one value.
The ``continue`` and ``break`` statements can only be used inside loop bodies
and have to be in the same function as the loop (or both have to be at the
top level).
The ``leave`` statement can only be used inside a function.
The condition part of the for-loop has to evaluate to exactly one value.
Functions cannot be defined anywhere inside for loop init blocks.
@ -220,7 +223,7 @@ The two state objects are the global state object
blockchain) and the local state object (the state of local variables, i.e. a
segment of the stack in the EVM).
If the AST node is a statement, E returns the two state objects and a "mode",
which is used for the ``break`` and ``continue`` statements.
which is used for the ``break``, ``continue`` and ``leave`` statements.
If the AST node is an expression, E returns the two state objects and
as many values as the expression evaluates to.
@ -261,12 +264,13 @@ We will use a destructuring notation for the AST nodes.
G, L2, regular
E(G, L, <for { i1, ..., in } condition post body>: ForLoop) =
if n >= 1:
let G1, L1, mode = E(G, L, i1, ..., in)
// mode has to be regular due to the syntactic restrictions
let G1, L, mode = E(G, L, i1, ..., in)
// mode has to be regular or leave due to the syntactic restrictions
if mode is leave then
G1, L1 restricted to variables of L, leave
otherwise
let G2, L2, mode = E(G1, L1, for {} condition post body)
// mode has to be regular due to the syntactic restrictions
let L3 be the restriction of L2 to only variables of L
G2, L3, regular
G2, L2 restricted to variables of L, mode
else:
let G1, L1, v = E(G, L, condition)
if v is false:
@ -275,13 +279,20 @@ We will use a destructuring notation for the AST nodes.
let G2, L2, mode = E(G1, L, body)
if mode is break:
G2, L2, regular
otherwise if mode is leave:
G2, L2, leave
else:
G3, L3, mode = E(G2, L2, post)
if mode is leave:
G2, L3, leave
otherwise
E(G3, L3, for {} condition post body)
E(G, L, break: BreakContinue) =
G, L, break
E(G, L, continue: BreakContinue) =
G, L, continue
E(G, L, leave: Leave) =
G, L, leave
E(G, L, <if condition body>: If) =
let G0, L0, v = E(G, L, condition)
if v is true:

View File

@ -23,6 +23,7 @@
#pragma once
#include <iterator>
#include <libdevcore/Common.h>
#include <vector>
@ -49,14 +50,27 @@ template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U&& _
std::move(_b.begin(), _b.end(), std::back_inserter(_a));
return _a;
}
/// Concatenate the contents of a container onto a multiset
template <class U, class... T> std::multiset<T...>& operator+=(std::multiset<T...>& _a, U const& _b)
{
_a.insert(_b.begin(), _b.end());
return _a;
}
/// Concatenate the contents of a container onto a multiset, move variant.
template <class U, class... T> std::multiset<T...>& operator+=(std::multiset<T...>& _a, U&& _b)
{
for (auto&& x: _b)
_a.insert(std::move(x));
return _a;
}
/// Concatenate the contents of a container onto a set
template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U const& _b)
template <class U, class... T> std::set<T...>& operator+=(std::set<T...>& _a, U const& _b)
{
_a.insert(_b.begin(), _b.end());
return _a;
}
/// Concatenate the contents of a container onto a set, move variant.
template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U&& _b)
template <class U, class... T> std::set<T...>& operator+=(std::set<T...>& _a, U&& _b)
{
for (auto&& x: _b)
_a.insert(std::move(x));
@ -97,9 +111,27 @@ inline std::set<T> operator+(std::set<T>&& _a, U&& _b)
ret += std::forward<U>(_b);
return ret;
}
/// Remove one set from another one.
template <class T>
inline std::set<T>& operator-=(std::set<T>& _a, std::set<T> const& _b)
/// Remove the elements of a container from a set.
template <class C, class... T>
inline std::set<T...>& operator-=(std::set<T...>& _a, C const& _b)
{
for (auto const& x: _b)
_a.erase(x);
return _a;
}
template <class C, class... T>
inline std::set<T...> operator-(std::set<T...> const& _a, C const& _b)
{
auto result = _a;
result -= _b;
return result;
}
/// Remove the elements of a container from a multiset.
template <class C, class... T>
inline std::multiset<T...>& operator-=(std::multiset<T...>& _a, C const& _b)
{
for (auto const& x: _b)
_a.erase(x);
@ -109,6 +141,21 @@ inline std::set<T>& operator-=(std::set<T>& _a, std::set<T> const& _b)
namespace dev
{
template <class T, class U>
T convertContainer(U const& _from)
{
return T{_from.cbegin(), _from.cend()};
}
template <class T, class U>
T convertContainer(U&& _from)
{
return T{
std::make_move_iterator(_from.begin()),
std::make_move_iterator(_from.end())
};
}
// String conversion functions, mainly to/from hex/nibble/byte representations.
enum class WhenError
@ -260,6 +307,12 @@ bool contains(T const& _t, V const& _v)
return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v);
}
template <class T, class Predicate>
bool contains_if(T const& _t, Predicate const& _p)
{
return std::end(_t) != std::find_if(std::begin(_t), std::end(_t), _p);
}
/// Function that iterates over a vector, calling a function on each of its
/// elements. If that function returns a vector, the element is replaced by
/// the returned vector. During the iteration, the original vector is only valid

View File

@ -86,6 +86,7 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i)
m_items.emplace_back(_i);
if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty())
m_items.back().setLocation(m_currentSourceLocation);
m_items.back().m_modifierDepth = m_currentModifierDepth;
return back();
}

View File

@ -181,6 +181,8 @@ protected:
int m_deposit = 0;
langutil::SourceLocation m_currentSourceLocation;
public:
size_t m_currentModifierDepth = 0;
};
inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a)

View File

@ -146,6 +146,8 @@ public:
std::string toAssemblyText() const;
size_t m_modifierDepth = 0;
private:
AssemblyItemType m_type;
Instruction m_instruction; ///< Only valid if m_type == Operation

View File

@ -68,7 +68,7 @@ string langutil::to_string(ScannerError _errorCode)
{
case ScannerError::NoError: return "No error.";
case ScannerError::IllegalToken: return "Invalid token.";
case ScannerError::IllegalHexString: return "Expected even number of hex-nibbles within double-quotes.";
case ScannerError::IllegalHexString: return "Expected even number of hex-nibbles.";
case ScannerError::IllegalHexDigit: return "Hexadecimal digit missing or invalid.";
case ScannerError::IllegalCommentTerminator: return "Expected multi-line comment-terminator.";
case ScannerError::IllegalEscapeSequence: return "Invalid escape sequence.";
@ -784,13 +784,25 @@ Token Scanner::scanHexString()
char const quote = m_char;
advance(); // consume quote
LiteralScope literal(this, LITERAL_TYPE_STRING);
bool allowUnderscore = false;
while (m_char != quote && !isSourcePastEndOfInput())
{
char c = m_char;
if (!scanHexByte(c))
// can only return false if hex-byte is incomplete (only one hex digit instead of two)
return setError(ScannerError::IllegalHexString);
if (scanHexByte(c))
{
addLiteralChar(c);
allowUnderscore = true;
}
else if (c == '_')
{
advance();
if (!allowUnderscore || m_char == quote)
return setError(ScannerError::IllegalNumberSeparator);
allowUnderscore = false;
}
else
return setError(ScannerError::IllegalHexString);
}
if (m_char != quote)

View File

@ -72,6 +72,9 @@ void ElementaryTypeNameToken::assertDetails(Token _baseType, unsigned const& _fi
"No elementary type " + string(TokenTraits::toString(_baseType)) + to_string(_first) + "x" + to_string(_second) + "."
);
}
else
solAssert(_first == 0 && _second == 0, "Unexpected size arguments");
m_token = _baseType;
m_firstNumber = _first;
m_secondNumber = _second;

View File

@ -143,6 +143,7 @@ namespace langutil
/* Inline Assembly Operators */ \
T(AssemblyAssign, ":=", 2) \
/* Keywords */ \
K(Abstract, "abstract", 0) \
K(Anonymous, "anonymous", 0) \
K(As, "as", 0) \
K(Assembly, "assembly", 0) \
@ -157,6 +158,7 @@ namespace langutil
K(Emit, "emit", 0) \
K(Event, "event", 0) \
K(External, "external", 0) \
K(Fallback, "fallback", 0) \
K(For, "for", 0) \
K(Function, "function", 0) \
K(Hex, "hex", 0) \
@ -171,11 +173,13 @@ namespace langutil
K(Memory, "memory", 0) \
K(Modifier, "modifier", 0) \
K(New, "new", 0) \
K(Override, "override", 0) \
K(Payable, "payable", 0) \
K(Public, "public", 0) \
K(Pragma, "pragma", 0) \
K(Private, "private", 0) \
K(Pure, "pure", 0) \
K(Receive, "receive", 0) \
K(Return, "return", 0) \
K(Returns, "returns", 0) \
K(Storage, "storage", 0) \
@ -186,6 +190,7 @@ namespace langutil
K(Using, "using", 0) \
K(Var, "var", 0) \
K(View, "view", 0) \
K(Virtual, "virtual", 0) \
K(While, "while", 0) \
\
/* Ether subdenominations */ \
@ -228,7 +233,6 @@ namespace langutil
T(Identifier, nullptr, 0) \
\
/* Keywords reserved for future use. */ \
K(Abstract, "abstract", 0) \
K(After, "after", 0) \
K(Alias, "alias", 0) \
K(Apply, "apply", 0) \
@ -249,7 +253,6 @@ namespace langutil
K(Mutable, "mutable", 0) \
K(NullLiteral, "null", 0) \
K(Of, "of", 0) \
K(Override, "override", 0) \
K(Partial, "partial", 0) \
K(Promise, "promise", 0) \
K(Reference, "reference", 0) \
@ -311,7 +314,7 @@ namespace TokenTraits
constexpr bool isEtherSubdenomination(Token op) { return op == Token::SubWei || op == Token::SubSzabo || op == Token::SubFinney || op == Token::SubEther; }
constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; }
constexpr bool isReservedKeyword(Token op) { return (Token::Abstract <= op && op <= Token::Unchecked); }
constexpr bool isReservedKeyword(Token op) { return (Token::After <= op && op <= Token::Unchecked); }
inline Token AssignmentToBinaryOp(Token op)
{

View File

@ -2,7 +2,7 @@ if (EMSCRIPTEN)
# Specify which functions to export in soljson.js.
# Note that additional Emscripten-generated methods needed by solc-js are
# defined to be exported in cmake/EthCompilerSettings.cmake.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_solidity_license\",\"_solidity_version\",\"_solidity_compile\"]' -s RESERVED_FUNCTION_POINTERS=20")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_solidity_license\",\"_solidity_version\",\"_solidity_compile\",\"_solidity_alloc\",\"_solidity_free\",\"_solidity_reset\"]' -s RESERVED_FUNCTION_POINTERS=20")
add_executable(soljson libsolc.cpp libsolc.h)
target_link_libraries(soljson PRIVATE solidity)
else()

View File

@ -27,6 +27,8 @@
#include <libdevcore/Common.h>
#include <libdevcore/JSON.h>
#include <cstdlib>
#include <list>
#include <string>
#include "license.h"
@ -38,51 +40,78 @@ using namespace solidity;
namespace
{
ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = nullptr)
// The strings in this list must not be resized after they have been added here (via solidity_alloc()), because
// this may potentially change the pointer that was passed to the caller from solidity_alloc().
static list<string> solidityAllocations;
/// Find the equivalent to @p _data in the list of allocations of solidity_alloc(),
/// removes it from the list and returns its value.
///
/// If any invalid argument is being passed, it is considered a programming error
/// on the caller-side and hence, will call abort() then.
string takeOverAllocation(char const* _data)
{
for (auto iter = begin(solidityAllocations); iter != end(solidityAllocations); ++iter)
if (iter->data() == _data)
{
string chunk = move(*iter);
solidityAllocations.erase(iter);
return chunk;
}
abort();
}
/// Resizes a std::string to the proper length based on the occurrence of a zero terminator.
void truncateCString(string& _data)
{
size_t pos = _data.find('\0');
if (pos != string::npos)
_data.resize(pos);
}
ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback, void* _readContext)
{
ReadCallback::Callback readCallback;
if (_readCallback)
{
readCallback = [=](string const& _path)
readCallback = [=](string const& _kind, string const& _data)
{
char* contents_c = nullptr;
char* error_c = nullptr;
_readCallback(_path.c_str(), &contents_c, &error_c);
_readCallback(_readContext, _kind.data(), _data.data(), &contents_c, &error_c);
ReadCallback::Result result;
result.success = true;
if (!contents_c && !error_c)
{
result.success = false;
result.responseOrErrorMessage = "File not found.";
result.responseOrErrorMessage = "Callback not supported.";
}
if (contents_c)
{
result.success = true;
result.responseOrErrorMessage = string(contents_c);
free(contents_c);
result.responseOrErrorMessage = takeOverAllocation(contents_c);
}
if (error_c)
{
result.success = false;
result.responseOrErrorMessage = string(error_c);
free(error_c);
result.responseOrErrorMessage = takeOverAllocation(error_c);
}
truncateCString(result.responseOrErrorMessage);
return result;
};
}
return readCallback;
}
string compile(string _input, CStyleReadFileCallback _readCallback = nullptr)
string compile(string _input, CStyleReadFileCallback _readCallback, void* _readContext)
{
StandardCompiler compiler(wrapReadCallback(_readCallback));
return compiler.compile(std::move(_input));
StandardCompiler compiler(wrapReadCallback(_readCallback, _readContext));
return compiler.compile(move(_input));
}
}
static string s_outputBuffer;
extern "C"
{
extern char const* solidity_license() noexcept
@ -90,20 +119,40 @@ extern char const* solidity_license() noexcept
static string fullLicenseText = otherLicenses + licenseText;
return fullLicenseText.c_str();
}
extern char const* solidity_version() noexcept
{
return VersionString.c_str();
}
extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) noexcept
extern char* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) noexcept
{
s_outputBuffer = compile(_input, _readCallback);
return s_outputBuffer.c_str();
return solidityAllocations.emplace_back(compile(_input, _readCallback, _readContext)).data();
}
extern void solidity_free() noexcept
extern char* solidity_alloc(size_t _size) noexcept
{
try
{
return solidityAllocations.emplace_back(_size, '\0').data();
}
catch (...)
{
// most likely a std::bad_alloc(), if at all.
return nullptr;
}
}
extern void solidity_free(char* _data) noexcept
{
takeOverAllocation(_data);
}
extern void solidity_reset() noexcept
{
// This is called right before each compilation, but not at the end, so additional memory
// can be freed here.
yul::YulStringRepository::reset();
s_outputBuffer.clear();
solidityAllocations.clear();
}
}

View File

@ -23,6 +23,7 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
#define SOLC_NOEXCEPT noexcept
@ -34,31 +35,65 @@
extern "C" {
#endif
/// Callback used to retrieve additional source files.
/// Callback used to retrieve additional source files or data.
///
/// "Returns" two pointers that should be heap-allocated and are free'd by the caller.
typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error);
/// @param _context The readContext passed to solidity_compile. Can be NULL.
/// @param _kind The kind of callback (a string).
/// @param _data The data for the callback (a string).
/// @param o_contents A pointer to the contents of the file, if found. Allocated via solidity_alloc().
/// @param o_error A pointer to an error message, if there is one.
///
/// The file (as well as error) contents that is to be allocated by the callback
/// implementor must use the solidity_alloc() API to allocate its underlying
/// storage. Ownership is then transferred to the compiler which will take care
/// of the deallocation.
///
/// If the callback is not supported, *o_contents and *o_error must be set to NULL.
typedef void (*CStyleReadFileCallback)(void* _context, char const* _kind, char const* _data, char** o_contents, char** o_error);
/// Returns the complete license document.
///
/// The pointer returned must not be freed by the caller.
/// The pointer returned must NOT be freed by the caller.
char const* solidity_license() SOLC_NOEXCEPT;
/// Returns the compiler version.
///
/// The pointer returned must not be freed by the caller.
/// The pointer returned must NOT be freed by the caller.
char const* solidity_version() SOLC_NOEXCEPT;
/// Allocates a chunk of memory of @p _size bytes.
///
/// Use this function inside callbacks to allocate data that is to be passed to
/// the compiler. You may use solidity_free() or solidity_reset() to free this
/// memory again but it is not required as the compiler takes ownership for any
/// data passed to it via callbacks.
///
/// This function will return NULL if the requested memory region could not be allocated.
char* solidity_alloc(size_t _size) SOLC_NOEXCEPT;
/// Explicitly frees the memory (@p _data) that was being allocated with solidity_alloc()
/// or returned by a call to solidity_compile().
///
/// Important, this call will abort() in case of any invalid argument being passed to this call.
void solidity_free(char* _data) SOLC_NOEXCEPT;
/// Takes a "Standard Input JSON" and an optional callback (can be set to null). Returns
/// a "Standard Output JSON". Both are to be UTF-8 encoded.
///
/// The pointer returned must not be freed by the caller.
char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) SOLC_NOEXCEPT;
/// @param _input The input JSON to process.
/// @param _readCallback The optional callback pointer. Can be NULL, but if not NULL,
/// it can be called by the compiler to request additional input.
/// Please see the documentation of the type for details.
/// @param _readContext An optional context pointer passed to _readCallback. Can be NULL.
///
/// @returns A pointer to the result. The pointer returned must be freed by the caller using solidity_free() or solidity_reset().
char* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback, void* _readContext) SOLC_NOEXCEPT;
/// Frees up any allocated memory.
///
/// NOTE: the pointer returned by solidity_compile is invalid after calling this!
void solidity_free() SOLC_NOEXCEPT;
/// NOTE: the pointer returned by solidity_compile as well as any other pointer retrieved via solidity_alloc()
/// is invalid after calling this!
void solidity_reset() SOLC_NOEXCEPT;
#ifdef __cplusplus
}

View File

@ -18,6 +18,8 @@ set(sources
analysis/GlobalContext.h
analysis/NameAndTypeResolver.cpp
analysis/NameAndTypeResolver.h
analysis/OverrideChecker.cpp
analysis/OverrideChecker.h
analysis/PostTypeChecker.cpp
analysis/PostTypeChecker.h
analysis/ReferencesResolver.cpp
@ -39,8 +41,6 @@ set(sources
ast/ASTForward.h
ast/ASTJsonConverter.cpp
ast/ASTJsonConverter.h
ast/ASTPrinter.cpp
ast/ASTPrinter.h
ast/ASTUtils.cpp
ast/ASTUtils.h
ast/ASTVisitor.h
@ -107,6 +107,7 @@ set(sources
interface/ABI.h
interface/CompilerStack.cpp
interface/CompilerStack.h
interface/DebugSettings.h
interface/GasEstimator.cpp
interface/GasEstimator.h
interface/Natspec.cpp

View File

@ -26,6 +26,7 @@
#include <libsolidity/analysis/TypeChecker.h>
#include <liblangutil/ErrorReporter.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/algorithm/string/predicate.hpp>
using namespace std;
@ -33,20 +34,33 @@ using namespace dev;
using namespace langutil;
using namespace dev::solidity;
namespace
{
template <class T, class B>
bool hasEqualNameAndParameters(T const& _a, B const& _b)
{
return
_a.name() == _b.name() &&
FunctionType(_a).asCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asCallableFunction(false)
);
}
}
bool ContractLevelChecker::check(ContractDefinition const& _contract)
{
checkDuplicateFunctions(_contract);
checkDuplicateEvents(_contract);
checkIllegalOverrides(_contract);
checkAbstractFunctions(_contract);
m_overrideChecker.check(_contract);
checkBaseConstructorArguments(_contract);
checkConstructor(_contract);
checkFallbackFunction(_contract);
checkAbstractFunctions(_contract);
checkExternalTypeClashes(_contract);
checkHashCollisions(_contract);
checkLibraryRequirements(_contract);
checkBaseABICompatibility(_contract);
checkPayableFallbackWithoutReceive(_contract);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
@ -58,6 +72,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
map<string, vector<FunctionDefinition const*>> functions;
FunctionDefinition const* constructor = nullptr;
FunctionDefinition const* fallback = nullptr;
FunctionDefinition const* receive = nullptr;
for (FunctionDefinition const* function: _contract.definedFunctions())
if (function->isConstructor())
{
@ -79,6 +94,16 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
);
fallback = function;
}
else if (function->isReceive())
{
if (receive)
m_errorReporter.declarationError(
function->location(),
SecondarySourceLocation().append("Another declaration is here:", receive->location()),
"Only one receive function is allowed."
);
receive = function;
}
else
{
solAssert(!function->name().empty(), "");
@ -111,9 +136,7 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
SecondarySourceLocation ssl;
for (size_t j = i + 1; j < overloads.size(); ++j)
if (FunctionType(*overloads[i]).asCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(*overloads[j]).asCallableFunction(false))
)
if (hasEqualNameAndParameters(*overloads[i], *overloads[j]))
{
ssl.append("Other declaration is here:", overloads[j]->location());
reported.insert(j);
@ -133,87 +156,6 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
}
}
void ContractLevelChecker::checkIllegalOverrides(ContractDefinition const& _contract)
{
// TODO unify this at a later point. for this we need to put the constness and the access specifier
// into the types
map<string, vector<FunctionDefinition const*>> functions;
map<string, ModifierDefinition const*> modifiers;
// We search from derived to base, so the stored item causes the error.
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
{
for (FunctionDefinition const* function: contract->definedFunctions())
{
if (function->isConstructor())
continue; // constructors can neither be overridden nor override anything
string const& name = function->name();
if (modifiers.count(name))
m_errorReporter.typeError(modifiers[name]->location(), "Override changes function to modifier.");
for (FunctionDefinition const* overriding: functions[name])
checkFunctionOverride(*overriding, *function);
functions[name].push_back(function);
}
for (ModifierDefinition const* modifier: contract->functionModifiers())
{
string const& name = modifier->name();
ModifierDefinition const*& override = modifiers[name];
if (!override)
override = modifier;
else if (ModifierType(*override) != ModifierType(*modifier))
m_errorReporter.typeError(override->location(), "Override changes modifier signature.");
if (!functions[name].empty())
m_errorReporter.typeError(override->location(), "Override changes modifier to function.");
}
}
}
void ContractLevelChecker::checkFunctionOverride(FunctionDefinition const& _function, FunctionDefinition const& _super)
{
FunctionTypePointer functionType = FunctionType(_function).asCallableFunction(false);
FunctionTypePointer superType = FunctionType(_super).asCallableFunction(false);
if (!functionType->hasEqualParameterTypes(*superType))
return;
if (!functionType->hasEqualReturnTypes(*superType))
overrideError(_function, _super, "Overriding function return types differ.");
if (!_function.annotation().superFunction)
_function.annotation().superFunction = &_super;
if (_function.visibility() != _super.visibility())
{
// Visibility change from external to public is fine.
// Any other change is disallowed.
if (!(
_super.visibility() == FunctionDefinition::Visibility::External &&
_function.visibility() == FunctionDefinition::Visibility::Public
))
overrideError(_function, _super, "Overriding function visibility differs.");
}
if (_function.stateMutability() != _super.stateMutability())
overrideError(
_function,
_super,
"Overriding function changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
}
void ContractLevelChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message)
{
m_errorReporter.typeError(
function.location(),
SecondarySourceLocation().append("Overridden function is here:", super.location()),
message
);
}
void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _contract)
{
// Mapping from name to function definition (exactly one per argument type equality class) and
@ -230,11 +172,6 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
});
if (it == overloads.end())
overloads.emplace_back(_type, _implemented);
else if (it->second)
{
if (!_implemented)
m_errorReporter.typeError(_declaration.location(), "Redeclaring an already implemented function as abstract");
}
else if (_implemented)
it->second = true;
};
@ -257,6 +194,8 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
}
// Set to not fully implemented if at least one flag is false.
// Note that `_contract.annotation().unimplementedFunctions` has already been
// pre-filled by `checkBaseConstructorArguments`.
for (auto const& it: functions)
for (auto const& funAndFlag: it.second)
if (!funAndFlag.second)
@ -266,6 +205,32 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
_contract.annotation().unimplementedFunctions.push_back(function);
break;
}
if (_contract.abstract())
{
if (_contract.contractKind() == ContractDefinition::ContractKind::Interface)
m_errorReporter.typeError(_contract.location(), "Interfaces do not need the \"abstract\" keyword, they are abstract implicitly.");
else if (_contract.contractKind() == ContractDefinition::ContractKind::Library)
m_errorReporter.typeError(_contract.location(), "Libraries cannot be abstract.");
else
solAssert(_contract.contractKind() == ContractDefinition::ContractKind::Contract, "");
}
// For libraries, we emit errors on function-level, so this is fine as long as we do
// not have inheritance for libraries.
if (
_contract.contractKind() == ContractDefinition::ContractKind::Contract &&
!_contract.abstract() &&
!_contract.annotation().unimplementedFunctions.empty()
)
{
SecondarySourceLocation ssl;
for (auto function: _contract.annotation().unimplementedFunctions)
ssl.append("Missing implementation:", function->location());
m_errorReporter.typeError(_contract.location(), ssl,
"Contract \"" + _contract.annotation().canonicalName
+ "\" should be marked as abstract.");
}
}
@ -358,48 +323,6 @@ void ContractLevelChecker::annotateBaseConstructorArguments(
}
void ContractLevelChecker::checkConstructor(ContractDefinition const& _contract)
{
FunctionDefinition const* constructor = _contract.constructor();
if (!constructor)
return;
if (!constructor->returnParameters().empty())
m_errorReporter.typeError(constructor->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (constructor->stateMutability() != StateMutability::NonPayable && constructor->stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
constructor->location(),
"Constructor must be payable or non-payable, but is \"" +
stateMutabilityToString(constructor->stateMutability()) +
"\"."
);
if (constructor->visibility() != FunctionDefinition::Visibility::Public && constructor->visibility() != FunctionDefinition::Visibility::Internal)
m_errorReporter.typeError(constructor->location(), "Constructor must be public or internal.");
}
void ContractLevelChecker::checkFallbackFunction(ContractDefinition const& _contract)
{
FunctionDefinition const* fallback = _contract.fallbackFunction();
if (!fallback)
return;
if (_contract.isLibrary())
m_errorReporter.typeError(fallback->location(), "Libraries cannot have fallback functions.");
if (fallback->stateMutability() != StateMutability::NonPayable && fallback->stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
fallback->location(),
"Fallback function must be payable or non-payable, but is \"" +
stateMutabilityToString(fallback->stateMutability()) +
"\"."
);
if (!fallback->parameters().empty())
m_errorReporter.typeError(fallback->parameterList().location(), "Fallback function cannot take parameters.");
if (!fallback->returnParameters().empty())
m_errorReporter.typeError(fallback->returnParameterList()->location(), "Fallback function cannot return values.");
if (fallback->visibility() != FunctionDefinition::Visibility::External)
m_errorReporter.typeError(fallback->location(), "Fallback function must be defined as \"external\".");
}
void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _contract)
{
map<string, vector<pair<Declaration const*, FunctionTypePointer>>> externalDeclarations;
@ -493,7 +416,7 @@ void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _
for (TypePointer const& paramType: func.second->parameterTypes() + func.second->parameterTypes())
if (!TypeChecker::typeSupportedByOldABIEncoder(*paramType, false))
{
errors.append("Type only supported by the new experimental ABI encoder", currentLoc);
errors.append("Type only supported by ABIEncoderV2", currentLoc);
break;
}
}
@ -504,9 +427,20 @@ void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _
errors,
std::string("Contract \"") +
_contract.name() +
"\" does not use the new experimental ABI encoder but wants to inherit from a contract " +
"\" does not use ABIEncoderV2 but wants to inherit from a contract " +
"which uses types that require it. " +
"Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature."
);
}
void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition const& _contract)
{
if (auto const* fallback = _contract.fallbackFunction())
if (fallback->isPayable() && !_contract.interfaceFunctionList().empty() && !_contract.receiveFunction())
m_errorReporter.warning(
_contract.location(),
"This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function.",
SecondarySourceLocation{}.append("The payable fallback function is defined here.", fallback->location())
);
}

View File

@ -22,7 +22,11 @@
#pragma once
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/analysis/OverrideChecker.h>
#include <liblangutil/SourceLocation.h>
#include <map>
#include <functional>
#include <set>
namespace langutil
{
@ -41,8 +45,10 @@ namespace solidity
class ContractLevelChecker
{
public:
/// @param _errorReporter provides the error logging functionality.
explicit ContractLevelChecker(langutil::ErrorReporter& _errorReporter):
m_overrideChecker{_errorReporter},
m_errorReporter(_errorReporter)
{}
@ -57,20 +63,15 @@ private:
void checkDuplicateEvents(ContractDefinition const& _contract);
template <class T>
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message);
void checkIllegalOverrides(ContractDefinition const& _contract);
/// Reports a type error with an appropriate message if overridden function signature differs.
/// Also stores the direct super function in the AST annotations.
void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super);
void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message);
void checkAbstractFunctions(ContractDefinition const& _contract);
/// Checks that the base constructor arguments are properly provided.
/// Fills the list of unimplemented functions in _contract's annotations.
void checkBaseConstructorArguments(ContractDefinition const& _contract);
void annotateBaseConstructorArguments(
ContractDefinition const& _currentContract,
FunctionDefinition const* _baseConstructor,
ASTNode const* _argumentNode
);
void checkConstructor(ContractDefinition const& _contract);
void checkFallbackFunction(ContractDefinition const& _contract);
/// Checks that different functions with external visibility end up having different
/// external argument types (i.e. different signature).
void checkExternalTypeClashes(ContractDefinition const& _contract);
@ -81,6 +82,10 @@ private:
/// Checks base contracts for ABI compatibility
void checkBaseABICompatibility(ContractDefinition const& _contract);
/// Warns if the contract has a payable fallback, but no receive ether function.
void checkPayableFallbackWithoutReceive(ContractDefinition const& _contract);
OverrideChecker m_overrideChecker;
langutil::ErrorReporter& m_errorReporter;
};

View File

@ -85,6 +85,18 @@ bool ControlFlowBuilder::visit(Conditional const& _conditional)
return false;
}
bool ControlFlowBuilder::visit(TryStatement const& _tryStatement)
{
appendControlFlow(_tryStatement.externalCall());
auto nodes = splitFlow(_tryStatement.clauses().size());
for (size_t i = 0; i < _tryStatement.clauses().size(); ++i)
nodes[i] = createFlow(nodes[i], _tryStatement.clauses()[i]->block());
mergeFlow(nodes);
return false;
}
bool ControlFlowBuilder::visit(IfStatement const& _ifStatement)
{
solAssert(!!m_currentNode, "");
@ -384,7 +396,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
_variableDeclaration.value().get()
);
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
else if (_variableDeclaration.isCallableParameter() && !_variableDeclaration.isReturnParameter())
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Assignment,

View File

@ -45,6 +45,7 @@ private:
// Visits for constructing the control flow.
bool visit(BinaryOperation const& _operation) override;
bool visit(Conditional const& _conditional) override;
bool visit(TryStatement const& _tryStatement) override;
bool visit(IfStatement const& _ifStatement) override;
bool visit(ForStatement const& _forStatement) override;
bool visit(WhileStatement const& _whileStatement) override;
@ -98,12 +99,27 @@ private:
return result;
}
/// Splits the control flow starting at the current node into @a _n paths.
/// m_currentNode is set to nullptr and has to be set manually or
/// using mergeFlow later.
std::vector<CFGNode*> splitFlow(size_t n)
{
std::vector<CFGNode*> result(n);
for (auto& node: result)
{
node = m_nodeContainer.newNode();
connect(m_currentNode, node);
}
m_currentNode = nullptr;
return result;
}
/// Merges the control flow of @a _nodes to @a _endNode.
/// If @a _endNode is nullptr, a new node is creates and used as end node.
/// Sets the merge destination as current node.
/// Note: @a _endNode may be one of the nodes in @a _nodes.
template<size_t n>
void mergeFlow(std::array<CFGNode*, n> const& _nodes, CFGNode* _endNode = nullptr)
template<typename C>
void mergeFlow(C const& _nodes, CFGNode* _endNode = nullptr)
{
CFGNode* mergeDestination = (_endNode == nullptr) ? m_nodeContainer.newNode() : _endNode;
for (auto& node: _nodes)

View File

@ -129,9 +129,36 @@ void DocStringAnalyser::parseDocStrings(
m_errorOccured = true;
_annotation.docTags = parser.tags();
}
size_t returnTagsVisited = 0;
for (auto const& docTag: _annotation.docTags)
{
if (!_validTags.count(docTag.first))
appendError("Doc tag @" + docTag.first + " not valid for " + _nodeName + ".");
appendError("Documentation tag @" + docTag.first + " not valid for " + _nodeName + ".");
else
if (docTag.first == "return")
{
returnTagsVisited++;
if (auto* function = dynamic_cast<FunctionDefinition const*>(&_node))
{
string content = docTag.second.content;
string firstWord = content.substr(0, content.find_first_of(" \t"));
if (returnTagsVisited > function->returnParameters().size())
appendError("Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
" exceedes the number of return parameters."
);
else
{
auto parameter = function->returnParameters().at(returnTagsVisited - 1);
if (!parameter->name().empty() && parameter->name() != firstWord)
appendError("Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
" does not contain the name of its return parameter."
);
}
}
}
}
}
void DocStringAnalyser::appendError(string const& _description)

View File

@ -346,11 +346,19 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
solAssert(conflictingDeclaration, "");
// Usual shadowing is not an error
if (dynamic_cast<VariableDeclaration const*>(declaration) && dynamic_cast<VariableDeclaration const*>(conflictingDeclaration))
if (
dynamic_cast<ModifierDefinition const*>(declaration) &&
dynamic_cast<ModifierDefinition const*>(conflictingDeclaration)
)
continue;
// Usual shadowing is not an error
if (dynamic_cast<ModifierDefinition const*>(declaration) && dynamic_cast<ModifierDefinition const*>(conflictingDeclaration))
// Public state variable can override functions
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(conflictingDeclaration))
if (
dynamic_cast<FunctionDefinition const*>(declaration) &&
varDecl->isStateVariable() &&
varDecl->isPublic()
)
continue;
if (declaration->location().start < conflictingDeclaration->location().start)
@ -572,6 +580,7 @@ bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract)
m_globalContext.setCurrentContract(_contract);
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, false, true);
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, false, true);
m_currentContract = &_contract;
registerDeclaration(_contract, true);
_contract.annotation().canonicalName = currentCanonicalName();
@ -580,6 +589,7 @@ bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract)
void DeclarationRegistrationHelper::endVisit(ContractDefinition&)
{
m_currentContract = nullptr;
closeCurrentScope();
}
@ -617,6 +627,7 @@ bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function)
{
registerDeclaration(_function, true);
m_currentFunction = &_function;
_function.annotation().contract = m_currentContract;
return true;
}
@ -626,6 +637,18 @@ void DeclarationRegistrationHelper::endVisit(FunctionDefinition&)
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(TryCatchClause& _tryCatchClause)
{
_tryCatchClause.setScope(m_currentScope);
enterNewSubScope(_tryCatchClause);
return true;
}
void DeclarationRegistrationHelper::endVisit(TryCatchClause&)
{
closeCurrentScope();
}
bool DeclarationRegistrationHelper::visit(ModifierDefinition& _modifier)
{
registerDeclaration(_modifier, true);

View File

@ -181,6 +181,8 @@ private:
bool visit(EnumValue& _value) override;
bool visit(FunctionDefinition& _function) override;
void endVisit(FunctionDefinition& _function) override;
bool visit(TryCatchClause& _tryCatchClause) override;
void endVisit(TryCatchClause& _tryCatchClause) override;
bool visit(ModifierDefinition& _modifier) override;
void endVisit(ModifierDefinition& _modifier) override;
bool visit(FunctionTypeName& _funTypeName) override;
@ -206,6 +208,7 @@ private:
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr;
ContractDefinition const* m_currentContract = nullptr;
langutil::ErrorReporter& m_errorReporter;
GlobalContext& m_globalContext;
};

View File

@ -0,0 +1,857 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component that verifies overloads, abstract contracts, function clashes and others
* checks at contract or function level.
*/
#include <libsolidity/analysis/OverrideChecker.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <liblangutil/ErrorReporter.h>
#include <libdevcore/Visitor.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/algorithm/string/predicate.hpp>
using namespace std;
using namespace dev;
using namespace langutil;
using namespace dev::solidity;
namespace
{
// Helper struct to do a search by name
struct MatchByName
{
string const& m_name;
bool operator()(OverrideProxy const& _item)
{
return _item.name() == m_name;
}
};
/**
* Construct the override graph for this signature.
* Reserve node 0 for the current contract and node
* 1 for an artificial top node to which all override paths
* connect at the end.
*/
struct OverrideGraph
{
OverrideGraph(set<OverrideProxy> const& _baseCallables)
{
for (auto const& baseFunction: _baseCallables)
addEdge(0, visit(baseFunction));
}
std::map<OverrideProxy, int> nodes;
std::map<int, OverrideProxy> nodeInv;
std::map<int, std::set<int>> edges;
int numNodes = 2;
void addEdge(int _a, int _b)
{
edges[_a].insert(_b);
edges[_b].insert(_a);
}
private:
/// Completes the graph starting from @a _function and
/// @returns the node ID.
int visit(OverrideProxy const& _function)
{
auto it = nodes.find(_function);
if (it != nodes.end())
return it->second;
int currentNode = numNodes++;
nodes[_function] = currentNode;
nodeInv[currentNode] = _function;
if (_function.overrides())
for (auto const& baseFunction: _function.baseFunctions())
addEdge(currentNode, visit(baseFunction));
else
addEdge(currentNode, 1);
return currentNode;
}
};
/**
* Detect cut vertices following https://en.wikipedia.org/wiki/Biconnected_component#Pseudocode
* Can ignore the root node, since it is never a cut vertex in our case.
*/
struct CutVertexFinder
{
CutVertexFinder(OverrideGraph const& _graph): m_graph(_graph)
{
run();
}
std::set<OverrideProxy> const& cutVertices() const { return m_cutVertices; }
private:
OverrideGraph const& m_graph;
std::vector<bool> m_visited = std::vector<bool>(m_graph.numNodes, false);
std::vector<int> m_depths = std::vector<int>(m_graph.numNodes, -1);
std::vector<int> m_low = std::vector<int>(m_graph.numNodes, -1);
std::vector<int> m_parent = std::vector<int>(m_graph.numNodes, -1);
std::set<OverrideProxy> m_cutVertices{};
void run(int _u = 0, int _depth = 0)
{
m_visited.at(_u) = true;
m_depths.at(_u) = m_low.at(_u) = _depth;
for (int v: m_graph.edges.at(_u))
if (!m_visited.at(v))
{
m_parent[v] = _u;
run(v, _depth + 1);
if (m_low[v] >= m_depths[_u] && m_parent[_u] != -1)
m_cutVertices.insert(m_graph.nodeInv.at(_u));
m_low[_u] = min(m_low[_u], m_low[v]);
}
else if (v != m_parent[_u])
m_low[_u] = min(m_low[_u], m_depths[v]);
}
};
vector<ContractDefinition const*> resolveDirectBaseContracts(ContractDefinition const& _contract)
{
vector<ContractDefinition const*> resolvedContracts;
for (ASTPointer<InheritanceSpecifier> const& specifier: _contract.baseContracts())
{
Declaration const* baseDecl =
specifier->name().annotation().referencedDeclaration;
auto contract = dynamic_cast<ContractDefinition const*>(baseDecl);
solAssert(contract, "contract is null");
resolvedContracts.emplace_back(contract);
}
return resolvedContracts;
}
vector<ASTPointer<UserDefinedTypeName>> sortByContract(vector<ASTPointer<UserDefinedTypeName>> const& _list)
{
auto sorted = _list;
stable_sort(sorted.begin(), sorted.end(),
[] (ASTPointer<UserDefinedTypeName> _a, ASTPointer<UserDefinedTypeName> _b) {
if (!_a || !_b)
return _a < _b;
Declaration const* aDecl = _a->annotation().referencedDeclaration;
Declaration const* bDecl = _b->annotation().referencedDeclaration;
if (!aDecl || !bDecl)
return aDecl < bDecl;
return aDecl->id() < bDecl->id();
}
);
return sorted;
}
OverrideProxy makeOverrideProxy(CallableDeclaration const& _callable)
{
if (auto const* fun = dynamic_cast<FunctionDefinition const*>(&_callable))
return OverrideProxy{fun};
else if (auto const* mod = dynamic_cast<ModifierDefinition const*>(&_callable))
return OverrideProxy{mod};
else
solAssert(false, "Invalid call to makeOverrideProxy.");
return {};
}
}
bool OverrideProxy::operator<(OverrideProxy const& _other) const
{
return id() < _other.id();
}
bool OverrideProxy::isVariable() const
{
return holds_alternative<VariableDeclaration const*>(m_item);
}
bool OverrideProxy::isFunction() const
{
return holds_alternative<FunctionDefinition const*>(m_item);
}
bool OverrideProxy::isModifier() const
{
return holds_alternative<ModifierDefinition const*>(m_item);
}
bool OverrideProxy::CompareBySignature::operator()(OverrideProxy const& _a, OverrideProxy const& _b) const
{
return _a.overrideComparator() < _b.overrideComparator();
}
size_t OverrideProxy::id() const
{
return std::visit(GenericVisitor{
[&](auto const* _item) -> size_t { return _item->id(); }
}, m_item);
}
shared_ptr<OverrideSpecifier> OverrideProxy::overrides() const
{
return std::visit(GenericVisitor{
[&](auto const* _item) { return _item->overrides(); }
}, m_item);
}
set<OverrideProxy> OverrideProxy::baseFunctions() const
{
return std::visit(GenericVisitor{
[&](auto const* _item) -> set<OverrideProxy> {
set<OverrideProxy> ret;
for (auto const* f: _item->annotation().baseFunctions)
ret.insert(makeOverrideProxy(*f));
return ret;
}
}, m_item);
}
void OverrideProxy::storeBaseFunction(OverrideProxy const& _base) const
{
std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) {
_item->annotation().baseFunctions.emplace(std::get<FunctionDefinition const*>(_base.m_item));
},
[&](ModifierDefinition const* _item) {
_item->annotation().baseFunctions.emplace(std::get<ModifierDefinition const*>(_base.m_item));
},
[&](VariableDeclaration const* _item) {
_item->annotation().baseFunctions.emplace(std::get<FunctionDefinition const*>(_base.m_item));
}
}, m_item);
}
string const& OverrideProxy::name() const
{
return std::visit(GenericVisitor{
[&](auto const* _item) -> string const& { return _item->name(); }
}, m_item);
}
ContractDefinition const& OverrideProxy::contract() const
{
return std::visit(GenericVisitor{
[&](auto const* _item) -> ContractDefinition const& {
return dynamic_cast<ContractDefinition const&>(*_item->scope());
}
}, m_item);
}
string const& OverrideProxy::contractName() const
{
return contract().name();
}
Visibility OverrideProxy::visibility() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return _item->visibility(); },
[&](ModifierDefinition const* _item) { return _item->visibility(); },
[&](VariableDeclaration const*) { return Visibility::External; }
}, m_item);
}
StateMutability OverrideProxy::stateMutability() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return _item->stateMutability(); },
[&](ModifierDefinition const*) { solAssert(false, "Requested state mutability from modifier."); return StateMutability{}; },
[&](VariableDeclaration const*) { return StateMutability::View; }
}, m_item);
}
bool OverrideProxy::virtualSemantics() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return _item->virtualSemantics(); },
[&](ModifierDefinition const* _item) { return _item->virtualSemantics(); },
[&](VariableDeclaration const*) { return false; }
}, m_item);
}
Token OverrideProxy::functionKind() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return _item->kind(); },
[&](ModifierDefinition const*) { return Token::Function; },
[&](VariableDeclaration const*) { return Token::Function; }
}, m_item);
}
FunctionType const* OverrideProxy::functionType() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return FunctionType(*_item).asCallableFunction(false); },
[&](VariableDeclaration const* _item) { return FunctionType(*_item).asCallableFunction(false); },
[&](ModifierDefinition const*) -> FunctionType const* { solAssert(false, "Requested function type of modifier."); return nullptr; }
}, m_item);
}
ModifierType const* OverrideProxy::modifierType() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const*) -> ModifierType const* { solAssert(false, "Requested modifier type of function."); return nullptr; },
[&](VariableDeclaration const*) -> ModifierType const* { solAssert(false, "Requested modifier type of variable."); return nullptr; },
[&](ModifierDefinition const* _modifier) -> ModifierType const* { return TypeProvider::modifier(*_modifier); }
}, m_item);
}
SourceLocation const& OverrideProxy::location() const
{
return std::visit(GenericVisitor{
[&](auto const* _item) -> SourceLocation const& { return _item->location(); }
}, m_item);
}
string OverrideProxy::astNodeName() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const*) { return "function"; },
[&](ModifierDefinition const*) { return "modifier"; },
[&](VariableDeclaration const*) { return "public state variable"; },
}, m_item);
}
string OverrideProxy::astNodeNameCapitalized() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const*) { return "Function"; },
[&](ModifierDefinition const*) { return "Modifier"; },
[&](VariableDeclaration const*) { return "Public state variable"; },
}, m_item);
}
string OverrideProxy::distinguishingProperty() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const*) { return "name and parameter types"; },
[&](ModifierDefinition const*) { return "name"; },
[&](VariableDeclaration const*) { return "name and parameter types"; },
}, m_item);
}
bool OverrideProxy::unimplemented() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return !_item->isImplemented(); },
[&](ModifierDefinition const*) { return false; },
[&](VariableDeclaration const*) { return false; }
}, m_item);
}
bool OverrideProxy::OverrideComparator::operator<(OverrideComparator const& _other) const
{
if (name != _other.name)
return name < _other.name;
if (!functionKind || !_other.functionKind)
return false;
if (functionKind != _other.functionKind)
return *functionKind < *_other.functionKind;
if (!parameterTypes || !_other.parameterTypes)
return false;
return boost::lexicographical_compare(*parameterTypes, *_other.parameterTypes);
}
OverrideProxy::OverrideComparator const& OverrideProxy::overrideComparator() const
{
if (!m_comparator)
{
m_comparator = make_shared<OverrideComparator>(std::visit(GenericVisitor{
[&](FunctionDefinition const* _function)
{
vector<string> paramTypes;
for (Type const* t: functionType()->parameterTypes())
paramTypes.emplace_back(t->richIdentifier());
return OverrideComparator{
_function->name(),
_function->kind(),
std::move(paramTypes)
};
},
[&](VariableDeclaration const* _var)
{
vector<string> paramTypes;
for (Type const* t: functionType()->parameterTypes())
paramTypes.emplace_back(t->richIdentifier());
return OverrideComparator{
_var->name(),
Token::Function,
std::move(paramTypes)
};
},
[&](ModifierDefinition const* _mod)
{
return OverrideComparator{
_mod->name(),
{},
{}
};
}
}, m_item));
}
return *m_comparator;
}
bool OverrideChecker::CompareByID::operator()(ContractDefinition const* _a, ContractDefinition const* _b) const
{
if (!_a || !_b)
return _a < _b;
return _a->id() < _b->id();
}
void OverrideChecker::check(ContractDefinition const& _contract)
{
checkIllegalOverrides(_contract);
checkAmbiguousOverrides(_contract);
}
void OverrideChecker::checkIllegalOverrides(ContractDefinition const& _contract)
{
OverrideProxyBySignatureMultiSet const& inheritedFuncs = inheritedFunctions(_contract);
OverrideProxyBySignatureMultiSet const& inheritedMods = inheritedModifiers(_contract);
for (ModifierDefinition const* modifier: _contract.functionModifiers())
{
if (contains_if(inheritedFuncs, MatchByName{modifier->name()}))
m_errorReporter.typeError(
modifier->location(),
"Override changes function or public state variable to modifier."
);
checkOverrideList(OverrideProxy{modifier}, inheritedMods);
}
for (FunctionDefinition const* function: _contract.definedFunctions())
{
if (function->isConstructor())
continue;
if (contains_if(inheritedMods, MatchByName{function->name()}))
m_errorReporter.typeError(function->location(), "Override changes modifier to function.");
checkOverrideList(OverrideProxy{function}, inheritedFuncs);
}
for (auto const* stateVar: _contract.stateVariables())
{
if (!stateVar->isPublic())
continue;
if (contains_if(inheritedMods, MatchByName{stateVar->name()}))
m_errorReporter.typeError(stateVar->location(), "Override changes modifier to public state variable.");
checkOverrideList(OverrideProxy{stateVar}, inheritedFuncs);
}
}
void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverrideProxy const& _super)
{
solAssert(_super.isModifier() == _overriding.isModifier(), "");
if (_super.isFunction() || _super.isModifier())
_overriding.storeBaseFunction(_super);
if (_overriding.isModifier() && *_overriding.modifierType() != *_super.modifierType())
m_errorReporter.typeError(
_overriding.location(),
"Override changes modifier signature."
);
if (!_overriding.overrides())
overrideError(_overriding, _super, "Overriding " + _overriding.astNodeName() + " is missing \"override\" specifier.");
if (_super.isVariable())
overrideError(
_super,
_overriding,
"Cannot override public state variable.",
"Overriding " + _overriding.astNodeName() + " is here:"
);
else if (!_super.virtualSemantics())
overrideError(
_super,
_overriding,
"Trying to override non-virtual " + _super.astNodeName() + ". Did you forget to add \"virtual\"?",
"Overriding " + _overriding.astNodeName() + " is here:"
);
if (_overriding.isVariable())
{
if (_super.visibility() != Visibility::External)
overrideError(_overriding, _super, "Public state variables can only override functions with external visibility.");
solAssert(_overriding.visibility() == Visibility::External, "");
}
else if (_overriding.visibility() != _super.visibility())
{
// Visibility change from external to public is fine.
// Any other change is disallowed.
if (!(
_super.visibility() == Visibility::External &&
_overriding.visibility() == Visibility::Public
))
overrideError(_overriding, _super, "Overriding " + _overriding.astNodeName() + " visibility differs.");
}
if (_super.isFunction())
{
FunctionType const* functionType = _overriding.functionType();
FunctionType const* superType = _super.functionType();
solAssert(functionType->hasEqualParameterTypes(*superType), "Override doesn't have equal parameters!");
if (!functionType->hasEqualReturnTypes(*superType))
overrideError(_overriding, _super, "Overriding " + _overriding.astNodeName() + " return types differ.");
// This is only relevant for a function overriding a function.
if (_overriding.isFunction())
{
if (_overriding.stateMutability() != _super.stateMutability())
overrideError(
_overriding,
_super,
"Overriding function changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +
stateMutabilityToString(_overriding.stateMutability()) +
"\"."
);
if (_overriding.unimplemented() && !_super.unimplemented())
overrideError(
_overriding,
_super,
"Overriding an implemented function with an unimplemented function is not allowed."
);
}
}
}
void OverrideChecker::overrideListError(
OverrideProxy const& _item,
set<ContractDefinition const*, CompareByID> _secondary,
string const& _message1,
string const& _message2
)
{
// Using a set rather than a vector so the order is always the same
set<string> names;
SecondarySourceLocation ssl;
for (Declaration const* c: _secondary)
{
ssl.append("This contract: ", c->location());
names.insert("\"" + c->name() + "\"");
}
string contractSingularPlural = "contract ";
if (_secondary.size() > 1)
contractSingularPlural = "contracts ";
m_errorReporter.typeError(
_item.overrides() ? _item.overrides()->location() : _item.location(),
ssl,
_message1 +
contractSingularPlural +
_message2 +
joinHumanReadable(names, ", ", " and ") +
"."
);
}
void OverrideChecker::overrideError(Declaration const& _overriding, Declaration const& _super, string const& _message, string const& _secondaryMsg)
{
m_errorReporter.typeError(
_overriding.location(),
SecondarySourceLocation().append(_secondaryMsg, _super.location()),
_message
);
}
void OverrideChecker::overrideError(OverrideProxy const& _overriding, OverrideProxy const& _super, string const& _message, string const& _secondaryMsg)
{
m_errorReporter.typeError(
_overriding.location(),
SecondarySourceLocation().append(_secondaryMsg, _super.location()),
_message
);
}
void OverrideChecker::checkAmbiguousOverrides(ContractDefinition const& _contract) const
{
{
// Fetch inherited functions and sort them by signature.
// We get at least one function per signature and direct base contract, which is
// enough because we re-construct the inheritance graph later.
OverrideProxyBySignatureMultiSet nonOverriddenFunctions = inheritedFunctions(_contract);
// Remove all functions that match the signature of a function in the current contract.
for (FunctionDefinition const* f: _contract.definedFunctions())
nonOverriddenFunctions.erase(OverrideProxy{f});
for (VariableDeclaration const* v: _contract.stateVariables())
if (v->isPublic())
nonOverriddenFunctions.erase(OverrideProxy{v});
// Walk through the set of functions signature by signature.
for (auto it = nonOverriddenFunctions.cbegin(); it != nonOverriddenFunctions.cend();)
{
std::set<OverrideProxy> baseFunctions;
for (auto nextSignature = nonOverriddenFunctions.upper_bound(*it); it != nextSignature; ++it)
baseFunctions.insert(*it);
checkAmbiguousOverridesInternal(std::move(baseFunctions), _contract.location());
}
}
{
OverrideProxyBySignatureMultiSet modifiers = inheritedModifiers(_contract);
for (ModifierDefinition const* mod: _contract.functionModifiers())
modifiers.erase(OverrideProxy{mod});
for (auto it = modifiers.cbegin(); it != modifiers.cend();)
{
std::set<OverrideProxy> baseModifiers;
for (auto next = modifiers.upper_bound(*it); it != next; ++it)
baseModifiers.insert(*it);
checkAmbiguousOverridesInternal(std::move(baseModifiers), _contract.location());
}
}
}
void OverrideChecker::checkAmbiguousOverridesInternal(set<OverrideProxy> _baseCallables, SourceLocation const& _location) const
{
if (_baseCallables.size() <= 1)
return;
OverrideGraph overrideGraph(_baseCallables);
CutVertexFinder cutVertexFinder{overrideGraph};
// Remove all base functions overridden by cut vertices (they don't need to be overridden).
for (OverrideProxy const& function: cutVertexFinder.cutVertices())
{
std::set<OverrideProxy> toTraverse = function.baseFunctions();
while (!toTraverse.empty())
{
OverrideProxy base = *toTraverse.begin();
toTraverse.erase(toTraverse.begin());
_baseCallables.erase(base);
for (OverrideProxy const& f: base.baseFunctions())
toTraverse.insert(f);
}
// Remove unimplemented base functions at the cut vertices itself as well.
if (function.unimplemented())
_baseCallables.erase(function);
}
// If more than one function is left, they have to be overridden.
if (_baseCallables.size() <= 1)
return;
SecondarySourceLocation ssl;
for (OverrideProxy const& baseFunction: _baseCallables)
ssl.append("Definition in \"" + baseFunction.contractName() + "\": ", baseFunction.location());
string callableName = _baseCallables.begin()->astNodeName();
if (_baseCallables.begin()->isVariable())
callableName = "function";
string distinguishigProperty = _baseCallables.begin()->distinguishingProperty();
bool foundVariable = false;
for (auto const& base: _baseCallables)
if (base.isVariable())
foundVariable = true;
string message =
"Derived contract must override " + callableName + " \"" +
_baseCallables.begin()->name() +
"\". Two or more base classes define " + callableName + " with same " + distinguishigProperty + ".";
if (foundVariable)
message +=
" Since one of the bases defines a public state variable which cannot be overridden, "
"you have to change the inheritance layout or the names of the functions.";
m_errorReporter.typeError(_location, ssl, message);
}
set<ContractDefinition const*, OverrideChecker::CompareByID> OverrideChecker::resolveOverrideList(OverrideSpecifier const& _overrides) const
{
set<ContractDefinition const*, CompareByID> resolved;
for (ASTPointer<UserDefinedTypeName> const& override: _overrides.overrides())
{
Declaration const* decl = override->annotation().referencedDeclaration;
solAssert(decl, "Expected declaration to be resolved.");
// If it's not a contract it will be caught
// in the reference resolver
if (ContractDefinition const* contract = dynamic_cast<decltype(contract)>(decl))
resolved.insert(contract);
}
return resolved;
}
void OverrideChecker::checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited)
{
set<ContractDefinition const*, CompareByID> specifiedContracts =
_item.overrides() ?
resolveOverrideList(*_item.overrides()) :
decltype(specifiedContracts){};
// Check for duplicates in override list
if (_item.overrides() && specifiedContracts.size() != _item.overrides()->overrides().size())
{
// Sort by contract id to find duplicate for error reporting
vector<ASTPointer<UserDefinedTypeName>> list =
sortByContract(_item.overrides()->overrides());
// Find duplicates and output error
for (size_t i = 1; i < list.size(); i++)
{
Declaration const* aDecl = list[i]->annotation().referencedDeclaration;
Declaration const* bDecl = list[i-1]->annotation().referencedDeclaration;
if (!aDecl || !bDecl)
continue;
if (aDecl->id() == bDecl->id())
{
SecondarySourceLocation ssl;
ssl.append("First occurrence here: ", list[i-1]->location());
m_errorReporter.typeError(
list[i]->location(),
ssl,
"Duplicate contract \"" +
joinHumanReadable(list[i]->namePath(), ".") +
"\" found in override list of \"" +
_item.name() +
"\"."
);
}
}
}
set<ContractDefinition const*, CompareByID> expectedContracts;
// Build list of expected contracts
for (auto [begin, end] = _inherited.equal_range(_item); begin != end; begin++)
{
// Validate the override
checkOverride(_item, *begin);
expectedContracts.insert(&begin->contract());
}
if (_item.overrides() && expectedContracts.empty())
m_errorReporter.typeError(
_item.overrides()->location(),
_item.astNodeNameCapitalized() + " has override specified but does not override anything."
);
set<ContractDefinition const*, CompareByID> missingContracts;
// If we expect only one contract, no contract needs to be specified
if (expectedContracts.size() > 1)
missingContracts = expectedContracts - specifiedContracts;
if (!missingContracts.empty())
overrideListError(
_item,
missingContracts,
_item.astNodeNameCapitalized() + " needs to specify overridden ",
""
);
auto surplusContracts = specifiedContracts - expectedContracts;
if (!surplusContracts.empty())
overrideListError(
_item,
surplusContracts,
"Invalid ",
"specified in override list: "
);
}
OverrideChecker::OverrideProxyBySignatureMultiSet const& OverrideChecker::inheritedFunctions(ContractDefinition const& _contract) const
{
if (!m_inheritedFunctions.count(&_contract))
{
OverrideProxyBySignatureMultiSet result;
for (auto const* base: resolveDirectBaseContracts(_contract))
{
set<OverrideProxy, OverrideProxy::CompareBySignature> functionsInBase;
for (FunctionDefinition const* fun: base->definedFunctions())
if (!fun->isConstructor())
functionsInBase.emplace(OverrideProxy{fun});
for (VariableDeclaration const* var: base->stateVariables())
if (var->isPublic())
functionsInBase.emplace(OverrideProxy{var});
for (OverrideProxy const& func: inheritedFunctions(*base))
functionsInBase.insert(func);
result += functionsInBase;
}
m_inheritedFunctions[&_contract] = result;
}
return m_inheritedFunctions[&_contract];
}
OverrideChecker::OverrideProxyBySignatureMultiSet const& OverrideChecker::inheritedModifiers(ContractDefinition const& _contract) const
{
if (!m_inheritedModifiers.count(&_contract))
{
OverrideProxyBySignatureMultiSet result;
for (auto const* base: resolveDirectBaseContracts(_contract))
{
set<OverrideProxy, OverrideProxy::CompareBySignature> modifiersInBase;
for (ModifierDefinition const* mod: base->functionModifiers())
modifiersInBase.emplace(OverrideProxy{mod});
for (OverrideProxy const& mod: inheritedModifiers(*base))
modifiersInBase.insert(mod);
result += modifiersInBase;
}
m_inheritedModifiers[&_contract] = result;
}
return m_inheritedModifiers[&_contract];
}

View File

@ -0,0 +1,196 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component that verifies override properties.
*/
#pragma once
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/ast/ASTEnums.h>
#include <liblangutil/SourceLocation.h>
#include <map>
#include <functional>
#include <set>
#include <variant>
#include <optional>
namespace langutil
{
class ErrorReporter;
struct SourceLocation;
}
namespace dev
{
namespace solidity
{
class FunctionType;
class ModifierType;
/**
* Class that represents a function, public state variable or modifier
* and helps with overload checking.
* Regular comparison is performed based on AST node, while CompareBySignature
* results in two elements being equal when they can override each
* other.
*/
class OverrideProxy
{
public:
OverrideProxy() {}
explicit OverrideProxy(FunctionDefinition const* _fun): m_item{_fun} {}
explicit OverrideProxy(ModifierDefinition const* _mod): m_item{_mod} {}
explicit OverrideProxy(VariableDeclaration const* _var): m_item{_var} {}
bool operator<(OverrideProxy const& _other) const;
struct CompareBySignature
{
bool operator()(OverrideProxy const& _a, OverrideProxy const& _b) const;
};
bool isVariable() const;
bool isFunction() const;
bool isModifier() const;
size_t id() const;
std::shared_ptr<OverrideSpecifier> overrides() const;
std::set<OverrideProxy> baseFunctions() const;
/// This stores the item in the list of base items.
void storeBaseFunction(OverrideProxy const& _base) const;
std::string const& name() const;
ContractDefinition const& contract() const;
std::string const& contractName() const;
Visibility visibility() const;
StateMutability stateMutability() const;
bool virtualSemantics() const;
/// @returns receive / fallback / function (only the latter for modifiers and variables);
langutil::Token functionKind() const;
FunctionType const* functionType() const;
ModifierType const* modifierType() const;
langutil::SourceLocation const& location() const;
std::string astNodeName() const;
std::string astNodeNameCapitalized() const;
std::string distinguishingProperty() const;
/// @returns true if this AST elements supports the feature of being unimplemented
/// and is actually not implemented.
bool unimplemented() const;
/**
* Struct to help comparing override items about whether they override each other.
* Does not produce a total order.
*/
struct OverrideComparator
{
std::string name;
std::optional<langutil::Token> functionKind;
std::optional<std::vector<std::string>> parameterTypes;
bool operator<(OverrideComparator const& _other) const;
};
/// @returns a structure used to compare override items with regards to whether
/// they override each other.
OverrideComparator const& overrideComparator() const;
private:
std::variant<
FunctionDefinition const*,
ModifierDefinition const*,
VariableDeclaration const*
> m_item;
std::shared_ptr<OverrideComparator> mutable m_comparator;
};
/**
* Component that verifies override properties.
*/
class OverrideChecker
{
public:
/// @param _errorReporter provides the error logging functionality.
explicit OverrideChecker(langutil::ErrorReporter& _errorReporter):
m_errorReporter(_errorReporter)
{}
void check(ContractDefinition const& _contract);
private:
struct CompareByID
{
bool operator()(ContractDefinition const* _a, ContractDefinition const* _b) const;
};
void checkIllegalOverrides(ContractDefinition const& _contract);
/// Performs various checks related to @a _overriding overriding @a _super like
/// different return type, invalid visibility change, etc.
/// Works on functions, modifiers and public state variables.
/// Also stores @a _super as a base function of @a _function in its AST annotations.
void checkOverride(OverrideProxy const& _overriding, OverrideProxy const& _super);
void overrideListError(
OverrideProxy const& _item,
std::set<ContractDefinition const*, CompareByID> _secondary,
std::string const& _message1,
std::string const& _message2
);
void overrideError(
Declaration const& _overriding,
Declaration const& _super,
std::string const& _message,
std::string const& _secondaryMsg = "Overridden function is here:"
);
void overrideError(
OverrideProxy const& _overriding,
OverrideProxy const& _super,
std::string const& _message,
std::string const& _secondaryMsg = "Overridden function is here:"
);
/// Checks for functions in different base contracts which conflict with each
/// other and thus need to be overridden explicitly.
void checkAmbiguousOverrides(ContractDefinition const& _contract) const;
void checkAmbiguousOverridesInternal(std::set<OverrideProxy> _baseCallables, langutil::SourceLocation const& _location) const;
/// Resolves an override list of UserDefinedTypeNames to a list of contracts.
std::set<ContractDefinition const*, CompareByID> resolveOverrideList(OverrideSpecifier const& _overrides) const;
using OverrideProxyBySignatureMultiSet = std::multiset<OverrideProxy, OverrideProxy::CompareBySignature>;
void checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited);
/// Returns all functions of bases (including public state variables) that have not yet been overwritten.
/// May contain the same function multiple times when used with shared bases.
OverrideProxyBySignatureMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const;
OverrideProxyBySignatureMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const;
langutil::ErrorReporter& m_errorReporter;
/// Cache for inheritedFunctions().
std::map<ContractDefinition const*, OverrideProxyBySignatureMultiSet> mutable m_inheritedFunctions;
std::map<ContractDefinition const*, OverrideProxyBySignatureMultiSet> mutable m_inheritedModifiers;
};
}
}

View File

@ -60,6 +60,27 @@ void PostTypeChecker::endVisit(ContractDefinition const&)
m_constVariableDependencies.clear();
}
void PostTypeChecker::endVisit(OverrideSpecifier const& _overrideSpecifier)
{
for (ASTPointer<UserDefinedTypeName> const& override: _overrideSpecifier.overrides())
{
Declaration const* decl = override->annotation().referencedDeclaration;
solAssert(decl, "Expected declaration to be resolved.");
if (dynamic_cast<ContractDefinition const*>(decl))
continue;
TypeType const* actualTypeType = dynamic_cast<TypeType const*>(decl->type());
m_errorReporter.typeError(
override->location(),
"Expected contract but got " +
actualTypeType->actualType()->toString(true) +
"."
);
}
}
bool PostTypeChecker::visit(VariableDeclaration const& _variable)
{
solAssert(!m_currentConstVariable, "");

View File

@ -37,6 +37,7 @@ namespace solidity
/**
* This module performs analyses on the AST that are done after type checking and assignments of types:
* - whether there are circular references in constant state variables
* - whether override specifiers are actually contracts
* @TODO factor out each use-case into an individual class (but do the traversal only once)
*/
class PostTypeChecker: private ASTConstVisitor
@ -53,6 +54,7 @@ private:
bool visit(ContractDefinition const& _contract) override;
void endVisit(ContractDefinition const& _contract) override;
void endVisit(OverrideSpecifier const& _overrideSpecifier) override;
bool visit(VariableDeclaration const& _variable) override;
void endVisit(VariableDeclaration const& _variable) override;

View File

@ -198,21 +198,21 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
{
switch (_typeName.visibility())
{
case VariableDeclaration::Visibility::Internal:
case VariableDeclaration::Visibility::External:
case Visibility::Internal:
case Visibility::External:
break;
default:
fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
return;
}
if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External)
if (_typeName.isPayable() && _typeName.visibility() != Visibility::External)
{
fatalTypeError(_typeName.location(), "Only external function types can be payable.");
return;
}
if (_typeName.visibility() == VariableDeclaration::Visibility::External)
if (_typeName.visibility() == Visibility::External)
for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes())
{
solAssert(t->annotation().type, "Type not set for parameter.");
@ -279,10 +279,34 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
ErrorList errors;
ErrorReporter errorsIgnored(errors);
yul::ExternalIdentifierAccess::Resolver resolver =
[&](yul::Identifier const& _identifier, yul::IdentifierContext, bool _crossesFunctionBoundary) {
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
[&](yul::Identifier const& _identifier, yul::IdentifierContext _context, bool _crossesFunctionBoundary) {
bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), "_slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), "_offset");
if (_context == yul::IdentifierContext::VariableDeclaration)
{
string namePrefix = _identifier.name.str().substr(0, _identifier.name.str().find('.'));
if (isSlot || isOffset)
declarationError(_identifier.location, "In variable declarations _slot and _offset can not be used as a suffix.");
else if (
auto declarations = m_resolver.nameFromCurrentScope(namePrefix);
!declarations.empty()
)
{
SecondarySourceLocation ssl;
for (auto const* decl: declarations)
ssl.append("The shadowed declaration is here:", decl->location());
if (!ssl.infos.empty())
declarationError(
_identifier.location,
ssl,
namePrefix.size() < _identifier.name.str().size() ?
"The prefix of this declaration conflicts with a declaration outside the inline assembly block." :
"This declaration shadows a declaration outside the inline assembly block."
);
}
return size_t(-1);
}
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
if (isSlot || isOffset)
{
// special mode to access storage variables
@ -323,12 +347,10 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
// Will be re-generated later with correct information
// We use the latest EVM version because we will re-run it anyway.
yul::AsmAnalysisInfo analysisInfo;
std::optional<Error::Type> errorTypeForLoose = Error::Type::SyntaxError;
yul::AsmAnalyzer(
analysisInfo,
errorsIgnored,
errorTypeForLoose,
yul::EVMDialect::looseAssemblyForEVM(m_evmVersion),
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
resolver
).analyze(_inlineAssembly.operations());
return false;
@ -388,7 +410,7 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
", ",
" or "
);
if (_variable.isCallableParameter())
if (_variable.isCallableOrCatchParameter())
errorString +=
" for " +
string(_variable.isReturnParameter() ? "return " : "") +
@ -466,6 +488,12 @@ void ReferencesResolver::declarationError(SourceLocation const& _location, strin
m_errorReporter.declarationError(_location, _description);
}
void ReferencesResolver::declarationError(SourceLocation const& _location, SecondarySourceLocation const& _ssl, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.declarationError(_location, _ssl, _description);
}
void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;

View File

@ -94,6 +94,9 @@ private:
/// Adds a new error to the list of errors.
void declarationError(langutil::SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors.
void declarationError(langutil::SourceLocation const& _location, langutil::SecondarySourceLocation const& _ssl, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalDeclarationError(langutil::SourceLocation const& _location, std::string const& _description);

View File

@ -118,10 +118,12 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
for (auto const& var: m_localVarUseCount)
if (var.second == 0)
{
if (var.first.second->isCallableParameter())
if (var.first.second->isCallableOrCatchParameter())
m_errorReporter.warning(
var.first.second->location(),
"Unused function parameter. Remove or comment out the variable name to silence this warning."
"Unused " +
string(var.first.second->isTryCatchParameter() ? "try/catch" : "function") +
" parameter. Remove or comment out the variable name to silence this warning."
);
else
m_errorReporter.warning(var.first.second->location(), "Unused local variable.");

View File

@ -106,7 +106,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
{
auto feature = ExperimentalFeatureNames.at(literal);
m_sourceUnit->annotation().experimentalFeatures.insert(feature);
if (!ExperimentalFeatureOnlyAnalysis.count(feature))
if (!ExperimentalFeatureWithoutWarning.count(feature))
m_errorReporter.warning(_pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments.");
}
}
@ -295,7 +295,7 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
if (_function.noVisibilitySpecified())
{
string suggestedVisibility = _function.isFallback() || m_isInterface ? "external" : "public";
string suggestedVisibility = _function.isFallback() || _function.isReceive() || m_isInterface ? "external" : "public";
m_errorReporter.syntaxError(
_function.location(),
"No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?"

View File

@ -89,7 +89,6 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
for (auto const& n: _contract.subNodes())
n->accept(*this);
return false;
}
@ -137,19 +136,17 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
);
if (arguments.size() >= 1)
{
BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory());
if (!result)
m_errorReporter.typeErrorConcatenateDescriptions(
if (
!type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()) &&
!type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata())
)
m_errorReporter.typeError(
arguments.front()->location(),
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
"The first argument to \"abi.decode\" must be implicitly convertible to "
"bytes memory or bytes calldata, but is of type " +
type(*arguments.front())->toString() +
" to bytes memory requested.",
result.message()
"."
);
}
if (arguments.size() < 2)
return {};
@ -330,11 +327,20 @@ bool TypeChecker::visit(StructDefinition const& _struct)
bool TypeChecker::visit(FunctionDefinition const& _function)
{
bool isLibraryFunction = _function.inContractKind() == ContractDefinition::ContractKind::Library;
if (_function.markedVirtual())
{
if (_function.annotation().contract->isInterface())
m_errorReporter.warning(_function.location(), "Interface functions are implicitly \"virtual\"");
if (_function.visibility() == Visibility::Private)
m_errorReporter.typeError(_function.location(), "\"virtual\" and \"private\" cannot be used together.");
}
if (_function.isPayable())
{
if (isLibraryFunction)
m_errorReporter.typeError(_function.location(), "Library functions cannot be payable.");
if (!_function.isConstructor() && !_function.isFallback() && !_function.isPartOfExternalInterface())
if (_function.isOrdinary() && !_function.isPartOfExternalInterface())
m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable.");
}
auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) {
@ -374,7 +380,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
)
m_errorReporter.typeError(
var.location(),
"This type is only supported in the new experimental ABI encoder. "
"This type is only supported in ABIEncoderV2. "
"Use \"pragma experimental ABIEncoderV2;\" to enable the feature."
);
};
@ -413,7 +419,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
if (_function.isImplemented())
m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation.");
if (_function.visibility() != FunctionDefinition::Visibility::External)
if (_function.visibility() != Visibility::External)
m_errorReporter.typeError(_function.location(), "Functions in interfaces must be declared external.");
if (_function.isConstructor())
@ -426,8 +432,19 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
_function.body().accept(*this);
else if (_function.isConstructor())
m_errorReporter.typeError(_function.location(), "Constructor must be implemented if declared.");
else if (isLibraryFunction && _function.visibility() <= FunctionDefinition::Visibility::Internal)
m_errorReporter.typeError(_function.location(), "Internal library function must be implemented if declared.");
else if (isLibraryFunction)
m_errorReporter.typeError(_function.location(), "Library functions must be implemented if declared.");
else if (!_function.virtualSemantics())
m_errorReporter.typeError(_function.location(), "Functions without implementation must be marked virtual.");
if (_function.isFallback())
typeCheckFallbackFunction(_function);
else if (_function.isReceive())
typeCheckReceiveFunction(_function);
else if (_function.isConstructor())
typeCheckConstructor(_function);
return false;
}
@ -438,7 +455,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
// * or inside of a struct definition.
if (
m_scope->isInterface()
&& !_variable.isCallableParameter()
&& !_variable.isCallableOrCatchParameter()
&& !m_insideStruct
)
m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces.");
@ -481,7 +498,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (!varType->canLiveOutsideStorage())
m_errorReporter.typeError(_variable.location(), "Type " + varType->toString() + " is only valid in storage.");
}
else if (_variable.visibility() >= VariableDeclaration::Visibility::Public)
else if (_variable.visibility() >= Visibility::Public)
{
FunctionType getter(_variable);
if (!_variable.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
@ -492,7 +509,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
unsupportedTypes.emplace_back(param->toString());
if (!unsupportedTypes.empty())
m_errorReporter.typeError(_variable.location(),
"The following types are only supported for getters in the new experimental ABI encoder: " +
"The following types are only supported for getters in ABIEncoderV2: " +
joinHumanReadable(unsupportedTypes) +
". Either remove \"public\" or use \"pragma experimental ABIEncoderV2;\" to enable the feature."
);
@ -587,7 +604,7 @@ void TypeChecker::visitManually(
bool TypeChecker::visit(EventDefinition const& _eventDef)
{
solAssert(_eventDef.visibility() > Declaration::Visibility::Internal, "");
solAssert(_eventDef.visibility() > Visibility::Internal, "");
unsigned numIndexed = 0;
for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters())
{
@ -603,7 +620,7 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
)
m_errorReporter.typeError(
var->location(),
"This type is only supported in the new experimental ABI encoder. "
"This type is only supported in ABIEncoderV2. "
"Use \"pragma experimental ABIEncoderV2;\" to enable the feature."
);
}
@ -743,7 +760,6 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
yul::AsmAnalyzer analyzer(
*_inlineAssembly.annotation().analysisInfo,
m_errorReporter,
Error::Type::SyntaxError,
_inlineAssembly.dialect(),
identifierAccess
);
@ -761,6 +777,133 @@ bool TypeChecker::visit(IfStatement const& _ifStatement)
return false;
}
void TypeChecker::endVisit(TryStatement const& _tryStatement)
{
FunctionCall const* externalCall = dynamic_cast<FunctionCall const*>(&_tryStatement.externalCall());
if (!externalCall || externalCall->annotation().kind != FunctionCallKind::FunctionCall)
{
m_errorReporter.typeError(
_tryStatement.externalCall().location(),
"Try can only be used with external function calls and contract creation calls."
);
return;
}
FunctionType const& functionType = dynamic_cast<FunctionType const&>(*externalCall->expression().annotation().type);
if (
functionType.kind() != FunctionType::Kind::External &&
functionType.kind() != FunctionType::Kind::Creation &&
functionType.kind() != FunctionType::Kind::DelegateCall
)
{
m_errorReporter.typeError(
_tryStatement.externalCall().location(),
"Try can only be used with external function calls and contract creation calls."
);
return;
}
externalCall->annotation().tryCall = true;
solAssert(_tryStatement.clauses().size() >= 2, "");
solAssert(_tryStatement.clauses().front(), "");
TryCatchClause const& successClause = *_tryStatement.clauses().front();
if (successClause.parameters())
{
TypePointers returnTypes =
m_evmVersion.supportsReturndata() ?
functionType.returnParameterTypes() :
functionType.returnParameterTypesWithoutDynamicTypes();
std::vector<ASTPointer<VariableDeclaration>> const& parameters =
successClause.parameters()->parameters();
if (returnTypes.size() != parameters.size())
m_errorReporter.typeError(
successClause.location(),
"Function returns " +
to_string(functionType.returnParameterTypes().size()) +
" values, but returns clause has " +
to_string(parameters.size()) +
" variables."
);
size_t len = min(returnTypes.size(), parameters.size());
for (size_t i = 0; i < len; ++i)
{
solAssert(returnTypes[i], "");
if (parameters[i] && *parameters[i]->annotation().type != *returnTypes[i])
m_errorReporter.typeError(
parameters[i]->location(),
"Invalid type, expected " +
returnTypes[i]->toString(false) +
" but got " +
parameters[i]->annotation().type->toString() +
"."
);
}
}
TryCatchClause const* errorClause = nullptr;
TryCatchClause const* lowLevelClause = nullptr;
for (size_t i = 1; i < _tryStatement.clauses().size(); ++i)
{
TryCatchClause const& clause = *_tryStatement.clauses()[i];
if (clause.errorName() == "")
{
if (lowLevelClause)
m_errorReporter.typeError(
clause.location(),
SecondarySourceLocation{}.append("The first clause is here:", lowLevelClause->location()),
"This try statement already has a low-level catch clause."
);
lowLevelClause = &clause;
if (clause.parameters() && !clause.parameters()->parameters().empty())
{
if (
clause.parameters()->parameters().size() != 1 ||
*clause.parameters()->parameters().front()->type() != *TypeProvider::bytesMemory()
)
m_errorReporter.typeError(clause.location(), "Expected `catch (bytes memory ...) { ... }` or `catch { ... }`.");
if (!m_evmVersion.supportsReturndata())
m_errorReporter.typeError(
clause.location(),
"This catch clause type cannot be used on the selected EVM version (" +
m_evmVersion.name() +
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
);
}
}
else if (clause.errorName() == "Error")
{
if (!m_evmVersion.supportsReturndata())
m_errorReporter.typeError(
clause.location(),
"This catch clause type cannot be used on the selected EVM version (" +
m_evmVersion.name() +
"). You need at least a Byzantium-compatible EVM or use `catch { ... }`."
);
if (errorClause)
m_errorReporter.typeError(
clause.location(),
SecondarySourceLocation{}.append("The first clause is here:", errorClause->location()),
"This try statement already has an \"Error\" catch clause."
);
errorClause = &clause;
if (
!clause.parameters() ||
clause.parameters()->parameters().size() != 1 ||
*clause.parameters()->parameters().front()->type() != *TypeProvider::stringMemory()
)
m_errorReporter.typeError(clause.location(), "Expected `catch Error(string memory ...) { ... }`.");
}
else
m_errorReporter.typeError(
clause.location(),
"Invalid catch clause name. Expected either `catch (...)` or `catch Error(...)`."
);
}
}
bool TypeChecker::visit(WhileStatement const& _whileStatement)
{
expectType(_whileStatement.condition(), *TypeProvider::boolean());
@ -1413,6 +1556,23 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
"might overflow. Silence this warning by converting the literal to the "
"expected type."
);
if (
commonType->category() == Type::Category::Integer &&
rightType->category() == Type::Category::Integer &&
dynamic_cast<IntegerType const&>(*commonType).numBits() <
dynamic_cast<IntegerType const&>(*rightType).numBits()
)
m_errorReporter.warning(
_operation.location(),
"The result type of the " +
operation +
" operation is equal to the type of the first operand (" +
commonType->toString() +
") ignoring the (larger) type of the second operand (" +
rightType->toString() +
") which might be unexpected. Silence this warning by either converting "
"the first or the second operand to the type of the other."
);
}
}
@ -1494,12 +1654,28 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
variableDeclaration->location()
);
m_errorReporter.typeError(
_functionCall.location(), ssl,
_functionCall.location(),
ssl,
"Explicit type conversion not allowed from non-payable \"address\" to \"" +
resultType->toString() +
"\", which has a payable fallback function."
);
}
else if (
auto const* functionType = dynamic_cast<FunctionType const*>(argType);
functionType &&
functionType->kind() == FunctionType::Kind::External &&
resultType->category() == Type::Category::Address
)
m_errorReporter.typeError(
_functionCall.location(),
"Explicit type conversion not allowed from \"" +
argType->toString() +
"\" to \"" +
resultType->toString() +
"\". To obtain the address of the contract of the function, " +
"you can use the .address member of the function."
);
else
m_errorReporter.typeError(
_functionCall.location(),
@ -1510,9 +1686,12 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
"\"."
);
}
if (resultType->category() == Type::Category::Address)
if (auto addressType = dynamic_cast<AddressType const*>(resultType))
if (addressType->stateMutability() != StateMutability::Payable)
{
bool const payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress());
bool payable = false;
if (argType->category() != Type::Category::Address)
payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress());
resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address();
}
}
@ -1550,6 +1729,71 @@ void TypeChecker::typeCheckFunctionCall(
typeCheckFunctionGeneralChecks(_functionCall, _functionType);
}
void TypeChecker::typeCheckFallbackFunction(FunctionDefinition const& _function)
{
solAssert(_function.isFallback(), "");
if (_function.inContractKind() == ContractDefinition::ContractKind::Library)
m_errorReporter.typeError(_function.location(), "Libraries cannot have fallback functions.");
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
_function.location(),
"Fallback function must be payable or non-payable, but is \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != Visibility::External)
m_errorReporter.typeError(_function.location(), "Fallback function must be defined as \"external\".");
if (!_function.returnParameters().empty())
{
if (_function.returnParameters().size() > 1 || *type(*_function.returnParameters().front()) != *TypeProvider::bytesMemory())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Fallback function can only have a single \"bytes memory\" return value.");
else
m_errorReporter.typeError(_function.returnParameterList()->location(), "Return values for fallback functions are not yet implemented.");
}
if (!_function.parameters().empty())
m_errorReporter.typeError(_function.parameterList().location(), "Fallback function cannot take parameters.");
}
void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)
{
solAssert(_function.isReceive(), "");
if (_function.inContractKind() == ContractDefinition::ContractKind::Library)
m_errorReporter.typeError(_function.location(), "Libraries cannot have receive ether functions.");
if (_function.stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
_function.location(),
"Receive ether function must be payable, but is \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != Visibility::External)
m_errorReporter.typeError(_function.location(), "Receive ether function must be defined as \"external\".");
if (!_function.returnParameters().empty())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Receive ether function cannot return values.");
if (!_function.parameters().empty())
m_errorReporter.typeError(_function.parameterList().location(), "Receive ether function cannot take parameters.");
}
void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
{
solAssert(_function.isConstructor(), "");
if (!_function.returnParameters().empty())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
_function.location(),
"Constructor must be payable or non-payable, but is \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != Visibility::Public && _function.visibility() != Visibility::Internal)
m_errorReporter.typeError(_function.location(), "Constructor must be public or internal.");
}
void TypeChecker::typeCheckABIEncodeFunctions(
FunctionCall const& _functionCall,
FunctionTypePointer _functionType
@ -1871,7 +2115,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
FunctionCallAnnotation& funcCallAnno = _functionCall.annotation();
FunctionTypePointer functionType = nullptr;
// Determine and assign function call kind, purity and function type for this FunctionCall node
// Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node
switch (expressionType->category())
{
case Type::Category::Function:
@ -1885,6 +2129,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
functionType &&
functionType->isPure();
if (
functionType->kind() == FunctionType::Kind::ArrayPush ||
functionType->kind() == FunctionType::Kind::ByteArrayPush
)
funcCallAnno.isLValue = functionType->parameterTypes().empty();
break;
case Type::Category::TypeType:
@ -1992,21 +2242,10 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
if (contract->isInterface())
m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface.");
if (!contract->annotation().unimplementedFunctions.empty())
{
SecondarySourceLocation ssl;
for (auto function: contract->annotation().unimplementedFunctions)
ssl.append("Missing implementation:", function->location());
string msg = "Trying to create an instance of an abstract contract.";
ssl.limitSize(msg);
m_errorReporter.typeError(
_newExpression.location(),
ssl,
msg
);
}
if (!contract->constructorIsPublic())
m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
if (contract->abstract())
m_errorReporter.typeError(_newExpression.location(), "Cannot instantiate an abstract contract.");
solAssert(!!m_scope, "");
m_scope->annotation().contractDependencies.insert(contract);
@ -2169,14 +2408,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (auto const* structType = dynamic_cast<StructType const*>(exprType))
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
else if (exprType->category() == Type::Category::Array)
{
auto const& arrayType(dynamic_cast<ArrayType const&>(*exprType));
annotation.isLValue = (
memberName == "length" &&
arrayType.location() == DataLocation::Storage &&
arrayType.isDynamicallySized()
);
}
annotation.isLValue = false;
else if (exprType->category() == Type::Category::FixedBytes)
annotation.isLValue = false;
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType))
@ -2225,6 +2457,14 @@ bool TypeChecker::visit(IndexAccess const& _access)
Expression const* index = _access.indexExpression();
switch (baseType->category())
{
case Type::Category::ArraySlice:
{
auto const& arrayType = dynamic_cast<ArraySliceType const&>(*baseType).arrayType();
if (arrayType.location() != DataLocation::CallData || !arrayType.isDynamicallySized())
m_errorReporter.typeError(_access.location(), "Index access is only implemented for slices of dynamic calldata arrays.");
baseType = &arrayType;
[[fallthrough]];
}
case Type::Category::Array:
{
ArrayType const& actualType = dynamic_cast<ArrayType const&>(*baseType);
@ -2321,6 +2561,50 @@ bool TypeChecker::visit(IndexAccess const& _access)
return false;
}
bool TypeChecker::visit(IndexRangeAccess const& _access)
{
_access.baseExpression().accept(*this);
bool isLValue = false; // TODO: set this correctly when implementing slices for memory and storage arrays
bool isPure = _access.baseExpression().annotation().isPure;
if (Expression const* start = _access.startExpression())
{
expectType(*start, *TypeProvider::uint256());
if (!start->annotation().isPure)
isPure = false;
}
if (Expression const* end = _access.endExpression())
{
expectType(*end, *TypeProvider::uint256());
if (!end->annotation().isPure)
isPure = false;
}
TypePointer exprType = type(_access.baseExpression());
if (exprType->category() == Type::Category::TypeType)
{
m_errorReporter.typeError(_access.location(), "Types cannot be sliced.");
_access.annotation().type = exprType;
return false;
}
ArrayType const* arrayType = nullptr;
if (auto const* arraySlice = dynamic_cast<ArraySliceType const*>(exprType))
arrayType = &arraySlice->arrayType();
else if (!(arrayType = dynamic_cast<ArrayType const*>(exprType)))
m_errorReporter.fatalTypeError(_access.location(), "Index range access is only possible for arrays and array slices.");
if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized())
m_errorReporter.typeError(_access.location(), "Index range access is only supported for dynamic calldata arrays.");
_access.annotation().type = TypeProvider::arraySlice(*arrayType);
_access.annotation().isLValue = isLValue;
_access.annotation().isPure = isPure;
return false;
}
bool TypeChecker::visit(Identifier const& _identifier)
{
IdentifierAnnotation& annotation = _identifier.annotation();
@ -2433,7 +2717,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
{
_expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.typeName()));
_expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.type().typeName(), _expr.type().stateMutability()));
_expr.annotation().isPure = true;
}
@ -2581,17 +2865,9 @@ void TypeChecker::requireLValue(Expression const& _expression)
if (structType->dataStoredIn(DataLocation::CallData))
return "Calldata structs are read-only.";
}
else if (auto arrayType = dynamic_cast<ArrayType const*>(type(memberAccess->expression())))
else if (dynamic_cast<ArrayType const*>(type(memberAccess->expression())))
if (memberAccess->memberName() == "length")
switch (arrayType->location())
{
case DataLocation::Memory:
return "Memory arrays cannot be resized.";
case DataLocation::CallData:
return "Calldata arrays cannot be resized.";
case DataLocation::Storage:
break;
}
return "Member \"length\" is read-only and cannot be used to resize arrays.";
}
if (auto identifier = dynamic_cast<Identifier const*>(&_expression))

View File

@ -96,6 +96,10 @@ private:
FunctionTypePointer _functionType
);
void typeCheckFallbackFunction(FunctionDefinition const& _function);
void typeCheckReceiveFunction(FunctionDefinition const& _function);
void typeCheckConstructor(FunctionDefinition const& _function);
/// Performs general number and type checks of arguments against function call and struct ctor FunctionCall node parameters.
void typeCheckFunctionGeneralChecks(
FunctionCall const& _functionCall,
@ -120,6 +124,7 @@ private:
void endVisit(FunctionTypeName const& _funType) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(IfStatement const& _ifStatement) override;
void endVisit(TryStatement const& _tryStatement) override;
bool visit(WhileStatement const& _whileStatement) override;
bool visit(ForStatement const& _forStatement) override;
void endVisit(Return const& _return) override;
@ -136,6 +141,7 @@ private:
void endVisit(NewExpression const& _newExpression) override;
bool visit(MemberAccess const& _memberAccess) override;
bool visit(IndexAccess const& _indexAccess) override;
bool visit(IndexRangeAccess const& _indexRangeAccess) override;
bool visit(Identifier const& _identifier) override;
void endVisit(ElementaryTypeNameExpression const& _expr) override;
void endVisit(Literal const& _literal) override;

View File

@ -43,24 +43,16 @@ public:
m_dialect(_dialect),
m_reportMutability(_reportMutability) {}
void operator()(yul::Label const&) { }
void operator()(yul::Instruction const& _instruction)
{
checkInstruction(_instruction.location, _instruction.instruction);
}
void operator()(yul::Literal const&) {}
void operator()(yul::Identifier const&) {}
void operator()(yul::FunctionalInstruction const& _instr)
{
checkInstruction(_instr.location, _instr.instruction);
for (auto const& arg: _instr.arguments)
std::visit(*this, arg);
}
void operator()(yul::ExpressionStatement const& _expr)
{
std::visit(*this, _expr.expression);
}
void operator()(yul::StackAssignment const&) {}
void operator()(yul::Assignment const& _assignment)
{
std::visit(*this, *_assignment.value);
@ -112,6 +104,9 @@ public:
void operator()(yul::Continue const&)
{
}
void operator()(yul::Leave const&)
{
}
void operator()(yul::Block const& _block)
{
for (auto const& s: _block.statements)
@ -175,7 +170,8 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
!_funDef.body().statements().empty() &&
!_funDef.isConstructor() &&
!_funDef.isFallback() &&
!_funDef.annotation().superFunction
!_funDef.isReceive() &&
!_funDef.overrides()
)
m_errorReporter.warning(
_funDef.location(),
@ -387,7 +383,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{
auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage))
mutability = writes ? StateMutability::NonPayable : StateMutability::View;
mutability = StateMutability::View;
break;
}
default:
@ -415,6 +411,13 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
}
}
void ViewPureChecker::endVisit(IndexRangeAccess const& _indexRangeAccess)
{
bool writes = _indexRangeAccess.annotation().lValueRequested;
if (_indexRangeAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexRangeAccess.location());
}
void ViewPureChecker::endVisit(ModifierInvocation const& _modifier)
{
solAssert(_modifier.name(), "");

View File

@ -59,6 +59,7 @@ private:
bool visit(MemberAccess const& _memberAccess) override;
void endVisit(MemberAccess const& _memberAccess) override;
void endVisit(IndexAccess const& _indexAccess) override;
void endVisit(IndexRangeAccess const& _indexAccess) override;
void endVisit(ModifierInvocation const& _modifier) override;
void endVisit(FunctionCall const& _functionCall) override;
void endVisit(InlineAssembly const& _inlineAssembly) override;

View File

@ -146,7 +146,7 @@ bool ContractDefinition::constructorIsPublic() const
bool ContractDefinition::canBeDeployed() const
{
return constructorIsPublic() && annotation().unimplementedFunctions.empty();
return constructorIsPublic() && !abstract() && !isInterface();
}
FunctionDefinition const* ContractDefinition::fallbackFunction() const
@ -158,6 +158,15 @@ FunctionDefinition const* ContractDefinition::fallbackFunction() const
return nullptr;
}
FunctionDefinition const* ContractDefinition::receiveFunction() const
{
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
for (FunctionDefinition const* f: contract->definedFunctions())
if (f->isReceive())
return f;
return nullptr;
}
vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() const
{
if (!m_interfaceEvents)
@ -303,19 +312,29 @@ ContractDefinition::ContractKind FunctionDefinition::inContractKind() const
return contractDef->contractKind();
}
CallableDeclarationAnnotation& CallableDeclaration::annotation() const
{
solAssert(
m_annotation,
"CallableDeclarationAnnotation is an abstract base, need to call annotation on the concrete class first."
);
return dynamic_cast<CallableDeclarationAnnotation&>(*m_annotation);
}
FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
{
if (_internal)
{
switch (visibility())
{
case Declaration::Visibility::Default:
case Visibility::Default:
solAssert(false, "visibility() should not return Default");
case Declaration::Visibility::Private:
case Declaration::Visibility::Internal:
case Declaration::Visibility::Public:
case Visibility::Private:
case Visibility::Internal:
case Visibility::Public:
return TypeProvider::function(*this, _internal);
case Declaration::Visibility::External:
case Visibility::External:
return {};
}
}
@ -323,13 +342,13 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
{
switch (visibility())
{
case Declaration::Visibility::Default:
case Visibility::Default:
solAssert(false, "visibility() should not return Default");
case Declaration::Visibility::Private:
case Declaration::Visibility::Internal:
case Visibility::Private:
case Visibility::Internal:
return {};
case Declaration::Visibility::Public:
case Declaration::Visibility::External:
case Visibility::Public:
case Visibility::External:
return TypeProvider::function(*this, _internal);
}
}
@ -340,7 +359,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
TypePointer FunctionDefinition::type() const
{
solAssert(visibility() != Declaration::Visibility::External, "");
solAssert(visibility() != Visibility::External, "");
return TypeProvider::function(*this);
}
@ -349,6 +368,11 @@ string FunctionDefinition::externalSignature() const
return TypeProvider::function(*this)->externalSignature();
}
string FunctionDefinition::externalIdentifierHex() const
{
return TypeProvider::function(*this)->externalIdentifierHex();
}
FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
{
if (!m_annotation)
@ -443,12 +467,13 @@ bool VariableDeclaration::isLocalVariable() const
dynamic_cast<FunctionTypeName const*>(s) ||
dynamic_cast<CallableDeclaration const*>(s) ||
dynamic_cast<Block const*>(s) ||
dynamic_cast<TryCatchClause const*>(s) ||
dynamic_cast<ForStatement const*>(s);
}
bool VariableDeclaration::isCallableParameter() const
bool VariableDeclaration::isCallableOrCatchParameter() const
{
if (isReturnParameter())
if (isReturnParameter() || isTryCatchParameter())
return true;
vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr;
@ -467,7 +492,7 @@ bool VariableDeclaration::isCallableParameter() const
bool VariableDeclaration::isLocalOrReturn() const
{
return isReturnParameter() || (isLocalVariable() && !isCallableParameter());
return isReturnParameter() || (isLocalVariable() && !isCallableOrCatchParameter());
}
bool VariableDeclaration::isReturnParameter() const
@ -487,13 +512,18 @@ bool VariableDeclaration::isReturnParameter() const
return false;
}
bool VariableDeclaration::isTryCatchParameter() const
{
return dynamic_cast<TryCatchClause const*>(scope());
}
bool VariableDeclaration::isExternalCallableParameter() const
{
if (!isCallableParameter())
if (!isCallableOrCatchParameter())
return false;
if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
if (callable->visibility() == Declaration::Visibility::External)
if (callable->visibility() == Visibility::External)
return !isReturnParameter();
return false;
@ -501,19 +531,19 @@ bool VariableDeclaration::isExternalCallableParameter() const
bool VariableDeclaration::isInternalCallableParameter() const
{
if (!isCallableParameter())
if (!isCallableOrCatchParameter())
return false;
if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope()))
return funTypeName->visibility() == Declaration::Visibility::Internal;
return funTypeName->visibility() == Visibility::Internal;
else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
return callable->visibility() <= Declaration::Visibility::Internal;
return callable->visibility() <= Visibility::Internal;
return false;
}
bool VariableDeclaration::isLibraryFunctionParameter() const
{
if (!isCallableParameter())
if (!isCallableOrCatchParameter())
return false;
if (auto const* funDef = dynamic_cast<FunctionDefinition const*>(scope()))
return dynamic_cast<ContractDefinition const&>(*funDef->scope()).isLibrary();
@ -549,10 +579,10 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
locations.insert(Location::Storage);
return locations;
}
else if (isCallableParameter())
else if (isCallableOrCatchParameter())
{
set<Location> locations{ Location::Memory };
if (isInternalCallableParameter() || isLibraryFunctionParameter())
if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter())
locations.insert(Location::Storage);
return locations;
}
@ -571,6 +601,12 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
return set<Location>{ Location::Unspecified };
}
string VariableDeclaration::externalIdentifierHex() const
{
solAssert(isStateVariable() && isPublic(), "Can only be called for public state variables");
return TypeProvider::function(*this)->externalIdentifierHex();
}
TypePointer VariableDeclaration::type() const
{
return annotation().type;
@ -582,13 +618,13 @@ FunctionTypePointer VariableDeclaration::functionType(bool _internal) const
return nullptr;
switch (visibility())
{
case Declaration::Visibility::Default:
case Visibility::Default:
solAssert(false, "visibility() should not return Default");
case Declaration::Visibility::Private:
case Declaration::Visibility::Internal:
case Visibility::Private:
case Visibility::Internal:
return nullptr;
case Declaration::Visibility::Public:
case Declaration::Visibility::External:
case Visibility::Public:
case Visibility::External:
return TypeProvider::function(*this);
}

View File

@ -182,20 +182,18 @@ protected:
class Declaration: public ASTNode, public Scopable
{
public:
/// Visibility ordered from restricted to unrestricted.
enum class Visibility { Default, Private, Internal, Public, External };
static std::string visibilityToString(Declaration::Visibility _visibility)
static std::string visibilityToString(Visibility _visibility)
{
switch (_visibility)
{
case Declaration::Visibility::Public:
case Visibility::Public:
return "public";
case Declaration::Visibility::Internal:
case Visibility::Internal:
return "internal";
case Declaration::Visibility::Private:
case Visibility::Private:
return "private";
case Declaration::Visibility::External:
case Visibility::External:
return "external";
default:
solAssert(false, "Invalid visibility specifier.");
@ -388,13 +386,15 @@ public:
ASTPointer<ASTString> const& _documentation,
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
std::vector<ASTPointer<ASTNode>> const& _subNodes,
ContractKind _contractKind = ContractKind::Contract
ContractKind _contractKind = ContractKind::Contract,
bool _abstract = false
):
Declaration(_location, _name),
Documented(_documentation),
m_baseContracts(_baseContracts),
m_subNodes(_subNodes),
m_contractKind(_contractKind)
m_contractKind(_contractKind),
m_abstract(_abstract)
{}
void accept(ASTVisitor& _visitor) override;
@ -433,6 +433,9 @@ public:
/// Returns the fallback function or nullptr if no fallback function was specified.
FunctionDefinition const* fallbackFunction() const;
/// Returns the ether receiver function or nullptr if no receive function was specified.
FunctionDefinition const* receiveFunction() const;
std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
TypePointer type() const override;
@ -441,10 +444,13 @@ public:
ContractKind contractKind() const { return m_contractKind; }
bool abstract() const { return m_abstract; }
private:
std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
std::vector<ASTPointer<ASTNode>> m_subNodes;
ContractKind m_contractKind;
bool m_abstract{false};
mutable std::unique_ptr<std::vector<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
@ -563,7 +569,7 @@ public:
};
/**
* Parameter list, used as function parameter list and return list.
* Parameter list, used as function parameter list, return list and for try and catch.
* None of the parameters is allowed to contain mappings (not even recursively
* inside structs).
*/
@ -594,24 +600,61 @@ public:
CallableDeclaration(
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
Declaration::Visibility _visibility,
Visibility _visibility,
ASTPointer<ParameterList> const& _parameters,
bool _isVirtual = false,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
ASTPointer<ParameterList> const& _returnParameters = ASTPointer<ParameterList>()
):
Declaration(_location, _name, _visibility),
m_parameters(_parameters),
m_returnParameters(_returnParameters)
m_overrides(_overrides),
m_returnParameters(_returnParameters),
m_isVirtual(_isVirtual)
{
}
std::vector<ASTPointer<VariableDeclaration>> const& parameters() const { return m_parameters->parameters(); }
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
ParameterList const& parameterList() const { return *m_parameters; }
ASTPointer<ParameterList> const& returnParameterList() const { return m_returnParameters; }
bool markedVirtual() const { return m_isVirtual; }
virtual bool virtualSemantics() const { return markedVirtual(); }
CallableDeclarationAnnotation& annotation() const override;
protected:
ASTPointer<ParameterList> m_parameters;
ASTPointer<OverrideSpecifier> m_overrides;
ASTPointer<ParameterList> m_returnParameters;
bool m_isVirtual = false;
};
/**
* Function override specifier. Consists of a single override keyword
* potentially followed by a parenthesized list of base contract names.
*/
class OverrideSpecifier: public ASTNode
{
public:
OverrideSpecifier(
SourceLocation const& _location,
std::vector<ASTPointer<UserDefinedTypeName>> const& _overrides
):
ASTNode(_location),
m_overrides(_overrides)
{
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
/// @returns the list of specific overrides, if any
std::vector<ASTPointer<UserDefinedTypeName>> const& overrides() const { return m_overrides; }
protected:
std::vector<ASTPointer<UserDefinedTypeName>> m_overrides;
};
class FunctionDefinition: public CallableDeclaration, public Documented, public ImplementationOptional
@ -620,44 +663,54 @@ public:
FunctionDefinition(
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
Declaration::Visibility _visibility,
Visibility _visibility,
StateMutability _stateMutability,
bool _isConstructor,
Token _kind,
bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<ASTString> const& _documentation,
ASTPointer<ParameterList> const& _parameters,
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
ASTPointer<ParameterList> const& _returnParameters,
ASTPointer<Block> const& _body
):
CallableDeclaration(_location, _name, _visibility, _parameters, _returnParameters),
CallableDeclaration(_location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters),
Documented(_documentation),
ImplementationOptional(_body != nullptr),
m_stateMutability(_stateMutability),
m_isConstructor(_isConstructor),
m_kind(_kind),
m_functionModifiers(_modifiers),
m_body(_body)
{}
{
solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, "");
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
StateMutability stateMutability() const { return m_stateMutability; }
bool isConstructor() const { return m_isConstructor; }
bool isFallback() const { return !m_isConstructor && name().empty(); }
bool isOrdinary() const { return m_kind == Token::Function; }
bool isConstructor() const { return m_kind == Token::Constructor; }
bool isFallback() const { return m_kind == Token::Fallback; }
bool isReceive() const { return m_kind == Token::Receive; }
Token kind() const { return m_kind; }
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
Block const& body() const { solAssert(m_body, ""); return *m_body; }
bool isVisibleInContract() const override
{
return Declaration::isVisibleInContract() && !isConstructor() && !isFallback();
return Declaration::isVisibleInContract() && isOrdinary();
}
bool isPartOfExternalInterface() const override { return isPublic() && !isConstructor() && !isFallback(); }
bool isPartOfExternalInterface() const override { return isPublic() && isOrdinary(); }
/// @returns the external signature of the function
/// That consists of the name of the function followed by the types of the
/// arguments separated by commas all enclosed in parentheses without any spaces.
std::string externalSignature() const;
/// @returns the external identifier of this function (the hash of the signature) as a hex string.
std::string externalIdentifierHex() const;
ContractDefinition::ContractKind inContractKind() const;
TypePointer type() const override;
@ -668,9 +721,15 @@ public:
FunctionDefinitionAnnotation& annotation() const override;
bool virtualSemantics() const override
{
return
CallableDeclaration::virtualSemantics() ||
annotation().contract->isInterface();
}
private:
StateMutability m_stateMutability;
bool m_isConstructor;
Token const m_kind;
std::vector<ASTPointer<ModifierInvocation>> m_functionModifiers;
ASTPointer<Block> m_body;
};
@ -693,6 +752,7 @@ public:
bool _isStateVar = false,
bool _isIndexed = false,
bool _isConstant = false,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
Location _referenceLocation = Location::Unspecified
):
Declaration(_sourceLocation, _name, _visibility),
@ -701,8 +761,10 @@ public:
m_isStateVariable(_isStateVar),
m_isIndexed(_isIndexed),
m_isConstant(_isConstant),
m_overrides(_overrides),
m_location(_referenceLocation) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -716,9 +778,12 @@ public:
/// (or function type name or event) or declared inside a function body.
bool isLocalVariable() const;
/// @returns true if this variable is a parameter or return parameter of a function.
bool isCallableParameter() const;
bool isCallableOrCatchParameter() const;
/// @returns true if this variable is a return parameter of a function.
bool isReturnParameter() const;
/// @returns true if this variable is a parameter of the success or failure clausse
/// of a try/catch statement.
bool isTryCatchParameter() const;
/// @returns true if this variable is a local variable or return parameter.
bool isLocalOrReturn() const;
/// @returns true if this variable is a parameter (not return parameter) of an external function.
@ -740,10 +805,14 @@ public:
bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_isConstant; }
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
Location referenceLocation() const { return m_location; }
/// @returns a set of allowed storage locations for the variable.
std::set<Location> allowedDataLocations() const;
/// @returns the external identifier of this variable (the hash of the signature) as a hex string (works only for public state variables).
std::string externalIdentifierHex() const;
TypePointer type() const override;
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
@ -760,10 +829,11 @@ private:
/// Initially assigned value, can be missing. For local variables, this is stored inside
/// VariableDeclarationStatement and not here.
ASTPointer<Expression> m_value;
bool m_isStateVariable; ///< Whether or not this is a contract state variable
bool m_isIndexed; ///< Whether this is an indexed variable (used by events).
bool m_isConstant; ///< Whether the variable is a compile-time constant.
Location m_location; ///< Location of the variable if it is of reference type.
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
bool m_isConstant = false; ///< Whether the variable is a compile-time constant.
ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node
Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type.
};
/**
@ -777,9 +847,11 @@ public:
ASTPointer<ASTString> const& _name,
ASTPointer<ASTString> const& _documentation,
ASTPointer<ParameterList> const& _parameters,
bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<Block> const& _body
):
CallableDeclaration(_location, _name, Visibility::Internal, _parameters),
CallableDeclaration(_location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
Documented(_documentation),
m_body(_body)
{
@ -960,7 +1032,7 @@ public:
SourceLocation const& _location,
ASTPointer<ParameterList> const& _parameterTypes,
ASTPointer<ParameterList> const& _returnTypes,
Declaration::Visibility _visibility,
Visibility _visibility,
StateMutability _stateMutability
):
TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes),
@ -974,9 +1046,9 @@ public:
ASTPointer<ParameterList> const& parameterTypeList() const { return m_parameterTypes; }
ASTPointer<ParameterList> const& returnParameterTypeList() const { return m_returnTypes; }
Declaration::Visibility visibility() const
Visibility visibility() const
{
return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility;
return m_visibility == Visibility::Default ? Visibility::Internal : m_visibility;
}
StateMutability stateMutability() const { return m_stateMutability; }
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
@ -984,7 +1056,7 @@ public:
private:
ASTPointer<ParameterList> m_parameterTypes;
ASTPointer<ParameterList> m_returnTypes;
Declaration::Visibility m_visibility;
Visibility m_visibility;
StateMutability m_stateMutability;
};
@ -1150,6 +1222,76 @@ private:
ASTPointer<Statement> m_falseBody; ///< "else" part, optional
};
/**
* Clause of a try-catch block. Includes both the successful case and the
* unsuccessful cases.
* Names are only allowed for the unsuccessful cases.
*/
class TryCatchClause: public ASTNode, public Scopable
{
public:
TryCatchClause(
SourceLocation const& _location,
ASTPointer<ASTString> const& _errorName,
ASTPointer<ParameterList> const& _parameters,
ASTPointer<Block> const& _block
):
ASTNode(_location),
m_errorName(_errorName),
m_parameters(_parameters),
m_block(_block)
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
ASTString const& errorName() const { return *m_errorName; }
ParameterList const* parameters() const { return m_parameters.get(); }
Block const& block() const { return *m_block; }
private:
ASTPointer<ASTString> m_errorName;
ASTPointer<ParameterList> m_parameters;
ASTPointer<Block> m_block;
};
/**
* Try-statement with a variable number of catch statements.
* Syntax:
* try <call> returns (uint x, uint y) {
* // success code
* } catch Error(string memory cause) {
* // error code, reason provided
* } catch (bytes memory lowLevelData) {
* // error code, no reason provided or non-matching error signature.
* }
*
* The last statement given above can also be specified as
* } catch () {
*/
class TryStatement: public Statement
{
public:
TryStatement(
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> const& _externalCall,
std::vector<ASTPointer<TryCatchClause>> const& _clauses
):
Statement(_location, _docString),
m_externalCall(_externalCall),
m_clauses(_clauses)
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Expression const& externalCall() const { return *m_externalCall; }
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
private:
ASTPointer<Expression> m_externalCall;
std::vector<ASTPointer<TryCatchClause>> m_clauses;
};
/**
* Statement in which a break statement is legal (abstract class).
*/
@ -1618,6 +1760,32 @@ private:
ASTPointer<Expression> m_index;
};
/**
* Index range access to an array. Example: a[2:3]
*/
class IndexRangeAccess: public Expression
{
public:
IndexRangeAccess(
SourceLocation const& _location,
ASTPointer<Expression> const& _base,
ASTPointer<Expression> const& _start,
ASTPointer<Expression> const& _end
):
Expression(_location), m_base(_base), m_start(_start), m_end(_end) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Expression const& baseExpression() const { return *m_base; }
Expression const* startExpression() const { return m_start.get(); }
Expression const* endExpression() const { return m_end.get(); }
private:
ASTPointer<Expression> m_base;
ASTPointer<Expression> m_start;
ASTPointer<Expression> m_end;
};
/**
* Primary expression, i.e. an expression that cannot be divided any further. Examples are literals
* or variable references.
@ -1658,16 +1826,21 @@ private:
class ElementaryTypeNameExpression: public PrimaryExpression
{
public:
ElementaryTypeNameExpression(SourceLocation const& _location, ElementaryTypeNameToken const& _type):
PrimaryExpression(_location), m_typeToken(_type)
{}
ElementaryTypeNameExpression(
SourceLocation const& _location,
ASTPointer<ElementaryTypeName> const& _type
):
PrimaryExpression(_location),
m_type(_type)
{
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
ElementaryTypeNameToken const& typeName() const { return m_typeToken; }
ElementaryTypeName const& type() const { return *m_type; }
private:
ElementaryTypeNameToken m_typeToken;
ASTPointer<ElementaryTypeName> m_type;
};
/**

View File

@ -104,18 +104,23 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
};
struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
struct CallableDeclarationAnnotation: ASTAnnotation
{
/// The function this function overrides, if any. This is always the closest
/// in the linearized inheritance hierarchy.
FunctionDefinition const* superFunction = nullptr;
/// The set of functions/modifiers/events this callable overrides.
std::set<CallableDeclaration const*> baseFunctions;
};
struct EventDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
{
/// Pointer to the contract this function is defined in
ContractDefinition const* contract = nullptr;
};
struct EventDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
{
};
struct ModifierDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
{
};
@ -123,6 +128,8 @@ struct VariableDeclarationAnnotation: ASTAnnotation
{
/// Type of variable (type of identifier referencing this variable).
TypePointer type = nullptr;
/// The set of functions this (public state) variable overrides.
std::set<CallableDeclaration const*> baseFunctions;
};
struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation
@ -217,6 +224,8 @@ enum class FunctionCallKind
struct FunctionCallAnnotation: ExpressionAnnotation
{
FunctionCallKind kind = FunctionCallKind::Unset;
/// If true, this is the external call of a try statement.
bool tryCall = false;
};
}

View File

@ -34,6 +34,9 @@ namespace solidity
// How a function can mutate the EVM state.
enum class StateMutability { Pure, View, NonPayable, Payable };
/// Visibility ordered from restricted to unrestricted.
enum class Visibility { Default, Private, Internal, Public, External };
inline std::string stateMutabilityToString(StateMutability const& _stateMutability)
{
switch (_stateMutability)

View File

@ -26,7 +26,12 @@
#include <string>
#include <vector>
// Forward-declare all AST node types
// Forward-declare all AST node types and related enums.
namespace langutil
{
enum class Token : unsigned int;
}
namespace dev
{
@ -39,6 +44,7 @@ class PragmaDirective;
class ImportDirective;
class Declaration;
class CallableDeclaration;
class OverrideSpecifier;
class ContractDefinition;
class InheritanceSpecifier;
class UsingForDirective;
@ -63,6 +69,8 @@ class Statement;
class Block;
class PlaceholderStatement;
class IfStatement;
class TryCatchClause;
class TryStatement;
class BreakableStatement;
class WhileStatement;
class ForStatement;

View File

@ -22,11 +22,15 @@
#include <libsolidity/ast/ASTJsonConverter.h>
#include <libsolidity/ast/AST.h>
#include <libyul/AsmJsonConverter.h>
#include <libyul/AsmData.h>
#include <libyul/AsmPrinter.h>
#include <libdevcore/JSON.h>
#include <libdevcore/UTF8.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <vector>
#include <algorithm>
@ -124,11 +128,17 @@ void ASTJsonConverter::setJsonNode(
}
}
size_t ASTJsonConverter::sourceIndexFromLocation(SourceLocation const& _location) const
{
if (_location.source && m_sourceIndices.count(_location.source->name()))
return m_sourceIndices.at(_location.source->name());
else
return size_t(-1);
}
string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location) const
{
int sourceIndex{-1};
if (_location.source && m_sourceIndices.count(_location.source->name()))
sourceIndex = m_sourceIndices.at(_location.source->name());
size_t sourceIndex = sourceIndexFromLocation(_location);
int length = -1;
if (_location.start >= 0 && _location.end >= 0)
length = _location.end - _location.start;
@ -245,7 +255,7 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
{
Json::Value tuple(Json::objectValue);
solAssert(symbolAlias.symbol, "");
tuple["foreign"] = nodeId(*symbolAlias.symbol);
tuple["foreign"] = toJson(*symbolAlias.symbol);
tuple["local"] = symbolAlias.alias ? Json::Value(*symbolAlias.alias) : Json::nullValue;
symbolAliases.append(tuple);
}
@ -260,6 +270,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
make_pair("contractKind", contractKind(_node.contractKind())),
make_pair("abstract", _node.abstract()),
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
make_pair("baseContracts", toJson(_node.baseContracts())),
@ -326,15 +337,24 @@ bool ASTJsonConverter::visit(ParameterList const& _node)
return false;
}
bool ASTJsonConverter::visit(OverrideSpecifier const& _node)
{
setJsonNode(_node, "OverrideSpecifier", {
make_pair("overrides", toJson(_node.overrides()))
});
return false;
}
bool ASTJsonConverter::visit(FunctionDefinition const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
make_pair("kind", _node.isConstructor() ? "constructor" : (_node.isFallback() ? "fallback" : "function")),
make_pair("kind", TokenTraits::toString(_node.kind())),
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
make_pair("superFunction", idOrNull(_node.annotation().superFunction)),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("virtual", _node.markedVirtual()),
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
make_pair("parameters", toJson(_node.parameterList())),
make_pair("returnParameters", toJson(*_node.returnParameterList())),
make_pair("modifiers", toJson(_node.modifiers())),
@ -342,6 +362,10 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
make_pair("implemented", _node.isImplemented()),
make_pair("scope", idOrNull(_node.scope()))
};
if (_node.isPartOfExternalInterface())
attributes.emplace_back("functionSelector", _node.externalIdentifierHex());
if (!_node.annotation().baseFunctions.empty())
attributes.emplace_back(make_pair("baseFunctions", getContainerIds(_node.annotation().baseFunctions, true)));
if (m_legacy)
attributes.emplace_back("isConstructor", _node.isConstructor());
setJsonNode(_node, "FunctionDefinition", std::move(attributes));
@ -356,26 +380,36 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
make_pair("constant", _node.isConstant()),
make_pair("stateVariable", _node.isStateVariable()),
make_pair("storageLocation", location(_node.referenceLocation())),
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue),
make_pair("scope", idOrNull(_node.scope())),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
};
if (_node.isStateVariable() && _node.isPublic())
attributes.emplace_back("functionSelector", _node.externalIdentifierHex());
if (m_inEvent)
attributes.emplace_back("indexed", _node.isIndexed());
if (!_node.annotation().baseFunctions.empty())
attributes.emplace_back(make_pair("baseFunctions", getContainerIds(_node.annotation().baseFunctions, true)));
setJsonNode(_node, "VariableDeclaration", std::move(attributes));
return false;
}
bool ASTJsonConverter::visit(ModifierDefinition const& _node)
{
setJsonNode(_node, "ModifierDefinition", {
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue),
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("parameters", toJson(_node.parameterList())),
make_pair("virtual", _node.markedVirtual()),
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
make_pair("body", toJson(_node.body()))
});
};
if (!_node.annotation().baseFunctions.empty())
attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true)));
setJsonNode(_node, "ModifierDefinition", std::move(attributes));
return false;
}
@ -459,17 +493,22 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node)
bool ASTJsonConverter::visit(InlineAssembly const& _node)
{
Json::Value externalReferences(Json::arrayValue);
vector<pair<string, Json::Value>> externalReferences;
for (auto const& it: _node.annotation().externalReferences)
if (it.first)
{
Json::Value tuple(Json::objectValue);
tuple[it.first->name.str()] = inlineAssemblyIdentifierToJson(it);
externalReferences.append(tuple);
}
externalReferences.emplace_back(make_pair(
it.first->name.str(),
inlineAssemblyIdentifierToJson(it)
));
Json::Value externalReferencesJson = Json::arrayValue;
for (auto&& it: boost::range::sort(externalReferences))
externalReferencesJson.append(std::move(it.second));
setJsonNode(_node, "InlineAssembly", {
make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))),
make_pair("externalReferences", std::move(externalReferences))
m_legacy ?
make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))) :
make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))),
make_pair("externalReferences", std::move(externalReferencesJson))
});
return false;
}
@ -498,6 +537,25 @@ bool ASTJsonConverter::visit(IfStatement const& _node)
return false;
}
bool ASTJsonConverter::visit(TryCatchClause const& _node)
{
setJsonNode(_node, "TryCatchClause", {
make_pair("errorName", _node.errorName()),
make_pair("parameters", toJsonOrNull(_node.parameters())),
make_pair("block", toJson(_node.block()))
});
return false;
}
bool ASTJsonConverter::visit(TryStatement const& _node)
{
setJsonNode(_node, "TryStatement", {
make_pair("externalCall", toJson(_node.externalCall())),
make_pair("clauses", toJson(_node.clauses()))
});
return false;
}
bool ASTJsonConverter::visit(WhileStatement const& _node)
{
setJsonNode(
@ -646,7 +704,8 @@ bool ASTJsonConverter::visit(FunctionCall const& _node)
std::vector<pair<string, Json::Value>> attributes = {
make_pair("expression", toJson(_node.expression())),
make_pair("names", std::move(names)),
make_pair("arguments", toJson(_node.arguments()))
make_pair("arguments", toJson(_node.arguments())),
make_pair("tryCall", _node.annotation().tryCall)
};
if (m_legacy)
{
@ -693,6 +752,18 @@ bool ASTJsonConverter::visit(IndexAccess const& _node)
return false;
}
bool ASTJsonConverter::visit(IndexRangeAccess const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair("baseExpression", toJson(_node.baseExpression())),
make_pair("startExpression", toJsonOrNull(_node.startExpression())),
make_pair("endExpression", toJsonOrNull(_node.endExpression())),
};
appendExpressionAttributes(attributes, _node.annotation());
setJsonNode(_node, "IndexRangeAccess", std::move(attributes));
return false;
}
bool ASTJsonConverter::visit(Identifier const& _node)
{
Json::Value overloads(Json::arrayValue);
@ -711,7 +782,7 @@ bool ASTJsonConverter::visit(Identifier const& _node)
bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node)
{
std::vector<pair<string, Json::Value>> attributes = {
make_pair(m_legacy ? "value" : "typeName", _node.typeName().toString())
make_pair(m_legacy ? "value" : "typeName", toJson(_node.type()))
};
appendExpressionAttributes(attributes, _node.annotation());
setJsonNode(_node, "ElementaryTypeNameExpression", std::move(attributes));

View File

@ -81,6 +81,7 @@ public:
bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override;
bool visit(ParameterList const& _node) override;
bool visit(OverrideSpecifier const& _node) override;
bool visit(FunctionDefinition const& _node) override;
bool visit(VariableDeclaration const& _node) override;
bool visit(ModifierDefinition const& _node) override;
@ -95,6 +96,8 @@ public:
bool visit(Block const& _node) override;
bool visit(PlaceholderStatement const& _node) override;
bool visit(IfStatement const& _node) override;
bool visit(TryCatchClause const& _node) override;
bool visit(TryStatement const& _node) override;
bool visit(WhileStatement const& _node) override;
bool visit(ForStatement const& _node) override;
bool visit(Continue const& _node) override;
@ -113,6 +116,7 @@ public:
bool visit(NewExpression const& _node) override;
bool visit(MemberAccess const& _node) override;
bool visit(IndexAccess const& _node) override;
bool visit(IndexRangeAccess const& _node) override;
bool visit(Identifier const& _node) override;
bool visit(ElementaryTypeNameExpression const& _node) override;
bool visit(Literal const& _node) override;
@ -130,6 +134,7 @@ private:
std::string const& _nodeName,
std::vector<std::pair<std::string, Json::Value>>&& _attributes
);
size_t sourceIndexFromLocation(langutil::SourceLocation const& _location) const;
std::string sourceLocationToString(langutil::SourceLocation const& _location) const;
static std::string namePathToString(std::vector<ASTString> const& _namePath);
static Json::Value idOrNull(ASTNode const* _pt)

View File

@ -1,644 +0,0 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging.
*/
#include <libsolidity/ast/ASTPrinter.h>
#include <libsolidity/ast/AST.h>
#include <boost/algorithm/string/join.hpp>
#include <json/json.h>
using namespace std;
using namespace langutil;
namespace dev
{
namespace solidity
{
ASTPrinter::ASTPrinter(
ASTNode const& _ast,
string const& _source,
GasEstimator::ASTGasConsumption const& _gasCosts
): m_indentation(0), m_source(_source), m_ast(&_ast), m_gasCosts(_gasCosts)
{
}
void ASTPrinter::print(ostream& _stream)
{
m_ostream = &_stream;
m_ast->accept(*this);
m_ostream = nullptr;
}
bool ASTPrinter::visit(PragmaDirective const& _node)
{
writeLine("PragmaDirective");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ImportDirective const& _node)
{
writeLine("ImportDirective \"" + _node.path() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ContractDefinition const& _node)
{
writeLine("ContractDefinition \"" + _node.name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(InheritanceSpecifier const& _node)
{
writeLine("InheritanceSpecifier");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(UsingForDirective const& _node)
{
writeLine("UsingForDirective");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(StructDefinition const& _node)
{
writeLine("StructDefinition \"" + _node.name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(EnumDefinition const& _node)
{
writeLine("EnumDefinition \"" + _node.name() + "\"");
return goDeeper();
}
bool ASTPrinter::visit(EnumValue const& _node)
{
writeLine("EnumValue \"" + _node.name() + "\"");
return goDeeper();
}
bool ASTPrinter::visit(ParameterList const& _node)
{
writeLine("ParameterList");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(FunctionDefinition const& _node)
{
writeLine(
"FunctionDefinition \"" +
_node.name() +
"\"" +
(_node.isPublic() ? " - public" : "") +
(_node.stateMutability() == StateMutability::View ? " - const" : "")
);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(VariableDeclaration const& _node)
{
writeLine("VariableDeclaration \"" + _node.name() + "\"");
*m_ostream << indentation() << (
_node.annotation().type ?
string(" Type: ") + _node.annotation().type->toString() :
string(" Type unknown.")
) << "\n";
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ModifierDefinition const& _node)
{
writeLine("ModifierDefinition \"" + _node.name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ModifierInvocation const& _node)
{
writeLine("ModifierInvocation \"" + _node.name()->name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(EventDefinition const& _node)
{
writeLine("EventDefinition \"" + _node.name() + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ElementaryTypeName const& _node)
{
writeLine(string("ElementaryTypeName ") + _node.typeName().toString());
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(UserDefinedTypeName const& _node)
{
writeLine("UserDefinedTypeName \"" + boost::algorithm::join(_node.namePath(), ".") + "\"");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(FunctionTypeName const& _node)
{
writeLine("FunctionTypeName");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Mapping const& _node)
{
writeLine("Mapping");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ArrayTypeName const& _node)
{
writeLine("ArrayTypeName");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(InlineAssembly const& _node)
{
writeLine("InlineAssembly");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Block const& _node)
{
writeLine("Block");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(PlaceholderStatement const& _node)
{
writeLine("PlaceholderStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(IfStatement const& _node)
{
writeLine("IfStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(WhileStatement const& _node)
{
writeLine(_node.isDoWhile() ? "DoWhileStatement" : "WhileStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ForStatement const& _node)
{
writeLine("ForStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Continue const& _node)
{
writeLine("Continue");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Break const& _node)
{
writeLine("Break");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Return const& _node)
{
writeLine("Return");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Throw const& _node)
{
writeLine("Throw");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(EmitStatement const& _node)
{
writeLine("EmitStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(VariableDeclarationStatement const& _node)
{
writeLine("VariableDeclarationStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ExpressionStatement const& _node)
{
writeLine("ExpressionStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Conditional const& _node)
{
writeLine("Conditional");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Assignment const& _node)
{
writeLine(string("Assignment using operator ") + TokenTraits::toString(_node.assignmentOperator()));
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(TupleExpression const& _node)
{
writeLine(string("TupleExpression"));
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(UnaryOperation const& _node)
{
writeLine(
string("UnaryOperation (") +
(_node.isPrefixOperation() ? "prefix" : "postfix") +
") " +
TokenTraits::toString(_node.getOperator())
);
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(BinaryOperation const& _node)
{
writeLine(string("BinaryOperation using operator ") + TokenTraits::toString(_node.getOperator()));
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(FunctionCall const& _node)
{
writeLine("FunctionCall");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(NewExpression const& _node)
{
writeLine("NewExpression");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(MemberAccess const& _node)
{
writeLine("MemberAccess to member " + _node.memberName());
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(IndexAccess const& _node)
{
writeLine("IndexAccess");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Identifier const& _node)
{
writeLine(string("Identifier ") + _node.name());
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(ElementaryTypeNameExpression const& _node)
{
writeLine(string("ElementaryTypeNameExpression ") + _node.typeName().toString());
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Literal const& _node)
{
char const* tokenString = TokenTraits::toString(_node.token());
if (!tokenString)
tokenString = "[no token]";
writeLine(string("Literal, token: ") + tokenString + " value: " + _node.value());
printType(_node);
printSourcePart(_node);
return goDeeper();
}
void ASTPrinter::endVisit(PragmaDirective const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ImportDirective const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ContractDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(InheritanceSpecifier const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(UsingForDirective const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(StructDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(EnumDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(EnumValue const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ParameterList const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(FunctionDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(VariableDeclaration const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ModifierDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ModifierInvocation const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(EventDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ElementaryTypeName const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(UserDefinedTypeName const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(FunctionTypeName const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Mapping const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ArrayTypeName const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(InlineAssembly const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Block const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(PlaceholderStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(IfStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(WhileStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ForStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Continue const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Break const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Return const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Throw const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(EmitStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(VariableDeclarationStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ExpressionStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Conditional const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Assignment const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(TupleExpression const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(UnaryOperation const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(BinaryOperation const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(FunctionCall const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(NewExpression const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(MemberAccess const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(IndexAccess const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Identifier const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ElementaryTypeNameExpression const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Literal const&)
{
m_indentation--;
}
void ASTPrinter::printSourcePart(ASTNode const& _node)
{
if (m_gasCosts.count(&_node))
*m_ostream << indentation() << " Gas costs: " << m_gasCosts.at(&_node) << endl;
if (!m_source.empty())
{
SourceLocation const& location(_node.location());
*m_ostream <<
indentation() <<
" Source: " <<
Json::valueToQuotedString(m_source.substr(location.start, location.end - location.start).c_str()) <<
endl;
}
}
void ASTPrinter::printType(Expression const& _expression)
{
if (_expression.annotation().type)
*m_ostream << indentation() << " Type: " << _expression.annotation().type->toString() << "\n";
else
*m_ostream << indentation() << " Type unknown.\n";
}
string ASTPrinter::indentation() const
{
return string(m_indentation * 2, ' ');
}
void ASTPrinter::writeLine(string const& _line)
{
*m_ostream << indentation() << _line << endl;
}
}
}

View File

@ -1,155 +0,0 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging.
*/
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/interface/GasEstimator.h>
#include <ostream>
namespace dev
{
namespace solidity
{
/**
* Pretty-printer for the abstract syntax tree (the "pretty" is arguable) for debugging purposes.
*/
class ASTPrinter: public ASTConstVisitor
{
public:
/// Create a printer for the given abstract syntax tree. If the source is specified,
/// the corresponding parts of the source are printed with each node.
ASTPrinter(
ASTNode const& _ast,
std::string const& _source = std::string(),
GasEstimator::ASTGasConsumption const& _gasCosts = GasEstimator::ASTGasConsumption()
);
/// Output the string representation of the AST to _stream.
void print(std::ostream& _stream);
bool visit(PragmaDirective const& _node) override;
bool visit(ImportDirective const& _node) override;
bool visit(ContractDefinition const& _node) override;
bool visit(InheritanceSpecifier const& _node) override;
bool visit(UsingForDirective const& _node) override;
bool visit(StructDefinition const& _node) override;
bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override;
bool visit(ParameterList const& _node) override;
bool visit(FunctionDefinition const& _node) override;
bool visit(VariableDeclaration const& _node) override;
bool visit(ModifierDefinition const& _node) override;
bool visit(ModifierInvocation const& _node) override;
bool visit(EventDefinition const& _node) override;
bool visit(ElementaryTypeName const& _node) override;
bool visit(UserDefinedTypeName const& _node) override;
bool visit(FunctionTypeName const& _node) override;
bool visit(Mapping const& _node) override;
bool visit(ArrayTypeName const& _node) override;
bool visit(InlineAssembly const& _node) override;
bool visit(Block const& _node) override;
bool visit(PlaceholderStatement const& _node) override;
bool visit(IfStatement const& _node) override;
bool visit(WhileStatement const& _node) override;
bool visit(ForStatement const& _node) override;
bool visit(Continue const& _node) override;
bool visit(Break const& _node) override;
bool visit(Return const& _node) override;
bool visit(Throw const& _node) override;
bool visit(EmitStatement const& _node) override;
bool visit(VariableDeclarationStatement const& _node) override;
bool visit(ExpressionStatement const& _node) override;
bool visit(Conditional const& _node) override;
bool visit(Assignment const& _node) override;
bool visit(TupleExpression const& _node) override;
bool visit(UnaryOperation const& _node) override;
bool visit(BinaryOperation const& _node) override;
bool visit(FunctionCall const& _node) override;
bool visit(NewExpression const& _node) override;
bool visit(MemberAccess const& _node) override;
bool visit(IndexAccess const& _node) override;
bool visit(Identifier const& _node) override;
bool visit(ElementaryTypeNameExpression const& _node) override;
bool visit(Literal const& _node) override;
void endVisit(PragmaDirective const&) override;
void endVisit(ImportDirective const&) override;
void endVisit(ContractDefinition const&) override;
void endVisit(InheritanceSpecifier const&) override;
void endVisit(UsingForDirective const&) override;
void endVisit(StructDefinition const&) override;
void endVisit(EnumDefinition const&) override;
void endVisit(EnumValue const&) override;
void endVisit(ParameterList const&) override;
void endVisit(FunctionDefinition const&) override;
void endVisit(VariableDeclaration const&) override;
void endVisit(ModifierDefinition const&) override;
void endVisit(ModifierInvocation const&) override;
void endVisit(EventDefinition const&) override;
void endVisit(ElementaryTypeName const&) override;
void endVisit(UserDefinedTypeName const&) override;
void endVisit(FunctionTypeName const&) override;
void endVisit(Mapping const&) override;
void endVisit(ArrayTypeName const&) override;
void endVisit(InlineAssembly const&) override;
void endVisit(Block const&) override;
void endVisit(PlaceholderStatement const&) override;
void endVisit(IfStatement const&) override;
void endVisit(WhileStatement const&) override;
void endVisit(ForStatement const&) override;
void endVisit(Continue const&) override;
void endVisit(Break const&) override;
void endVisit(Return const&) override;
void endVisit(Throw const&) override;
void endVisit(EmitStatement const&) override;
void endVisit(VariableDeclarationStatement const&) override;
void endVisit(ExpressionStatement const&) override;
void endVisit(Conditional const&) override;
void endVisit(Assignment const&) override;
void endVisit(TupleExpression const&) override;
void endVisit(UnaryOperation const&) override;
void endVisit(BinaryOperation const&) override;
void endVisit(FunctionCall const&) override;
void endVisit(NewExpression const&) override;
void endVisit(MemberAccess const&) override;
void endVisit(IndexAccess const&) override;
void endVisit(Identifier const&) override;
void endVisit(ElementaryTypeNameExpression const&) override;
void endVisit(Literal const&) override;
private:
void printSourcePart(ASTNode const& _node);
void printType(Expression const& _expression);
std::string indentation() const;
void writeLine(std::string const& _line);
bool goDeeper() { m_indentation++; return true; }
int m_indentation;
std::string m_source;
ASTNode const* m_ast;
GasEstimator::ASTGasConsumption m_gasCosts;
std::ostream* m_ostream = nullptr;
};
}
}

View File

@ -54,6 +54,7 @@ public:
virtual bool visit(EnumDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumValue& _node) { return visitNode(_node); }
virtual bool visit(ParameterList& _node) { return visitNode(_node); }
virtual bool visit(OverrideSpecifier& _node) { return visitNode(_node); }
virtual bool visit(FunctionDefinition& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclaration& _node) { return visitNode(_node); }
virtual bool visit(ModifierDefinition& _node) { return visitNode(_node); }
@ -68,6 +69,8 @@ public:
virtual bool visit(Block& _node) { return visitNode(_node); }
virtual bool visit(PlaceholderStatement& _node) { return visitNode(_node); }
virtual bool visit(IfStatement& _node) { return visitNode(_node); }
virtual bool visit(TryCatchClause& _node) { return visitNode(_node); }
virtual bool visit(TryStatement& _node) { return visitNode(_node); }
virtual bool visit(WhileStatement& _node) { return visitNode(_node); }
virtual bool visit(ForStatement& _node) { return visitNode(_node); }
virtual bool visit(Continue& _node) { return visitNode(_node); }
@ -86,6 +89,7 @@ public:
virtual bool visit(NewExpression& _node) { return visitNode(_node); }
virtual bool visit(MemberAccess& _node) { return visitNode(_node); }
virtual bool visit(IndexAccess& _node) { return visitNode(_node); }
virtual bool visit(IndexRangeAccess& _node) { return visitNode(_node); }
virtual bool visit(Identifier& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); }
virtual bool visit(Literal& _node) { return visitNode(_node); }
@ -100,6 +104,7 @@ public:
virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue& _node) { endVisitNode(_node); }
virtual void endVisit(ParameterList& _node) { endVisitNode(_node); }
virtual void endVisit(OverrideSpecifier& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclaration& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierDefinition& _node) { endVisitNode(_node); }
@ -114,6 +119,8 @@ public:
virtual void endVisit(Block& _node) { endVisitNode(_node); }
virtual void endVisit(PlaceholderStatement& _node) { endVisitNode(_node); }
virtual void endVisit(IfStatement& _node) { endVisitNode(_node); }
virtual void endVisit(TryCatchClause& _node) { endVisitNode(_node); }
virtual void endVisit(TryStatement& _node) { endVisitNode(_node); }
virtual void endVisit(WhileStatement& _node) { endVisitNode(_node); }
virtual void endVisit(ForStatement& _node) { endVisitNode(_node); }
virtual void endVisit(Continue& _node) { endVisitNode(_node); }
@ -132,6 +139,7 @@ public:
virtual void endVisit(NewExpression& _node) { endVisitNode(_node); }
virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); }
virtual void endVisit(IndexAccess& _node) { endVisitNode(_node); }
virtual void endVisit(IndexRangeAccess& _node) { endVisitNode(_node); }
virtual void endVisit(Identifier& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); }
virtual void endVisit(Literal& _node) { endVisitNode(_node); }
@ -159,6 +167,7 @@ public:
virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumValue const& _node) { return visitNode(_node); }
virtual bool visit(ParameterList const& _node) { return visitNode(_node); }
virtual bool visit(OverrideSpecifier const& _node) { return visitNode(_node); }
virtual bool visit(FunctionDefinition const& _node) { return visitNode(_node); }
virtual bool visit(VariableDeclaration const& _node) { return visitNode(_node); }
virtual bool visit(ModifierDefinition const& _node) { return visitNode(_node); }
@ -172,6 +181,8 @@ public:
virtual bool visit(Block const& _node) { return visitNode(_node); }
virtual bool visit(PlaceholderStatement const& _node) { return visitNode(_node); }
virtual bool visit(IfStatement const& _node) { return visitNode(_node); }
virtual bool visit(TryCatchClause const& _node) { return visitNode(_node); }
virtual bool visit(TryStatement const& _node) { return visitNode(_node); }
virtual bool visit(WhileStatement const& _node) { return visitNode(_node); }
virtual bool visit(ForStatement const& _node) { return visitNode(_node); }
virtual bool visit(Continue const& _node) { return visitNode(_node); }
@ -191,6 +202,7 @@ public:
virtual bool visit(NewExpression const& _node) { return visitNode(_node); }
virtual bool visit(MemberAccess const& _node) { return visitNode(_node); }
virtual bool visit(IndexAccess const& _node) { return visitNode(_node); }
virtual bool visit(IndexRangeAccess const& _node) { return visitNode(_node); }
virtual bool visit(Identifier const& _node) { return visitNode(_node); }
virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); }
virtual bool visit(Literal const& _node) { return visitNode(_node); }
@ -205,6 +217,7 @@ public:
virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); }
virtual void endVisit(ParameterList const& _node) { endVisitNode(_node); }
virtual void endVisit(OverrideSpecifier const& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(VariableDeclaration const& _node) { endVisitNode(_node); }
virtual void endVisit(ModifierDefinition const& _node) { endVisitNode(_node); }
@ -218,6 +231,8 @@ public:
virtual void endVisit(Block const& _node) { endVisitNode(_node); }
virtual void endVisit(PlaceholderStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(IfStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(TryCatchClause const& _node) { endVisitNode(_node); }
virtual void endVisit(TryStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(WhileStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(ForStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(Continue const& _node) { endVisitNode(_node); }
@ -237,6 +252,7 @@ public:
virtual void endVisit(NewExpression const& _node) { endVisitNode(_node); }
virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); }
virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); }
virtual void endVisit(IndexRangeAccess const& _node) { endVisitNode(_node); }
virtual void endVisit(Identifier const& _node) { endVisitNode(_node); }
virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); }
virtual void endVisit(Literal const& _node) { endVisitNode(_node); }

View File

@ -187,10 +187,26 @@ void ParameterList::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void OverrideSpecifier::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
listAccept(m_overrides, _visitor);
_visitor.endVisit(*this);
}
void OverrideSpecifier::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
listAccept(m_overrides, _visitor);
_visitor.endVisit(*this);
}
void FunctionDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_overrides)
m_overrides->accept(_visitor);
m_parameters->accept(_visitor);
if (m_returnParameters)
m_returnParameters->accept(_visitor);
@ -205,6 +221,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_overrides)
m_overrides->accept(_visitor);
m_parameters->accept(_visitor);
if (m_returnParameters)
m_returnParameters->accept(_visitor);
@ -221,6 +239,8 @@ void VariableDeclaration::accept(ASTVisitor& _visitor)
{
if (m_typeName)
m_typeName->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
if (m_value)
m_value->accept(_visitor);
}
@ -233,6 +253,8 @@ void VariableDeclaration::accept(ASTConstVisitor& _visitor) const
{
if (m_typeName)
m_typeName->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
if (m_value)
m_value->accept(_visitor);
}
@ -244,6 +266,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
if (_visitor.visit(*this))
{
m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
@ -254,6 +278,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
if (_visitor.visit(*this))
{
m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
@ -443,6 +469,48 @@ void IfStatement::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void TryCatchClause::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_parameters)
m_parameters->accept(_visitor);
m_block->accept(_visitor);
}
_visitor.endVisit(*this);
}
void TryCatchClause::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_parameters)
m_parameters->accept(_visitor);
m_block->accept(_visitor);
}
_visitor.endVisit(*this);
}
void TryStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
m_externalCall->accept(_visitor);
listAccept(m_clauses, _visitor);
}
_visitor.endVisit(*this);
}
void TryStatement::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
m_externalCall->accept(_visitor);
listAccept(m_clauses, _visitor);
}
_visitor.endVisit(*this);
}
void WhileStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
@ -765,6 +833,32 @@ void IndexAccess::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void IndexRangeAccess::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
m_base->accept(_visitor);
if (m_start)
m_start->accept(_visitor);
if (m_end)
m_end->accept(_visitor);
}
_visitor.endVisit(*this);
}
void IndexRangeAccess::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
m_base->accept(_visitor);
if (m_start)
m_start->accept(_visitor);
if (m_end)
m_end->accept(_visitor);
}
_visitor.endVisit(*this);
}
void Identifier::accept(ASTVisitor& _visitor)
{
_visitor.visit(*this);

View File

@ -21,6 +21,7 @@
#pragma once
#include <map>
#include <set>
namespace dev
{
@ -35,10 +36,11 @@ enum class ExperimentalFeature
TestOnlyAnalysis
};
static std::map<ExperimentalFeature, bool> const ExperimentalFeatureOnlyAnalysis =
static std::set<ExperimentalFeature> const ExperimentalFeatureWithoutWarning =
{
{ ExperimentalFeature::SMTChecker, true },
{ ExperimentalFeature::TestOnlyAnalysis, true },
ExperimentalFeature::ABIEncoderV2,
ExperimentalFeature::SMTChecker,
ExperimentalFeature::TestOnlyAnalysis,
};
static std::map<std::string, ExperimentalFeature> const ExperimentalFeatureNames =

View File

@ -31,6 +31,7 @@ InaccessibleDynamicType const TypeProvider::m_inaccessibleDynamic{};
/// they rely on `byte` being available which we cannot guarantee in the static init context.
unique_ptr<ArrayType> TypeProvider::m_bytesStorage;
unique_ptr<ArrayType> TypeProvider::m_bytesMemory;
unique_ptr<ArrayType> TypeProvider::m_bytesCalldata;
unique_ptr<ArrayType> TypeProvider::m_stringStorage;
unique_ptr<ArrayType> TypeProvider::m_stringMemory;
@ -177,6 +178,7 @@ void TypeProvider::reset()
clearCache(m_inaccessibleDynamic);
clearCache(m_bytesStorage);
clearCache(m_bytesMemory);
clearCache(m_bytesCalldata);
clearCache(m_stringStorage);
clearCache(m_stringMemory);
clearCache(m_emptyTuple);
@ -200,7 +202,7 @@ inline T const* TypeProvider::createAndGet(Args&& ... _args)
return static_cast<T const*>(instance().m_generalTypes.back().get());
}
Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& _type, std::optional<StateMutability> _stateMutability)
{
solAssert(
TokenTraits::isElementaryTypeName(_type.token()),
@ -233,7 +235,14 @@ Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const&
case Token::UFixed:
return fixedPoint(128, 18, FixedPointType::Modifier::Unsigned);
case Token::Address:
{
if (_stateMutability)
{
solAssert(*_stateMutability == StateMutability::Payable, "");
return payableAddress();
}
return address();
}
case Token::Bool:
return boolean();
case Token::Bytes:
@ -307,6 +316,13 @@ ArrayType const* TypeProvider::bytesMemory()
return m_bytesMemory.get();
}
ArrayType const* TypeProvider::bytesCalldata()
{
if (!m_bytesCalldata)
m_bytesCalldata = make_unique<ArrayType>(DataLocation::CallData, false);
return m_bytesCalldata.get();
}
ArrayType const* TypeProvider::stringStorage()
{
if (!m_stringStorage)
@ -494,6 +510,11 @@ ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseTy
return createAndGet<ArrayType>(_location, _baseType, _length);
}
ArraySliceType const* TypeProvider::arraySlice(ArrayType const& _arrayType)
{
return createAndGet<ArraySliceType>(_arrayType);
}
ContractType const* TypeProvider::contract(ContractDefinition const& _contractDef, bool _isSuper)
{
return createAndGet<ContractType>(_contractDef, _isSuper);

View File

@ -55,7 +55,7 @@ public:
/// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type.
static Type const* fromElementaryTypeName(ElementaryTypeNameToken const& _type);
static Type const* fromElementaryTypeName(ElementaryTypeNameToken const& _type, std::optional<StateMutability> _stateMutability = {});
/// Converts a given elementary type name with optional data location
/// suffix " storage", " calldata" or " memory" to a type pointer. If suffix not given, defaults to " storage".
@ -69,6 +69,7 @@ public:
static ArrayType const* bytesStorage();
static ArrayType const* bytesMemory();
static ArrayType const* bytesCalldata();
static ArrayType const* stringStorage();
static ArrayType const* stringMemory();
@ -81,6 +82,8 @@ public:
/// Constructor for a fixed-size array type ("type[20]")
static ArrayType const* array(DataLocation _location, Type const* _baseType, u256 const& _length);
static ArraySliceType const* arraySlice(ArrayType const& _arrayType);
static AddressType const* payableAddress() noexcept { return &m_payableAddress; }
static AddressType const* address() noexcept { return &m_address; }
@ -204,6 +207,7 @@ private:
/// These are lazy-initialized because they depend on `byte` being available.
static std::unique_ptr<ArrayType> m_bytesStorage;
static std::unique_ptr<ArrayType> m_bytesMemory;
static std::unique_ptr<ArrayType> m_bytesCalldata;
static std::unique_ptr<ArrayType> m_stringStorage;
static std::unique_ptr<ArrayType> m_stringMemory;

View File

@ -52,23 +52,6 @@ using namespace dev::solidity;
namespace
{
unsigned int mostSignificantBit(bigint const& _number)
{
#if BOOST_VERSION < 105500
solAssert(_number > 0, "");
bigint number = _number;
unsigned int result = 0;
while (number != 0)
{
number >>= 1;
++result;
}
return --result;
#else
return boost::multiprecision::msb(_number);
#endif
}
/// Check whether (_base ** _exp) fits into 4096 bits.
bool fitsPrecisionExp(bigint const& _base, bigint const& _exp)
{
@ -79,7 +62,7 @@ bool fitsPrecisionExp(bigint const& _base, bigint const& _exp)
size_t const bitsMax = 4096;
unsigned mostSignificantBaseBit = mostSignificantBit(_base);
unsigned mostSignificantBaseBit = boost::multiprecision::msb(_base);
if (mostSignificantBaseBit == 0) // _base == 1
return true;
if (mostSignificantBaseBit > bitsMax) // _base >= 2 ^ 4096
@ -105,7 +88,7 @@ bool fitsPrecisionBaseX(
size_t const bitsMax = 4096;
unsigned mostSignificantMantissaBit = mostSignificantBit(_mantissa);
unsigned mostSignificantMantissaBit = boost::multiprecision::msb(_mantissa);
if (mostSignificantMantissaBit > bitsMax) // _mantissa >= 2 ^ 4096
return false;
@ -412,7 +395,9 @@ BoolResult AddressType::isImplicitlyConvertibleTo(Type const& _other) const
BoolResult AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
if (auto const* contractType = dynamic_cast<ContractType const*>(&_convertTo))
if (_convertTo.category() == category())
return true;
else if (auto const* contractType = dynamic_cast<ContractType const*>(&_convertTo))
return (m_stateMutability >= StateMutability::Payable) || !contractType->isPayable();
return isImplicitlyConvertibleTo(_convertTo) ||
_convertTo.category() == Category::Integer ||
@ -600,6 +585,17 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other
else
return nullptr;
}
else if (Token::Exp == _operator)
{
if (auto otherIntType = dynamic_cast<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 nullptr;
return this;
}
auto commonType = Type::commonType(this, _other); //might be an integer or fixed point
if (!commonType)
@ -610,14 +606,6 @@ TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other
return commonType;
if (TokenTraits::isBooleanOp(_operator))
return nullptr;
if (auto intType = dynamic_cast<IntegerType const*>(commonType))
{
if (Token::Exp == _operator && intType->isSigned())
return TypeResult::err("Exponentiation is not allowed for signed integer types.");
}
else if (dynamic_cast<FixedPointType const*>(commonType))
if (Token::Exp == _operator)
return nullptr;
return commonType;
}
@ -1095,7 +1083,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const*
else
{
uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
if (exponent > mostSignificantBit(boost::multiprecision::abs(m_value.numerator())))
if (exponent > boost::multiprecision::msb(boost::multiprecision::abs(m_value.numerator())))
value = m_value.numerator() < 0 ? -1 : 0;
else
{
@ -1119,7 +1107,7 @@ TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const*
}
// verify that numerator and denominator fit into 4096 bit after every operation
if (value.numerator() != 0 && max(mostSignificantBit(abs(value.numerator())), mostSignificantBit(abs(value.denominator()))) > 4096)
if (value.numerator() != 0 && max(boost::multiprecision::msb(abs(value.numerator())), boost::multiprecision::msb(abs(value.denominator()))) > 4096)
return TypeResult::err("Precision of rational constants is limited to 4096 bits.");
return TypeResult{TypeProvider::rationalNumber(value)};
@ -1464,8 +1452,9 @@ BoolResult ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const
bool ContractType::isPayable() const
{
auto receiveFunction = m_contract.receiveFunction();
auto fallbackFunction = m_contract.fallbackFunction();
return fallbackFunction && fallbackFunction->isPayable();
return receiveFunction || (fallbackFunction && fallbackFunction->isPayable());
}
TypeResult ContractType::unaryOperatorResult(Token _operator) const
@ -1783,10 +1772,17 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
if (isDynamicallySized() && location() == DataLocation::Storage)
{
members.emplace_back("push", TypeProvider::function(
TypePointers{},
TypePointers{baseType()},
TypePointers{TypeProvider::uint256()},
strings{},
strings{string()},
isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush
));
members.emplace_back("push", TypeProvider::function(
TypePointers{baseType()},
TypePointers{},
strings{string()},
strings{},
isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush
));
members.emplace_back("pop", TypeProvider::function(
@ -1871,6 +1867,30 @@ std::unique_ptr<ReferenceType> ArrayType::copyForLocation(DataLocation _location
return copy;
}
BoolResult ArraySliceType::isImplicitlyConvertibleTo(Type const& _other) const
{
if (m_arrayType.location() == DataLocation::CallData && m_arrayType.isDynamicallySized() && m_arrayType == _other)
return true;
return (*this) == _other;
}
string ArraySliceType::richIdentifier() const
{
return m_arrayType.richIdentifier() + "_slice";
}
bool ArraySliceType::operator==(Type const& _other) const
{
if (auto const* other = dynamic_cast<ArraySliceType const*>(&_other))
return m_arrayType == other->m_arrayType;
return false;
}
string ArraySliceType::toString(bool _short) const
{
return m_arrayType.toString(_short) + " slice";
}
string ContractType::richIdentifier() const
{
return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id());
@ -2564,7 +2584,7 @@ FunctionType::FunctionType(EventDefinition const& _event):
FunctionType::FunctionType(FunctionTypeName const& _typeName):
m_parameterNames(_typeName.parameterTypes().size(), ""),
m_returnParameterNames(_typeName.returnParameterTypes().size(), ""),
m_kind(_typeName.visibility() == VariableDeclaration::Visibility::External ? Kind::External : Kind::Internal),
m_kind(_typeName.visibility() == Visibility::External ? Kind::External : Kind::Internal),
m_stateMutability(_typeName.stateMutability())
{
if (_typeName.isPayable())
@ -2735,8 +2755,6 @@ bool FunctionType::operator==(Type const& _other) const
BoolResult FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
if (m_kind == Kind::External && _convertTo == *TypeProvider::address())
return true;
return _convertTo.category() == category();
}
@ -2931,7 +2949,10 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
{
MemberList::MemberMap members;
if (m_kind == Kind::External)
{
members.emplace_back("selector", TypeProvider::fixedBytes(4));
members.emplace_back("address", TypeProvider::address());
}
if (m_kind != Kind::BareDelegateCall)
{
if (isPayable())
@ -2973,8 +2994,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
{
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(m_declaration);
solAssert(functionDefinition, "");
solAssert(functionDefinition->visibility() != Declaration::Visibility::Private, "");
if (functionDefinition->visibility() != Declaration::Visibility::Internal)
solAssert(functionDefinition->visibility() != Visibility::Private, "");
if (functionDefinition->visibility() != Visibility::Internal)
{
auto const* contract = dynamic_cast<ContractDefinition const*>(m_declaration->scope());
solAssert(contract, "");
@ -3153,6 +3174,11 @@ u256 FunctionType::externalIdentifier() const
return FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(externalSignature())));
}
string FunctionType::externalIdentifierHex() const
{
return FixedHash<4>(dev::keccak256(externalSignature())).hex();
}
bool FunctionType::isPure() const
{
// TODO: replace this with m_stateMutability == StateMutability::Pure once

View File

@ -161,7 +161,7 @@ public:
enum class Category
{
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array,
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice,
FixedBytes, Contract, Struct, Function, Enum, Tuple,
Mapping, TypeType, Modifier, Magic, Module,
InaccessibleDynamic
@ -773,6 +773,35 @@ private:
mutable std::optional<TypeResult> m_interfaceType_library;
};
class ArraySliceType: public ReferenceType
{
public:
explicit ArraySliceType(ArrayType const& _arrayType): ReferenceType(_arrayType.location()), m_arrayType(_arrayType) {}
Category category() const override { return Category::ArraySlice; }
BoolResult isImplicitlyConvertibleTo(Type const& _other) const override;
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
unsigned calldataEncodedSize(bool) const override { solAssert(false, ""); }
unsigned calldataEncodedTailSize() const override { return 32; }
bool isDynamicallySized() const override { return true; }
bool isDynamicallyEncoded() const override { return true; }
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
unsigned sizeOnStack() const override { return 2; }
std::string toString(bool _short) const override;
/// @returns true if this is valid to be stored in calldata
bool validForCalldata() const { return m_arrayType.validForCalldata(); }
ArrayType const& arrayType() const { return m_arrayType; }
u256 memoryDataSize() const override { solAssert(false, ""); }
std::unique_ptr<ReferenceType> copyForLocation(DataLocation, bool) const override { solAssert(false, ""); }
private:
ArrayType const& m_arrayType;
};
/**
* The type of a contract instance or library, there is one distinct type for each contract definition.
*/
@ -817,7 +846,8 @@ public:
/// See documentation of m_super
bool isSuper() const { return m_super; }
// @returns true if and only if the contract has a payable fallback function
// @returns true if and only if the contract has a receive ether function or a payable fallback function, i.e.
// if it has code that will be executed on plain ether transfers
bool isPayable() const;
ContractDefinition const& contractDefinition() const { return m_contract; }
@ -1160,6 +1190,8 @@ public:
std::string externalSignature() const;
/// @returns the external identifier of this function (the hash of the signature).
u256 externalIdentifier() const;
/// @returns the external identifier of this function (the hash of the signature) as a hex string.
std::string externalIdentifierHex() const;
Declaration const& declaration() const
{
solAssert(m_declaration, "Requested declaration from a FunctionType that has none");

View File

@ -35,7 +35,7 @@ void Compiler::compileContract(
bytes const& _metadata
)
{
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings);
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings, m_revertStrings);
runtimeCompiler.compileContract(_contract, _otherCompilers);
m_runtimeContext.appendAuxiliaryData(_metadata);
@ -45,7 +45,7 @@ void Compiler::compileContract(
// The creation code will be executed at most once, so we modify the optimizer
// settings accordingly.
creationSettings.expectedExecutionsPerDeployment = 1;
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings);
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings, m_revertStrings);
m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
m_context.optimise(m_optimiserSettings);

View File

@ -24,6 +24,7 @@
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/DebugSettings.h>
#include <liblangutil/EVMVersion.h>
#include <libevmasm/Assembly.h>
#include <functional>
@ -35,8 +36,9 @@ namespace solidity {
class Compiler
{
public:
explicit Compiler(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings):
Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings):
m_optimiserSettings(std::move(_optimiserSettings)),
m_revertStrings(_revertStrings),
m_runtimeContext(_evmVersion),
m_context(_evmVersion, &m_runtimeContext)
{ }
@ -79,6 +81,7 @@ public:
private:
OptimiserSettings const m_optimiserSettings;
RevertStrings const m_revertStrings;
CompilerContext m_runtimeContext;
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
CompilerContext m_context;

View File

@ -411,7 +411,6 @@ void CompilerContext::appendInlineAssembly(
analyzerResult = yul::AsmAnalyzer(
analysisInfo,
errorReporter,
std::nullopt,
dialect,
identifierAccess.resolve
).analyze(*parserResult);

View File

@ -262,6 +262,8 @@ public:
ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); }
};
void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; }
private:
/// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns
/// the first function definition that is overwritten by _function.

View File

@ -95,6 +95,26 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
m_context << Instruction::REVERT;
}
void CompilerUtils::returnDataToArray()
{
if (m_context.evmVersion().supportsReturndata())
{
m_context << Instruction::RETURNDATASIZE;
m_context.appendInlineAssembly(R"({
switch v case 0 {
v := 0x60
} default {
v := mload(0x40)
mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f))))
mstore(v, returndatasize())
returndatacopy(add(v, 0x20), 0, returndatasize())
}
})", {"v"});
}
else
pushZeroPointer();
}
void CompilerUtils::accessCalldataTail(Type const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
@ -984,6 +1004,17 @@ void CompilerUtils::convertType(
}
break;
}
case Type::Category::ArraySlice:
{
auto& typeOnStack = dynamic_cast<ArraySliceType const&>(_typeOnStack);
solAssert(_targetType == typeOnStack.arrayType(), "");
solUnimplementedAssert(
typeOnStack.arrayType().location() == DataLocation::CallData &&
typeOnStack.arrayType().isDynamicallySized(),
""
);
break;
}
case Type::Category::Struct:
{
solAssert(targetTypeCategory == stackTypeCategory, "");
@ -1115,16 +1146,8 @@ void CompilerUtils::convertType(
m_context << Instruction::ISZERO << Instruction::ISZERO;
break;
default:
if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address)
{
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
solAssert(typeOnStack.kind() == FunctionType::Kind::External, "Only external function type can be converted.");
// stack: <address> <function_id>
m_context << Instruction::POP;
}
else
{
// we used to allow conversions from function to address
solAssert(!(stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address), "");
if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Function)
{
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
@ -1145,7 +1168,6 @@ void CompilerUtils::convertType(
m_context
<< ((u256(1) << (8 * _targetType.storageBytes())) - 1)
<< Instruction::AND;
}
break;
}

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