Merge remote-tracking branch 'origin/develop' into HEAD

This commit is contained in:
chriseth 2020-04-28 13:02:06 +02:00
commit 1fe55370f4
195 changed files with 3903 additions and 1336 deletions

View File

@ -284,12 +284,18 @@ jobs:
chk_coding_style:
docker:
- image: buildpack-deps:disco
- image: buildpack-deps:focal
steps:
- checkout
- run:
name: Install shellcheck
command: apt -q update && apt install -y shellcheck
- run:
name: Check for C++ coding style
command: ./scripts/check_style.sh
- run:
name: checking shell scripts
command: ./scripts/chk_shellscripts/chk_shellscripts.sh
chk_pylint:
docker:

View File

@ -16,15 +16,21 @@ Bugfixes:
### 0.6.7 (unreleased)
Language Features:
* Add support for EIP 165 interface identifiers with `type(I).interfaceId`.
* Allow virtual modifiers inside abstract contracts to have empty body.
Compiler Features:
* Optimizer: Simplify repeated AND and OR operations.
Bugfixes:
* SMTChecker: Fix internal error when fixed points are used.
* SMTChecker: Fix internal error when using array slices.
* Type Checker: Disallow ``virtual`` and ``override`` for constructors.
* Type Checker: Fix several internal errors by performing size and recursiveness checks of types before the full type checking.
* Type Checker: Fix internal error when assigning to empty tuples.
* Type Checker: Perform recursiveness check on structs declared at the file level.
* Standard Json Input: Fix error when using prefix ``file://`` in the field ``urls``.
Build System:
* soltest.sh: ``SOLIDITY_BUILD_DIR`` is no longer relative to ``REPO_ROOT`` to allow for build directories outside of the source tree.

View File

@ -68,7 +68,7 @@ structDefinition
'{' ( variableDeclaration ';' (variableDeclaration ';')* )? '}' ;
modifierDefinition
: 'modifier' identifier parameterList? ( VirtualKeyword | overrideSpecifier )* block ;
: 'modifier' identifier parameterList? ( VirtualKeyword | overrideSpecifier )* ( ';' | block ) ;
functionDefinition
: functionDescriptor parameterList modifierList returnParameters? ( ';' | block ) ;

View File

@ -507,13 +507,17 @@ A function description is a JSON object with the fields:
- ``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).
state <view-functions>`), ``nonpayable`` (function does not accept Ether - the default) and ``payable`` (function accepts Ether).
Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either.
.. note::
Sending non-zero Ether to non-payable function will revert the transaction.
.. note::
The state mutability ``nonpayable`` is reflected in Solidity by not specifying
a state mutability modifier at all.
An event description is a JSON object with fairly similar fields:
- ``type``: always ``"event"``

View File

@ -11,6 +11,7 @@
"name": "MemoryArrayCreationOverflow",
"summary": "The creation of very large memory arrays can result in overlapping memory regions and thus memory corruption.",
"description": "No runtime overflow checks were performed for the length of memory arrays during creation. In cases for which the memory size of an array in bytes, i.e. the array length times 32, is larger than 2^256-1, the memory allocation will overflow, potentially resulting in overlapping memory areas. The length of the array is still stored correctly, so copying or iterating over such an array will result in out-of-gas.",
"link": "https://solidity.ethereum.org/2020/04/06/memory-creation-overflow-bug/",
"introduced": "0.2.0",
"fixed": "0.6.5",
"severity": "low"
@ -73,6 +74,7 @@
"name": "SignedArrayStorageCopy",
"summary": "Assigning an array of signed integers to a storage array of different type can lead to data corruption in that array.",
"description": "In two's complement, negative integers have their higher order bits set. In order to fit into a shared storage slot, these have to be set to zero. When a conversion is done at the same time, the bits to set to zero were incorrectly determined from the source and not the target type. This means that such copy operations can lead to incorrect values being stored.",
"link": "https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs/",
"introduced": "0.4.7",
"fixed": "0.5.10",
"severity": "low/medium"
@ -81,6 +83,7 @@
"name": "ABIEncoderV2StorageArrayWithMultiSlotElement",
"summary": "Storage arrays containing structs or other statically-sized arrays are not read properly when directly encoded in external function calls or in abi.encode*.",
"description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in external function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable or if the storage array only contains value types or dynamically-sized arrays.",
"link": "https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs/",
"introduced": "0.4.16",
"fixed": "0.5.10",
"severity": "low",

191
docs/cheatsheet.rst Normal file
View File

@ -0,0 +1,191 @@
**********
Cheatsheet
**********
.. index:: precedence
.. _order:
Order of Precedence of Operators
================================
The following is the order of precedence for operators, listed in order of evaluation.
+------------+-------------------------------------+--------------------------------------------+
| Precedence | Description | Operator |
+============+=====================================+============================================+
| *1* | Postfix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
| | New expression | ``new <typename>`` |
+ +-------------------------------------+--------------------------------------------+
| | Array subscripting | ``<array>[<index>]`` |
+ +-------------------------------------+--------------------------------------------+
| | Member access | ``<object>.<member>`` |
+ +-------------------------------------+--------------------------------------------+
| | Function-like call | ``<func>(<args...>)`` |
+ +-------------------------------------+--------------------------------------------+
| | Parentheses | ``(<statement>)`` |
+------------+-------------------------------------+--------------------------------------------+
| *2* | Prefix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
| | Unary minus | ``-`` |
+ +-------------------------------------+--------------------------------------------+
| | Unary operations | ``delete`` |
+ +-------------------------------------+--------------------------------------------+
| | Logical NOT | ``!`` |
+ +-------------------------------------+--------------------------------------------+
| | Bitwise NOT | ``~`` |
+------------+-------------------------------------+--------------------------------------------+
| *3* | Exponentiation | ``**`` |
+------------+-------------------------------------+--------------------------------------------+
| *4* | Multiplication, division and modulo | ``*``, ``/``, ``%`` |
+------------+-------------------------------------+--------------------------------------------+
| *5* | Addition and subtraction | ``+``, ``-`` |
+------------+-------------------------------------+--------------------------------------------+
| *6* | Bitwise shift operators | ``<<``, ``>>`` |
+------------+-------------------------------------+--------------------------------------------+
| *7* | Bitwise AND | ``&`` |
+------------+-------------------------------------+--------------------------------------------+
| *8* | Bitwise XOR | ``^`` |
+------------+-------------------------------------+--------------------------------------------+
| *9* | Bitwise OR | ``|`` |
+------------+-------------------------------------+--------------------------------------------+
| *10* | Inequality operators | ``<``, ``>``, ``<=``, ``>=`` |
+------------+-------------------------------------+--------------------------------------------+
| *11* | Equality operators | ``==``, ``!=`` |
+------------+-------------------------------------+--------------------------------------------+
| *12* | Logical AND | ``&&`` |
+------------+-------------------------------------+--------------------------------------------+
| *13* | Logical OR | ``||`` |
+------------+-------------------------------------+--------------------------------------------+
| *14* | Ternary operator | ``<conditional> ? <if-true> : <if-false>`` |
+ +-------------------------------------+--------------------------------------------+
| | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, |
| | | ``>>=``, ``+=``, ``-=``, ``*=``, ``/=``, |
| | | ``%=`` |
+------------+-------------------------------------+--------------------------------------------+
| *15* | Comma operator | ``,`` |
+------------+-------------------------------------+--------------------------------------------+
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
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.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)), ...)```
- ``block.coinbase`` (``address payable``): current block miner's address
- ``block.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit
- ``block.number`` (``uint``): current block number
- ``block.timestamp`` (``uint``): current block timestamp
- ``gasleft() returns (uint256)``: remaining gas
- ``msg.data`` (``bytes``): complete calldata
- ``msg.sender`` (``address payable``): sender of the message (current call)
- ``msg.value`` (``uint``): number of wei sent with the message
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
- ``tx.gasprice`` (``uint``): gas price of the transaction
- ``tx.origin`` (``address payable``): sender of the transaction (full call chain)
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
- ``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.
- ``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>.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>`.
- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`.
- ``type(I).interfaceId`` (``bytes4``): value containing the EIP-165 interface identifier of the given interface, see :ref:`Type Information<meta-type>`.
.. note::
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
unless you know what you are doing.
Both the timestamp and the block hash can be influenced by miners to some degree.
Bad actors in the mining community can for example run a casino payout function on a chosen hash
and just retry a different hash if they did not receive any money.
The current block timestamp must be strictly larger than the timestamp of the last block,
but the only guarantee is that it will be somewhere between the timestamps of two
consecutive blocks in the canonical chain.
.. note::
The block hashes are not available for all blocks for scalability reasons.
You can only access the hashes of the most recent 256 blocks, all other
values will be zero.
.. note::
In version 0.5.0, the following aliases were removed: ``suicide`` as alias for ``selfdestruct``,
``msg.gas`` as alias for ``gasleft``, ``block.blockhash`` as alias for ``blockhash`` and
``sha3`` as alias for ``keccak256``.
.. index:: visibility, public, private, external, internal
Function Visibility Specifiers
==============================
::
function myFunction() <visibility specifier> returns (bool) {
return true;
}
- ``public``: visible externally and internally (creates a :ref:`getter function<getter-functions>` for storage/state variables)
- ``private``: only visible in the current contract
- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``)
- ``internal``: only visible internally
.. index:: modifiers, pure, view, payable, constant, anonymous, indexed
Modifiers
=========
- ``pure`` for functions: Disallows modification or access of state.
- ``view`` for functions: Disallows modification of state.
- ``payable`` for functions: Allows them to receive Ether together with a call.
- ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
- ``immutable`` for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code.
- ``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:
``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``,
``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``,
``unchecked``.

View File

@ -17,7 +17,7 @@ import sys
import os
import re
from pygments_lexer_solidity import SolidityLexer
from pygments_lexer_solidity import SolidityLexer, YulLexer
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@ -27,6 +27,7 @@ def setup(sphinx):
thisdir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, thisdir + '/utils')
sphinx.add_lexer('Solidity', SolidityLexer())
sphinx.add_lexer('Yul', YulLexer())
sphinx.add_stylesheet('css/custom.css')

View File

@ -356,6 +356,11 @@ by as many concatenations of its contents as there were sets of variables suppli
each time replacing any ``<inner>`` items by their respective value. Top-level variables can also be used
inside such areas.
There are also conditionals of the form ``<?name>...<!name>...</name>``, where template replacements
continue recursively either in the first or the second segment depending on the value of the boolean
parameter ``name``. If ``<?+name>...<!+name>...</+name>`` is used, then the check is whether
the string parameter ``name`` is non-empty.
.. _documentation-style:
Documentation Style Guide

6
docs/grammar.rst Normal file
View File

@ -0,0 +1,6 @@
****************
Language Grammar
****************
.. literalinclude:: Solidity.g4
:language: antlr

View File

@ -36,7 +36,7 @@ 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.
"Language Description" 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>`.
@ -88,17 +88,49 @@ Contents
.. toctree::
:maxdepth: 2
:caption: Basics
introduction-to-smart-contracts.rst
installing-solidity.rst
solidity-by-example.rst
solidity-in-depth.rst
.. toctree::
:maxdepth: 2
:caption: Language Description
layout-of-source-files.rst
structure-of-a-contract.rst
types.rst
units-and-global-variables.rst
control-structures.rst
contracts.rst
assembly.rst
cheatsheet.rst
grammar.rst
.. toctree::
:maxdepth: 2
:caption: Internals
internals/layout_in_storage.rst
internals/layout_in_memory.rst
internals/layout_in_calldata.rst
internals/variable_cleanup.rst
internals/source_mappings.rst
internals/optimiser.rst
metadata.rst
abi-spec.rst
.. toctree::
:maxdepth: 2
:caption: Additional Material
050-breaking-changes.rst
060-breaking-changes.rst
natspec-format.rst
security-considerations.rst
resources.rst
using-the-compiler.rst
metadata.rst
abi-spec.rst
yul.rst
style-guide.rst
common-patterns.rst

View File

@ -0,0 +1,13 @@
*******************
Layout of Call Data
*******************
The input data for a function call is assumed to be in the format defined by the :ref:`ABI
specification <ABI>`. Among others, the ABI specification requires arguments to be padded to multiples of 32
bytes. The internal function calls use a different convention.
Arguments for the constructor of a contract are directly appended at the end of the
contract's code, also in ABI encoding. The constructor will access them through a hard-coded offset, and
not by using the ``codesize`` opcode, since this of course changes when appending
data to the code.

View File

@ -0,0 +1,39 @@
.. index: memory layout
****************
Layout in Memory
****************
Solidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows:
- ``0x00`` - ``0x3f`` (64 bytes): scratch space for hashing methods
- ``0x40`` - ``0x5f`` (32 bytes): currently allocated memory size (aka. free memory pointer)
- ``0x60`` - ``0x7f`` (32 bytes): zero slot
Scratch space can be used between statements (i.e. within inline assembly). The zero slot
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).
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 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 unexpected results.
.. index: calldata layout

View File

@ -0,0 +1,359 @@
.. index:: storage, state variable, mapping
************************************
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:
- 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).
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
by the above rules, state variables from different contracts do share the same storage slot.
The elements of structs and arrays are stored after each other, just as if they were given explicitly.
.. warning::
When using elements that are smaller than 32 bytes, your contract's gas usage may be higher.
This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller
than that, the EVM must use more operations in order to reduce the size of the element from 32
bytes to the desired size.
It is only beneficial to use reduced-size arguments if you are dealing with storage values
because the compiler will pack multiple elements into one storage slot, and thus, combine
multiple reads or writes into a single operation. When dealing with function arguments or memory
values, there is no inherent benefit because the compiler does not pack these values.
Finally, in order to allow the EVM to optimize for this, ensure that you try to order your
storage variables and ``struct`` members such that they can be packed tightly. For example,
declaring your storage variables in the order of ``uint128, uint128, uint256`` instead of
``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the
latter will take up three.
.. note::
The layout of state variables in storage is considered to be part of the external interface
of Solidity due to the fact that storage pointers can be passed to libraries. This means that
any change to the rules outlined in this section is considered a breaking change
of the language and due to its critical nature should be considered very carefully before
being executed.
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.
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>`).
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
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;
contract C {
struct S { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => S)) data;
}
.. _bytes-and-string:
``bytes`` and ``string``
------------------------
``bytes`` and ``string`` are encoded identically. For short byte arrays, they store their data in the same
slot where the length is also stored. In particular: if the data is at most ``31`` bytes long, it is stored
in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``.
For byte arrays that store data which is ``32`` or more bytes long, the main slot stores ``length * 2 + 1`` and the data is
stored as usual in ``keccak256(slot)``. This means that you can distinguish a short array from a long array
by checking if the lowest bit is set: short (not set) and long (set).
.. note::
Handling invalidly encoded slots is currently not supported but may be added in the future.
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,
``storage`` and ``types``. The ``storage`` object is an array where each
element has the following form:
.. code::
{
"astId": 2,
"contract": "fileA:A",
"label": "x",
"offset": 0,
"slot": "0",
"type": "t_uint256"
}
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
- ``contract`` is the name of the contract including its path as prefix
- ``label`` is the name of the state variable
- ``offset`` is the offset in bytes within the storage slot according to the encoding
- ``slot`` is the storage slot where the state variable resides or starts. This
number may be very large and therefore its JSON value is represented as a
string.
- ``type`` is an identifier used as key to the variable's type information (described in the following)
The given ``type``, in this case ``t_uint256`` represents an element in
``types``, which has the form:
.. code::
{
"encoding": "inplace",
"label": "uint256",
"numberOfBytes": "32",
}
where
- ``encoding`` how the data is encoded in storage, where the possible values are:
- ``inplace``: data is laid out contiguously in storage (see :ref:`above <storage-inplace-encoding>`).
- ``mapping``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
- ``dynamic_array``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
- ``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.
Some types have extra information besides the four above. Mappings contain
its ``key`` and ``value`` types (again referencing an entry in this mapping
of types), arrays have its ``base`` type, and structs list their ``members`` in
the same format as the top-level ``storage`` (see :ref:`above
<storage-layout-top-level>`).
.. note ::
The JSON output format of a contract's storage layout is still considered experimental
and is subject to change in non-breaking releases of Solidity.
The following example shows a contract and its storage layout, containing
value and reference types, types that are encoded packed, and nested types.
.. code::
pragma solidity >=0.4.0 <0.7.0;
contract A {
struct S {
uint128 a;
uint128 b;
uint[2] staticArray;
uint[] dynArray;
}
uint x;
uint y;
S s;
address addr;
mapping (uint => mapping (address => bool)) map;
uint[] array;
string s1;
bytes b1;
}
.. code::
"storageLayout": {
"storage": [
{
"astId": 14,
"contract": "fileA:A",
"label": "x",
"offset": 0,
"slot": "0",
"type": "t_uint256"
},
{
"astId": 16,
"contract": "fileA:A",
"label": "y",
"offset": 0,
"slot": "1",
"type": "t_uint256"
},
{
"astId": 18,
"contract": "fileA:A",
"label": "s",
"offset": 0,
"slot": "2",
"type": "t_struct(S)12_storage"
},
{
"astId": 20,
"contract": "fileA:A",
"label": "addr",
"offset": 0,
"slot": "6",
"type": "t_address"
},
{
"astId": 26,
"contract": "fileA:A",
"label": "map",
"offset": 0,
"slot": "7",
"type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))"
},
{
"astId": 29,
"contract": "fileA:A",
"label": "array",
"offset": 0,
"slot": "8",
"type": "t_array(t_uint256)dyn_storage"
},
{
"astId": 31,
"contract": "fileA:A",
"label": "s1",
"offset": 0,
"slot": "9",
"type": "t_string_storage"
},
{
"astId": 33,
"contract": "fileA:A",
"label": "b1",
"offset": 0,
"slot": "10",
"type": "t_bytes_storage"
}
],
"types": {
"t_address": {
"encoding": "inplace",
"label": "address",
"numberOfBytes": "20"
},
"t_array(t_uint256)2_storage": {
"base": "t_uint256",
"encoding": "inplace",
"label": "uint256[2]",
"numberOfBytes": "64"
},
"t_array(t_uint256)dyn_storage": {
"base": "t_uint256",
"encoding": "dynamic_array",
"label": "uint256[]",
"numberOfBytes": "32"
},
"t_bool": {
"encoding": "inplace",
"label": "bool",
"numberOfBytes": "1"
},
"t_bytes_storage": {
"encoding": "bytes",
"label": "bytes",
"numberOfBytes": "32"
},
"t_mapping(t_address,t_bool)": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => bool)",
"numberOfBytes": "32",
"value": "t_bool"
},
"t_mapping(t_uint256,t_mapping(t_address,t_bool))": {
"encoding": "mapping",
"key": "t_uint256",
"label": "mapping(uint256 => mapping(address => bool))",
"numberOfBytes": "32",
"value": "t_mapping(t_address,t_bool)"
},
"t_string_storage": {
"encoding": "bytes",
"label": "string",
"numberOfBytes": "32"
},
"t_struct(S)12_storage": {
"encoding": "inplace",
"label": "struct A.S",
"members": [
{
"astId": 2,
"contract": "fileA:A",
"label": "a",
"offset": 0,
"slot": "0",
"type": "t_uint128"
},
{
"astId": 4,
"contract": "fileA:A",
"label": "b",
"offset": 16,
"slot": "0",
"type": "t_uint128"
},
{
"astId": 8,
"contract": "fileA:A",
"label": "staticArray",
"offset": 0,
"slot": "1",
"type": "t_array(t_uint256)2_storage"
},
{
"astId": 11,
"contract": "fileA:A",
"label": "dynArray",
"offset": 0,
"slot": "3",
"type": "t_array(t_uint256)dyn_storage"
}
],
"numberOfBytes": "128"
},
"t_uint128": {
"encoding": "inplace",
"label": "uint128",
"numberOfBytes": "16"
},
"t_uint256": {
"encoding": "inplace",
"label": "uint256",
"numberOfBytes": "32"
}
}
}

