mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into HEAD
This commit is contained in:
commit
1fe55370f4
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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 ) ;
|
||||
|
@ -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"``
|
||||
|
@ -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
191
docs/cheatsheet.rst
Normal 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``.
|
@ -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')
|
||||
|
||||
|
@ -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
6
docs/grammar.rst
Normal file
@ -0,0 +1,6 @@
|
||||
****************
|
||||
Language Grammar
|
||||
****************
|
||||
|
||||
.. literalinclude:: Solidity.g4
|
||||
:language: antlr
|
@ -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
|
||||
|
13
docs/internals/layout_in_calldata.rst
Normal file
13
docs/internals/layout_in_calldata.rst
Normal 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.
|
||||
|
39
docs/internals/layout_in_memory.rst
Normal file
39
docs/internals/layout_in_memory.rst
Normal 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
|
359
docs/internals/layout_in_storage.rst
Normal file
359
docs/internals/layout_in_storage.rst
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
71
docs/internals/optimiser.rst
Normal file
71
docs/internals/optimiser.rst
Normal 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;
|
62
docs/internals/source_mappings.rst
Normal file
62
docs/internals/source_mappings.rst
Normal 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;;``
|
47
docs/internals/variable_cleanup.rst
Normal file
47
docs/internals/variable_cleanup.rst
Normal 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 |
|
||||
+---------------+---------------+-------------------+
|
@ -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
|
@ -1,2 +1,2 @@
|
||||
sphinx_rtd_theme>=0.3.1
|
||||
pygments-lexer-solidity>=0.3.1
|
||||
pygments-lexer-solidity>=0.5.1
|
||||
|
@ -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 |
|
||||
+-----------------------------------+--------------------------------------+
|
||||
|
@ -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
|
@ -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.
|
||||
|
||||
|
38
docs/yul.rst
38
docs/yul.rst
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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"}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)));
|
||||
|
@ -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"))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.");
|
||||
|
@ -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:
|
||||
|
@ -502,6 +502,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
|
||||
&meter,
|
||||
_object,
|
||||
_optimiserSettings.optimizeStackAllocation,
|
||||
_optimiserSettings.yulOptimiserSteps,
|
||||
_externalIdentifiers
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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: // !
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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) };
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -183,7 +183,8 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
|
||||
dialect,
|
||||
meter.get(),
|
||||
_object,
|
||||
m_optimiserSettings.optimizeStackAllocation
|
||||
m_optimiserSettings.optimizeStackAllocation,
|
||||
m_optimiserSettings.yulOptimiserSteps
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
27
scripts/chk_shellscripts/chk_shellscripts.sh
Executable file
27
scripts/chk_shellscripts/chk_shellscripts.sh
Executable 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[*]}
|
50
scripts/chk_shellscripts/ignore.txt
Normal file
50
scripts/chk_shellscripts/ignore.txt
Normal 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
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
--ir-optimized --optimize
|
@ -0,0 +1,7 @@
|
||||
pragma solidity >=0.6.0;
|
||||
|
||||
contract C {
|
||||
constructor() public {}
|
||||
}
|
||||
contract D is C {
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
test/cmdlineTests/ir_compiler_subobjects/args
Normal file
1
test/cmdlineTests/ir_compiler_subobjects/args
Normal file
@ -0,0 +1 @@
|
||||
--ir-optimized --optimize
|
5
test/cmdlineTests/ir_compiler_subobjects/err
Normal file
5
test/cmdlineTests/ir_compiler_subobjects/err
Normal file
@ -0,0 +1,5 @@
|
||||
Warning: Unused local variable.
|
||||
--> ir_compiler_subobjects/input.sol:6:9:
|
||||
|
|
||||
6 | C c = new C();
|
||||
| ^^^
|
8
test/cmdlineTests/ir_compiler_subobjects/input.sol
Normal file
8
test/cmdlineTests/ir_compiler_subobjects/input.sol
Normal file
@ -0,0 +1,8 @@
|
||||
pragma solidity >=0.6.0;
|
||||
|
||||
contract C {}
|
||||
contract D {
|
||||
function f() public {
|
||||
C c = new C();
|
||||
}
|
||||
}
|
96
test/cmdlineTests/ir_compiler_subobjects/output
Normal file
96
test/cmdlineTests/ir_compiler_subobjects/output
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
test/cmdlineTests/message_format/err
Normal file
50
test/cmdlineTests/message_format/err
Normal 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).
|
103
test/cmdlineTests/message_format/input.sol
Normal file
103
test/cmdlineTests/message_format/input.sol
Normal 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; /**/
|
||||
}
|
||||
}
|
@ -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}}}
|
||||
|
@ -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 {
|
||||
|
@ -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}}}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
{"sources":{"A":{"id":0}}}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"}]}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"}]}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"}]}
|
@ -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]["
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"}]}
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user