View File

@ -0,0 +1,71 @@
.. index:: optimizer, common subexpression elimination, constant propagation
*************
The Optimiser
*************
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>`_.
The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks
at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser
analyses the instructions and records every modification to the stack,
memory, or storage as an expression which consists of an instruction and
a list of arguments which are pointers to other expressions. The optimiser
uses a component called "CommonSubexpressionEliminator" that amongst other
tasks, finds expressions that are always equal (on every input) and combines
them into an expression class. The optimiser first tries to find each new
expression in a list of already known expressions. If this does not work,
it simplifies the expression according to rules like
``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is
a recursive process, we can also apply the latter rule if the second factor
is a more complex expression where we know that it always evaluates to one.
Modifications to storage and memory locations have to erase knowledge about
storage and memory locations which are not known to be different. If we first
write to location x and then to location y and both are input variables, the
second could overwrite the first, so we do not know what is stored at x after
we wrote to y. If simplification of the expression x - y evaluates to a
non-zero constant, we know that we can keep our knowledge about what is stored at x.
After this process, we know which expressions have to be on the stack at
the end, and have a list of modifications to memory and storage. This information
is stored together with the basic blocks and is used to link them. Furthermore,
knowledge about the stack, storage and memory configuration is forwarded to
the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions,
we can build a complete control flow graph of the program. If there is only
one target we do not know (this can happen as in principle, jump targets can
be computed from inputs), we have to erase all knowledge about the input state
of a block as it can be the target of the unknown ``JUMP``. If the optimiser
finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it
to an unconditional jump.
As the last step, the code in each block is re-generated. The optimiser creates
a dependency graph from the expressions on the stack at the end of the block,
and it drops every operation that is not part of this graph. It generates code
that applies the modifications to memory and storage in the order they were
made in the original code (dropping modifications which were found not to be
needed). Finally, it generates all values that are required to be on the
stack in the correct place.
These steps are applied to each basic block and the newly generated code
is used as replacement if it is smaller. If a basic block is split at a
``JUMPI`` and during the analysis, the condition evaluates to a constant,
the ``JUMPI`` is replaced depending on the value of the constant. Thus code like
::
uint x = 7;
data[7] = 9;
if (data[x] != x + 2)
return 2;
else
return 1;
still simplifies to code which you can compile even though the instructions contained
a jump in the beginning of the process:
::
data[7] = 9;
return 1;

View File

@ -0,0 +1,62 @@
.. index:: source mappings
***************
Source Mappings
***************
As part of the AST output, the compiler provides the range of the source
code that is represented by the respective node in the AST. This can be
used for various purposes ranging from static analysis tools that report
errors based on the AST and debugging tools that highlight local variables
and their uses.
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. 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
``output['sources'][sourceName]['id']`` where ``output`` is the output of the
standard-json compiler interface parsed as JSON.
.. note ::
In the case of instructions that are not associated with any particular source file,
the source mapping assigns an integer identifier of ``-1``. This may happen for
bytecode sections stemming from compiler-generated inline assembly statements.
The source mappings inside the AST use the following
notation:
``s:l:f``
Where ``s`` is the byte-offset to the start of the range in the source file,
``l`` is the length of the source range in bytes and ``f`` is the source
index mentioned above.
The encoding in the source mapping for the bytecode is more complicated:
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. ``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:
- If a field is empty, the value of the preceding element is used.
- If a ``:`` is missing, all following fields are considered empty.
This means the following source mappings represent the same information:
``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2``
``1:2:1;:9;2:1:2;;``

View File

@ -0,0 +1,47 @@
.. index: variable cleanup
*********************
Cleaning Up Variables
*********************
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 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
because otherwise the garbled value can be observed.
On the other hand, we do not clean the bits if the immediately
following operation is not affected. For instance, since any non-zero
value is considered ``true`` by ``JUMPI`` instruction, we do not clean
the boolean values before they are used as the condition for
``JUMPI``.
In addition to the design principle above, the Solidity compiler
cleans input data when it is loaded onto the stack.
Different types have different rules for cleaning up invalid values:
+---------------+---------------+-------------------+
|Type |Valid Values |Invalid Values Mean|
+===============+===============+===================+
|enum of n |0 until n - 1 |exception |
|members | | |
+---------------+---------------+-------------------+
|bool |0 or 1 |1 |
+---------------+---------------+-------------------+
|signed integers|sign-extended |currently silently |
| |word |wraps; in the |
| | |future exceptions |
| | |will be thrown |
| | | |
| | | |
+---------------+---------------+-------------------+
|unsigned |higher bits |currently silently |
|integers |zeroed |wraps; in the |
| | |future exceptions |
| | |will be thrown |
+---------------+---------------+-------------------+

View File

@ -1,814 +0,0 @@
#############
Miscellaneous
#############
.. index:: storage, state variable, mapping
************************************
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:
- 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).
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
by the above rules, state variables from different contracts do share the same storage slot.
The elements of structs and arrays are stored after each other, just as if they were given explicitly.
.. warning::
When using elements that are smaller than 32 bytes, your contract's gas usage may be higher.
This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller
than that, the EVM must use more operations in order to reduce the size of the element from 32
bytes to the desired size.
It is only beneficial to use reduced-size arguments if you are dealing with storage values
because the compiler will pack multiple elements into one storage slot, and thus, combine
multiple reads or writes into a single operation. When dealing with function arguments or memory
values, there is no inherent benefit because the compiler does not pack these values.
Finally, in order to allow the EVM to optimize for this, ensure that you try to order your
storage variables and ``struct`` members such that they can be packed tightly. For example,
declaring your storage variables in the order of ``uint128, uint128, uint256`` instead of
``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the
latter will take up three.
.. note::
The layout of state variables in storage is considered to be part of the external interface
of Solidity due to the fact that storage pointers can be passed to libraries. This means that
any change to the rules outlined in this section is considered a breaking change
of the language and due to its critical nature should be considered very carefully before
being executed.
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.
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>`).
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
the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``::
pragma solidity >=0.4.0 <0.8.0;
contract C {
struct S { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => S)) data;
}
.. _bytes-and-string:
``bytes`` and ``string``
------------------------
``bytes`` and ``string`` are encoded identically. For short byte arrays, they store their data in the same
slot where the length is also stored. In particular: if the data is at most ``31`` bytes long, it is stored
in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``.
For byte arrays that store data which is ``32`` or more bytes long, the main slot stores ``length * 2 + 1`` and the data is
stored as usual in ``keccak256(slot)``. This means that you can distinguish a short array from a long array
by checking if the lowest bit is set: short (not set) and long (set).
.. note::
Handling invalidly encoded slots is currently not supported but may be added in the future.
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,
``storage`` and ``types``. The ``storage`` object is an array where each
element has the following form:
.. code::
{
"astId": 2,
"contract": "fileA:A",
"label": "x",
"offset": 0,
"slot": "0",
"type": "t_uint256"
}
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
- ``contract`` is the name of the contract including its path as prefix
- ``label`` is the name of the state variable
- ``offset`` is the offset in bytes within the storage slot according to the encoding
- ``slot`` is the storage slot where the state variable resides or starts. This
number may be very large and therefore its JSON value is represented as a
string.
- ``type`` is an identifier used as key to the variable's type information (described in the following)
The given ``type``, in this case ``t_uint256`` represents an element in
``types``, which has the form:
.. code::
{
"encoding": "inplace",
"label": "uint256",
"numberOfBytes": "32",
}
where
- ``encoding`` how the data is encoded in storage, where the possible values are:
- ``inplace``: data is laid out contiguously in storage (see :ref:`above <storage-inplace-encoding>`).
- ``mapping``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
- ``dynamic_array``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
- ``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.
Some types have extra information besides the four above. Mappings contain
its ``key`` and ``value`` types (again referencing an entry in this mapping
of types), arrays have its ``base`` type, and structs list their ``members`` in
the same format as the top-level ``storage`` (see :ref:`above
<storage-layout-top-level>`).
.. note ::
The JSON output format of a contract's storage layout is still considered experimental
and is subject to change in non-breaking releases of Solidity.
The following example shows a contract and its storage layout, containing
value and reference types, types that are encoded packed, and nested types.
.. code::
pragma solidity >=0.4.0 <0.8.0;
contract A {
struct S {
uint128 a;
uint128 b;
uint[2] staticArray;
uint[] dynArray;
}
uint x;
uint y;
S s;
address addr;
mapping (uint => mapping (address => bool)) map;
uint[] array;
string s1;
bytes b1;
}
.. code::
"storageLayout": {
"storage": [
{
"astId": 14,
"contract": "fileA:A",
"label": "x",
"offset": 0,
"slot": "0",
"type": "t_uint256"
},
{
"astId": 16,
"contract": "fileA:A",
"label": "y",
"offset": 0,
"slot": "1",
"type": "t_uint256"
},
{
"astId": 18,
"contract": "fileA:A",
"label": "s",
"offset": 0,
"slot": "2",
"type": "t_struct(S)12_storage"
},
{
"astId": 20,
"contract": "fileA:A",
"label": "addr",
"offset": 0,
"slot": "6",
"type": "t_address"
},
{
"astId": 26,
"contract": "fileA:A",
"label": "map",
"offset": 0,
"slot": "7",
"type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))"
},
{
"astId": 29,
"contract": "fileA:A",
"label": "array",
"offset": 0,
"slot": "8",
"type": "t_array(t_uint256)dyn_storage"
},
{
"astId": 31,
"contract": "fileA:A",
"label": "s1",
"offset": 0,
"slot": "9",
"type": "t_string_storage"
},
{
"astId": 33,
"contract": "fileA:A",
"label": "b1",
"offset": 0,
"slot": "10",
"type": "t_bytes_storage"
}
],
"types": {
"t_address": {
"encoding": "inplace",
"label": "address",
"numberOfBytes": "20"
},
"t_array(t_uint256)2_storage": {
"base": "t_uint256",
"encoding": "inplace",
"label": "uint256[2]",
"numberOfBytes": "64"
},
"t_array(t_uint256)dyn_storage": {
"base": "t_uint256",
"encoding": "dynamic_array",
"label": "uint256[]",
"numberOfBytes": "32"
},
"t_bool": {
"encoding": "inplace",
"label": "bool",
"numberOfBytes": "1"
},
"t_bytes_storage": {
"encoding": "bytes",
"label": "bytes",
"numberOfBytes": "32"
},
"t_mapping(t_address,t_bool)": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => bool)",
"numberOfBytes": "32",
"value": "t_bool"
},
"t_mapping(t_uint256,t_mapping(t_address,t_bool))": {
"encoding": "mapping",
"key": "t_uint256",
"label": "mapping(uint256 => mapping(address => bool))",
"numberOfBytes": "32",
"value": "t_mapping(t_address,t_bool)"
},
"t_string_storage": {
"encoding": "bytes",
"label": "string",
"numberOfBytes": "32"
},
"t_struct(S)12_storage": {
"encoding": "inplace",
"label": "struct A.S",
"members": [
{
"astId": 2,
"contract": "fileA:A",
"label": "a",
"offset": 0,
"slot": "0",
"type": "t_uint128"
},
{
"astId": 4,
"contract": "fileA:A",
"label": "b",
"offset": 16,
"slot": "0",
"type": "t_uint128"
},
{
"astId": 8,
"contract": "fileA:A",
"label": "staticArray",
"offset": 0,
"slot": "1",
"type": "t_array(t_uint256)2_storage"
},
{
"astId": 11,
"contract": "fileA:A",
"label": "dynArray",
"offset": 0,
"slot": "3",
"type": "t_array(t_uint256)dyn_storage"
}
],
"numberOfBytes": "128"
},
"t_uint128": {
"encoding": "inplace",
"label": "uint128",
"numberOfBytes": "16"
},
"t_uint256": {
"encoding": "inplace",
"label": "uint256",
"numberOfBytes": "32"
}
}
}
.. index: memory layout
****************
Layout in Memory
****************
Solidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows:
- ``0x00`` - ``0x3f`` (64 bytes): scratch space for hashing methods
- ``0x40`` - ``0x5f`` (32 bytes): currently allocated memory size (aka. free memory pointer)
- ``0x60`` - ``0x7f`` (32 bytes): zero slot
Scratch space can be used between statements (i.e. within inline assembly). The zero slot
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).
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 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 unexpected results.
.. index: calldata layout
*******************
Layout of Call Data
*******************
The input data for a function call is assumed to be in the format defined by the :ref:`ABI
specification <ABI>`. Among others, the ABI specification requires arguments to be padded to multiples of 32
bytes. The internal function calls use a different convention.
Arguments for the constructor of a contract are directly appended at the end of the
contract's code, also in ABI encoding. The constructor will access them through a hard-coded offset, and
not by using the ``codesize`` opcode, since this of course changes when appending
data to the code.
.. index: variable cleanup
*********************************
Internals - Cleaning Up Variables
*********************************
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 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
because otherwise the garbled value can be observed.
On the other hand, we do not clean the bits if the immediately
following operation is not affected. For instance, since any non-zero
value is considered ``true`` by ``JUMPI`` instruction, we do not clean
the boolean values before they are used as the condition for
``JUMPI``.
In addition to the design principle above, the Solidity compiler
cleans input data when it is loaded onto the stack.
Different types have different rules for cleaning up invalid values:
+---------------+---------------+-------------------+
|Type |Valid Values |Invalid Values Mean|
+===============+===============+===================+
|enum of n |0 until n - 1 |exception |
|members | | |
+---------------+---------------+-------------------+
|bool |0 or 1 |1 |
+---------------+---------------+-------------------+
|signed integers|sign-extended |currently silently |
| |word |wraps; in the |
| | |future exceptions |
| | |will be thrown |
| | | |
| | | |
+---------------+---------------+-------------------+
|unsigned |higher bits |currently silently |
|integers |zeroed |wraps; in the |
| | |future exceptions |
| | |will be thrown |
+---------------+---------------+-------------------+
.. index:: optimizer, common subexpression elimination, constant propagation
*************************
Internals - The Optimiser
*************************
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>`_.
The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks
at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser
analyses the instructions and records every modification to the stack,
memory, or storage as an expression which consists of an instruction and
a list of arguments which are pointers to other expressions. The optimiser
uses a component called "CommonSubexpressionEliminator" that amongst other
tasks, finds expressions that are always equal (on every input) and combines
them into an expression class. The optimiser first tries to find each new
expression in a list of already known expressions. If this does not work,
it simplifies the expression according to rules like
``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is
a recursive process, we can also apply the latter rule if the second factor
is a more complex expression where we know that it always evaluates to one.
Modifications to storage and memory locations have to erase knowledge about
storage and memory locations which are not known to be different. If we first
write to location x and then to location y and both are input variables, the
second could overwrite the first, so we do not know what is stored at x after
we wrote to y. If simplification of the expression x - y evaluates to a
non-zero constant, we know that we can keep our knowledge about what is stored at x.
After this process, we know which expressions have to be on the stack at
the end, and have a list of modifications to memory and storage. This information
is stored together with the basic blocks and is used to link them. Furthermore,
knowledge about the stack, storage and memory configuration is forwarded to
the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions,
we can build a complete control flow graph of the program. If there is only
one target we do not know (this can happen as in principle, jump targets can
be computed from inputs), we have to erase all knowledge about the input state
of a block as it can be the target of the unknown ``JUMP``. If the optimiser
finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it
to an unconditional jump.
As the last step, the code in each block is re-generated. The optimiser creates
a dependency graph from the expressions on the stack at the end of the block,
and it drops every operation that is not part of this graph. It generates code
that applies the modifications to memory and storage in the order they were
made in the original code (dropping modifications which were found not to be
needed). Finally, it generates all values that are required to be on the
stack in the correct place.
These steps are applied to each basic block and the newly generated code
is used as replacement if it is smaller. If a basic block is split at a
``JUMPI`` and during the analysis, the condition evaluates to a constant,
the ``JUMPI`` is replaced depending on the value of the constant. Thus code like
::
uint x = 7;
data[7] = 9;
if (data[x] != x + 2)
return 2;
else
return 1;
still simplifies to code which you can compile even though the instructions contained
a jump in the beginning of the process:
::
data[7] = 9;
return 1;
.. index:: source mappings
***************
Source Mappings
***************
As part of the AST output, the compiler provides the range of the source
code that is represented by the respective node in the AST. This can be
used for various purposes ranging from static analysis tools that report
errors based on the AST and debugging tools that highlight local variables
and their uses.
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. 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
``output['sources'][sourceName]['id']`` where ``output`` is the output of the
standard-json compiler interface parsed as JSON.
.. note ::
In the case of instructions that are not associated with any particular source file,
the source mapping assigns an integer identifier of ``-1``. This may happen for
bytecode sections stemming from compiler-generated inline assembly statements.
The source mappings inside the AST use the following
notation:
``s:l:f``
Where ``s`` is the byte-offset to the start of the range in the source file,
``l`` is the length of the source range in bytes and ``f`` is the source
index mentioned above.
The encoding in the source mapping for the bytecode is more complicated:
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. ``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:
- If a field is empty, the value of the preceding element is used.
- If a ``:`` is missing, all following fields are considered empty.
This means the following source mappings represent the same information:
``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2``
``1:2:1;:9;2:1:2;;``
***************
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!
* 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.
**********
Cheatsheet
**********
.. index:: precedence
.. _order:
Order of Precedence of Operators
================================
The following is the order of precedence for operators, listed in order of evaluation.
+------------+-------------------------------------+--------------------------------------------+
| Precedence | Description | Operator |
+============+=====================================+============================================+
| *1* | Postfix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
| | New expression | ``new <typename>`` |
+ +-------------------------------------+--------------------------------------------+
| | Array subscripting | ``<array>[<index>]`` |
+ +-------------------------------------+--------------------------------------------+
| | Member access | ``<object>.<member>`` |
+ +-------------------------------------+--------------------------------------------+
| | Function-like call | ``<func>(<args...>)`` |
+ +-------------------------------------+--------------------------------------------+
| | Parentheses | ``(<statement>)`` |
+------------+-------------------------------------+--------------------------------------------+
| *2* | Prefix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
| | Unary minus | ``-`` |
+ +-------------------------------------+--------------------------------------------+
| | Unary operations | ``delete`` |
+ +-------------------------------------+--------------------------------------------+
| | Logical NOT | ``!`` |
+ +-------------------------------------+--------------------------------------------+
| | Bitwise NOT | ``~`` |
+------------+-------------------------------------+--------------------------------------------+
| *3* | Exponentiation | ``**`` |
+------------+-------------------------------------+--------------------------------------------+
| *4* | Multiplication, division and modulo | ``*``, ``/``, ``%`` |
+------------+-------------------------------------+--------------------------------------------+
| *5* | Addition and subtraction | ``+``, ``-`` |
+------------+-------------------------------------+--------------------------------------------+
| *6* | Bitwise shift operators | ``<<``, ``>>`` |
+------------+-------------------------------------+--------------------------------------------+
| *7* | Bitwise AND | ``&`` |
+------------+-------------------------------------+--------------------------------------------+
| *8* | Bitwise XOR | ``^`` |
+------------+-------------------------------------+--------------------------------------------+
| *9* | Bitwise OR | ``|`` |
+------------+-------------------------------------+--------------------------------------------+
| *10* | Inequality operators | ``<``, ``>``, ``<=``, ``>=`` |
+------------+-------------------------------------+--------------------------------------------+
| *11* | Equality operators | ``==``, ``!=`` |
+------------+-------------------------------------+--------------------------------------------+
| *12* | Logical AND | ``&&`` |
+------------+-------------------------------------+--------------------------------------------+
| *13* | Logical OR | ``||`` |
+------------+-------------------------------------+--------------------------------------------+
| *14* | Ternary operator | ``<conditional> ? <if-true> : <if-false>`` |
+ +-------------------------------------+--------------------------------------------+
| | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, |
| | | ``>>=``, ``+=``, ``-=``, ``*=``, ``/=``, |
| | | ``%=`` |
+------------+-------------------------------------+--------------------------------------------+
| *15* | Comma operator | ``,`` |
+------------+-------------------------------------+--------------------------------------------+
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
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.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)), ...)```
- ``block.coinbase`` (``address payable``): current block miner's address
- ``block.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit
- ``block.number`` (``uint``): current block number
- ``block.timestamp`` (``uint``): current block timestamp
- ``gasleft() returns (uint256)``: remaining gas
- ``msg.data`` (``bytes``): complete calldata
- ``msg.sender`` (``address payable``): sender of the message (current call)
- ``msg.value`` (``uint``): number of wei sent with the message
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
- ``tx.gasprice`` (``uint``): gas price of the transaction
- ``tx.origin`` (``address payable``): sender of the transaction (full call chain)
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
- ``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.
- ``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>.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>`.
- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`.
.. note::
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
unless you know what you are doing.
Both the timestamp and the block hash can be influenced by miners to some degree.
Bad actors in the mining community can for example run a casino payout function on a chosen hash
and just retry a different hash if they did not receive any money.
The current block timestamp must be strictly larger than the timestamp of the last block,
but the only guarantee is that it will be somewhere between the timestamps of two
consecutive blocks in the canonical chain.
.. note::
The block hashes are not available for all blocks for scalability reasons.
You can only access the hashes of the most recent 256 blocks, all other
values will be zero.
.. note::
In version 0.5.0, the following aliases were removed: ``suicide`` as alias for ``selfdestruct``,
``msg.gas`` as alias for ``gasleft``, ``block.blockhash`` as alias for ``blockhash`` and
``sha3`` as alias for ``keccak256``.
.. index:: visibility, public, private, external, internal
Function Visibility Specifiers
==============================
::
function myFunction() <visibility specifier> returns (bool) {
return true;
}
- ``public``: visible externally and internally (creates a :ref:`getter function<getter-functions>` for storage/state variables)
- ``private``: only visible in the current contract
- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``)
- ``internal``: only visible internally
.. index:: modifiers, pure, view, payable, constant, anonymous, indexed
Modifiers
=========
- ``pure`` for functions: Disallows modification or access of state.
- ``view`` for functions: Disallows modification of state.
- ``payable`` for functions: Allows them to receive Ether together with a call.
- ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
- ``immutable`` for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code.
- ``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:
``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``,
``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``,
``unchecked``.
Language Grammar
================
.. literalinclude:: Solidity.g4
:language: antlr

View File

@ -1,2 +1,2 @@
sphinx_rtd_theme>=0.3.1
pygments-lexer-solidity>=0.3.1
pygments-lexer-solidity>=0.5.1

View File

@ -491,7 +491,8 @@ 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.
by this engine. Internal function calls are supported, but external 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.
@ -505,10 +506,16 @@ 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.
Calls to functions in other contracts are not inlined even if their code is
In the BMC engine, function calls to the same contract (or base contracts) are
inlined when possible, that is, when their implementation is available. Calls
to functions in other contracts are not inlined even if their code is
available, since we cannot guarantee that the actual deployed code is the same.
The CHC engine creates nonlinear Horn clauses that use summaries of the called
functions to support internal function calls. The same approach can and will be
used for external function calls, but the latter requires more work regarding
the entire state of the blockchain and is still unimplemented.
Complex pure functions are abstracted by an uninterpreted function (UF) over
the arguments.
@ -519,11 +526,14 @@ the arguments.
+-----------------------------------+--------------------------------------+
|``require`` |Assumption |
+-----------------------------------+--------------------------------------+
|internal |Inline function call |
|internal |BMC: Inline function call |
| |CHC: Function summaries |
+-----------------------------------+--------------------------------------+
|external |Inline function call |
| |Erase knowledge about state variables |
| |and local storage references |
|external |BMC: Inline function call or |
| |erase knowledge about state variables |
| |and local storage references. |
| |CHC: Function summaries and erase |
| |state knowledge. |
+-----------------------------------+--------------------------------------+
|``gasleft``, ``blockhash``, |Abstracted with UF |
|``keccak256``, ``ecrecover`` | |
@ -534,8 +544,8 @@ the arguments.
|implementation (external or | |
|complex) | |
+-----------------------------------+--------------------------------------+
|external functions without |Unsupported |
|implementation | |
|external functions without |BMC: Unsupported |
|implementation |CHC: Nondeterministic summary |
+-----------------------------------+--------------------------------------+
|others |Currently unsupported |
+-----------------------------------+--------------------------------------+

View File

@ -1,22 +0,0 @@
#################
Solidity in Depth
#################
This section should provide you with all you need to know about Solidity.
If something is missing here, please contact us on
`Gitter <https://gitter.im/ethereum/solidity>`_ or create a pull request on
`Github <https://github.com/ethereum/solidity/pulls>`_.
.. toctree::
:maxdepth: 2
layout-of-source-files.rst
structure-of-a-contract.rst
types.rst
units-and-global-variables.rst
control-structures.rst
contracts.rst
assembly.rst
miscellaneous.rst
050-breaking-changes.rst
060-breaking-changes.rst

View File

@ -319,3 +319,12 @@ available for a contract type ``C``:
regular calls.
The same restrictions as with ``.creationCode`` also apply for this
property.
In addition to the properties above, the following properties are available
for an interface type ``I``:
``type(I).interfaceId``:
A ``bytes4`` value containing the `EIP-165 <https://eips.ethereum.org/EIPS/eip-165>`_
interface identifier of the given interface ``I``. This identifier is defined as the ``XOR`` of all
function selectors defined within the interface itself - excluding all inherited functions.

View File

@ -70,7 +70,7 @@ The following example program is written in the EVM dialect and computes exponen
It can be compiled using ``solc --strict-assembly``. The builtin functions
``mul`` and ``div`` compute product and division, respectively.
.. code::
.. code-block:: yul
{
function power(base, exponent) -> result
@ -91,7 +91,7 @@ It is also possible to implement the same function using a for-loop
instead of with recursion. Here, ``lt(a, b)`` computes whether ``a`` is less than ``b``.
less-than comparison.
.. code::
.. code-block:: yul
{
function power(base, exponent) -> result
@ -115,7 +115,7 @@ This will use the :ref:`Yul object notation <yul-object>` so that it is possible
to code as data to deploy contracts. This Yul mode is available for the commandline compiler
(use ``--strict-assembly``) and for the :ref:`standard-json interface <compiler-api>`:
::
.. code-block:: json
{
"language": "Yul",
@ -180,14 +180,14 @@ bitwise ``and`` with the string "abc" is computed.
The final value is assigned to a local variable called ``x``.
Strings are stored left-aligned and cannot be longer than 32 bytes.
.. code::
.. code-block:: yul
let x := and("abc", add(3, 2))
Unless it is the default type, the type of a literal
has to be specified after a colon:
.. code::
.. code-block:: yul
let x := and("abc":uint32, add(3:uint256, 2:uint256))
@ -201,7 +201,7 @@ If the function returns a single value, it can be directly used
inside an expression again. If it returns multiple values,
they have to be assigned to local variables.
.. code::
.. code-block:: yul
mstore(0x80, add(mload(0x80), 3))
// Here, the user-defined function `f` returns
@ -242,7 +242,7 @@ Future dialects migh introduce specific types for such pointers.
When a variable is referenced, its current value is copied.
For the EVM, this translates to a ``DUP`` instruction.
.. code::
.. code-block:: yul
{
let zero := 0
@ -260,7 +260,7 @@ you denote that following a colon. You can also declare multiple
variables in one statement when you assign from a function call
that returns multiple values.
.. code::
.. code-block:: yul
{
let zero:uint32 := 0:uint32
@ -283,7 +283,7 @@ values have to match.
If you want to assign the values returned from a function that has
multiple return parameters, you have to provide multiple variables.
.. code::
.. code-block:: yul
let v := 0
// re-assign v
@ -301,7 +301,7 @@ The if statement can be used for conditionally executing code.
No "else" block can be defined. Consider using "switch" instead (see below) if
you need multiple alternatives.
.. code::
.. code-block:: yul
if eq(value, 0) { revert(0, 0) }
@ -317,7 +317,7 @@ Contrary to other programming languages, for safety reasons, control flow does
not continue from one case to the next. There can be a fallback or default
case called ``default`` which is taken if none of the literal constants matches.
.. code::
.. code-block:: yul
{
let x := 0
@ -349,7 +349,7 @@ or skip to the post-part, respectively.
The following example computes the sum of an area in memory.
.. code::
.. code-block:: yul
{
let x := 0
@ -361,7 +361,7 @@ The following example computes the sum of an area in memory.
For loops can also be used as a replacement for while loops:
Simply leave the initialization and post-iteration parts empty.
.. code::
.. code-block:: yul
{
let x := 0
@ -404,7 +404,7 @@ the current yul function.
The following example implements the power function by square-and-multiply.
.. code::
.. code-block:: yul
{
function power(base, exponent) -> result {
@ -425,7 +425,7 @@ Specification of Yul
This chapter describes Yul code formally. Yul code is usually placed inside Yul objects,
which are explained in their own chapter.
Grammar::
.. code-block:: none
Block = '{' Statement* '}'
Statement =
@ -588,7 +588,7 @@ For an identifier ``v``, let ``$v`` be the name of the identifier.
We will use a destructuring notation for the AST nodes.
.. code::
.. code-block:: none
E(G, L, <{St1, ..., Stn}>: Block) =
let G1, L1, mode = E(G, L, St1, ..., Stn)
@ -915,7 +915,7 @@ Hex strings can be used to specify data in hex encoding,
regular strings in native encoding. For code,
``datacopy`` will access its assembled binary representation.
Grammar::
.. code-block:: none
Object = 'object' StringLiteral '{' Code ( Object | Data )* '}'
Code = 'code' Block
@ -927,7 +927,7 @@ Above, ``Block`` refers to ``Block`` in the Yul code grammar explained in the pr
An example Yul Object is shown below:
.. code::
.. code-block:: yul
// A contract consists of a single object with sub-objects representing
// the code to be deployed or other contracts it can create.
@ -1010,7 +1010,7 @@ for more details about its internals.
If you want to use Solidity in stand-alone Yul mode, you activate the optimizer using ``--optimize``:
::
.. code-block:: sh
solc --strict-assembly --optimize

View File

@ -232,6 +232,28 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4(
};
}
template <class Pattern>
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4_5(
Pattern,
Pattern,
Pattern,
Pattern X,
Pattern Y
)
{
using Builtins = typename Pattern::Builtins;
return std::vector<SimplificationRule<Pattern>>{
// idempotent operations
{Builtins::AND(Builtins::AND(X, Y), Y), [=]{ return Builtins::AND(X, Y); }, true},
{Builtins::AND(Y, Builtins::AND(X, Y)), [=]{ return Builtins::AND(X, Y); }, true},
{Builtins::AND(Builtins::AND(Y, X), Y), [=]{ return Builtins::AND(Y, X); }, true},
{Builtins::AND(Y, Builtins::AND(Y, X)), [=]{ return Builtins::AND(Y, X); }, true},
{Builtins::OR(Builtins::OR(X, Y), Y), [=]{ return Builtins::OR(X, Y); }, true},
{Builtins::OR(Y, Builtins::OR(X, Y)), [=]{ return Builtins::OR(X, Y); }, true},
{Builtins::OR(Builtins::OR(Y, X), Y), [=]{ return Builtins::OR(Y, X); }, true},
{Builtins::OR(Y, Builtins::OR(Y, X)), [=]{ return Builtins::OR(Y, X); }, true},
};
}
template <class Pattern>
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
@ -663,6 +685,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList(
rules += simplificationRuleListPart2(A, B, C, W, X);
rules += simplificationRuleListPart3(A, B, C, W, X);
rules += simplificationRuleListPart4(A, B, C, W, X);
rules += simplificationRuleListPart4_5(A, B, C, W, X);
rules += simplificationRuleListPart5(A, B, C, W, X);
rules += simplificationRuleListPart6(A, B, C, W, X);
rules += simplificationRuleListPart7(A, B, C, W, X);

View File

@ -21,7 +21,6 @@
#include <liblangutil/SourceReferenceFormatterHuman.h>
#include <liblangutil/Scanner.h>
#include <liblangutil/Exceptions.h>
#include <cmath>
#include <iomanip>
using namespace std;
@ -70,58 +69,66 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _
if (_ref.sourceName.empty())
return; // Nothing we can print here
int const leftpad = static_cast<int>(log10(max(_ref.position.line, 1))) + 1;
// line 0: source name
frameColored() << string(leftpad, ' ') << "--> ";
if (_ref.position.line < 0)
{
m_stream << _ref.sourceName << "\n";
frameColored() << "-->";
m_stream << ' ' << _ref.sourceName << '\n';
return; // No line available, nothing else to print
}
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n';
string line = std::to_string(_ref.position.line + 1); // one-based line number as string
string leftpad = string(line.size(), ' ');
// line 0: source name
m_stream << leftpad;
frameColored() << "-->";
m_stream << ' ' << _ref.sourceName << ':' << line << ':' << (_ref.position.column + 1) << ":\n";
if (!_ref.multiline)
{
int const locationLength = _ref.endColumn - _ref.startColumn;
// line 1:
m_stream << string(leftpad, ' ');
frameColored() << " |" << '\n';
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << '\n';
// line 2:
frameColored() << (_ref.position.line + 1) << " | ";
m_stream << _ref.text.substr(0, _ref.startColumn);
frameColored() << line << " |";
m_stream << ' ' << _ref.text.substr(0, _ref.startColumn);
highlightColored() << _ref.text.substr(_ref.startColumn, locationLength);
m_stream << _ref.text.substr(_ref.endColumn) << '\n';
// line 3:
m_stream << string(leftpad, ' ');
frameColored() << " | ";
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << ' ';
for_each(
_ref.text.cbegin(),
_ref.text.cbegin() + _ref.startColumn,
[this](char ch) { m_stream << (ch == '\t' ? '\t' : ' '); }
);
diagColored() << string(locationLength, '^') << '\n';
diagColored() << string(locationLength, '^');
m_stream << '\n';
}
else
{
// line 1:
m_stream << string(leftpad, ' ');
frameColored() << " |" << '\n';
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << '\n';
// line 2:
frameColored() << (_ref.position.line + 1) << " | ";
m_stream << _ref.text.substr(0, _ref.startColumn);
frameColored() << line << " |";
m_stream << ' ' << _ref.text.substr(0, _ref.startColumn);
highlightColored() << _ref.text.substr(_ref.startColumn) << '\n';
// line 3:
frameColored() << string(leftpad, ' ') << " | ";
m_stream << string(_ref.startColumn, ' ');
diagColored() << "^ (Relevant source part starts here and spans across multiple lines).\n";
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << ' ' << string(_ref.startColumn, ' ');
diagColored() << "^ (Relevant source part starts here and spans across multiple lines).";
m_stream << '\n';
}
}

View File

@ -55,7 +55,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
checkDuplicateEvents(_contract);
m_overrideChecker.check(_contract);
checkBaseConstructorArguments(_contract);
checkAbstractFunctions(_contract);
checkAbstractDefinitions(_contract);
checkExternalTypeClashes(_contract);
checkHashCollisions(_contract);
checkLibraryRequirements(_contract);
@ -156,55 +156,43 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
}
}
void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _contract)
void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _contract)
{
// Mapping from name to function definition (exactly one per argument type equality class) and
// flag to indicate whether it is fully implemented.
using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>;
map<string, vector<FunTypeAndFlag>> functions;
// Collects functions, static variable getters and modifiers. If they
// override (unimplemented) base class ones, they are replaced.
set<OverrideProxy, OverrideProxy::CompareBySignature> proxies;
auto registerFunction = [&](Declaration const& _declaration, FunctionTypePointer const& _type, bool _implemented)
auto registerProxy = [&proxies](OverrideProxy const& _overrideProxy)
{
auto& overloads = functions[_declaration.name()];
auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag)
{
return _type->hasEqualParameterTypes(*_funAndFlag.first);
});
if (it == overloads.end())
overloads.emplace_back(_type, _implemented);
else if (_implemented)
it->second = true;
// Overwrite an existing proxy, if it exists.
if (!_overrideProxy.unimplemented())
proxies.erase(_overrideProxy);
proxies.insert(_overrideProxy);
};
// Search from base to derived, collect all functions and update
// the 'implemented' flag.
// Search from base to derived, collect all functions and modifiers and
// update proxies.
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
{
for (VariableDeclaration const* v: contract->stateVariables())
if (v->isPartOfExternalInterface())
registerFunction(*v, TypeProvider::function(*v), true);
registerProxy(OverrideProxy(v));
for (FunctionDefinition const* function: contract->definedFunctions())
if (!function->isConstructor())
registerFunction(
*function,
TypeProvider::function(*function)->asCallableFunction(false),
function->isImplemented()
);
registerProxy(OverrideProxy(function));
for (ModifierDefinition const* modifier: contract->functionModifiers())
registerProxy(OverrideProxy(modifier));
}
// Set to not fully implemented if at least one flag is false.
// Note that `_contract.annotation().unimplementedFunctions` has already been
// Note that `_contract.annotation().unimplementedDeclarations` has already been
// pre-filled by `checkBaseConstructorArguments`.
for (auto const& it: functions)
for (auto const& funAndFlag: it.second)
if (!funAndFlag.second)
{
FunctionDefinition const* function = dynamic_cast<FunctionDefinition const*>(&funAndFlag.first->declaration());
solAssert(function, "");
_contract.annotation().unimplementedFunctions.push_back(function);
break;
}
for (auto const& proxy: proxies)
if (proxy.unimplemented())
_contract.annotation().unimplementedDeclarations.push_back(proxy.declaration());
if (_contract.abstract())
{
@ -221,15 +209,16 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
if (
_contract.contractKind() == ContractKind::Contract &&
!_contract.abstract() &&
!_contract.annotation().unimplementedFunctions.empty()
!_contract.annotation().unimplementedDeclarations.empty()
)
{
SecondarySourceLocation ssl;
for (auto function: _contract.annotation().unimplementedFunctions)
ssl.append("Missing implementation:", function->location());
for (auto declaration: _contract.annotation().unimplementedDeclarations)
ssl.append("Missing implementation: ", declaration->location());
m_errorReporter.typeError(_contract.location(), ssl,
"Contract \"" + _contract.annotation().canonicalName
+ "\" should be marked as abstract.");
}
}
@ -277,7 +266,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons
if (FunctionDefinition const* constructor = contract->constructor())
if (contract != &_contract && !constructor->parameters().empty())
if (!_contract.annotation().baseConstructorArguments.count(constructor))
_contract.annotation().unimplementedFunctions.push_back(constructor);
_contract.annotation().unimplementedDeclarations.push_back(constructor);
}
void ContractLevelChecker::annotateBaseConstructorArguments(

View File

@ -61,7 +61,8 @@ private:
void checkDuplicateEvents(ContractDefinition const& _contract);
template <class T>
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message);
void checkAbstractFunctions(ContractDefinition const& _contract);
/// Checks for unimplemented functions and modifiers.
void checkAbstractDefinitions(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);

View File

@ -148,7 +148,8 @@ bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDec
funcDef->body().accept(*this);
}
else if (ModifierDefinition const* modDef = dynamic_cast<decltype(modDef)>(&_callableDeclaration))
modDef->body().accept(*this);
if (modDef->isImplemented())
modDef->body().accept(*this);
m_currentConstructor = prevConstructor;

View File

@ -327,6 +327,16 @@ ModifierType const* OverrideProxy::modifierType() const
}, m_item);
}
Declaration const* OverrideProxy::declaration() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _function) -> Declaration const* { return _function; },
[&](VariableDeclaration const* _variable) -> Declaration const* { return _variable; },
[&](ModifierDefinition const* _modifier) -> Declaration const* { return _modifier; }
}, m_item);
}
SourceLocation const& OverrideProxy::location() const
{
return std::visit(GenericVisitor{
@ -365,7 +375,7 @@ bool OverrideProxy::unimplemented() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return !_item->isImplemented(); },
[&](ModifierDefinition const*) { return false; },
[&](ModifierDefinition const* _item) { return !_item->isImplemented(); },
[&](VariableDeclaration const*) { return false; }
}, m_item);
}

View File

@ -85,6 +85,8 @@ public:
FunctionType const* functionType() const;
ModifierType const* modifierType() const;
Declaration const* declaration() const;
langutil::SourceLocation const& location() const;
std::string astNodeName() const;

View File

@ -141,7 +141,7 @@ bool SyntaxChecker::visit(ModifierDefinition const&)
void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
{
if (!m_placeholderFound)
if (_modifier.isImplemented() && !m_placeholderFound)
m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
m_placeholderFound = false;
}

View File

@ -289,6 +289,12 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected.");
}
void TypeChecker::endVisit(ModifierDefinition const& _modifier)
{
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
m_errorReporter.typeError(_modifier.location(), "Modifiers without implementation must be marked virtual.");
}
bool TypeChecker::visit(FunctionDefinition const& _function)
{
bool isLibraryFunction = _function.inContractKind() == ContractKind::Library;
@ -1290,8 +1296,11 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
{
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
{
if (tupleExpression->components().empty())
m_errorReporter.typeError(_expression.location(), "Empty tuple on the left hand side.");
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
auto const& types = tupleType && tupleExpression->components().size() > 1 ? tupleType->components() : vector<TypePointer> { &_type };
auto const& types = tupleType && tupleExpression->components().size() != 1 ? tupleType->components() : vector<TypePointer> { &_type };
solAssert(
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
@ -2591,6 +2600,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
}
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
annotation.isPure = true;
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId")
annotation.isPure = true;
}
return false;

View File

@ -112,6 +112,7 @@ private:
void endVisit(InheritanceSpecifier const& _inheritance) override;
void endVisit(UsingForDirective const& _usingFor) override;
void endVisit(ModifierDefinition const& _modifier) override;
bool visit(FunctionDefinition const& _function) override;
bool visit(VariableDeclaration const& _variable) override;
/// We need to do this manually because we want to pass the bases of the current contract in

View File

@ -356,6 +356,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{MagicType::Kind::MetaType, "creationCode"},
{MagicType::Kind::MetaType, "runtimeCode"},
{MagicType::Kind::MetaType, "name"},
{MagicType::Kind::MetaType, "interfaceId"},
};
set<MagicMember> static const payableMembers{
{MagicType::Kind::Message, "value"}

View File

@ -98,9 +98,9 @@ bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const
return util::contains(annotation().linearizedBaseContracts, &_base);
}
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions(bool _includeInheritedFunctions) const
{
auto exportedFunctionList = interfaceFunctionList();
auto exportedFunctionList = interfaceFunctionList(_includeInheritedFunctions);
map<util::FixedHash<4>, FunctionTypePointer> exportedFunctions;
for (auto const& it: exportedFunctionList)
@ -176,14 +176,16 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons
return *m_interfaceEvents;
}
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList() const
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const
{
if (!m_interfaceFunctionList)
if (!m_interfaceFunctionList[_includeInheritedFunctions])
{
set<string> signaturesSeen;
m_interfaceFunctionList = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
m_interfaceFunctionList[_includeInheritedFunctions] = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
{
if (_includeInheritedFunctions == false && contract != this)
continue;
vector<FunctionTypePointer> functions;
for (FunctionDefinition const* f: contract->definedFunctions())
if (f->isPartOfExternalInterface())
@ -201,12 +203,12 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
{
signaturesSeen.insert(functionSignature);
util::FixedHash<4> hash(util::keccak256(functionSignature));
m_interfaceFunctionList->emplace_back(hash, fun);
m_interfaceFunctionList[_includeInheritedFunctions]->emplace_back(hash, fun);
}
}
}
}
return *m_interfaceFunctionList;
return *m_interfaceFunctionList[_includeInheritedFunctions];
}
TypePointer ContractDefinition::type() const

View File

@ -489,8 +489,8 @@ public:
/// @returns a map of canonical function signatures to FunctionDefinitions
/// as intended for use by the ABI.
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions() const;
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList() const;
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions(bool _includeInheritedFunctions = true) const;
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList(bool _includeInheritedFunctions = true) const;
/// @returns a list of all declarations in this contract
std::vector<Declaration const*> declarations() const { return filteredNodes<Declaration>(m_subNodes); }
@ -529,7 +529,7 @@ private:
ContractKind m_contractKind;
bool m_abstract{false};
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
};
@ -969,7 +969,7 @@ private:
/**
* Definition of a function modifier.
*/
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional
{
public:
ModifierDefinition(
@ -980,18 +980,19 @@ public:
ASTPointer<ParameterList> const& _parameters,
bool _isVirtual,
ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<Block> _body
ASTPointer<Block> const& _body
):
CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
StructurallyDocumented(_documentation),
m_body(std::move(_body))
ImplementationOptional(_body != nullptr),
m_body(_body)
{
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Block const& body() const { return *m_body; }
Block const& body() const { solAssert(m_body, ""); return *m_body; }
TypePointer type() const override;

View File

@ -140,8 +140,8 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
{
/// List of functions without a body. Can also contain functions from base classes.
std::vector<FunctionDefinition const*> unimplementedFunctions;
/// List of functions and modifiers without a body. Can also contain functions from base classes.
std::vector<Declaration const*> unimplementedDeclarations;
/// List of all (direct and indirect) base contracts in order from derived to
/// base, including the contract itself.
std::vector<ContractDefinition const*> linearizedBaseContracts;

View File

@ -272,7 +272,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("contractKind", contractKind(_node.contractKind())),
make_pair("abstract", _node.abstract()),
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
make_pair("fullyImplemented", _node.annotation().unimplementedDeclarations.empty()),
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
make_pair("baseContracts", toJson(_node.baseContracts())),
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
@ -407,7 +407,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
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()))
make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue)
};
if (!_node.annotation().baseFunctions.empty())
attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true)));

View File

@ -453,7 +453,7 @@ ASTPointer<ModifierDefinition> ASTJsonImporter::createModifierDefinition(Json::V
createParameterList(member(_node, "parameters")),
memberAsBool(_node, "virtual"),
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
createBlock(member(_node, "body"))
_node["body"].isNull() ? nullptr: createBlock(member(_node, "body"))
);
}

View File

@ -288,7 +288,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor);
if (m_body)
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
}
@ -302,7 +303,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor);
if (m_body)
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
}

View File

@ -3774,7 +3774,9 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
{"name", TypeProvider::stringMemory()},
});
else
return {};
return MemberList::MemberMap({
{"interfaceId", TypeProvider::fixedBytes(4)},
});
}
}
solAssert(false, "Unknown kind of magic.");

View File

@ -1390,7 +1390,6 @@ public:
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
std::string toString(bool _short) const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private:

View File

@ -502,6 +502,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
&meter,
_object,
_optimiserSettings.optimizeStackAllocation,
_optimiserSettings.yulOptimiserSteps,
_externalIdentifiers
);

View File

@ -1582,6 +1582,15 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
utils().storeStringData(contract.name());
}
else if (member == "interfaceId")
{
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
uint64_t result{0};
for (auto const& function: contract.interfaceFunctionList(false))
result ^= fromBigEndian<uint64_t>(function.first.ref());
m_context << (u256{result} << (256 - 32));
}
else if ((set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member))
{
// no-op

View File

@ -1337,6 +1337,34 @@ string YulUtilFunctions::allocationFunction()
});
}
string YulUtilFunctions::allocationTemporaryMemoryFunction()
{
string functionName = "allocateTemporaryMemory";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> memPtr {
memPtr := mload(<freeMemoryPointer>)
}
)")
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::releaseTemporaryMemoryFunction()
{
string functionName = "releaseTemporaryMemory";
return m_functionCollector.createFunction(functionName, [&](){
return Whiskers(R"(
function <functionName>() {
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
{
if (_type.baseType()->hasSimpleZeroValueInMemory())
@ -2334,3 +2362,39 @@ string YulUtilFunctions::extractReturndataFunction()
});
}
string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction(
ContractDefinition const& _contract,
string const& _creationObjectName
)
{
string functionName = "copy_arguments_for_constructor_" +
toString(_contract.constructor()->id()) +
"_object_" +
_contract.name() +
"_" +
toString(_contract.id());
return m_functionCollector.createFunction(functionName, [&]() {
string returnParams = suffixedVariableNameList("ret_param_",0, _contract.constructor()->parameters().size());
ABIFunctions abiFunctions(m_evmVersion, m_revertStrings, m_functionCollector);
return util::Whiskers(R"(
function <functionName>() -> <retParams> {
let programSize := datasize("<object>")
let argSize := sub(codesize(), programSize)
let memoryDataOffset := <allocate>(argSize)
codecopy(memoryDataOffset, programSize, argSize)
<retParams> := <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
}
)")
("functionName", functionName)
("retParams", returnParams)
("object", _creationObjectName)
("allocate", allocationFunction())
("abiDecode", abiFunctions.tupleDecoder(FunctionType(*_contract.constructor()).parameterTypes(), true))
.render();
});
}

View File

@ -253,6 +253,13 @@ public:
/// Return value: pointer
std::string allocationFunction();
/// @returns the name of the function that allocates temporary memory with predefined size
/// Return value: pointer
std::string allocationTemporaryMemoryFunction();
/// @returns the name of the function that releases previously allocated temporary memory
std::string releaseTemporaryMemoryFunction();
/// @returns the name of a function that zeroes an array.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryArrayFunction(ArrayType const& _type);
@ -337,6 +344,13 @@ public:
/// If returndatacopy() is not supported by the underlying target, a empty array will be returned instead.
std::string extractReturndataFunction();
/// @returns function name that returns constructor arguments copied to memory
/// signature: () -> arguments
std::string copyConstructorArgumentsToMemoryFunction(
ContractDefinition const& _contract,
std::string const& _creationObjectName
);
private:
/// Special case of conversionFunction - handles everything that does not
/// use exactly one variable to hold the value.

View File

@ -21,6 +21,7 @@
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
@ -96,6 +97,15 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
string IRGenerationContext::creationObjectName(ContractDefinition const& _contract) const
{
return _contract.name() + "_" + toString(_contract.id());
}
string IRGenerationContext::runtimeObjectName(ContractDefinition const& _contract) const
{
return _contract.name() + "_" + toString(_contract.id()) + "_deployed";
}
string IRGenerationContext::newYulVariable()
{
return "_" + to_string(++m_varCounter);
@ -122,7 +132,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
<#cases>
case <funID>
{
<out> := <name>(<in>)
<out> <assignment_op> <name>(<in>)
}
</cases>
default { invalid() }
@ -133,7 +143,13 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions);
templ("in", suffixedVariableNameList("in_", 0, _in));
templ("arrow", _out > 0 ? "->" : "");
templ("assignment_op", _out > 0 ? ":=" : "");
templ("out", suffixedVariableNameList("out_", 0, _out));
// UNIMPLEMENTED: Internal library calls via pointers are not implemented yet.
// We're not generating code for internal library functions here even though it's possible
// to call them via pointers. Right now such calls end up triggering the `default` case in
// the switch above.
vector<map<string, string>> functions;
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
for (FunctionDefinition const* function: contract->definedFunctions())
@ -164,6 +180,11 @@ YulUtilFunctions IRGenerationContext::utils()
return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions);
}
ABIFunctions IRGenerationContext::abiFunctions()
{
return ABIFunctions(m_evmVersion, m_revertStrings, m_functions);
}
std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message)
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);

View File

@ -20,6 +20,7 @@
#pragma once
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/ir/IRVariable.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/DebugSettings.h>
@ -38,11 +39,8 @@
namespace solidity::frontend
{
class ContractDefinition;
class VariableDeclaration;
class FunctionDefinition;
class Expression;
class YulUtilFunctions;
class ABIFunctions;
/**
* Class that contains contextual information during IR generation.
@ -93,6 +91,9 @@ public:
std::string functionName(FunctionDefinition const& _function);
std::string functionName(VariableDeclaration const& _varDecl);
std::string creationObjectName(ContractDefinition const& _contract) const;
std::string runtimeObjectName(ContractDefinition const& _contract) const;
std::string newYulVariable();
std::string internalDispatch(size_t _in, size_t _out);
@ -102,6 +103,8 @@ public:
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
ABIFunctions abiFunctions();
/// @returns code that stores @param _message for revert reason
/// if m_revertStrings is debug.
std::string revertReasonIfDebug(std::string const& _message = "");
@ -112,6 +115,8 @@ public:
/// function call that was invoked as part of the try statement.
std::string trySuccessConditionVariable(Expression const& _expression) const;
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
@ -131,6 +136,8 @@ private:
/// long as the order of Yul functions in the generated code is deterministic and the same on
/// all platforms - which is a property guaranteed by MultiUseYulFunctionCollector.
std::set<FunctionDefinition const*> m_functionGenerationQueue;
std::set<ContractDefinition const*, ASTNode::CompareByID> m_subObjects;
};
}

View File

@ -48,9 +48,12 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
pair<string, string> IRGenerator::run(
ContractDefinition const& _contract,
map<ContractDefinition const*, string const> const& _otherYulSources
)
{
string const ir = yul::reindent(generate(_contract));
string const ir = yul::reindent(generate(_contract, _otherYulSources));
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
if (!asmStack.parseAndAnalyze("", ir))
@ -73,15 +76,28 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
return {warning + ir, warning + asmStack.print()};
}
string IRGenerator::generate(ContractDefinition const& _contract)
string IRGenerator::generate(
ContractDefinition const& _contract,
map<ContractDefinition const*, string const> const& _otherYulSources
)
{
solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented.");
auto subObjectSources = [&_otherYulSources](std::set<ContractDefinition const*, ASTNode::CompareByID> const& subObjects) -> string
{
std::string subObjectsSources;
for (ContractDefinition const* subObject: subObjects)
subObjectsSources += _otherYulSources.at(subObject);
return subObjectsSources;
};
Whiskers t(R"(
object "<CreationObject>" {
code {
<memoryInit>
<constructor>
<callValueCheck>
<?notLibrary>
<?constructorHasParams> let <constructorParams> := <copyConstructorArguments>() </constructorHasParams>
<implicitConstructor>(<constructorParams>)
</notLibrary>
<deploy>
<functions>
}
@ -91,24 +107,46 @@ string IRGenerator::generate(ContractDefinition const& _contract)
<dispatch>
<runtimeFunctions>
}
<runtimeSubObjects>
}
<subObjects>
}
)");
resetContext(_contract);
t("CreationObject", creationObjectName(_contract));
t("CreationObject", m_context.creationObjectName(_contract));
t("memoryInit", memoryInit());
t("constructor", constructorCode(_contract));
t("notLibrary", !_contract.isLibrary());
FunctionDefinition const* constructor = _contract.constructor();
t("callValueCheck", !constructor || !constructor->isPayable() ? callValueCheck() : "");
vector<string> constructorParams;
if (constructor && !constructor->parameters().empty())
{
for (size_t i = 0; i < constructor->parameters().size(); ++i)
constructorParams.emplace_back(m_context.newYulVariable());
t(
"copyConstructorArguments",
m_utils.copyConstructorArgumentsToMemoryFunction(_contract, m_context.creationObjectName(_contract))
);
}
t("constructorParams", joinHumanReadable(constructorParams));
t("constructorHasParams", !constructorParams.empty());
t("implicitConstructor", implicitConstructorName(_contract));
t("deploy", deployCode(_contract));
generateImplicitConstructors(_contract);
generateQueuedFunctions();
t("functions", m_context.functionCollector().requestedFunctions());
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
resetContext(_contract);
t("RuntimeObject", runtimeObjectName(_contract));
t("RuntimeObject", m_context.runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
generateQueuedFunctions();
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
return t.render();
}
@ -238,64 +276,116 @@ string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDec
return generator.code();
}
string IRGenerator::constructorCode(ContractDefinition const& _contract)
pair<string, map<ContractDefinition const*, string>> IRGenerator::evaluateConstructorArguments(
ContractDefinition const& _contract
)
{
// Initialization of state variables in base-to-derived order.
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
map<ContractDefinition const*, string> constructorParams;
vector<pair<ContractDefinition const*, std::vector<ASTPointer<Expression>>const *>> baseConstructorArguments;
using boost::adaptors::reverse;
for (ASTPointer<InheritanceSpecifier> const& base: _contract.baseContracts())
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
base->name().annotation().referencedDeclaration
)->constructor(); baseConstructor && base->arguments())
baseConstructorArguments.emplace_back(
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
base->arguments()
);
ostringstream out;
if (FunctionDefinition const* constructor = _contract.constructor())
for (auto const& modifier: constructor->modifiers())
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
modifier->name()->annotation().referencedDeclaration
)->constructor(); baseConstructor && modifier->arguments())
baseConstructorArguments.emplace_back(
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
modifier->arguments()
);
FunctionDefinition const* constructor = _contract.constructor();
if (constructor && !constructor->isPayable())
out << callValueCheck();
for (ContractDefinition const* contract: reverse(_contract.annotation().linearizedBaseContracts))
IRGeneratorForStatements generator{m_context, m_utils};
for (auto&& [baseContract, arguments]: baseConstructorArguments)
{
out <<
"\n// Begin state variable initialization for contract \"" <<
contract->name() <<
"\" (" <<
contract->stateVariables().size() <<
" variables)\n";
IRGeneratorForStatements generator{m_context, m_utils};
for (VariableDeclaration const* variable: contract->stateVariables())
if (!variable->isConstant() && !variable->immutable())
generator.initializeStateVar(*variable);
out << generator.code();
out << "// End state variable initialization for contract \"" << contract->name() << "\".\n";
solAssert(baseContract && arguments, "");
if (baseContract->constructor() && !arguments->empty())
{
vector<string> params;
for (size_t i = 0; i < arguments->size(); ++i)
params.emplace_back(generator.evaluateExpression(
*(arguments->at(i)),
*(baseContract->constructor()->parameters()[i]->type())
).commaSeparatedList());
constructorParams[baseContract] = joinHumanReadable(params);
}
}
if (constructor)
return {generator.code(), constructorParams};
}
string IRGenerator::initStateVariables(ContractDefinition const& _contract)
{
IRGeneratorForStatements generator{m_context, m_utils};
for (VariableDeclaration const* variable: _contract.stateVariables())
if (!variable->isConstant() && !variable->immutable())
generator.initializeStateVar(*variable);
return generator.code();
}
void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contract)
{
auto listAllParams = [&](
map<ContractDefinition const*, string> const& baseParams) -> string
{
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
unsigned paramVars = make_shared<TupleType>(constructor->functionType(false)->parameterTypes())->sizeOnStack();
vector<string> params;
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
if (baseParams.count(contract))
params.emplace_back(baseParams.at(contract));
return joinHumanReadable(params);
};
Whiskers t(R"X(
let programSize := datasize("<object>")
let argSize := sub(codesize(), programSize)
map<ContractDefinition const*, string> baseConstructorParams;
for (size_t i = 0; i < _contract.annotation().linearizedBaseContracts.size(); ++i)
{
ContractDefinition const* contract = _contract.annotation().linearizedBaseContracts[i];
baseConstructorParams.erase(contract);
let memoryDataOffset := <allocate>(argSize)
codecopy(memoryDataOffset, programSize, argSize)
m_context.functionCollector().createFunction(implicitConstructorName(*contract), [&]() {
Whiskers t(R"(
function <functionName>(<params><comma><baseParams>) {
<evalBaseArguments>
<?hasNextConstructor> <nextConstructor>(<nextParams>) </hasNextConstructor>
<initStateVariables>
<userDefinedConstructorBody>
}
)");
string params;
if (contract->constructor())
for (ASTPointer<VariableDeclaration> const& varDecl: contract->constructor()->parameters())
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params);
string baseParamsString = listAllParams(baseConstructorParams);
t("baseParams", baseParamsString);
t("comma", !params.empty() && !baseParamsString.empty() ? ", " : "");
t("functionName", implicitConstructorName(*contract));
pair<string, map<ContractDefinition const*, string>> evaluatedArgs = evaluateConstructorArguments(*contract);
baseConstructorParams.insert(evaluatedArgs.second.begin(), evaluatedArgs.second.end());
t("evalBaseArguments", evaluatedArgs.first);
if (i < _contract.annotation().linearizedBaseContracts.size() - 1)
{
t("hasNextConstructor", true);
ContractDefinition const* nextContract = _contract.annotation().linearizedBaseContracts[i + 1];
t("nextConstructor", implicitConstructorName(*nextContract));
t("nextParams", listAllParams(baseConstructorParams));
}
else
t("hasNextConstructor", false);
t("initStateVariables", initStateVariables(*contract));
t("userDefinedConstructorBody", contract->constructor() ? generate(contract->constructor()->body()) : "");
<assignToParams> <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
<constructorName>(<params>)
)X");
t("object", creationObjectName(_contract));
t("allocate", m_utils.allocationFunction());
t("assignToParams", paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ");
t("params", suffixedVariableNameList("param_", 0, paramVars));
t("abiDecode", abiFunctions.tupleDecoder(constructor->functionType(false)->parameterTypes(), true));
t("constructorName", m_context.enqueueFunctionForCodeGeneration(*constructor));
out << t.render();
return t.render();
});
}
return out.str();
}
string IRGenerator::deployCode(ContractDefinition const& _contract)
@ -304,7 +394,7 @@ string IRGenerator::deployCode(ContractDefinition const& _contract)
codecopy(0, dataoffset("<object>"), datasize("<object>"))
return(0, datasize("<object>"))
)X");
t("object", runtimeObjectName(_contract));
t("object", m_context.runtimeObjectName(_contract));
return t.render();
}
@ -313,14 +403,9 @@ string IRGenerator::callValueCheck()
return "if callvalue() { revert(0, 0) }";
}
string IRGenerator::creationObjectName(ContractDefinition const& _contract)
string IRGenerator::implicitConstructorName(ContractDefinition const& _contract)
{
return _contract.name() + "_" + to_string(_contract.id());
}
string IRGenerator::runtimeObjectName(ContractDefinition const& _contract)
{
return _contract.name() + "_" + to_string(_contract.id()) + "_deployed";
return "constructor_" + _contract.name() + "_" + to_string(_contract.id());
}
string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)

View File

@ -50,10 +50,16 @@ public:
/// Generates and returns the IR code, in unoptimized and optimized form
/// (or just pretty-printed, depending on the optimizer settings).
std::pair<std::string, std::string> run(ContractDefinition const& _contract);
std::pair<std::string, std::string> run(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
);
private:
std::string generate(ContractDefinition const& _contract);
std::string generate(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
);
std::string generate(Block const& _block);
/// Generates code for all the functions from the function generation queue.
@ -67,12 +73,27 @@ private:
/// Generates code that assigns the initial value of the respective type.
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
std::string constructorCode(ContractDefinition const& _contract);
/// Generates implicit constructors for all contracts in the inheritance hierarchy of
/// @a _contract
/// If there are user defined constructors, their body will be included in implicit constructors body.
void generateImplicitConstructors(ContractDefinition const& _contract);
/// Evaluates constructor's arguments for all base contracts (listed in inheritance specifiers) of
/// @a _contract
/// @returns Pair of expressions needed to evaluate params and list of parameters in a map contract -> params
std::pair<std::string, std::map<ContractDefinition const*, std::string>> evaluateConstructorArguments(
ContractDefinition const& _contract
);
/// Initializes state variables of
/// @a _contract
/// @returns Source code to initialize state variables
std::string initStateVariables(ContractDefinition const& _contract);
std::string deployCode(ContractDefinition const& _contract);
std::string callValueCheck();
std::string creationObjectName(ContractDefinition const& _contract);
std::string runtimeObjectName(ContractDefinition const& _contract);
std::string implicitConstructorName(ContractDefinition const& _contract);
std::string dispatchRoutine(ContractDefinition const& _contract);

View File

@ -169,6 +169,14 @@ void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _va
assign(m_context.localVariable(_varDecl), zero);
}
IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType)
{
_expression.accept(*this);
IRVariable variable{m_context.newYulVariable(), _targetType};
define(variable, _expression);
return variable;
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (Expression const* expression = _varDeclStatement.initialValue())
@ -559,9 +567,20 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]);
}
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
if (auto expressionType = dynamic_cast<TypeType const*>(memberAccess->expression().annotation().type))
if (auto contractType = dynamic_cast<ContractType const*>(expressionType->actualType()))
solUnimplementedAssert(
!contractType->contractDefinition().isLibrary() || functionType->kind() == FunctionType::Kind::Internal,
"Only internal function calls implemented for libraries"
);
solUnimplementedAssert(!functionType->bound(), "");
switch (functionType->kind())
{
case FunctionType::Kind::Declaration:
solAssert(false, "Attempted to generate code for calling a function definition.");
break;
case FunctionType::Kind::Internal:
{
vector<string> args;
@ -571,32 +590,60 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
else
args.emplace_back(convert(*arguments[i], *parameterTypes[i]).commaSeparatedList());
if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
optional<FunctionDefinition const*> functionDef;
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
{
solAssert(!functionType->bound(), "");
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
solUnimplementedAssert(!functionType->bound(), "Internal calls to bound functions are not yet implemented for libraries and not allowed for contracts");
functionDef = dynamic_cast<FunctionDefinition const*>(memberAccess->annotation().referencedDeclaration);
if (functionDef.value() != nullptr)
solAssert(functionType->declaration() == *memberAccess->annotation().referencedDeclaration, "");
else
{
define(_functionCall) <<
m_context.enqueueFunctionForCodeGeneration(
functionDef->resolveVirtual(m_context.mostDerivedContract())
) <<
"(" <<
joinHumanReadable(args) <<
")\n";
return;
solAssert(dynamic_cast<VariableDeclaration const*>(memberAccess->annotation().referencedDeclaration), "");
solAssert(!functionType->hasDeclaration(), "");
}
}
else if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
{
solAssert(!functionType->bound(), "");
define(_functionCall) <<
// NOTE: internalDispatch() takes care of adding the function to function generation queue
m_context.internalDispatch(
TupleType(functionType->parameterTypes()).sizeOnStack(),
TupleType(functionType->returnParameterTypes()).sizeOnStack()
) <<
"(" <<
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
joinHumanReadablePrefixed(args) <<
")\n";
if (auto unresolvedFunctionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
{
functionDef = &unresolvedFunctionDef->resolveVirtual(m_context.mostDerivedContract());
solAssert(functionType->declaration() == *identifier->annotation().referencedDeclaration, "");
}
else
{
functionDef = nullptr;
solAssert(dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration), "");
solAssert(!functionType->hasDeclaration(), "");
}
}
else
// Not a simple expression like x or A.x
functionDef = nullptr;
solAssert(functionDef.has_value(), "");
solAssert(functionDef.value() == nullptr || functionDef.value()->isImplemented(), "");
if (functionDef.value() != nullptr)
define(_functionCall) <<
m_context.enqueueFunctionForCodeGeneration(*functionDef.value()) <<
"(" <<
joinHumanReadable(args) <<
")\n";
else
define(_functionCall) <<
// NOTE: internalDispatch() takes care of adding the function to function generation queue
m_context.internalDispatch(
TupleType(functionType->parameterTypes()).sizeOnStack(),
TupleType(functionType->returnParameterTypes()).sizeOnStack()
) <<
"(" <<
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
joinHumanReadablePrefixed(args) <<
")\n";
break;
}
case FunctionType::Kind::External:
@ -716,6 +763,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
"))\n";
break;
}
case FunctionType::Kind::ECRecover:
case FunctionType::Kind::SHA256:
case FunctionType::Kind::RIPEMD160:
{
solAssert(!_functionCall.annotation().tryCall, "");
appendExternalFunctionCall(_functionCall, arguments);
break;
}
case FunctionType::Kind::ArrayPop:
{
auto const& memberAccessExpression = dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression();
@ -759,6 +814,100 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
break;
}
case FunctionType::Kind::MetaType:
{
break;
}
case FunctionType::Kind::GasLeft:
{
define(_functionCall) << "gas()\n";
break;
}
case FunctionType::Kind::Selfdestruct:
{
solAssert(arguments.size() == 1, "");
define(_functionCall) << "selfdestruct(" << expressionAsType(*arguments.front(), *parameterTypes.front()) << ")\n";
break;
}
case FunctionType::Kind::Log0:
case FunctionType::Kind::Log1:
case FunctionType::Kind::Log2:
case FunctionType::Kind::Log3:
case FunctionType::Kind::Log4:
{
unsigned logNumber = int(functionType->kind()) - int(FunctionType::Kind::Log0);
solAssert(arguments.size() == logNumber + 1, "");
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
string indexedArgs;
for (unsigned arg = 0; arg < logNumber; ++arg)
indexedArgs += ", " + expressionAsType(*arguments[arg + 1], *(parameterTypes[arg + 1]));
Whiskers templ(R"({
let <pos> := <freeMemory>
let <end> := <encode>(<pos>, <nonIndexedArgs>)
<log>(<pos>, sub(<end>, <pos>) <indexedArgs>)
})");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("freeMemory", freeMemory());
templ("encode", abi.tupleEncoder({arguments.front()->annotation().type}, {parameterTypes.front()}));
templ("nonIndexedArgs", IRVariable(*arguments.front()).commaSeparatedList());
templ("log", "log" + to_string(logNumber));
templ("indexedArgs", indexedArgs);
m_code << templ.render();
break;
}
case FunctionType::Kind::Creation:
{
solAssert(!functionType->gasSet(), "Gas limit set for contract creation.");
solAssert(
functionType->returnParameterTypes().size() == 1,
"Constructor should return only one type"
);
TypePointers argumentTypes;
string constructorParams;
for (ASTPointer<Expression const> const& arg: arguments)
{
argumentTypes.push_back(arg->annotation().type);
constructorParams += ", " + IRVariable{*arg}.commaSeparatedList();
}
ContractDefinition const* contract =
&dynamic_cast<ContractType const&>(*functionType->returnParameterTypes().front()).contractDefinition();
m_context.subObjectsCreated().insert(contract);
Whiskers t(R"(
let <memPos> := <allocateTemporaryMemory>()
let <memEnd> := add(<memPos>, datasize("<object>"))
if or(gt(<memEnd>, 0xffffffffffffffff), lt(<memEnd>, <memPos>)) { revert(0, 0) }
datacopy(<memPos>, dataoffset("<object>"), datasize("<object>"))
<memEnd> := <abiEncode>(<memEnd><constructorParams>)
<?saltSet>
let <retVars> := create2(<value>, <memPos>, sub(<memEnd>, <memPos>), <salt>)
<!saltSet>
let <retVars> := create(<value>, <memPos>, sub(<memEnd>, <memPos>))
</saltSet>
<releaseTemporaryMemory>()
)");
t("memPos", m_context.newYulVariable());
t("memEnd", m_context.newYulVariable());
t("allocateTemporaryMemory", m_utils.allocationTemporaryMemoryFunction());
t("releaseTemporaryMemory", m_utils.releaseTemporaryMemoryFunction());
t("object", m_context.creationObjectName(*contract));
t("abiEncode",
m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(),false)
);
t("constructorParams", constructorParams);
t("value", functionType->valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
t("saltSet", functionType->saltSet());
if (functionType->saltSet())
t("salt", IRVariable(_functionCall.expression()).part("salt").name());
t("retVars", IRVariable(_functionCall).commaSeparatedList());
m_code << t.render();
break;
}
default:
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
}
@ -916,6 +1065,15 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{
solUnimplementedAssert(false, "");
}
else if (member == "interfaceId")
{
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
uint64_t result{0};
for (auto const& function: contract.interfaceFunctionList(false))
result ^= fromBigEndian<uint64_t>(function.first.ref());
define(_memberAccess) << formatNumber(u256{result} << (256 - 32)) << "\n";
}
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
{
// no-op
@ -1069,7 +1227,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
solAssert(holds_alternative<yul::Block>(modified), "");
// Do not provide dialect so that we get the full type information.
m_code << yul::AsmPrinter()(std::get<yul::Block>(std::move(modified))) << "\n";
m_code << yul::AsmPrinter()(std::get<yul::Block>(modified)) << "\n";
return false;
}
@ -1271,9 +1429,9 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
handleVariableReference(*varDecl, _identifier);
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
else if (dynamic_cast<ContractDefinition const*>(declaration))
{
solUnimplementedAssert(!contract->isLibrary(), "Libraries not yet supported.");
// no-op
}
else if (dynamic_cast<EventDefinition const*>(declaration))
{
@ -1365,7 +1523,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
for (auto const& arg: _arguments)
{
argumentTypes.emplace_back(&type(*arg));
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
if (IRVariable(*arg).type().sizeOnStack() > 0)
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
}
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
@ -1383,7 +1542,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
solUnimplementedAssert(!funType.isBareCall(), "");
Whiskers templ(R"(
<?checkExistence>
if iszero(extcodesize(<address>)) { revert(0, 0) }
@ -1391,8 +1549,18 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
// storage for arguments and returned data
let <pos> := <freeMemory>
mstore(<pos>, <shl28>(<funId>))
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
<?bareCall>
<!bareCall>
mstore(<pos>, <shl28>(<funId>))
</bareCall>
let <end> := <encodeArgs>(
<?bareCall>
<pos>
<!bareCall>
add(<pos>, 4)
</bareCall>
<argumentString>
)
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
<?noTryCall>
@ -1414,14 +1582,25 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
)");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("bareCall", funType.isBareCall());
if (_functionCall.annotation().tryCall)
templ("success", m_context.trySuccessConditionVariable(_functionCall));
else
templ("success", m_context.newYulVariable());
templ("freeMemory", freeMemory());
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
templ("address", IRVariable(_functionCall.expression()).part("address").name());
if (!funType.isBareCall())
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
if (funKind == FunctionType::Kind::ECRecover)
templ("address", "1");
else if (funKind == FunctionType::Kind::SHA256)
templ("address", "2");
else if (funKind == FunctionType::Kind::RIPEMD160)
templ("address", "3");
else
templ("address", IRVariable(_functionCall.expression()).part("address").name());
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
// This ensures it can catch badly formatted input from external calls.
@ -1453,9 +1632,15 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
// but all parameters of ecrecover are value types anyway.
encodeInPlace = false;
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
solUnimplementedAssert(!encodeInPlace, "");
solUnimplementedAssert(funType.padArguments(), "");
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
solUnimplementedAssert(encodeInPlace == !funType.padArguments(), "");
if (encodeInPlace)
{
solUnimplementedAssert(!encodeForLibraryCall, "");
templ("encodeArgs", abi.tupleEncoderPacked(argumentTypes, funType.parameterTypes()));
}
else
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
templ("argumentString", argumentString);
// Output data will replace input data, unless we have ECRecover (then, output

View File

@ -51,6 +51,9 @@ public:
/// Generates code to initialize the given local variable.
void initializeLocalVar(VariableDeclaration const& _varDecl);
/// Calculates expression's value and returns variable where it was stored
IRVariable evaluateExpression(Expression const& _expression, Type const& _to);
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Conditional const& _conditional) override;
bool visit(Assignment const& _assignment) override;

View File

@ -29,8 +29,7 @@ class Expression;
/**
* An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression
* of a specific S
* olidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
* of a specific Solidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
* Otherwise the set of yul variables it refers to is (recursively) determined by @see ``Type::stackItems()``.
* For example, an IRVariable referring to a dynamically sized calldata array will consist of two parts named
* ``offset`` and ``length``, whereas an IRVariable referring to a statically sized calldata type, a storage reference

View File

@ -304,7 +304,10 @@ void BMC::endVisit(UnaryOperation const& _op)
{
SMTEncoder::endVisit(_op);
if (_op.annotation().type->category() == Type::Category::RationalNumber)
if (
_op.annotation().type->category() == Type::Category::RationalNumber ||
_op.annotation().type->category() == Type::Category::FixedPoint
)
return;
switch (_op.getOperator())

View File

@ -194,7 +194,8 @@ void SMTEncoder::inlineModifierInvocation(ModifierInvocation const* _invocation,
pushCallStack({_definition, _invocation});
if (auto modifier = dynamic_cast<ModifierDefinition const*>(_definition))
{
modifier->body().accept(*this);
if (modifier->isImplemented())
modifier->body().accept(*this);
popCallStack();
}
else if (auto function = dynamic_cast<FunctionDefinition const*>(_definition))
@ -457,6 +458,9 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
createExpr(_op);
if (_op.annotation().type->category() == Type::Category::FixedPoint)
return;
switch (_op.getOperator())
{
case Token::Not: // !

View File

@ -69,8 +69,14 @@ SortPointer smtSort(frontend::Type const& _type)
}
else
{
solAssert(isArray(_type.category()), "");
auto arrayType = dynamic_cast<frontend::ArrayType const*>(&_type);
frontend::ArrayType const* arrayType = nullptr;
if (auto const* arr = dynamic_cast<frontend::ArrayType const*>(&_type))
arrayType = arr;
else if (auto const* slice = dynamic_cast<frontend::ArraySliceType const*>(&_type))
arrayType = &slice->arrayType();
else
solAssert(false, "");
solAssert(arrayType, "");
return make_shared<ArraySort>(SortProvider::intSort, smtSortAbstractFunction(*arrayType->baseType()));
}
@ -297,7 +303,8 @@ bool isMapping(frontend::Type::Category _category)
bool isArray(frontend::Type::Category _category)
{
return _category == frontend::Type::Category::Array ||
_category == frontend::Type::Category::StringLiteral;
_category == frontend::Type::Category::StringLiteral ||
_category == frontend::Type::Category::ArraySlice;
}
bool isTuple(frontend::Type::Category _category)

View File

@ -237,8 +237,8 @@ z3::sort Z3Interface::z3Sort(Sort const& _sort)
z3::func_decl tupleConstructor = m_context.tuple_sort(
tupleSort.name.c_str(),
tupleSort.members.size(),
&cMembers[0],
&sorts[0],
cMembers.data(),
sorts.data(),
projs
);
return tupleConstructor.range();

View File

@ -1129,15 +1129,21 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
if (!_contract.canBeDeployed())
return;
map<ContractDefinition const*, string const> otherYulSources;
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
if (!compiledContract.yulIR.empty())
return;
string dependenciesSource;
for (auto const* dependency: _contract.annotation().contractDependencies)
{
generateIR(*dependency);
otherYulSources.emplace(dependency, m_contracts.at(dependency->fullyQualifiedName()).yulIR);
}
IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings);
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract);
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract, otherYulSources);
}
void CompilerStack::generateEwasm(ContractDefinition const& _contract)
@ -1265,6 +1271,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
{
details["yulDetails"] = Json::objectValue;
details["yulDetails"]["stackAllocation"] = m_optimiserSettings.optimizeStackAllocation;
details["yulDetails"]["optimizerSteps"] = m_optimiserSettings.yulOptimiserSteps;
}
meta["settings"]["optimizer"]["details"] = std::move(details);

View File

@ -23,12 +23,31 @@
#pragma once
#include <cstddef>
#include <string>
namespace solidity::frontend
{
struct OptimiserSettings
{
static char constexpr DefaultYulOptimiserSteps[] =
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse
"["
"xarrscLM" // Turn into SSA and simplify
"cCTUtTOntnfDIul" // Perform structural simplification
"Lcul" // Simplify again
"Vcul jj" // Reverse SSA
// should have good "compilability" property here.
"eul" // Run functional expression inliner
"xarulrul" // Prune a bit more in SSA
"xarrcL" // Turn into SSA again and simplify
"gvif" // Run full inliner
"CTUcarrLsTOtfDncarrIulc" // SSA plus simplify
"]"
"jmuljuljul VcTOcul jmul"; // Make source short and pretty
/// No optimisations at all - not recommended.
static OptimiserSettings none()
{
@ -74,6 +93,7 @@ struct OptimiserSettings
runConstantOptimiser == _other.runConstantOptimiser &&
optimizeStackAllocation == _other.optimizeStackAllocation &&
runYulOptimiser == _other.runYulOptimiser &&
yulOptimiserSteps == _other.yulOptimiserSteps &&
expectedExecutionsPerDeployment == _other.expectedExecutionsPerDeployment;
}
@ -95,6 +115,11 @@ struct OptimiserSettings
bool optimizeStackAllocation = false;
/// Yul optimiser with default settings. Will only run on certain parts of the code for now.
bool runYulOptimiser = false;
/// Sequence of optimisation steps to be performed by Yul optimiser.
/// Note that there are some hard-coded steps in the optimiser and you cannot disable
/// them just by setting this to an empty string. Set @a runYulOptimiser to false if you want
/// no optimisations.
std::string yulOptimiserSteps = DefaultYulOptimiserSteps;
/// This specifies an estimate on how often each opcode in this assembly will be executed,
/// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage.
size_t expectedExecutionsPerDeployment = 200;

View File

@ -25,6 +25,7 @@
#include <libsolidity/ast/ASTJsonConverter.h>
#include <libyul/AssemblyStack.h>
#include <libyul/Exceptions.h>
#include <libyul/optimiser/Suite.h>
#include <liblangutil/SourceReferenceFormatter.h>
#include <libevmasm/Instruction.h>
#include <libsolutil/JSON.h>
@ -402,6 +403,33 @@ std::optional<Json::Value> checkOptimizerDetail(Json::Value const& _details, std
return {};
}
std::optional<Json::Value> checkOptimizerDetailSteps(Json::Value const& _details, std::string const& _name, string& _setting)
{
if (_details.isMember(_name))
{
if (_details[_name].isString())
{
try
{
yul::OptimiserSuite::validateSequence(_details[_name].asString());
}
catch (yul::OptimizerException const& _exception)
{
return formatFatalError(
"JSONError",
"Invalid optimizer step sequence in \"settings.optimizer.details." + _name + "\": " + _exception.what()
);
}
_setting = _details[_name].asString();
}
else
return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be a string");
}
return {};
}
std::optional<Json::Value> checkMetadataKeys(Json::Value const& _input)
{
if (_input.isObject())
@ -511,10 +539,12 @@ boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Valu
if (!settings.runYulOptimiser)
return formatFatalError("JSONError", "\"Providing yulDetails requires Yul optimizer to be enabled.");
if (auto result = checkKeys(details["yulDetails"], {"stackAllocation"}, "settings.optimizer.details.yulDetails"))
if (auto result = checkKeys(details["yulDetails"], {"stackAllocation", "optimizerSteps"}, "settings.optimizer.details.yulDetails"))
return *result;
if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation))
return *error;
if (auto error = checkOptimizerDetailSteps(details["yulDetails"], "optimizerSteps", settings.yulOptimiserSteps))
return *error;
}
}
return { std::move(settings) };

View File

@ -595,13 +595,13 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinition()
ASTPointer<Block> block;
nodeFactory.markEndPosition();
if (m_scanner->currentToken() != Token::Semicolon)
if (m_scanner->currentToken() == Token::Semicolon)
m_scanner->next();
else
{
block = parseBlock();
nodeFactory.setEndPositionFromNode(block);
}
else
m_scanner->next(); // just consume the ';'
return nodeFactory.createNode<FunctionDefinition>(
name,
header.visibility,
@ -851,9 +851,16 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
break;
}
ASTPointer<Block> block;
nodeFactory.markEndPosition();
if (m_scanner->currentToken() != Token::Semicolon)
{
block = parseBlock();
nodeFactory.setEndPositionFromNode(block);
}
else
m_scanner->next(); // just consume the ';'
ASTPointer<Block> block = parseBlock();
nodeFactory.setEndPositionFromNode(block);
return nodeFactory.createNode<ModifierDefinition>(name, documentation, parameters, isVirtual, overrides, block);
}

View File

@ -132,7 +132,11 @@ string Whiskers::replace(
map<string, vector<StringMap>> const& _listParameters
)
{
static regex listOrTag("<(" + paramRegex() + ")>|<#(" + paramRegex() + ")>((?:.|\\r|\\n)*?)</\\2>|<\\?(" + paramRegex() + ")>((?:.|\\r|\\n)*?)(<!\\4>((?:.|\\r|\\n)*?))?</\\4>");
static regex listOrTag(
"<(" + paramRegex() + ")>|"
"<#(" + paramRegex() + ")>((?:.|\\r|\\n)*?)</\\2>|"
"<\\?(\\+?" + paramRegex() + ")>((?:.|\\r|\\n)*?)(<!\\4>((?:.|\\r|\\n)*?))?</\\4>"
);
return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string
{
string tagName(_match[1]);
@ -164,12 +168,26 @@ string Whiskers::replace(
else
{
assertThrow(!conditionName.empty(), WhiskersError, "");
assertThrow(
_conditions.count(conditionName),
WhiskersError, "Condition parameter " + conditionName + " not set."
);
bool conditionValue = false;
if (conditionName[0] == '+')
{
string tag = conditionName.substr(1);
assertThrow(
_parameters.count(tag),
WhiskersError, "Tag " + tag + " used as condition but was not set."
);
conditionValue = !_parameters.at(tag).empty();
}
else
{
assertThrow(
_conditions.count(conditionName),
WhiskersError, "Condition parameter " + conditionName + " not set."
);
conditionValue = _conditions.at(conditionName);
}
return replace(
_conditions.at(conditionName) ? _match[5] : _match[7],
conditionValue ? _match[5] : _match[7],
_parameters,
_conditions,
_listParameters

View File

@ -59,6 +59,9 @@ DEV_SIMPLE_EXCEPTION(WhiskersError);
* - Condition parameter: <?name>...<!name>...</name>, where "<!name>" is optional
* replaced (and recursively expanded) by the first part if the condition is true
* and by the second (or empty string if missing) if the condition is false
* - Conditional string parameter: <?+name>...<!+name>...</+name>
* Works similar to a conditional parameter where the checked condition is
* that the regular (string) parameter called "name" is non-empty.
* - List parameter: <#list>...</list>
* The part between the tags is repeated as often as values are provided
* in the mapping. Each list element can have its own parameter -> value mapping.

View File

@ -183,7 +183,8 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
dialect,
meter.get(),
_object,
m_optimiserSettings.optimizeStackAllocation
m_optimiserSettings.optimizeStackAllocation,
m_optimiserSettings.yulOptimiserSteps
);
}

View File

@ -59,6 +59,13 @@ string solidity::yul::reindent(string const& _code)
for (string& line: lines)
boost::trim(line);
// Reduce multiple consecutive empty lines.
lines = fold(lines, vector<string>{}, [](auto&& _lines, auto&& _line) {
if (!(_line.empty() && !_lines.empty() && _lines.back().empty()))
_lines.emplace_back(std::move(_line));
return std::move(_lines);
});
stringstream out;
int depth = 0;

View File

@ -67,6 +67,7 @@
#include <libsolutil/CommonData.h>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
using namespace std;
@ -78,6 +79,7 @@ void OptimiserSuite::run(
GasMeter const* _meter,
Object& _object,
bool _optimizeStackAllocation,
string const& _optimisationSequence,
set<YulString> const& _externallyUsedIdentifiers
)
{
@ -93,25 +95,12 @@ void OptimiserSuite::run(
OptimiserSuite suite(_dialect, reservedIdentifiers, Debug::None, ast);
suite.runSequence(
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse
"("
"xarrscLM" // Turn into SSA and simplify
"cCTUtTOntnfDIul" // Perform structural simplification
"Lcul" // Simplify again
"Vcul jj" // Reverse SSA
// Some steps depend on properties ensured by FunctionHoister, FunctionGrouper and
// ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely.
suite.runSequence("fgo", ast);
// should have good "compilability" property here.
"eul" // Run functional expression inliner
"xarulrul" // Prune a bit more in SSA
"xarrcL" // Turn into SSA again and simplify
"gvif" // Run full inliner
"CTUcarrLsTOtfDncarrIulc" // SSA plus simplify
")"
"jmuljuljul VcTOcul jmul", // Make source short and pretty
ast
);
// Now the user-supplied part
suite.runSequence(_optimisationSequence, ast);
// This is a tuning parameter, but actually just prevents infinite loops.
size_t stackCompressorMaxIterations = 16;
@ -235,6 +224,12 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
{VarDeclInitializer::name, 'd'},
};
yulAssert(lookupTable.size() == allSteps().size(), "");
yulAssert((
util::convertContainer<set<char>>(string(NonStepAbbreviations)) -
util::convertContainer<set<char>>(lookupTable | boost::adaptors::map_values)
).size() == string(NonStepAbbreviations).size(),
"Step abbreviation conflicts with a character reserved for another syntactic element"
);
return lookupTable;
}
@ -246,32 +241,44 @@ map<char, string> const& OptimiserSuite::stepAbbreviationToNameMap()
return lookupTable;
}
void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast)
void OptimiserSuite::validateSequence(string const& _stepAbbreviations)
{
string input = _stepAbbreviations;
boost::remove_erase(input, ' ');
boost::remove_erase(input, '\n');
bool insideLoop = false;
for (char abbreviation: input)
for (char abbreviation: _stepAbbreviations)
switch (abbreviation)
{
case '(':
assertThrow(!insideLoop, OptimizerException, "Nested parentheses not supported");
case ' ':
case '\n':
break;
case '[':
assertThrow(!insideLoop, OptimizerException, "Nested brackets are not supported");
insideLoop = true;
break;
case ')':
assertThrow(insideLoop, OptimizerException, "Unbalanced parenthesis");
case ']':
assertThrow(insideLoop, OptimizerException, "Unbalanced brackets");
insideLoop = false;
break;
default:
yulAssert(
string(NonStepAbbreviations).find(abbreviation) == string::npos,
"Unhandled syntactic element in the abbreviation sequence"
);
assertThrow(
stepAbbreviationToNameMap().find(abbreviation) != stepAbbreviationToNameMap().end(),
OptimizerException,
"Invalid optimisation step abbreviation"
"'"s + abbreviation + "' is not a valid step abbreviation"
);
}
assertThrow(!insideLoop, OptimizerException, "Unbalanced parenthesis");
assertThrow(!insideLoop, OptimizerException, "Unbalanced brackets");
}
void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast)
{
validateSequence(_stepAbbreviations);
string input = _stepAbbreviations;
boost::remove_erase(input, ' ');
boost::remove_erase(input, '\n');
auto abbreviationsToSteps = [](string const& _sequence) -> vector<string>
{
@ -281,21 +288,21 @@ void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast)
return steps;
};
// The sequence has now been validated and must consist of pairs of segments that look like this: `aaa(bbb)`
// `aaa` or `(bbb)` can be empty. For example we consider a sequence like `fgo(aaf)Oo` to have
// four segments, the last of which is an empty parenthesis.
// The sequence has now been validated and must consist of pairs of segments that look like this: `aaa[bbb]`
// `aaa` or `[bbb]` can be empty. For example we consider a sequence like `fgo[aaf]Oo` to have
// four segments, the last of which is an empty bracket.
size_t currentPairStart = 0;
while (currentPairStart < input.size())
{
size_t openingParenthesis = input.find('(', currentPairStart);
size_t closingParenthesis = input.find(')', openingParenthesis);
size_t firstCharInside = (openingParenthesis == string::npos ? input.size() : openingParenthesis + 1);
yulAssert((openingParenthesis == string::npos) == (closingParenthesis == string::npos), "");
size_t openingBracket = input.find('[', currentPairStart);
size_t closingBracket = input.find(']', openingBracket);
size_t firstCharInside = (openingBracket == string::npos ? input.size() : openingBracket + 1);
yulAssert((openingBracket == string::npos) == (closingBracket == string::npos), "");
runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingParenthesis - currentPairStart)), _ast);
runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingParenthesis - firstCharInside)), _ast);
runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingBracket - currentPairStart)), _ast);
runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingBracket - firstCharInside)), _ast);
currentPairStart = (closingParenthesis == string::npos ? input.size() : closingParenthesis + 1);
currentPairStart = (closingBracket == string::npos ? input.size() : closingBracket + 1);
}
}

View File

@ -47,6 +47,10 @@ class OptimiserSuite
public:
static constexpr size_t MaxRounds = 12;
/// Special characters that do not represent optimiser steps but are allowed in abbreviation sequences.
/// Some of them (like whitespace) are ignored, others (like brackets) are a part of the syntax.
static constexpr char NonStepAbbreviations[] = " \n[]";
enum class Debug
{
None,
@ -58,9 +62,14 @@ public:
GasMeter const* _meter,
Object& _object,
bool _optimizeStackAllocation,
std::string const& _optimisationSequence,
std::set<YulString> const& _externallyUsedIdentifiers = {}
);
/// Ensures that specified sequence of step abbreviations is well-formed and can be executed.
/// @throw OptimizerException if the sequence is invalid
static void validateSequence(std::string const& _stepAbbreviations);
void runSequence(std::vector<std::string> const& _steps, Block& _ast);
void runSequence(std::string const& _stepAbbreviations, Block& _ast);
void runSequenceUntilStable(

View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -e
REPO_ROOT="$(dirname "$0")"/../..
REPO_ROOT=$(realpath "${REPO_ROOT}")
IGNORE_FILENAME="ignore.txt"
IGNORE_FILE="${REPO_ROOT}/scripts/chk_shellscripts/${IGNORE_FILENAME}"
FOUND_FILES_TMP=$(mktemp)
IGNORE_FILES_TMP=$(mktemp)
trap 'rm -f ${FOUND_FILES_TMP} ; rm -f ${IGNORE_FILES_TMP}' EXIT
sort < "${IGNORE_FILE}" >"${IGNORE_FILES_TMP}"
cd "${REPO_ROOT}"
find . -type f -name "*.sh" | sort >"${FOUND_FILES_TMP}"
SHELLCHECK=${SHELLCHECK:-"$(command -v -- shellcheck)"}
if [ ! -f "${SHELLCHECK}" ]; then
echo "error: shellcheck '${SHELLCHECK}' not found."
exit 1
fi
FILES=$(join -v2 "${IGNORE_FILES_TMP}" "${FOUND_FILES_TMP}")
# shellcheck disable=SC2086
"${SHELLCHECK}" ${FILES[*]}

View File

@ -0,0 +1,50 @@
./test/docsCodeStyle.sh
./test/cmdlineTests.sh
./test/externalTests.sh
./test/externalTests/common.sh
./test/externalTests/gnosis.sh
./test/externalTests/zeppelin.sh
./test/externalTests/colony.sh
./test/externalTests/solc-js/solc-js.sh
./scripts/common.sh
./scripts/isoltest.sh
./scripts/get_version.sh
./scripts/soltest.sh
./scripts/test_emscripten.sh
./scripts/wasm-rebuild/docker-scripts/rebuild_tags.sh
./scripts/wasm-rebuild/docker-scripts/rebuild_current.sh
./scripts/wasm-rebuild/docker-scripts/genbytecode.sh
./scripts/wasm-rebuild/docker-scripts/patch.sh
./scripts/wasm-rebuild/rebuild.sh
./scripts/build_emscripten.sh
./scripts/travis-emscripten/build_emscripten.sh
./scripts/travis-emscripten/install_deps.sh
./scripts/travis-emscripten/publish_binary.sh
./scripts/docker_build.sh
./scripts/docs_version_pragma_check.sh
./scripts/uniqueErrors.sh
./scripts/report_errors.sh
./scripts/tests.sh
./scripts/docker_deploy.sh
./scripts/bytecodecompare/storebytecode.sh
./scripts/deps-ppa/static_z3.sh
./scripts/ASTImportTest.sh
./scripts/install_static_z3.sh
./scripts/install_obsolete_jsoncpp_1_7_4.sh
./scripts/install_deps.sh
./scripts/build.sh
./scripts/check_style.sh
./scripts/run_proofs.sh
./scripts/common_cmdline.sh
./scripts/docker_deploy_manual.sh
./scripts/endToEndExtraction/create_traces.sh
./scripts/release.sh
./scripts/download_ossfuzz_corpus.sh
./scripts/release_ppa.sh
./scripts/install_cmake.sh
./scripts/release_emscripten.sh
./scripts/create_source_tarball.sh
./scripts/docs.sh
./.circleci/soltest.sh
./.circleci/osx_install_dependencies.sh
./.circleci/soltest_all.sh

View File

@ -37,6 +37,7 @@
#include <libsolidity/interface/StorageLayout.h>
#include <libyul/AssemblyStack.h>
#include <libyul/optimiser/Suite.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/GasMeter.h>
@ -145,6 +146,7 @@ static string const g_strOpcodes = "opcodes";
static string const g_strOptimize = "optimize";
static string const g_strOptimizeRuns = "optimize-runs";
static string const g_strOptimizeYul = "optimize-yul";
static string const g_strYulOptimizations = "yul-optimizations";
static string const g_strOutputDir = "output-dir";
static string const g_strOverwrite = "overwrite";
static string const g_strRevertStrings = "revert-strings";
@ -781,7 +783,6 @@ Allowed options)",
"Import ASTs to be compiled, assumes input holds the AST in compact JSON format. "
"Supported Inputs is the output of the --standard-json or the one produced by --combined-json ast,compact-format"
)
(
g_argAssemble.c_str(),
"Switch to assembly mode, ignoring all options except --machine, --yul-dialect and --optimize and assumes input is assembly."
@ -835,7 +836,12 @@ Allowed options)",
"Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage."
)
(g_strOptimizeYul.c_str(), "Legacy option, ignored. Use the general --optimize to enable Yul optimizer.")
(g_strNoOptimizeYul.c_str(), "Disable Yul optimizer in Solidity.");
(g_strNoOptimizeYul.c_str(), "Disable Yul optimizer in Solidity.")
(
g_strYulOptimizations.c_str(),
po::value<string>()->value_name("steps"),
"Forces yul optimizer to use the specified sequence of optimization steps instead of the built-in one."
);
desc.add(optimizerOptions);
po::options_description outputComponents("Output Components");
outputComponents.add_options()
@ -947,7 +953,10 @@ bool CommandLineInterface::processInput()
"ReadFile callback used as callback kind " +
_kind
));
auto path = boost::filesystem::path(_path);
string validPath = _path;
if (validPath.find("file://") == 0)
validPath.erase(0, 7);
auto path = boost::filesystem::path(validPath);
auto canonicalPath = boost::filesystem::weakly_canonical(path);
bool isAllowed = false;
for (auto const& allowedDir: m_allowedDirectories)
@ -1168,6 +1177,26 @@ bool CommandLineInterface::processInput()
settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as<unsigned>();
if (m_args.count(g_strNoOptimizeYul))
settings.runYulOptimiser = false;
if (m_args.count(g_strYulOptimizations))
{
if (!settings.runYulOptimiser)
{
serr() << "--" << g_strYulOptimizations << " is invalid if Yul optimizer is disabled" << endl;
return false;
}
try
{
yul::OptimiserSuite::validateSequence(m_args[g_strYulOptimizations].as<string>());
}
catch (yul::OptimizerException const& _exception)
{
serr() << "Invalid optimizer step sequence in --" << g_strYulOptimizations << ": " << _exception.what() << endl;
return false;
}
settings.yulOptimiserSteps = m_args[g_strYulOptimizations].as<string>();
}
settings.optimizeStackAllocation = settings.runYulOptimiser;
m_compiler->setOptimiserSettings(settings);

View File

@ -19,6 +19,7 @@
#include <test/TestCase.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string.hpp>
#include <stdexcept>
#include <iostream>
@ -60,6 +61,18 @@ void TestCase::expect(string::iterator& _it, string::iterator _end, string::valu
++_it;
}
void TestCase::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const
{
stringstream output(_output);
string line;
while (getline(output, line))
if (line.empty())
// Avoid trailing spaces.
_stream << boost::trim_right_copy(_linePrefix) << endl;
else
_stream << _linePrefix << line << endl;
}
EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(string const& _filename):
TestCase(_filename)
{

View File

@ -93,6 +93,8 @@ protected:
++_it;
}
void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const;
TestCaseReader m_reader;
bool m_shouldRun = true;
};

View File

@ -126,7 +126,7 @@ function test_solc_behaviour()
# Remove trailing empty lines. Needs a line break to make OSX sed happy.
sed -i.bak -e '1{/^$/d
}' "$stderr_path"
rm "$stderr_path.bak"
rm "$stderr_path.bak" "$stdout_path.bak"
fi
# Remove path to cpp file
sed -i.bak -e 's/^\(Exception while assembling:\).*/\1/' "$stderr_path"
@ -175,6 +175,8 @@ function test_solc_behaviour()
exit 1
fi
fi
rm -f "$stdout_path" "$stderr_path"
}

View File

@ -0,0 +1 @@
--ir-optimized --optimize

View File

@ -0,0 +1,7 @@
pragma solidity >=0.6.0;
contract C {
constructor() public {}
}
contract D is C {
}

View File

@ -0,0 +1,55 @@
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "C_6" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("C_6_deployed")
codecopy(0, dataoffset("C_6_deployed"), _1)
return(0, _1)
}
}
object "C_6_deployed" {
code {
{
mstore(64, 128)
revert(0, 0)
}
}
}
}
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "D_9" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("D_9_deployed")
codecopy(0, dataoffset("D_9_deployed"), _1)
return(0, _1)
}
}
object "D_9_deployed" {
code {
{
mstore(64, 128)
revert(0, 0)
}
}
}
}

View File

@ -0,0 +1 @@
--ir-optimized --optimize

View File

@ -0,0 +1,5 @@
Warning: Unused local variable.
--> ir_compiler_subobjects/input.sol:6:9:
|
6 | C c = new C();
| ^^^

View File

@ -0,0 +1,8 @@
pragma solidity >=0.6.0;
contract C {}
contract D {
function f() public {
C c = new C();
}
}

View File

@ -0,0 +1,96 @@
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "C_2" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("C_2_deployed")
codecopy(0, dataoffset("C_2_deployed"), _1)
return(0, _1)
}
}
object "C_2_deployed" {
code {
{
mstore(64, 128)
revert(0, 0)
}
}
}
}
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "D_13" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("D_13_deployed")
codecopy(0, dataoffset("D_13_deployed"), _1)
return(0, _1)
}
}
object "D_13_deployed" {
code {
{
mstore(64, 128)
if iszero(lt(calldatasize(), 4))
{
let _1 := 0
if eq(0x26121ff0, shr(224, calldataload(_1)))
{
if callvalue() { revert(_1, _1) }
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
let _2 := datasize("C_2")
let _3 := add(128, _2)
if or(gt(_3, 0xffffffffffffffff), lt(_3, 128)) { revert(_1, _1) }
datacopy(128, dataoffset("C_2"), _2)
pop(create(_1, 128, _2))
return(allocateMemory(_1), _1)
}
}
revert(0, 0)
}
function allocateMemory(size) -> memPtr
{
memPtr := mload(64)
let newFreePtr := add(memPtr, size)
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
mstore(64, newFreePtr)
}
}
object "C_2" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("C_2_deployed")
codecopy(0, dataoffset("C_2_deployed"), _1)
return(0, _1)
}
}
object "C_2_deployed" {
code {
{
mstore(64, 128)
revert(0, 0)
}
}
}
}
}
}

View File

@ -0,0 +1,50 @@
Warning: Source file does not specify required compiler version!
--> message_format/input.sol
Warning: Unused local variable.
--> message_format/input.sol:9:27:
|
9 | function f() public { int x; }
| ^^^^^
Warning: Unused local variable.
--> message_format/input.sol:10:27:
|
10 | function g() public { int x; }
| ^^^^^
Warning: Unused local variable.
--> message_format/input.sol:99:14:
|
99 | /**/ int a; /**/
| ^^^^^
Warning: Unused local variable.
--> message_format/input.sol:100:14:
|
100 | /**/ int b; /**/
| ^^^^^
Warning: Unused local variable.
--> message_format/input.sol:101:14:
|
101 | /**/ int c; /**/
| ^^^^^
Warning: Function state mutability can be restricted to pure
--> message_format/input.sol:9:5:
|
9 | function f() public { int x; }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Warning: Function state mutability can be restricted to pure
--> message_format/input.sol:10:5:
|
10 | function g() public { int x; }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Warning: Function state mutability can be restricted to pure
--> message_format/input.sol:11:5:
|
11 | function h() public {
| ^ (Relevant source part starts here and spans across multiple lines).

View File

@ -0,0 +1,103 @@
// checks that error messages around power-or-10 lines are formatted correctly
contract C {
function f() public { int x; }
function g() public { int x; }
function h() public {
/**/ int a; /**/
/**/ int b; /**/
/**/ int c; /**/
}
}

View File

@ -1,6 +1,8 @@
{"contracts":{"A":{"C":{"ewasm":{"wast":"(module
;; sub-module \"C_2_deployed\" will be encoded as custom section in binary here, but is skipped in text mode.
(import \"ethereum\" \"codeCopy\" (func $eth.codeCopy (param i32 i32 i32)))
(import \"ethereum\" \"revert\" (func $eth.revert (param i32 i32)))
(import \"ethereum\" \"getCallValue\" (func $eth.getCallValue (param i32)))
(import \"ethereum\" \"finish\" (func $eth.finish (param i32 i32)))
(memory $memory (export \"memory\") 1)
(export \"main\" (func $main))
@ -9,27 +11,30 @@
(local $_1 i64)
(local $p i64)
(local $r i64)
(local $hi i64)
(local $hi_1 i64)
(local $y i64)
(local $hi_2 i64)
(local $_2 i64)
(local $z1 i64)
(local $z2 i64)
(local $z3 i64)
(local $_3 i64)
(local.set $_1 (i64.const 0))
(local.set $p (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 64)))
(local.set $r (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $p)) (i32.wrap_i64 (i64.const 64)))))
(if (i64.ne (i64.extend_i32_u (i32.lt_u (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (local.get $p)))) (i64.const 0)) (then
(unreachable)))
(local.set $hi (i64.shl (call $endian_swap_16 (local.get $_1)) (i64.const 16)))
(local.set $hi_1 (i64.shl (i64.or (local.get $hi) (call $endian_swap_16 (i64.shr_u (local.get $_1) (i64.const 16)))) (i64.const 32)))
(local.set $y (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (local.get $_1) (i64.const 32)))))
(i64.store (i32.wrap_i64 (local.get $r)) (local.get $y))
(i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 8))))) (local.get $y))
(i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 16))))) (local.get $y))
(local.set $hi_2 (i64.shl (call $endian_swap_32 (i64.const 128)) (i64.const 32)))
(i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 24))))) (i64.or (local.get $hi_2) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32)))))
(local.set $_2 (datasize \"C_2_deployed\"))
(call $eth.codeCopy (i32.wrap_i64 (call $to_internal_i32ptr (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (dataoffset \"C_2_deployed\"))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_2))))
(call $eth.finish (i32.wrap_i64 (call $to_internal_i32ptr (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_2))))
(local.set $_2 (call $endian_swap (local.get $_1)))
(i64.store (i32.wrap_i64 (local.get $r)) (local.get $_2))
(i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 8))))) (local.get $_2))
(i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 16))))) (local.get $_2))
(i64.store (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (local.get $r)) (i32.wrap_i64 (i64.const 24))))) (call $endian_swap (i64.const 128)))
(call $eth.getCallValue (i32.wrap_i64 (i64.const 0)))
(local.set $z1 (call $endian_swap (i64.load (i32.wrap_i64 (i64.const 0)))))
(local.set $z2 (call $endian_swap (i64.load (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (i64.const 0)) (i32.wrap_i64 (i64.const 8))))))))
(local.set $z3 (call $endian_swap (i64.load (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (i64.const 0)) (i32.wrap_i64 (i64.const 16))))))))
(if (i64.ne (i64.extend_i32_u (i32.eqz (i32.wrap_i64 (i64.extend_i32_u (i64.eqz (i64.or (i64.or (local.get $z1) (local.get $z2)) (i64.or (local.get $z3) (call $endian_swap (i64.load (i32.wrap_i64 (i64.extend_i32_u (i32.add (i32.wrap_i64 (i64.const 0)) (i32.wrap_i64 (i64.const 24)))))))))))))) (i64.const 0)) (then
(call $revert (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1))))
(local.set $_3 (datasize \"C_2_deployed\"))
(call $codecopy (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (dataoffset \"C_2_deployed\") (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_3))
(call $return (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_3))
)
(func $u256_to_i32
@ -62,6 +67,22 @@
(local.get $r)
)
(func $codecopy
(param $x1 i64)
(param $x2 i64)
(param $x3 i64)
(param $x4 i64)
(param $y1 i64)
(param $y2 i64)
(param $y3 i64)
(param $y4 i64)
(param $z1 i64)
(param $z2 i64)
(param $z3 i64)
(param $z4 i64)
(call $eth.codeCopy (i32.wrap_i64 (call $to_internal_i32ptr (local.get $x1) (local.get $x2) (local.get $x3) (local.get $x4))) (i32.wrap_i64 (call $u256_to_i32 (local.get $y1) (local.get $y2) (local.get $y3) (local.get $y4))) (i32.wrap_i64 (call $u256_to_i32 (local.get $z1) (local.get $z2) (local.get $z3) (local.get $z4))))
)
(func $endian_swap_16
(param $x i64)
(result i64)
@ -80,5 +101,39 @@
(local.get $y)
)
(func $endian_swap
(param $x i64)
(result i64)
(local $y i64)
(local $hi i64)
(local.set $hi (i64.shl (call $endian_swap_32 (local.get $x)) (i64.const 32)))
(local.set $y (i64.or (local.get $hi) (call $endian_swap_32 (i64.shr_u (local.get $x) (i64.const 32)))))
(local.get $y)
)
(func $return
(param $x1 i64)
(param $x2 i64)
(param $x3 i64)
(param $x4 i64)
(param $y1 i64)
(param $y2 i64)
(param $y3 i64)
(param $y4 i64)
(call $eth.finish (i32.wrap_i64 (call $to_internal_i32ptr (local.get $x1) (local.get $x2) (local.get $x3) (local.get $x4))) (i32.wrap_i64 (call $u256_to_i32 (local.get $y1) (local.get $y2) (local.get $y3) (local.get $y4))))
)
(func $revert
(param $x1 i64)
(param $x2 i64)
(param $x3 i64)
(param $x4 i64)
(param $y1 i64)
(param $y2 i64)
(param $y3 i64)
(param $y4 i64)
(call $eth.revert (i32.wrap_i64 (call $to_internal_i32ptr (local.get $x1) (local.get $x2) (local.get $x3) (local.get $x4))) (i32.wrap_i64 (call $u256_to_i32 (local.get $y1) (local.get $y2) (local.get $y3) (local.get $y4))))
)
)
"}}}},"sources":{"A":{"id":0}}}

View File

@ -8,8 +8,12 @@
object \"C_6\" {
code {
mstore(64, 128)
if callvalue() { revert(0, 0) }
constructor_C_6()
codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))
return(0, datasize(\"C_6_deployed\"))
function constructor_C_6()
{ }
}
object \"C_6_deployed\" {
code {

View File

@ -9,14 +9,16 @@
object \"C_6\" {
code {
mstore(64, 128)
if callvalue() { revert(0, 0) }
// Begin state variable initialization for contract \"C\" (0 variables)
// End state variable initialization for contract \"C\".
constructor_C_6()
codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))
return(0, datasize(\"C_6_deployed\"))
function constructor_C_6() {
}
}
object \"C_6_deployed\" {
@ -44,7 +46,6 @@ object \"C_6\" {
if iszero(calldatasize()) { }
revert(0, 0)
function abi_decode_tuple_(headStart, dataEnd) {
if slt(sub(dataEnd, headStart), 0) { revert(0, 0) }
@ -65,7 +66,6 @@ object \"C_6\" {
function fun_f_5() {
}
function shift_right_224_unsigned(value) -> newValue {
@ -76,7 +76,9 @@ object \"C_6\" {
}
}
}
}
"}}},"sources":{"A":{"id":0}}}

View File

@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "dhfoDgvulfnTUtnIf\n[ xarrscLM\n]\njmuljuljul VcTOcul jmul"
}
}
}
}
}

View File

@ -0,0 +1 @@
{"sources":{"A":{"id":0}}}

View File

@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "abcdefg{hijklmno}pqr[st]uvwxyz"
}
}
}
}
}

View File

@ -0,0 +1 @@
{"errors":[{"component":"general","formattedMessage":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": 'b' is not a valid step abbreviation","message":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": 'b' is not a valid step abbreviation","severity":"error","type":"JSONError"}]}

View File

@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "a[a][aa[aa]]a"
}
}
}
}
}

View File

@ -0,0 +1 @@
{"errors":[{"component":"general","formattedMessage":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Nested brackets are not supported","message":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Nested brackets are not supported","severity":"error","type":"JSONError"}]}

View File

@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": 42
}
}
}
}
}

View File

@ -0,0 +1 @@
{"errors":[{"component":"general","formattedMessage":"\"settings.optimizer.details.optimizerSteps\" must be a string","message":"\"settings.optimizer.details.optimizerSteps\" must be a string","severity":"error","type":"JSONError"}]}

View File

@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources":
{
"A":
{
"content": "pragma solidity >=0.0; contract C { function f() public pure {} }"
}
},
"settings":
{
"optimizer": {
"details": {
"yul": true,
"yulDetails": {
"optimizerSteps": "a[a]["
}
}
}
}
}

View File

@ -0,0 +1 @@
{"errors":[{"component":"general","formattedMessage":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Unbalanced brackets","message":"Invalid optimizer step sequence in \"settings.optimizer.details.optimizerSteps\": Unbalanced brackets","severity":"error","type":"JSONError"}]}

View File

@ -1,5 +1,5 @@
Warning: Source file does not specify required compiler version!
--> too_long_line/input.sol
--> too_long_line/input.sol
Error: Identifier not found or not unique.
--> too_long_line/input.sol:2:164:

View File

@ -1,5 +1,5 @@
Warning: Source file does not specify required compiler version!
--> too_long_line_both_sides_short/input.sol
--> too_long_line_both_sides_short/input.sol
Error: Identifier not found or not unique.
--> too_long_line_both_sides_short/input.sol:2:15:

View File

@ -1,5 +1,5 @@
Warning: Source file does not specify required compiler version!
--> too_long_line_edge_in/input.sol
--> too_long_line_edge_in/input.sol
Error: Identifier not found or not unique.
--> too_long_line_edge_in/input.sol:2:36:

View File

@ -1,5 +1,5 @@
Warning: Source file does not specify required compiler version!
--> too_long_line_edge_out/input.sol
--> too_long_line_edge_out/input.sol
Error: Identifier not found or not unique.
--> too_long_line_edge_out/input.sol:2:37:

View File

@ -1,5 +1,5 @@
Warning: Source file does not specify required compiler version!
--> too_long_line_left_short/input.sol
--> too_long_line_left_short/input.sol
Error: Identifier not found or not unique.
--> too_long_line_left_short/input.sol:2:15:

View File

@ -5,4 +5,4 @@ Error: No visibility specified. Did you intend to add "public"?
| ^ (Relevant source part starts here and spans across multiple lines).
Warning: Source file does not specify required compiler version!
--> too_long_line_multiline/input.sol
--> too_long_line_multiline/input.sol

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